电子工程师技术服务社区
公告
登录
|
注册
首页
技术问答
厂商活动
正点原子
板卡试用
资源库
下载
文章
社区首页
文章
万字长文理解吃透 Zynq 的定时器们
分 享
扫描二维码分享
万字长文理解吃透 Zynq 的定时器们
定时器
李肖遥
关注
发布时间: 2020-09-23
丨
阅读: 1928
## 前言 在ZYNQ的体系结构中定时器太丰富了,而大量的教程中基本就只玩了私有定时器,可以中断就OK了。 其实在ZYNQ中定时器资源很丰富,每个CPU有自己的私有定时器和看门狗,有一个所有CPU共享的全局定时器和看门狗,两个三路定时器还有AXI_TIMER的IP可用,不过好像很少有博客把这些测试完。 最后一个AXI_TIMER还是出了名的不好用,所以我把私有定时器,全局定时器,三路定时器和AXI_TIMER都测一测,看看有什么坑,让大家少走弯路。 ## 私有的真是自己的? 先来看看ZYNQ的私有定时器,在UG585中是这样描述ZYNQ的私有定时器的: 每个CPU有自己的私有定时器和看门狗,中断号均为29,寄存器基址都是0xF8F00600,这让我觉得深深地不安啊!为啥不分开编址呢,两个CPU私有定时器的相关寄存器地址一模一样,中断号一模一样,虽然私有定时器中断是属于PPI私有中断但我觉得还是不大靠谱,所以需要验证一下。 ![](https://cf04.ickimg.com/bbsimages/202009/e128b4e97fa801f0a6778b6fcf77dbed.png) 在我的Block Design中使用了AXI_GPIO连接8位的LED灯,每4位连接一个通道。然后让两个CPU分别控制一个通道,根据自己私有定时器的定时间隔控制LED的闪烁。程序非常简单,两个CPU的程序除了CPU0唤醒CPU1的代码外几乎一模一样,不同的只是定时器的重装数值。 我设定CPU0的间隔是1s而CPU1的间隔是0.5s,下载bit后分别Debug两个CPU得到了我想要的结果,闪烁频率和我预计的没有出入。因此即使地址相同,中断号一样,但每个CPU只可能访问到自己的私有定时器,私有定时器的中断也只能发送到对应的CPU上。 **测试代码如下:** **AMP_TIMER_CPU0.c的代码:** ``` #include "xparameters.h" #include "xscutimer.h" #include "xscugic.h" #include "xgpio.h" #include "xil_io.h" /@@************************** Constant Definitions *****************************/ #define TIMER_DEVICE_ID XPAR_XSCUTIMER_0_DEVICE_ID #define TIMER_IRPT_INTR XPAR_SCUTIMER_INTR #define TIMER_LOAD_VALUE 333333333 //1s #define LEDS_DEVICE_ID XPAR_AXI_GPIO_0_DEVICE_ID #define INTC XScuGic #define INTC_DEVICE_ID XPAR_PS7_SCUGIC_0_DEVICE_ID #define INTC_HANDLER XScuGic_InterruptHandler #define CPU1STARTADR 0x20000000 #define CPU1_START_UP_REG 0xFFFFFFF0U //set CPU1 start DDR address in this register #define sev() __asm__("sev") #define SEG_CHANNEL 1 #define LED_CHANNEL 2 /@@************************** Function Prototypes ******************************/ static int SetupIntrSystem(INTC *IntcInstancePtr); static void TimerIntrHandler(void *CallBackRef); static void RemapOCMAddr(); /@@************************** Variable Definitions *****************************/ XScuTimer TimerInstance; /@@* Cortex A9 Scu Private Timer Instance */ XGpio Gpio_LEDS; /@@* The Instance of the GPIO Driver */ INTC IntcInstancePtr; /@@* The Instance of the Interrupt Controller Driver */ volatile int TimerExpired; /@@*****************************************************************************/ /@@** * * 该函数将禁止OCM最后64KB的Cache属性并唤醒CPU1 * ******************************************************************************/ static void RemapOCMAddr() { //Disable cache on OCM Xil_SetTlbAttributes(0xFFFF0000,0x14de2); // S=b1 TEX=b100 AP=b11, Domain=b1111, C=b0, B=b0 print("CPU0: writing startaddress for cpu1\n\r"); Xil_Out32(CPU1_START_UP_REG, CPU1STARTADR); //CPU1STARTADR=0xFFFFFFF0, CPU1STARTADR=0x20000000); dmb(); //waits until write has finished print("CPU0: sending the SEV to wake up CPU1\n\r"); sev(); dmb(); //waits until write has finished } /@@*****************************************************************************/ /@@** * * 该函数是定时器中断服务函数 * @param CallBackRef is a pointer to the callback function. * * @return None. * * @note None. * ******************************************************************************/ static void TimerIntrHandler(void *CallbackRef) { XScuTimer *TimerInstancePtr = (XScuTimer *) CallbackRef; if (XScuTimer_IsExpired(TimerInstancePtr)) { XScuTimer_ClearInterruptStatus(TimerInstancePtr); TimerExpired=~TimerExpired; XGpio_DiscreteWrite(&Gpio_LEDS, SEG_CHANNEL, TimerExpired); } } /@@*****************************************************************************/ /@@** * * This function setups initializes the interrupt system. * * @param IntcInstancePtr is a pointer to the instance of the Intc driver. * @param PeriphInstancePtr is a pointer to the instance of peripheral driver. * @param IntrId is the Interrupt Id of the peripheral interrupt * * @return XST_SUCCESS if successful, otherwise XST_FAILURE. * * @note None. * ******************************************************************************/ static int SetupIntrSystem(INTC *IntcInstancePtr) { int Status; TimerExpired=0; /@@* Initialize the GPIO driver */ Status = XGpio_Initialize(&Gpio_LEDS, LEDS_DEVICE_ID); if (Status != XST_SUCCESS) { xil_printf("Gpio Initialization Failed\r\n"); return XST_FAILURE; } /@@* Set the direction for all signals as inputs except the LED output */ XGpio_SetDataDirection(&Gpio_LEDS, SEG_CHANNEL, 0);//set all pins as output XScuGic_Config *IntcConfig; /@@* * Initialize the interrupt controller driver so that it is ready to * use. */ IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID); if (NULL == IntcConfig) { return XST_FAILURE; } Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig, IntcConfig->CpuBaseAddress); if (Status != XST_SUCCESS) { return XST_FAILURE; } /@@* * Initialize the exception table */ Xil_ExceptionInit(); /@@* * Register the interrupt controller handler with the exception table */ Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)INTC_HANDLER, IntcInstancePtr); /@@* * Enable non-critical exceptions */ Xil_ExceptionEnable(); /@@* * Initialize the Scu Private Timer driver. */ XScuTimer_Config *ConfigPtr; ConfigPtr = XScuTimer_LookupConfig(TIMER_DEVICE_ID); /@@* * This is where the virtual address would be used, this example * uses physical address. */ Status = XScuTimer_CfgInitialize(&TimerInstance, &ConfigPtr, ConfigPtr->BaseAddr); if (Status != XST_SUCCESS) { return XST_FAILURE; } /@@* * Perform a self-test to ensure that the hardware was built correctly. */ Status = XScuTimer_SelfTest(&TimerInstance); if (Status != XST_SUCCESS) { return XST_FAILURE; } Status = XScuGic_Connect(IntcInstancePtr, TIMER_IRPT_INTR, (Xil_ExceptionHandler)TimerIntrHandler, &TimerInstance);//(void *) if (Status != XST_SUCCESS) { return Status; } XScuGic_Enable(IntcInstancePtr, TIMER_IRPT_INTR); //XScuGic_InterruptMaptoCpu(IntcInstancePtr,0,TIMER_IRPT_INTR); /@@* * Enable Auto reload mode. */ XScuTimer_EnableAutoReload(&TimerInstance); /@@* * Load the timer counter register. */ XScuTimer_LoadTimer(&TimerInstance, TIMER_LOAD_VALUE); /@@* * Start the timer counter and then wait for it * to timeout a number of times. */ XScuTimer_Start(&TimerInstance); XScuTimer_EnableInterrupt(&TimerInstance); return XST_SUCCESS; } main() { RemapOCMAddr(); SetupIntrSystem(&IntcInstancePtr); while(1); } ``` **AMP_TIMER_CPU1.c的代码:** ``` #include "xparameters.h" #include "xscutimer.h" #include "xscugic.h" #include "xgpio.h" #include "xil_io.h" /@@************************** Constant Definitions *****************************/ #define TIMER_DEVICE_ID XPAR_XSCUTIMER_0_DEVICE_ID #define TIMER_IRPT_INTR XPAR_SCUTIMER_INTR #define TIMER_LOAD_VALUE 166666667 //0.5s #define LEDS_DEVICE_ID XPAR_AXI_GPIO_0_DEVICE_ID #define INTC XScuGic #define INTC_DEVICE_ID XPAR_PS7_SCUGIC_0_DEVICE_ID #define INTC_HANDLER XScuGic_InterruptHandler #define SEG_CHANNEL 2 #define LED_CHANNEL 1 /@@************************** Function Prototypes ******************************/ static int SetupIntrSystem(INTC *IntcInstancePtr); static void TimerIntrHandler(void *CallBackRef); /@@************************** Variable Definitions *****************************/ XScuTimer TimerInstance; /@@* Cortex A9 Scu Private Timer Instance */ XGpio Gpio_LEDS; /@@* The Instance of the GPIO Driver */ INTC IntcInstancePtr; /@@* The Instance of the Interrupt Controller Driver */ volatile int TimerExpired; /@@*****************************************************************************/ /@@** * * 该函数是定时器中断服务函数 * @param CallBackRef is a pointer to the callback function. * * @return None. * * @note None. * ******************************************************************************/ static void TimerIntrHandler(void *CallbackRef) { XScuTimer *TimerInstancePtr = (XScuTimer *) CallbackRef; if (XScuTimer_IsExpired(TimerInstancePtr)) { XScuTimer_ClearInterruptStatus(TimerInstancePtr); TimerExpired=~TimerExpired; XGpio_DiscreteWrite(&Gpio_LEDS, SEG_CHANNEL, TimerExpired); } } /@@*****************************************************************************/ /@@** * * This function setups initializes the interrupt system. * * @param IntcInstancePtr is a pointer to the instance of the Intc driver. * @param PeriphInstancePtr is a pointer to the instance of peripheral driver. * @param IntrId is the Interrupt Id of the peripheral interrupt * * @return XST_SUCCESS if successful, otherwise XST_FAILURE. * * @note None. * ******************************************************************************/ static int SetupIntrSystem(INTC *IntcInstancePtr) { int Status; TimerExpired=0; /@@* Initialize the GPIO driver */ Status = XGpio_Initialize(&Gpio_LEDS, LEDS_DEVICE_ID); if (Status != XST_SUCCESS) { xil_printf("Gpio Initialization Failed\r\n"); return XST_FAILURE; } /@@* Set the direction for all signals as inputs except the LED output */ XGpio_SetDataDirection(&Gpio_LEDS, SEG_CHANNEL, 0);//set all pins as output XScuGic_Config *IntcConfig; /@@* * Initialize the interrupt controller driver so that it is ready to * use. */ IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID); if (NULL == IntcConfig) { return XST_FAILURE; } Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig, IntcConfig->CpuBaseAddress); if (Status != XST_SUCCESS) { return XST_FAILURE; } /@@* * Initialize the exception table */ Xil_ExceptionInit(); /@@* * Register the interrupt controller handler with the exception table */ Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)INTC_HANDLER, IntcInstancePtr); /@@* * Enable non-critical exceptions */ /@@* * Initialize the Scu Private Timer driver. */ XScuTimer_Config *ConfigPtr; ConfigPtr = XScuTimer_LookupConfig(TIMER_DEVICE_ID); /@@* * This is where the virtual address would be used, this example * uses physical address. */ Status = XScuTimer_CfgInitialize(&TimerInstance, &ConfigPtr, ConfigPtr->BaseAddr); if (Status != XST_SUCCESS) { return XST_FAILURE; } /@@* * Perform a self-test to ensure that the hardware was built correctly. */ Status = XScuTimer_SelfTest(&TimerInstance); if (Status != XST_SUCCESS) { return XST_FAILURE; } Status = XScuGic_Connect(IntcInstancePtr, TIMER_IRPT_INTR, (Xil_ExceptionHandler)TimerIntrHandler, &TimerInstance);//(void *) if (Status != XST_SUCCESS) { return Status; } XScuGic_Enable(IntcInstancePtr, TIMER_IRPT_INTR); //XScuGic_InterruptMaptoCpu(IntcInstancePtr,0,TIMER_IRPT_INTR); /@@* * Enable Auto reload mode. */ XScuTimer_EnableAutoReload(&TimerInstance); /@@* * Load the timer counter register. */ XScuTimer_LoadTimer(&TimerInstance, TIMER_LOAD_VALUE); /@@* * Start the timer counter and then wait for it * to timeout a number of times. */ XScuTimer_Start(&TimerInstance); XScuTimer_EnableInterrupt(&TimerInstance); Xil_ExceptionEnable(); return XST_SUCCESS; } main() { SetupIntrSystem(&IntcInstancePtr); while(1); } ``` ## 全局定时器是个坑? 翻开UG585,文档里头言之凿凿说有全局定时器,而且还有相关的几个寄存器: Global_Timer_Counter_Register0(全局定时器计数器低32位),地址0xF8F00200,复位值0,可读写。 ![](https://cf04.ickimg.com/bbsimages/202009/0ba03a1c6e0c0f744662a73a2167d9a0.png) Global_Timer_Counter_Register1(全局定时器计数器高32位),地址0xF8F00204,复位值0,可读写。 ![](https://cf04.ickimg.com/bbsimages/202009/bb3760c83f8b8e7477b4d5816044f140.png) Global_Timer_Control_Register(全局定时器控制寄存器),地址0xF8F00208,复位值0,可读写; ![](https://cf04.ickimg.com/bbsimages/202009/1607a30b98c985aac256a3eca507c65d.png) 其中[15:8]位即从低位数起的第二个字节为Prescaler(步距),该字节可用来延迟定时间隔,其计算公式为:(步距值+1)*(加载值+1)*(计数时钟周期); ![](https://cf04.ickimg.com/bbsimages/202009/18f2315a2873c6b32b31b54464170c81.png) 第3位为触发模式位为0则为单次触发,为1则为自动递增模式,该模式下每次计数器达到比较器的数值则比较器会自动加上递增寄存器的数值让定时间隔变得有周期性; ![](https://cf04.ickimg.com/bbsimages/202009/d4196e9e290525b4ef7a84ecbf2e16ab.png) 第2位为中断允许位,默认为0即不触发中断,为1时在中断状态寄存器置位时会触发中断; ![](https://cf04.ickimg.com/bbsimages/202009/03b212de43d919015dc31fc17625d813.png) 第1位为比较允许位,默认为0即不比较,为1时每当计数器数值达到比较器数值则会置位事件标志位; ![](https://cf04.ickimg.com/bbsimages/202009/939e857a741944a1e43d76c509f372f7.png) 第0位为定时器允许位,默认为0即不计数,为1时计数器在计数时钟下开始计数。 ![](https://cf04.ickimg.com/bbsimages/202009/01615199f148582b300cad508ac1e434.png) Global_Timer_Interrupt_Status_Register(中断状态寄存器),地址0xF8F0020C,复位值0,可读写; ![](https://cf04.ickimg.com/bbsimages/202009/0d9ef257ef848f9d268d03dc9162e513.png) 其中第0位为事件标志位,当计数器达到比较器数值时该位置1,需要手动清零,向该位写一个1就能清零(看清楚是写1不是写0)。 ![](https://cf04.ickimg.com/bbsimages/202009/01da1b0effb8187cfd0f4c4bc222adbf.png) Comparator_Value_Register0(比较器低32位),地址0xF8F00210,复位值0,可读写; ![](https://cf04.ickimg.com/bbsimages/202009/157754bc7e604b5d819d745e14a9d31a.png) Comparator_Value_Register1(比较器高32位),地址0xF8F00214,复位值0,可读写; ![](https://cf04.ickimg.com/bbsimages/202009/1c25052c9f20a2776a2049ae0329a1cc.png) Auto_increment_Register(自动递增寄存器),地址0xF8F00218,复位值0,可读写; ![](https://cf04.ickimg.com/bbsimages/202009/44f8f1ca2c584a45c9466ae60c625a38.png) ![](https://cf04.ickimg.com/bbsimages/202009/159a1adb779e4d8ec845cf4e358fce0e.png) 在自动递增模式下,每当计数器数值达到比较器数值,则比较器会自动加上自动递增寄存器的值以产生周期性中断。 ![](https://cf04.ickimg.com/bbsimages/202009/0f451a0b298d09080a54a63c7029501b.png) 一共7个寄存器,感觉不算很难,不过当你打开SDK再想看看对应的BSP文档时就傻眼了: ![](https://cf04.ickimg.com/bbsimages/202009/03e18b218a5b32a5529ce1f8fe1c1ea5.png) **现在才知道全局定时器是个坑!得想办法爬出来!** 本来还想参照私有定时器弄个结构体采用指针操作,定义可读性很高的函数,采用对象化的方法让代码能重用,看看时间又是半夜了,于是让各位失望了,这次得采用最原始暴力的寄存器地址操作了。 定义全局定时器的7个寄存器全部按照地址进行了宏定义,采用xil_io.h里的out32和in32两个函数进行读写操作: ``` #define Global_Timer_INTR XPAR_GLOBAL_TMR_INTR #define Global_Timer_Counter_Register0 XPAR_GLOBAL_TMR_BASEADDR+0x0U #define Global_Timer_Counter_Register1 XPAR_GLOBAL_TMR_BASEADDR+0x4U #define Global_Timer_Control_Register XPAR_GLOBAL_TMR_BASEADDR+0x8U #define Global_Timer_Interrupt_Status_Register XPAR_GLOBAL_TMR_BASEADDR+0xCU #define Comparator_Value_Register0 XPAR_GLOBAL_TMR_BASEADDR+0x10U #define Comparator_Value_Register1 XPAR_GLOBAL_TMR_BASEADDR+0x14U #define Auto_increment_Register XPAR_GLOBAL_TMR_BASEADDR+0x18U ``` **设定了两个寄存器读写函数:** ``` void GT_Write_Reg(u32 Reg_Addr,u32 Reg_Val) { Xil_Out32(Reg_Addr, Reg_Val); } u32 GT_Read_Reg(u32 Reg_Addr) { return Xil_In32(Reg_Addr); } ``` **接下来进行全局定时器的初始化和中断函数绑定:** ``` GT_Write_Reg(Global_Timer_Control_Register,0);//停止全局定时器 GT_Write_Reg(Global_Timer_Counter_Register0,0);//清空计数器低32位 GT_Write_Reg(Global_Timer_Counter_Register1,0);//清空计数器高32位 GT_Write_Reg(Global_Timer_Interrupt_Status_Register,1);//清除中断标志位 GT_Write_Reg(Comparator_Value_Register0,TIMER_LOAD_VALUE);//加载比较器低32位 GT_Write_Reg(Comparator_Value_Register1,0);//加载比较器高32位 GT_Write_Reg(Auto_increment_Register,TIMER_LOAD_VALUE);//加载递增寄存器数值 Status = XScuGic_Connect(IntcInstancePtr, Global_Timer_INTR, (Xil_ExceptionHandler)TimerIntrHandler, 0);//绑定全局定时器中断服务函数 if (Status != XST_SUCCESS) { return Status; } XScuGic_InterruptMaptoCpu(IntcInstancePtr,1,Global_Timer_INTR);//将27号全局定时器中断映射到CPU1 XScuGic_Enable(IntcInstancePtr, Global_Timer_INTR);//打开全局定时器中断(27号) ``` **主程序中打开全局定时器开始计时** ``` GT_Write_Reg(Global_Timer_Control_Register,//启动全局定时器 Auto_Increment_Bit|IRQ_Enable_Bit|Comp_Enable_Bit|Timer_Enable_Bit); ``` 该测试程序将全局定时器中断绑定在了CPU1上,是在上一章的基础上修改的,所以只修改了AMP_TIMER_CPU1.c: ``` #include "xparameters.h" #include "xscutimer.h" #include "xscugic.h" #include "xgpio.h" #include "xil_io.h" /@@************************** Constant Definitions *****************************/ /@@* * The following constants map to the XPAR parameters created in the * xparameters.h file. They are defined here such that a user can easily * change all the needed parameters in one place. */ #define TIMER_LOAD_VALUE 166666667 //0.5s #define LEDS_DEVICE_ID XPAR_AXI_GPIO_0_DEVICE_ID #define INTC XScuGic #define INTC_DEVICE_ID XPAR_PS7_SCUGIC_0_DEVICE_ID #define INTC_HANDLER XScuGic_InterruptHandler #define Global_Timer_INTR XPAR_GLOBAL_TMR_INTR #define Global_Timer_Counter_Register0 XPAR_GLOBAL_TMR_BASEADDR+0x0U #define Global_Timer_Counter_Register1 XPAR_GLOBAL_TMR_BASEADDR+0x4U #define Global_Timer_Control_Register XPAR_GLOBAL_TMR_BASEADDR+0x8U #define Global_Timer_Interrupt_Status_Register XPAR_GLOBAL_TMR_BASEADDR+0xCU #define Comparator_Value_Register0 XPAR_GLOBAL_TMR_BASEADDR+0x10U #define Comparator_Value_Register1 XPAR_GLOBAL_TMR_BASEADDR+0x14U #define Auto_increment_Register XPAR_GLOBAL_TMR_BASEADDR+0x18U #define Auto_Increment_Bit 0x08U #define IRQ_Enable_Bit 0x04U #define Comp_Enable_Bit 0x02U #define Timer_Enable_Bit 0x01U /@@* * The following constant is used to determine which channel of the GPIO is * used for the LED if there are 2 channels supported. */ #define SEG_CHANNEL 2 #define LED_CHANNEL 1 /@@************************** Function Prototypes ******************************/ static int SetupIntrSystem(INTC *IntcInstancePtr); void Stop_Global_Timer(); void Start_Global_Timer(); void AUTO_Increment_Enable(); void AUTO_Increment_Disable(); void GT_Interrupt_Enable(); void GT_Interrupt_Disable(); void GT_Comp_Enable(); void GT_Comp_Disable(); void GT_Write_Reg(u32 Reg_Addr,u32 Reg_Val); u32 GT_Read_Reg(u32 Reg_Addr); static void TimerIntrHandler(); /@@************************** Variable Definitions *****************************/ XGpio Gpio_LEDS; /@@* The Instance of the GPIO Driver */ INTC IntcInstancePtr; /@@* The Instance of the Interrupt Controller Driver */ volatile int TimerExpired; /@@*****************************************************************************/ /@@** * * 该函数是定时器中断服务函数 * @param CallBackRef is a pointer to the callback function. * * @return None. * * @note None. * ******************************************************************************/ static void TimerIntrHandler() { int status=Xil_In32(Global_Timer_Interrupt_Status_Register); if(status) { Xil_Out32(Global_Timer_Interrupt_Status_Register, status); TimerExpired=~TimerExpired; XGpio_DiscreteWrite(&Gpio_LEDS, SEG_CHANNEL, TimerExpired); } } void Stop_Global_Timer() { int status=Xil_In32(Global_Timer_Control_Register); Xil_Out32(Global_Timer_Control_Register, status&(~Timer_Enable_Bit)); } void Start_Global_Timer() { int status=Xil_In32(Global_Timer_Control_Register); Xil_Out32(Global_Timer_Control_Register, status|Timer_Enable_Bit); } void AUTO_Increment_Enable() { int status=Xil_In32(Global_Timer_Control_Register); Xil_Out32(Global_Timer_Control_Register, status|Auto_Increment_Bit); } void AUTO_Increment_Disable() { int status=Xil_In32(Global_Timer_Control_Register); Xil_Out32(Global_Timer_Control_Register, status&(~Auto_Increment_Bit)); } void GT_Interrupt_Enable() { int status=Xil_In32(Global_Timer_Control_Register); Xil_Out32(Global_Timer_Control_Register, status|IRQ_Enable_Bit); } void GT_Interrupt_Disable() { int status=Xil_In32(Global_Timer_Control_Register); Xil_Out32(Global_Timer_Control_Register, status|IRQ_Enable_Bit); } void GT_Comp_Enable() { int status=Xil_In32(Global_Timer_Control_Register); Xil_Out32(Global_Timer_Control_Register, status|Comp_Enable_Bit); } void GT_Comp_Disable() { int status=Xil_In32(Global_Timer_Control_Register); Xil_Out32(Global_Timer_Control_Register, status|Comp_Enable_Bit); } void GT_Write_Reg(u32 Reg_Addr,u32 Reg_Val) { Xil_Out32(Reg_Addr, Reg_Val); } u32 GT_Read_Reg(u32 Reg_Addr) { return Xil_In32(Reg_Addr); } /@@*****************************************************************************/ /@@** * * This function setups initializes the interrupt system. * * @param IntcInstancePtr is a pointer to the instance of the Intc driver. * @return XST_SUCCESS if successful, otherwise XST_FAILURE. * * @note None. * ******************************************************************************/ static int SetupIntrSystem(INTC *IntcInstancePtr) { int Status; TimerExpired=0; /@@* Initialize the GPIO driver */ Status = XGpio_Initialize(&Gpio_LEDS, LEDS_DEVICE_ID); if (Status != XST_SUCCESS) { xil_printf("Gpio Initialization Failed\r\n"); return XST_FAILURE; } /@@* Set the direction for all signals as inputs except the LED output */ XGpio_SetDataDirection(&Gpio_LEDS, SEG_CHANNEL, 0);//set all pins as output XScuGic_Config *IntcConfig; /@@* * Initialize the interrupt controller driver so that it is ready to * use. */ IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID); if (NULL == IntcConfig) { return XST_FAILURE; } Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig, IntcConfig->CpuBaseAddress); if (Status != XST_SUCCESS) { return XST_FAILURE; } /@@* * Initialize the exception table */ Xil_ExceptionInit(); /@@* * Register the interrupt controller handler with the exception table */ Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)INTC_HANDLER, IntcInstancePtr); GT_Write_Reg(Global_Timer_Control_Register,0);//停止全局定时器 GT_Write_Reg(Global_Timer_Counter_Register0,0);//清空计数器低32位 GT_Write_Reg(Global_Timer_Counter_Register1,0);//清空计数器高32位 GT_Write_Reg(Global_Timer_Interrupt_Status_Register,1);//清除中断标志位 GT_Write_Reg(Comparator_Value_Register0,TIMER_LOAD_VALUE);//加载比较器低32位 GT_Write_Reg(Comparator_Value_Register1,0);//加载比较器高32位 GT_Write_Reg(Auto_increment_Register,TIMER_LOAD_VALUE);//加载递增寄存器数值 Status = XScuGic_Connect(IntcInstancePtr, Global_Timer_INTR, (Xil_ExceptionHandler)TimerIntrHandler, 0);//绑定全局定时器中断服务函数 if (Status != XST_SUCCESS) { return Status; } XScuGic_InterruptMaptoCpu(IntcInstancePtr,1,Global_Timer_INTR);//将27号全局定时器中断映射到CPU1 XScuGic_Enable(IntcInstancePtr, Global_Timer_INTR);//打开全局定时器中断(27号) Xil_ExceptionEnable(); return XST_SUCCESS; } main() { SetupIntrSystem(&IntcInstancePtr); GT_Write_Reg(Global_Timer_Control_Register,//启动全局定时器 Auto_Increment_Bit|IRQ_Enable_Bit|Comp_Enable_Bit|Timer_Enable_Bit); while(1); } ``` ## TTC定时器到底能干啥? ZYNQPS部分的最后一种定时器TTC在UG585中的描述只有6页(P244-249),SDK中的API函数有15个,宏定义太多了,就没数了。那么TTC能干啥?忙完这阵子后终于可以来跟各位说道说道了。 ![](https://cf04.ickimg.com/bbsimages/202009/21e82336d656d7c846bea6cdbc611fa2.png) TTC定时器直译过来就是三路定时器,而ZYNQ中的PS有两个TTC,每一个定时器有三路,一共是6路。 **从上面的框图可以看出TTC每一路的功能可以分为三种:** 1. 传统定时计数器(Overflow mode、Interval mode),这个和其他定时器一样通过在每个计数时钟周期向上或向下计数来获得固定的时间间隔; 2. PWM输出(Overflow mode、Interval mode),可以输出固定频率和占空比的方波; 3. 脉宽计数器(Event Timer),针对外部输入脉冲记录其脉冲宽度; 那么操控这些模式肯定需要读写相关寄存器,下面就是每一路TTC定时器的相关寄存器: ![](https://cf04.ickimg.com/bbsimages/202009/4a9c6d45157b6962a4400173231fdf3e.png) ![](https://cf04.ickimg.com/bbsimages/202009/c374ade10501569de47724b845742051.png) **时钟控制寄存器(Clock Control register):** ![](https://cf04.ickimg.com/bbsimages/202009/ac28ba2fd3797209ad7cfd7838c93ad6.png) ![](https://cf04.ickimg.com/bbsimages/202009/2491f3736688fd95adc7c42f6760e484.png) **计数器控制寄存器(Counter Control register):** ![](https://cf04.ickimg.com/bbsimages/202009/760bc2dd749f1a0d76bc58f137077f4a.png) ![](https://cf04.ickimg.com/bbsimages/202009/a094e9bb5600b1a27cb9fc7aaf906096.png) **计数器数值寄存器(Counter Value register):** ![](https://cf04.ickimg.com/bbsimages/202009/715529c9c965964ad9892090d7b7974e.png) ![](https://cf04.ickimg.com/bbsimages/202009/3b323c21bcad1f14473d64f9d8ec1253.png) **间隔寄存器(Interval register):** ![](https://cf04.ickimg.com/bbsimages/202009/91ee453ac9916040accfca221abedd47.png) ![](https://cf04.ickimg.com/bbsimages/202009/df494091b7cebc136e93158add49b281.png) **匹配值寄存器(Match register1、2、3):** ![](https://cf04.ickimg.com/bbsimages/202009/349af847ebb2f74335b73bbe1825fea3.png) ![](https://cf04.ickimg.com/bbsimages/202009/c251b2b53b8362c8e625df877ad67511.png) **中断寄存器(Interrupt register):** ![](https://cf04.ickimg.com/bbsimages/202009/5306e4d9ed8c2c71131faebb716af71a.png) ![](https://cf04.ickimg.com/bbsimages/202009/248242c41c2a57820382260d6503264b.png) **中断允许寄存器(Interrupt Enable register):** ![](https://cf04.ickimg.com/bbsimages/202009/d270e4c3d866d1e7dfc4a63734a1886f.png) ![](https://cf04.ickimg.com/bbsimages/202009/8c5594180ca24ed37d025e374572b214.png) **脉宽计数器控制寄存器(Event Control Timer register)**: ![](https://cf04.ickimg.com/bbsimages/202009/b845425e71fdda10155c0b92f8d20111.png) ![](https://cf04.ickimg.com/bbsimages/202009/8bad9e7f9a4f4e1732467a8f62d2bf1a.png) **脉宽寄存器(Event register):** ![](https://cf04.ickimg.com/bbsimages/202009/f86a35a2b5a2f5e8cd89d698eb982b7f.png) ![](https://cf04.ickimg.com/bbsimages/202009/434358fd0d73c19d0ee66bdfa19522d9.png) 在你看懂之后完全可以不用SDK中的函数,直接用寄存器操作就能得到你想要的效果,不过需要注意的是TTC定时器是16位定时器,虽然可以用分频,而且分频能到65536分频可以看成是32位定时器,但分频会带来精度降低的问题,因此在使用TTC时最大计数范围要清楚同时一定要注意计数时钟的选择。 **接下来我会在这里的例子中使用间隔模式加上PWM输出:** ![](https://cf04.ickimg.com/bbsimages/202009/4d7a3fb23e3a265e949be7b637215a12.png) 上图是我的BlockDesign,计数主频设定为100KHz,将6路TTC的PWM输出全部接到了LED灯上,然后看看能否按照我的需求闪烁LED灯。现在各自设定闪烁频率为1Hz,2Hz,1Hz,2Hz,1Hz,2Hz在6位LED灯上闪烁,各自用自己的一路TTC定时器控制,不使用中断。 那么,我们看看BSP中的TTC提供了哪些数据结构,常数和API函数供我们使用。 首先最重要的数据结构是:XTtcPs_Config和XTtcPs。 **他们在xttcps.h中的定义如下:** ``` /@@** * This typedef contains configuration information for the device. */ typedef struct { u16 DeviceId; /@@**< Unique ID for device */ u32 BaseAddress; /@@**< Base address for device */ u32 InputClockHz; /@@**< Input clock frequency */ } XTtcPs_Config; /@@** * The XTtcPs driver instance data. The user is required to allocate a * variable of this type for each PS timer/counter device in the system. A * pointer to a variable of this type is then passed to various driver API * functions. */ typedef struct { XTtcPs_Config Config; /@@**< Configuration structure */ u32 IsReady; /@@**< Device is initialized and ready */ } XTtcPs; ``` **接下来是比较重要的几个常数的定义:** ``` /@@** @name Configuration options * * Options for the device. Each of the options is bit field, so more than one * options can be specified. * * @{ */ #define XTTCPS_OPTION_EXTERNAL_CLK 0x00000001U /@@**< External clock source */ #define XTTCPS_OPTION_CLK_EDGE_NEG 0x00000002U /@@**< Clock on trailing edge for external clock*/ #define XTTCPS_OPTION_INTERVAL_MODE 0x00000004U /@@**< Interval mode */ #define XTTCPS_OPTION_DECREMENT 0x00000008U /@@**< Decrement the counter */ #define XTTCPS_OPTION_MATCH_MODE 0x00000010U /@@**< Match mode */ #define XTTCPS_OPTION_WAVE_DISABLE 0x00000020U /@@**< No waveform output */ #define XTTCPS_OPTION_WAVE_POLARITY 0x00000040U /@@**< Waveform polarity */ 上面的常数并不是设置到一个寄存器里的设置,实际上这些常数会分别设置到两个寄存器中,具体设置请查阅UG585。 ``` **API函数比较多,我们的例子中用到的我全部列出来了:** ``` XTtcPs_CalcIntervalFromFreq() : xttcps.c XTtcPs_CfgInitialize() : xttcps.c XTtcPs_LookupConfig() : xttcps.h XTtcPs_SetMatchValue() : xttcps.c XTtcPs_SetOptions() : xttcps.h XTtcPs_SetInterval(): xttcps.h XTtcPs_SetPrescaler() : xttcps.c XTtcPs_Start : xttcps.h ``` **接下来是我们的示例代码:** ``` /@@***************************** Include Files *********************************/ #include
#include
#include "xparameters.h" #include "xstatus.h" #include "xil_exception.h" #include "xttcps.h" #include "xscugic.h" #include "xil_printf.h" /@@************************** Constant Definitions *****************************/ /@@* * The following constants map to the XPAR parameters created in the * xparameters.h file. They are only defined here such that a user can easily * change all the needed parameters in one place. */ #define PWM_DELTA_DUTY 50 /@@* PWM 输出方波的占空比*/ /@@**************************** Type Definitions *******************************/ typedef struct { u32 OutputHz; /@@* Output frequency */ XInterval Interval; /@@* Interval value */ XInterval Matchval; /@@* Matchval value */ u8 Prescaler; /@@* Prescaler value */ u16 Options; /@@* Option settings */ } TmrCntrSetup; /@@***************** Macros (Inline Functions) Definitions *********************/ /@@************************** Function Prototypes ******************************/ static int SetupPWM(void); static int SetupTimer(int DeviceID); /@@************************** Variable Definitions *****************************/ static XTtcPs TtcPsInst[6]; /@@* Six timer counters */ static TmrCntrSetup SettingsTable[6] = { {1, 0, 0, 0, 0}, {2, 0, 0, 0, 0}, {1, 0, 0, 0, 0}, {2, 0, 0, 0, 0}, {1, 0, 0, 0, 0}, {2, 0, 0, 0, 0},}; /@@*****************************************************************************/ /@@** * * This function calls the Ttc interrupt example. * * @param None * * @return * - XST_SUCCESS to indicate Success * - XST_FAILURE to indicate Failure. * * @note None * *****************************************************************************/ int main(void) { int Status; xil_printf("TTC PWM Example Test\r\n"); Status=SetupPWM(); if(Status != XST_SUCCESS) { return Status; } while(1); return XST_SUCCESS; } /@@****************************************************************************/ /@@** * * This function sets up the waveform output timer counter (PWM). * * @param None * * @return XST_SUCCESS if everything sets up well, XST_FAILURE otherwise. * * @note None * *****************************************************************************/ int SetupPWM(void) { int Status,i; TmrCntrSetup *TimerSetup; XTtcPs *TtcPsPWM; for(i=0;i<6;i++) { TimerSetup = &(SettingsTable[i]); /@@* * Set up appropriate options for PWM: interval mode and * match mode for waveform output. */ TimerSetup->Options |= (XTTCPS_OPTION_INTERVAL_MODE | XTTCPS_OPTION_MATCH_MODE|XTTCPS_OPTION_WAVE_POLARITY| XTTCPS_OPTION_EXTERNAL_CLK|XTTCPS_OPTION_CLK_EDGE_NEG); /@@* * Calling the timer setup routine * initialize device * set options */ Status = SetupTimer(i); if(Status != XST_SUCCESS) { return Status; } TtcPsPWM = &(TtcPsInst[i]); /@@* * Start the tick timer/counter */ XTtcPs_Start(TtcPsPWM); xil_printf("TTC timer %d is started!\r\n",i); } return Status; } /@@****************************************************************************/ /@@** * * This function sets up a timer counter device, using the information in its * setup structure. * . initialize device * . set options * . set interval and prescaler value for given output frequency. * * @param DeviceID is the unique ID for the device. * * @return XST_SUCCESS if successful, otherwise XST_FAILURE. * * @note None. * *****************************************************************************/ int SetupTimer(int DeviceID) { int Status; XTtcPs_Config *Config; XTtcPs *Timer; TmrCntrSetup *TimerSetup; TimerSetup = &(SettingsTable[DeviceID]); Timer = &(TtcPsInst[DeviceID]); /@@* * Look up the configuration based on the device identifier */ Config = XTtcPs_LookupConfig(DeviceID); if (NULL == Config) { return XST_FAILURE; } /@@* * Initialize the device */ Status = XTtcPs_CfgInitialize(Timer, Config, Config->BaseAddress); if (Status != XST_SUCCESS) { return XST_FAILURE; } xil_printf("TTC timer %d initialize successfully!\r\n",DeviceID); /@@* * Set the options */ XTtcPs_SetOptions(Timer, TimerSetup->Options); /@@* * Timer frequency is preset in the TimerSetup structure, * however, the value is not reflected in its other fields, such as * IntervalValue and PrescalerValue. The following call will map the * frequency to the interval and prescaler values. */ XTtcPs_CalcIntervalFromFreq(Timer, TimerSetup->OutputHz, &(TimerSetup->Interval), &(TimerSetup->Prescaler)); /@@* * Set the interval and prescale */ TimerSetup->Matchval=TimerSetup->Interval*PWM_DELTA_DUTY/100; XTtcPs_SetInterval(Timer, TimerSetup->Interval); XTtcPs_SetPrescaler(Timer, TimerSetup->Prescaler+1); XTtcPs_SetMatchValue(Timer,0,TimerSetup->Matchval); return XST_SUCCESS; } ``` ## 总结 在ZYNQ的体系结构中定时器太丰富了,这里讲到了私有定时器,全局定时器,三路定时器和AXI_TIMER,参考ug585,让大家少走弯路。
原创作品,未经权利人授权禁止转载。详情见
转载须知
。
举报文章
点赞
(
0
)
李肖遥
关注
评论
(0)
登录后可评论,请
登录
或
注册
相关文章推荐
MK-米客方德推出工业级存储卡
Beetle ESP32 C3 蓝牙数据收发
Beetle ESP32 C3 wifi联网获取实时天气信息
开箱测评Beetle ESP32-C3 (RISC-V芯片)模块
正点原子数控电源DP100测评
DP100试用评测-----开箱+初体验
Beetle ESP32 C3环境搭建
【花雕体验】16 使用Beetle ESP32 C3控制8X32位WS2812硬屏之二
X
你的打赏是对原创作者最大的认可
请选择打赏IC币的数量,一经提交无法退回 !
100IC币
500IC币
1000IC币
自定义
IC币
确定
X
提交成功 ! 谢谢您的支持
返回
我要举报该内容理由
×
广告及垃圾信息
抄袭或未经授权
其它举报理由
请输入您举报的理由(50字以内)
取消
提交