软件版本:VIVADO2017.4
操作系统:WIN10 64bit
硬件平台:适用米联客 ZYNQ系列开发板
米联客(MSXBO)论坛:www.osrc.cn答疑解惑专栏开通,欢迎大家给我提问!!
8.1 概述
本课将中断对于实时系统是非常重要。本课简要介绍了ZYNQ的中断原理和中断类型,详细介绍了私有定时器,在ZYNQ的纯PS里实现私有定时器中断。每隔一秒钟中断一次,在中断函数里计数加1,通过串口打印输出。
8.2 中断原理
中断对于保证任务的实时性非常必要,ZYNQ中集成了中断控制器GIC(Generic Interrupt Controller).GIC可以接受I/O外设中断IOP和PL中断,将这些中断发给CPU。
中断体系结构框图图下:
8.2.1 软件中断(SGI)
SGI通过写ICDSGIR寄存器产生SGI.
8.2.2 共享中断SPI
通过PS和PL内各种I/O和存储器控制器产生。
8.2.3 私有中断(PPI)
包含:全局定时器,私有看门狗定时器,私有定时器以及来自PL的FIQ/IRQ。本文主要介绍PPI,其它的请参考官方手册ug585_Zynq_7000_TRM.pdf。
ZYNQ每个CPU连接5个私有外设中断,所有中断的触发类型固定不变。并且来自PL的快速中断信号FIQ和中断信号IRQ反向。尽管在ICDICFR1寄存器内反应它们是低电平触发,但是PS-PL接口中为高电平触发。如图所示:
8.2.4 私有定时器
ZYNQ每个ARM core都有自己的私有定时器,私有定时器的工作频率为CPU的一半。ARM工作频率为666MHZ,则私有定时器的频率为333MHz.
私有定时器的特性如下:
(1)32位计数器,达到零时产生一个中断
(2)8位预分频计数器,可以更好的控制中断周期
(3)可配置一次性或者自动重加载模式
(4)定时器时间可以通过下式计算:
定时时间 = 1/定时器频率*(预加载值+1)
8.3 搭建FPGA BD工程
Step1:新建一个名为为Miz_sys的工程。
Step2:创建一个BD文件,并命名为system,添加并且配置好ZYNQ IP。读者需要根据自己的硬件类型配置好输入时钟频率、内存型号、串口,连接时钟等。新手不清楚这些内容个,请参考“CH01 HelloWold/DDR/网口测试及固化”这一节课。
Step5:单击窗口上的运行按钮,运行程序。
8.6 实验结果
系统运行结果如下图所示:
8.7 程序分析
本节课封装了sys_intr中断函数,以及timer_intr中断函数,其中,sys_intr负责控制整个中断的启动,而timer_intr函数负责管理timer中断。
8.7.1 timer_intr.c
#include "timer_intr.h"
volatile int usec;
static void TimerIntrHandler(void *CallBackRef) {
XScuTimer *TimerInstancePtr = (XScuTimer *) CallBackRef; XScuTimer_ClearInterruptStatus(TimerInstancePtr); usec++; printf(" %d Second\n\r",usec); //每秒打印输出一次 } void Timer_start(XScuTimer *TimerPtr) { XScuTimer_Start(TimerPtr); }
void Timer_Setup_Intr_System(XScuGic *GicInstancePtr,XScuTimer *TimerInstancePtr, u16 TimerIntrId) { 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 }
int Timer_init(XScuTimer *TimerPtr,u32 Load_Value,u32 DeviceId) { XScuTimer_Config *TMRConfigPtr; //timer config //私有定时器初始化 TMRConfigPtr = XScuTimer_LookupConfig(DeviceId); XScuTimer_CfgInitialize(TimerPtr, TMRConfigPtr,TMRConfigPtr->BaseAddr); //XScuTimer_SelfTest(&Timer); //加载计数周期,私有定时器的时钟为CPU的一半,为333MHZ,如果计数1S,加载值为1sx(333x1000x1000)(1/s)-1=0x13D92D3F XScuTimer_LoadTimer(TimerPtr, Load_Value);//F8F00600+0=reg=F8F00600 //自动装载 XScuTimer_EnableAutoReload(TimerPtr);//F8F00600+8=reg=F8F00608
return 1; } |
8.7.1.1 Timer_init 函数
int Timer_init(XScuTimer *TimerPtr,u32 Load_Value,u32 DeviceId)函数负责初始化Timer
下面对Timer_init中的函数做一些介绍,同时也是对如何分析XILINX 库函数做一些介绍:
1、ScuTimer_LookupConfig函数分析:
ScuTimer_LookupConfig(DeviceId)负责查看parameters.h里面是否有关于Timer的资源分配
ScuTimer_LookupConfig函数的定义如下
在parameters.h可以看到XscuTimer_ConfigTable的定义如下,包括了外设的ID号、基地址、DIST基地址,只要我们在FPGA硬件电路上添加了定时器,那这些参数就会自动被添加,定时器的参数也将会自动生成。
因此当调用ScuTimer_LookupConfig会对TMRConfigPtr初始化,TMRConfigPtr。
2、XScuTimer_CfgInitialize函数分析:
XScuTimer_CfgInitialize(TimerPtr, TMRConfigPtr,TMRConfigPtr->BaseAddr);负责初始化Timer对象。
函数定义如下:
Xil_AssertNonvoid(InstancePtr= !NULL)和Xil_AssertNonvoid(ConfigPtr= !NULL)用来检测传递进来的参数是否是空的,如果是,则不能正常跳转到下一个语句。
接下来的一句是检测定时器是否已经初始化,如果没有,就跳到if中的语句里面。否则,返回一个已经初始化了的标志。接下来我们看到if语句里面的程序。
程序把配置指针中的设备id拷贝进入了定时器的实例结构中的DeviceId。接着把程序的最后一个参数EffectiveAddress(这个就是定时器的基地址)也传递到了定时器的实例结构中的BaseAddr,紧接着把实例结构里的IsStarted标志置为0,再之后把实例结构中的IsReady标志置为XIL_COMPONENT_IS_READY。最后再给Status变量赋值为XST_SUCCUSS。可以看出来,定时器的一系列的初始化都是围绕着这个实例结构来的。那么,我们就来看看这个实例结构到底是什么?我们在主函数中找到这个实例结构。
static XScuTimer Timer;//timer
在这里,这个实例结构是指向一个结构体的,查看这个结构体的内容。
可以看到,这个结构体中又包含了一个结构体,我们再继续查看它包含的这个结构体。
3、XScuTimer_LoadTimer函数
XScuTimer_LoadTimer(TimerPtr, Load_Value);加载定时器的初值
本课程将程序的定时时间设置为了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中查找一下这个寄存器地址,看看这个寄存器是干什么用的。
4、XScuTimer_EnableAutoReload函数
XScuTimer_EnableAutoReload(TimerPtr);使能定时器的重载功能
这段程序与上一句差不多一致,我们通过分析寄存器,看看这段程序完成的功能。这段程序的寄存器地址为:F8F00600+08=F8F00608。这段程序写入的数据为:(F8F00600+08)|0x00000002=F8F0060A。查找ug585看看寄存器的功能。
这个寄存器是把中断的预加载值设置为了自动装载模式(也就是中断一次过后,系统又会自动的装入初值,不用人工载入初值),也就是图中用方框圈出的部分。
8.7.1.2 Timer_Setup_Intr_System函数
void Timer_Setup_Intr_System(XScuGic *GicInstancePtr,XScuTimer *TimerInstancePtr, u16 TimerIntrId);负责初始化定时器中断,这个函数带了三个参数:第一个参数指向了中断控制器,第二个指向的是定时器,第三个是中断号。将鼠标放在中断号上面时,我们可以发现中断号为29。我们可以在ug585的中断部分查看一下中断号29是什么类型的中断。
可以看到这是定时器中断,上升沿触发的。这样定义是有一定依据的。这段程序与上一课PL_PS中断是差不多的,我们上一课对其进行过详细的分析,大家可参照上一课介绍的方法对其进行分析。
8.7.1.3 Timer_start函数
void Timer_start(XScuTimer *TimerPtr) 用来启动定时器,在Timer_start中调用了XScuTimer_Start(TimerPtr)
可以看出来,这也是一个通过读写寄存器的方式来操作定时器的过程,我们依然是来分析一下寄存器。
之前的一些初始化程序就跳过不再讲解了,直接看到上图所示的程序,这个程序的分析与我们刚才讲的装载初值的方法是一样的,这里我们可以直接计算此程序读出的寄存器地址:F8F00600+08=F8F00608。
这一句的意思就是把刚才得到的寄存器的地址与0x00000001或操作。此时寄存器地址为:F8F00608 | 0x00000001U =F8F00609。
这里我们发现,上图中这个函数的源程序中,第一个参数即为我们第一次得到的寄存器地址,写入的数据为第二次得到的寄存器地址。也就是说向F8F00608这个寄存器里写入数据F8F00609。查看ug585,看看这么配置是什么意思。
此时就可以清晰的知晓,通过控制这个寄存器的最后一位,就可以控制定时器的工作与否,刚才我们写入的是F8F00609,将最后一位置1,也就是启动了定时器。
8.7.1.4 TimerIntrHandler函数
定时器中断产生后,会调用void TimerIntrHandler(void *CallBackRef)函数。
8.7.2 sys_intr.c
/*sys_intr.c * Created on: 2016年11月22日 * www.osrc.cn * copyright by nan jin mi lian dian zi www.osrc.cn */ #include "sys_intr.h" void Setup_Intr_Exception(XScuGic * IntcInstancePtr) { /* Enable interrupts from the hardware */ Xil_ExceptionInit(); Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, (void *)IntcInstancePtr);
Xil_ExceptionEnable(); }
int Init_Intr_System(XScuGic * IntcInstancePtr) { int Status;
XScuGic_Config *IntcConfig; /* * Initialize the interrupt controller driver so that it is ready to * use. */ IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID); if (NULL == IntcConfig) { return XST_FAILURE; }
Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig, IntcConfig->CpuBaseAddress); if (Status != XST_SUCCESS) { return XST_FAILURE; } return XST_SUCCESS; } |
8.7.2.1 Init_Intr_System函数
int Init_Intr_System(XScuGic * IntcInstancePtr)负责初始化全局中断控制器
8.7.2.2 Setup_Intr_Exception函数
void Setup_Intr_Exception(XScuGic * IntcInstancePtr)函数负责启动全局中断控制器
8.8.3 main.c
#include "timer_intr.h" #include "sys_intr.h" static XScuGic Intc; //GIC static XScuTimer Timer;//timer
#define TIMER_LOAD_VALUE 0x13D92D3F //1S
int init_intr_sys(void) {
Timer_init(&Timer,TIMER_LOAD_VALUE,0); Init_Intr_System(&Intc); // initial interrupt system Setup_Intr_Exception(&Intc); Timer_Setup_Intr_System(&Intc,&Timer,TIMER_IRPT_INTR); Timer_start(&Timer); }
int main(void) { printf("------------START-------------\n"); init_intr_sys();
while(1); } |
本课程对timer中断,以及系统中断控制器部分做了封装,方便后面在项目中管理调用,main函数演示了如何对系统中断控制器以及Timer定时器的初始化和使用。