STM32嵌入式-内部RTC时钟实验

STM32嵌入式-内部RTC时钟实验学习目标:1.了解 STM32 才内部时钟结构2.了解时钟的计算方式。23.

大家好,欢迎来到IT知识分享网。

学习目标:

1.了解 STM32 才内部时钟结构

2.了解时钟的计算方式。

23.1 STM32 内部 RTC 时钟简介

要讲到 STM32 的内部 RTC 时钟,我们首先要了解一些 STM32 的备份寄存器,备份寄存器是 42 个 16 位的寄存器,可用来存储 84 个字节的用户应用程序数据。他们处在备份域里,当 VDD 电源被切断,他们仍然由 VBAT 维持供电。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。而STM32 的内部 RTC 时钟就在备份寄存器中。所以我们得到一个结论,就是要操作 RTC 时钟就要操作备份寄存器。接下来我们来看一下 RTC 的结构框图:

STM32嵌入式-内部RTC时钟实验

RTC 的结构框图

从框图中我们可以看出,其实 RTC 时钟里面存储时钟信号的只是一个 32 位

的寄存器,如果按秒来计算的话可以存储可以记录 秒,约合 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 %60; //求出秒 }

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); //显示时钟 } }

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/53203.html

(0)

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信