S02_CH08_ ZYNQ 定时器中断实验
上一章实现了PS接受来自PL的中断,本章将在ZYNQ的纯PS里实现私有定时器中断。每隔一秒中断一次,在中断函数里计数加1,通过串口打印输出。
8.1中断原理
中断对于保证任务的实时性非常必要,在ZYNQ里集成了中断控制器GIC(Generic Interrupt Controller).GIC可以接受I/O外设中断IOP和PL中断,将这些中断发给CPU。
中断体系结构框图图下:
8.1.1软件中断(SGI)
SGI通过写ICDSGIR寄存器产生SGI.
8.1.2共享中断SPI
通过PS和PL内各种I/O和存储器控制器产生。
8.1.3私有中断(PPI)
包含:全局定时器,私有看门狗定时器,私有定时器以及来自PL的FIQ/IRQ。本文主要介绍PPI,其它的请参考官方手册ug585_Zynq_7000_TRM.pdf。
ZYNQ每个CPU链接5个私有外设中断,所有中断的触发类型都是固定不变的。并且来自PL的快速中断信号FIQ和中断信号IRQ反向,然后送到中断控制器因此尽管在ICDICFR1寄存器内反应的他们是低电平触发,但是PS-PL接口中为高电平触发。如图所示:
8.1.4私有定时器
zynq中每个ARM core都有自己的私有定时器,私有定时器的工作频率为CPU的一半,比如Miz702的ARM工作频率为666MHZ,则私有定时器的频率为333MHz.
私有定时器的特性如下:
(1)32位计数器,达到零时产生一个中断
(2)8位预分频计数器,可以更好的控制中断周期
(3)可配置一次性或者自动重加载模式
(4)定时器时间可以通过下式计算:
定时时间 = 1/定时器频率*(预加载值+1)
8.2 搭建硬件工程
Step1:新建一个名为为Miz_sys的工程,芯片类型根据自身情况设置。
Step2:创建一个BD文件,并命名为system。
Step3:添加 ZYNQ7 Processing System,根据自己的硬件类型配置好输入时钟频率与内存型号。
Step4:在ZYNQ7 Processing System配置窗口中,使能中断,单击OK完成配置。
Step5:单击添加IP按钮,添加两个逻辑门模块和一个concat IP。
Step6:双击逻辑门模块,将其配置为非功能。
Step7:按以下电路,完善整体电路。
Step8:右键单击Block文件,文件选择Generate the Output Products。
Step9:右键单击Block文件,选择Create a HDL wrapper,根据Block文件内容产生一个HDL 的顶层文件,并选择让vivado自动完成。
Step10:添加一个约束文件,打开对应自己硬件的原理图,查看按键部分引脚连接情况,完成约束。Miz702约束文件如下所示:
set_property PACKAGE_PIN T18 [get_ports {SW1[0]}] set_property IOSTANDARD LVCMOS33 [get_ports {SW1[0]}] set_property PACKAGE_PIN R18 [get_ports {SW2[0]}] set_property IOSTANDARD LVCMOS33 [get_ports {SW2[0]}] |
Step10:生成Bit文件。
8.3 加载到SDK
Step1:导出硬件。
Step2:新建一个空SDK工程,并添加一个main.c的文件。
Step3:在main.c文件中添加以下程序,按Ctrl+S保存后自动开始编译。
/* * 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 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 ",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("------------START------------- "); //私有定时器初始化 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; } |
Step4:右击工程,选择Debug as ->Debug configuration。
Step5:选中system Debugger,双击创建一个系统调试。
Step6:设置系统调试。
打开系统自带的窗口调试助手,点击运行按钮开始运行程序。
系统运行结果如下图所示:
8.4 程序分析
本章的程序讲解依然是从main函数处开始。首先我们看看整个程序的结构。
程序一开始的指针和测试程序就不再啰嗦了。接下来的查找配置程序也与我们上一章PL_PS中断是一样的,只是换了个函数名字与基地址而已。还未掌握的可以看看我们上一章的分析。
接下来看到定时器的初始化程序,直接跟踪这个程序,查看其定义。如下图所示:
Xilinx官方提供的程序,开头都会给出程序的功能和参数的注释,若是不懂程序是什么意思,不妨先看看这些。从图片上的程序功能注释来看,这是一个指定定时器的初始化函数,在这个定时器被其他函数调用之前,这个函数必须先被调用。也就是说必须先进行定时器的初始化,定时器才能正常的使用。接下来看到程序部分。程序一开始用了一个特定的函数来检测传递进来的参数是否是空的,如果是,则不能正常跳转到下一个语句。
接下来的一句是检测定时器是否已经开始了(也就是有没有初始化成功),如果没有,就跳到if中的语句里面。否则,返回一个已经初始化了的标志。接下来我们看到if语句里面的程序。
一开始,程序把配置指针中的设备id拷贝进入了定时器的实例结构中的DeviceId。接着把程序的最后一个参数EffectiveAddress(可以猜到这个是一个基地址,具体是什么现在还不知晓)也传递到了定时器的实例结构中的BaseAddr,紧接着把实例结构里的IsStarted标志置为0,再之后把实例结构中的IsReady标志置为XIL_COMPONENT_IS_READY。最后再给Status变量赋值为XST_SUCCUSS。可以看出来,定时器的一系列的初始化都是围绕着这个实例结构来的。那么,我们就来看看这个实例结构到底是什么?我们在主函数中找到这个实例结构。
在这里,这个实例结构是指向一个结构体的,我们来看看这个结构体的内容。
可以看到,这个结构体中又包含了一个结构体,我们再继续看看它包含的这个结构体。
此处,我们发现这两个结构体中的内容正好是我们刚才初始化程序中配置的那些参数。接下来,我们再来看看这些参数是如何来的。这就得看到刚才我们提到过的查找配置程序了。
这些参数就是通过查找配置这个程序获取的。我们回过来看看这个程序。
从上图可以看到,这些配置是存放在一个数组当中的,让我们继续查看一下数组。
图中的两个对象,是我们parameters.h中系统自动生成的定时器的设备地址和基地址。只要我们在硬件电路上添加了定时器,那这两个参数就会自动被添加,定时器的参数也将会自动生成。
回到main函数的分析,接下来的是一个建立中断的函数,这个函数带了三个参数:第一个参数指向了中断控制器,第二个指向的是定时器,第三个是中断号。将鼠标放在中断号上面时,我们可以发现中断号为29。我们可以在ug585的中断部分查看一下中断号29是什么类型的中断。
可以看到这是定时器中断,上升沿触发的。这样定义是有一定依据的。这段程序与上一章PL_PS中断是差不多的,我们上一章对其进行过详细的分析,大家可参照上一章介绍的方法对其进行分析。
回到main函数,接下来的这句是本章程序中的核心部分。它将程序的定时时间设置为了1秒。那么,系统是如何做到定时一秒的呢?定时器时间可以通过下式计算:定时时间 = 1/定时器频率*(预加载值+1),则可以推算出:预加载值=定时时间*定时器频率-1。定时时间是已知的,如果再知道定时器频率则可以计算出加载的值,查看xilinx的编程指导手册ug585-zynq-7000-TRM的定时器篇得知:
定时器频率为处理器频率的一半,比如Miz702的ARM工作频率为666MHZ,则私有定时器的频率为333MHz,则加载值为1*333_000_000*(1/s)-1=0x13D92D3F。
回到main函数的分析,当我们把鼠标停留在装载加载值函数XScuTimer_LoadTimer上时,SDK会显示关于这个函数的一些信息。
我们看到这个函数的原函数是向一个寄存器地址中写入了预加载值,我们计算一下这个寄存器地址。原函数的第一个参数我们刚才提到过,就是那个实例结构中的基地址,也就是定时器的基地址。我们在xparameters.h中找到它。
此时我们就可以计算了:F8F00600+00=F8F00600。在ug585中查找一下这个寄存器地址,看看这个寄存器是干什么用的。
可以看到,这个寄存器就是个装载预加载值的寄存器。接着看到main函数的下一句。
这段程序与上一句差不多一致,我们通过分析寄存器,看看这段程序完成的功能。这段程序的寄存器地址为:F8F00600+08=F8F00608。这段程序写入的数据为:(F8F00600+08)|0x00000002=F8F0060A。查找ug585看看寄存器的功能。
这个寄存器是一个预加载值控制寄存器,通过写入我们上面分析出的那个数据,把中断的预加载值设置为了自动装载模式(也就是中断一次过后,系统又会自动的装入初值,不用人工载入初值),也就是图中用方框圈出的部分。
回到main函数,讲解最后一个函数,启动定时器的函数。还是先跟踪一下这个函数。
可以看出来,这也是一个通过读写寄存器的方式来操作定时器的过程,我们依然是来分析一下寄存器。
之前的一些初始化程序就跳过不再讲解了,直接看到上图所示的程序,这个程序的分析与我们刚才讲的装载初值的方法是一样的,这里我们可以直接计算此程序读出的寄存器地址:F8F00600+08=F8F00608。
这一句的意思就是把刚才得到的寄存器的地址与0x00000001或操作。此时寄存器地址为:F8F00608 | 0x00000001U =F8F00609。
这里我们发现,上图中这个函数的源程序中,第一个参数即为我们第一次得到的寄存器地址,写入的数据为第二次得到的寄存器地址。也就是说向F8F00608这个寄存器里写入数据F8F00609。查看ug585,看看这么配置是什么意思。
此时就可以清晰的知晓,通过控制这个寄存器的最后一位,就可以控制定时器的工作与否,刚才我们写入的是F8F00609,将最后一位置1,也就是启动了定时器。
8.5 本章小结
中断对于实时系统是非常重要的,可以说是是实时性的保障吧。本章简要介绍了ZYNQ的中断原理和中断类型,详细介绍了私有定时器,建立了完整的工程进行测试。