1)实验平台:正点原子MPSoC开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=692450874670
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html
第九章定时器中断实验
MPSOC中PS部分包含许多不同类型的定时器,包括全局定时器、TTC定时器、系统看门狗定时器等。定时器可以不受CPU的干预,自己独立运行,来完成计时、定时、中断以及计算来自MIO或EMIO引脚的信号脉冲宽度等。本章我们将向大家介绍TTC(三路定时器)以TTC(三路定时器)中断的使用方法。
本章包括以下几个部分:
99.1简介
9.2实验任务
9.3硬件设计
9.4软件设计
9.5下载验证
9.6在线调试
9.1简介
PS有许多不同类型的定时器和计数器,包括APU MPCore AArch64定时器,三路定时器(TTC),系统看门狗定时器。APU MPCore AArch64定时器包括APU MPCore全局定时器和APU私有定时器。MPSOC有4个三路定时器,每个三路定时器中有3个相似的计数器,三路定时器可以计算来自MIO引脚或EMIO引脚信号脉冲宽度,并产生中断。PS中有3个系统看门狗定时器(SWDT),用于检测系统故障并从中恢复。
定时器的系统框图如图 9.1.1所示:
图 9.1.1 定时器系统框图
图中的几种定时器都有连接到中断控制器(Interrupt Controller),我们可以很方便的使用定时器来完成定时器中断的实验。本次实验是基于三路定时器(TTC)来完成定时器的中断实验。
三路定时器(TTC)的特性如下:
1、32位APB编程接口;
2、可选的时钟输入:
内部总线时钟(LPD_LSBUS_CLK);
内部时钟(来自PL);
外部时钟(来自MIO);
3、支持三个独立的32位定时器/计数器;
4、支持16位时钟预分频器;
5、三个系统中断,用于每路定时器;
6、计数器和可编程值相等时,产生中断;
7、递增和递减计数;
8、产生波形输出(例如PWM);
9.2实验任务
本章的实验任务是通过定时器的中断,每1s控制一次PS LED灯的亮灭。
9.3硬件设计
从实验任务我们可以画出如下的系统框图,DDR4中存放和运行程序、三路定时器(TTC)产生定时中断、UART打印信息、MIO驱动LED外设。虽然本实验可以不需要UART控制器,不过为了方便打印一些信息,此处我们加上UART。
图 9.3.1 系统框图
本次实验在《“Hello World”实验》的基础上修改而来,添加MIO和TTC单元。
打开《“Hello World”实验》工程,另存为名为“timer_intr_led”的工程。点击Open Block Design,在打开的Diagram窗口中双击Zynq Ultrascale+ MPSOC打开重定义窗口,分别添加MIO和TTC,然后点击“OK”,如下图所示:
图 9.3.2 添加MIO和TTC
嵌入式系统最终搭建的框图如下:
图 9.3.3 嵌入式系统框图界面
保存设计,然后右键点击design_1_wrapper选择Generate Output Products。导出Hardware,并将导出的design_1_wrapper.xsa放到vitis文件夹,启动Vitis。
启动Vitis开发环境后,新建一个名为“timer_intr_led”的应用工程,工程建好后如下图所示:
图9.4.1 新建工程
双击硬件平台工程下的platform.spr,在右侧出现的界面中点击板级支持包Board Support Package,然后可以看到外设驱动Peripheral Drivers,如图9.4.2所示,下拉可以看到三路定时器(TTC)的文档和示例,如图9.4.3所示:
图9.4.2 展开Peripheral Drivers
图9.4.3 导入TTC示例
如果我们点击Import Examples,会弹出下图所示的导入示例界面,关于TTC有4个示例,如下图所示:
图9.4.4 导入示例
感兴趣的朋友可以参考下官方提供的TTC例程,其中xttcps_intr_example是TTC中断的示例。
这里我们不导入官方的例程,而是新建一个源文件。在timer_intr_led/src目录上右键,选择New-> File。在弹出的对话框中File name一栏我们输入文件名“main.c”,然后点击“Finish”。
新建源文件之后,在左侧timer_intr_led/src目录下可以看到main.c文件,同时在主页面已经打开了该文件的文本编辑框。我们在新建的main.c文件中输入以下代码:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include "xparameters.h"
4 #include "xstatus.h"
5 #include "xil_exception.h"
6 #include "xttcps.h"
7 #include "xscugic.h"
8 #include "xil_printf.h"
9 #include "xgpiops.h"
10
11 /************************** Constant Definitions *****************************/
12 #define NUM_DEVICES 12U
13 #define TTC_TICK_DEVICE_ID XPAR_XTTCPS_1_DEVICE_ID
14 #define TTC_TICK_INTR_ID XPAR_XTTCPS_1_INTR
15 #define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
16
17 #define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID //宏定义 GPIO_PS ID
18
19 #define MIO_LED 38 //连接 PS_LED1 到 MIO38
20 #define MIO_LED2 39 //连接 PS_LED2 到 MIO39
21
22 /**************************** Type Definitions *******************************/
23 typedef struct {
24 u32 OutputHz;
25 XInterval Interval;
26 u8 Prescaler;
27 u16 Options;
28 } TmrCntrSetup;
29
30 /************************** Function Prototypes ******************************/
31 static int SetupTicker(void);
32 static int IniTimer(int DeviceID);
33 static int SetupInterruptSystem(u16 IntcDeviceID, XScuGic *IntcInstancePtr);
34 static void TickHandler(void *CallBackRef, u32 StatusEvent);
35
36 /************************** Variable Definitions *****************************/
37
38 static XTtcPs TtcPsInst[NUM_DEVICES]; /* Number of available timer counters */
39
40 XScuGic InterruptController; //中断控制器实例
41 XGpioPs Gpio; //GPIO设备的驱动程序实例
42
43 //MIO初始化
44 int mio_init(XGpioPs *mio_ptr)
45 {
46 XGpioPs_Config *mio_cfg_ptr;
47
48 mio_cfg_ptr = XGpioPs_LookupConfig(GPIO_DEVICE_ID);
49 if (NULL == mio_cfg_ptr)
50 return XST_FAILURE;
51
52 XGpioPs_CfgInitialize(mio_ptr, mio_cfg_ptr,
53 mio_cfg_ptr->BaseAddr);
54
55 //设置指定引脚的方向: 0 输入, 1 输出
56 XGpioPs_SetDirectionPin(mio_ptr, MIO_LED, 1);
57
58 //使能指定引脚输出: 0 禁止输出使能, 1 使能输出
59 XGpioPs_SetOutputEnablePin(mio_ptr, MIO_LED, 1);
60
61 //向指定引脚写入数据: 0 或 1
62 XGpioPs_WritePin(mio_ptr, MIO_LED, 0);
63
64 return XST_SUCCESS;
65 }
66
67 //中断处理函数
68 u32 ttc_InterruptHandler(XTtcPs *InstancePtr)
69 {
70 u32 XTtcPsStatusReg;
71
72 XTtcPsStatusReg = XTtcPs_GetInterruptStatus(InstancePtr);
73
74 //LED状态,用于控制LED灯状态翻转
75 static int led_state = 0;
76 if(led_state == 0){
77 led_state = 1;
78 }
79 else{
80 led_state = 0;
81 }
82
83 //向指定引脚写入数据: 0 或 1
84 XGpioPs_WritePin(&Gpio, MIO_LED,led_state);
85
86 return XST_SUCCESS;
87 }
88
89 int main(void)
90 {
91 int Status;
92
93 //MIO初始化
94 mio_init(&Gpio);
95
96 Status = SetupInterruptSystem(INTC_DEVICE_ID, &InterruptController);
97 if (Status != XST_SUCCESS) {
98 xil_printf("TTC Interrupt Example Test Failed\r\n");
99 return XST_FAILURE;
100 }
101
102 SetupTicker();
103
104 xil_printf("Successfully ran TTC Interrupt Test\r\n");
105
106 return XST_SUCCESS;
107 }
108
109 int SetupTicker(void)
110 {
111 int Status;
112 XTtcPs *TtcPsTick;
113
114 Status = IniTimer(TTC_TICK_DEVICE_ID);
115 if(Status != XST_SUCCESS) {
116 return Status;
117 }
118
119 TtcPsTick = &(TtcPsInst[TTC_TICK_DEVICE_ID]);
120
121 Status = XScuGic_Connect(&InterruptController, TTC_TICK_INTR_ID,
122 (Xil_ExceptionHandler)ttc_InterruptHandler, (void *)TtcPsTick);
123 if (Status != XST_SUCCESS) {
124 return XST_FAILURE;
125 }
126
127 XTtcPs_SetStatusHandler(&(TtcPsInst[TTC_TICK_DEVICE_ID]), &(TtcPsInst[TTC_TICK_DEVICE_ID]),
128 (XTtcPs_StatusHandler)TickHandler);
129
130 //使能定时计数器中断
131 XScuGic_Enable(&InterruptController, TTC_TICK_INTR_ID);
132
133 XTtcPs_EnableInterrupts(TtcPsTick, XTTCPS_IXR_INTERVAL_MASK);
134
135 XTtcPs_Start(TtcPsTick);
136
137 return Status;
138 }
139
140 //配置TTC
141 int IniTimer(int DeviceID)
142 {
143 //int Status;
144 XTtcPs_Config *Config;
145 XTtcPs *Timer;
146
147 Timer = &(TtcPsInst[DeviceID]);
148
149 //查找配置
150 Config = XTtcPs_LookupConfig(DeviceID);
151
152 //初始化ttc
153 XTtcPs_CfgInitialize(Timer, Config, Config->BaseAddress);
154
155 XTtcPs_SetOptions (Timer, 0x24); //设置Option
156
157 //设置Interval
158 XTtcPs_SetInterval (Timer, 50000000); //每隔500ms产生中断,led灯间隔1s亮灭一次
159
160 //设置Prescaler
161 XTtcPs_SetPrescaler (Timer, 0); //不分频,默认输入100Mhz
162
163 return XST_SUCCESS;
164 }
165
166 static int SetupInterruptSystem(u16 IntcDeviceID, XScuGic *IntcInstancePtr)
167 {
168 int Status;
169 XScuGic_Config *IntcConfig;
170
171 IntcConfig = XScuGic_LookupConfig(IntcDeviceID);
172 if (NULL == IntcConfig) {
173 return XST_FAILURE;
174 }
175
176 Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig,
177 IntcConfig->CpuBaseAddress);
178 if (Status != XST_SUCCESS) {
179 return XST_FAILURE;
180 }
181
182 Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
183 (Xil_ExceptionHandler) XScuGic_InterruptHandler,
184 IntcInstancePtr);
185
186 Xil_ExceptionEnable();
187
188 return XST_SUCCESS;
189 }
190
191 static void TickHandler(void *CallBackRef, u32 StatusEvent)
192 {
193
194 }
在代码的19行定义了MIO_LED为38,即PS_LED连接到MIO的38号引脚上。然后在代码的第44行至第65行对MIO进行初始化,并将MIO38设置为输出。
代码的第68行至第87行定义了中断处理函数。在函数中首先定义一个led_state变量,并赋初始值0,每当中断发生时,对led_state的值进行翻转,并将翻转后的值写到MIO38引脚。
在程序的main函数中,首先对GPIO进行初始化,然后在程序的第96行调用中断函数。中断函数在代码的第166行至第189行。在中断函数的第171行,根据器件ID查找GIC的配置,然后在代码的第176行对GIC进行初始化。在代码的第182行至第186行,设置并使能中断异常。
main函数中第102行调用了SetupTicker函数,该函数主要完成TTC中断的配置以及定时器的配置。第114行的SetupTicker函数中调用了IniTimer定时器配置函数。TTC使用系统输入的100Mhz时钟,这里不对系统时钟进行分频,所以分频系数设置为0,如代码的第161行所示。
由于实验任务是led灯间隔1s亮灭一次,所以每隔500ms改变一次led灯的状态。时钟周期是10ns,因此可以计算出定时器Interval的值是50_000_000 (500_000_000/10),如代码第158行所示。
9.5下载验证
首先我们将下载器与开发板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用USB连接线将USB_UART接口(开发板右上角PS PORT)与电脑连接,用于串口通信。最后连接开发板的电源,给开发板上电。
打开Vitis Terminal终端,设置并连接串口。然后下载本次实验的程序,下载完成后,在下方的Terminal中可以看到应用程序打印的信息“Successfully ran TTC Interrupt Test”,如下图所示:
图 9.5.1 程序打印结果
接下来观察开发板,可以看到PS_LED1每隔200ms亮灭一次,如图 9.5.2所示,说明本次实验在MPSOC开发板上面下载验证成功。
图 9.5.2 开发板实验现象
9.6在线调试
至此,定时器中断实验的讲解已经结束。最后,我们来教大家如何对代码进行调试。
在应用工程timer_intr_led上右击,选择“Debug As”,然后选择第一项“1 Launch on Hardware (System Debugger)”,如下图所示:
图 9.6.1 打开调试界面
进入下图所示的调试界面,程序首先从main函数开始运行,如程序第116行所示。
图9.6.2 开始调试界面
图9.6.2标注为1的位置是用于调试的工具栏;标注为3的位置可以观察程序中变量的值;标注为2的位置即为我们所要调试的代码,图中高亮的代码行就是接下来将要执行的代码。
我们来详细看下用于调试的工具栏界面,如下图所示:
图9.6.3 工具栏
图9.6.3标注为1的图标表示程序继续运行(Resume,快捷键F8);标注为2的图标表示暂停(Suspend,只有程序在运行时才可以点击);标注为3的图标表示单步执行(Step Into,快捷键F5);标注为4的图标表示单步执行结束(Step Over,快捷键F6);标注为5的图标表示执行完并返回(Step Return,快捷键F7)。同时,也可以点击菜单栏的Run,找到调试按钮,如下图所示:
图 9.6.4点击Run界面
我们首先打开Vitis Serial Terminal窗口的界面并点击右上角“+”设置串口,以便于观察程序调试的结果,如下图所示。如果不小心把Terminal窗口关掉了,需要点击工具栏Window->Show view…,然后在弹出的窗口中输入Terminal搜索并打开串口终端。
图 9.6.5 窗口打印界面
接下来开始调试代码。点击Step Over图标或者按下快捷键F6来执行代码,执行MIO初始化函数mio_init()。接下来点击Step Info图标或者按下快捷键F5,跳转进入SetupInterruptSystem()函数,如下图所示:
图9.6.6 跳转至SetupInterruptSystem()函数
接下来可以继续按F5单步执行。如果此时想迅速跳出SetupInterruptSystem()函数,可以直接点击Step Return或者按下快捷键F7,跳转结果如下图所示:
图9.6.7 跳出函数
调试界面支持设置断点,直接双击行号前面蓝色区域的位置设置断点,再次双击可取消断点。如在第126行位置设置断点,双击第126行前面蓝色区域的位置,如下图所示:
图9.6.8 设置断点
接下来点击Resume图标或者按下快捷键F8,程序可执行至断点位置,如下图所示:
图9.6.9 程序执行至断点处
再次双击第126行前面蓝色区域的位置可取消断点。此时继续点击Resume图标或者按下快捷键F8,程序会一直执行下去,此时可以看到串口打印信息“Successfully ran TTC Interrupt Test”,并且开发板上的PS_LED1每隔200毫秒亮灭一次。如果想要暂停程序的执行,点击Suspend图标即可。
如果要退出Debug界面,点击下图中的图标即可。
图9.6.10 退出Debug界面