|
本帖最后由 正点原子 于 2020-10-24 10:22 编辑
1)实验平台:正点原子领航者ZYNQ开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_linhanz.html
4)对正点原子FPGA感兴趣的同学可以加群讨论:876744900
5)关注正点原子公众号,获取最新资料
第九章定时器中断实验
定时器作为PS的重要组成部分,可以不受CPU的干预,自己独立运行,来完成计时、定时、中断以及计算来自MIO或EMIO引脚的信号脉冲宽度等。本章我们将向大家介绍定时器以及定时器中断的使用方法。
本章包括以下几个部分:
11.1简介
1.2实验任务
1.3硬件设计
1.4软件设计
1.5下载验证
简介
在ZYNQ嵌入式系统中,定时器的资源是非常丰富的,每个Cortex-A9处理器都有各自独立的32位私有定时器和32位看门狗定时器,这两个CPU同时共享一个64位的全局定时器(GT)。除此之外,PS中还有一个24位的系统看门狗定时器(SWDT)和两个TTC(Triple Timer Counters)。系统看门狗定时器可以在系统发生灾难性的故障时(如PS中的PLL工作异常)发出信号,使得系统程序重新启动,保证了系统安全可靠的运行。TTC用于计算来自MIO引脚或EMIO引脚的信号脉冲宽度,每个TTC都有三个独立的计数器。
定时器的系统框图如图 9.1.1所示:
图 9.1.1 定时器系统框图
图中的几种定时器都有连接到中断控制器(Interrupt Controller),我们可以很方便的使用定时器来完成定时器中断的实验。其中私有定时器(CPU Private Timer)是最为常用的,本次实验是基于私有定时器来完成定时器的中断实验。
私有定时器的时钟频率为CPU时钟频率的一半,如ARM的工作时钟频率为666.666Mhz,则私有定时器的时钟频率为333.333Mhz。私有定时器的特性如下:
1、32位计数器,当计数器递减至0后产生中断;
2、8位预分频计数器,可以更好的控制中断周期;
3、可以配置单次定时或者自动重载模式;
4、通过配置起始计数值来设置定时时间。
实验任务
本章的实验任务是通过定时器的中断,每200ms控制一次PS LED灯的亮灭。
硬件设计
从实验任务我们可以画出如下的系统框图,DDR3中存放和运行程序、Timer定时器产生定时中断、UART打印信息、MIO驱动LED外设。虽然本实验可以不需要UART控制器,不过为了方便打印一些信息,此处我们加上UART。
图 9.3.1 系统框图
由于ARM处理器自带了私有定时器,因此本次实验在搭建嵌入式系统时,不需要额外添加定时器,只需在ZYNQ嵌入式最小系统中添加UART和MIO。
首先创建Vivado工程,工程名为“timer_intr_led”,然后创建Block Design设计(system.bd)并添加ZYNQ7 Processing System模块。接下来按照《“Hello World”实验》中的步骤2-7、2-8分别配置PS的UART和DDR控制器,并移除PS中与PL端交互的接口信号,外设IO引脚配置界面如下图所示:
图 9.3.2 外设IO引脚配置界面
勾选UART0对应的引脚14和15,同时勾选GPIO MIO。
嵌入式系统最终搭建的框图如下:
图 9.3.3 嵌入式系统框图界面
软件设计
在将硬件导出至SDK,并打开SDK开发环境后,创建应用工程的步骤都是一样的,这里不再赘述,新创建的应用工程命名为timer_intr_led。
我们打开timer_intr_led_bsp目录下的system.mss文件,找到scutimer,可以看到定时器的文档和导入示例,如图 9.4.1所示:
图 9.4.1 system.mss文件
如果我们点击Import Examples,会弹出下图所示的导入示例界面,关于定时器有2个示例,如下图所示:
图 9.4.2 导入示例
感兴趣的朋友可以参考下官方提供的定时器例程,其中xscutimer_intr_example是定时器中断的示例。
这里我们不导入官方的例程,而是新建一个源文件。在timer/src目录上右键,选择New->Source File。在弹出的对话框中Source file一栏我们输入文件名“main.c”,然后点击“Finish”。
新建源文件之后,在左侧timer_intr_led/src目录下可以看到main.c文件,同时在主页面已经打开了该文件的文本编辑框。我们在新建的main.c文件中输入以下代码:
- 1 #include "xparameters.h"
- 2 #include "xscutimer.h"
- 3 #include "xscugic.h"
- 4 #include "xgpiops.h"
- 5 #include "xil_exception.h"
- 6 #include "xil_printf.h"
- 7
- 8 #define TIMER_DEVICE_ID XPAR_XSCUTIMER_0_DEVICE_ID //定时器ID
- 9 #define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID //中断ID
- 10 #define TIMER_IRPT_INTR XPAR_SCUTIMER_INTR //定时器中断ID
- 11 #define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID //宏定义 GPIO_PS ID
- 12 #define MIO_LED 0 //led连接到 MIO0
- 13
- 14 //私有定时器的时钟频率 = CPU时钟频率/2 = 333MHz
- 15 //0.2s闪烁一次,0.2*1000_000_000/(1000/333) - 1 = 3F83C3F
- 16 #define TIMER_LOAD_VALUE 0x3F83C3F //定时器装载值
- 17
- 18 XScuGic Intc; //中断控制器驱动程序实例
- 19 XScuTimer Timer; //定时器驱动程序实例
- 20 XGpioPs Gpio; //GPIO设备的驱动程序实例
- 21
- 22 //MIO引脚初始化
- 23 int mio_init(XGpioPs *mio_ptr)
- 24 {
- 25 int status;
- 26
- 27 XGpioPs_Config *mio_cfg_ptr;
- 28 mio_cfg_ptr = XGpioPs_LookupConfig(GPIO_DEVICE_ID);
- 29 if (NULL == mio_cfg_ptr)
- 30 return XST_FAILURE;
- 31 status = XGpioPs_CfgInitialize(mio_ptr, mio_cfg_ptr,
- 32 mio_cfg_ptr->BaseAddr);
- 33 if (status != XST_SUCCESS)
- 34 return XST_FAILURE;
- 35
- 36 //设置指定引脚的方向: 0 输入, 1 输出
- 37 XGpioPs_SetDirectionPin(&Gpio, MIO_LED, 1);
- 38 //使能指定引脚输出: 0 禁止输出使能, 1 使能输出
- 39 XGpioPs_SetOutputEnablePin(&Gpio, MIO_LED, 1);
- 40 return XST_SUCCESS;
- 41 }
- 42
- 43 //定时器初始化程序
- 44 int timer_init(XScuTimer *timer_ptr)
- 45 {
- 46 int status;
- 47 //私有定时器初始化
- 48 XScuTimer_Config *timer_cfg_ptr;
- 49 timer_cfg_ptr = XScuTimer_LookupConfig(TIMER_DEVICE_ID);
- 50 if (NULL == timer_cfg_ptr)
- 51 return XST_FAILURE;
- 52 status = XScuTimer_CfgInitialize(timer_ptr, timer_cfg_ptr,
- 53 timer_cfg_ptr->BaseAddr);
- 54 if (status != XST_SUCCESS)
- 55 return XST_FAILURE;
- 56
- 57 XScuTimer_LoadTimer(timer_ptr, TIMER_LOAD_VALUE); // 加载计数周期
- 58 XScuTimer_EnableAutoReload(timer_ptr); // 设置自动装载模式
- 59
- 60 return XST_SUCCESS;
- 61 }
- 62
- 63 //定时器中断处理程序
- 64 void timer_intr_handler(void *CallBackRef)
- 65 {
- 66 //LED状态,用于控制LED灯状态翻转
- 67 static int led_state = 0;
- 68 XScuTimer *timer_ptr = (XScuTimer *) CallBackRef;
- 69 if(led_state == 0)
- 70 led_state = 1;
- 71 else
- 72 led_state = 0;
- 73 //向指定引脚写入数据: 0 或 1
- 74 XGpioPs_WritePin(&Gpio, MIO_LED,led_state);
- 75 //清除定时器中断标志
- 76 XScuTimer_ClearInterruptStatus(timer_ptr);
- 77 }
- 78
- 79 //定时器中断初始化
- 80 void timer_intr_init(XScuGic *intc_ptr,XScuTimer *timer_ptr)
- 81 {
- 82 //初始化中断控制器
- 83 XScuGic_Config *intc_cfg_ptr;
- 84 intc_cfg_ptr = XScuGic_LookupConfig(INTC_DEVICE_ID);
- 85 XScuGic_CfgInitialize(intc_ptr, intc_cfg_ptr,
- 86 intc_cfg_ptr->CpuBaseAddress);
- 87 //设置并打开中断异常处理功能
- 88 Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
- 89 (Xil_ExceptionHandler)XScuGic_InterruptHandler, intc_ptr);
- 90 Xil_ExceptionEnable();
- 91
- 92 //设置定时器中断
- 93 XScuGic_Connect(intc_ptr, TIMER_IRPT_INTR,
- 94 (Xil_ExceptionHandler)timer_intr_handler, (void *)timer_ptr);
- 95
- 96 XScuGic_Enable(intc_ptr, TIMER_IRPT_INTR); //使能GIC中的定时器中断
- 97 XScuTimer_EnableInterrupt(timer_ptr); //使能定时器中断
- 98 }
- 99
- 100 //main函数
- 101 int main()
- 102 {
- 103 int status;
- 104 xil_printf("SCU Timer Interrupt Test \r\n");
- 105
- 106 mio_init(&Gpio); //MIO引脚初始化
- 107 status = timer_init(&Timer); //定时器初始化
- 108 if (status != XST_SUCCESS) {
- 109 xil_printf("Timer Initial Failed\r\n");
- 110 return XST_FAILURE;
- 111 }
- 112 timer_intr_init(&Intc,&Timer); //定时器中断初始化
- 113 XScuTimer_Start(&Timer); //启动定时器
- 114
- 115 while(1);
- 116 return 0;
- 117 }
复制代码
在代码的第16行定义了定时器的装载值TIMER_LOAD_VALUE,定时器在运行时,初始值为定时器的装载值,当定时器的装载值递减至0后,产生中断,通过修改装载值的大小,来控制LED灯亮灭的时间。需要说明的是,私有定时器的时钟频率等于CPU时钟频率的一半,因此私有定时器的时钟频率约为333Mhz。
在程序的main函数中,首先对MIO引脚和定时器进行初始化。初始化完成后,函数返回初始化的结果,如果初始化失败,打印错误信息并返回;如果初始化成功,则开始执行定时器中断初始化函数(timer_intr_init),并启动定时器。最后主程序会一直停留在while无限循环,如代码中第100行至第117行代码所示。
在代码的第22行至第41行完成了对MIO引脚的初始化。在代码的第43行至第61行完成了对定时器的初始化。其中XScuTimer_LoadTimer函数设置定时器的装载值,输入的参数即为宏定义TIMER_LOAD_VALUE。XScuTimer_EnableAutoReload函数将定时器设置成自动装载模式,从而循环产生定时器的中断。
在代码的第79行至第98行完成了定时器中断的初始化。程序首先对中断控制器进行初始化,随后设置并打开中断异常处理的功能。接下来为定时器中断设置中断处理函数,通过XScuGic_Connect函数进行设置,这里设置的定时器中断处理函数为timer_intr_handler。XScuGic_Enable函数使能GIC中的定时器中断,XScuTimer_EnableInterrupt函数使能定时器中断。
在代码的第84行至第98行是定时器中断处理函数,由于定时器的装载值等于0x3F83C3F(十进制:66599999),CPU的时钟频率约为333Mhz,因此每隔200ms进入此中断函数。程序中定义一个静态变量led_state,用于控制LED灯的翻转。每进入一次中断函数,led_state的状态改变一次,并将led_state的值写入MIO_LED引脚,从而控制LED灯的亮灭。最后通过XScuTimer_ClearInterruptStatus函数来清除中断标志。
下载验证
首先我们将下载器与领航者底板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用Mini USB连接线将USB_UART接口与电脑连接,用于串口通信。最后连接开发板的电源,并打开电源开关。
在SDK软件下方的SDK Terminal窗口中点击右上角的加号设置并连接串口。然后在应用工程uart_intr_loop上右击,选择“Run As”,然后选择第一项“1 Launch on Hardware (System Debugger)”。
软件程序下载完成后,在下方的SDK Terminal中可以看到应用程序打印的信息“SCU Timer Interrupt Test”,如下图所示:
图 9.5.1 程序打印结果
接下来观察开发板,可以看到,核心板上的LED2(PS LED)每隔200ms亮灭一次,如图 9.5.2所示,说明本次实验在领航者ZYNQ开发板上面下载验证成功。
图 9.5.2 开发板实验现象
至此,定时器中断实验的讲解已经结束。最后,我们来教大家如何对代码进行调试。
在应用工程timer_intr_led上右击,选择“Debug As”,然后选择第一项“1 Launch on Hardware (System Debugger)”,如下图所示:
图 9.5.3 打开调试界面
如果出现下图所示的WARNING,直接点击“OK”按钮,如果不想以后再出现,可以勾
选“Do not show this warning again”。
图 9.5.4 WARING提示界面
在弹出的下图所示界面中,点击“Yes”按钮,如果不想以后再出现该界面,可以点击
“Remember my decision”。
图 9.5.5 确认进入调试界面
进入下图所示的调试界面,程序首先从main函数开始运行。
图 9.5.6 开始调试界面
图 9.5.6标注为1的位置是用于调试的工具栏;标注为2的位置可以观察程序中变量的值;标注为3的位置即为我们所要调试的代码,图中高亮的代码行就是接下来将要执行的代码。
我们来详细看下用于调试的工具栏界面,如下图所示:
图 9.5.7 工具栏
图 9.5.7标注为1的图标表示程序继续运行(Resume,快捷键F8);标注为2的图标表示暂停(Suspend,只有程序在运行时才可以点击);标注为3的图标表示单步执行(Step Info,快捷键F5);标注为4的图标表示单步执行结束(Step Over,快捷键F6);标注为5的图标表示执行完并返回(Step Return,快捷键F7)。同时,也可以点击菜单栏的Run,找到调试按钮,如下图所示:
图 9.5.8 点击Run界面
我们首先点击SDK Terminal窗口的界面,便于观察程序调试的结果,如下图所示:
图 9.5.9 窗口打印界面
接下来开始调试代码。点击Step Over图标或者按下快捷键F6来执行代码,执行结果如下图所示:
图 9.5.10 窗口打印结果
此时,执行完xil_printf函数,并在窗口中显示打印的结果。接下来点击Step Info图标或者按下快捷键F5,开始跳转至mio_init函数,如下图所示:
图 9.5.11 进入mio_init函数
如果此时想要迅速跳出mio_init函数,只需要点击Step Return图标或者按下快捷键F7,跳转结果如下图所示:
图 9.5.12 跳出函数
调试界面支持设置断点,直接双击行号前面蓝色区域的位置设置断点,再次按下可取消断点。如在第135行位置设置断点,双击第135行前面蓝色区域的位置,如下图所示:
图 9.5.13 设置断点
接下来点击Resume图标或者按下快捷键F8,程序可执行至断点位置,如下图所示:
图 9.5.14 程序执行至断点处
再次双击第135行前面蓝色区域的位置可取消断点。此时继续点击Resume图标或者按下快捷键F8,程序会一直执行下去,此时可以看到开发板上的LED2每隔两秒亮灭一次。如果想要暂停程序的执行,点击Suspend图标即可。
如果要退出Debug界面,点击下图中的图标即可。
图 9.5.15 退出Debug界面
|
阿莫论坛20周年了!感谢大家的支持与爱护!!
月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!
|