[X]关闭
0

S02-CH08 定时器中断实验

摘要: 软件版本:VIVADO2017.4操作系统:WIN10 64bit硬件平台:适用米联客 ZYNQ系列开发板米联客(MSXBO)论坛:www.osrc.cn答疑解惑专栏开通,欢迎大家给我提问!!8.1 概述 本课将中断对于实时系统是非常重要。本课简要 ...

软件版本: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定时器的初始化和使用。


路过

雷人

握手

鲜花

鸡蛋

最新评论

本文作者
2019-9-6 19:18
  • 7
    粉丝
  • 2218
    阅读
  • 0
    回复

关注米联客

扫描关注,了解最新资讯

联系人:汤经理
电话:0519-80699907
EMAIL:270682667@qq.com
地址:常州溧阳市天目云谷3号楼北楼201B
热门评论
排行榜