stm32_ota/SYSTEM/RTC/rtc.c

519 lines
21 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "rtc.h"
#include "syslib.h"
#include "stm32f10x_pwr.h"
#include "stm32f10x_bkp.h"
#include "stm32f10x_rtc.h"
#include "stm32f10x_exti.h"
#include "stm32f10x_it.h"
#include "sysport.h"
/********************************************************************************
* @file rtc.c
* @author 晏诚科技 Mr.Wang
* @version V1.0.0
* @date 11-Dec-2018
* @brief 提供STM32内部rtc相关驱动
******************************************************************************
*******************************************************************************/
/*****************************************
*供内部使用的常变量
****************************************/
RTCFP rtcSecFp = NULL ; //RTC秒中断回调函数指针
RTCFP rtcAlrFp = NULL ; //RTC闹钟中断回调函数指针
const uint8_t weekTable[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表
const uint8_t monTable[12] ={31,28,31,30,31,30,31,31,30,31,30,31}; //平年的月份日期表
/*****************************************
*供外部使用的常变量
****************************************/
Calendar_u uCalendar ; //Calendar_u共用体变量uCalendar用于纪录实时时间
/**************************************************************************************************
* 名 称RTCFP Rtc_RegHookCallback(uint16_t rtcIt, RTCFP pCallback)
* 功能说明rtc中断回调函数-注册函数驱动中定义了rtcSecFp秒中断回调函数指针类型并定义了rtcAlrFp闹钟中断回调函数指针变量
* Rtc_RegHookCallback函数就是将回调函数地址传递给rtcSecFp\rtcAlrFp指针变量
* 入口参数:
* @param1 rtcIt RTC_IT_SEC 或者RTC_IT_ALR
* @param2 pCallback RTCFP类型函数指针
* 出口参数:
* @param1 pCallback RTCFP类型函数指针
*************************************************************************************************/
RTCFP Rtc_RegHookCallback(uint16_t rtcIt, RTCFP pCallback)
{
if( RTC_IT_SEC == rtcIt ) //秒中断回调函数注册
{
if( rtcSecFp == NULL )
rtcSecFp = pCallback ;
else
SysErr("Rtc SecTi Callback repeat reg!") ;
}
else if( RTC_IT_ALR == rtcIt)
{
if( rtcAlrFp == NULL )
rtcAlrFp = pCallback ;
else
SysErr("Rtc AlrTi Callback repeat reg!") ;
}
else
{
SysErr("Rtc_RegHookCallback Failed!") ;
}
return pCallback ;
}
/**************************************************************************************************
* 名 称void Rtc_Hook(uint16_t rtcIt)
* 功能说明RTC中断内调用的钩子函数执行到中断会把相应的回调函数勾出来运行嘿嘿嘿。。。
* 当然不同的RTC_IT会勾出不同的回调函数
* 入口参数:
* @param1 rtcIt RTC_IT_SEC 或者RTC_IT_ALR
*************************************************************************************************/
void Rtc_Hook(uint16_t rtcIt)
{
switch(rtcIt)
{
case RTC_IT_SEC:
if( rtcSecFp != NULL)
rtcSecFp() ;
break ;
case RTC_IT_ALR: if( rtcAlrFp != NULL) rtcAlrFp() ; break ;
default: break ;
}
}
/**************************************************************************************************
* 名 称: void RTC_IRQHandler(void)
* 功能说明: RTC时钟中断
* 说 明: 每秒触发一次中断
*************************************************************************************************/
void RTC_IRQHandler(void)
{
if ( RTC_GetITStatus(RTC_IT_SEC) != RESET )//秒钟中断
{
RTC_ClearITPendingBit(RTC_IT_SEC); //清秒中断
RTC_WaitForLastTask() ;
SysLog("RTC_IT_SEC!") ;
Rtc_Hook(RTC_IT_SEC) ;
}
if( RTC_GetITStatus(RTC_IT_ALR)!= RESET ) //闹钟中断
{
RTC_ClearITPendingBit(RTC_IT_ALR); //清闹钟中断
RTC_WaitForLastTask() ;
SysLog("RTC_IT_ALR!") ;
Rtc_Hook(RTC_IT_ALR) ;
}
}
/**************************************************************************************************
* 名 称: void RTCAlarm_IRQHandler(void)
* 功能说明: RTC闹钟中断
* 说 明: RTC计数器到达RTC->ALR值时发生中断。 注意RTC闹钟中断挂载在EXTI_Line17中断线上的注意需要清标志位
*************************************************************************************************/
void RTCAlarm_IRQHandler(void)
{
if( RTC_GetITStatus(RTC_IT_ALR)!= RESET ) //闹钟中断
{
EXTI_ClearITPendingBit(EXTI_Line17);
RTC_ClearITPendingBit(RTC_IT_ALR); //清闹钟中断
RTC_WaitForLastTask() ;
//SysLog("RTC_IT_ALR!") ;
Rtc_Hook(RTC_IT_ALR) ;
}
}
/**************************************************************************************************
* 名 称: void Rtc_Sec_Callback(void)
* 功能说明: RTC秒中断回调函数
*************************************************************************************************/
void Rtc_Sec_Callback(void)
{
RTC_Get(&uCalendar); //更新时间
}
/**************************************************************************************************
* 名 称: void RTC_NVIC_Config(IntPriority_e ePriority)
* 功能说明: RTC时钟中断和RTC闹钟中断优先级管理
* 入口参数:
* @param1 ePriority IntPriority_e枚举类型表示RTC中断的中断抢占优先级
*************************************************************************************************/
void RTC_NVIC_Config(IntPriority_e ePriority)
{
NVIC_InitTypeDef NVIC_InitStructure ;
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn ; //RTC中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = ePriority ; //设置抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0 ; //设置子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE ; //使能该通道中断
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
// NVIC_InitStructure.NVIC_IRQChannel = RTCAlarm_IRQn ; //RTCAlarm中断
// NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = ePriority ; //设置抢占优先级
// NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0 ; //设置子优先级
// NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
// NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
}
/**************************************************************************************************
* 名 称: RunResult RTC_Init(IntPriority_e ePriority)
* 功能说明: 初始化RTC功能块
* 入口参数:
* @param1 ePriority IntPriority_e枚举类型表示RTC中断的中断抢占优先级
* 出口参数: RunResult: 反映处理结果
* 说 明: 当LSE失效会启用LSI作为RTC时钟LSI频率漂移大可能会导致走时不准的问题
*************************************************************************************************/
RunResult RTC_Init(IntPriority_e ePriority)
{
//检查是不是第一次配置时钟
uint32_t lsTimeOut = 0 ;
uCalendar.sCalendar.spacing = 0x20 ; uCalendar.sCalendar.dash1 = uCalendar.sCalendar.dash2 = '-' ; uCalendar.sCalendar.colon1 = uCalendar.sCalendar.colon2 = ':' ;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE ) ; //使能PWR和BKP外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE) ; //使能闹钟必须开启AFIO时钟
PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问
if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050) //从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎
{
BKP_DeInit(); //复位备份区域
SysLog("BKP_DeInit!") ;
RCC_LSEConfig(RCC_LSE_ON); //设置外部低速晶振(LSE),使用外设低速晶振
RTC_EnterConfigMode() ; //RTC允许配置 CNT\ALR\PRL
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
while ( (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) && (lsTimeOut < 0x00FFFFFF)) //检查指定的RCC标志位设置与否,等待低速晶振就绪 72M主频下大概等待2.8S
{
lsTimeOut++;
//Wait_For_Nms(10) ;
}
if( lsTimeOut < 0x00FFFFF0 ) //LSE就绪成功
{
RCC_RTCCLKConfig( RCC_RTCCLKSource_LSE ) ; //设置RTC时钟(RTCCLK),选择LSE作为RTC时钟
lsTimeOut = 1 ;
}
else
{
lsTimeOut = 0 ;
RCC_LSICmd(ENABLE) ; //开启内部LSI时钟
while ( (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET) && (lsTimeOut < 0x00FFFFFF)) //检查指定的RCC标志位设置与否,等待低速晶振就绪
{
lsTimeOut++;
//Wait_For_Nms(10) ;
}
if( lsTimeOut < 0x00FFFFF0 ) //内部LSI时钟准备就绪
{
RCC_RTCCLKConfig( RCC_RTCCLKSource_LSI ) ; //设置RTC时钟(RTCCLK),选择LSI作为RTC时钟
SysErr("") ; //LSE失效,LEI作为RTC时钟
lsTimeOut = 0 ;
}
else
{
SysErr("") ; //LSE+LSI均失效
return (RUNERR) ; //初始化时钟失败,晶振有问题
}
}
RCC_RTCCLKCmd(ENABLE) ; //使能RTC时钟
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
if( lsTimeOut == 0 )
{
RTC_SetPrescaler(40000); //设置RTC预分频的值 LSI
}
else
{
RTC_SetPrescaler(32767); //设置RTC预分频的值 LSE
}
RTC_WaitForLastTask(); //等待上次对RTC寄存器写操作完成
//RTC_Set((Calendar_u *)"2018-01-21 09:00:00") ;
RTC_WaitForLastTask(); //等待上次对RTC寄存器写操作完成
RTC_ExitConfigMode(); //退出配置模式
RTC_WaitForLastTask(); //等待上次对RTC寄存器写操作完成
BKP_WriteBackupRegister( BKP_DR1, 0x5050 ) ; //向指定的后备寄存器中写入用户程序数据
}
else//已经初始化过RTC期间后备份区域没有断电系统继续计时
{
//RTC_WaitForSynchro() ; //RTC读操作前等待
RTC_WaitForLastTask() ; //RTC写操作前等待
RTC_ITConfig( RTC_IT_SEC, ENABLE ) ; //使能RTC秒中断
}
RTC_WaitForLastTask(); //等待上次对RTC寄存器写操作完成
RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能RTC秒中断
RTC_WaitForLastTask(); //等待上次对RTC寄存器写操作完成
RTC_ITConfig(RTC_IT_ALR, ENABLE); //使能闹钟中断
RTC_WaitForLastTask(); //等待上次对RTC寄存器写操作完成
// EXTI_InitTypeDef EXTI_InitStructure; //如果这只了EXTI线17中断测产生RTC闹钟中断RTCAlarm_IRQHandler否则如果开启闹钟中断在RTC全局中断内判断闹钟中断标志位
// EXTI_ClearITPendingBit( EXTI_Line17 );
// EXTI_InitStructure.EXTI_Line = EXTI_Line17 ;
// EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt ; //如果是中断事件则在这条线上产生一个脉冲不产生RTC闹钟中断RTCAlarm_IRQHandler
// EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling ;
// EXTI_InitStructure.EXTI_LineCmd = ENABLE;
// EXTI_Init( &EXTI_InitStructure ); //EXTI中断线17为RTC闹钟中断线
PWR_BackupAccessCmd(DISABLE); //禁止后备寄存器访问
RTC_NVIC_Config(ePriority) ; //RCT中断分组设置
RTC_Get(&uCalendar); //更新uCalendar时间
Rtc_RegHookCallback(RTC_IT_SEC, Rtc_Sec_Callback) ; //注册RTC秒中断回调函数
return (RUNOK) ; //ok
}
/**************************************************************************************************
* 名 称: RunResult RTC_Get(Calendar_u *getCalendar)
* 功能说明: 设置RTC闹钟时间点
* 入口参数:
* @param1 *getCalendar: 指向Calendar_u类型数据的指针
* 出口参数:
* @param RunResult返回值,返回函数运行结果.
* 说 明: RTC_Get(&uCalendar) ;
*************************************************************************************************/
RunResult RTC_Get(Calendar_u *getCalendar)
{
static uint16_t daycnt = 0 ; //static修饰 保证只有改变天数时才更新年月日
u32 timecount = 0, temp = 0 ;
uint16_t temp1 = 0 ;
vu8 hour, min, sec, w_month, w_date, week;
vu16 w_year;
RTC_WaitForSynchro() ; //RTC读操作前等待
timecount = RTC_GetCounter() ;
temp = timecount/86400 ; //得到天数(秒钟数对应的)
if( daycnt != temp ) //超过一天了才会影响年月日
{
daycnt = temp;
temp1 = 1970; //从1970年开始
while( temp >= 365 )
{
if( CheckLeepYear(temp1) ) //是闰年
{
if( temp >= 366 )
temp -= 366 ; //闰年的秒钟数
else
{
temp1++ ;
break ;
}
}
else
temp -= 365 ; //平年
temp1++ ;
}
w_year = temp1 ; //得到年份
getCalendar->sCalendar.w_year[0] = w_year/1000+'0' ;
getCalendar->sCalendar.w_year[1] = (w_year%1000)/100+'0' ;
getCalendar->sCalendar.w_year[2] = ((w_year%1000)%100)/10 + '0' ;
getCalendar->sCalendar.w_year[3] = w_year%10 + '0' ;
temp1 = 0 ;
while( temp >= 28 ) //超过了一个月
{
if( CheckLeepYear(w_year)&&temp1==1 )//当年是不是闰年/2月份
{
if( temp >= 29 )
temp -= 29 ; //闰年的秒钟数
else
break ;
}
else
{
if( temp >= monTable[temp1] )
temp -= monTable[temp1] ;//平年
else
break ;
}
temp1++ ;
}
w_month = temp1+1 ; //得到月份
w_date = temp+1 ; //得到日期
getCalendar->sCalendar.w_month[0] = w_month/10+'0' ;
getCalendar->sCalendar.w_month[1] = w_month%10+'0' ;
getCalendar->sCalendar.w_date[0] = w_date/10+'0' ;
getCalendar->sCalendar.w_date[1] = w_date%10+'0' ;
}
temp = timecount%86400 ; //得到秒钟数
hour = temp/3600 ; //小时
min = (temp%3600)/60 ; //分钟
sec = (temp%3600)%60 ; //秒钟
getCalendar->sCalendar.hour[0] = hour/10+'0' ;
getCalendar->sCalendar.hour[1] = hour%10+'0' ;
getCalendar->sCalendar.min[0] = min/10+'0' ;
getCalendar->sCalendar.min[1] = min%10+'0' ;
getCalendar->sCalendar.sec[0] = sec/10+'0' ;
getCalendar->sCalendar.sec[1] = sec%10+'0' ;
week = RTC_Get_Week( w_year, w_month, w_date ) ; //获取星期
return (RUNOK) ;
}
/**************************************************************************************************
* 名 称: uint8_t RTC_Set(Calendar_u *setCalendar)
* 功能说明: 设置RTC当前时间
* 入口参数:
* @param *setCalendarCalendar_u类型共用体指针
* 出口参数:
* @param uint8_t返回值:0,成功;其他:错误代码.
* 说 明:
*************************************************************************************************/
RunResult RTC_Set(Calendar_u *setCalendar) //
{
u16 t = 0 ;
u32 seccount = 0 ; //存储setCalendar日期计算出来的总秒钟数 初始化RTC计数器
uint16_t syear = (setCalendar->bytes[0]-'0')*1000+(setCalendar->bytes[1]-'0')*100+(setCalendar->bytes[2]-'0')*10+(setCalendar->bytes[3]-'0') ;
uint8_t smon = (setCalendar->bytes[5]-'0')*10 +(setCalendar->bytes[6]-'0') ;
uint8_t sday = (setCalendar->bytes[8]-'0')*10 +(setCalendar->bytes[9]-'0') ;
uint8_t hour = (setCalendar->bytes[11]-'0')*10 +(setCalendar->bytes[12]-'0') ;
uint8_t min = (setCalendar->bytes[14]-'0')*10 +(setCalendar->bytes[15]-'0') ;
uint8_t sec = (setCalendar->bytes[17]-'0')*10 +(setCalendar->bytes[18]-'0');
if( syear<1970 || syear>2099 )
return (RUNERR);
for( t = 1970; t < syear; t++ ) //把所有年份的秒钟相加
{
if(CheckLeepYear(t))
seccount += 31622400; //闰年的秒钟数
else
seccount += 31536000; //平年的秒钟数
}
smon -= 1;
for( t=0; t<smon; t++ ) //把前面月份的秒钟数相加
{
seccount += (u32)monTable[t]*86400; //月份秒钟数相加
if(CheckLeepYear(syear)&&t==1)
seccount += 86400; //闰年2月份增加一天的秒钟数
}
seccount += (u32)(sday-1)*86400 ; //把前面日的秒钟数相加
seccount += (u32)hour*3600 ; //小时秒钟数
seccount += (u32)min*60 ; //分钟秒钟数
seccount += sec ; //最后的秒钟加上去
PWR_BackupAccessCmd( ENABLE ) ; //使能RTC和后备寄存器访问
RTC_EnterConfigMode() ; //进入RTC配置模式 CNT\ALR\PRL
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_SetCounter( seccount ) ; //设置RTC计数器的值
RTC_WaitForLastTask() ; //等待最近一次对RTC寄存器的写操作完成
RTC_ExitConfigMode() ; //退出RTC配置模式
RTC_WaitForLastTask() ; //等待最近一次对RTC寄存器的写操作完成
return (RUNOK) ;
}
/**************************************************************************************************
* 名 称: uint8_t RTC_Alarm_Set(uint16_t syear,uint8_t smon,uint8_t sday,uint8_t hour,uint8_t min,uint8_t sec)
* 功能说明: 设置RTC闹钟时间
* 入口参数:
* @param *setCalendarCalendar_u类型共用体指针
* 出口参数:
* @param uint8_t返回值:0,成功;其他:错误代码.
* 说 明: 1970~2099年为合法年份
*************************************************************************************************/
RunResult RTC_Alarm_Set(Calendar_u *setCalendar)
{
u16 t = 0 ;
u32 seccount = 0 ;
uint16_t syear = (setCalendar->bytes[0]-'0')*1000+(setCalendar->bytes[1]-'0')*100+(setCalendar->bytes[2]-'0')*10+(setCalendar->bytes[3]-'0') ;
uint8_t smon = (setCalendar->bytes[5]-'0')*10 +(setCalendar->bytes[6]-'0') ;
uint8_t sday = (setCalendar->bytes[8]-'0')*10 +(setCalendar->bytes[9]-'0') ;
uint8_t hour = (setCalendar->bytes[11]-'0')*10 +(setCalendar->bytes[12]-'0') ;
uint8_t min = (setCalendar->bytes[14]-'0')*10 +(setCalendar->bytes[15]-'0') ;
uint8_t sec = (setCalendar->bytes[17]-'0')*10 +(setCalendar->bytes[18]-'0');
if( syear<1970 || syear>2099 )
return (RUNERR) ;
for( t=1970; t<syear; t++ ) //把所有年份的秒钟相加
{
if(CheckLeepYear(t))
seccount += 31622400 ; //闰年的秒钟数
else
seccount += 31536000 ; //平年的秒钟数
}
smon -= 1;
for( t=0; t<smon; t++ ) //把前面月份的秒钟数相加
{
seccount += (u32)monTable[t]*86400 ; //月份秒钟数相加
if(CheckLeepYear(syear)&& (t==1) )
seccount += 86400 ; //闰年2月份增加一天的秒钟数
}
seccount += (u32)(sday-1)*86400 ; //把前面日期的秒钟数相加
seccount += (u32)hour*3600 ; //小时秒钟数
seccount += (u32)min*60; //分钟秒钟数
seccount += sec; //最后的秒钟加上去
PWR_BackupAccessCmd( ENABLE ) ; //使能后备寄存器访问
RTC_EnterConfigMode() ; //RTC允许配置 CNT\ALR\PRL
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_SetAlarm(seccount);
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_ExitConfigMode(); //RTC退出配置 CNT\ALR\PRL
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
PWR_BackupAccessCmd(DISABLE); //禁止后备寄存器访问
return (RUNOK);
}
/**************************************************************************************************
* 名 称: uint8_t CheckLeepYear(uint16_t year)
* 功能说明: 检查year是否为闰年
* 入口参数:
* @param uint16_t year年份数值
* 出口参数:
* @param uint8_t检查结果
@arg 1: year为闰年
* @arg 0: year为平年
* 说 明:
* 月份 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
*************************************************************************************************/
uint8_t CheckLeepYear(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 ;
}
/**************************************************************************************************
* 名 称: Ruint8_t RTC_Get_Week(uint16_t year, uint8_t month, uint8_t day)
* 功能说明: 获取现在是星期几
* 入口参数:
* @param1 year: 年分
* @param2 mon: 月份
* @param3 day: 日
* 出口参数:
* @param1 uint8_t: 星期几?
* @param RunResult返回值,返回函数运行结果.
* 说 明: 输入公历日期得到星期(只允许1901-2099年)
*************************************************************************************************/
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 ;
if ( yearH>19 ) // 如果为21世纪,年份数加100
yearL+=100;
// 所过闰年数只算1900年之后的
temp2 = yearL+yearL/4 ;
temp2 = temp2%7 ;
temp2 = temp2+day+weekTable[month-1] ;
if ( yearL%4==0&&month<3 )
temp2--;
return(temp2%7) ;
}