[STM32F1]

STM32基础篇 内部 RTC 时钟实验

[复制链接]
8713|21
手机看帖
扫描二维码
随时随地手机跟帖
aizaixiyuanqian|  楼主 | 2018-1-14 17:26 | 显示全部楼层 |阅读模式
本帖最后由 aizaixiyuanqian 于 2018-1-14 17:28 编辑

本次试验我们不需要外接DS1302来实现时钟的目的,需要用到内部RTC,试验目标
1. 了解 STM32 才内部时钟结构
2. 了解时钟的计算方式。
aizaixiyuanqian|  楼主 | 2018-1-14 17:28 | 显示全部楼层
STM32  内部 RTC  时钟简介
要讲到 STM32 的内部 RTC 时钟,我们首先要了解一些 STM32 的备份寄存器,备份寄存器是 42 个 16 位的寄存器,可用来存储 84 个字节的用户应用程序数据。他们处在备份域里,当 VDD 电源被切断,他们仍然由 VBAT 维持供电。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。而STM32 的内部 RTC 时钟就在备份寄存器中。所以我们得到一个结论,就是要操作 RTC 时钟就要操作备份寄存器。

使用特权

评论回复
aizaixiyuanqian|  楼主 | 2018-1-14 17:28 | 显示全部楼层
接下来我们来看一下 RTC 的结构框图:
1.PNG

使用特权

评论回复
aizaixiyuanqian|  楼主 | 2018-1-14 17:30 | 显示全部楼层
从框图中我们可以看出,其实 RTC 时钟里面存储时钟信号的只是一个 32 位的寄存器,如果按秒来计算的话可以存储可以记录 4294967296 秒,约合 136 年左右,作为一般应用,这已经是足够了的。但是从这里看出我们要具体知道现在的时间是哪年哪月哪日,还有时分秒,那么就要自己进行处理了,将读取出来的计数值,转换为我们熟悉的年月日时分秒。接下来我们来看一下怎么操作 RTC时钟。

使用特权

评论回复
aizaixiyuanqian|  楼主 | 2018-1-14 17:31 | 显示全部楼层
1. RTC 的初始化
1) 打开相应的时钟
从上面我们知道,如果我们操作 RTC,那么我们就要操作备份寄存器,所以呢我们要打开一个是备份区域时钟。而一般操作 RTC 的话还会用到一个时钟,就是电源时钟,在电源控制里面有操作 RTC 的一些设置,所以我们还有将电源控制的时钟打开。所以代码为:/* 使能 PWR 电源时钟和 BKP 备份区域外设时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR  |RCC_APB1Periph_BKP, ENABLE);

使用特权

评论回复
aizaixiyuanqian|  楼主 | 2018-1-14 17:31 | 显示全部楼层
2) 使能备份寄存器操作
可以使用 PWR_BackupAccessCmd()函数(从开头的 PWR 我们就知道这个设置是在电源控制部分设置的,所以我们要打开电源控制的时钟)。它只有一个参数,也就是设置的状态,我们要使能所以设为:ENABLE。

使用特权

评论回复
aizaixiyuanqian|  楼主 | 2018-1-14 17:34 | 显示全部楼层
3) 复位备份寄存器
当然这个操作不要每次都执行,因为备份区域的复位将导致之前存在的数据丢失,所以要不要复位,要看情况而定。我们可以使用BKP_DeInit()函数。

使用特权

评论回复
aizaixiyuanqian|  楼主 | 2018-1-14 17:35 | 显示全部楼层
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 时钟

使用特权

评论回复
aizaixiyuanqian|  楼主 | 2018-1-14 17:35 | 显示全部楼层
5) 使能 RTC 时钟
打开 RTC 时钟,代码为:
RCC_RTCCLKCmd(ENABLE); //使能 RTC 时钟在对 RTC 操作的时候,注意连续操作的时候还要检测它是否执行完成,才能够接着对起进行操作,所以操作完之后再检测是否操作完成。
代码为:RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成

使用特权

评论回复
aizaixiyuanqian|  楼主 | 2018-1-14 17:38 | 显示全部楼层
6) 等待 RTC 时钟寄存器同步代码为:RTC_WaitForSynchro(); //等待 RTC 寄存器同步

使用特权

评论回复
aizaixiyuanqian|  楼主 | 2018-1-14 21:25 | 显示全部楼层
7) 开启秒中断
我们要读取时钟秒更新一次,所以我们开启秒中断。代码为:RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能 RTC 秒中断然后等待操作完成。RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成。

使用特权

评论回复
aizaixiyuanqian|  楼主 | 2018-1-14 21:27 | 显示全部楼层
8) 然后设置 RTC 时钟的预分频首先要进入配置模式。代码为:RTC_EnterConfigMode(); //允许配置 然后开始设置分频,我们要进行 32767 分频。所以代码为:RTC_SetPrescaler(32767); //设置 RTC 预分频的值然后等待操作完成:
RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成。

使用特权

评论回复
aizaixiyuanqian|  楼主 | 2018-1-14 21:28 | 显示全部楼层
9) 设置初始化时间
也就是要初始化的时钟存入到 32 位寄存器,这里原理就是,首先你要找一个最低时间点,比如说,当存储时钟的 32 位的寄存器都是零的时候,那么就表示 2000 年 1 月 1 日,0 时 0 分 0 秒(我们可以叫基础时间)。那这个时候,每当这个 32 位寄存器加 1,那么比 2000 年 1 月 1 日,0 时 0 分 0秒,多了一秒。就是 2000 年 1 月 1 日,0 时 0 分 1 秒。可能有人不理解,认为那直接讲这个寄存器设为 0 不就好了?不过后面我们读取时间的,将计数器的值,转化为年月日的时候,要一个参考时间。从寄存器中的值,可以有看出当前时间在基础时间上面多了多少秒,然后根据这个数值,计算出当前的年月日时秒。

使用特权

评论回复
aizaixiyuanqian|  楼主 | 2018-1-14 21:29 | 显示全部楼层
设置时钟配置函数
void RTC_SetClock(RTC_TimeTypeDef *time)
{
RTC_EnterConfigMode(); //允许配置
RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成
RTC_SetTime(time); //设置时间
RTC_ExitConfigMode(); //退出配置模式
RTC_GetTime(); //更新时间
}

使用特权

评论回复
aizaixiyuanqian|  楼主 | 2018-1-14 21:31 | 显示全部楼层
这个函数中调用的一个设置时钟的函数 RTC_SetTime()这个函数是用来将我们要设置的年月日转换为计数器计数,然后写入计数器中的函数,代码如下:
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

}

使用特权

评论回复
aizaixiyuanqian|  楼主 | 2018-1-14 21:32 | 显示全部楼层
10) 退出配置模式
上面我们一开始设置的时候,我们进入了配置模式,设置完了之后我们要退出模式。代码为:RTC_ExitConfigMode(); //退出配置模式

使用特权

评论回复
aizaixiyuanqian|  楼主 | 2018-1-14 22:01 | 显示全部楼层
11) 初始化 RTC 的中断 NVIC
上面我们要使用 RTC 是时钟的秒中断,所以我们要初始化它的 NVIC,配置 NVIC 的方式我们在前面讲过了,这里就不再详细讲了。

使用特权

评论回复
aizaixiyuanqian|  楼主 | 2018-1-14 22:02 | 显示全部楼层
初始化代码
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;
}

使用特权

评论回复
aizaixiyuanqian|  楼主 | 2018-1-14 22:29 | 显示全部楼层
获取时间
我们知道,STM32 的时钟其实也就是一个 24 位长度的计数器而已,而获取时间的方式就是使用 STM32 时钟的秒中断,进入中断,然后读取计算器的计数值,然后计数值转换为时间,我们例程的获取时间函数如下:
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; //求出秒

}

使用特权

评论回复
aizaixiyuanqian|  楼主 | 2018-1-14 22:30 | 显示全部楼层
例程主函数
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); //显示时钟
}
}

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

62

主题

1353

帖子

6

粉丝