项目需求:
使用STM32F103CBT6单片机通过HAL库开发和LL库共同开发RTC实时时钟。
且整个设备使用电池供电,考虑到产品大小并没有使用外接时钟芯片和备用时钟电池供电,RTC时钟使用单片机的HSE,单片机进入待机模式超24小时后,通过PA0唤醒单片机,年月日时分秒依旧显示正常,不会清零。
问题分析与描述
能翻到这篇文章的想必都是困于STM32F1系列单片机没有单独存储年月日的寄存器,且HAL库自动生成的库函数,使原本能计数138年的RTC计数器寄存器(RTC_CNTH和RTC_CNTL)只能加数到23时59分59秒就会被清零,这使得HAL库的RTC时钟在进入待机模式24小时后并不会将日期的天数+1,除非中间用闹钟中断唤醒,始终不让单片机进入待机模式24小时(这个方法小编试了没成)。
事实证明:STM32F1系列想要单独使用HAL库实现RTC实时时钟并在进入待机模式24小时后依旧正常运行比较困难。小编使用了HAL库+LL库共同开发的方式。且看下文:
解决思路:
使用标准库并不会出现RTC计数寄存器(RTC_CNTH和RTC_CNTL)在加到23时59分59秒被清零的情况,这样一来是不是可以使用标准库和HAL库相结合的方式实现RTC进入待机模式24小时唤醒后正常运行的目的呢,但话说回来标准库移植为HAL库稍微有些麻烦,然后小编就发现了LL库比较合适,LL库可以说比标准库更加库一点儿,也是可以直接对寄存器进行操作,更重要的是,它可以用STM32CubeMX直接生成而且可以一部分外设用HAL库,只有RTC用LL库,这样一来就达到了使RTC的计数寄存器(RTC_CNTH和RTC_CNTL)在加数到23时59分59秒并不会被清零,可以一直向上加的目的。解决了这一大问题就可以站在巨人的肩膀上看世界了,参考正点原子RTC实时时钟例程的实现方式,通过设置时间函数将要设置的年月日时分秒换算成秒数,直接赋值给RTC计数寄存器(RTC_CNTH和RTC_CNTL),之后不管待机不待机,RTC计数寄存器都会一直加下去,直到电池彻底没电。
RTC_CNTH和RTC_CNTL分别的数值
在想要获取当前时间的时候,读出寄存器中的秒数,通过年月日时分秒的换算,最后得出几年几月几日几时几分几秒。
HAL库RTC配置:
代码展示:
下面展示 rtc.c
。
/* Includes ------------------------------------------------------------------*/
#include "rtc.h"
/* USER CODE BEGIN 0 */
_calendar_obj calendar;//时钟结构体
const uint8_t table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表
const uint8_t mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};//平年的月份日期表
/* USER CODE END 0 */
/* RTC init function */
void MX_RTC_Init(void)
{
/* USER CODE BEGIN RTC_Init 0 */
/* USER CODE END RTC_Init 0 */
LL_RTC_InitTypeDef RTC_InitStruct = {0};
// LL_RTC_TimeTypeDef RTC_TimeStruct = {0};
LL_PWR_EnableBkUpAccess();
/* Enable BKP CLK enable for backup registers */
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_BKP);
/* Peripheral clock enable */
LL_RCC_EnableRTC();
/* RTC interrupt Init */
/* USER CODE BEGIN RTC_Init 1 */
LL_RTC_EnableIT_SEC(RTC);//开启秒中断
NVIC_SetPriority(RTC_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
NVIC_EnableIRQ(RTC_IRQn);
RTC_InitStruct.AsynchPrescaler = 32768;//32.768Mhz时钟的分频
LL_RTC_Init(RTC, &RTC_InitStruct);
LL_RTC_SetAsynchPrescaler(RTC,32768);
/* USER CODE END RTC_Init 1 */
/** Initialize RTC and set the Time and Date
*/
/** Initialize RTC and set the Time and Date
*/
/* USER CODE BEGIN RTC_Init 2 */
rtc_init_user();//设置初始时间值并每次待机唤醒后更新时间
//if(LL_RTC_BKP_GetRegister(BKP,LL_RTC_BKP_DR1)!= 0x5555)
// {
// LL_RTC_TIME_Init(RTC, LL_RTC_FORMAT_BIN, &RTC_TimeStruct);
// }
/* USER CODE END RTC_Init 2 */
}
/* USER CODE BEGIN 1 */
void rtc_init_user(void)
{
if(LL_RTC_BKP_GetRegister(BKP,LL_RTC_BKP_DR1)!=0x7777)//是否第一次配置
{
RTC_Set(2023,10,17,16,28,0); //设置日期和时间
LL_RTC_BKP_SetRegister(BKP,LL_RTC_BKP_DR1,0x7777);//标记已经初始化过了
}
RTC_Get();//更新时间
}
void RTC_Set(uint16_t syear,uint8_t smon,uint8_t sday,uint8_t hour,uint8_t min,uint8_t sec)
{
uint16_t t;
uint32_t seccount=0;
if(syear<1970||syear>2099) return;
for(t=1970;t<syear;t++) //把所有年份的秒钟相加
{
if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数
else seccount+=31536000; //平年的秒钟数
}
smon-=1;
for(t=0;t<smon;t++) //把前面月份的秒钟数相加
{
seccount+=(uint32_t)mon_table[t]*86400;//月份秒钟数相加
if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数
}
seccount+=(uint32_t)(sday-1)*86400;//把前面日期的秒钟数相加
seccount+=(uint32_t)hour*3600;//小时秒钟数
seccount+=(uint32_t)min*60; //分钟秒钟数
seccount+=sec;//最后的秒钟加上去
//设置时钟
RCC->APB1ENR|=1<<28;//使能电源时钟
RCC->APB1ENR|=1<<27;//使能备份时钟
PWR->CR|=1<<8; //取消备份区写保护
//上面三步是必须的!
RTC->CRL|=1<<4; //允许配置
RTC->CNTL=seccount&0xffff;
RTC->CNTH=seccount>>16;
RTC->CRL&=~(1<<4);//配置更新
while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成
RTC_Get();//设置完之后更新一下数据
}
//获取时间
void RTC_Get(void)
{
static uint16_t daycnt=0;
uint32_t timecount=0;
uint32_t temp=0;
uint16_t temp1=0;
timecount=RTC->CNTH;//得到计数器中的值(秒钟数)
timecount<<=16;
timecount+=RTC->CNTL;
temp=timecount/86400; //得到天数(秒钟数对应的)
if(daycnt!=temp)//超过一天了
{
daycnt=temp;
temp1=1970; //从1970年开始
while(temp>=365)
{
if(Is_Leap_Year(temp1))//是闰年
{
if(temp>=366)temp-=366;//闰年的秒钟数
else break;
}
else temp-=365; //平年
temp1++;
}
calendar.w_year=temp1;//得到年份
temp1=0;
while(temp>=28)//超过了一个月
{
if(Is_Leap_Year(calendar.w_year)&&temp1==1)//当年是不是闰年/2月份
{
if(temp>=29)temp-=29;//闰年的秒钟数
else break;
}
else
{
if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年
else break;
}
temp1++;
}
calendar.w_month=temp1+1; //得到月份
calendar.w_date=temp+1; //得到日期
}
temp=timecount%86400; //得到秒钟数
calendar.hour=temp/3600; //小时
calendar.min=(temp%3600)/60; //分钟
calendar.sec=(temp%3600)%60; //秒钟
calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);//获取星期
}
//判断是否是闰年函数
//月份 1 2 3 4 5 6 7 8 9 10 11 12
//闰年 31 29 31 30 31 30 31 31 30 31 30 31
//非闰年 31 28 31 30 31 30 31 31 30 31 30 31
//year:年份
//返回值:该年份是不是闰年.1,是.0,不是
uint8_t Is_Leap_Year(uint16_t year)
{
if(year%4==0) //必须能被4整除
{
if(year%100==0)
{
if(year%400==0)return 1;//如果以00结尾,还要能被400整除
else return 0;
}else return 1;
}else return 0;
}
//获得现在是星期几
//功能描述:输入公历日期得到星期(只允许1901-2099年)
//year,month,day:公历年月日
//返回值:星期号
uint8_t RTC_Get_Week(uint16_t year,uint8_t month,uint8_t day)
{
uint16_t temp2;
uint8_t yearH,yearL;
yearH=year/100; yearL=year%100;
// 如果为21世纪,年份数加100
if (yearH>19)yearL+=100;
// 所过闰年数只算1900年之后的
temp2=yearL+yearL/4;
temp2=temp2%7;
temp2=temp2+day+table_week[month-1];
if (yearL%4==0&&month<3)temp2--;
return(temp2%7);
}
/* USER CODE END 1 */
//秒中断在文件stm32f1xx_it.的第210行,主要用于每秒刷新一下时间
void RTC_IRQHandler(void)
{
/* USER CODE BEGIN RTC_IRQn 0 */
if(LL_RTC_IsActiveFlag_SEC(RTC))
{
LL_RTC_ClearFlag_SEC(RTC);
RTC_Get();//更新时间
SEGGER_RTT_printf(0,"666\r\n");
}
/* USER CODE END RTC_IRQn 0 */
/* USER CODE BEGIN RTC_IRQn 1 */
/* USER CODE END RTC_IRQn 1 */
}
首先感谢个位观众,希望能对你们有帮助;其次感谢优快云的各位前辈,每次看你们的文章都会感到同拨云见日茅塞顿开,最后希望世界更美好!