软件环境:vivado 2017.4 硬件平台:XC7Z020
手册说的很清楚呀,ZYNQ的每个CPU都有自己的私有定时器,私有定时器工作频率是CPU工作频率的一半,即ARM工作频率是666MHz时候,私有定时器的工作频率是333MHz。其主要特性有以下四点:
(1)32位计数器,过零时产生中断。 (2)8位预分频器,能够更好的控制中断周期。
(3)可配置为单次模式或自动重载模式。(4)定时时间=(1/定时器频率)*(预装载值+1)。
另外,在上一章中断控制,表7-3中可以看出,私有定时器是上升沿触发中断。
Vivado 2017.4 Create Block Design后,添加ZYNQ7 Processing system,然后自动连接就行,Generate the output products,Create a HDL wrapper,Generate Bitstream,Export Hardware(注意勾选include biestream),最后launch SDK进行测试。。
SDK中new application然后添加如下测试代码。
/*
* main.c
*
* Created on: 2016年6月26日
* Author: Administrator
*/
#include <stdio.h>
#include "xadcps.h"
#include "xil_types.h"
#include "Xscugic.h"
#include "Xil_exception.h"
#include "xscutimer.h"
//timer info
#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
#define TIMER_LOAD_VALUE 0x13D92D3F //332999999
static XScuGic Intc; //GIC
static XScuTimer Timer; //timer
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; //GIC config
Xil_ExceptionInit();
//initialise the GIC
IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
XScuGic_CfgInitialize(GicInstancePtr, IntcConfig,
IntcConfig->CpuBaseAddress);
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,//connect to the hardware
GicInstancePtr);
XScuGic_Connect(GicInstancePtr, TimerIntrId,
(Xil_ExceptionHandler)TimerIntrHandler,//set up the timer interrupt
(void *)TimerInstancePtr);
XScuGic_Enable(GicInstancePtr, TimerIntrId);//enable the interrupt for the Timer at GIC
XScuTimer_EnableInterrupt(TimerInstancePtr);//enable interrupt on the timer
Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ); //Enable interrupts in the Processor.
}
int main()
{
XScuTimer_Config *TMRConfigPtr; //timer config
printf("------------BEGIN-------------\n");
//私有定时器初始化
TMRConfigPtr = XScuTimer_LookupConfig(TIMER_DEVICE_ID);
XScuTimer_CfgInitialize(&Timer, TMRConfigPtr,TMRConfigPtr->BaseAddr);
//set up the interrupts
SetupInterruptSystem(&Intc,&Timer,TIMER_IRPT_INTR);
//加载计数周期,私有定时器的时钟为CPU的一般,为333MHZ,如果计数1S,加载值为1sx(333x1000x1000)(1/s)-1=0x13D92D3F
XScuTimer_LoadTimer(&Timer, TIMER_LOAD_VALUE);
//自动装载
XScuTimer_EnableAutoReload(&Timer);
//启动定时器
XScuTimer_Start(&Timer);
while(1);
return 0;
}
接下来对程序中的一些函数做一些解释说明:.
先来看几个结构体,一个是程序最开始处定义的XScuTimer,包括定时器的配置,是否初始化完毕,以及是否处于运行状态。
typedef struct {
XScuTimer_Config Config; /**< Hardware Configuration */
u32 IsReady; /**< Device is initialized and ready */
u32 IsStarted; /**< Device timer is running */
} XScuTimer;
另一个是main里面最开始的XScuTimer_Config,包括设备ID和设备的基址。
typedef struct {
u16 DeviceId; /**< Unique ID of device */
u32 BaseAddr; /**< Base address of the device */
} XScuTimer_Config;
XScuTimer_LookupConfig()和XScuTimer_CfgInitialize()就不多说了,这两个函数一个是查找设备ID,看设备是否存在,另一个是设备存在的前提下,获取设备的配置表的基址。
接下来和中断控制初始化时候差不多,结构体XScuGic是配置必须的。Gic(Generic Interrupt Controller)通用中断控制器。
typedef struct
{
XScuGic_Config *Config; /**< Configuration table entry */
u32 IsReady; /**< Device is initialized and ready */
u32 UnhandledInterrupts; /**< Intc Statistics */
} XScuGic;
XScuGic_Config,包括设备ID号,基址,向量表。
typedef struct
{
u16 DeviceId; /**< Unique ID of device */
u32 CpuBaseAddress; /**< CPU Interface Register base address */
u32 DistBaseAddress; /**< Distributor Register base address */
XScuGic_VectorTableEntry HandlerTable[XSCUGIC_MAX_NUM_INTR_INPUTS];/**<
Vector table of interrupt handlers */
} XScuGic_Config;
Xil_ExceptionInit()在这实际是个空函数,里面直接return的,据函数前的说明意思是这函数是个通用API,用于在所有支持的arm处理器上初始化异常处理程序,还放在这里主要是为了避免与之前软件版本产生意外的兼容性问题。
XScuGic_LookupConfig()和XScuGic_CfgInitialize()是与通用中断控制器相关的查找设备ID和获取设备配置表基址的函数,与前面定时器相关的XScuTimer_LookupConfig()和XScuTimer_CfgInitialize()可以看出来,各自配置各自的,各自有各自的初始化函数,分开配置,分开调用。
Xil_ExceptionRegisterHandler()处理特定异常时用的。在上一章里已经说过了,这就不多哔哔了。
void Xil_ExceptionRegisterHandler(u32 Exception_id,
Xil_ExceptionHandler Handler,
void *Data)
{
XExc_VectorTable[Exception_id].Handler = Handler;
XExc_VectorTable[Exception_id].Data = Data;
}
绑定的这个向量表理所当然也是用于异常处理的。
XExc_VectorTableEntry XExc_VectorTable[XIL_EXCEPTION_ID_LAST + 1] =
{
{Xil_ExceptionNullHandler, NULL},
{Xil_UndefinedExceptionHandler, NULL},
{Xil_ExceptionNullHandler, NULL},
{Xil_PrefetchAbortHandler, NULL},
{Xil_DataAbortHandler, NULL},
{Xil_ExceptionNullHandler, NULL},
{Xil_ExceptionNullHandler, NULL},
};
XScuGic_Connect()将定时器中断的中断函数和中断控制器绑定,当定时器触发中断时,找TimerIntrHandler()中断处理函数。
s32 XScuGic_Connect(XScuGic *InstancePtr, u32 Int_Id,
Xil_InterruptHandler Handler, void *CallBackRef)
{
/*
* Assert the arguments
*/
Xil_AssertNonvoid(InstancePtr != NULL);
Xil_AssertNonvoid(Int_Id < XSCUGIC_MAX_NUM_INTR_INPUTS);
Xil_AssertNonvoid(Handler != NULL);
Xil_AssertNonvoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);
/*
* The Int_Id is used as an index into the table to select the proper
* handler
*/
InstancePtr->Config->HandlerTable[Int_Id].Handler = Handler;
InstancePtr->Config->HandlerTable[Int_Id].CallBackRef = CallBackRef;
return XST_SUCCESS;
}
XScuGic_Enable()使能定时器中断。
void XScuGic_Enable(XScuGic *InstancePtr, u32 Int_Id)
{
u32 Mask;
/*
* Assert the arguments
*/
Xil_AssertVoid(InstancePtr != NULL);
Xil_AssertVoid(Int_Id < XSCUGIC_MAX_NUM_INTR_INPUTS);
Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);
/*
* The Int_Id is used to create the appropriate mask for the
* desired bit position. Int_Id currently limited to 0 - 31
*/
Mask = 0x00000001U << (Int_Id % 32U);
/*
* Enable the selected interrupt source by setting the
* corresponding bit in the Enable Set register.
*/
XScuGic_DistWriteReg(InstancePtr, (u32)XSCUGIC_ENABLE_SET_OFFSET +
((Int_Id / 32U) * 4U), Mask);
}
XScuTimer_EnableInterrupt()使能定时器中断,大概过程是,将XSCUTIMER_CONTROL_IRQ_ENABLE_MASK = 0x00000004U或进定时器控制寄存器(基址Config.BaseAddr+偏移量XSCUTIMER_CONTROL_OFFSET = 0xF8F00608)。
#define XScuTimer_EnableInterrupt(InstancePtr) \
XScuTimer_WriteReg((InstancePtr)->Config.BaseAddr, \
XSCUTIMER_CONTROL_OFFSET, \
(XScuTimer_ReadReg((InstancePtr)->Config.BaseAddr, \
XSCUTIMER_CONTROL_OFFSET) | \
XSCUTIMER_CONTROL_IRQ_ENABLE_MASK))
Xil_ExceptionEnableMask()整个中断使能呗。
#define Xil_ExceptionEnableMask(Mask) \
mtcpsr(mfcpsr() & ~ ((Mask) & XIL_EXCEPTION_ALL))
接下来就回到主函数了,XScuTimer_LoadTimer()定时器装载初始值。TIMER_LOAD_VALUE=0x13D92D3F=329999999,计算方法前面已经说过了。被设置的寄存器0xF8F00600。
#define XScuTimer_LoadTimer(InstancePtr, Value) \
XScuTimer_WriteReg((InstancePtr)->Config.BaseAddr, \
XSCUTIMER_LOAD_OFFSET, (Value))
XScuTimer_EnableAutoReload()使能自动重装载。被设置的寄存器依旧是0xF8F00608。
#define XScuTimer_EnableAutoReload(InstancePtr) \
XScuTimer_WriteReg((InstancePtr)->Config.BaseAddr, \
XSCUTIMER_CONTROL_OFFSET, \
(XScuTimer_ReadReg((InstancePtr)->Config.BaseAddr, \
XSCUTIMER_CONTROL_OFFSET) | \
XSCUTIMER_CONTROL_AUTO_RELOAD_MASK))
XScuTimer_Start()最后启动定时器。
void XScuTimer_Start(XScuTimer *InstancePtr)
{
u32 Register;
Xil_AssertVoid(InstancePtr != NULL);
Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);
/*
* Read the contents of the Control register.
*/
Register = XScuTimer_ReadReg(InstancePtr->Config.BaseAddr,
XSCUTIMER_CONTROL_OFFSET);
/*
* Set the 'timer enable' bit in the register.
*/
Register |= XSCUTIMER_CONTROL_ENABLE_MASK;
/*
* Update the Control register with the new value.
*/
XScuTimer_WriteReg(InstancePtr->Config.BaseAddr,
XSCUTIMER_CONTROL_OFFSET, Register);
/*
* Indicate that the device is started.
*/
InstancePtr->IsStarted = XIL_COMPONENT_IS_STARTED;
}
最后, TimerIntrHandler()定时器中断函数,程序想要达到的效果是每秒中断进入一次,然后在串口处输出1 second...2 second...等时间计时,可以看见,每次进入时候sec自加一次。记得在里面清除中断标志XScuTimer_ClearInterruptStatus(),不然就一直处于中断状态。
static void TimerIntrHandler(void *CallBackRef)
{
static int sec = 0; //计数清零
XScuTimer *TimerInstancePtr = (XScuTimer *) CallBackRef;
XScuTimer_ClearInterruptStatus(TimerInstancePtr);
sec++;
printf(" %d Second\n\r",sec); //每秒打印输出一次
}
XScuTimer_ClearInterruptStatus()记得在中断处理函数里面清除中断标志,不然就一直处于中断状态。
#define XScuTimer_ClearInterruptStatus(InstancePtr) \
XScuTimer_WriteReg((InstancePtr)->Config.BaseAddr, \
XSCUTIMER_ISR_OFFSET, XSCUTIMER_ISR_EVENT_FLAG_MASK)
所以在最后总结一下TIMER,在SDK中大致的流程:
通过XScuTimer_LookupConfig函数,找到TIMER的基址------------>
通过XScuTimer_CfgInitialize函数,初始化TIMER配置------------>
通过Xil_ExceptionInit函数,避免与前版本有兼容性问题------------>
通过XScuGic_LookupConfig函数,找到通用中断控制器基址------------>
通过XScuGic_CfgInitialize函数,初始化通用中断控制器------------>
通过Xil_ExceptionRegisterHandler函数,使能异常中断处理------------>
通过XScuGic_Connect函数,绑定定时器中断处理函数------------>
通过XScuGic_Enable函数,在通用中断控制器中使能定时器中断------------>
通过XScuTimer_EnableInterrupt函数,使能定时器中断------------>
通过Xil_ExceptionEnableMask函数,使能中断------------>
通过XScuTimer_LoadTimer函数,装载定时器初值------------>
通过XScuTimer_EnableAutoReload函数,使能自动重装载------------>
通过XScuTimer_Start函数,启动定时器。