每个Cortex-A9处理器都有私有的32位定时器和32位看门狗定时器。这两种定时器都是32位的计数器,计数到0时产生中断;带有8位的预分频器,能够更好地控制中断周期;可配置为单次重载或自动重载模式;可配置初始值。它们的工作时钟固定为CPU频率的1/2(CPU_3x2x)。
两个CPU同时共享一个64位的全局定时器GT,这是一个递增的计数器,带有自动递增功能。全局定时器与私有定时器在内存中映射的地址空间相同。两个Cortex-A9有各自的64位的比较器,可以共同访问全局定时器,达到比较器值时产生一个私有中断。它的时钟也固定为CPU频率的1/2(CPU_3x2x)。
除了两个CPU的私有看门狗定时器,还有一个24位的系统看门狗SWDT,在发生灾难性的系统故障时发出信号(比如PS中的PLL工作失常)。SWDT可以工作在CPU频率的1/4或1/6(CPU_1x),也可以工作在设备外部或PL提供的时钟下,并向它们输出一个复位信号。
此外PS中还有两个TTC(Triple Timer Counters),每个TTC都有三个独立的定时器/计数器。TTC只能工作在CPU频率的1/4或1/6(CPU_1x),Zynq使用该定时器计算来自MIO管脚或PL的信号脉冲宽度。
私有定时器和看门狗定时器、全局定时器属于PPI;SWDT和TTC属于SPI。各种定时器资源之间的关系如下:
几种定时器中,私有定时器是最常用的,使用双核时可能会用到全局定时器。私有定时器是CPU五种PPI中断源的一种,固定为上升沿敏感。
做一个简单的设计体会私有定时器的使用,每隔1s串口输出一次。Vivado中搭建硬件环境,使用最小系统即可,导出硬件到SDK中。
timer.h文件的代码如下:
#include
#include "xadcps.h"
#include "xil_types.h"
#include "Xscugic.h"
#include "Xil_exception.h"
#include "xscutimer.h"
//---------------------------------------------------------
// 参数定义
//---------------------------------------------------------
#define TIMER_DEVICE_ID XPAR_XSCUTIMER_0_DEVICE_ID
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
#define TIMER_IRPT_INTR XPAR_SCUTIMER_INTR //定时器中断ID号
#define TIMER_LOAD_VALUE 0x13D92D3F //定时器装载值
//---------------------------------------------------------
// 函数申明
//---------------------------------------------------------
void SetupInterruptSystem(XScuGic *GicInstancePtr,
XScuTimer *TimerInstancePtr, u16 TimerIntrId);
void TimreInit(XScuTimer Timer, XScuGic Intc);
timer.c文件的代码如下:
#include "timer.h"
//---------------------------------------------------------
// 定时器中断处理程序
//---------------------------------------------------------
static void TimerIntrHandler(void *CallBackRef)
{
static int sec = 0; //计数
XScuTimer *TimerInstancePtr = (XScuTimer *) CallBackRef;
XScuTimer_ClearInterruptStatus(TimerInstancePtr);
sec++;
printf(" %d Second\n\r",sec); //每秒打印输出一次
}
//---------------------------------------------------------
// 定时器中断配置
//---------------------------------------------------------
void SetupInterruptSystem(XScuGic *GicInstancePtr,
XScuTimer *TimerInstancePtr, u16 TimerIntrId)
{
/* 初始化中断控制器 */
XScuGic_Config *IntcConfig;
IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
XScuGic_CfgInitialize(GicInstancePtr, IntcConfig, IntcConfig->CpuBaseAddress);
/* 设置中断异常 */
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler, GicInstancePtr);
Xil_ExceptionEnable();
/* 设置定时器中断 */
XScuGic_Connect(GicInstancePtr, TimerIntrId,
(Xil_ExceptionHandler)TimerIntrHandler, (void *)TimerInstancePtr);
XScuGic_Enable(GicInstancePtr, TimerIntrId); //使能中断
XScuTimer_EnableInterrupt(TimerInstancePtr); //使能定时器中断
}
//---------------------------------------------------------
// 定时器初始化程序
//---------------------------------------------------------
void TimreInit(XScuTimer Timer, XScuGic Intc)
{
/* 私有定时器初始化 */
XScuTimer_Config *TMRConfigPtr;
TMRConfigPtr = XScuTimer_LookupConfig(TIMER_DEVICE_ID);
XScuTimer_CfgInitialize(&Timer, TMRConfigPtr,TMRConfigPtr->BaseAddr);
XScuTimer_LoadTimer(&Timer, TIMER_LOAD_VALUE); // 加载计数周期
XScuTimer_EnableAutoReload(&Timer); // 设置自动装载模式
SetupInterruptSystem(&Intc,&Timer,TIMER_IRPT_INTR); // 设置定时器中断
XScuTimer_Start(&Timer); // 启动定时器
}
mian.c文件的代码如下:
#include "timer.h"
static XScuTimer Timer; //定时器
static XScuGic Intc; //中断控制器
int main()
{
TimreInit(Timer, Intc);
while(1);
return 0;
}
查阅PPI列表,可以看到私有定时器的中断号为29。
初始化部分中,我们看到了似曾相识的XScuTimer、XscuTimer_Config两个结构体、XScuTimer_LookupConfig()和XScuTimer_CfgInitialize()两个函数,只是从“GPIO设备”、“中断设备”换成了“定时器设备”。这算是Zynq中各种设备(device)初始化的套路,甚至传入的参数类型都是十分相近的,这里不再赘述。
/* 私有定时器初始化 */
XScuTimer_Config *TMRConfigPtr;
TMRConfigPtr = XScuTimer_LookupConfig(TIMER_DEVICE_ID);
XScuTimer_CfgInitialize(&Timer, TMRConfigPtr,TMRConfigPtr->BaseAddr);
使用XScuTimer_LoadTimer()函数加载定时器的计数器中的值,其本质上只是操作Timer Load寄存器。函数原型采用宏定义,但也可以看作一个C语言风格的函数接口:
#define XScuTimer_LoadTimer(InstancePtr, Value) \
XScuTimer_WriteReg((InstancePtr)->Config.BaseAddr, \
XSCUTIMER_LOAD_OFFSET, (Value))
//可视作如下C语言函数
void XScuTimer_LoadTimer(XScuTimer *InstancePtr, u32 Value)
私有定时器是从装载值递减到0时发出中断。我们这里默认使用666MHz的CPU时钟,则私有定时器的工作时钟为333MHz,每1/333M秒减1。因此若想定时1s,则装载值为1/(1/333M)-1,将该值在timer.h中宏定义。
本例程需要定时器一直工作,因此使用XScuTimer_EnableAutoReload()函数启用自动装载模式。不需要时使用XScuTimer_DisableAutoReload()函数禁用自动装载。这两个函数本质上也是宏定义,操作相关寄存器中的相应bit位。下面只给出等效的C语言模型:
void XScuTimer_EnableAutoReload(XScuTimer *InstancePtr)
void XScuTimer_DisableAutoReload(XScuTimer *InstancePtr)
这部分通过自定义函数来设置定时器的中断,过程和第8篇中PL中断的初始化过程基本相同。实际上用到中断时基本都要有这个过程:初始化中断控制器、设置中断异常、连接中断、使能中断。
我们这里使用的是定时器中断,连接函数如下。在定时器中断处理程序中,我们必须清除中断标志,因此XscuGic_Connect的第三个参数设置为定时器的控制设备,在调用中断状态清除函数时会用到该参数。
XScuGic_Connect(GicInstancePtr, TimerIntrId,
(Xil_ExceptionHandler)TimerIntrHandler, (void *)TimerInstancePtr);
中断处理程序中,除了计时和串口打印输出,还要调用XScuTimer_ClearInterruptStatus()函数清除中断状态。相关代码如下:
XScuTimer *TimerInstancePtr = (XScuTimer *) CallBackRef;
XScuTimer_ClearInterruptStatus(TimerInstancePtr);
该函数本质上也是宏定义,等效的C语言接口如下:
void XScuTimer_ClearInterruptStatus(XScuTimer *InstancePtr)
传入的XscuTimer*类型的参数在连接定时器中断时设置。使用时首先要将其从void*类型转换为XscuTimer*类型。