stm32——RTC实时时钟

stm32——RTC实时时钟

一、关于时间

  2038年问题

  在计算机应用上,2038年问题可能会导致某些软件在2038年无法正常工作。所有使用UNIX时间表示时间的程序都将将受其影响,因为它们以自1970年1月1日经过的秒数(忽略闰秒)来表示时间。这种时间表示法在类Unix(Unix-like)操作系统上是一个标准,并会影响以其C编程语言开发给其他大部份操作系统使用的软件。

  在大部份的32位操作系统上,此“time_t”数据模式使用一个有正负号的32位元整数(signedint32)存储计算的秒数。也就是说最大可以计数的秒数为 2^31次方 可以算得:

2^31/3600/24/365 ≈ 68年

  所以依照此“time_t”标准,在此格式能被表示的最后时间是2038年1月19日03:14:07,星期二(UTC)。超过此一瞬间,时间将会被掩盖(wrap around)且在内部被表示为一个负数,并造成程序无法工作,因为它们无法将此时间识别为2038年,而可能会依个别实作而跳回1970年或1901年。

  对于PC机来说,时间开始于1980年1月1日,并以无正负符号的32位整数的形式按秒递增,这与UNIX时间非常类似。可以算得:

2^32/3600/24/365 ≈ 136年

  到2116年,这个整数将溢出。

  

  Windows NT使用64位整数来计时。但是,它使用100纳秒作为增量单位,且时间开始于1601年1月1日,所以NT将遇到2184年问题。

  苹果公司声明,Mac在29,940年之前不会出现时间问题!

二、RTC使用说明

  "RTC"是Real Time Clock 的简称,意为实时时钟。stm32提供了一个秒中断源和一个闹钟中断源,修改计数器的值可以重新设置系统当前的时间和日期。

  RTC模块之所以具有实时时钟功能,是因为它内部维持了一个独立的定时器,通过配置,可以让它准确地每秒钟中断一次。但实际上,RTC就只是一个定时器而已,掉电之后所有信息都会丢失,因此我们需要找一个地方来存储这些信息,于是就找到了备份寄存器。其在掉电后仍然可以通过纽扣电池供电,所以能时刻保存这些数据。

  配置RTC前须知:

  BKP:

  RTC模块和时钟配置系统的寄存器是在后备区域的(即BKP),通过BKP后备区域来存储RTC配置的数据可以让其在系统复位或待机模式下唤醒后,RTC里面配置的数据维持不变。

  PWR:

  PWR为电源的寄存器,我们需要用到的是电源控制寄存器(PWR_CR),通过使能PWR_CR的DBP位来取消后备区域BKP的写保护。

  RTC:

  由一组可编程计数器组成,分成两个模块。第一个模块是RTC的预分频模块,它可编程产生最长为1秒的RTC时间基准TR_CLK。RTC的预分频模块包含了一个20位的可编程分频器(RTC)TR_CLK 周期中RTC产生一个中断(秒中断)。第二个模块是一个32位的可编程计数器,可被初始化为当前的系统时间。系统时间按TR_CLK周期累加并与存储在RTC_ALR寄存器中的可编程时间相比较,如果RTC_CR控制寄存器中设置了相应允许位,比较匹配时,将产生一个闹钟中断。

  

  下面讲解下配置整体过程:

   第一步: 通过设置寄存器 RCC_APB1ENR 的 PWREN 和 BKPEN 位来打开电源和后备接口的时钟

   调用库函数:

    RCC_APB1PeriphClockCmd (RCC_APB1Periph_PWR | RCC_APB1Periph_BKP,ENABLE );

   第二步:电源控制寄存器(PWR_CR) 的 DBP 位来使能对后备寄存器和 RTC 的访问

   调用库函数:

    PWR_BackupAccessCmd(ENABLE );

   第三步:初始化复位 BKP 寄存器

   调用库函数:

   BKP_DeInit ();

   第四步:设置 RTCCLK,如下图:

  

   我们需要将 RTCCLK 设置为 LSE OSC  这个 32.768KHZ 的晶振。

   调用的库函数:  

   RCC_LSEConfig (RCC_LSE_ON);

   While(!RCC_GetFlagStatus (RCC_FLAG_HSERDY));//设置后需要等待启动

   第五步:将 RTC 输入时钟 选择为 LSE 时钟输入并使能 RTC,等待 RTC 和 APB 时钟同步

   调用库函数:

   RCC_RTCCLKConfig (RCC_RTCCLKSource_LSE);//选择 LSE 为 RTC 设备的时钟

   RCC_RTCCLKCmd (ENABLE );//使能

   RTC RTC_WaitForSynchro();//等待同步

  第六步:配置 RTC 时钟参数。

  1. 查询 RTOFF 位,直到 RTOFF 的值变为’1’
  2. 置 CNF 值为 1 ,进入配置模式
  3. 对一个或多个 RTC 寄存器进行写操作
  4. 清除 CNF 标志位,退出配置模式
  5. 查询 RTOFF,直至 RTOFF 位变为’1’ 以确认写操作已经完成。仅当 CNF 标志位被清除时,写操作才能进行,这个过程至少需要 3 个 RTCCLK 周期。

  按照上述步骤用库函数来配置:

/* 1.    查询 RTOFF 位,直到 RTOFF 的值变为’1’ */

RTC_WaitForLastTask();//大家可以打开函数库看看这个函数内部的代码,就是查询 RTOFF的值

/*

2.置 CNF 值为 1 ,进入配置模式

3.对一个或多个 RTC 寄存器进行写操作

4.清除 CNF 标志位,退出配置模式

*/

RTC_SetPrescaler(32767); // 这里配置了预分频值,大家可以打开函数库看看这个函数的内部的代码,里面就有包含了 2、3、4 讲述的操作。

/*
每完成一个操作一般都要查询 RTOFF 来判断是否 RTC 正在更新数据,如果是则等待它完成!!!
*/
RTC_WaitForLastTask();//等待更新结束

RTC_ITConfig(RTC_IT_SEC, ENABLE);//配置秒中断

RTC_WaitForLastTask();//等待更新结束

三、程序演示

  rtc.h

#ifndef __RTC_H
#define __RTC_H
#include "stm32f10x.h"

//时间结构体
typedef struct
{
    vu8 hour;
    vu8 min;
    vu8 sec;
    //公历年月日周
    vu16 w_year;
    vu8  w_month;
    vu8  w_date;
    vu8  week;
}_calendar_obj;
extern _calendar_obj calendar;
void RCC_Configuration(void);
void RTC_Init(void);
u8 RTC_Set(u16 year,u8 mon,u8 day,u8 hour,u8 min,u8 sec);
u8 RTC_Get(void);
#endif

  rtc.c

#include "rtc.h"
_calendar_obj calendar;    //时钟结构体
//平均的月份日期表
const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};
/*rtc中断向量配置*/
void NVIC_Configuration(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

void RTC_Configuration(void)

{
    /* 使能PWR和BKP时钟 */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR|RCC_APB1Periph_BKP,ENABLE);
    /* 使能对后备寄存器的访问 */
    PWR_BackupAccessCmd(ENABLE);
    /* 复位BKP寄存器 */
    BKP_DeInit();
    /* 使能LSE */
    RCC_LSEConfig(RCC_LSE_ON);
    /*等待启动完成 */
    while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) {}
    /* 将 RTC时钟设置为LSE这个32.768KHZ的晶振*/
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
    /* 使能RTC Clock */
    RCC_RTCCLKCmd(ENABLE);
    /* 等待同步 */
    RTC_WaitForSynchro();
    /* 等待对RTC寄存器最后的写操作完成*/
    RTC_WaitForLastTask();
    /* 配置了预分频值: 设置RTC时钟周期为1s */
    RTC_SetPrescaler(32767); /* RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1)*/
    /* 等待对RTC寄存器最后的写操作完成 */
    RTC_WaitForLastTask();
    /* 使能RTC秒中断 */
    RTC_ITConfig(RTC_IT_SEC, ENABLE);
    /* 等待对RTC寄存器最后的写操作完成 */
    RTC_WaitForLastTask();

void RTC_Init(void)
{
    /*如果是第一次配置时钟,则执行RCC_Configuration()进行配置*/
    if(BKP_ReadBackupRegister(BKP_DR1)!=0x1016)
    {
            RCC_Configuration();
            RTC_Set(2016,5,11,9,7,55);
            GPIO_SetBits(GPIOD, GPIO_Pin_13);//点亮D1
            BKP_WriteBackupRegister(BKP_DR1, 0x1016);//向执行的后备寄存器中写入用户程序数据
    }
    else
    {
        RTC_WaitForSynchro();//等待RTC寄存器同步完成
        RTC_ITConfig(RTC_IT_SEC, ENABLE);//使能RTC秒中断
        RTC_WaitForLastTask();//等待最近一次对RTC寄存器的写操作完成
        GPIO_SetBits(GPIOG, GPIO_Pin_14);//点亮D2
    }
    NVIC_Configuration();
    RTC_Get();//更新时间
}
u8 Is_Leap_Year(u16 pyear)
{
    if(pyear%4==0)//首先需能被4整除
    {
        if(pyear%100==0)
        {
            if(pyear%400==0)    return 1;//如果以00结尾,还要能被400整除
            else    return 0;
        }
        else
            return 1;
    }
    else
        return 0;
}
/*
*设置时钟
*把输入的时钟转换为秒钟
*以1970年1月1日为基准
*1970~2099年为合法年份
返回值:0,成功;其它:错误
*/
u8 RTC_Set(u16 year,u8 mon,u8 day,u8 hour,u8 min,u8 sec)
{
    u16 t;
    u32 secCount=0;
    if(year<1970||year>2099)
        return 1;//³ö´í
    for(t=1970;t<year;t++)    //把所有年份的秒钟相加
    {
        if(Is_Leap_Year(t))//闰年
            secCount+=31622400;//闰年的秒钟数
        else
            secCount+=31536000;
    }
    mon-=1;//先减掉一个月再算秒数(如现在是5月10日,则只需要算前4个月的天数,再加上10天,然后计算秒数)
    for(t=0;t<mon;t++)
    {
        secCount+=(u32)mon_table[t]*86400;//月份秒钟数相加
        if(Is_Leap_Year(year)&&t==1)
            secCount+=86400;//闰年,2月份增加一天的秒钟数
    }

    secCount+=(u32)(day-1)*86400;//把前面日期的秒钟数相加(这一天还没过完,所以-1)
    secCount+=(u32)hour*3600;//小时秒钟数
    secCount+=(u32)min*60;//分钟秒钟数
    secCount+=sec;
//    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR    | RCC_APB1Periph_BKP,ENABLE);
//    PWR_BackupAccessCmd(ENABLE);
    RTC_SetCounter(secCount);//设置RTC计数器的值
    RTC_WaitForLastTask();    //等待最近一次对RTC寄存器的写操作完成
    RTC_Get();//更新时间
    return 0;
}

/*
得到当前的时间
成功返回0,错误返回其它
*/
u8 RTC_Get(void)
{
        static u16 dayCount=0;
        u32 secCount=0;
        u32 tmp=0;
        u16 tmp1=0;
        secCount=RTC_GetCounter();
        tmp=secCount/86400;//得到天数
        if(dayCount!=tmp)//超过一天
        {
            dayCount=tmp;
            tmp1=1970;//从1970年开始
            while(tmp>=365)
            {
                if(Is_Leap_Year(tmp1))//是闰年
                {
                    if(tmp>=366)
                        tmp-=366;//减掉闰年的天数
                    else
                    {
                    //    tmp1++;
                        break;
                    }
                }
                else
                    tmp-=365;//平年
                tmp1++;
            }
            calendar.w_year=tmp1;//得到年份
            tmp1=0;
            while(tmp>=28)//超过一个月
            {
                if(Is_Leap_Year(calendar.w_year)&&tmp1==1)/当年是闰年且轮循到2月
                {
                    if(tmp>=29)
                        tmp-=29;
                    else
                        break;
                }
                else
                {
                    if(tmp>=mon_table[tmp1])//平年
                        tmp-=mon_table[tmp1];
                    else
                        break;
                }
                tmp1++;
            }
            calendar.w_month=tmp1+1;//得到月份,tmp1=0表示1月,所以要加1
            calendar.w_date=tmp+1;    //得到日期,因为这一天还没过完,所以tmp只到其前一天,但是显示的时候要显示正常日期
        }
        tmp=secCount%86400;//得到秒钟数
        calendar.hour=tmp/3600;//小时
        calendar.min=(tmp%3600)/60;//分钟
        calendar.sec=(tmp%3600)%60;//秒
        return 0;
}
/*
RTC时钟中断
每秒触发一次
*/
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_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW);//清闹钟中断
    RTC_WaitForLastTask();
}

  main.c

#include "stm32f10x.h"
#include "usart1.h"
#include "LED.h"
#include "delay.h"
#include "flash.h"
#include "rtc.h"
#include "stdio.h"
int main(void)
{
    u8 t=0;
    USART1_Config();
    GPIO_Configuration();
    RTC_Init();
    while(1)
    {
        if(t!=calendar.sec)
        {
            t=calendar.sec;
            printf("\r\n now is %d 年 %d 月 %d 日 %d 时 %d 分 %d 秒 \r\n ",       calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec);
        }
        Delay(0x02FFFF);
    }

}    
时间: 2024-08-04 05:48:19

stm32——RTC实时时钟的相关文章

cortex_m3_stm32嵌入式学习笔记(十四):RTC实时时钟(秒中断)

STM32 的实时时钟( RTC)是一个独立的定时器. STM32 的 RTC 模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能.修改计数器的值可以重新设置系统当前的时间和日期. 由于时钟只需要配置一次,下次开机不需要重新配置(开发板有电池的情况下),所以需要用到备份区域(BKP)来标记是否配置过时钟 简单介绍BKP:备份寄存器是 42 个 16 位的寄存器( Mini 开发板就是大容量的),可用来存储 84 个字节的用户应用程序数据.他们处在备份域里, 当 VDD 电源被切

RTC实时时钟驱动

RTC(Real-Time Clock)实时时钟为操作系统提供了一个可靠的时间,并且在断电的情况下,RTC实时时钟也可以通过电池供电,一直运行下去. RTC通过STRB/LDRB这两个ARM指令向CPU传送8位数据(BCD码).数据包括秒,分,小时,日期,天,月和年.RTC实时时钟依靠一个外部的32.768Khz的石英晶体,产生周期性的脉冲信号.每一个信号到来时,计数器就加1,通过这种方式,完成计时功能. RTC实时时钟有如下一些特性: 1,BCD数据:这些数据包括秒.分.小时.日期..星期几.

STM32F4学习笔记10——RTC实时时钟

RTC实时时钟 实时时钟 (RTC) 是一个独立的 BCD 定时器/计数器.RTC 提供一个日历时钟.两个可编程 闹钟中断,以及一个具有中断功能的周期性可编程唤醒标志.RTC 还包含用于管理低功耗模 式的自动唤醒单元. 两个 32 位寄存器包含二进码十进数格式 (BCD) 的秒.分钟.小时(12 或 24 小时制).星 期几.日期.月份和年份.此外,还可提供二进制格式的亚秒值. 系统可以自动将月份的天数补偿为 28.29(闰年).30 和 31 天.并且还可以进行夏令时 补偿. 其它 32 位寄

RTC实时时钟

作者:宋老师,华清远见嵌入式学院讲师. 1.1 RTC介绍 在 一个嵌入式系统中,通常采用RTC 来提供可靠的系统时间,包括时分秒和年月日等,而且要求在系统处于关机状态下它也能够正常工作(通常采用后备电池供电).它的外围也不需要太多的辅助电 路,典型的就是只需要一个高精度的32.768kHz晶体和电阻电容等,如图10-8所示. 图10-8 RTC外接电路 1.2 RTC控制器 实时时钟(RTC)单元可以通过备用电池供电,因此,即使系统电源关闭,它也可以继续工作.RTC 可以通过STRB/LDRB

第43章 RTC—实时时钟

第43章     RTC-实时时钟 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/firege 43.1 RTC简介 RTC-real time clock,实时时钟,主要包含日历.闹钟和自动唤醒这三部分的功能,其中的日历功能我们使用的最多.日历包含两个32bit的时间寄存器,可直接输出时分秒,星期.月.日.年.比起F103系列的RTC只能输出秒中断,剩下的其他时间需要软件来实现,429的

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

RTC(Real Time Clock):实时时钟 BCD码:用4位2进制来表示10以内的十进制的形式. RTC的时钟源:LSE(32.768KHZ).HSE_RTC.LSI.经过一个精密校准(RTC_CALR).再经过7位的预分频器进行分频(RTC_PRER,默认127+1=128)会产生一个ck_apre()时钟.再经过粗略校准(RTC_CALPR).再经过RTC_PRER(一个同步15位预分频器(默认值=256),影子寄存器(RTC_SSR)).然后,再产生一个ck_spre(默认1HZ)

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

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

linux 实时时钟(RTC)驱动【转】

本文转载自:http://blog.csdn.net/yaozhenguo2006/article/details/6820218 这个是linux内核文档关于rtc实时时钟部分的说明,此文档主要描述了rtc实时时钟的作用和编程接口,分别介绍了老的rtc接口和新的rtc类架构.并给出了一个测试rtc驱动的程序.     linux 实时时钟(RTC)驱动                                                                        

STM32-RTC实时时钟-毫秒计时实现

OS:Windows 64 Development kit:MDK5.14 IDE:UV4 MCU:STM32F103C8T6 1.RTC时钟简介 STM32 的实时时钟(RTC)是一个独立的定时器,在相应软件配置下,可提供时钟日历的功能. 详细资料请参考ALIENTEK的官方文档--<STM32F1开发指南(精英版-库函数版)>,以下为博主摘录要点: RTC 模块和时钟配置系统(RCC_BDCR 寄存器)在后备区域 ,系统复位后,会自动禁止访问后备寄存器和 RTC ,所以在要设置时间之前,