STM32单片机芯片与内部26 RTC——万年历、时钟 标准库配置 HAL库配置
目录
一、RTC-LSE-标准库
1、后备寄存器
2、晶振设定
3、时间设置
4、时间获取
5、闹钟设定
6、中断配置
6、中断服务函数
二、RTC-LSE-HAL库
1、后备寄存器
2、晶振设定
3、时间设置
4、时间获取
5、闹钟设定
6、中断配置
6、中断服务函数
三、RTC-LSI-标准库
2、晶振设定
四、RTC-LSI-HAL库
2、晶振设定
五、RTC-用户侧
1、时间设置
2、时间获取
3、闹钟设定
4、星期计算
5、设定与复位
一、RTC-LSE-标准库
使用LSE时钟,安装纽扣电池,主电源掉电后可以继续运行,但是LSE晶振不太稳定,量产可能出现一定的问题。
1、后备寄存器
主要是PWR和BKP外设时钟,电源管理与备份时钟。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟
PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问
2、晶振设定
前文提到,LSE是不稳定的。
BKP_DeInit(); //复位备份区域
RCC_LSEConfig(RCC_LSE_ON); //设置外部低速晶振(LSE),使用外设低速晶振
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp<250) //检查指定的RCC标志位设置与否,等待低速晶振就绪
{
temp++;
delay_ms(10);
}
if(temp>=250)return 1;//初始化时钟失败,晶振有问题
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //设置RTC时钟(RTCCLK),选择LSE作为RTC时钟
RCC_RTCCLKCmd(ENABLE); //使能RTC时钟
......
RTC_SetPrescaler(32767); //设置RTC预分频的值
3、时间设置
本质设置的是UNIX时间,需要进行一定的转换。
void RTC_SetCounter(uint32_t CounterValue)
{
RTC_EnterConfigMode();
/* Set RTC COUNTER MSB word */
RTC->CNTH = CounterValue >> 16;
/* Set RTC COUNTER LSB word */
RTC->CNTL = (CounterValue & RTC_LSB_MASK);
RTC_ExitConfigMode();
}
4、时间获取
uint32_t RTC_GetCounter(void)
{
uint16_t tmp = 0;
tmp = RTC->CNTL;
return (((uint32_t)RTC->CNTH << 16 ) | tmp) ;
}
5、闹钟设定
void RTC_SetAlarm(uint32_t AlarmValue)
{
RTC_EnterConfigMode();
/* Set the ALARM MSB word */
RTC->ALRH = AlarmValue >> 16;
/* Set the ALARM LSB word */
RTC->ALRL = (AlarmValue & RTC_LSB_MASK);
RTC_ExitConfigMode();
}
6、中断配置
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_WaitForSynchro(); //等待RTC寄存器同步
RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能RTC秒中断
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_EnterConfigMode();/// 允许配置
RTC_NVIC_Config();//RCT中断分组设置
......
static void RTC_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn; //RTC全局中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级0位
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0位
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能该通道中断
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
}
6、中断服务函数
同时具有秒中断以及闹钟中断。注意RCC_IRQn为全局中断,秒中断与闹钟共用,如果未设定秒中断仅使能闹钟的话,可以用RTC_Alarm_IRQn。
void RTC_IRQHandler(void)
{
if (RTC_GetITStatus(RTC_IT_SEC) != RESET)//秒钟中断
{
RTC_Get();//更新时间
}
if(RTC_GetITStatus(RTC_IT_ALR)!= RESET)//闹钟中断
{
RTC_ClearITPendingBit(RTC_IT_ALR); //清闹钟中断
RTC_Get(); //更新时间
}
RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW); //清中断
RTC_WaitForLastTask();
}
二、RTC-LSE-HAL库
1、后备寄存器
主要是PWR和BKP外设时钟,电源管理与备份时钟。
__HAL_RCC_PWR_CLK_ENABLE(); //使能电源时钟PWR
HAL_PWR_EnableBkUpAccess(); //取消备份区域写保护
__HAL_RCC_BKP_CLK_ENABLE(); //使能BKP时钟
2、晶振设定
前文提到,LSE是不稳定的。
RCC_OscInitTypeDef RCC_OscInitStruct;
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct;
RCC_OscInitStruct.OscillatorType=RCC_OSCILLATORTYPE_LSE;//LSE配置
RCC_OscInitStruct.PLL.PLLState=RCC_PLL_NONE;
RCC_OscInitStruct.LSEState=RCC_LSE_ON; //RTC使用LSE
HAL_RCC_OscConfig(&RCC_OscInitStruct);
PeriphClkInitStruct.PeriphClockSelection=RCC_PERIPHCLK_RTC;//外设为RTC
PeriphClkInitStruct.RTCClockSelection=RCC_RTCCLKSOURCE_LSE;//RTC时钟源为LSE
HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);
__HAL_RCC_RTC_ENABLE();//RTC时钟使能
3、时间设置
本质设置的是UNIX时间,需要进行一定的转换。
static HAL_StatusTypeDef RTC_WriteTimeCounter(RTC_HandleTypeDef *hrtc, uint32_t TimeCounter)
{
HAL_StatusTypeDef status = HAL_OK;
/* Set Initialization mode */
if (RTC_EnterInitMode(hrtc) != HAL_OK)
{
status = HAL_ERROR;
}
else
{
/* Set RTC COUNTER MSB word */
WRITE_REG(hrtc->Instance->CNTH, (TimeCounter >> 16U));
/* Set RTC COUNTER LSB word */
WRITE_REG(hrtc->Instance->CNTL, (TimeCounter & RTC_CNTL_RTC_CNT));
/* Wait for synchro */
if (RTC_ExitInitMode(hrtc) != HAL_OK)
{
status = HAL_ERROR;
}
}
return status;
}
4、时间获取
static uint32_t RTC_ReadAlarmCounter(RTC_HandleTypeDef *hrtc)
{
uint16_t high1 = 0U, low = 0U;
high1 = READ_REG(hrtc->Instance->ALRH & RTC_CNTH_RTC_CNT);
low = READ_REG(hrtc->Instance->ALRL & RTC_CNTL_RTC_CNT);
return (((uint32_t) high1 << 16U) | low);
}
5、闹钟设定
static HAL_StatusTypeDef RTC_WriteAlarmCounter(RTC_HandleTypeDef *hrtc, uint32_t AlarmCounter)
{
HAL_StatusTypeDef status = HAL_OK;
/* Set Initialization mode */
if (RTC_EnterInitMode(hrtc) != HAL_OK)
{
status = HAL_ERROR;
}
else
{
/* Set RTC COUNTER MSB word */
WRITE_REG(hrtc->Instance->ALRH, (AlarmCounter >> 16U));
/* Set RTC COUNTER LSB word */
WRITE_REG(hrtc->Instance->ALRL, (AlarmCounter & RTC_ALRL_RTC_ALR));
/* Wait for synchro */
if (RTC_ExitInitMode(hrtc) != HAL_OK)
{
status = HAL_ERROR;
}
}
return status;
}
6、中断配置
__HAL_RTC_ALARM_ENABLE_IT(&RTC_Handler,RTC_IT_SEC); //允许秒中断
__HAL_RTC_ALARM_ENABLE_IT(&RTC_Handler,RTC_IT_ALRA); //允许闹钟中断
HAL_NVIC_SetPriority(RTC_IRQn,0x01,0x02); //抢占优先级1,子优先级2
HAL_NVIC_EnableIRQ(RTC_IRQn);
6、中断服务函数
同时具有秒中断以及闹钟中断。
void RTC_IRQHandler(void)
{
if(__HAL_RTC_ALARM_GET_FLAG(&RTC_Handler,RTC_FLAG_SEC)!=RESET) //秒中断
{
__HAL_RTC_ALARM_CLEAR_FLAG(&RTC_Handler,RTC_FLAG_SEC); //清除秒中断
RTC_Get(); //更新时间
LED1=!LED1; //LED1翻转
}
if(__HAL_RTC_ALARM_GET_FLAG(&RTC_Handler,RTC_FLAG_SEC)!=RESET) //闹钟中断
{
__HAL_RTC_ALARM_CLEAR_FLAG(&RTC_Handler,RTC_FLAG_ALRAF); //清除闹钟中断
RTC_Get(); //更新时间
printf("ALARM A!\r\n");
}
__HAL_RTC_ALARM_CLEAR_FLAG(&RTC_Handler,RTC_FLAG_OW); //清除溢出
}
三、RTC-LSI-标准库
使用LSI时钟,安装纽扣电池,主电源掉电后不可以继续运行,但是LSI晶振稳定,量产一般不出现问题。
只阐述配置不同的地方
2、晶振设定
/* 使能 LSI */
RCC_LSICmd(ENABLE);
/* 等待 LSI 准备好 */
while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET)
{}
当然还有分频设定,这个就不写了,因为LSI是40KHz,进行40000分频即可1Hz,外部时钟不太一样,一般而言用32768Hz,进行32768分频即可1Hz。
四、RTC-LSI-HAL库
2、晶振设定
RCC_OscInitTypeDef RCC_OscInitStruct;
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct;
/*##-2-将ISE配置为RTC时钟源 ###################################*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE ;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
RCC_OscInitStruct.LSEState = RCC_LSE_ON;
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;
PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
五、RTC-用户侧
将Unix转为正常可看时间。用户侧理论不和BSP打交道,所以除了基本调用的函数外,其他的应该均保持一致。
1、时间设置
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
{
u16 t;
u32 seccount=0;
if(syear<1970||syear>2099)return 1;
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+=(u32)mon_table[t]*86400;//月份秒钟数相加
if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数
}
seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加
seccount+=(u32)hour*3600;//小时秒钟数
seccount+=(u32)min*60; //分钟秒钟数
seccount+=sec;//最后的秒钟加上去
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟
PWR_BackupAccessCmd(ENABLE); //使能RTC和后备寄存器访问
RTC_SetCounter(seccount); //设置RTC计数器的值
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
return 0;
}
2、时间获取
u8 RTC_Get(void)
{
static u16 daycnt=0;
u32 timecount=0;
u32 temp=0;
u16 temp1=0;
timecount=RTC_GetCounter();
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 {temp1++;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);//获取星期
return 0;
}
3、闹钟设定
u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
{
u16 t;
u32 seccount=0;
if(syear<1970||syear>2099)return 1;
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+=(u32)mon_table[t]*86400;//月份秒钟数相加
if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数
}
seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加
seccount+=(u32)hour*3600;//小时秒钟数
seccount+=(u32)min*60; //分钟秒钟数
seccount+=sec;//最后的秒钟加上去
//设置时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟
PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问
//上面三步是必须的!
RTC_SetAlarm(seccount);
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
return 0;
}
4、星期计算
u8 RTC_Get_Week(u16 year,u8 month,u8 day)
{
u16 temp2;
u8 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);
}
5、设定与复位
烧录的代码是对时间进行一个设定,那岂不是每次上电重启后又会对时间进行重设;总不能先烧录一个设定代码,再烧录一个不设定时间的代码吧。
本质上,需要有一个标志,决定是第一次运行代码还是第二次及之后运行代码,且该标志可以掉电不丢失。
BKP_DR寄存器。备份寄存器是42个16位的寄存器,可用来存储84个字节的用户应用程序数据。他们处在备份域里,当VDD电源被切断,他们仍然由VBAT维持供电。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。
void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data)
{
__IO uint32_t tmp = 0;
/* Check the parameters */
assert_param(IS_BKP_DR(BKP_DR));
tmp = (uint32_t)BKP_BASE;
tmp += BKP_DR;
*(__IO uint32_t *) tmp = Data;
}
uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR)
{
__IO uint32_t tmp = 0;
/* Check the parameters */
assert_param(IS_BKP_DR(BKP_DR));
tmp = (uint32_t)BKP_BASE;
tmp += BKP_DR;
return (*(__IO uint16_t *) tmp);
}
#define BKP_DR1 ((uint16_t)0x0004)
#define BKP_DR2 ((uint16_t)0x0008)
#define BKP_DR3 ((uint16_t)0x000C)
#define BKP_DR4 ((uint16_t)0x0010)
#define BKP_DR5 ((uint16_t)0x0014)
#define BKP_DR6 ((uint16_t)0x0018)
#define BKP_DR7 ((uint16_t)0x001C)
#define BKP_DR8 ((uint16_t)0x0020)
#define BKP_DR9 ((uint16_t)0x0024)
#define BKP_DR10 ((uint16_t)0x0028)
#define BKP_DR11 ((uint16_t)0x0040)
#define BKP_DR12 ((uint16_t)0x0044)
#define BKP_DR13 ((uint16_t)0x0048)
#define BKP_DR14 ((uint16_t)0x004C)
#define BKP_DR15 ((uint16_t)0x0050)
#define BKP_DR16 ((uint16_t)0x0054)
#define BKP_DR17 ((uint16_t)0x0058)
#define BKP_DR18 ((uint16_t)0x005C)
#define BKP_DR19 ((uint16_t)0x0060)
#define BKP_DR20 ((uint16_t)0x0064)
#define BKP_DR21 ((uint16_t)0x0068)
#define BKP_DR22 ((uint16_t)0x006C)
#define BKP_DR23 ((uint16_t)0x0070)
#define BKP_DR24 ((uint16_t)0x0074)
#define BKP_DR25 ((uint16_t)0x0078)
#define BKP_DR26 ((uint16_t)0x007C)
#define BKP_DR27 ((uint16_t)0x0080)
#define BKP_DR28 ((uint16_t)0x0084)
#define BKP_DR29 ((uint16_t)0x0088)
#define BKP_DR30 ((uint16_t)0x008C)
#define BKP_DR31 ((uint16_t)0x0090)
#define BKP_DR32 ((uint16_t)0x0094)
#define BKP_DR33 ((uint16_t)0x0098)
#define BKP_DR34 ((uint16_t)0x009C)
#define BKP_DR35 ((uint16_t)0x00A0)
#define BKP_DR36 ((uint16_t)0x00A4)
#define BKP_DR37 ((uint16_t)0x00A8)
#define BKP_DR38 ((uint16_t)0x00AC)
#define BKP_DR39 ((uint16_t)0x00B0)
#define BKP_DR40 ((uint16_t)0x00B4)
#define BKP_DR41 ((uint16_t)0x00B8)
#define BKP_DR42 ((uint16_t)0x00BC)
可以看到,如果每次上电后先读取一次BKP_DR1,如果不是设定的标志,就说明第一次运行则进行时间设定,并对BKP_DR1进行写入标志;后续上电读取发现已设定,则不再重复设定。