有时候会看到技术交流群里有人问到CUBE建立的RTC工程怎么日期保存不住呢。还有人说F4的可以啊。其实这就要去看手册的描述了先来看看F1参考手册怎么说的RTC实时时钟
再来看看F4参考手册RTC实时时钟的描述
这就可以看出区别了。其实F1就是一个计数器,并没有所谓的日历功能。然后又有人质疑,那为什么原子的可以啊。那我就应该让你去仔细去看看代码了。[mw_shl_code=c,true]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;
}[/mw_shl_code]
其中最关键的一句就是“RTC_SetCounter(seccount); //设置RTC计数器的值”
来看看是怎么实现的
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();
}
这样就明白了吧,其实就是通过日期计算出从1970年1月1日(一般都是这个时间具体为什么百度吧)到现在所有的秒数,然后写入计数器中。
然后我们在看看CUBE(4.22.1版本)生成的代码
[mw_shl_code=c,true]/* RTC init function */
void MX_RTC_Init(void)
{
RTC_TimeTypeDef sTime;
RTC_DateTypeDef DateToUpdate;
/**Initialize RTC Only
*/
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_ALARM;
if (HAL_RTC_Init(&hrtc) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
/**Initialize RTC and set the Time and Date
*/
if(HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1) != 0x32F2){
sTime.Hours = 1;
sTime.Minutes = 0;
sTime.Seconds = 0;
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
DateToUpdate.WeekDay = RTC_WEEKDAY_SATURDAY;
DateToUpdate.Month = RTC_MONTH_SEPTEMBER;
DateToUpdate.Date = 30;
DateToUpdate.Year = 17;
if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,0x32F2);
}
}[/mw_shl_code]
其中if(HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1) != 0x32F2)来判断是否设置过RTC
但是内部对日期的操作确实没有实质性东西,然我们看看日期操作是怎么操作的
[mw_shl_code=c,true]HAL_StatusTypeDef HAL_RTC_SetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)
{
uint32_t counter_time = 0U, counter_alarm = 0U, hours = 0U;
/* Check input parameters */
if((hrtc == NULL) || (sDate == NULL))
{
return HAL_ERROR;
}
/* Check the parameters */
assert_param(IS_RTC_FORMAT(Format));
/* Process Locked */
__HAL_LOCK(hrtc);
hrtc->State = HAL_RTC_STATE_BUSY;
if(Format == RTC_FORMAT_BIN)
{
assert_param(IS_RTC_YEAR(sDate->Year));
assert_param(IS_RTC_MONTH(sDate->Month));
assert_param(IS_RTC_DATE(sDate->Date));
/* Change the current date */
hrtc->DateToUpdate.Year = sDate->Year;
hrtc->DateToUpdate.Month = sDate->Month;
hrtc->DateToUpdate.Date = sDate->Date;
}
else
{
assert_param(IS_RTC_YEAR(RTC_Bcd2ToByte(sDate->Year)));
assert_param(IS_RTC_MONTH(RTC_Bcd2ToByte(sDate->Month)));
assert_param(IS_RTC_DATE(RTC_Bcd2ToByte(sDate->Date)));
/* Change the current date */
hrtc->DateToUpdate.Year = RTC_Bcd2ToByte(sDate->Year);
hrtc->DateToUpdate.Month = RTC_Bcd2ToByte(sDate->Month);
hrtc->DateToUpdate.Date = RTC_Bcd2ToByte(sDate->Date);
}
/* WeekDay set by user can be ignored because automatically calculated */
hrtc->DateToUpdate.WeekDay = RTC_WeekDayNum(hrtc->DateToUpdate.Year, hrtc->DateToUpdate.Month, hrtc->DateToUpdate.Date);
sDate->WeekDay = hrtc->DateToUpdate.WeekDay;
/* Reset time to be aligned on the same day */
/* Read the time counter*/
counter_time = RTC_ReadTimeCounter(hrtc);
/* Fill the structure fields with the read parameters */
hours = counter_time / 3600U;
if (hours > 24U)
{
/* Set updated time in decreasing counter by number of days elapsed */
counter_time -= ((hours / 24U) * 24U * 3600U);
/* Write time counter in RTC registers */
if (RTC_WriteTimeCounter(hrtc, counter_time) != HAL_OK)
{
/* Set RTC state */
hrtc->State = HAL_RTC_STATE_ERROR;
/* Process Unlocked */
__HAL_UNLOCK(hrtc);
return HAL_ERROR;
}
/* Read current Alarm counter in RTC registers */
counter_alarm = RTC_ReadAlarmCounter(hrtc);
/* Set again alarm to match with new time if enabled */
if (counter_alarm != RTC_ALARM_RESETVALUE)
{
if(counter_alarm < counter_time)
{
/* Add 1 day to alarm counter*/
counter_alarm += (uint32_t)(24U * 3600U);
/* Write new Alarm counter in RTC registers */
if (RTC_WriteAlarmCounter(hrtc, counter_alarm) != HAL_OK)
{
/* Set RTC state */
hrtc->State = HAL_RTC_STATE_ERROR;
/* Process Unlocked */
__HAL_UNLOCK(hrtc);
return HAL_ERROR;
}
}
}
}
hrtc->State = HAL_RTC_STATE_READY ;
/* Process Unlocked */
__HAL_UNLOCK(hrtc);
return HAL_OK;
}[/mw_shl_code]
仔细看看并没有把日期的秒数计算出来写入计数器寄存器里;并且日期只定义了一个临时结构体变量,当第二次上电的时候。日期并不会被赋值。
////////////////////////////////////分割线/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
下面我们来根据原子例程进行一些修改
将初始化修改如下
[mw_shl_code=c,true]uint8_t MY_RTC_Init(void)
{
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
//检查是不是第一次配置时钟
if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1) != 0xA5A5)//从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎
{
if (HAL_RTC_Init(&hrtc) != HAL_OK)
{
Error_Handler();
}
HAL_RTCEx_SetSecond_IT(&hrtc);//使能RTC秒中断
RTC_Set(2017,9,30,15,46,40);
HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,0xA5A5);
}
else
{
if (HAL_RTC_Init(&hrtc) != HAL_OK)
{
Error_Handler();
}
HAL_RTCEx_SetSecond_IT(&hrtc);
}
RTC_Get();//更新时间
return 0;
}[/mw_shl_code]
时间设置 其中原子原来是1970年开始 我稍微改了一下从2000年开始
[mw_shl_code=c,true]//设置时钟
//把输入的时钟转换为秒钟
//以1970年1月1日为基准
//1970~2099年为合法年份
//返回值:0,成功;其他:错误代码.
//月份数据表
//u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表
//平年的月份日期表
//const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};
HAL_StatusTypeDef 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<2000||syear>2129)
return HAL_ERROR;
for(t=2000;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;//最后的秒钟加上去
hrtc.State = HAL_RTC_STATE_BUSY;
__HAL_LOCK(&hrtc);
if (RTC_SetCounter(&hrtc, seccount) != HAL_OK)
{
hrtc.State = HAL_RTC_STATE_ERROR;
return HAL_ERROR;
}
else
{
hrtc.State = HAL_RTC_STATE_READY;
}
__HAL_UNLOCK(&hrtc);
return HAL_OK;
}[/mw_shl_code]
还有获取时间
[mw_shl_code=c,true]void RTC_Get(void)
{
static uint16_t daycnt=0;
uint32_t timecount=0;
uint32_t temp=0;
uint16_t temp1=0;
timecount=RTC_ReadTimeCounter(&hrtc);
temp=timecount/86400; //得到天数(秒钟数对应的)
if(daycnt!=temp)//超过一天了
{
daycnt=temp;
temp1=2000; //从2000年开始
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);//获取星期
}[/mw_shl_code]
由于static HAL_StatusTypeDef RTC_WriteTimeCounter(RTC_HandleTypeDef* hrtc, uint32_t TimeCounter);和static uint32_t RTC_ReadTimeCounter(RTC_HandleTypeDef* hrtc)是库里内部代码,但是就自己按照库写了一个一样的函数来取代他们;
[mw_shl_code=c,true]static HAL_StatusTypeDef RTC_SetCounter(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 >> 16));
/* 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;
}
static uint32_t RTC_ReadTimeCounter(RTC_HandleTypeDef* hrtc)
{
uint16_t high1 = 0, high2 = 0, low = 0;
uint32_t timecounter = 0;
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)
{ /* In this case the counter roll over during reading of CNTL and CNTH registers,
read again CNTL register then return the counter value */
timecounter = (((uint32_t) high2 << 16 ) | READ_REG(hrtc->Instance->CNTL & RTC_CNTL_RTC_CNT));
}
else
{ /* No counter roll over during reading of CNTL and CNTH registers, counter
value is equal to first value of CNTL and CNTH */
timecounter = (((uint32_t) high1 << 16 ) | low);
}
return timecounter;
}
static HAL_StatusTypeDef RTC_EnterInitMode(RTC_HandleTypeDef* hrtc)
{
uint32_t tickstart = 0;
tickstart = HAL_GetTick();
/* Wait till RTC is in INIT state and if Time out is reached exit */
while((hrtc->Instance->CRL & RTC_CRL_RTOFF) == (uint32_t)RESET)
{
if((HAL_GetTick() - tickstart) > RTC_TIMEOUT_VALUE)
{
return HAL_TIMEOUT;
}
}
/* Disable the write protection for RTC registers */
__HAL_RTC_WRITEPROTECTION_DISABLE(hrtc);
return HAL_OK;
}
static HAL_StatusTypeDef RTC_ExitInitMode(RTC_HandleTypeDef* hrtc)
{
uint32_t tickstart = 0;
/* Disable the write protection for RTC registers */
__HAL_RTC_WRITEPROTECTION_ENABLE(hrtc);
tickstart = HAL_GetTick();
/* Wait till RTC is in INIT state and if Time out is reached exit */
while((hrtc->Instance->CRL & RTC_CRL_RTOFF) == (uint32_t)RESET)
{
if((HAL_GetTick() - tickstart) > RTC_TIMEOUT_VALUE)
{
return HAL_TIMEOUT;
}
}
return HAL_OK;
}[/mw_shl_code]
这样需要改的地方就差不多了;我上传工程让大家参考一下,有什么不足之处,多多提出,我再修改修改;
下面是测试结果
第一次输出时间;
第二次上电
日期还在,时间也在走,基本测试成功了。
第一次发技术贴,拍砖轻一点=0=!
F1_RTC.rar
(9.71 MB, 下载次数: 32953)
2017-9-30 17:51 上传
点击文件名下载附件
哥们,读时间这块是不是有问题?你可以用2000年12月31号去试一下,你的读出来应该是2001年了:闰年里面else好像不用temp1++ if(Is_Leap_Year(temp1))//是闰年 { if(temp>=366) temp-=366;//闰年的秒钟数 else { temp1++; break; }
说错了,是2000年12月30日设置会出错,
一周热门 更多>