打印
[应用相关]

RTC实时时钟(秒中断)

[复制链接]
2998|8
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
antusheng|  楼主 | 2017-10-18 11:06 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
STM32 的实时时钟( RTC)是一个独立的定时器。 STM32 的 RTC 模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。
由于时钟只需要配置一次,下次开机不需要重新配置(开发板有电池的情况下),所以需要用到备份区域(BKP)来标记是否配置过时钟
简单介绍BKP:备份寄存器是 42 个 16 位的寄存器( Mini 开发板就是大容量的),可用来存储 84 个字节的用户应用程序数据。他们处在备份域里, 当 VDD 电源被切断,他们仍然由 VBAT 维持供电。即使系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。此外, BKP 控制寄存器用来管理侵入检测和 RTC 校准功能。
简单说一下我对时钟工作原理的理解:一个32位的计数器,从0向上计数的话,假设每加一就是1秒,那么一个32位的计数器跑到溢出需要100多年。。已经很长了,这里时钟自带一个秒中断,当每加一的时候就会触发一次秒中断,我们通过往秒中断里写更新时间的函数来达到时间同步的效果
由于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};
const u8  table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表
u8 Is_LeapYear(u16 year)
{
        return (year%4==0&&year%100!=0)||year%400==0;
}

//获得现在是星期几
//功能描述:输入公历日期得到星期(只允许1901-2099年)
//输入参数:公历年月日
//返回值:星期号
u8 RTC_Get_Week(u16 year,u8 month,u8 day)
{
        u16 temp2;
        u8 yearH,yearL;
       
        yearH=year/100;        yearL=year%100;
        // 如果为21世纪,年份数加100  
        if (yearH>19)yearL+=100;
        // 所过闰年数只算1900年之后的  
        temp2=yearL+yearL/4;
        temp2=temp2%7;
        temp2=temp2+day+table_week[month-1];
        if (yearL%4==0&&month<3)temp2--;
        return(temp2%7);
}
static void RTC_NVIC_Config(void)
{       
  NVIC_InitTypeDef NVIC_InitStructure;
        NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;                //RTC全局中断
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;        //先占优先级1位,从优先级3位
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;        //先占优先级0位,从优先级4位
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                //使能该通道中断
        NVIC_Init(&NVIC_InitStructure);                //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
}
//得到当前的时间,结果保存在calendar结构体里面
//返回值:0,成功;其他:错误代码.
u8 RTC_Get(void)
{
        static u16 daycnt=0;
        u32 timecount=RTC_GetCounter();
        u32 daynum=timecount/86400;
        u16 tem=0;
        if(daycnt!=daynum)//大于1天
        {
                        daycnt=daynum;
                        tem=1970;
                        while(daynum>=365)
                        {
                                if(Is_LeapYear(tem))
                                {
                                        if(daynum>=366)daynum-=366;
                                        else break;
                                }
                                else daynum-=365;
                                tem++;
                         }
                        calendar.w_year=tem;//年
                        tem=0;
                        while(daynum>=28)
                        {
                                if(Is_LeapYear(calendar.w_year)&&tem==1)
                                {
                                        if(daynum>=29)daynum-=29;
                                        else break;
                                }
                                else
                                {
                                        if(daynum>=mon_table[tem])daynum-=mon_table[tem];
                                        else break;
                                }
                                tem++;
                        }
                        calendar.w_month=tem+1;//月
                        calendar.w_date=daynum+1;//日
        }
          daynum=timecount%86400;
                calendar.hour=daynum/3600;//时
                calendar.min=(daynum%3600)/60;//分
                calendar.sec=(daynum%3600)%60;//秒
                calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);
    return 0;
}


//设置时钟
//把输入的时钟转换为秒钟
//以 1970 年 1 月 1 日为基准
//1970~2099 年为合法年份
//返回值:0,成功;其他:错误代码.
//平年的月份日期表
//year,mon,day,hour,min,sec:年月日时分秒
//返回值:设置结果。 0,成功; 1,失败。
u8 RTC_Set(u16 year,u8 mon,u8 day,u8 hour,u8 min,u8 sec)
{
        u16 i;u32 seccnt=0;
        if(year<1970||year>2099)return 1;
        for(i=1970;i<year;i++)
        {
                if(Is_LeapYear(i))seccnt+=31622400;
                else seccnt+=31536000;
        }
        mon-=1;
        for(i=0;i<mon;i++)
        {
                seccnt+=(u32)mon_table[i]*86400;
                if(Is_LeapYear(year)&&i==1)
                        seccnt+=86400;
        }
        seccnt+=(u32)(day-1)*86400;
        seccnt+=(u32)hour*3600;
        seccnt+=(u32)min*60;
        seccnt+=(u32)sec;
        //使能 PWR 和 BKP 外设时钟
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR|RCC_APB1Periph_BKP, ENABLE);  
  PWR_BackupAccessCmd(ENABLE); //使能 RTC 和后备寄存器访问
  RTC_SetCounter(seccnt);  //设置 RTC 计数器的值
  RTC_WaitForLastTask();  //等待最近一次对 RTC 寄存器的写操作完成
  return 0;
}
//初始化RTC
u8 RTC_Init(void)
{
        u8 tem=0;
        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标志位设置与否,等待低速晶振就绪
                while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)==RESET)
                {
                        tem++;
                        delay_ms(10);
                }
                if(tem>=250)return 1;//初始化时钟失败,晶振有问题
                RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//设置RTC时钟
                RCC_RTCCLKCmd(ENABLE);//使能 RTC 时钟
                RTC_WaitForLastTask();//等待最近一次对 RTC 寄存器的写操作完成
                RTC_WaitForSynchro();//等待 RTC 寄存器同步
                RTC_ITConfig(RTC_IT_SEC,ENABLE);//使能 RTC 秒中断
                RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成
    RTC_SetPrescaler(32767);  //设置 RTC 预分频的值
    RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成
    RTC_Set(2009,12,2,10,0,55); //设置时间
    RTC_ExitConfigMode();  //退出配置模式
                //向指定的后备寄存器中写入用户程序数据 0x5050
    BKP_WriteBackupRegister(BKP_DR1,0X5050);
        }
        else
        {
                RTC_WaitForSynchro(); //等待最近一次对 RTC 寄存器的写操作完成
    RTC_ITConfig(RTC_IT_SEC,ENABLE); //使能 RTC 秒中断
    RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成
        }
        RTC_NVIC_Config(); //RCT 中断分组设置  
  RTC_Get(); //更新时间
  return 0; //ok
}
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();
}
我们是以1970年为基准,往后计时的,即当1970年一月一日00点00分00秒时,计数器从0开始计时。。
说一个比较粗心的地方(导致调试了一晚上都没调好),在写RTC_Get()函数(更新时间)的时候不小心将一个16位的变量当成32的用了。。结果是一晚上都忙的热火朝天还没找到是哪错了,早晨来到一直到中午才弄好。。sad

沙发
antusheng|  楼主 | 2017-10-18 11:07 | 只看该作者
[size=13.3333px]rtc.h
#ifndef _RTC_H
#define _RTC_H
#include "sys.h"
#include "delay.h"
#include "usart.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 Disp_Time(u8 x,u8 y,u8 size); //在制定位置开始显示时间
//void Disp_Week(u8 x,u8 y,u8 size,u8 lang);//在指定位置显示星期
u8 RTC_Init(void); //初始化 RTC,返回 0,失败;1,成功;
u8 Is_LeapYear(u16 year); //平年,闰年判断
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);//设置时间
#endif




使用特权

评论回复
板凳
antusheng|  楼主 | 2017-10-18 11:07 | 只看该作者
主函数
#include "led.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "lcd.h"
#include "usmart.h"
#include "rtc.h"

void init(void)
{
        NVIC_Configuration();
        delay_init();
        uart_init(9600);
        LED_Init();
        LCD_Init();
        usmart_dev.init(72);//初始化SMART组件
}
int main(void)
{
        u8 t;
        init();
  POINT_COLOR=RED;
        while(RTC_Init())                //RTC初始化        ,一定要初始化成功
        {
                LCD_ShowString(60,130,200,16,16,"RTC ERROR!   ");       
                delay_ms(800);
                LCD_ShowString(60,130,200,16,16,"RTC Trying...");       
        }
        //显示时间
        POINT_COLOR=BLUE;//设置字体为蓝色                                         
        LCD_ShowString(60,130,200,16,16,"    -  -     ");          
        LCD_ShowString(60,162,200,16,16,"  :  :  ");                             
        while(1)
        {                                                                    
                if(t!=calendar.sec)
                {
                        t=calendar.sec;
                        LCD_ShowNum(60,130,calendar.w_year,4,16);                                                                          
                        LCD_ShowNum(100,130,calendar.w_month,1,16);                                                                          
                        LCD_ShowNum(124,130,calendar.w_date,2,16);         
                        switch(calendar.week)
                        {
                                case 0:
                                        LCD_ShowString(60,148,200,16,16,"Sunday   ");
                                        break;
                                case 1:
                                        LCD_ShowString(60,148,200,16,16,"Monday   ");
                                        break;
                                case 2:
                                        LCD_ShowString(60,148,200,16,16,"Tuesday  ");
                                        break;
                                case 3:
                                        LCD_ShowString(60,148,200,16,16,"Wednesday");
                                        break;
                                case 4:
                                        LCD_ShowString(60,148,200,16,16,"Thursday ");
                                        break;
                                case 5:
                                        LCD_ShowString(60,148,200,16,16,"Friday   ");
                                        break;
                                case 6:
                                        LCD_ShowString(60,148,200,16,16,"Saturday ");
                                        break;  
                        }
                        LCD_ShowNum(60,162,calendar.hour,2,16);                                                                          
                        LCD_ShowNum(84,162,calendar.min,2,16);                                                                          
                        LCD_ShowNum(108,162,calendar.sec,2,16);
                        LED0=!LED0;
                }       
                delay_ms(10);                                                                  
        }                                                                                         
}


通过USMART就可以设置任意时间啦。。

使用特权

评论回复
地板
antusheng|  楼主 | 2017-10-18 11:08 | 只看该作者
以上内容转载于网络,感觉很有参考价值。

使用特权

评论回复
5
一路向北lm| | 2017-10-18 13:30 | 只看该作者
这种参考代码很多啊,原子哥的很详细。

使用特权

评论回复
6
antusheng|  楼主 | 2017-10-18 17:00 | 只看该作者
一路向北lm 发表于 2017-10-18 13:30
这种参考代码很多啊,原子哥的很详细。

是的,好多个哥的都很牛叉,要不怎么一战成名

使用特权

评论回复
7
wanduzi| | 2017-10-18 20:13 | 只看该作者
非常好的参考,以前没有做过这个方方面面,没想到计时器是这么工作的。

使用特权

评论回复
8
zhuomuniao110| | 2017-10-18 20:41 | 只看该作者
官方的库函数应该有RTC相关的,可以看看能否直接调用。

使用特权

评论回复
9
mmuuss586| | 2017-10-19 09:20 | 只看该作者
哦,谢谢分享;

使用特权

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

本版积分规则

82

主题

1447

帖子

5

粉丝