RTC的时钟源共有3种可以选择:
― LEXT:外部低速晶振,通常为32.768kHz
― LICK:内部低速晶振,通常典型值为40kHz范围(30~60kHz),详情请见各型号的datasheet
― HEXT_DIV:外部高速晶振分频后得到的时钟,不同型号分频值请见下表
表2. 各型号HEXT的预分频值
表3. 各时钟源优缺点对比
能在电池供电下以及低功耗模式下工作,节省一颗晶振,降低了PCB布线面积
精度也比较高(取决于主晶振精度),节省一颗晶振,降低了PCB布线面积
RTC时钟源设置相关函数
选择对应时钟使能
| void crm_clock_source_enable(crm_clock_source_type source, confirm_state new_state) |
选择RTC时钟
| void crm_rtc_clock_select(crm_rtc_clock_type value) |
使能RTC时钟
| void crm_rtc_clock_enable(confirm_state new_state) |
预分频器设置
RTC_CLK通过20位预分频器后获得1Hz时钟
表4. 分频设置举例
RTC分频设置相关函数
设置RTC预分频器
| void rtc_divider_set(uint32_t div_value); |
获取RTC预分频器值
| uint32_t rtc_divider_get(void); |
RTC时钟初始化举例:
| /* 使能LEXT时钟 */ crm_clock_source_enable(CRM_CLOCK_SOURCE_LEXT, TRUE); /* 等待LEXT时钟稳定 */ while(crm_flag_get(CRM_LEXT_STABLE_FLAG) == RESET) { } /* 选择LEXT时钟作为RTC时钟 */ crm_rtc_clock_select(CRM_RTC_CLOCK_LEXT); /* 使能RTC时钟 */ crm_rtc_clock_enable(TRUE); /* 配置RTC分频器DIV=32767 */ rtc_divider_set(32767); |
2.3 日历RTC内部是一个32位的计数器,通常使用中该计数器1秒增加1,也就是该计数器相当于秒钟,然后根据当前的秒钟值,通过转换得到年、月、日、星期、时、分、秒,实现日历的功能,修改计数器的值便可修改时间和日期。
根据使用需要还可以产生秒中断:若秒中断使能(TSIEN=1),每隔一秒产生一个秒中断。
计数相关函数
设置RTC计数值
| void rtc_counter_set(uint32_t counter_value); |
获取RTC计数值
| uint32_t rtc_counter_get(void); |
秒钟转换成日历
先规定一个起始时间,例如1970-1-1 00:00:00对应计数器为0,现在比如计数值为200000,那么换算成时间为:
― 天数:200000 / 86400 = 2
― 小时:(200000 % 86400) / 3600= 7
― 分钟:(200000 % 3600) / 60= 33
― 秒钟:200000 % 60 = 20
所以现在的时间对应为1970-1-3 07:33:20,对应日历转换成秒钟也是相同的思路。
在BSP的例程project\at_start_f403a\examples\rtc\calendar中,我们提供了秒钟与日历的相互转换函数。
设置日历值(日历转换成秒钟)
| uint8_t rtc_time_set(calendar_type *calendar); |
结构体calendar_type里面参数含义如下:
― year:年
― month:月
― day:日
― hour:时
― min:分
― sec:秒
― week:星期几
读取日历值(秒钟转换成日历)
2.4 闹钟RTC闹钟是一个32位的值,当闹钟值和计数值相等时产生闹钟事件(TAF置1),当中断使能时,会产生中断。
闹钟相关函数
闹钟值设置函数
| void rtc_alarm_set(uint32_t alarm_value); |
中断使能函数
| void rtc_interrupt_enable(uint16_t source, confirm_state new_state); |
标志获取函数
| flag_status rtc_flag_get(uint16_t flag); |
标志清除函数
| void rtc_flag_clear(uint16_t flag); |
2.5 计数值溢出由于计数值为32位,所以存在溢出问题,当计数值为0xFFFFFFFF溢出到0x00000000时,产生溢出事件,OVFF标志置1当闹钟使能后,由于溢出后,秒与日历的相转换关系便不正确,所以用户需妥善处理溢出事件。
0xFFFFFFFF所能代表的最大时间为136年,例程起始时间为1975,所以能够到2106年不溢出。
2.6 中断当发生闹钟、秒、溢出事件时,RTC可产生中断。闹钟中断有两种配置模式:
― 不配置EXINT线使用RTC_IRQn中断向量,此种方式不能唤醒DEEPSLEEP和STANDBY模式;
― 配置EXINT线使用RTCAlarm_IRQn中断向量,此种方式可以唤醒DEEPSLEEP和STANDBY模式。
要使能RTC闹钟(不需要唤醒低功耗模式)、秒、溢出中断可按以下操作配置:
― 使能RTC中断对应的NVIC通道。
― 使能对应的RTC中断控制位。
要使能RTC闹钟(需要唤醒低功耗模式)中断可按以下操作配置:
― EXINT线17配置为中断模式并使能,有效沿选择上升沿。
― 使能RTC中断对应的NVIC通道。
― 使能对应的RTC中断控制位。
下表说明了RTC时钟源、事件以及中断对唤醒低功耗模式的影响:
表5. RTC唤醒低功耗模式
表6. 中断控制
表7. 事件对应中断向量
中断、事件相关函数
中断使能函数
| void rtc_interrupt_enable(uint16_t source, confirm_state new_state); |
标志获取函数
| flag_status rtc_flag_get(uint16_t flag); |
标志清除函数
| void rtc_flag_clear(uint16_t flag); |
中断配置示例1:以AT32F403A的闹钟为例,使用RTCAlarm_IRQn中断向量
| /* 配置EXINT线 */ exint_default_para_init(&exint_init_struct); exint_init_struct.line_enable = TRUE; //使能 exint_init_struct.line_mode = EXINT_LINE_INTERRUPUT; //中断模式 exint_init_struct.line_select = EXINT_LINE_17;//EXINT线17 exint_init_struct.line_polarity = EXINT_TRIGGER_RISING_EDGE;//上升沿触发 exint_init(&exint_init_struct); /* 使能闹钟中断NVIC向量:RTCAlarm_IRQn */ nvic_irq_enable(RTCAlarm_IRQn, 0, 1); /* 设置闹钟值 */ rtc_alarm_set(seccount); /* 使能闹钟中断 */ rtc_interrupt_enable(RTC_TA_INT, TRUE); |
中断处理函数
| void RTCAlarm_IRQHandler(void) { if(rtc_flag_get(RTC_TA_FLAG) != RESET) { /* 清除闹钟标志 */ rtc_flag_clear(RTC_TA_FLAG); /* 清除EXINT线17标志 */ exint_flag_clear(EXINT_LINE_17); } } |
中断配置示例2:以AT32F403A的闹钟为例,使用RTC_IRQn中断向量
| /* 使能闹钟中断NVIC向量:RTC_IRQn */ nvic_irq_enable(RTC_IRQn, 0, 1); /* 设置闹钟值 */ rtc_alarm_set(seccount); /* 使能闹钟中断 */ rtc_interrupt_enable(RTC_TA_INT, TRUE); |
中断处理函数
| void RTC_IRQHandler(void) { if(rtc_flag_get(RTC_TA_FLAG) != RESET) { /* 清除闹钟标志 */ rtc_flag_clear(RTC_TA_FLAG); } } |
3 电池供电域功能3.1 电池供电数据寄存器电池供电域一共提供了42个16位电池供电数据寄存器,可以在只由电池供电下保存数据,不会被系统复位所复位,只能通过电池供电域复位或入侵事件进行复位。在写电池供电数据寄存器时,需要先解除读保护,解锁方式同2.1章节相同。
电池供电域数据操作相关函数
写电池供电数据寄存器
| void bpr_data_write(bpr_data_type bpr_data, uint16_t data_value); |
读电池供电数据寄存器
| uint16_t bpr_data_read(bpr_data_type bpr_data); |
电池供电域复位
3.2 RTC校准电池供电域还提供了RTC校准功能,通过RTC_CALVAL寄存器进行配置。
当RTC_CLK为32.768kHz时,校准周期为220个RTC_CLK约32秒。CALVAL[7:0]值指定了220个RTC_CLK中忽略的脉冲数,最多可忽略127个脉冲,这可以将时钟调慢,调慢范围为0~121ppm。
可以选择将校准前或校准后的RTC时钟64分频后输出到PC13脚。
校准设置相关函数
校准值设置函数
| void bpr_rtc_clock_calibration_value_set(uint8_t calibration_value); |
校准时钟输出设置函数
| void bpr_rtc_output_select(bpr_rtc_output_type output_source); |
3.3 入侵检测电池供电域提供了1组入侵检测TAMPER,当在发生入侵事件时,TPEF标志位置1,同时将自动清除电池供电数据寄存器(RTC_BPRx)的值;若已使能入侵中断,将产生入侵中断,同时TPIF标志位置1。入侵检测引脚固定为PC13。
入侵检测模式分为高电平检测和低电平检测。
入侵检测相关函数
入侵检测有效电平设置
| void bpr_tamper_pin_active_level_set(bpr_tamper_pin_active_level_type active_level); |
入侵检测使能
| void bpr_tamper_pin_enable(confirm_state new_state); |
入侵检测标志获取
| flag_status bpr_flag_get(uint32_t flag); |
入侵检测标准清除
| void bpr_flag_clear(uint32_t flag); |
入侵检测中断使能
| void bpr_interrupt_enable(confirm_state new_state); |
3.4 事件输出功能电池供电域提供了一组复用功能输出,在PC13脚可以输出以下事件:
― 校准输出:校准前64分频输出、校准后64分频输出。
― 事件输出:闹钟事件、秒事件
当输出模式为事件输出时(闹钟事件、秒事件),可以通过OUTM选择输出类型为脉冲输出(输出脉冲的宽度为一个RTC时钟的周期)或者是翻转输出(每来一次闹钟或秒输出事件,相对应管脚翻转一次)。
事件输出相关函数
事件输出设置并使能
| void bpr_rtc_output_select(bpr_rtc_output_type output_source); |
4 案例 使用日历以及闹钟功能4.1 功能简介演示日历功能、闹钟功能的使用。
4.2 资源准备1) 硬件环境:
对应产品型号的AT-START BOARD
2) 软件环境:
project\at_start_f4xx\examples\rtc\calendar
注:所有project都是基于keil 5而建立,若用户需要在其他编译环境上使用,请参考
AT32xxx_Firmware_Library_V2.x.x\project\at_start_xxx\templates中各种编译环境(例如IAR6/7,keil4/5)进行简单修改即可。
4.3 软件设计1) 配置流程
n 开启PWC、BPR时钟
n 解锁电池供电域写保护
n 检查日历是否已经初始化,如果正确就跳过初始化,如果不正确就初始化日历以及闹钟
n 主函数里每秒打印一次日历信息
n 在21-05-01 12:00:05时刻发生闹钟。
2) 代码介绍
n main函数代码描述
| int main(void) { calendar_type time_struct; /* 配置NVIC优先级组 */ nvic_priority_group_config(NVIC_PRIORITY_GROUP_4); /* 初始化系统时钟 */ system_clock_config(); /* 初始化ATSTART板子资源 */ at32_board_init(); /* 初始化串口 */ uart_print_init(115200); /* RTC初始化 */ time_struct.year = 2021; time_struct.month = 5; time_struct.date = 1; time_struct.hour = 12; time_struct.min = 0; time_struct.sec = 0; rtc_init(&time_struct); /* 闹钟初始化 */ alarm_init(); printf("initial ok\r\n"); while(1) { /* 秒更新了 */ if(rtc_flag_get(RTC_TS_FLAG) != RESET) { at32_led_toggle(LED3); /* 获取当前日历 */ rtc_time_get(); /* 打印日期:年-月-日 */ printf("%d/%d/%d ", calendar.year, calendar.month, calendar.date); /* 打印时间:时:分:秒 */ printf("%02d:%02d:%02d %s\r\n", calendar.hour, calendar.min, calendar.sec, weekday_table[calendar.week]); /* 等待上一次寄存器配置同步完成 */ rtc_wait_config_finish(); /* 清除秒钟标志 */ rtc_flag_clear(RTC_TS_FLAG); /* 等待寄存器配置同步完成 */ rtc_wait_config_finish(); } } } |
n RTC初始化rtc_init函数代码描述
| uint8_t rtc_init(calendar_type *calendar) { /* 开启PWC BPR时钟 */ crm_periph_clock_enable(CRM_PWC_PERIPH_CLOCK, TRUE); crm_periph_clock_enable(CRM_BPR_PERIPH_CLOCK, TRUE); /*解锁电池供电域写保护 */ pwc_battery_powered_domain_access(TRUE); /* 检测RTC是否初始化 */ if(bpr_data_read(BPR_DATA1) != 0x1234) { /* 复位电池供电域寄存器 */ bpr_reset(); /* 使能LEXT时钟 */ crm_clock_source_enable(CRM_CLOCK_SOURCE_LEXT, TRUE); /* 等待LEXT时钟稳定 */ while(crm_flag_get(CRM_LEXT_STABLE_FLAG) == RESET); /* 选择RTC时钟源 */ crm_rtc_clock_select(CRM_RTC_CLOCK_LEXT); /* 使能RTC时钟 */ crm_rtc_clock_enable(TRUE); /* 等待RTC寄存器同步 */ rtc_wait_update_finish(); /* 等待上一次寄存器配置同步完成 */ rtc_wait_config_finish(); /* 配置RTC分频器 */ rtc_divider_set(32767); /* 等待上一次寄存器配置同步完成 */ rtc_wait_config_finish(); /* 设置时间 */ rtc_time_set(calendar); /* 写入标志到电池供电域寄存器 */ bpr_data_write(BPR_DATA1, 0x1234); return 1; } else { /* 等待RTC寄存器同步 */ rtc_wait_update_finish(); /* 等待上一次寄存器配置同步完成 */ rtc_wait_config_finish(); return 0; } } |
n 闹钟中断函数代码描述
| void RTC_IRQHandler(void) { /*闹钟中断标志置起 */ if(rtc_flag_get(RTC_TA_FLAG) != RESET) { at32_led_toggle(LED4); /* 清除闹钟中断标志 */ rtc_flag_clear(RTC_TA_FLAG); } } |
4.4 实验效果n 信息通过串口打印出来,在电脑上通过串口助手观看打印信息。
n 主函数里每秒打印一次日历信息。
n 在21-05-01 12:00:05时刻发生闹钟,发生闹钟时LED4点亮。
5 案例 使用LICK时钟并校准5.1 功能简介使用LICK时钟作为RTC时钟,并通过定时器测量出LICK时钟频率,通过得到的频率值,调整RTC分频,达到在一定范围内校准时间的效果。
5.2 资源准备1) 硬件环境:
对应产品型号的AT-START BOARD
2) 软件环境:
project\at_start_f4xx\examples\rtc\lick_calibration
注:所有project都是基于keil 5而建立,若用户需要在其他编译环境上使用,请参考
AT32xxx_Firmware_Library_V2.x.x\project\at_start_xxx\templates中各种编译环境(例如IAR6/7,keil4/5)进行简单修改即可。
5.3 软件设计1) 配置流程
n RTC初始化
n 配置测量LICK频率的定时器
n 根据测量到的频率重新配置RTC分频
2) 代码介绍
n main函数代码描述
| int main(void) { tmr_input_config_type tmr_ic_init_structure; /* 初始化系统时钟 */ system_clock_config(); /* 初始化ATSTART板子资源 */ at32_board_init(); /* 初始化串口 */ uart_print_init(115200); /* RTC初始化 */ rtc_configuration(); printf("\r\n\nstart calib\r\n\r\n"); /* 获取系统时钟频率 */ crm_clocks_freq_get(&crm_clocks); /* 使能定时器 */ crm_periph_clock_enable(CRM_TMR5_PERIPH_CLOCK, TRUE); crm_periph_clock_enable(CRM_IOMUX_PERIPH_CLOCK, TRUE); /* 将LICK连接到定时器 */ gpio_pin_remap_config(TMR5CH4_MUX, TRUE); /* 定时器初始化 */ tmr_base_init(TMR5, 0xFFFF, 0); tmr_cnt_dir_set(TMR5, TMR_COUNT_UP); tmr_clock_source_div_set(TMR5, TMR_CLOCK_DIV1); /* 定时器初始化 */ tmr_input_default_para_init(&tmr_ic_init_structure); tmr_ic_init_structure.input_channel_select = TMR_SELECT_CHANNEL_4; tmr_ic_init_structure.input_polarity_select = TMR_INPUT_RISING_EDGE; tmr_ic_init_structure.input_mapped_select = TMR_CC_CHANNEL_MAPPED_DIRECT; tmr_ic_init_structure.input_filter_value = 0; tmr_input_channel_init(TMR5, &tmr_ic_init_structure, TMR_CHANNEL_INPUT_DIV_1); /* 初始化变量 */ operationcomplete = 0; /* 使能定时器输入捕获 */ tmr_counter_enable(TMR5, TRUE); /* 清除定时器标志 */ tmr_flag_get(TMR5, TMR_C4_FLAG); /* 使能定时器 */ tmr_interrupt_enable(TMR5, TMR_C4_INT, TRUE); /* 初始化中断 */ nvic_configuration(); /* 等待测量完成 */ while(operationcomplete != 2); /* 计算LICK频率 */ if(periodvalue != 0) { lickfreq = (uint32_t)((uint32_t)(crm_clocks.apb1_freq * 2) / (uint32_t)periodvalue); } printf("apb1_freq = %d\r\n", crm_clocks.apb1_freq); printf("period_value = %d\r\n", periodvalue); printf("lick_freq = %d\r\n", lickfreq); /* 调整RTC分频 */ rtc_divider_set((lickfreq - 1)); /* 等待寄存器写完成 */ rtc_wait_config_finish(); /* turn on led2 */ at32_led_on(LED2); while(1) { } } |
5.4 实验效果n 信息通过串口打印出来,在电脑上通过串口助手观看打印信息。
n 通串口打印出当前测量出的LICK的频率以及分频值。
n 每秒钟打印一次日历。
6 案例 读写电池供电数据寄存器6.1 功能简介对电池供电数据寄存器(RTC_BPRx)进行读写访问。
6.2 资源准备1) 硬件环境:
对应产品型号的AT-START BOARD
2) 软件环境:
project\at_start_f4xx\examples\bpr\bpr_data
注:所有project都是基于keil 5而建立,若用户需要在其他编译环境上使用,请参考
AT32xxx_Firmware_Library_V2.x.x\project\at_start_xxx\templates中各种编译环境(例如IAR6/7,keil4/5)进行简单修改即可。
6.3 软件设计1) 配置流程
n 开启PWC、BPR时钟
n 解锁电池供电域写保护
n 检查电池供电域数据是否正确
n 关闭PWC、BPR时钟降低功耗
2) 代码介绍
n main函数代码描述
| int main(void) { /* 初始化系统时钟 */ system_clock_config(); /* 初始化串口 */ uart_print_init(115200); /* 开启PWC BPR时钟 */ crm_periph_clock_enable(CRM_PWC_PERIPH_CLOCK, TRUE); crm_periph_clock_enable(CRM_BPR_PERIPH_CLOCK, TRUE); /*解锁电池供电域写保护 */ pwc_battery_powered_domain_access(TRUE); /* 清除入侵检测标志 */ bpr_flag_clear(BPR_TAMPER_EVENT_FLAG); /* 检查电池供电域数据 */ if(bpr_reg_check() == TRUE) { printf("bpr reg => none reset\r\n"); } else { printf("bpr reg => reset\r\n"); } /* 复位电池供电域寄存器 */ bpr_reset(); /*写电池供电域寄存器 */ bpr_reg_write(); /* 检查电池供电域数据 */ if(bpr_reg_check() == TRUE) { printf("write bpr reg ok\r\n"); } else { printf("write bpr reg fail\r\n"); } /* 关闭PWC BPR时钟降低功耗 */ crm_periph_clock_enable(CRM_PWC_PERIPH_CLOCK, FALSE); crm_periph_clock_enable(CRM_BPR_PERIPH_CLOCK, FALSE); while(1) { } } |
6.4 实验效果n 信息通过串口打印出来,在电脑上通过串口助手观看打印信息。
n 如果寄存器里数据正确打印bprreg => none reset。
n 如果寄存器里数据正确打印bprreg => reset。
n 主函数里每秒打印一次日历信息。
7 案例 入侵检测7.1 功能简介演示入侵检测功能使用,PC13脚当检测到一个上升沿后将触发入侵检测,当入侵事件发生时,电池供电数据寄存器将会被清除。
7.2 资源准备1) 硬件环境:
对应产品型号的AT-START BOARD
2) 软件环境:
project\at_start_f4xx\examples\bpr\tamper
注:所有project都是基于keil 5而建立,若用户需要在其他编译环境上使用,请参考
AT32xxx_Firmware_Library_V2.x.x\project\at_start_xxx\templates中各种编译环境(例如IAR6/7,keil4/5)进行简单修改即可。
7.3 软件设计1) 配置流程
n RTC初始化
n 初始化入侵检测
n 初始化电池供电寄存器
2) 代码介绍
n main函数代码描述
| int main(void) { /* 配置NVIC优先级组 */ nvic_priority_group_config(NVIC_PRIORITY_GROUP_4); /* 初始化系统时钟 */ system_clock_config(); /* 初始化ATSTART板子资源 */ at32_board_init(); /* 入侵检测中断配置 */ nvic_irq_enable(TAMPER_IRQn, 0, 0); /* 开启PWC BPR时钟 */ crm_periph_clock_enable(CRM_PWC_PERIPH_CLOCK, TRUE); crm_periph_clock_enable(CRM_BPR_PERIPH_CLOCK, TRUE); /* 解锁电池供电域写保护 */ pwc_battery_powered_domain_access(TRUE); /* 禁止入侵检测功能 */ bpr_tamper_pin_enable(FALSE); /* 禁止入侵检测中断 */ bpr_interrupt_enable(FALSE); /* 配置入侵检测为低电平检测 */ bpr_tamper_pin_active_level_set(BPR_TAMPER_PIN_ACTIVE_LOW); /* 清除入侵检测标志 */ bpr_flag_clear(BPR_TAMPER_EVENT_FLAG); /* 写数据到电池供电域寄存器 */ bpr_reg_write(); /* 使能入侵检测中断 */ bpr_interrupt_enable(TRUE); /* 使能入侵检测 */ bpr_tamper_pin_enable(TRUE); /* 检查电池供电域寄存器值 */ if(bpr_reg_check() == TRUE) { at32_led_on(LED2); } else { at32_led_off(LED2); } while(1) { } } |
n 入侵检测中断处理函数代码描述
| void TAMPER_IRQHandler(void) { if(bpr_flag_get(BPR_TAMPER_INTERRUPT_FLAG) != RESET) { /* 检查电池供电寄存器是否被清除掉 */ if(bpr_reg_judge() == 0) { /* ok, bpr registers are reset as expected */ at32_led_on(LED3); } else { /* bpr registers are not reset */ at32_led_off(LED3); } /* 清除入侵检测中断标志 */ bpr_flag_clear(BPR_TAMPER_INTERRUPT_FLAG); /* 清除入侵检测事件标志 */ bpr_flag_clear(BPR_TAMPER_EVENT_FLAG); /* 禁止入侵检测 */ bpr_tamper_pin_enable(FALSE); /* 使能入侵检测 */ bpr_tamper_pin_enable(TRUE); } } |
7.4 实验效果n 信息通过串口打印出来,在电脑上通过串口助手观看打印信息。
n 当发生入侵事件时(PC13出现低电平),LED3亮表示电池供电寄存器被清除。