和所有的MCU一样ZYNQ的PS端也有自己的定时器中断功能,本节将对该功能进行演示。
- 此章节内容适用于Lemon ZYNQ主板,如是其他板子请看对应板子目录
- 本文在 vivado2018.3版本上演示
定时器中断可以为系统提供标准的时钟,心跳,也可以用来作为为一些计算的时间基准(如脉冲宽度,脉冲周期等)在实际项目中都是必不可少的。
ZYNQ中每个ARM core都有自己的私有定时器,私有定时器的工作频率为CPU的一半,如ZYNQ的工作频率为666MHZ,则定时器的频率为333MHz,后面需要注意这个时钟。
本节内容我们将完成定时器中断实验,并了解PS 端私有定时器的使用方法。
一、实验内容:
我们在PS端设置一个定时器,并让定时器每计数满1ms触发一次中断,并且每累计一秒钟的时候点亮或者熄灭一个LED灯,来达到演示的效果。
所以整个系统我们只需要在BLOCK DESIGN 中 zynq核内 拓展一个EMIO 的GPIO口,并用PS的SDK 程序的定时器每秒去驱动这个GPIO即可。
二、工程创建
工程创建的过程可以参考试验一中的内容,这里不再详细描述了。基于Lemon ZYNQ的PS实验一 GPIO之用EMIO方式点亮LED(完整图文)(芯片型号选XC7Z020CLG400-1)
三、Vivado 中的设置
1)在BLOCK DESIGN 中搜索并添加一个ZYNQ模块
2)在ZYNQ 设置界面,设置 DDR MT41K256M16RE-125的型号 和位宽16bit
3)因为Lemon ZYNQ主板的PS时钟是50M的晶振输入的,所以这里需要把默认的PS输入时钟33.33M改成50M
4)添加一个GPIO的接口(EMIO方式)为后面点亮LED做准备
5)记录下 CPU的时钟(这里不需要修改 只需要看一下记录下当前时钟频率就好)
6)对GPIO make external,并且连接AXI的时钟(AXI默认开启的,这里用不到AXI,也可以去设置里关闭AXI功能,就不需要连接了)
7)点选Create HDL Wrapper
8)之后创建和添加约束文件对管脚进行定义:
set_property PACKAGE_PIN R14 [get_ports {GPIO_0_0_tri_io[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {GPIO_0_0_tri_io[0]}]
9) 之后对程序进行正常的编译综合,并且导出 Export Hardware (勾选 include bitstream)供SDK加载
四、PS部分程序设计:
在SDK环境下建立一个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; }
五、下载与验证
1)新创建的工程最好先对Run as 进行配置:右键工程,并点选Run As -> Run Configurations
2) 在弹出的窗口中对Reset entire system 和 Program FPGA两个选项进行勾选操作,这样就不会出现下载程序debug的时候概率性不工作的问题了。(这样操作后系统会自动对FPGA进行配置,不需要按之前工程手动对FPGA进行编程了)
PS如果没有出现下图对话框,可以直接双击左侧的applicationo (System Debugger)。
3)设置好之后,选择Run As
-> Launch on Hardware (System Debugger)
进行调试。
下载到我们的板子上,可以看到 主板上的LED 灯每秒钟闪烁一次, 说明我们的定时器程序正常在工作了。程序比较简单,仅供参考学习,对于ZYNQ的定时器来说,还有很多功能(包括用PL去触发定时器中断等),需要实际做项目的时候自行去拓展了。
- 本文的完整工程下载:05_PS_TIMER_TEST
- VIVADO的版本:2018.3
- 工程创建目录: E:\Lemon_ZYNQ\SDK\05_PS_TIMER_TEST