1)实验平台:正点原子MPSoC开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=692450874670
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html
第十二章PS端RTC中断实验
RTC(Real_Time Clock)是一种实时时钟模块,它为人们提供精确的实时时间,或者为电子系统提供精确的时间基准。MPSOC芯片内部集成了一个实时时钟模块,本章是通过这个模块来显示当前时间和使用RTC的中断来实现周期性报警的功能。
本章包括以下几个部分:
1212.1简介
12.2实验任务
12.3硬件设计
12.4软件设计
12.5下载验证
12.1简介
RTC(Real_Time Clock)实时时钟模块是由集成电路构成,通常被称为时钟芯片。实时时钟(RTC)单元的作用一般是为系统和应用软件提供准确的时基,为了提升时钟精度,RTC还包括校准电路以抵消温度和电压波动。RTC由VCC_PSAUX或VCC_PSBATT电源供电,当辅助电源(VCC_PSAUX)可用时,RTC使用它来保持计数器工作;当辅助电源不可用时,RTC自动切换到电池电源(VCC_PSBATT)工作。
图 12.1.1 RTC实时时钟模块
RTC具有以下特点:
•既可以使用辅助电源(VCC_PSAUX)工作也可以使用电池电源(VCC_PSBATT)工作,当系统掉电时,RTC会自动切换至电池供电保持持续工作。
•报警设置和周期性中断(定时中断)。
•用于高精度计时的复杂校准电路。
•32位秒计数器,可计数约136年的时间。
•三个计数器:32位秒计数器。
16位滴答计数器,用于测量基于32KHz晶振的秒数。
用于校准的4位小数计数器。
RTC控制器的系统级结构图如下图所示,从图12.1.2可知RTC控制器主要由三部分组成,分别是RTC控制寄存器、RTC计数器与晶振模块,每个模块的具体功能如图12.1.3所示。
图12.1.2 RTC控制器结构图
RTC功能主要分为三个模块。
1)RTC控制寄存器:在低功耗域(LPD)中实现,该模块包含与RTC控制器相关的所有寄存器。
2)RTC计数器模块:包括用于在电池供电域(BPD)中保持编程时间和校准的所有计数器、校准逻辑和锁存器。它还包括以下功能:
a.与同样在BPD中工作的晶体振荡器接口。
b.以秒为单位保持当前时间。
c.包含校准电路,用于通过使用具有任意静态误差的晶体振荡器来计算具有最大ppm误差的一秒。
d.保持先前编程的时间,以便软件回读和校准。
e.保持振荡器和电源开关电路使用的控制值。
3)晶振:提供通过GPIO实现的RTC时钟,电源由RTC计数器模块提供。
图12.1.3 RTC 控制器功能框图
由上图可知,可以通过APB接口来访问控制器内的寄存器和RTC计数器,APB接口是由LPD提供随路时钟(LPD_LSBUS_CLK)的接口。
上图左边的RTC控制寄存器(RTC Control Registers)映射从0xFFA6_0000开始到4KB空间中的每个寄存器的描述和偏移地址,如下表所示:
图12.1.4 RTC寄存器列表
重要提示:
每次LPD上电时都必须对控制寄存器进行编程,否则通过读取控制寄存器返回的值可能与存储在BPD中的实际控制设置不同。
SET_TIME_WRITE、CALIB_WRITE和CURRENT_TIME寄存器都在电池供电的RTC内实现,但通过LPD中的APB接口访问。
控制器逻辑还包括ALARM警报寄存器和警报生成逻辑。每当RTC中秒计数器的值与显式加载到闹钟寄存器中的值相匹配,并且启用报警中断时,就会生成RTC_Alarm系统中断。
图12.1.3右边的RTC计数模块(RTC Counters)主要由32位秒计数器、16位滴答计数器与4位小数计数器组成:
32位秒计数器(Seconds Counter)是一个同步计数器,用于保存距特定参考点(操作系统已知)的秒数。操作系统的时钟设备驱动程序通过基于从参考点过去的秒数来计算当前时间,获得该当前时间值后通过初始化秒计数器的时间设置寄存器被编程到RTC计数器中,之后,秒计数器以每秒递增的方式计时并保持更新的当前时间,然后当前时间通过接口被读取到RTC控制器。
对于每个振荡器时钟周期,滴答计数器(Tick Counter)中的值与校准寄存器中存储的值(CALIB_WRITE[Max_Tick])进行比较。如果这些值匹配,则滴答计数器重置为零并产生中断。来自RTC计数器的中断信号在一个osc_rtc_clk周期内被置位,并且仅在上升沿转换时被RTC控制器的中断状态寄存器捕获。
4位小数计数器(Fraction Counter)的小数校准功能(如果启用)每16秒生效一次,并根据校准寄存器小数校准字段中编程的振荡器周期数延迟向滴答计数器释放清除信号。
校准电路的详细原理与实时时钟精度直接参考xilinx官方文档ug1085-zynq-ultrascale-trm.pdf手册。
12.2实验任务
本章的实验任务是使用MPSOC芯片内部集成的RTC模块来显示当前时间和使用RTC的中断来实现周期性报警的功能。
12.3硬件设计
从实验任务我们可以画出如下的系统框图,DDR4中存放和运行程序、UART打印中断报警信息与当前时间。
图 12.3.1系统框图
本章实验的硬件设计部分只需要MPSOC嵌入式最小系统就可以完成实验任务的需求,可以直接使用“Hello Word”实验的硬件设计,所以打开《“Hello World”实验》工程另存名为“rtc_intr”的工程。
图 12.3.2新建工程“rtc_intr”
重新保存工程后如下图所示:
图 12.3.3 “rtc_intr”建立完成
图 12.3.4 导出硬件
在弹出的对话框中,因为本次实验未用到PL部分,所以没有生成bitstream文件,因此无需勾选“Include bitstream”,直接点击“OK”按钮。
图 12.3.5 无需勾选“Include bitstream”
硬件导出完成后,将导出的design_1_wrapper.xsa放到vitis文件夹,启动Vitis。
12.4软件设计
启动Vitis开发环境后,新建一个名为“rtc_intr”的应用工程,工程建好后如下图所示,
我们打开design_1_wrapper目录下的platform.spr文件,点击BSP–Peripheral Drivers,可以看到psu_rtc的文档和导入示例,如图 12.4.2所示:
图 12.4.1工程页面
图 12.4.2 RTC中断模板界面
如果我们点击Import Examples,会弹出下图所示的导入示例界面,关于RTC模板有7个示例,如下图所示:
图 12.4.3 选择中断示例
感兴趣的朋友可以参考下官方提供的RTC中断例程,其中xrtcpsu_periodic_alarm_interropt_example是RTC中断周期报警的示例,RTC周期性报警编程示例流程图如下图所示:
图 12.4.4RTC周期性报警编程示例流程图
这里我们不导入官方的例程,而是新建一个源文件。在rtc_int/src目录上右键,选择New->Source File。在弹出的对话框中Source file一栏我们输入文件名“main.c”,然后点击“Finish”。新建源文件之后,在左侧rtc_int/src目录下可以看到main.c文件,同时在主页已经打开了该文件的文本编辑框。我们在新建的main.c文件中输入以下代码:
1 #include "xparameters.h"
2 #include "xrtcpsu.h"
3 #include "xscugic.h"
4 #include "xil_exception.h"
5 #include "xil_printf.h"
6
7 /************************** 常量定义 **************************/
8 //RTC设备ID
9 #define RTC_DEVICE_ID XPAR_XRTCPSU_0_DEVICE_ID
10 //中断控制器设备ID
11 #define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
12 //RTC闹钟中断ID
13 #define RTC_ALARM_INT_IRQ_ID XPAR_XRTCPSU_ALARM_INTR
14
15 /***************** 宏定义 ***************************************/
16 #define REPETATIONS 10
17 #define PERIODIC_ALARM_PERIOD 2U
18
19 /************************** 函数原型 ***************************/
20 int RtcPsuPeriodicAlarmIntrExample(XScuGic *IntcInstPtr,
21 XRtcPsu *RtcInstPtr, u16 DeviceId, u16 RtcIntrId);
22
23 static int SetupInterruptSystem(XScuGic *IntcInstancePtr,
24 XRtcPsu *RtcInstancePtr,u16 RtcIntrId);
25
26 void Handler(void *CallBackRef, u32 Event);
27
28 /************************** 变量定义 ***************************/
29 XRtcPsu RtcPsu; /* RTC设备实例 */
30 XScuGic InterruptController; /* 中断控制器实例 */
31 XRtcPsu_DT RtcPsu_DT={
32 2022, /* 设置年 2000-2099 */
33 1, /* 设置月 */
34 4, /* 设置日 */
35 00, /* 设置时 */
36 00, /* 设置分 */
37 00, /* 设置秒 */
38 /*设置星期0-Fri,1-Sat,2-Sun,3-Mon,4-Tue,5-Wed,6-Thur*/
39 4,
40 }; // RTC实时时间实例
41
42 volatile u32 PeriodicAlarms;
43
44 /**** 调用 RTC 中断模板的主函数。 ****/
45 int main(void)
46 {
47 int Status;
48
49 /* 运行 RtcPsu 中断示例,指定设备 ID */
50 Status = RtcPsuPeriodicAlarmIntrExample(
51 &InterruptController, &RtcPsu,
52 RTC_DEVICE_ID, RTC_ALARM_INT_IRQ_ID);
53 if (Status != XST_SUCCESS) {
54 xil_printf("RTC Periodic Alarm
55 Interrupt Example Test Failed\r\n");
56 return XST_FAILURE;
57 }
58 xil_printf("Successfully ran RTC
59 Periodic Alarm Interrupt Example Test\r\n");
60 return XST_SUCCESS;
61 }
在代码的第17行定义了RTC中断发生时间的参数PERIODIC_ALARM_PERIOD为2s,从RTC运行开始计数,计数到2s时产生中断,在代码的第16行定义了RTC中断连续发生的次数的参数REPETATIONS为10次。
在程序的31行到40行定义了一个设置日期的结构体变量(RtcPsu_DT),在这个结构体变量里可以设置当前日期的年、月、日、时、分、秒。程序的第91行使用XRtcPsu_DateTimeToSec函数获取RtcPsu_DT里的日期元素将获取的日期转换为秒数(Seconds),按代码中设置的时间是2022年1月4日0时0分0秒,转换的秒数就是从2000年1月1日0时0分0秒到2022年1月4日0时0分0秒之间过去时间的秒数(Seconds)。
在程序的第45行到61行代码是main函数,main函数主要是调用了RTC中断函数的模板(RtcPsuPeriodicAlarmIntrExample),RTC中断模板函数运行成功打印“Successfully ran RTC Periodic Alarm Interrupt Example Test”,RTC中断函数运行失败打印“RTC Periodic Alarm Interrupt Example Test Failed”。
63 /**** RTC中断模板函数 ****/
64 int RtcPsuPeriodicAlarmIntrExample
65 (XScuGic *IntcInstPtr, XRtcPsu *RtcInstPtr,
66 u16 DeviceId, u16 RtcIntrId)
67 {
68 int Status;
69 XRtcPsu_Config *Config;
70 u32 Time, Seconds, CurrentTime, Alarm;
71 XRtcPsu_DT dt0;
72
73 /* 初始化 RTC 驱动程序 */
74 Config = XRtcPsu_LookupConfig(DeviceId);
75 if (NULL == Config) {
76 return XST_FAILURE;
77 }
78
79 /* 该函数初始化一个 XRtcPsu 实例/驱动程序 */
80 Status = XRtcPsu_CfgInitialize
81 (RtcInstPtr, Config, Config->BaseAddr);
82 if (Status != XST_SUCCESS) {
83 return XST_FAILURE;
84 }
85
86 xil_printf("\n\rDay Convention : 0-Fri, 1-Sat,
87 2-Sun, 3-Mon, 4-Tue, 5-Wed, 6-Thur\n\r");
88 xil_printf("Current RTC time is..\n\r");
89 /* 通过XRtcPsu_DateTimeToSec将用户
90 在RtcPsu_DT结构体中设置的日期转换为秒数*/
91 Seconds = XRtcPsu_DateTimeToSec(&RtcPsu_DT);
92 CurrentTime = XRtcPsu_GetCurrentTime(RtcInstPtr);
93 /* 通过函数XRtcPsu_GetCurrentTime来获取系统秒计数器值,
94 用函数XRtcPsu_SecToDateTime将计数值转换为
95 我们能看明白的年月日时分秒*/
96 Time = CurrentTime + Seconds;
97 XRtcPsu_SecToDateTime(Time,&dt0);
98 xil_printf("YEAR:MM:DD HR:MM:SS
99 \t %04d:%02d:%02d %02d:%02d:%02d\t Day = %d\n\r",
100 dt0.Year,dt0.Month,dt0.Day,
101 dt0.Hour,dt0.Min,dt0.Sec,dt0.WeekDay);
102 /* 将 RTC 连接到中断子系统,以便可以发生中断 */
103 Status = SetupInterruptSystem
104 (IntcInstPtr, RtcInstPtr, RtcIntrId);
105 if (Status != XST_SUCCESS) {
106 return XST_FAILURE;
107 }
108
109 /* RTC中断发生调用中断处理程序 */
110 XRtcPsu_SetHandler(RtcInstPtr,
111 (XRtcPsu_Handler)Handler, RtcInstPtr);
112
113 /* 启动RTC设备的中断 */
114 XRtcPsu_SetInterruptMask
115 (RtcInstPtr, XRTC_INT_EN_ALRM_MASK );
116
117 CurrentTime = XRtcPsu_GetCurrentTime(RtcInstPtr);
118 /* 设置中断时间PERIODIC_ALARM_PERIOD宏定义为2,
119 也就是2秒中断一次 */
120 Alarm = CurrentTime + PERIODIC_ALARM_PERIOD;
121 /* 连续中断 */
122 XRtcPsu_SetAlarm(RtcInstPtr,Alarm,1U);
123 /* 等待10次中断 */
124 while( PeriodicAlarms != REPETATIONS);
125
126 /* 禁用 RTC 设备的中断,这样就不会发生中断。*/
127 XRtcPsu_ClearInterruptMask
128 (RtcInstPtr,XRTC_INT_DIS_ALRM_MASK);
129 XRtcPsu_ResetAlarm(RtcInstPtr);
130
131 return XST_SUCCESS;
132 }
在程序的第64行到132行代码是RTC中断模板函数(RtcPsuPeriodicAlarmIntrExample),
74到84行代码完成了对RTC的初始化。92行代码通过函数XRtcPsu_GetCurrentTime来获取系统秒计数器的值(即系统上电后rtc的计时);97行代码用函数XRtcPsu_SecToDateTime将rtc上电后的计数值与秒数(Seconds)的和转换为我们能看明白的年月日时分秒;103行代码用函数SetupInterruptSystem将RTC连接到中断子系统,以便可以发生中断;110-111行代码是中断发生后运行RTC中断处理程序XRtcPsu_SetHandler;114-115行代码使用XRtcPsu_SetInterruptMask函数启动RTC设备的中断;120行的代码设置了中断时间,输入的参数即为2的宏定义PERIODIC_ALARM_PERIOD,也就是2秒中断一次;122行设置中断次数宏参数REPETATIONS为10次;127到129行代码通过调用函数XScuTimer_ClearInterruptStatus来清除中断标志,禁用RTC设备的中断,这样就不会发生中断了。
134 /**** RTC中断处理程序 ****/
135 void Handler(void *CallBackRef, u32 Event)
136 {
137 /* RTC中断发生 */
138 if (Event == XRTCPSU_EVENT_ALARM_GEN) {
139 PeriodicAlarms++;
140 xil_printf("%dSec Periodic Alarm generated.
141 \n\r", PERIODIC_ALARM_PERIOD);}
142 }
143
144 /**** 将RTC中断源连接到中断控制器。****/
145 static int SetupInterruptSystem
146 (XScuGic *IntcInstancePtr,
147 XRtcPsu *RtcInstancePtr, u16 RtcIntrId)
148 {
149 int Status;
150
151 XScuGic_Config *IntcConfig; /* 配置中断控制器 */
152
153 /* 初始化中断控制器驱动 */
154 IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
155 if (NULL == IntcConfig) {
156 return XST_FAILURE;
157 }
158
159 Status = XScuGic_CfgInitialize
160 (IntcInstancePtr, IntcConfig,
161 IntcConfig->CpuBaseAddress);
162 if (Status != XST_SUCCESS) {
163 return XST_FAILURE;
164 }
165
166 /* 设置并打开中断异常处理功能 */
167 Xil_ExceptionRegisterHandler
168 (XIL_EXCEPTION_ID_INT,(Xil_ExceptionHandler)
169 XScuGic_InterruptHandler, IntcInstancePtr);
170
171 /* 设置定时器中断 */
172 Status = XScuGic_Connect
173 (IntcInstancePtr, RtcIntrId,(Xil_ExceptionHandler)
174 XRtcPsu_InterruptHandler,(void *) RtcInstancePtr);
175 if (Status != XST_SUCCESS) {
176 return XST_FAILURE;
177 }
178
179 /* 使能RTC中断 */
180 XScuGic_Enable(IntcInstancePtr, RtcIntrId);
181
182 /* 使能中断异常处理 */
183 Xil_ExceptionEnable();
184
185 return XST_SUCCESS;
186 }
135行到142行代码是发生RTC中断后调用的处理程序Handler,当RTC中断发生后打印“2Sec Periodic Alarm generated.”
145行到186行代码是配置中断控制器(GIC)的函数SetupInterruptSystem,154到164行代码是对中断控制器进行初始化配置;167到169行代码运行的函数XScuGic_InterruptHandler是中断异常服务函数注册,即设置并打开中断异常处理功能;174到177行代码运行的函数XRtcPsu_InterruptHandler是中断服务函数注册,即RTC中断发生后调用该服务函数;180行运行的函数XScuGic_Enable的功能是使能RTC中断,即在中断控制器中使能中断(根据中断号使能相应的中断);183行代码运行的函数Xil_ExceptionEnable是使能中断异常处理,发生异常中断时调用该函数。
选中应用工程,右键后选中Build Project对工程进行编译。
图 12.4.5编译工程
编译进度可以在工具下方的控制台面板(Console)中进行查看,编译完成后显示“Finished building:rtc_intr.elf.size”,如下图所示:
图 12.4.6编译完成
到这里我们已经完成了本次实验的软件设计部分。
12.5下载验证
首先我们将下载器与MPSOC开发板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用USB连接线将开发板USB_UART (PS_PORT)接口与电脑连接,用于串口通信。接下来将开发板上的启动模式开关均置为ON,即设置为JTAG模式。最后连接开发板电源给开发板上电。如下图所示:
图 12.5.1 MPSOC开发板实物图
使用UART串口打印信息对于串口的设置可以直接参考“Hello Word”中对串口的设置。
下载程序,右键点击rtc_intr工程,选择“Run As”,然后选择最后一项“Run Confagurations…”,如下图所示:
图 12.5.2打开下载页面
在打开的下载页面中,没有出现下载选项,这时需要双击左侧列表中Single Application Debug一项,双击后,该项下面出现新的项Debugger_rtc_int_Default,同时在右侧出现的页面中选择Target Setup标签页,勾选复位,然后点击run下载程序,如下图所示:
图 12.5.3下载程序
下载完成后,在Terminal窗口可以看到上位机接收到的字符串,如下图所示:
中断时间为2s,所以串口每隔2s会打印一次,连续打印10次。最后打印信息“Successfully ran RTC Periodic Alarm Interrupt Example Test”RTC中断运行成功。
图12.5.4 程序运行结果