学会Zynq(9)定时器使用示例(PPI)

定时器资源

每个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。各种定时器资源之间的关系如下:
学会Zynq(9)定时器使用示例(PPI)_第1张图片


私有定时器的使用

几种定时器中,私有定时器是最常用的,使用双核时可能会用到全局定时器。私有定时器是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;
}

相关API函数

查阅PPI列表,可以看到私有定时器的中断号为29。

学会Zynq(9)定时器使用示例(PPI)_第2张图片

1.私有定时器初始化

初始化部分中,我们看到了似曾相识的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);

2.加载计数周期

使用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中宏定义。

3.设置装载模式

本例程需要定时器一直工作,因此使用XScuTimer_EnableAutoReload()函数启用自动装载模式。不需要时使用XScuTimer_DisableAutoReload()函数禁用自动装载。这两个函数本质上也是宏定义,操作相关寄存器中的相应bit位。下面只给出等效的C语言模型:

void XScuTimer_EnableAutoReload(XScuTimer *InstancePtr)
void XScuTimer_DisableAutoReload(XScuTimer *InstancePtr)

4.设置定时器中断

这部分通过自定义函数来设置定时器的中断,过程和第8篇中PL中断的初始化过程基本相同。实际上用到中断时基本都要有这个过程:初始化中断控制器、设置中断异常、连接中断、使能中断。
我们这里使用的是定时器中断,连接函数如下。在定时器中断处理程序中,我们必须清除中断标志,因此XscuGic_Connect的第三个参数设置为定时器的控制设备,在调用中断状态清除函数时会用到该参数。

XScuGic_Connect(GicInstancePtr, TimerIntrId,
   (Xil_ExceptionHandler)TimerIntrHandler, (void *)TimerInstancePtr);

5.清除定时器中断

中断处理程序中,除了计时和串口打印输出,还要调用XScuTimer_ClearInterruptStatus()函数清除中断状态。相关代码如下:

XScuTimer *TimerInstancePtr = (XScuTimer *) CallBackRef;
XScuTimer_ClearInterruptStatus(TimerInstancePtr);

该函数本质上也是宏定义,等效的C语言接口如下:

void XScuTimer_ClearInterruptStatus(XScuTimer *InstancePtr)

传入的XscuTimer*类型的参数在连接定时器中断时设置。使用时首先要将其从void*类型转换为XscuTimer*类型。

你可能感兴趣的:(FPGA,Zynq)