OS:Windows 64

Development kit:MDK5.14

IDE:UV4

MCU:STM32F103C8T6

1、RTC时钟简介

  STM32 的实时时钟(RTC)是一个独立的定时器,在相应软件配置下,可提供时钟日历的功能。 详细资料请参考ALIENTEK的官方文档——《STM32F1开发指南(精英版-库函数版)》,以下为博主摘录要点:

  • RTC 模块和时钟配置系统(RCC_BDCR 寄存器)在后备区域 ,系统复位后,会自动禁止访问后备寄存器和 RTC ,所以在要设置时间之前, 先要取消备份区域(BKP)的写保护
  • RTC 内核完全独立于 RTC APB1 接口,而软件是通过 APB1 接口访问 RTC 的预分频值、计数器值和闹钟值,因此需要等待时钟同步,寄存器同步标志位(RSF)会硬件置1
  • RTC相关寄存器包括:控制寄存器(CRH、CRL)、预分频装载寄存器(PRLH、PRLL)、预分频器余数寄存器(DIVH、DIVL)、计数寄存器(CNTH、CNTL)、闹钟寄存器(ALRH、ALRL)
  • STM32备份寄存器,存RTC校验值和一些重要参数,最大字节84,可由VBAT供电
  • 计数器时钟频率:RTCCLK频率/(预分频装载寄存器值+1)

2、软硬件设计

  由于RTC是STM32芯片自带的时钟资源,所以自主开发的时候只需要在设计时加上晶振电路和纽扣电池即可。编程时在HARDWARE文件夹新建 rtc.c、rtc.h 文件。

3、时钟配置与函数编写

  为了使用RTC时钟,需要进行配置和时间获取,基本上按照例程来写就可以了。为避免零散,我将附上完整代码。函数说明如下:

rtc.c中需要编写的函数列表
RTC_Init(void) 配置时钟
RTC_NVIC_Config(void) 中断分组
RTC_IRQHandler(void) 秒中断处理
RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec) 设置时间
RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec) 闹钟设置
RTC_Get(void) 获取时钟
RTC_Get_Week(u16 year,u8 month,u8 day) 星期计算
Is_Leap_Year(u16 year) 闰年判断

  事实上,以上函数并不都要,闹钟没有用到的话就不要,秒中断也可以不作处理,看项目需求。

 #include "sys.h"
#include "delay.h"
#include "rtc.h" _calendar_obj calendar;//时钟结构体 static void RTC_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn; //RTC全局中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = ; //先占优先级1位,从优先级3位
NVIC_InitStructure.NVIC_IRQChannelSubPriority = ; //先占优先级0位,从优先级4位
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能该通道中断
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
} //实时时钟配置
//初始化RTC时钟,同时检测时钟是否工作正常
//BKP->DR1用于保存是否第一次配置的设置
//返回0:正常
//其他:错误代码 u8 RTC_Init(void)
{
//检查是不是第一次配置时钟
u8 temp=;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟
PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问
if (BKP_ReadBackupRegister(BKP_DR1) != 0x5051) //从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎
{
BKP_DeInit(); //复位备份区域
RCC_LSEConfig(RCC_LSE_ON); //设置外部低速晶振(LSE),使用外设低速晶振
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp<) //检查指定的RCC标志位设置与否,等待低速晶振就绪
{
temp++;
delay_ms();
}
if(temp>=)return ;//初始化时钟失败,晶振有问题
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(); //设置RTC预分频的值
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_Set(,,,,,); //设置时间
RTC_ExitConfigMode(); //退出配置模式
BKP_WriteBackupRegister(BKP_DR1, 0X5051); //向指定的后备寄存器中写入用户程序数据
}
else//系统继续计时
{ RTC_WaitForSynchro(); //等待最近一次对RTC寄存器的写操作完成
RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能RTC秒中断
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
}
RTC_NVIC_Config();//RCT中断分组设置
RTC_Get();//更新时间
return ; //ok }
//RTC时钟中断
//每秒触发一次
//extern u16 tcnt;
void RTC_IRQHandler(void)
{
// if (RTC_GetITStatus(RTC_IT_SEC) != RESET)//秒钟中断
// {
// RTC_Get();//更新时间
// }
// if(RTC_GetITStatus(RTC_IT_ALR)!= RESET)//闹钟中断
// {
// RTC_ClearITPendingBit(RTC_IT_ALR); //清闹钟中断
// RTC_Get(); //更新时间
// //printf("Alarm Time:%d-%d-%d %d:%d:%d\n",calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec);//输出闹铃时间
//
// }
RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW); //清闹钟中断
RTC_WaitForLastTask();
} //判断是否是闰年函数
//月份 1 2 3 4 5 6 7 8 9 10 11 12
//闰年 31 29 31 30 31 30 31 31 30 31 30 31
//非闰年 31 28 31 30 31 30 31 31 30 31 30 31
//输入:年份
//输出:该年份是不是闰年.1,是.0,不是
u8 Is_Leap_Year(u16 year)
{
if(year%==) //必须能被4整除
{
if(year%==)
{
if(year%==)return ;//如果以00结尾,还要能被400整除
else return ;
}else return ;
}else return ;
} //设置时钟
//把输入的时钟转换为秒钟
//以1970年1月1日为基准
//1970~2099年为合法年份
//返回值:0,成功;其他:错误代码.
//月份数据表
const u8 table_week[]={,,,,,,,,,,,}; //月修正数据表
const u8 mon_table[]={,,,,,,,,,,,};//平年的月份日期表 u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
{
u16 t;
u32 seccount=;
if(syear<||syear>)return ;
for(t=;t<syear;t++) //把所有年份的秒钟相加
{
if(Is_Leap_Year(t))seccount+=;//闰年的秒钟数
else seccount+=; //平年的秒钟数
}
smon-=;
for(t=;t<smon;t++) //把前面月份的秒钟数相加
{
seccount+=(u32)mon_table[t]*;//月份秒钟数相加
if(Is_Leap_Year(syear)&&t==)seccount+=;//闰年2月份增加一天的秒钟数
}
seccount+=(u32)(sday-)*;//把前面日期的秒钟数相加
seccount+=(u32)hour*;//小时秒钟数
seccount+=(u32)min*; //分钟秒钟数
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 ;
} //初始化闹钟
//以1970年1月1日为基准
//1970~2099年为合法年份
//syear,smon,sday,hour,min,sec:闹钟的年月日时分秒
//返回值:0,成功;其他:错误代码.
u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
{
u16 t;
u32 seccount=;
if(syear<||syear>)return ;
for(t=;t<syear;t++) //把所有年份的秒钟相加
{
if(Is_Leap_Year(t))seccount+=;//闰年的秒钟数
else seccount+=; //平年的秒钟数
}
smon-=;
for(t=;t<smon;t++) //把前面月份的秒钟数相加
{
seccount+=(u32)mon_table[t]*;//月份秒钟数相加
if(Is_Leap_Year(syear)&&t==)seccount+=;//闰年2月份增加一天的秒钟数
}
seccount+=(u32)(sday-)*;//把前面日期的秒钟数相加
seccount+=(u32)hour*;//小时秒钟数
seccount+=(u32)min*; //分钟秒钟数
seccount+=sec;//最后的秒钟加上去
//设置时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟
PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问
//上面三步是必须的! RTC_SetAlarm(seccount); RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 return ;
} //得到当前的时间
//返回值:0,成功;其他:错误代码.
u8 RTC_Get(void)
{
static u16 daycnt=;
u32 timecount=;
u32 temp=;
u16 temp1=;
timecount=RTC_GetCounter();
temp=timecount/; //得到天数(秒钟数对应的)
if(daycnt!=temp)//超过一天了
{
daycnt=temp;
temp1=; //从1970年开始
while(temp>=)
{
if(Is_Leap_Year(temp1))//是闰年
{
if(temp>=)temp-=;//闰年的秒钟数
else {temp1++;break;}
}
else temp-=; //平年
temp1++;
}
calendar.w_year=temp1;//得到年份
temp1=;
while(temp>=)//超过了一个月
{
if(Is_Leap_Year(calendar.w_year)&&temp1==)//当年是不是闰年/2月份
{
if(temp>=)temp-=;//闰年的秒钟数
else break;
}
else
{
if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年
else break;
}
temp1++;
}
calendar.w_month=temp1+; //得到月份
calendar.w_date=temp+; //得到日期
}
temp=timecount%; //得到秒钟数
calendar.hour=temp/; //小时
calendar.min=(temp%)/; //分钟
calendar.sec=(temp%)%; //秒钟
calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);//获取星期
calendar.msec=(-RTC_GetDivider())* /;
return ;
} //获得现在是星期几
//功能描述:输入公历日期得到星期(只允许1901-2099年)
//输入参数:公历年月日
//返回值:星期号
u8 RTC_Get_Week(u16 year,u8 month,u8 day)
{
u16 temp2;
u8 yearH,yearL; yearH=year/; yearL=year%;
// 如果为21世纪,年份数加100
if (yearH>)yearL+=;
// 所过闰年数只算1900年之后的
temp2=yearL+yearL/;
temp2=temp2%;
temp2=temp2+day+table_week[month-];
if (yearL%==&&month<)temp2--;
return(temp2%);
}

rtc.c

 #include "sys.h"

 //时间结构体
typedef struct
{
vu8 hour;//vu8
vu8 min;
vu8 sec;
vu16 msec; //公历日月年周
vu16 w_year;
vu8 w_month;
vu8 w_date;
vu8 week;
}_calendar_obj; extern _calendar_obj calendar; //日历结构体
extern u8 const mon_table[]; //月份日期数据表 u8 RTC_Init(void); //初始化RTC,返回0,失败;1,成功;
u8 Is_Leap_Year(u16 year);//平年,闰年判断 //u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);
u8 RTC_Get(void); //更新时间
u8 RTC_Get_Week(u16 year,u8 month,u8 day);
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);//设置时间

rtc.h

4、秒钟计时原理

  使用外部32.768KHz的晶振作为时钟的输入频率,设置预分频装载寄存器的值为32767,根据计算公式,刚好可以得到1秒的计数频率。时间基准设置为1970年1月1日0时0分0秒,后续的时间都以这个为基准进行计算。RTC计数器是32位的,理论上可以记录136年左右的时间。(注意不必在秒中断里更新时间)

5、毫秒计时原理

  如果要获取到毫秒级的时钟怎么办?在我的项目中就有这样的要求。事实上,获取毫秒时钟也非常简单。

  查阅开发指南,RTC预分频器余数寄存器(RTC_DIVH、RTC_DIVL),这两个寄存器的作用就是用来获得比秒钟更为准确的时钟。 该寄存器的值自减的,用于保存还需要多少时钟周期获得一个秒信号。在一次秒钟更新后,由硬件重新装载。这两个寄存器和 RTC 预分频装载寄存器位数是一样的。也就是说,如果预分频装载寄存器的值为32767,那么余数寄存器就会在每一次秒更新时由硬件重新装载为32767,然后向下计数,计数到0表示一秒,也即1000ms。

  因此,我们在时钟结构体中添加msec成员

 //时间结构体
typedef struct
{
vu8 hour;//vu8
vu8 min;
vu8 sec;
vu16 msec; //公历日月年周
vu16 w_year;
vu8 w_month;
vu8 w_date;
vu8 week;
}_calendar_obj;

  获取毫秒时间

 calendar.msec=(-RTC_GetDivider())*/;

6、修改时间  

  如果RTC时钟在使用的过程中不准了(我遇到的情况大概是掉电跑了2个月,重新测试的时候差了2分钟左右),可以重新校准时钟。我们在备份区域 BKP_DR1 中写入 0X5051 ,下次开机(或复位)的时候,先读取 BKP_DR1 的值,然后判断是否是 0X5051来决定是不是要配置。 如果要修改时间,请将0x5051改为其它数据,修改RTC_Set函数实参,再重新烧写一下程序即可。

if (BKP_ReadBackupRegister(BKP_DR1) != 0x5051)
{
...
RTC_EnterConfigMode();/// 允许配置
RTC_SetPrescaler(); //设置RTC预分频的值
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_Set(,,,,,); //设置时间
RTC_ExitConfigMode(); //退出配置模式
BKP_WriteBackupRegister(BKP_DR1, 0X5051); //向指定的后备寄存器中写入用户程序数据
}

STM32-RTC实时时钟-毫秒计时实现的更多相关文章

  1. stm32——RTC实时时钟

    stm32——RTC实时时钟 一.关于时间 2038年问题 在计算机应用上,2038年问题可能会导致某些软件在2038年无法正常工作.所有使用UNIX时间表示时间的程序都将将受其影响,因为它们以自19 ...

  2. stm32 rtc 实时时钟

    STM32的实时时钟是一个独立的定时器 通常会在后备区域供电端加一个纽扣电池,当主电源没有电的时,RTC不会停止工作 若VDD电源有效,RTC可以触发秒中断.溢出中断和闹钟中断 备份寄存器BKP 备份 ...

  3. 教你在树莓派使用上RTC实时时钟,不用再担心断电后时间归零的问题,开机后自动同步RTC时钟!!!

    准备工作:1.系统建议使用官方最新的镜像文件 2.RTC时钟模块板(I2C接口)建议使用DS1307时钟模块,或者RTC时钟模块RTC时钟模块: 大家知道arduino的电平是5V,树莓派是3.3V, ...

  4. RTC实时时钟驱动

    RTC(Real-Time Clock)实时时钟为操作系统提供了一个可靠的时间,并且在断电的情况下,RTC实时时钟也可以通过电池供电,一直运行下去. RTC通过STRB/LDRB这两个ARM指令向CP ...

  5. 第43章 RTC—实时时钟

    第43章     RTC—实时时钟 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/fireg ...

  6. 第43章 RTC—实时时钟—零死角玩转STM32-F429系列

    第43章     RTC—实时时钟 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/fireg ...

  7. RTC实时时钟

    作者:宋老师,华清远见嵌入式学院讲师. 1.1 RTC介绍 在 一个嵌入式系统中,通常采用RTC 来提供可靠的系统时间,包括时分秒和年月日等,而且要求在系统处于关机状态下它也能够正常工作(通常采用后备 ...

  8. RTC实时时钟-备份区域BKP--原理讲解

    RTC(Real Time Clock):实时时钟 BCD码:用4位2进制来表示10以内的十进制的形式. RTC的时钟源:LSE(32.768KHZ).HSE_RTC.LSI.经过一个精密校准(RTC ...

  9. 【iCore3 双核心板】例程十:RTC实时时钟实验——显示日期和时间

    实验指导书及代码包下载: http://pan.baidu.com/s/1jHuZcnc iCore3 购买链接: https://item.taobao.com/item.htm?id=524229 ...

随机推荐

  1. KVC(forKey,forKeyPath)

    KVC是Cocoa一个大招,非常牛逼. 利用KVC可以随意修改一个对象的属性或者成员变量(并且私有的也可以修改)  如:Person.m文件中: @implementation Person{    ...

  2. cmake条件编译

    CMake的条件编译基于if elseif endif.3.0版本具体语法如下 if(expression) # then section. COMMAND1(ARGS ...) COMMAND2(A ...

  3. Laravel trait 使用心得

    trait 是在PHP5.4中为了方便代码复用的一种实现方式,但目前我在看的的PHP项目中较少看的有程序员去主动使用这个实现方式,在laravel中有很多 trait 的使用,关于trait 在 la ...

  4. swift - 动画学习

    // //  ViewController.swift //  MapAnimation // //  Created by su on 15/12/10. //  Copyright © 2015年 ...

  5. (转)C# .net微信开发,开发认证,关注触发消息,自动应答,事件响应,自定义菜单

    原文地址:http://www.cnblogs.com/qidian10/p/3492751.html 成为开发者 string[] ArrTmp = { "token", Req ...

  6. How to Baskup and Restore a MySQL database

    If you're storing anything in MySQL databases that you do not want to lose, it is very important to ...

  7. 咏南中间件增加HTTPS.SYS支持

    咏南中间件增加HTTPS.SYS支持 老客户可免费升级. HTTPS.SYS可以开发强大而稳定的REST SERVER. 微软在Windows Vista (server 2008) 以后使用http ...

  8. Python入门基础学习 二

    Python入门基础学习 二 猜数字小游戏进阶版 修改建议: 猜错的时候程序可以给出提示,告诉用户猜测的数字偏大还是偏小: 没运行一次程序只能猜测一次,应该提供多次机会给用户猜测: 每次运行程序,答案 ...

  9. linux 常用命令,开发记住这些基本能够玩转linux

    系统信息 arch 显示机器的处理器架构(1) uname -m 显示机器的处理器架构(2) uname -r 显示正在使用的内核版本 dmidecode -q 显示硬件系统部件 - (SMBIOS ...

  10. solr特点七:Plugins(扩展点)

    http://wiki.apache.org/solr/SolrPlugins 在 Solr 1.3 中,扩展 Solr 以及配置和重新整理扩展变得十分简单.以前,您需要编写一个 SolrReques ...