stm32的时钟配置:STM32嵌入式-内部RTC时钟实验
stm32的时钟配置:STM32嵌入式-内部RTC时钟实验左右,作为一般应用,这已经是足够了的。但是从这里看出我们要具体知道现在的寄存器,如果按秒来计算的话可以存储可以记录 4294967296 秒,约合 136 年要讲到 STM32 的内部 RTC 时钟,我们首先要了解一些 STM32 的备份寄存器,备份寄存器是 42 个 16 位的寄存器,可用来存储 84 个字节的用户应用程序数据。他们处在备份域里,当 VDD 电源被切断,他们仍然由 VBAT 维持供电。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。而STM32 的内部 RTC 时钟就在备份寄存器中。所以我们得到一个结论,就是要操作 RTC 时钟就要操作备份寄存器。接下来我们来看一下 RTC 的结构框图:RTC 的结构框图从框图中我们可以看出,其实 RTC 时钟里面存储时钟信号的只是一个 32 位
学习目标:
1.了解 STM32 才内部时钟结构
2.了解时钟的计算方式。
23.1 STM32 内部 RTC 时钟简介
要讲到 STM32 的内部 RTC 时钟,我们首先要了解一些 STM32 的备份寄存器,备份寄存器是 42 个 16 位的寄存器,可用来存储 84 个字节的用户应用程序数据。他们处在备份域里,当 VDD 电源被切断,他们仍然由 VBAT 维持供电。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。而STM32 的内部 RTC 时钟就在备份寄存器中。所以我们得到一个结论,就是要操作 RTC 时钟就要操作备份寄存器。接下来我们来看一下 RTC 的结构框图:
RTC 的结构框图
从框图中我们可以看出,其实 RTC 时钟里面存储时钟信号的只是一个 32 位
的寄存器,如果按秒来计算的话可以存储可以记录 4294967296 秒,约合 136 年
左右,作为一般应用,这已经是足够了的。但是从这里看出我们要具体知道现在
的时间是哪年哪月哪日,还有时分秒,那么就要自己进行处理了,将读取出来的
计数值,转换为我们熟悉的年月日时分秒。接下来我们来看一下怎么操作 RTC
时钟。
23.2 RTC 时钟的操作对 RTC 时钟的操作一般就是设置初始化 RTC 时钟,之后只是读取时钟了。那如何初始化呢?
1.RTC 的初始化
1) 打开相应的时钟
从上面我们知道,如果我们操作 RTC,那么我们就要操作备份寄存器,所以呢我们要打开一个是备份区域时钟。而一般操作 RTC 的话还会用到一个时钟,就是电源时钟,在电源控制里面有操作 RTC 的一些设置,所以我们还有将电源控制的时钟打开。所以代码为:
/* 使能 PWR 电源时钟和 BKP 备份区域外设时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR|RCC_APB1Periph_BKP ENABLE);
2) 使能备份寄存器操作
可以使用 PWR_BackupAccessCmd()函数(从开头的 PWR我们就知道这个设置是在电源控制部分设置的,所以我们要打开电源控制的时钟)。它只有一个参数,也就是设置的状态,我们要使能所以设为:ENABLE。
3) 复位备份寄存器
当然这个操作不要每次都执行,因为备份区域的复位将导致之前存在的数据丢失,所以要不要复位,要看情况而定。我们可以使用BKP_DeInit()函数。
4) 设置外部低速时钟
我们要使用外部的低速时钟来控制 RTC,代码为:RCC_LSEConfig(RCC_LSE_ON);//设置外部低速晶振(LSE)
使用外设低速晶振。在开启外部低速时钟的时候,我们还有确定它是否成功起振,之后才能够接着操作,所以我们还要检测,外部低速时钟是否开启。代码为:
/* 检查指定的 RCC 标志位设置与否,等待低速晶振(LSE)就绪 */
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)();
等待它起振好了之后将它作为 RTC 的时钟,代码为:
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //设置 RTC 时钟(RTCCLK) 选择 LSE 作为 RTC 时钟
5) 使能 RTC 时钟
打开 RTC 时钟,代码为:
RCC_RTCCLKCmd(ENABLE); //使能 RTC 时钟在对 RTC 操作的时候,注意连续操作的时候还要检测它是否执行完成,才能够接着对起进行操作,所以操作完之后再检测是否操作完成。
代码为:
RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成6) 等待 RTC 时钟寄器同步代码为:
RTC_WaitForSynchro();//等待 RTC 寄存器同步
7) 开启秒中断
我们要读取时钟秒更新一次,所以我们开启秒中断。代码为:
RTC_ITConfig(RTC_IT_SEC ENABLE); //使能 RTC 秒中断然后等待操作完成。
RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成
8) 然后设置 RTC 时钟的预分频
首先要进入配置模式。代码为:
RTC_EnterConfigMode(); //允许配置然后开始设置分频,我们要进行 32767 分频。所以代码为:
RTC_SetPrescaler(32767); //设置 RTC 预分频的值然后等待操作完成:
RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成
9) 设置初始化时间
也就是要初始化的时钟存入到 32 位寄存器,这里原理就是,首先你要找一个最低时间点,比如说,当存储时钟的 32 位的寄存器都是零的时候,那么就表示 2000 年 1 月 1 日,0 时 0 分 0 秒(我们可以叫它基础时间)。那这个时候,每当这个 32 位寄存器加 1,那么比 2000 年 1 月 1 日,0 时 0 分 0秒,多了一秒。就是 2000 年 1 月 1 日,0 时 0 分 1 秒。可能有人不理解,认为那直接讲这个寄存器设为 0 不就好了?不过后面我们读取时间的,将计数器的值,转化为年月日的时候,要一个参考时间。从寄存器中的值,可以有看出当前时间在基础时间上面多了多少秒,然后根据这个数值,计算出当前的年月日时分秒。这个过程是蛮麻烦的,不过也没办法,具体算法可以看后面的程序例程。在这里我们讲一些怎么初始化这个 32 位寄存器。
/*****************************************************************
* Function Name : RTC_SetClock
* Description : 设置时钟
* Input : *time:要设置的时钟值
* Output : None
* Return : None
*****************************************************************/
void RTC_SetClock(RTC_TimeTypeDef *time)
{
RTC_EnterConfigMode(); //允许配置RTC_WaitForLastTask(); //等待最近一次对 RTC 寄
存器的写操作完成
RTC_SetTime(time); //设置时间
RTC_ExitConfigMode(); //退出配置模式
RTC_GetTime(); //更新时间
}
这个函数中调用的一个设置时钟的函数 RTC_SetTime()这个函数是用来将我们要设置的年月日转换为计数器计数,然后写入计数器中的函数,代码如下:
/*****************************************************************
* Function Name : RTC_SetTime
* Description : 设置 RTC 时钟的计数器初始值
* Input : time:设置的初始值(注:年份设置从 2000 到 2100 年之
间)
* Output : None
* Return : 0:设置成功;0xFF:设置失败
****************************************************************/
static uint8_t RTC_SetTime(RTC_TimeTypeDef *time)
{
uint8_t leapYear = 0;
uint16_t i;
uint32_t secondCount = 0;
/* 确定写入的时间不超过年限 */
if((time->year < 2000) || (time->year > 2100)) //从 2000 年到 2100 年,一共100 年
{
return 0xFF; //超过时限返回失败
}
/* 将所有的年份秒数相加 */
for(i = RTC_BASE_YEAR; i<time->year; i )
{
if(RTC_CheckLeapYear(i) == 0) //如果年份是闰年
{
secondCount = RTC_LEEP_YEAR_SECOND;
}
else
{
secondCount = RTC_COMMON_YEAR_SECOND;
}
}/* 检测写入年份是闰年还是平年 */
if(RTC_CheckLeapYear(time->year) == 0) //如果是闰年
{
leapYear = 1; //标记为闰年
}
else
{
leapYear = 0; //标记为平年
}
/* 所有月份秒数相加 */
for(i=1; i<time->month; i )
{
if(leapYear == 1)
{
secondCount = RtcLeapMonth[i - 1] * RTC_DAY_SECOND;
}
else
{
secondCount = RtcCommonMonth[i- 1] *
RTC_DAY_SECOND;
}
}
/* 所有的日期秒数相加 */
for(i=1; i<time->day; i )
{
secondCount = RTC_DAY_SECOND;
}
/* 小时的秒数 */
secondCount = RTC_HOUR_SECOND * time->hour;
/* 分钟的秒数 */
secondCount = 60 * time->minit;
/* 加上秒数 */
secondCount = time->second;
/* 使能 PWR 电源时钟和 BKP 备份区域外设时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR
|
RCC_APB1Periph_BKP ENABLE);
PWR_BackupAccessCmd(ENABLE); //使能 RTC 和后备寄存器访问
RTC_SetCounter(secondCount); //设置 RTC 计数器的值RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成
return 0; //设置成功返回 0
}
10) 退出配置模式
上面我们一开始设置的时候,我们进入了配置模式,设置完了之后我们要退出模式。代码为:
RTC_ExitConfigMode(); //退出配置模式
11) 初始化 RTC 的中断 NVIC
上面我们要使用 RTC 是时钟的秒中断,所以我们要初始化它的 NVIC,配置 NVIC 的方式我们在前面讲过了,这里就不再详细讲了。
2. 初始化代码
/*************************************************************
* Function Name : RTC_Config
* Description : 初始化时钟,并初始化内部的时钟信息
* Input : time:要初始化的时钟
* Output : None
* Return : 0:初始化成功;0xFF:初始化失败
*************************************************************/
int8_t RTC_Config(RTC_TimeTypeDef *time)
{
uint32_t timeCount;
if(BKP_ReadBackupRegister(BKP_DR1) != 0x5050)
{
/* 使能 PWR 电源时钟和 BKP 备份区域外设时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR
|
RCC_APB1Periph_BKP ENABLE);
PWR_BackupAccessCmd(ENABLE);//使能后备寄存器访问
BKP_DeInit(); //复位备份区域
RCC_LSEConfig(RCC_LSE_ON);//设置外部低速晶振(LSE) 使用外设低速晶振
/* 检查指定的 RCC 标志位设置与否,等待低速晶振(LSE)就绪 */
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)
{
timeCount ;
if(timeCount > 0x00FFFFF){
break;
}
}
/* 外部晶振错误,返回设置失败 */
if(timeCount > 0x00FFFFF)
{
return 0xFF;
}
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //设置 RTC 时钟(RTCCLK) 选择 LSE 作为 RTC 时钟
RCC_RTCCLKCmd(ENABLE); //使能 RTC 时钟
RTC_WaitForLastTask();//等待最近一次对RTC 寄存器的写操作完成
RTC_WaitForSynchro();//等待 RTC 寄存器同步
RTC_ITConfig(RTC_IT_SEC ENABLE); //使能 RTC 秒中断
RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成
RTC_EnterConfigMode(); //允许配置
RTC_SetPrescaler(32767); //设置 RTC 预分频的值
RTC_WaitForLastTask(); //等待最近一次对 RTC寄存器的写操作完成
RTC_SetTime(time); //设置时间
RTC_ExitConfigMode(); //退出配置模式
BKP_WriteBackupRegister(BKP_DR1 0X5050); //向指定的后备寄存器中写入用户程序数据
}
else
{
RTC_WaitForSynchro(); //等待最近一次对 RTC 寄存器的写操作完成
RTC_ITConfig(RTC_IT_SEC ENABLE); //使能 RTC 秒中断
RTC_WaitForLastTask(); //等待最近一次对 RTC寄存器的写操作完成
}
RTC_NVIC_Config(); //RCT 中断分组设置,开启中断
RTC_GetTime(); //更新时间return 0;
}
3. 获取时间
我们知道,STM32 的时钟其实也就是一个 24 位长度的计数器而已,而获取时间的方式就是使用 STM32 时钟的秒中断,进入中断,然后读取计算器的计数值,然后计数值转换为时间,我们例程的获取时间函数如下:
/*****************************************************************
* Function Name : RTC_GetTime
* Description : 读取 RTC 计数器的值,并将其转化为日期
* Input : None
* Output : None
* Return : None
****************************************************************/
static void RTC_GetTime(void)
{
uint8_t leapYear = 0 i = 0;
uint32_t secondCount = 0;
uint32_t day;
/* 读取时钟计数器的值 */
secondCount = RTC->CNTH;
secondCount <<= 16;
secondCount |= RTC->CNTL;
day = secondCount / RTC_DAY_SECOND; //求出天数
secondCount = secondCount % RTC_DAY_SECOND; //求出剩余秒数
RTC_Time.year = RTC_BASE_YEAR;/* 求出星期几 */
RTC_Time.week = (day 6) % 7; //因为 2000 年 1 月 1 日是星期六所以加 6
/* 求出年份 */
while(day >= 365)
{
if(RTC_CheckLeapYear(RTC_Time.year) == 0) //是闰年
{
day -= 366; //闰年有 366 天
}
else{
day -= 365; //平年有 365 天
}
RTC_Time.year ;
}
/* 求出月份 */
if(RTC_CheckLeapYear(RTC_Time.year) == 0)
{
leapYear = 1; //如果是闰年标记
}
i = 0;
RTC_Time.month = 1;
while(day >= 28)
{
if(leapYear == 1)
{
if(day < RtcCommonMonth[i]) //天数不够一个月
{
break;
}
day -= RtcLeapMonth[i]; //减去闰年该月的天数
}
else
{
if(day < RtcCommonMonth[i]) //天数不够一个月
{
break;
}
day -= RtcCommonMonth[i]; //减去平年该月的天数
}
RTC_Time.month ; //月份加 1
i ; //月份数组加 1
}
/* 求出天数 */
RTC_Time.day = day 1; //月份剩下的天数就是日期(日期从 1 号开始)
RTC_Time.hour = secondCount / RTC_HOUR_SECOND; //求出小时
RTC_Time.minit = secondCount % RTC_HOUR_SECOND / 60; //求出分钟
RTC_Time.second = secondCount % RTC_HOUR_SECOND `; //求出秒
}
23.3 例程主函数
int main(void)
{
uint8_t ledState setState m;
uint8_t keyValue;
uint16_t i;
/* 初始化时钟值 */
time.year = 2013;
time.month = 12;
time.day = 24;
time.week = 2;
time.hour = 12;
time.minit = 0;
time.second = 0;
/* 初始化 */
TFT_Init();
FLASH_Init();
RTC_Config(&time);
LED_Config();
KEY_Config();
/* 彩屏显示初始化 */
TFT_ClearScreen(BLACK);
GUI_Show16Chinese(80 0 "普中科技" RED BLACK);
GUI_Show12ASCII(90 21 "PRECHIN" RED BLACK);
GUI_Show12ASCII(60 42 "www.prechin.com" RED BLACK);
GUI_Show12Chinese(60 63 "内部时钟实验" RED BLACK);
GUI_Show12Chinese(32 84 "年 月 日" RED BLACK);
GUI_Show12ASCII(128 84 ": :" RED BLACK);
GUI_Show12Chinese(0 105 "右键:进入或者退出设置模式" BLUE
BLACK);
GUI_Show12Chinese(0 126 "左键:设置位置左移" BLUE BLACK);
GUI_Show12Chinese(0 147 "上键:设置位置数字加一" BLUE BLACK);
GUI_Show12Chinese(0 168 "下键:设置位置数字减一" BLUE BLACK);setState = 0; //初始设置为普通模式,非设置模式
m = 0; //显示无高亮位置
while(1)
{
/*LED 灯闪烁 */
i ;
if(i > 0xFF)
{
i = 0;
if(ledState == 0xFE)
{
ledState = 0xFF;
}
else
{
ledState = 0xFE;
}
LED_SetState(ledState);
}
/* 键盘扫描 */
keyValue = KEY_Scan(); /* 如果按键是右键,进入或者退出设置模式 */
if(keyValue == KEY_RIGHT)
{
if(setState == 0)
{
setState = 1;
}
else
{
setState = 0;
}
if(setState) //退出设置模式则更新时间
{
m = 1;
}
else
{
RTC_SetClock(&time);
m = 0;
}}
/* 进入设置模式 */
if(setState == 1)
{
switch(keyValue)
{
case(KEY_UP): //上键高亮数字加 1
TIME_Set(m 1);
break;
case(KEY_DOWN): //下键高亮数字减 1
TIME_Set(m 0);
break;
case(KEY_LEFT): //左键高亮位置左移 1 位
if(m == 6)
{
m = 1;
}
else
{
m ;
}
break;
default:
break;
}
}
/* 普通模式显示时钟 */
else
{
/* 读取时钟 */
time = RTC_Time; //读取时钟
}
GUI_DisplayTime(m); //显示时钟
}
}