#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 *setCalendar:Calendar_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; tbytes[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; t19 ) // 如果为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) ; }