万字长文理解吃透 Zynq 的定时器们

作者:Watchman, 来源:ZYNQ微信公众号

前言
在ZYNQ的体系结构中定时器太丰富了,而大量的教程中基本就只玩了私有定时器,可以中断就OK了。

其实在ZYNQ中定时器资源很丰富,每个CPU有自己的私有定时器和看门狗,有一个所有CPU共享的全局定时器和看门狗,两个三路定时器还有AXI_TIMER的IP可用,不过好像很少有博客把这些测试完。

最后一个AXI_TIMER还是出了名的不好用,所以我把私有定时器,全局定时器,三路定时器和AXI_TIMER都测一测,看看有什么坑,让大家少走弯路。

私有的真是自己的?

先来看看ZYNQ的私有定时器,在UG585中是这样描述ZYNQ的私有定时器的:

每个CPU有自己的私有定时器和看门狗,中断号均为29,寄存器基址都是0xF8F00600,这让我觉得深深地不安啊!为啥不分开编址呢,两个CPU私有定时器的相关寄存器地址一模一样,中断号一模一样,虽然私有定时器中断是属于PPI私有中断但我觉得还是不大靠谱,所以需要验证一下。

在我的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的代码:

1#include "xparameters.h"
2#include "xscutimer.h"
3#include "xscugic.h"
4#include "xgpio.h"
5#include "xil_io.h"
6/************************** Constant Definitions *****************************/
7#define TIMER_DEVICE_ID XPAR_XSCUTIMER_0_DEVICE_ID
8#define TIMER_IRPT_INTR XPAR_SCUTIMER_INTR
9#define TIMER_LOAD_VALUE 333333333 //1s
10#define LEDS_DEVICE_ID XPAR_AXI_GPIO_0_DEVICE_ID
11#define INTC XScuGic
12#define INTC_DEVICE_ID XPAR_PS7_SCUGIC_0_DEVICE_ID
13#define INTC_HANDLER XScuGic_InterruptHandler
14#define CPU1STARTADR 0x20000000
15#define CPU1_START_UP_REG 0xFFFFFFF0U //set CPU1 start DDR address in this register
16#define sev() __asm__("sev")
17#define SEG_CHANNEL 1
18#define LED_CHANNEL 2
19/************************** Function Prototypes ******************************/
20static int SetupIntrSystem(INTC *IntcInstancePtr);
21static void TimerIntrHandler(void *CallBackRef);
22static void RemapOCMAddr();
23/************************** Variable Definitions *****************************/
24XScuTimer TimerInstance; /* Cortex A9 Scu Private Timer Instance */
25XGpio Gpio_LEDS; /* The Instance of the GPIO Driver */
26INTC IntcInstancePtr; /* The Instance of the Interrupt Controller Driver */
27volatile int TimerExpired;
28/*****************************************************************************/
29/**
30*
31* 该函数将禁止OCM最后64KB的Cache属性并唤醒CPU1
32*
33******************************************************************************/
34static void RemapOCMAddr()
35{
36 //Disable cache on OCM
37 Xil_SetTlbAttributes(0xFFFF0000,0x14de2); // S=b1 TEX=b100 AP=b11, Domain=b1111, C=b0, B=b0
38 print("CPU0: writing startaddress for cpu1\n\r");
39 Xil_Out32(CPU1_START_UP_REG, CPU1STARTADR); //CPU1STARTADR=0xFFFFFFF0, CPU1STARTADR=0x20000000);
40 dmb(); //waits until write has finished
41 print("CPU0: sending the SEV to wake up CPU1\n\r");
42 sev();
43 dmb(); //waits until write has finished
44}
45
46/*****************************************************************************/
47/**
48*
49* 该函数是定时器中断服务函数
50* @param CallBackRef is a pointer to the callback function.
51*
52* @return None.
53*
54* @note None.
55*
56******************************************************************************/
57static void TimerIntrHandler(void *CallbackRef)
58{
59 XScuTimer *TimerInstancePtr = (XScuTimer *) CallbackRef;
60 if (XScuTimer_IsExpired(TimerInstancePtr))
61 {
62 XScuTimer_ClearInterruptStatus(TimerInstancePtr);
63 TimerExpired=~TimerExpired;
64 XGpio_DiscreteWrite(&Gpio_LEDS, SEG_CHANNEL, TimerExpired);
65 }
66}
67/*****************************************************************************/
68/**
69*
70* This function setups initializes the interrupt system.
71*
72* @param IntcInstancePtr is a pointer to the instance of the Intc driver.
73* @param PeriphInstancePtr is a pointer to the instance of peripheral driver.
74* @param IntrId is the Interrupt Id of the peripheral interrupt
75*
76* @return XST_SUCCESS if successful, otherwise XST_FAILURE.
77*
78* @note None.
79*
80******************************************************************************/
81static int SetupIntrSystem(INTC *IntcInstancePtr)
82{
83 int Status;
84 TimerExpired=0;
85 /* Initialize the GPIO driver */
86 Status = XGpio_Initialize(&Gpio_LEDS, LEDS_DEVICE_ID);
87 if (Status != XST_SUCCESS) {
88 xil_printf("Gpio Initialization Failed\r\n");
89 return XST_FAILURE;
90 }
91 /* Set the direction for all signals as inputs except the LED output */
92 XGpio_SetDataDirection(&Gpio_LEDS, SEG_CHANNEL, 0);//set all pins as output
93 XScuGic_Config *IntcConfig;
94 /*
95 * Initialize the interrupt controller driver so that it is ready to
96 * use.
97 */
98 IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
99 if (NULL == IntcConfig) {
100 return XST_FAILURE;
101 }
102 Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig,
103 IntcConfig->CpuBaseAddress);
104 if (Status != XST_SUCCESS) {
105 return XST_FAILURE;
106 }
107 /*
108 * Initialize the exception table
109 */
110 Xil_ExceptionInit();
111 /*
112 * Register the interrupt controller handler with the exception table
113 */
114 Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
115 (Xil_ExceptionHandler)INTC_HANDLER,
116 IntcInstancePtr);
117 /*
118 * Enable non-critical exceptions
119 */
120 Xil_ExceptionEnable();
121 /*
122 * Initialize the Scu Private Timer driver.
123 */
124 XScuTimer_Config *ConfigPtr;
125 ConfigPtr = XScuTimer_LookupConfig(TIMER_DEVICE_ID);
126 /*
127 * This is where the virtual address would be used, this example
128 * uses physical address.
129 */
130 Status = XScuTimer_CfgInitialize(&TimerInstance, &ConfigPtr,
131 ConfigPtr->BaseAddr);
132 if (Status != XST_SUCCESS) {
133
134 return XST_FAILURE;
135 }
136 /*
137 * Perform a self-test to ensure that the hardware was built correctly.
138 */
139 Status = XScuTimer_SelfTest(&TimerInstance);
140 if (Status != XST_SUCCESS) {
141
142 return XST_FAILURE;
143 }
144
145 Status = XScuGic_Connect(IntcInstancePtr, TIMER_IRPT_INTR,
146 (Xil_ExceptionHandler)TimerIntrHandler,
147 &TimerInstance);//(void *)
148 if (Status != XST_SUCCESS)
149 {
150 return Status;
151 }
152 XScuGic_Enable(IntcInstancePtr, TIMER_IRPT_INTR);
153 //XScuGic_InterruptMaptoCpu(IntcInstancePtr,0,TIMER_IRPT_INTR);
154
155 /*
156 * Enable Auto reload mode.
157 */
158 XScuTimer_EnableAutoReload(&TimerInstance);
159 /*
160 * Load the timer counter register.
161 */
162 XScuTimer_LoadTimer(&TimerInstance, TIMER_LOAD_VALUE);
163 /*
164 * Start the timer counter and then wait for it
165 * to timeout a number of times.
166 */
167 XScuTimer_Start(&TimerInstance);
168 XScuTimer_EnableInterrupt(&TimerInstance);
169 return XST_SUCCESS;
170}
171main()
172{
173 RemapOCMAddr();
174 SetupIntrSystem(&IntcInstancePtr);
175 while(1);
176}

AMP_TIMER_CPU1.c的代码:

1#include "xparameters.h"
2#include "xscutimer.h"
3#include "xscugic.h"
4#include "xgpio.h"
5#include "xil_io.h"
6/************************** Constant Definitions *****************************/
7#define TIMER_DEVICE_ID XPAR_XSCUTIMER_0_DEVICE_ID
8#define TIMER_IRPT_INTR XPAR_SCUTIMER_INTR
9#define TIMER_LOAD_VALUE 166666667 //0.5s
10#define LEDS_DEVICE_ID XPAR_AXI_GPIO_0_DEVICE_ID
11#define INTC XScuGic
12#define INTC_DEVICE_ID XPAR_PS7_SCUGIC_0_DEVICE_ID
13#define INTC_HANDLER XScuGic_InterruptHandler
14#define SEG_CHANNEL 2
15#define LED_CHANNEL 1
16/************************** Function Prototypes ******************************/
17static int SetupIntrSystem(INTC *IntcInstancePtr);
18static void TimerIntrHandler(void *CallBackRef);
19/************************** Variable Definitions *****************************/
20XScuTimer TimerInstance; /* Cortex A9 Scu Private Timer Instance */
21XGpio Gpio_LEDS; /* The Instance of the GPIO Driver */
22INTC IntcInstancePtr; /* The Instance of the Interrupt Controller Driver */
23volatile int TimerExpired;
24/*****************************************************************************/
25/**
26*
27* 该函数是定时器中断服务函数
28* @param CallBackRef is a pointer to the callback function.
29*
30* @return None.
31*
32* @note None.
33*
34******************************************************************************/
35static void TimerIntrHandler(void *CallbackRef)
36{
37 XScuTimer *TimerInstancePtr = (XScuTimer *) CallbackRef;
38 if (XScuTimer_IsExpired(TimerInstancePtr))
39 {
40 XScuTimer_ClearInterruptStatus(TimerInstancePtr);
41 TimerExpired=~TimerExpired;
42 XGpio_DiscreteWrite(&Gpio_LEDS, SEG_CHANNEL, TimerExpired);
43 }
44}
45
46/*****************************************************************************/
47/**
48*
49* This function setups initializes the interrupt system.
50*
51* @param IntcInstancePtr is a pointer to the instance of the Intc driver.
52* @param PeriphInstancePtr is a pointer to the instance of peripheral driver.
53* @param IntrId is the Interrupt Id of the peripheral interrupt
54*
55* @return XST_SUCCESS if successful, otherwise XST_FAILURE.
56*
57* @note None.
58*
59******************************************************************************/
60static int SetupIntrSystem(INTC *IntcInstancePtr)
61{
62 int Status;
63 TimerExpired=0;
64 /* Initialize the GPIO driver */
65 Status = XGpio_Initialize(&Gpio_LEDS, LEDS_DEVICE_ID);
66 if (Status != XST_SUCCESS) {
67 xil_printf("Gpio Initialization Failed\r\n");
68 return XST_FAILURE;
69 }
70
71 /* Set the direction for all signals as inputs except the LED output */
72 XGpio_SetDataDirection(&Gpio_LEDS, SEG_CHANNEL, 0);//set all pins as output
73 XScuGic_Config *IntcConfig;
74
75 /*
76 * Initialize the interrupt controller driver so that it is ready to
77 * use.
78 */
79 IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
80 if (NULL == IntcConfig) {
81 return XST_FAILURE;
82 }
83
84 Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig,
85 IntcConfig->CpuBaseAddress);
86 if (Status != XST_SUCCESS) {
87 return XST_FAILURE;
88 }
89 /*
90 * Initialize the exception table
91 */
92 Xil_ExceptionInit();
93
94 /*
95 * Register the interrupt controller handler with the exception table
96 */
97 Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
98 (Xil_ExceptionHandler)INTC_HANDLER,
99 IntcInstancePtr);
100 /*
101 * Enable non-critical exceptions
102 */
103
104 /*
105 * Initialize the Scu Private Timer driver.
106 */
107
108 XScuTimer_Config *ConfigPtr;
109 ConfigPtr = XScuTimer_LookupConfig(TIMER_DEVICE_ID);
110 /*
111 * This is where the virtual address would be used, this example
112 * uses physical address.
113 */
114 Status = XScuTimer_CfgInitialize(&TimerInstance, &ConfigPtr,
115 ConfigPtr->BaseAddr);
116 if (Status != XST_SUCCESS) {
117
118 return XST_FAILURE;
119 }
120 /*
121 * Perform a self-test to ensure that the hardware was built correctly.
122 */
123 Status = XScuTimer_SelfTest(&TimerInstance);
124 if (Status != XST_SUCCESS) {
125
126 return XST_FAILURE;
127 }
128 Status = XScuGic_Connect(IntcInstancePtr, TIMER_IRPT_INTR,
129 (Xil_ExceptionHandler)TimerIntrHandler,
130 &TimerInstance);//(void *)
131 if (Status != XST_SUCCESS)
132 {
133 return Status;
134 }
135 XScuGic_Enable(IntcInstancePtr, TIMER_IRPT_INTR);
136 //XScuGic_InterruptMaptoCpu(IntcInstancePtr,0,TIMER_IRPT_INTR);
137
138 /*
139 * Enable Auto reload mode.
140 */
141 XScuTimer_EnableAutoReload(&TimerInstance);
142 /*
143 * Load the timer counter register.
144 */
145 XScuTimer_LoadTimer(&TimerInstance, TIMER_LOAD_VALUE);
146 /*
147 * Start the timer counter and then wait for it
148 * to timeout a number of times.
149 */
150 XScuTimer_Start(&TimerInstance);
151 XScuTimer_EnableInterrupt(&TimerInstance);
152 Xil_ExceptionEnable();
153 return XST_SUCCESS;
154}
155main()
156{
157 SetupIntrSystem(&IntcInstancePtr);
158 while(1);
159}

全局定时器是个坑?

翻开UG585,文档里头言之凿凿说有全局定时器,而且还有相关的几个寄存器:

Global_Timer_Counter_Register0(全局定时器计数器低32位),地址0xF8F00200,复位值0,可读写。

Global_Timer_Counter_Register1(全局定时器计数器高32位),地址0xF8F00204,复位值0,可读写。

Global_Timer_Control_Register(全局定时器控制寄存器),地址0xF8F00208,复位值0,可读写;

其中[15:8]位即从低位数起的第二个字节为Prescaler(步距),该字节可用来延迟定时间隔,其计算公式为:(步距值+1)(加载值+1)(计数时钟周期);

第3位为触发模式位为0则为单次触发,为1则为自动递增模式,该模式下每次计数器达到比较器的数值则比较器会自动加上递增寄存器的数值让定时间隔变得有周期性;

第2位为中断允许位,默认为0即不触发中断,为1时在中断状态寄存器置位时会触发中断;

第1位为比较允许位,默认为0即不比较,为1时每当计数器数值达到比较器数值则会置位事件标志位;

第0位为定时器允许位,默认为0即不计数,为1时计数器在计数时钟下开始计数。

Global_Timer_Interrupt_Status_Register(中断状态寄存器),地址0xF8F0020C,复位值0,可读写;

其中第0位为事件标志位,当计数器达到比较器数值时该位置1,需要手动清零,向该位写一个1就能清零(看清楚是写1不是写0)。

Comparator_Value_Register0(比较器低32位),地址0xF8F00210,复位值0,可读写;

Comparator_Value_Register1(比较器高32位),地址0xF8F00214,复位值0,可读写;

Auto_increment_Register(自动递增寄存器),地址0xF8F00218,复位值0,可读写;

在自动递增模式下,每当计数器数值达到比较器数值,则比较器会自动加上自动递增寄存器的值以产生周期性中断。

一共7个寄存器,感觉不算很难,不过当你打开SDK再想看看对应的BSP文档时就傻眼了:

现在才知道全局定时器是个坑!得想办法爬出来!

本来还想参照私有定时器弄个结构体采用指针操作,定义可读性很高的函数,采用对象化的方法让代码能重用,看看时间又是半夜了,于是让各位失望了,这次得采用最原始暴力的寄存器地址操作了。

定义全局定时器的7个寄存器全部按照地址进行了宏定义,采用xil_io.h里的out32和in32两个函数进行读写操作:
1 #define Global_Timer_INTR XPAR_GLOBAL_TMR_INTR
2 #define Global_Timer_Counter_Register0 XPAR_GLOBAL_TMR_BASEADDR+0x0U
3 #define Global_Timer_Counter_Register1 XPAR_GLOBAL_TMR_BASEADDR+0x4U
4 #define Global_Timer_Control_Register XPAR_GLOBAL_TMR_BASEADDR+0x8U
5 #define Global_Timer_Interrupt_Status_Register XPAR_GLOBAL_TMR_BASEADDR+0xCU
6 #define Comparator_Value_Register0 XPAR_GLOBAL_TMR_BASEADDR+0x10U
7 #define Comparator_Value_Register1 XPAR_GLOBAL_TMR_BASEADDR+0x14U
8 #define Auto_increment_Register XPAR_GLOBAL_TMR_BASEADDR+0x18U

设定了两个寄存器读写函数:
1 void GT_Write_Reg(u32 Reg_Addr,u32 Reg_Val)
2 {
3 Xil_Out32(Reg_Addr, Reg_Val);
4 }
5 u32 GT_Read_Reg(u32 Reg_Addr)
6 {
7 return Xil_In32(Reg_Addr);
8 }

接下来进行全局定时器的初始化和中断函数绑定:
1 GT_Write_Reg(Global_Timer_Control_Register,0);//停止全局定时器
2 GT_Write_Reg(Global_Timer_Counter_Register0,0);//清空计数器低32位
3 GT_Write_Reg(Global_Timer_Counter_Register1,0);//清空计数器高32位
4 GT_Write_Reg(Global_Timer_Interrupt_Status_Register,1);//清除中断标志位
5 GT_Write_Reg(Comparator_Value_Register0,TIMER_LOAD_VALUE);//加载比较器低32位
6 GT_Write_Reg(Comparator_Value_Register1,0);//加载比较器高32位
7 GT_Write_Reg(Auto_increment_Register,TIMER_LOAD_VALUE);//加载递增寄存器数值
8 Status = XScuGic_Connect(IntcInstancePtr, Global_Timer_INTR,
9 (Xil_ExceptionHandler)TimerIntrHandler,
10 0);//绑定全局定时器中断服务函数
11 if (Status != XST_SUCCESS)
12 {
13 return Status;
14 }
15 XScuGic_InterruptMaptoCpu(IntcInstancePtr,1,Global_Timer_INTR);//将27号全局定时器中断映射到CPU1
16 XScuGic_Enable(IntcInstancePtr, Global_Timer_INTR);//打开全局定时器中断(27号)

主程序中打开全局定时器开始计时
1 GT_Write_Reg(Global_Timer_Control_Register,//启动全局定时器
2 Auto_Increment_Bit|IRQ_Enable_Bit|Comp_Enable_Bit|Timer_Enable_Bit);
3

该测试程序将全局定时器中断绑定在了CPU1上,是在上一章的基础上修改的,所以只修改了AMP_TIMER_CPU1.c:
1#include "xparameters.h"
2#include "xscutimer.h"
3#include "xscugic.h"
4#include "xgpio.h"
5#include "xil_io.h"
6/************************** Constant Definitions *****************************/
7
8/*
9 * The following constants map to the XPAR parameters created in the
10 * xparameters.h file. They are defined here such that a user can easily
11 * change all the needed parameters in one place.
12 */
13#define TIMER_LOAD_VALUE 166666667 //0.5s
14#define LEDS_DEVICE_ID XPAR_AXI_GPIO_0_DEVICE_ID
15#define INTC XScuGic
16#define INTC_DEVICE_ID XPAR_PS7_SCUGIC_0_DEVICE_ID
17#define INTC_HANDLER XScuGic_InterruptHandler
18#define Global_Timer_INTR XPAR_GLOBAL_TMR_INTR
19#define Global_Timer_Counter_Register0 XPAR_GLOBAL_TMR_BASEADDR+0x0U
20#define Global_Timer_Counter_Register1 XPAR_GLOBAL_TMR_BASEADDR+0x4U
21#define Global_Timer_Control_Register XPAR_GLOBAL_TMR_BASEADDR+0x8U
22#define Global_Timer_Interrupt_Status_Register XPAR_GLOBAL_TMR_BASEADDR+0xCU
23#define Comparator_Value_Register0 XPAR_GLOBAL_TMR_BASEADDR+0x10U
24#define Comparator_Value_Register1 XPAR_GLOBAL_TMR_BASEADDR+0x14U
25#define Auto_increment_Register XPAR_GLOBAL_TMR_BASEADDR+0x18U
26#define Auto_Increment_Bit 0x08U
27#define IRQ_Enable_Bit 0x04U
28#define Comp_Enable_Bit 0x02U
29#define Timer_Enable_Bit 0x01U
30/*
31 * The following constant is used to determine which channel of the GPIO is
32 * used for the LED if there are 2 channels supported.
33 */
34#define SEG_CHANNEL 2
35#define LED_CHANNEL 1
36/************************** Function Prototypes ******************************/
37static int SetupIntrSystem(INTC *IntcInstancePtr);
38void Stop_Global_Timer();
39void Start_Global_Timer();
40void AUTO_Increment_Enable();
41void AUTO_Increment_Disable();
42void GT_Interrupt_Enable();
43void GT_Interrupt_Disable();
44void GT_Comp_Enable();
45void GT_Comp_Disable();
46void GT_Write_Reg(u32 Reg_Addr,u32 Reg_Val);
47u32 GT_Read_Reg(u32 Reg_Addr);
48static void TimerIntrHandler();
49/************************** Variable Definitions *****************************/
50XGpio Gpio_LEDS; /* The Instance of the GPIO Driver */
51INTC IntcInstancePtr; /* The Instance of the Interrupt Controller Driver */
52volatile int TimerExpired;
53/*****************************************************************************/
54/**
55*
56* 该函数是定时器中断服务函数
57* @param CallBackRef is a pointer to the callback function.
58*
59* @return None.
60*
61* @note None.
62*
63******************************************************************************/
64static void TimerIntrHandler()
65{
66 int status=Xil_In32(Global_Timer_Interrupt_Status_Register);
67 if(status)
68 {
69 Xil_Out32(Global_Timer_Interrupt_Status_Register, status);
70 TimerExpired=~TimerExpired;
71 XGpio_DiscreteWrite(&Gpio_LEDS, SEG_CHANNEL, TimerExpired);
72 }
73}
74void Stop_Global_Timer()
75{
76 int status=Xil_In32(Global_Timer_Control_Register);
77 Xil_Out32(Global_Timer_Control_Register, status&(~Timer_Enable_Bit));
78}
79void Start_Global_Timer()
80{
81 int status=Xil_In32(Global_Timer_Control_Register);
82 Xil_Out32(Global_Timer_Control_Register, status|Timer_Enable_Bit);
83}
84void AUTO_Increment_Enable()
85{
86 int status=Xil_In32(Global_Timer_Control_Register);
87 Xil_Out32(Global_Timer_Control_Register, status|Auto_Increment_Bit);
88}
89void AUTO_Increment_Disable()
90{
91 int status=Xil_In32(Global_Timer_Control_Register);
92 Xil_Out32(Global_Timer_Control_Register, status&(~Auto_Increment_Bit));
93}
94void GT_Interrupt_Enable()
95{
96 int status=Xil_In32(Global_Timer_Control_Register);
97 Xil_Out32(Global_Timer_Control_Register, status|IRQ_Enable_Bit);
98}
99void GT_Interrupt_Disable()
100{
101 int status=Xil_In32(Global_Timer_Control_Register);
102 Xil_Out32(Global_Timer_Control_Register, status|IRQ_Enable_Bit);
103}
104void GT_Comp_Enable()
105{
106 int status=Xil_In32(Global_Timer_Control_Register);
107 Xil_Out32(Global_Timer_Control_Register, status|Comp_Enable_Bit);
108}
109void GT_Comp_Disable()
110{
111 int status=Xil_In32(Global_Timer_Control_Register);
112 Xil_Out32(Global_Timer_Control_Register, status|Comp_Enable_Bit);
113}
114void GT_Write_Reg(u32 Reg_Addr,u32 Reg_Val)
115{
116 Xil_Out32(Reg_Addr, Reg_Val);
117}
118u32 GT_Read_Reg(u32 Reg_Addr)
119{
120 return Xil_In32(Reg_Addr);
121}
122/*****************************************************************************/
123/**
124*
125* This function setups initializes the interrupt system.
126*
127* @param IntcInstancePtr is a pointer to the instance of the Intc driver.
128* @return XST_SUCCESS if successful, otherwise XST_FAILURE.
129*
130* @note None.
131*
132******************************************************************************/
133static int SetupIntrSystem(INTC *IntcInstancePtr)
134{
135 int Status;
136 TimerExpired=0;
137 /* Initialize the GPIO driver */
138 Status = XGpio_Initialize(&Gpio_LEDS, LEDS_DEVICE_ID);
139 if (Status != XST_SUCCESS) {
140 xil_printf("Gpio Initialization Failed\r\n");
141 return XST_FAILURE;
142 }
143 /* Set the direction for all signals as inputs except the LED output */
144 XGpio_SetDataDirection(&Gpio_LEDS, SEG_CHANNEL, 0);//set all pins as output
145 XScuGic_Config *IntcConfig;
146 /*
147 * Initialize the interrupt controller driver so that it is ready to
148 * use.
149 */
150 IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
151 if (NULL == IntcConfig) {
152 return XST_FAILURE;
153 }
154 Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig,
155 IntcConfig->CpuBaseAddress);
156 if (Status != XST_SUCCESS) {
157 return XST_FAILURE;
158 }
159 /*
160 * Initialize the exception table
161 */
162 Xil_ExceptionInit();
163 /*
164 * Register the interrupt controller handler with the exception table
165 */
166 Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
167 (Xil_ExceptionHandler)INTC_HANDLER,
168 IntcInstancePtr);
169 GT_Write_Reg(Global_Timer_Control_Register,0);//停止全局定时器
170 GT_Write_Reg(Global_Timer_Counter_Register0,0);//清空计数器低32位
171 GT_Write_Reg(Global_Timer_Counter_Register1,0);//清空计数器高32位
172 GT_Write_Reg(Global_Timer_Interrupt_Status_Register,1);//清除中断标志位
173 GT_Write_Reg(Comparator_Value_Register0,TIMER_LOAD_VALUE);//加载比较器低32位
174 GT_Write_Reg(Comparator_Value_Register1,0);//加载比较器高32位
175 GT_Write_Reg(Auto_increment_Register,TIMER_LOAD_VALUE);//加载递增寄存器数值
176 Status = XScuGic_Connect(IntcInstancePtr, Global_Timer_INTR,
177 (Xil_ExceptionHandler)TimerIntrHandler,
178 0);//绑定全局定时器中断服务函数
179 if (Status != XST_SUCCESS)
180 {
181 return Status;
182 }
183 XScuGic_InterruptMaptoCpu(IntcInstancePtr,1,Global_Timer_INTR);//将27号全局定时器中断映射到CPU1
184 XScuGic_Enable(IntcInstancePtr, Global_Timer_INTR);//打开全局定时器中断(27号)
185 Xil_ExceptionEnable();
186 return XST_SUCCESS;
187}
188main()
189{
190 SetupIntrSystem(&IntcInstancePtr);
191 GT_Write_Reg(Global_Timer_Control_Register,//启动全局定时器
192 Auto_Increment_Bit|IRQ_Enable_Bit|Comp_Enable_Bit|Timer_Enable_Bit);
193 while(1);
194}

TTC定时器到底能干啥?

ZYNQPS部分的最后一种定时器TTC在UG585中的描述只有6页(P244-249),SDK中的API函数有15个,宏定义太多了,就没数了。那么TTC能干啥?忙完这阵子后终于可以来跟各位说道说道了。

http://xilinx.eetrend.com/files/2020-09/%E5%8D%9A%E5%AE%A2/100052870-107314-17.png

TTC定时器直译过来就是三路定时器,而ZYNQ中的PS有两个TTC,每一个定时器有三路,一共是6路。

从上面的框图可以看出TTC每一路的功能可以分为三种:

1. 传统定时计数器(Overflow mode、Interval mode),这个和其他定时器一样通过在每个计数时钟周期向上或向下计数来获得固定的时间间隔;

2. PWM输出(Overflow mode、Interval mode),可以输出固定频率和占空比的方波;

3. 脉宽计数器(Event Timer),针对外部输入脉冲记录其脉冲宽度;

那么操控这些模式肯定需要读写相关寄存器,下面就是每一路TTC定时器的相关寄存器:


时钟控制寄存器(Clock Control register):


计数器控制寄存器(Counter Control register):


计数器数值寄存器(Counter Value register):


间隔寄存器(Interval register):


匹配值寄存器(Match register1、2、3):


中断寄存器(Interrupt register):


中断允许寄存器(Interrupt Enable register):


脉宽计数器控制寄存器(Event Control Timer register):


脉宽寄存器(Event register):


在你看懂之后完全可以不用SDK中的函数,直接用寄存器操作就能得到你想要的效果,不过需要注意的是TTC定时器是16位定时器,虽然可以用分频,而且分频能到65536分频可以看成是32位定时器,但分频会带来精度降低的问题,因此在使用TTC时最大计数范围要清楚同时一定要注意计数时钟的选择。

接下来我会在这里的例子中使用间隔模式加上PWM输出:

上图是我的BlockDesign,计数主频设定为100KHz,将6路TTC的PWM输出全部接到了LED灯上,然后看看能否按照我的需求闪烁LED灯。现在各自设定闪烁频率为1Hz,2Hz,1Hz,2Hz,1Hz,2Hz在6位LED灯上闪烁,各自用自己的一路TTC定时器控制,不使用中断。

那么,我们看看BSP中的TTC提供了哪些数据结构,常数和API函数供我们使用。

首先最重要的数据结构是:XTtcPs_Config和XTtcPs。

他们在xttcps.h中的定义如下:
1/**
2 * This typedef contains configuration information for the device.
3 */
4typedef struct {
5 u16 DeviceId; /**< Unique ID for device */
6 u32 BaseAddress; /**< Base address for device */
7 u32 InputClockHz; /**< Input clock frequency */
8} XTtcPs_Config;
9
10/**
11 * The XTtcPs driver instance data. The user is required to allocate a
12 * variable of this type for each PS timer/counter device in the system. A
13 * pointer to a variable of this type is then passed to various driver API
14 * functions.
15 */
16typedef struct {
17 XTtcPs_Config Config; /**< Configuration structure */
18 u32 IsReady; /**< Device is initialized and ready */
19} XTtcPs;

接下来是比较重要的几个常数的定义:
1/** @name Configuration options
2 *
3 * Options for the device. Each of the options is bit field, so more than one
4 * options can be specified.
5 *
6 * @{
7 */
8#define XTTCPS_OPTION_EXTERNAL_CLK 0x00000001U /**< External clock source */
9#define XTTCPS_OPTION_CLK_EDGE_NEG 0x00000002U /**< Clock on trailing edge for
10 external clock*/
11#define XTTCPS_OPTION_INTERVAL_MODE 0x00000004U /**< Interval mode */
12#define XTTCPS_OPTION_DECREMENT 0x00000008U /**< Decrement the counter */
13#define XTTCPS_OPTION_MATCH_MODE 0x00000010U /**< Match mode */
14#define XTTCPS_OPTION_WAVE_DISABLE 0x00000020U /**< No waveform output */
15#define XTTCPS_OPTION_WAVE_POLARITY 0x00000040U /**< Waveform polarity */
16上面的常数并不是设置到一个寄存器里的设置,实际上这些常数会分别设置到两个寄存器中,具体设置请查阅UG585。

API函数比较多,我们的例子中用到的我全部列出来了:
1XTtcPs_CalcIntervalFromFreq() : xttcps.c
2XTtcPs_CfgInitialize() : xttcps.c
3XTtcPs_LookupConfig() : xttcps.h
4XTtcPs_SetMatchValue() : xttcps.c
5XTtcPs_SetOptions() : xttcps.h
6XTtcPs_SetInterval(): xttcps.h
7XTtcPs_SetPrescaler() : xttcps.c
8XTtcPs_Start : xttcps.h

接下来是我们的示例代码:
1/***************************** Include Files *********************************/
2
3#include
4#include
5#include "xparameters.h"
6#include "xstatus.h"
7#include "xil_exception.h"
8#include "xttcps.h"
9#include "xscugic.h"
10#include "xil_printf.h"
11
12/************************** Constant Definitions *****************************/
13
14/*
15 * The following constants map to the XPAR parameters created in the
16 * xparameters.h file. They are only defined here such that a user can easily
17 * change all the needed parameters in one place.
18 */
19#define PWM_DELTA_DUTY 50 /* PWM 输出方波的占空比*/
20/**************************** Type Definitions *******************************/
21typedef struct {
22 u32 OutputHz; /* Output frequency */
23 XInterval Interval; /* Interval value */
24 XInterval Matchval; /* Matchval value */
25 u8 Prescaler; /* Prescaler value */
26 u16 Options; /* Option settings */
27} TmrCntrSetup;
28
29/***************** Macros (Inline Functions) Definitions *********************/
30
31
32/************************** Function Prototypes ******************************/
33
34static int SetupPWM(void);
35static int SetupTimer(int DeviceID);
36/************************** Variable Definitions *****************************/
37
38static XTtcPs TtcPsInst[6]; /* Six timer counters */
39
40static TmrCntrSetup SettingsTable[6] = {
41 {1, 0, 0, 0, 0},
42 {2, 0, 0, 0, 0},
43 {1, 0, 0, 0, 0},
44 {2, 0, 0, 0, 0},
45 {1, 0, 0, 0, 0},
46 {2, 0, 0, 0, 0},};
47
48/*****************************************************************************/
49/**
50*
51* This function calls the Ttc interrupt example.
52*
53* @param None
54*
55* @return
56* - XST_SUCCESS to indicate Success
57* - XST_FAILURE to indicate Failure.
58*
59* @note None
60*
61*****************************************************************************/
62int main(void)
63{
64 int Status;
65
66 xil_printf("TTC PWM Example Test\r\n");
67
68 Status=SetupPWM();
69 if(Status != XST_SUCCESS) {
70 return Status;
71 }
72 while(1);
73 return XST_SUCCESS;
74}
75
76/****************************************************************************/
77/**
78*
79* This function sets up the waveform output timer counter (PWM).
80*
81* @param None
82*
83* @return XST_SUCCESS if everything sets up well, XST_FAILURE otherwise.
84*
85* @note None
86*
87*****************************************************************************/
88int SetupPWM(void)
89{
90 int Status,i;
91 TmrCntrSetup *TimerSetup;
92 XTtcPs *TtcPsPWM;
93 for(i=0;i<6;i++)
94 {
95 TimerSetup = &(SettingsTable[i]);
96
97 /*
98 * Set up appropriate options for PWM: interval mode and
99 * match mode for waveform output.
100 */
101 TimerSetup->Options |= (XTTCPS_OPTION_INTERVAL_MODE |
102 XTTCPS_OPTION_MATCH_MODE|XTTCPS_OPTION_WAVE_POLARITY|
103 XTTCPS_OPTION_EXTERNAL_CLK|XTTCPS_OPTION_CLK_EDGE_NEG);
104
105 /*
106 * Calling the timer setup routine
107 * initialize device
108 * set options
109 */
110 Status = SetupTimer(i);
111 if(Status != XST_SUCCESS) {
112 return Status;
113 }
114
115 TtcPsPWM = &(TtcPsInst[i]);
116
117 /*
118 * Start the tick timer/counter
119 */
120 XTtcPs_Start(TtcPsPWM);
121 xil_printf("TTC timer %d is started!\r\n",i);
122 }
123 return Status;
124}
125/****************************************************************************/
126/**
127*
128* This function sets up a timer counter device, using the information in its
129* setup structure.
130* . initialize device
131* . set options
132* . set interval and prescaler value for given output frequency.
133*
134* @param DeviceID is the unique ID for the device.
135*
136* @return XST_SUCCESS if successful, otherwise XST_FAILURE.
137*
138* @note None.
139*
140*****************************************************************************/
141int SetupTimer(int DeviceID)
142{
143 int Status;
144 XTtcPs_Config *Config;
145 XTtcPs *Timer;
146 TmrCntrSetup *TimerSetup;
147
148 TimerSetup = &(SettingsTable[DeviceID]);
149
150 Timer = &(TtcPsInst[DeviceID]);
151
152 /*
153 * Look up the configuration based on the device identifier
154 */
155 Config = XTtcPs_LookupConfig(DeviceID);
156 if (NULL == Config) {
157 return XST_FAILURE;
158 }
159
160 /*
161 * Initialize the device
162 */
163 Status = XTtcPs_CfgInitialize(Timer, Config, Config->BaseAddress);
164 if (Status != XST_SUCCESS) {
165 return XST_FAILURE;
166 }
167 xil_printf("TTC timer %d initialize successfully!\r\n",DeviceID);
168 /*
169 * Set the options
170 */
171 XTtcPs_SetOptions(Timer, TimerSetup->Options);
172
173 /*
174 * Timer frequency is preset in the TimerSetup structure,
175 * however, the value is not reflected in its other fields, such as
176 * IntervalValue and PrescalerValue. The following call will map the
177 * frequency to the interval and prescaler values.
178 */
179 XTtcPs_CalcIntervalFromFreq(Timer, TimerSetup->OutputHz,
180 &(TimerSetup->Interval), &(TimerSetup->Prescaler));
181
182 /*
183 * Set the interval and prescale
184 */
185 TimerSetup->Matchval=TimerSetup->Interval*PWM_DELTA_DUTY/100;
186 XTtcPs_SetInterval(Timer, TimerSetup->Interval);
187 XTtcPs_SetPrescaler(Timer, TimerSetup->Prescaler+1);
188 XTtcPs_SetMatchValue(Timer,0,TimerSetup->Matchval);
189 return XST_SUCCESS;
190}

总结
在ZYNQ的体系结构中定时器太丰富了,这里讲到了私有定时器,全局定时器,三路定时器和AXI_TIMER,参考ug585,让大家少走弯路,希望对你有帮助。

最新文章

最新文章