STM32F4 裸机RTC驱动,寄存器操作
扫描二维码
随时随地手机看文章
STM32F4裸机RTC驱动,寄存器操作。
/*************************************************************************************************************
* 文件名 : RTC.c
* 功能 : STM32F4 RTC驱动
* 作者 : cp1300@139.com
* 创建时间 : 2017-07-01
* 最后修改时间 : 2017-07-01
* 详细: 要正确读取 RTC 日历寄存器(RTC_SSR、RTC_TR 和 RTC_DR),APB1 时钟频率 (fPCLK1) 必须等于或大于 fRTCCLK RTC 时钟频率的七倍。这可以确保同步机制行为的安全性。
如果 APB1 时钟频率低于 RTC 时钟频率的七倍,则软件必须分两次读取日历时间寄存器和 日期寄存器。这样,当两次读取的 RTC_TR 结果相同时,才能确保数据正确。
否则必须执 行第三次读访问。任何情况下,APB1 的时钟频率都不能低于 RTC 的时钟频率。
RTC->BKP0R:用于标示是否进行了初始化RTC,请不要再使用了。
注意:初始化时会使用调试串口打印信息
2017-01-15:修改全局时间结构体名称为g_timer,并且增加系统命令支持
*************************************************************************************************************/
#include "rtc.h"
#include "system.h"
volatile tm g_timer; //全局系统时钟
//秒中断回调函数
static void (*RTC_SerIntCallBack)(void) ; //秒中断回调函数
//内部函数申明
u8 RTC_GetWeek(u16 year,u8 month,u8 day); //获取2000-2099年之间的日期对应的星期
u32 RTC_DECtoBCD( u8 DEC); //将数字转换为压缩BCD格式,最大支持99
u32 RTC_BCDtoDEC(u8 BCD); //将压缩BCD转为DEC,最大支持99
/*************************************************************************************************************************
* 函数 : bool RTC_WaitSynchro(void)
* 功能 : 等待RSF同步
* 参数 : 无
* 返回 : TRUE:成功,FALSE:失败
* 依赖 : 底层宏定义
* 作者 : cp1300@139.com
* 时间 : 2017-07-01
* 最后修改时间 : 2017-07-01
* 说明 :
*************************************************************************************************************************/
bool RTC_WaitSynchro(void)
{
u32 retry=0XFFFFF;
//执行以下步骤解锁RTC寄存器写保护
RTC->WPR = 0xCA; //写入KEY后关闭RTC寄存器写保护
RTC->WPR = 0x53;
RTC->ISR &= ~(1<ISR&(1<WPR= 0xff; //随便写入一个值(不是KEY)使能RTC寄存器写保护
return TRUE;
}
/*************************************************************************************************************************
* 函数 : bool RTC_InitMode(void)
* 功能 : RTC进入初始化模式
* 参数 : 无
* 返回 : TRUE:成功,FALSE:失败
* 依赖 : 底层宏定义
* 作者 : cp1300@139.com
* 时间 : 2017-07-01
* 最后修改时间 : 2017-07-01
* 说明 :
*************************************************************************************************************************/
bool RTC_InitMode(void)
{
u32 retry=0X10000;
if(RTC->ISR & (1<ISR |= 1<ISR&(1<WPR = 0xCA; //写入KEY后关闭RTC寄存器写保护
RTC->WPR = 0x53;
if(RTC_InitMode() == FALSE)return FALSE;//进入RTC初始化模式失败
temp = (u32)RTC_DECtoBCD(hour) << 16; //小时
temp |= (u32)RTC_DECtoBCD(min) << 8; //分钟
temp |= (u32)RTC_DECtoBCD(sec) << 0; //秒钟
RTC->TR=temp; //设置时间
RTC->ISR&=~(1<WPR = 0xFF; //开启写保护
return TRUE;
}
/*************************************************************************************************************************
* 函数 : bool RTC_SetDate(u16 year,u8 month,u8 date)
* 功能 : RTC日期设置
* 参数 : year,month,date:年(2000~2099),月(1~12),日(1~31)
* 返回 : TRUE:成功,FALSE:失败
* 依赖 : 底层宏定义
* 作者 : cp1300@139.com
* 时间 : 2017-07-01
* 最后修改时间 : 2017-07-01
* 说明 : 会自动退出写保护
*************************************************************************************************************************/
bool RTC_SetDate(u16 year,u8 month,u8 date)
{
u32 temp;
u8 week;
//执行以下步骤解锁RTC寄存器写保护
RTC->WPR = 0xCA; //写入KEY后关闭RTC寄存器写保护
RTC->WPR = 0x53;
if(RTC_InitMode() == FALSE)return FALSE;//进入RTC初始化模式失败
if(year < 2000) year = 2000; //限制年
if(year > 2099) year = 2099; //限制年
if(month < 1) month = 1; //限制月
if(month > 12) month = 12; //限制月
if(date < 1) date = 1; //限制日
if(date > 31) date = 31; //限制日
week = RTC_GetWeek(year,month,date); //计算设置的日期对应的星期
year -= 2000; //年转换为0-99
temp = (u32)RTC_DECtoBCD(year)<<16; //年
temp |= week<<13; //星期
temp |= (u32)RTC_DECtoBCD(month)<<8; //月份
temp |= (u32)RTC_DECtoBCD(date)<DR=temp; //写入
RTC->ISR&=~(1<WPR = 0xFF; //开启写保护
return TRUE;
}
/*************************************************************************************************************************
* 函数 : bool RTC_GetTime(u8 *hour,u8 *min,u8 *sec)
* 功能 : 获取RTC时间
* 参数 : *hour,*min,*sec:小时,分钟,秒钟
* 返回 : TRUE:成功,FALSE:失败
* 依赖 : 底层宏定义
* 作者 : cp1300@139.com
* 时间 : 2017-07-01
* 最后修改时间 : 2017-07-01
* 说明 : 24小时制
*************************************************************************************************************************/
bool RTC_GetTime(u8 *hour,u8 *min,u8 *sec)
{
u32 temp;
u32 retry = 5250*3;
u8 ampm;
while((RTC_WaitSynchro() == FALSE) && (retry!=0)) //等待同步
{
nop;nop;nop;nop;nop;nop;
retry --;
}
if(retry==0) return FALSE;
temp=RTC->TR;
*hour=RTC_BCDtoDEC((temp>>16)&0X3F);
*min=RTC_BCDtoDEC((temp>>8)&0X7F);
*sec=RTC_BCDtoDEC(temp&0X7F);
ampm=temp>>22;
if(ampm!=0) //使用的是12小时制,并且是下午,则小时加上12小时
{
*hour += 12;
}
return TRUE;
}
/*************************************************************************************************************************
* 函数 : bool RTC_GetDate(u16 *year,u8 *month,u8 *date,u8 *week)
* 功能 : 获取RTC日期
* 参数 : *year,*mon,*date:年,月,日;*week:星期
* 返回 : TRUE:成功,FALSE:失败
* 依赖 : 底层宏定义
* 作者 : cp1300@139.com
* 时间 : 2017-07-01
* 最后修改时间 : 2017-07-01
* 说明 :
*************************************************************************************************************************/
bool RTC_GetDate(u16 *year,u8 *month,u8 *date,u8 *week)
{
u32 retry = 5250*3;
u32 temp;
while((RTC_WaitSynchro() == FALSE) && (retry!=0)) //等待同步
{
nop;nop;nop;nop;nop;nop;
retry --;
}
if(retry==0) return FALSE;
temp=RTC->DR;
*year=RTC_BCDtoDEC((temp>>16)&0XFF)+2000;
*month=RTC_BCDtoDEC((temp>>8)&0X1F);
*date=RTC_BCDtoDEC(temp&0X3F);
*week=(temp>>13)&0X07;
return TRUE;
}
/*************************************************************************************************************************
* 函数 : bool RTC_Get(void)
* 功能 : 更新RTC时间
* 参数 : 无
* 返回 : TRUE:成功,FALSE:失败
* 依赖 : 底层宏定义
* 作者 : cp1300@139.com
* 时间 : 2017-07-01
* 最后修改时间 : 2017-07-01
* 说明 : APB时钟必须大于RTC时钟7倍,通常会远大于,一般APB少说也得1MHZ以上
时间写入到了全局缓冲区 timer 中
*************************************************************************************************************************/
bool RTC_Get(void)
{
u32 temp;
u32 retry = 5250*3;
u8 ampm;
while((RTC_WaitSynchro() == FALSE) && (retry!=0)) //等待同步
{
nop;nop;nop;nop;nop;nop;
retry --;
}
if(retry==0) return FALSE;
//获取日期
temp=RTC->DR;
g_timer.year=RTC_BCDtoDEC((temp>>16)&0XFF)+2000;
g_timer.month=RTC_BCDtoDEC((temp>>8)&0X1F);
g_timer.date=RTC_BCDtoDEC(temp&0X3F);
g_timer.week=(temp>>13)&0X07;
//获取时间
temp=RTC->TR;
g_timer.hour=RTC_BCDtoDEC((temp>>16)&0X3F);
g_timer.min=RTC_BCDtoDEC((temp>>8)&0X7F);
g_timer.sec=RTC_BCDtoDEC(temp&0X7F);
ampm=temp>>22;
if(ampm!=0) //使用的是12小时制,并且是下午,则小时加上12小时
{
g_timer.hour += 12;
}
return TRUE;
}
/*************************************************************************************************************************
* 函数 : bool RTC_SetWakeUp(bool isEnableInt, u16 WakeSecs)
* 功能 : RTC唤醒中断设置
* 参数 : isEnableInt:是否开启唤醒中断;WakeSecs:唤醒周期,单位秒
* 返回 : TRUE:开启成功;FALSE:开启失败
* 依赖 : 底层宏定义
* 作者 : cp1300@139.com
* 时间 : 2017-07-02
* 最后修改时间 : 2017-07-02
* 说明 :
*************************************************************************************************************************/
bool RTC_SetWakeUp(bool isEnableInt, u16 WakeSecs)
{
u32 temp = RTC->CR;
u32 retry = 500;
//关闭RTC寄存器写保护
RTC->WPR=0xCA;
RTC->WPR=0x53;
if(WakeSecs==0) WakeSecs = 1; //最少1秒
temp |= 1<<10; //唤醒定时器使能
if(isEnableInt) //需要开启唤醒中断
{
temp |= 1<<14; //使能唤醒定时器中断
SYS_EXTI_SetEdge(EXTI_RTC_WAKE_UP, EXTI_POS_EDGE); //设置RTC唤醒中断上升沿触发
SYS_EXTI_IntEnable(EXTI_RTC_WAKE_UP, TRUE); //RTC唤醒中断EXTI开关
SYS_EXTI_ClearInt(EXTI_RTC_WAKE_UP); //清除中断标记
SYS_NVIC_IntEnable(IRQ_RTC_WKUP, TRUE); //RTC唤醒中断 NVIC全局中断使能
}
else
{
temp &= ~(1<ISR &= ~(1<WUTR != (WakeSecs-1))
{
//在 RTC_CR 寄存器中的 WUTE 位置 0 后,当唤醒定时器值可更改时,由硬件将该位置 1。
//仅当 RTC_ISR 中的 WUTWF 置 1 时才可对该寄存器执行写操作。
RTC->CR &= ~(1<ISR & (1 << 2)) == 0) && retry)
{
Delay_US(1);
retry --;
}
if(retry==0)
{
uart_printf("设置RTC唤醒定时器失败!");
return FALSE;
}
RTC->WUTR = WakeSecs-1; //重新设置唤醒自动重载值
}
RTC->CR = temp; //设置配置寄存器
RTC->WPR=0xFF; //开启写保护
return TRUE;
}
/*************************************************************************************************************************
* 函数 : bool RTC_Init(bool SerIntEnable)
* 功能 : RTC初始化
* 参数 : SerIntEnable:TRUE:开启秒中断;FALSE:关闭秒中断
* 返回 : TRUE:成功,FALSE:失败
* 依赖 : 底层宏定义
* 作者 : cp1300@139.com
* 时间 : 2017-07-01
* 最后修改时间 : 2017-07-01
* 说明 : 使用备份区0存放标志来区别是否进行过初始化 RTC->BKP0R=0x5A5AA5A5
*************************************************************************************************************************/
bool RTC_Init(bool SerIntEnable)
{
u32 retry=0XFFFFFFF;
RCC->APB1ENR|=1<CR|=1<BKP0R != 0x5A5AA5A5) //没有进行过配置
{
info_printf("RTC没有初始化,开始初始化...rn");
RCC->BDCR|=1<BDCR&0X02)==0))//等待LSE准备好
{
retry--;
nop;nop;nop;
}
if(retry==0)
{
info_printf("RTC时钟初始化失败了!rn");
return FALSE; //LSE 开启失败.
}
RCC->BDCR|=1<BDCR|=1<WPR=0xCA;
RTC->WPR=0x53;
if(RTC_InitMode() == FALSE)//进入RTC初始化模式
{
info_printf("RTC进入初始化模式失败了!rn");
return FALSE;
}
//将32768 进行128x256分频 得到1秒的 ck_spre频率,用于产生秒唤醒中断
RTC->PRER=255; //RTC同步分频系数(0~7FFF),必须先设置同步分频,再设置异步分频,Frtc=Fclks/((Sprec+1)*(Asprec+1))
RTC->PRER|=127<CR&=~(1<ISR&=~(1<WPR=0xFF; //使能RTC寄存器写保护
RTC_SetTime(6,6,6); //设置初始时间
RTC_SetDate(2017,6,6); //设置初始日期
RTC->BKP0R = 0x5A5AA5A5; //写入备份区标记,标示已经初始化过RTC
info_printf("初始化RTC成功!rn");
//RTC_Set_AlarmA(7,0,0,10); //设置闹钟时间
}
//RTC_Set_WakeUp(4,0);
if(RTC_Get()==FALSE) //更新一次时间
{
info_printf("初始获取时间失败!rn");
}
RTC_SetWakeUp(SerIntEnable, 1);//配置WAKE UP中断,1秒钟中断一次
return TRUE;
}
//设置秒中断回调函数
void RTC_SetWkupCallBack(void (*pCallBack)(void))
{
RTC_SerIntCallBack = pCallBack;
}
//RTC闹钟中断服务函数
void RTC_Alarm_IRQHandler(void)
{
if(RTC->ISR&(1<ISR&=~(1<PR|=1<ISR&(1<ISR&=~(1<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--;
temp2%=7;
if(temp2==0)temp2=7;
return temp2;
}
//将数字转换为压缩BCD格式,最大支持99
u32 RTC_DECtoBCD( u8 DEC)
{
return ((u8)(DEC/10)<>4)*10+(BCD&0x0f);
}
//使能系统命令行
#if SYS_CMD_EN_
#include "sys_cmd.h"
#include "string.h"
const SYS_CMD_TYPE CMD_GET_TIME = {"TIME?", CMD_GetTime, "tt获取系统时间", TRUE};
const SYS_CMD_TYPE CMD_GET_DATE = {"DATE?", CMD_GetDate, "tt获取系统日期", TRUE};
const SYS_CMD_TYPE CMD_SET_TIME = {"TIME=", CMD_SetTime, "tt设置系统时间 如(12:32:54):TIME=12 32 54", TRUE};
const SYS_CMD_TYPE CMD_SET_DATE = {"DATE=", CMD_SetDate, "tt设置系统日期 如(2014 6 8):TIME=2014 6 8", TRUE};
//获取时间
void CMD_GetTime(SYS_CMD_HANDLE *pHandle,char *pStr)
{
RTC_Get(); //更新时间
pHandle->DataPrintf("[获取时间成功]:%02d:%02d:%02drn",g_timer.hour, g_timer.min, g_timer.sec);
}
//获取日期
void CMD_GetDate(SYS_CMD_HANDLE *pHandle,char *pStr)
{
RTC_Get(); //更新时间
pHandle->DataPrintf("[获取日期成功]:%04d-%02d-%02drn",g_timer.year, g_timer.month, g_timer.date);
}
//设置时间
void CMD_SetTime(SYS_CMD_HANDLE *pHandle,char *pStr)
{
u8 hour,min,sec;
u8 len;
char *p;
u8 num;
len = strlen(pStr); //获取长度
if(isStrNumAndSpc(pStr, len, 2) == FALSE)
{
pHandle->DataPrintf("[时间设置错误]:格式不对或非法参数!rn");
return;
}
//小时
p = strstr(pStr," "); //搜索空格
if(p == NULL)
{
pHandle->DataPrintf("[时间设置错误]:格式不对或非法参数!rn");
return;
}
num = p - pStr;
if((num > 2) || (num == 0))
{
pHandle->DataPrintf("[时间设置错误]:格式不对或非法参数!rn");
return;
}
hour = SYS_CMD_StringToDec(pStr, num);
if(hour>23)
{
pHandle->DataPrintf("[时间设置错误]:格式不对或非法参数!rn");
return;
}
//分钟
pStr = p+1;
p = strstr(pStr," "); //搜索空格
if(p == NULL)
{
pHandle->DataPrintf("[时间设置错误]:格式不对或非法参数!rn");
return;
}
num = p - pStr;
if((num > 2) || (num == 0))
{
pHandle->DataPrintf("[时间设置错误]:格式不对或非法参数!rn");
return;
}
min = SYS_CMD_StringToDec(pStr, num);
if(min>59)
{
pHandle->DataPrintf("[时间设置错误]:格式不对或非法参数!rn");
return;
}
//秒钟
pStr = p+1;
num = strlen(pStr);
if((num > 2) || (num == 0))
{
pHandle->DataPrintf("[时间设置错误]:格式不对或非法参数!rn");
return;
}
sec = SYS_CMD_StringToDec(pStr, num);
if(sec>59)
{
pHandle->DataPrintf("[时间设置错误]:格式不对或非法参数!rn");
return;
}
if(RTC_SetTime(hour, min, sec) == FALSE)
{
RTC_Get(); //更新时间
pHandle->DataPrintf("[时间设置失败]:%02d:%02d:%02drn",g_timer.hour, g_timer.min, g_timer.sec);
}
else
{
RTC_Get(); //更新时间
pHandle->DataPrintf("[时间设置成功]:%02d:%02d:%02drn",g_timer.hour, g_timer.min, g_timer.sec);
}
}
//设置日期
void CMD_SetDate(SYS_CMD_HANDLE *pHandle,char *pStr)
{
u16 year;
u8 month, date;
u8 len;
char *p;
u8 num;
len = strlen(pStr); //获取长度
if(isStrNumAndSpc(pStr, len, 2) == FALSE)
{
pHandle->DataPrintf("[日期设置错误]:格式不对或非法参数!rn");
return;
}
//年
p = strstr(pStr," "); //搜索空格
if(p == NULL)
{
pHandle->DataPrintf("[日期设置错误]:格式不对或非法参数!rn");
return;
}
num = p - pStr;
if((num > 4) || (num == 0))
{
pHandle->DataPrintf("[日期设置错误]:格式不对或非法参数!rn");
return;
}
year = SYS_CMD_StringToDec(pStr, num);
if(year>9999)
{
pHandle->DataPrintf("[日期设置错误]:格式不对或非法参数!rn");
return;
}
//月
pStr = p+1;
p = strstr(pStr," "); //搜索空格
if(p == NULL)
{
pHandle->DataPrintf("[日期设置错误]:格式不对或非法参数!rn");
return;
}
num = p - pStr;
if((num > 2) || (num == 0))
{
pHandle->DataPrintf("[日期设置错误]:格式不对或非法参数!rn");
return;
}
month = SYS_CMD_StringToDec(pStr, num);
if(month>12)
{
pHandle->DataPrintf("[日期设置错误]:格式不对或非法参数!rn");
return;
}
//日
pStr = p+1;
num = strlen(pStr);
if((num > 2) || (num == 0))
{
pHandle->DataPrintf("[日期设置错误]:格式不对或非法参数!rn");
return;
}
date = SYS_CMD_StringToDec(pStr, num);
if(date>31)
{
pHandle->DataPrintf("[日期设置错误]:格式不对或非法参数!rn");
return;
}
if(RTC_SetDate(year, month, date) == FALSE)
{
RTC_Get(); //更新时间
pHandle->DataPrintf("[日期设置失败]:%04d-%02d-%02drn",g_timer.year, g_timer.month, g_timer.date);
}
else
{
RTC_Get(); //更新时间
pHandle->DataPrintf("[日期设置成功]:%04d-%02d-%02drn",g_timer.year, g_timer.month, g_timer.date);
}
}
#endif //SYS_CMD_EN_/*************************************************************************************************************
* 文件名 : RTC.h
* 功能 : STM32F4 RTC驱动
* 作者 : cp1300@139.com
* 创建时间 : 2017-07-01
* 最后修改时间 : 2017-07-01
* 详细: 要正确读取 RTC 日历寄存器(RTC_SSR、RTC_TR 和 RTC_DR),APB1 时钟频率 (fPCLK1) 必须等于或大于 fRTCCLK RTC 时钟频率的七倍。这可以确保同步机制行为的安全性。
如果 APB1 时钟频率低于 RTC 时钟频率的七倍,则软件必须分两次读取日历时间寄存器和 日期寄存器。这样,当两次读取的 RTC_TR 结果相同时,才能确保数据正确。
否则必须执 行第三次读访问。任何情况下,APB1 的时钟频率都不能低于 RTC 的时钟频率。
RTC->BKP0R:用于标示是否进行了初始化RTC,请不要再使用了。
*************************************************************************************************************/
#ifndef __RTC_H_
#define __RTC_H_
#include "system.h"
//时间结构体
typedef struct
{
u8 hour; //小时
u8 min; //分钟
u8 sec; //秒
u8 month; //月
u8 date; //日
u8 week; //星期
u16 year; //年
}tm;
extern volatile tm g_timer;
//函数接口
bool RTC_SetTime(u8 hour,u8 min,u8 sec); //RTC时间设置
bool RTC_SetDate(u16 year,u8 month,u8 date);//RTC日期设置
bool RTC_GetTime(u8 *hour,u8 *min,u8 *sec); //获取RTC时间
bool RTC_GetDate(u16 *year,u8 *month,u8 *date,u8 *week);//获取RTC日期
bool RTC_Get(void); //更新RTC时间
bool RTC_Init(bool SerIntEnable); //RTC初始化
bool RTC_SetWakeUp(bool isEnableInt, u16 WakeSecs); //RTC唤醒中断设置
void RTC_SetWkupCallBack(void (*pCallBack)(void));//设置秒中断回调函数
//使能系统命令行
#if SYS_CMD_EN_
#include "sys_cmd.h"
#include "string.h"
extern const SYS_CMD_TYPE CMD_GET_TIME;
extern const SYS_CMD_TYPE CMD_GET_DATE;
extern const SYS_CMD_TYPE CMD_SET_TIME;
extern const SYS_CMD_TYPE CMD_SET_DATE;
//获取时间
void CMD_GetTime(SYS_CMD_HANDLE *pHandle,char *pStr);
//获取日期
void CMD_GetDate(SYS_CMD_HANDLE *pHandle,char *pStr);
//设置时间
void CMD_SetTime(SYS_CMD_HANDLE *pHandle,char *pStr);
//设置日期
void CMD_SetDate(SYS_CMD_HANDLE *pHandle,char *pStr);
#endif //SYS_CMD_EN_
#endif //__RTC_H_
使用时需要一个秒中断回调函数,比如我的秒中断是用来唤醒一个线程,处理秒任务
RTC_Init(TRUE);
//任务2:负责后台(RTC 1秒唤醒一次)
void TaskBack(void *pdata)
{
RTC_SetWkupCallBack(RTC_WkupCallBack); //设置RTC回调函数
while(1)
{
RTC_Get(); //更新系统时间
uart_printf("%04d-%02d-%02d %02d:%02d:%02drn",g_timer.year,g_timer.month,g_timer.date,g_timer.hour,g_timer.min,g_timer.sec);
OSTimeDlyHMSM(0,0,0,100);
IWDG_Feed();
OSTaskSuspend(BACK_TASK_Prio); //挂起后台进程
}
}//RTC秒中断回调函数
void RTC_WkupCallBack(void)
{
OSTaskResume(BACK_TASK_Prio); //唤醒后台进程
}




