蓝桥杯嵌入式开发指南 07 - 实时时钟配置(RTC)

实例

通过配置实时时钟,在 LCD 屏幕上显示时间。

电路分析

实时时钟(RTC)是一个用于获取时间的模块,STM32 把实时时钟集成在了芯片内部,它内部具有专门的实时时钟单元,不需要外接其他模块。

工程建立

本次工程在上一次的工程上继续修改,复制上一次的工程作为本次的工程基础。

添加库函数

本例中使用到了 RTC 时钟功能以及相关的 PWR 和 BKP 功能,故要添加以下库函数:

  • stm32f10x_rtc.c
  • stm32f10x_bkp.c
  • stm32f10x_pwr.c

实时时钟 RTC

简介

实时时钟是一个独立的定时器。RTC 模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。
可编程的预分频系数:分频系数最高为 220。

主要特性

  • 32 位的可编程计数器,可用于较长时间段的测量
  • 2 个单独的时钟:用于 APB1 接口的 PCLK1 和 RTC 时钟(此时的 RTC 时钟必须小于 PCLK1 时钟的四分之一以上)
  • 2 种独立的复位类型
  • APB1 接口由系统复位
  • RTC 只能由后备域复位。
  • 3 个专门的可屏蔽中断:
    • 闹钟中断:用来产生一个软件可编程的闹钟中断
    • 秒中断:用来产生一个可编程的周期性中断信号(最长可达 1 秒)
    • 溢出中断:检测内部可编程计数器溢出并回转为 0 的状态
STMicroelectronicsSTM32F10x 中文参考手册

RTC 相关模块

RTC 模块和时钟配置系统 (RCC_BDCR 寄存器) 处于后备区域,即在系统复位或从待机模式唤醒后,RTC 的设置和时间维持不变。
系统复位后,对后备寄存器和 RTC 的访问被禁止,这是为了防止对后备区域 (BKP) 的意外写操作。执行以下操作将使能对后备寄存器和 RTC 的访问:

  • 设置寄存器 RCC_APB1ENR 的 PWREN 和 BKPEN 位,使能电源和后备接口时钟
  • 设置寄存器 PWR_CR 的 DBP 位,使能对后备寄存器和 RTC 的访问。
STMicroelectronicsSTM32F10x 中文参考手册

故需要先使能备份寄存器 BKP 后,才能对 RTC 进行访问。
同时,备份寄存器 BKP 受到 PWR 电源管理控制,故需要在 PWR 中先使能 Backup 寄存器,若没有使能,则无法正常对 RTC 进行读写操作。

初始化 RTC 时钟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
void RTC_Init(void)
{
u8 HH=0,MM=0,SS=0;
NVIC_InitTypeDef NVIC_InitStructure;

//开启PWR和BKP时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR|RCC_APB1Periph_BKP,ENABLE);
//允许访问BKP区域
PWR_BackupAccessCmd(ENABLE);
//初始化BKP
BKP_DeInit();

//配置RTC中断
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

//使能RTC中断
NVIC_Init(&NVIC_InitStructure);

//使能LSI时钟
RCC_LSICmd(ENABLE);
//选择LSI作为时钟源
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);
//使能RTC时钟
RCC_RTCCLKCmd(ENABLE);

//等待APB1时钟与RTC时钟同步
RTC_WaitForSynchro();
RTC_WaitForLastTask();

//设置分频
RTC_SetPrescaler(40000-1);
//等待操作完成
RTC_WaitForLastTask();

//设置初始时间
RTC_SetCounter(3600*HH+60*MM+SS);
RTC_WaitForLastTask();

//秒中断使能
RTC_ITConfig(RTC_IT_SEC,ENABLE);
RTC_WaitForLastTask();
}

特别提醒


LSI 时钟频率大约 40KHz,RTC 需要 1HZ 的时钟,此处所填写的预分频值 40000-1 其实是一个估算值,若要将 RTC 时钟作为精准时钟,需要对通过定时器捕获来对 LSI 进行计算校正。

配置 RTC 中断

定义全局变量:

1
u8 RTC_Flag=1; //RTC1秒标志
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void RTC_IRQHandler(void)
{
u32 Secs;
if(RTC_GetITStatus(RTC_IT_SEC)==1)
{
RTC_ClearITPendingBit(RTC_IT_SEC);
RTC_Flag=1;
Secs = RTC_GetCounter();
RTC_WaitForLastTask();
if(Secs>=86400-1) //86400=24*60*60 Secs
{
RTC_SetCounter(0);
RTC_WaitForLastTask();
}
}
}

特别提醒

在编写 RTC 中断的时候需要特别注意:

1
if(Times>=86400-1) //86400=24\*60\*60
若没有 - 1,则会出现不合理的时间:24:00:00。

数据显示

实例,给程序添加如下功能:

  • 初始化 RTC 时钟时间为:23:59:55
  • 在 LCD 的第 8 行以 Blue2 颜色按格式显示 “Time:HH:MM:SS”RTC 时钟时间

定义全局变量:

1
2
3
4
u32 Time_Value=0;
u8 Hour=0;
u8 Min=0;
u8 Sec=0;

修改 RTC 初始化程序:

1
2
3
4
void RTC_Init(void)
{
u8 HH=23,MM=59,SS=55;
......

修改主程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main(void)
{
......
//初始化RTC时钟
RTC_Init();
......
while(1)
{
......
if(RTC_Flag)
{
RTC_Flag=0;
Time_Value=RTC_GetCounter();
Hour=Time_Value/3600;
Min=Time_Value%3600/60;
Sec=Time_Value%3600%60;
sprintf((char*) Display_String," Time:%.2d:%.2d:%.2d",Hour,Min,Sec);
LCD_SetTextColor(Blue2);
LCD_DisplayStringLine(Line8,Display_String);
}
}
}

下载测试

将程序下载到开发板,可以看到 LCD 屏幕第八行按要求正确显示 RTC 时钟时间。

延伸拓展

RTC 时钟 LSI 时钟源校准方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#ifdef LSI_TIM_MEASURE
/\* Enable the LSI OSC \*/
RCC_LSICmd(ENABLE);

/\* Wait till LSI is ready \*/
while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET);

/\* TIM Configuration \*/
TIM5_ConfigForLSI();

/\* Wait until the TIM5 get 2 LSI edges \*/
while(CaptureNumber != 2);

/\* Disable TIM5 CC4 Interrupt Request \*/
TIM_ITConfig(TIM5, TIM_IT_CC4, DISABLE);
#endif

#ifdef LSI_TIM_MEASURE
/\*\*
\* @brief Configures TIM5 to measure the LSI oscillator frequency.
\* @param None
\* @retval None
\*/
void TIM5_ConfigForLSI(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;

/\* Enable TIM5 clocks \*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);

/\* Enable the TIM5 Interrupt \*/
NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

/\* Configure TIM5 prescaler \*/
TIM_PrescalerConfig(TIM5, 0, TIM_PSCReloadMode_Immediate);

/\* Connect internally the TM5_CH4 Input Capture to the LSI clock output \*/
GPIO_PinRemapConfig(GPIO_Remap_TIM5CH4_LSI, ENABLE);

/\* TIM5 configuration: Input Capture mode ---------------------
The LSI oscillator is connected to TIM5 CH4
The Rising edge is used as active edge,
The TIM5 CCR4 is used to compute the frequency value
------------------------------------------------------------ \*/
TIM_ICInitStructure.TIM_Channel = TIM_Channel_4;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV8;
TIM_ICInitStructure.TIM_ICFilter = 0;
TIM_ICInit(TIM5, &TIM_ICInitStructure);

/\* TIM10 Counter Enable \*/
TIM_Cmd(TIM5, ENABLE);

/\* Reset the flags \*/
TIM5->SR = 0;

/\* Enable the CC4 Interrupt Request \*/
TIM_ITConfig(TIM5, TIM_IT_CC4, ENABLE);
}
#endif
STMicroelectronicsSTM32F10x Standard Peripherals Firmware Library