简介
实时时钟是一个独立的定时器
RTC模块和时钟配置系统(RCC_BDCR寄存器)处于后备区域,即在系统复位或从待机模式唤醒后,RTC的设置和时间维持不变
系统复位后,对后备寄存器和RTC的访问被禁止,这是为了防止对后备区域(BKP)的意外写操作。执行以下操作将使能对后备寄存器和RTC的访问:
- 设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备接口时钟
- 设置寄存器PWR_CR的DBP位,使能对后备寄存器和RTC的访问。
时钟源
通过设置备份域控制寄存器(RCC_BDCR)里的RTCSEL[1:0]位,RTCCLK时钟源可以由HSE/128、LSE或LSI时钟提供。 除非备份域复位,此选择不能被改变。
LSE由VDD或者VBAT供电。 LSI和HSE/128由VDD供电。 LSI可以在睡眠,停机和待机模式下保持运行
中断
- 闹钟中断,用来产生一个软件可编程的闹钟中断。
- 秒中断,用来产生一个可编程的周期性中断信号(最长可达1秒)。
- 溢出中断,指示内部可编程计数器溢出并回转为0的状态。
在待机模式下了RTC_PRL、RTC_ALR、RTC_CNT和RTC_DIV寄存器维持供电,且仅能通过备份域复位信号复位。
读RTC寄存器
写RTC寄存器
RTC寄存器配置过程
一般是用LSE低速外部时钟作为RTC时钟源
-
使能对后备寄存器和RTC的访问
- 设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备接口时钟
- 设置寄存器PWR_CR的DBP位,使能对后备寄存器和RTC的访问。
-
使能RTC的时钟,并选择RTC的时钟源 寄存器RCC_BDCR的RTCEN位和RTCSEL[1:0]位(一旦RTC时钟源被选定,直到下次后备域被复位,它不能在被改变) -
检查RTC寄存器同步标志 先将寄存器RTC_CRL的RSF写入0,再读取寄存器RTC_CRL的RSF是否被置位 -
进入配置模式 检查寄存器RTC_CRL的RTOFF是否被置位,被置位后将寄存器RTC_CRL的CNF位置位,RTC进入配置模式。 -
配置RTC中断与输出 根据需要配置RTC的输出(闹钟或者秒输出或者校准输出),寄存器BKP_RTCCR的 ASOS位,ASOE位,CCO位 RTC中断配置 寄存器RTC_CRH的OWIE位 ALRIE位 SECIE位 -
写入RTC预分配器与当前计数值 预分配器寄存器RTC_PRLH/L,RTC计数器寄存器 (RTC_CNTH / RTC_CNTL) -
设置RTC闹钟 RTC闹钟寄存器(RTC_ALRH/RTC_ALRL) -
退出配置模式 将寄存器RTC_CRL的CNF位复位,RTC退出配置模式。
demo
RTC实数时钟-使用C库函数time.h-外部时钟LSE-待机模式
采用STM32F103C8T6单片机,KeilMDK5.32版本
使用LSE作为时钟源 采用time.h库函数,可通过串口助手上位机修改RTC当前计数值 串口与上位机进行通信,串口发送设置为DMA单次模式发送(仿printf) 串口接收设置为DMA循环串口空闲接收,接收到用户数据并修改RTC CNT寄存器后进入待机模式 通过PA0来唤醒单片机 PC13控制LED灯,LED亮灭指示程序正在运行
STM32CubeMX配置 未开启RTC全局中断
修改生成的rtc.c文件MX_RTC_Init() 通过一个宏来确认RTC是否已经初始化完毕(时钟源的选择,预分频器,闹钟等等)。
#define RTC_INIT 1
RTC_HandleTypeDef hrtc;
void MX_RTC_Init(void)
{
#if (RTC_INIT == 1)
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
__HAL_RCC_BKP_CLK_ENABLE();
__HAL_RCC_PWR_CLK_ENABLE();
HAL_PWR_EnableBkUpAccess();
if (HAL_RTC_WaitForSynchro(&hrtc) != HAL_OK)
{
hrtc.State = HAL_RTC_STATE_ERROR;
}
else
{
hrtc.State = HAL_RTC_STATE_READY;
}
#else
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef DateToUpdate = {0};
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
if (HAL_RTC_Init(&hrtc) != HAL_OK)
{
Error_Handler();
}
sTime.Hours = 0x0;
sTime.Minutes = 0x0;
sTime.Seconds = 0x0;
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
{
Error_Handler();
}
DateToUpdate.WeekDay = RTC_WEEKDAY_MONDAY;
DateToUpdate.Month = RTC_MONTH_JANUARY;
DateToUpdate.Date = 0x1;
DateToUpdate.Year = 0x0;
if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BCD) != HAL_OK)
{
Error_Handler();
}
#endif
实现time.h库中time()函数 通过读取RTC当前计数值寄存器获取系统秒数
time_t time (time_t *_timer)
{
uint32_t timecounter = 0U;
uint16_t high1 = 0U, high2 = 0U, low = 0U;
high1 = READ_REG(hrtc.Instance->CNTH & RTC_CNTH_RTC_CNT);
low = READ_REG(hrtc.Instance->CNTL & RTC_CNTL_RTC_CNT);
high2 = READ_REG(hrtc.Instance->CNTH & RTC_CNTH_RTC_CNT);
if (high1 != high2)
{
timecounter = (((uint32_t) high2 << 16U) | READ_REG(hrtc.Instance->CNTL & RTC_CNTL_RTC_CNT));
}
else
{
timecounter = (((uint32_t) high1 << 16U) | low);
}
if(_timer != NULL)
{
*_timer = timecounter;
}
return timecounter;
}
串口空闲接收回调函数 接收来自用户自定义的系统秒数,并在main修改RTC CNT寄存器 重置DMA剩余传输数据
extern uint8_t receiveBuff[15];
extern uint32_t timer;
extern uint8_t flag;
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if(huart == &huart1)
{
__HAL_UART_CLEAR_IDLEFLAG(huart);
__HAL_DMA_DISABLE(huart->hdmarx);
WRITE_REG(huart->hdmarx->Instance->CNDTR, sizeof(receiveBuff));
__HAL_DMA_ENABLE(huart->hdmarx);
flag = 1;
timer = (uint32_t)receiveBuff[0] << 24 |
(uint32_t)receiveBuff[1] << 16 |
(uint32_t)receiveBuff[2] << 8 |
(uint32_t)receiveBuff[3];
}
}
在main()中修改系统秒数和串口打印当前时间
while (1)
{
HAL_Delay(1000);
time(&timeCount);
localtime_r(&timeCount, &timeNow);
USARTPrintf(&huart1, "%hu/%hhu/%hhu %hhu:%02hhu:%02hhu %zu\r\n",
timeNow.tm_year + 1900, timeNow.tm_mon + 1, timeNow.tm_mday,
timeNow.tm_hour + 8, timeNow.tm_min, timeNow.tm_sec, timeCount);
if(flag == 1)
{
flag = 0;
RTC_WriteTimeCounter(&hrtc, timer);
SET_BIT(PWR->CR, PWR_CR_CWUF_Msk);
SET_BIT(PWR->CR, PWR_CR_CSBF_Msk);
HAL_PWR_EnterSTANDBYMode();
}
PCout(13) = !PCin(13);
使能WKUP引脚(在STM32CubeMX中配置无效)
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
工程文件下载链接
RTC实数时钟-使用C库函数time.h-内部时钟LSI-待机模式
采用STM32F103C8T6单片机,KeilMDK5.32版本
使用LSI作为时钟源 采用time.h库函数,可通过串口助手上位机修改RTC当前计数值 串口与上位机进行通信,串口发送设置为DMA单次模式发送(仿printf) 串口接收设置为DMA循环串口空闲接收,接收到用户数据并修改RTC CNT寄存器后进入待机模式 通过PA0来唤醒单片机 PC13控制LED灯,LED亮灭指示程序正在运行
STM32CubeMX配置 其他配置与上面的工程一样
工程文件下载链接
调试发现 LSI 过快 LSE过慢。
低功耗模式下的自动唤醒(AWU)
RTC实数时钟-使用C库函数time.h-外部时钟LSE-周期性唤醒
采用STM32F103C8T6单片机,KeilMDK5.32版本
使用LSE作为时钟源 采用time.h库函数,可通过串口助手上位机修改RTC当前计数值 串口与上位机进行通信,串口发送设置为DMA单次模式发送(仿printf) 串口接收设置为DMA循环串口空闲接收,接收到用户数据并修改RTC CNT寄存器后进入待机模式(唤醒后等于复位,除了备份寄存器和RTC部分寄存器和电源控制/状态寄存器(PWR_CSR)没有被复位外,其他寄存器被复位。) 通过PA0来唤醒单片机或者RTC闹钟事件唤醒单片机 配置外部事件线17为上升沿触发 没有使用RTC的输出模式(秒/闹钟/校准输出到引脚PC13)故PC13还是可以正常使用 PC13控制LED灯,LED亮灭指示程序正在运行
为了简便起见,在每次串口接收到用户数据后,修改RTC的CNT寄存器后将RTC的闹钟寄存器的值加上10,也是就每次10秒后唤醒单片机。 本次实验在进入待机模式前设置闹钟每10秒唤醒一次
配置外部线事件17
STM32CubeMX配置 工程文件下载链接
|