- crm_periph_clock_enable(CRM_PWC_PERIPH_CLOCK, TRUE);
2) 使能 BPR 接口时钟
- crm_periph_clock_enable(CRM_BPR_PERIPH_CLOCK, TRUE);
3) 解锁电池供电域写保护
- pwc_battery_powered_domain_access(TRUE);
RTC 寄存器同步
由于 RTC 由电池供电域的计数逻辑和 APB1 接口的寄存器组成,寄存器的读写存在同步逻辑。
― 寄存器写:需要等待上一次的 RTC 寄存器配置完成后(CFGF = 1),才能进行新的写操作。
― 寄存器读:当寄存器值从电池供电域更新到 APB1 接口时 UPDF 标志置 1。当在系统复位、电源复位、从待机、深度睡眠模式唤醒后,有可能寄存器还未完全同步,所以需要先软件将 UPDF标志清除,然后等待 UPDF 标志置 1,以读取正确的值。
RTC 同步相关函数
等待上一次 RTC 寄存器配置完成(写寄存器之前使用)
- void rtc_wait_config_finish(void);
等待 RTC 寄存器更新完成(读取寄存器之前使用)
- void rtc_wait_update_finish(void);
RTC 寄存器写
写 RTC_DIV、RTC_TA、RTC_CNT 寄存器需要先进入配置模式(CFGEN = 1),然后才能对寄存器进行写操作,当退出配置模式(CFGEN = 0)时,就会将寄存器值实际写到电池供电域,这个过程至少需要 3 个 RTCCLK 周期。
下表列举了 RTC 寄存器受写保护状态,以及写入的条件:


寄存器复位
RTC 寄存器处于电池供电域,可以 CRM_BPDC 的 BPDRST 进行电池供电域复位,也可以由提供的库函数对每个寄存器写默认值进行复位。
RTC 复位相关函数
电池供电域复位
- void crm_battery_powered_domain_reset(confirm_state new_state);
或者
两个函数功能一样,只是 bpr_reset()封装了前一个函数。
2.2 时钟设置
时钟源选择RTC 时钟源经过选择后输入到分频器,最终得到 1Hz 的时钟用来更新日历。


RTC 的时钟源共有 3 种可以选择:
― LEXT:外部低速晶振,通常为 32.768kHz
― LICK:内部低速晶振,通常典型值为 40kHz 范围(30~60kHz),详情请见各型号的 datasheet
― HEXT_DIV:外部高速晶振分频后得到的时钟,不同型号分频值请见下表




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 时钟,计算公式如下:


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 时钟源、事件以及中断对唤醒低功耗模式的影响:






中断、事件相关函数
中断使能函数
- 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:以闹钟为例,使用 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:以闹钟为例,使用 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 分频输出。
― 事件输出:闹钟事件、秒事件

当输出模式为事件输出时(闹钟事件、秒事件),当发生相应事件时将会输出一个脉冲,脉冲的宽度为一个 RTC 时钟周期。
事件输出相关函数
事件输出设置并使能
- void bpr_rtc_output_select(bpr_rtc_output_type output_source);
4 RTC 使用注意事项
4.1 读写电池供电域寄存器前提
读写电池供电域寄存器(电池供电数据寄存器 BPR_DT、RTC 寄存器、CRM 的 BPDC 寄存器)之前,都需要将 PWC,BPR 时钟开启,解锁电池供电域写保护(BPWEN 位置 1),执行以下代码:
- /* 开启 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);
例如以下程序:先开启 PWC、BPR 时钟,解锁电池供电域写保护(BPWEN 位置 1),然后读取BPR_DT 寄存器、写 CRM 的 BPDC 寄存器、写 RTC 寄存器。- /* 开启 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);
- }
4.2 sBPWEN 位操作注意事项
在程序中不要去开关 BPWEN 位,若要操作 BPR 数据,在代码初始化时将 PWC、BPR 时钟和BPWEN 位开启:
- /* 开启 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 DT 数据时,直接更改即可。(不要去开关 BPWEN 位)- bpr_data_write(BPR_DATA1, 0x1234);
5 案例 使用日历以及闹钟功能
5.1 功能简介
演示日历功能、闹钟功能的使用。
5.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,keil 4/5)进行简单修改即可。
5.3 软件设计
1) 配置流程
- 开启 PWC、BPR 时钟
- 解锁电池供电域写保护
- 检查日历是否已经初始化,如果正确就跳过初始化,如果不正确就初始化日历以及闹钟
- 主函数里每秒打印一次日历信息
- 在 21-05-01 12:00:05 时刻发生闹钟。
2) 代码介绍
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();
- }
- }
- }
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;
- }
- }
闹钟中断函数代码描述
- void RTC_IRQHandler(void)
- {
- /*闹钟中断标志置起 */
- if(rtc_flag_get(RTC_TA_FLAG) != RESET)
- {
- at32_led_toggle(LED4);
- /* 清除闹钟中断标志 */
- rtc_flag_clear(RTC_TA_FLAG);
- }
- }
5.4 实验效果
- 信息通过串口打印出来,在电脑上通过串口助手观看打印信息。
- 主函数里每秒打印一次日历信息。
- 在 21-05-01 12:00:05 时刻发生闹钟,发生闹钟时 LED4 点亮。
6 案例 使用 LICK 时钟并校准
6.1 功能简介
使用 LICK 时钟作为 RTC 时钟,并通过定时器测量出 LICK 时钟频率,通过得到的频率值,调整RTC 分频,达到在一定范围内校准时间的效果。
6.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,keil 4/5)进行简单修改即可。
6.3 软件设计
1) 配置流程
- RTC 初始化
- 配置测量 LICK 频率的定时器
- 根据测量到的频率重新配置 RTC 分频
2) 代码介绍
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)
- {
- }
- }
6.4 实验效果
- 信息通过串口打印出来,在电脑上通过串口助手观看打印信息。
- 通串口打印出当前测量出的 LICK 的频率以及分频值。
- 每秒钟打印一次日历。
7 案例 读写电池供电数据寄存器
7.1 功能简介
对电池供电数据寄存器(RTC_BPRx)进行读写访问。
7.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,keil 4/5)进行简单修改即可。
7.3 软件设计
1) 配置流程
- 开启 PWC、BPR 时钟
- 解锁电池供电域写保护
- 检查电池供电域数据是否正确
- 关闭 PWC、BPR 时钟降低功耗
2) 代码介绍
main 函数代码描述