和所有的MCU一样ZYNQ的PS端也有自己的定时器中断功能,本节将对该功能进行演示。
定时器中断可以为系统提供标准的时钟,心跳,也可以用来作为为一些计算的时间基准(如脉冲宽度,脉冲周期等)在实际项目中都是必不可少的。
ZYNQ中每个ARM core都有自己的私有定时器,私有定时器的工作频率为CPU的一半,如ZYNQ的工作频率为666MHZ,则定时器的频率为333MHz,后面需要注意这个时钟。
系统设计:
系统方面我们在PS端设置一个定时器,并让定时器每计数满1ms触发一次中断,并且每累计一秒钟的时候点亮或者熄灭一个LED灯,来达到演示的效果。
所以整个系统我们只需要在BLOCK DESIGN 中 zynq核内 拓展一个EMIO 的GPIO口,并用PS的SDK 程序的定时器每秒去驱动这个GPIO即可。
EMIO的工程创建这里 简略写,完整的图文教程可以看前面的例程,Tiny ZYNQ板 工程七 用ZYNQ的PS点亮连接到PL端的LED灯EMIO 方式
在ZYNQ 设置界面,设置 DDR MT41K128M16JT的型号 和位宽16bit
添加一个GPIO的接口(EMIO方式)为后面点亮LED做准备
第三 记录下 CPU的时钟(这里不需要修改 只需要看一下记录下当前时钟频率就好)
增加并设置LED的管脚约束(D18 LVCMOS33)
设下的对项目行编译综合就好
程序设计:
建立一个TIMER_TEST空的工程,并且添加main.c 添加如下代码:
#include "xparameters.h" #include "xgpiops.h" #include "xstatus.h" #include "xplatform_info.h" #include "xscutimer.h" #include "Xscugic.h" #define LED 54 #define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID XGpioPs Gpio; void Gpio_Init(void){ XGpioPs_Config *ConfigPtr; ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID); XGpioPs_CfgInitialize(&Gpio, ConfigPtr,ConfigPtr->BaseAddr); XGpioPs_SetDirectionPin(&Gpio, LED, 1); XGpioPs_SetOutputEnablePin(&Gpio, LED, 1); XGpioPs_WritePin(&Gpio, LED, 0); } #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 0x514c7 //666*1000*1/2 666mhz static XScuGic Intc; //GIC static XScuTimer Timer;//timer static void SetupInterruptSystem(XScuGic *GicInstancePtr, XScuTimer *TimerInstancePtr, u16 TimerIntrId); static void TimerIntrHandler(void *CallBackRef); 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); //connect to the hardware Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, GicInstancePtr); //set up the timer interrupt XScuGic_Connect(GicInstancePtr, TimerIntrId, (Xil_ExceptionHandler)TimerIntrHandler, (void *)TimerInstancePtr); //enable the interrupt for the Timer at GIC XScuGic_Enable(GicInstancePtr, TimerIntrId); //enable interrupt on the timer XScuTimer_EnableInterrupt(TimerInstancePtr); // Enable interrupts in the Processor. Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ); } volatile int ms_count; unsigned char led_state=0; static void TimerIntrHandler(void *CallBackRef){ static int ms_count = 0; //计数 XScuTimer *TimerInstancePtr = (XScuTimer *) CallBackRef; XScuTimer_ClearInterruptStatus(TimerInstancePtr); ms_count++; if(ms_count>=1000){ ms_count=0; if(led_state==0)led_state=1; else led_state=0; XGpioPs_WritePin(&Gpio, LED, led_state); } } void Timer_Init(){ XScuTimer_Config *TMRConfigPtr; TMRConfigPtr = XScuTimer_LookupConfig(TIMER_DEVICE_ID); XScuTimer_CfgInitialize(&Timer, TMRConfigPtr,TMRConfigPtr->BaseAddr); XScuTimer_SelfTest(&Timer); XScuTimer_LoadTimer(&Timer, TIMER_LOAD_VALUE); //自动装载 XScuTimer_EnableAutoReload(&Timer); //启动定时器 XScuTimer_Start(&Timer); //set up the interrupts SetupInterruptSystem(&Intc,&Timer,TIMER_IRPT_INTR); } int main(void) { Gpio_Init(); Timer_Init(); while(1); return 0; }
代码简单介绍 :
Timer_Init(); 是定时器的初始化函数,在系统运行的时候,需要对定时器初始化才能正常工作,其中TIMER_LOAD_VALUE 用来控制 每一次定时器触发的时间, 还记得我们前面的CPU时钟为666mhz吗,那我们的定时器时钟就相当于工作在1/2的CPU时钟即333mhz频率下。 我们的系统设置是每1ms进入一次中断,那1ms就相当于需要计数(333000000)*1/1000=333000个周期,那我们就设置TIMER_LOAD_VALUE 为 333000-1,即0x514c7 (这里代表1ms的数值)
TimerIntrHandler 是定时器的回调函数,相当于单片机的中断服务函数,每当中断被触发,就会调用一次该回调函数。 因为是1ms进入一次,所以假设我们需要计数1S钟,就需要额外增加一个变量ms_count
每次进入该回调函数 ms_count都自增,当大于等于1000的时候让ms_count归零完成一次循环,同时执行我们的LED变换操作。 如果我们有其他程序需要添加,也可以增加到 TimerIntrHandler的回调函数钟。
ms_count++; if(ms_count>=1000){ ms_count=0; if(led_state==0)led_state=1; else led_state=0; XGpioPs_WritePin(&Gpio, LED, led_state); }
对于main函数来说,我们只需要执行GPIO 和定时器的初始化就好
int main(void){ Gpio_Init(); Timer_Init(); while(1); return 0; }
下载到我们的板子上,可以看到 主板上的LED 灯每秒钟闪烁一次, 说明我们的定时器程序正常在工作了。
程序比较简单,仅供参考学习,对于ZYNQ的定时器来说,还有很多功能(包括用PL去触发定时器中断等),需要实际做项目的时候自行去拓展了。
完整工程如下(供学习参考):