一、前言
APU系統中CPU以串行執行代碼的方式完成操作,軟件方式很難做到精准計時,因此調用內部定時器硬件完成計時是更好的選擇。本文以定時器中斷方式控制LED周期性閃爍為例學習私有定時器的使用。同時學習如何將軟件程序與硬件比特流文件一起固化到SD卡中,實現上電自動配置與啟動自定義系統。
功能定義:通過定時器中斷實現與MIO連接的單個LED每200ms變化依次電平,即點亮,200ms后熄滅,200ms后再次點亮,周期往復。
硬件平台:米聯客Miz702N
軟件工具:VIVADO 2017.4+SDK
二、硬件系統搭建
私有定時器屬於APU內部專用硬件資源,無需在VIVADO中做出配置。由於需要將軟硬件系統固化到SD卡中,選擇與SD控制器連接的I/O。
根據原理圖,SD卡連接在MIO40~47,這也與UG585中的描述一致:
這里直接使用PWM產生呼吸燈效果工程中的硬件系統,可以更直觀地觀察出定時器控制LED與PWM控制LED互不影響。
依然是重新產生輸出文件、生成HDL Wrapper、RUN Synthesis、Run Implementation、Generate bitstream、export Hardware with bitfile、Launch SDK. 剩下的任務均在SDK中完成。
三、軟件設計
關於私有定時器使用方式,xilinx同樣提供了文檔和示例程序。
軟件代碼中使用的定時器相關函數均來自示例程序。使用私有定時器第一步當然是初始化配置,老套路調用XScuTimer_LookupConfig和XScuTimer_CfgInitialize兩個函數。為了保證LED周期性閃爍,必須使能定時器的自動重載,這樣每當計數器計數完成后會重新計數。之后最重要的是向定時器裝載寄存器寫入計數周期數值。實際上私有定時器是一個遞減計數器,當從最大值遞減到0時刻會產生定時器中斷。如和將所要定時的時間長度換算為裝載計數器周期數值呢?
很簡單,n=t/T=t*f即可算出裝載數值,其中n、t和T分別指所要定時的時間和定時器工作時鍾周期。因為定時器工作時鍾頻率一直是CPU工作時鍾的一半,在本系統中即為333MHz。這個n=200*10^(-3)*333*10^6=666*10^5。計數器是N-1~0的計數方式,裝載值在n的基礎上減1,對應的十六進制數值是0x3F83C3F。
裝載完畢后調用XScuTimer_Start定時器隨即開始工作。最后在定時器中斷回調函數中對MIO進行反轉操作就可以滿足功能預期。另外,對之前PWM實現呼吸燈效果的工程做些改善,軟件程序如下:

1 /* 2 * main.c 3 * 4 * Created on: 2020年2月22日 5 * Author: s 6 */ 7 8 9 #include "environment.h" 10 11 12 13 int main() 14 { 15 int Status; 16 u8 i=0; 17 18 freq_step_value = 10; 19 20 Status = gpiops_initialize(&GpioPs,GPIOPS_DEVICE_ID); 21 if (Status != XST_SUCCESS) { 22 return XST_FAILURE; 23 } 24 25 Status = gpio_initialize(&Gpio,GPIO_DEVICE_ID); 26 if (Status != XST_SUCCESS) { 27 return XST_FAILURE; 28 } 29 30 Status = timer_initialize(&TimerInstance,TIMER_DEVICE_ID); 31 if (Status != XST_SUCCESS) { 32 return XST_FAILURE; 33 } 34 /* 35 * Set the direction for the pin to be output and 36 * Enable the Output enable for the LED Pin. 37 */ 38 gpiops_setOutput(&GpioPs,MIO_OUT_PIN_INDEX); 39 40 for(i=0;i<LOOP_NUM;i++){ 41 gpiops_setOutput(&GpioPs,EMIO_OUT_PIN_BASE_INDEX+i); 42 } 43 44 gpio_setDirect(&Gpio, 1,GPIO_CHANNEL1); 45 46 Status = setupIntSystem(&Intc,&Gpio,&TimerInstance, 47 INTC_GPIO_INTERRUPT_ID,TIMER_IRPT_INTR); 48 if (Status != XST_SUCCESS) { 49 return XST_FAILURE; 50 } 51 52 /* 53 * Enable Auto reload mode. 54 */ 55 XScuTimer_EnableAutoReload(&TimerInstance); 56 57 /* 58 * Load the timer counter register. 59 */ 60 XScuTimer_LoadTimer(&TimerInstance, TIMER_LOAD_VALUE); 61 62 /* 63 * Start the timer counter and then wait for it 64 * to timeout a number of times. 65 */ 66 XScuTimer_Start(&TimerInstance); 67 68 69 70 Status = pwm_led_setFreqStep(freq_step_value); 71 if (Status != XST_SUCCESS) { 72 return XST_FAILURE; 73 } 74 75 printf("Initialization finish.\n"); 76 77 while(1){ 78 79 for(i=0;i<LOOP_NUM;i++){ 80 if(int_flag == 0) 81 { 82 gpiops_outputValue(&GpioPs, EMIO_OUT_PIN_BASE_INDEX+i, 0x1); 83 usleep(200*1000); 84 gpiops_outputValue(&GpioPs, EMIO_OUT_PIN_BASE_INDEX+i, 0x0); 85 } 86 else 87 { 88 gpiops_outputValue(&GpioPs, EMIO_OUT_PIN_BASE_INDEX+LOOP_NUM-1-i, 0x1); 89 usleep(200*1000); 90 gpiops_outputValue(&GpioPs, EMIO_OUT_PIN_BASE_INDEX+LOOP_NUM-1-i, 0x0); 91 } 92 } 93 94 95 } 96 return 0; 97 } 98 99 100 101 int setupIntSystem(XScuGic *IntcInstancePtr,XGpio *gpioInstancePtr, 102 XScuTimer * TimerInstancePtr,u32 gpio_IntrId,u32 timer_IntrId) 103 { 104 int Result; 105 /* 106 * Initialize the interrupt controller driver so that it is ready to 107 * use. 108 */ 109 110 Result = gic_initialize(&Intc,INTC_DEVICE_ID); 111 if (Result != XST_SUCCESS) { 112 return XST_FAILURE; 113 } 114 115 XScuGic_SetPriorityTriggerType(IntcInstancePtr, gpio_IntrId, 116 0xA0, 0x3); 117 118 /* 119 * Connect the interrupt handler that will be called when an 120 * interrupt occurs for the device. 121 */ 122 Result = XScuGic_Connect(IntcInstancePtr, gpio_IntrId, 123 (Xil_ExceptionHandler)GpioHandler, gpioInstancePtr); 124 if (Result != XST_SUCCESS) { 125 return Result; 126 } 127 128 Result = XScuGic_Connect(IntcInstancePtr, timer_IntrId, 129 (Xil_ExceptionHandler)TimerIntrHandler, 130 (void *)TimerInstancePtr); 131 if (Result != XST_SUCCESS) { 132 return Result; 133 } 134 135 /* Enable the interrupt for the GPIO device.*/ 136 XScuGic_Enable(IntcInstancePtr, gpio_IntrId); 137 138 /* 139 * Enable the GPIO channel interrupts so that push button can be 140 * detected and enable interrupts for the GPIO device 141 */ 142 XGpio_InterruptEnable(gpioInstancePtr,GPIO_CHANNEL1); 143 XGpio_InterruptGlobalEnable(gpioInstancePtr); 144 145 /* 146 * Enable the interrupt for the device. 147 */ 148 XScuGic_Enable(IntcInstancePtr, timer_IntrId); 149 XScuTimer_EnableInterrupt(TimerInstancePtr); 150 151 /* 152 * Initialize the exception table and register the interrupt 153 * controller handler with the exception table 154 */ 155 exception_enable(&Intc); 156 157 IntrFlag = 0; 158 159 return XST_SUCCESS; 160 } 161 162 void GpioHandler(void *CallbackRef) 163 { 164 XGpio *GpioPtr = (XGpio *)CallbackRef; 165 u32 gpio_inputValue; 166 167 168 /* Clear the Interrupt */ 169 XGpio_InterruptClear(GpioPtr, GPIO_CHANNEL1); 170 printf("gpio interrupt.\n"); 171 172 //IntrFlag = 1; 173 gpio_inputValue = gpio_readValue(GpioPtr, 1); 174 switch(gpio_inputValue) 175 { 176 case 30: 177 //printf("button up\n"); 178 usleep(5); 179 gpio_inputValue = gpio_readValue(GpioPtr, 1); 180 if(gpio_inputValue == 30){ 181 freq_step_value = freq_step_value < FREQ_STEP_MAX ? 182 freq_step_value+10 : freq_step_value; 183 printf("%d\n",freq_step_value); 184 pwm_led_setFreqStep(freq_step_value); 185 } 186 break; 187 case 29: 188 //printf("button center\n"); 189 usleep(5); 190 gpio_inputValue = gpio_readValue(GpioPtr, 1); 191 if(gpio_inputValue == 29){ 192 freq_step_value = FREQ_STEP_SET_VALUE; 193 pwm_led_setFreqStep(freq_step_value); 194 } 195 break; 196 case 27: 197 //printf("button left\n"); 198 usleep(5); 199 gpio_inputValue = gpio_readValue(GpioPtr, 1); 200 if(gpio_inputValue == 27) 201 int_flag = 0; 202 break; 203 case 23: 204 //printf("button right\n"); 205 usleep(5); 206 gpio_inputValue = gpio_readValue(GpioPtr, 1); 207 if(gpio_inputValue == 23) 208 int_flag = 1; 209 break; 210 case 15: 211 //print("button down\n"); 212 usleep(5); 213 gpio_inputValue = gpio_readValue(GpioPtr, 1); 214 if(gpio_inputValue == 15){ 215 freq_step_value = freq_step_value > FREQ_STEP_MIN ? 216 freq_step_value-10 : freq_step_value; 217 printf("%d\n",freq_step_value); 218 pwm_led_setFreqStep(freq_step_value); 219 } 220 break; 221 } 222 223 } 224 225 void TimerIntrHandler(void *CallBackRef) 226 { 227 XScuTimer *TimerInstancePtr = (XScuTimer *) CallBackRef; 228 229 XScuTimer_ClearInterruptStatus(TimerInstancePtr); 230 231 gpiops_outputValue(&GpioPs, MIO_OUT_PIN_INDEX, sys_led_out); 232 sys_led_out = sys_led_out == 0x0 ? 0x1 : 0x0; 233 }

1 /* 2 * timer.h 3 * 4 * Created on: 2020年3月5日 5 * Author: s 6 */ 7 8 #ifndef SRC_TIMER_H_ 9 #define SRC_TIMER_H_ 10 11 #include "xscutimer.h" 12 13 #define TIMER_DEVICE_ID XPAR_XSCUTIMER_0_DEVICE_ID 14 #define TIMER_IRPT_INTR XPAR_SCUTIMER_INTR 15 16 //333*n(ms)*10^3-1 = 333*5*1000-1 = 1664999 0x1967E7 17 #define TIMER_LOAD_VALUE 0x3F83C3F 18 19 int timer_initialize(XScuTimer * TimerInstancePtr,u16 TimerDeviceId); 20 21 22 #endif /* SRC_TIMER_H_ */

1 /* 2 * timer.c 3 * 4 * Created on: 2020年3月5日 5 * Author: s 6 */ 7 8 9 #include "timer.h" 10 11 int timer_initialize(XScuTimer * TimerInstancePtr,u16 TimerDeviceId) 12 { 13 XScuTimer_Config *ConfigPtr; 14 /* 15 * Initialize the Scu Private Timer driver. 16 */ 17 ConfigPtr = XScuTimer_LookupConfig(TimerDeviceId); 18 19 /* 20 * This is where the virtual address would be used, this example 21 * uses physical address. 22 */ 23 return XScuTimer_CfgInitialize(TimerInstancePtr, ConfigPtr, 24 ConfigPtr->BaseAddr); 25 }
相比原來的程序,在GpioHandler中添加了對freq_step_value最值的限制以及按鍵消抖延時。
四、程序固化
本工程固化程序時要使用FAT文件系統,更改板級支持包設置,勾選xilffs庫並重新生成BSP。
在固化程序之前,了解CPU的啟動過程是非常必要的,這部分概念主要來自UG585文檔。在上電復位后,硬件邏輯會根據啟動模式引腳的高低電平選擇啟動方式。
硬件一些初始化操作后執行CPU內部一個ROM中的代碼來啟動整個系統,這個ROM的名字叫BootROM。BootROM中的程序是第一個被CPU執行的代碼,其主要任務是配置系統,並從boot device中拷貝系統鏡像到OCM,配置DDR操作。
Boot device可以是Quad-SPI,NAND,NOR或者SD卡。Boot device中存儲的是boot image,其由BootROM Header和FSBL以及User Code組成,當然也可包括用於配置PL的bitstream和軟件OS。軟件boot分為三個階段:
其中FSBL起到組織作用,將PS部分軟件生成的ELF文件和PL部分硬件bit文件組合在一起。該文件利用xilinx的FSBL示例工程生成,用戶無需關注內部實現細節。
創建工程后會自動編譯並生成ELF文件。點擊工具欄xilinx -> create boot image。按照如下順序添加三個文件:fsdb.elf --> bit --> <user_code>.elf
create image后會生成對應的bin文件,也就是之前闡述的啟動鏡像。
我們將BOOT.bin文件拷貝到空的SD卡中,利用撥碼開關配置Boot Mode MIO Strapping Pins從SD卡啟動。上電后等待一段時間MIO連接的LED燈周期性閃爍,PWM呼吸燈頻率與流水燈方向根據按鍵變換,系統正常工作,完成了定時器中斷應用和程序固化。