STM32学习笔记一一RTC实时时钟

发布者:风轻迟最新更新时间:2019-08-15 来源: eefocus关键字:STM32  RTC  实时时钟 手机看文章 扫描二维码
随时随地手机看文章

1. 简述

STM32 的实时时钟(RTC)是一个独立的定时器。 STM32 的 RTC 模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。


RTC 模块和时钟配置系统 (RCC_BDCR 寄存器)是在后备区域,即在系统复位或从待机模式唤醒后 RTC 的设置和时间维持不变。但是在系统复位后,会自动禁止访问后备寄存器和 RTC,以防止对后备区域 (BKP) 的意外写操作。所以在要设置时间之前, 先要取消备份区域(BKP)写保护。


在这里插入图片描述

RTC 由两个主要部分组成(参见上图), 第一部分(APB1 接口)用来和 APB1 总线相连。此单元还包含一组 16 位寄存器,可通过 APB1 总线对其进行读写操作。 APB1 接口由 APB1 总线时钟驱动,用来与 APB1 总线连接。


另一部分 (RTC 核心) 由一组可编程计数器组成,分成两个主要模块。第一个模块是 RTC 的预分频模块,它可编程产生 1 秒的 RTC 时间基准 TR_CLK。 RTC 的预分频模块包含了一个 20 位的可编程分频器 (RTC 预分频器)。如果在 RTC_CR 寄存器中设置了相应的允许位,则在每个TR_CLK 周期中 RTC 产生一个中断(秒中断)。第二个模块是一个 32 位的可编程计数器,可被初始化为当前的系统时间,一个 32 位的时钟计数器,按秒钟计算,可以记录 4294967296 秒,约合 136 年左右。


RTC 还有一个闹钟寄存器 RTC_ALR,用于产生闹钟。系统时间按 TR_CLK 周期累加并与存储在 RTC_ALR 寄存器中的可编程时间相比较,如果 RTC_CR 控制寄存器中设置了相应允许位,比较匹配时将产生一个闹钟中断。RTC 内核完全独立于 RTC APB1 接口,而软件是通过 APB1 接口访问 RTC 的预分频值、计数器值和闹钟值的。但是相关可读寄存器只在 RTC APB1 时钟进行重新同步的 RTC 时钟的上升沿被更新, RTC 标志也是如此。这就意味着,如果 APB1 接口刚刚被开启之后,在第一次的内部寄存器更新之前,从 APB1 上读取的 RTC 寄存器值可能被破坏了(通常读到 0)。因此,若在读取 RTC 寄存器曾经被禁止的 RTC APB1 接口,软件首先必须等待 RTC_CRL 寄存器的 RSF位(寄存器同步标志位, bit3)被硬件置 1。


2. RTC 寄存器介绍

2.1 RTC 的控制寄存器——RTC_CRH 寄存器

在这里插入图片描述

该寄存器用来控制中断的。


2.2 RTC 的控制寄存器——RTC_CRL 寄存器

在这里插入图片描述

RTC 用到的是该寄存器的 0、 3~5 这几个位,第 0 位是秒钟标志位,我们在进入闹钟中断的时候,通过判断这位来决定是不是发生了秒钟中断。然后必须通过软件将该位清零(写 0)。第 3 位为寄存器同步标志位,我们在修改控制寄存器 RTC_CRH/CRL 之前,必须先判断该位,是否已经同步了,如果没有则等待同步,在没同步的情况下修 RTC_CRH/CRL 的值是不行的。第 4 位为配置标位,在软件修改 RTC_CNT/RTC_ALR/RTC_PRL 的值的时候,必须先软件置位该位,以允许进入配置模式。第 5 位为 RTC 操作位,该位由硬件操作,软件只读。通过该位可以判断上次对 RTC 寄存器的操作是否完成,如果没有,我们必须等待上一次操作结束才能开始下一次操作。


2.3 RTC 预分频装载寄存器——RTC_PRLH 寄存器

这两个寄存器用来配置 RTC 时钟的分频数的,比如我们使用外部 32.768K 的晶振作为时钟的输入频率,那么我们要设置这两个寄存器的值为 32767,以得到一秒钟的计数频率。


在这里插入图片描述

2.4 RTC 预分频装载寄存器——RTC_PRLL 寄存器


在这里插入图片描述

2.5 RTC 预分频器余数寄存器——RTC_DIVH 寄存器

在这里插入图片描述

2.6 RTC 预分频器余数寄存器——RTC_DIVH 寄存器

在这里插入图片描述

这两个寄存器的作用就是用来获得比秒钟更为准确的时钟,比如可以得到 0.1 秒,或者 0.01 秒等。该寄存器的值自减的,用于保存还需要多少时钟周期获得一个秒信号。在一次秒钟更新后,由硬件重新装载。


2.7 RTC 计数器寄存器——RTC_CNT 寄存器

该寄存器由 2 个 16 位的寄存器组成 RTC_CNTH 和 RTC_CNTL,总共 32 位,用来记录秒钟值(一般情况下)。在修改这个寄存器的时候要先进入配置模式。

在这里插入图片描述

2.8 RTC 计数器寄存器——RTC 闹钟寄存器

该寄存器也是由 2 个 16 位的寄存器组成 RTC_ALRH 和 RTC_ALRL。总共也是 32 位,用来标记闹钟产生的时间(以秒为单位),如果 RTC_CNT 的值与 RTC_ALR 的值相等,并使能了中断的话,会产生一个闹钟中断。该寄存器的修改也要进入配置模式才能进行。


在这里插入图片描述

3. 备份寄存器介绍

备份寄存器是 42 个 16 位的寄存器(Mini 开发板就是大容量的),可用来存储 84 个字节的用户应用程序数据。他们处在备份域里,当 VDD 电源被切断,他们仍然由 VBAT 维持供电。即使系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。


复位后,对备份寄存器和 RTC 的访问被禁止,并且备份域被保护以防止可能存在的意外的

写操作。执行以下操作可以使能对备份寄存器和 RTC 的访问:


(1)通过设置寄存器 RCC_APB1ENR 的 PWREN 和 BKPEN 位来打开电源和后备接口的时钟;


(2)电源控制寄存器 (PWR_CR) 的 DBP 位来使能对后备寄存器和 RTC 的访问。


一般用 BKP 来存储 RTC 的校验值或者记录一些重要的数据,相当于一个 EEPROM,不过这个 EEPROM 并不是真正的 EEPROM,而是需要电池来维持它的数据。


在这里插入图片描述

RTC 的时钟源选择及使能设置都是通过这个寄存器来实现的,所以我们在 RTC 操作之前先要通过这个寄存器选择 RTC 的时钟源,然后才能开始其他的操作。


4. RTC 配置步骤

(1) 使能电源时钟和备份区域时钟


RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);


(2) 取消备份区写保护


PWR_BackupAccessCmd(ENABLE); //使能 RTC 和后备寄存器访问


(3) 复位备份区域,开启外部低速振荡器。


BKP_DeInit();//复位备份区域


(4) 选择 RTC 时钟,并使能


RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //选择 LSE 作为 RTC 时钟(RCC_RTCCLKSource_LSI 和 RCC_RTCCLKSource_HSE_Div128)

RCC_RTCCLKCmd(ENABLE); //使能 RTC 时钟


(5) 设置 RTC 的分频,以及配置 RTC 时钟


在开启了 RTC 时钟之后,我们要做的是设置 RTC 时钟的分频数,通过 RTC_PRLH 和RTC_PRLL 来设置,然后等待 RTC 寄存器操作完成,并同步之后,设置秒钟中断。然后设置 RTC 的允许配置位(RTC_CRH 的 CNF 位),设置时间(其实就是设置RTC_CNTH 和 RTC_CNTL两个寄存器)。


RTC_EnterConfigMode();/// 允许配置

RTC_ExitConfigMode();//退出配置模式,更新配置

void RTC_SetPrescaler(uint32_t PrescalerValue);

void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);//RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能 RTC 秒中断

void RTC_SetCounter(uint32_t CounterValue)最后在配置完成之后


(6) 更新配置,设置 RTC 中断分组


设置完时钟之后,我们将配置更新同时退出配置模式,这里还是通过 RTC_CRH 的 CNF

来实现。


RTC_ExitConfigMode();//退出配置模式,更新配置


在退出配置模式更新配置之后我们在备份区域 BKP_DR1 中写入 0X5050 代表我们已经初始化过时钟了,下次开机(或复位)的时候,先读取 BKP_DR1 的值,然后判断是否是 0X5050 来决定是不是要配置。接着我们配置 RTC 的秒钟中断,并进行分组。


void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);//往备份区域写用户数据

uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);//读取备份区域指定寄存器


(7) 编写中断服务函数


流程图:


在这里插入图片描述

5. 程序实现

5.1 初始化

u8 RTC_Init(void)

{

//检查是不是第一次配置时钟

u8 temp=0;

RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟   

PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问  

if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050) //从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎

{


BKP_DeInit(); //复位备份区域

RCC_LSEConfig(RCC_LSE_ON); //设置外部低速晶振(LSE),使用外设低速晶振

while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) //检查指定的RCC标志位设置与否,等待低速晶振就绪

{

temp++;

delay_ms(10);

}

if(temp>=250)

return 1;//初始化时钟失败,晶振有问题     

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_Set(1972,1,2,1,1,1);  //设置时间

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_Get();//更新时间

return 0; //ok


}


static void RTC_NVICConfig(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寄存器

}


中断服务函数:


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();            

}


5.2 RTC部分实现

闰年判断:


//判断是否是闰年函数

//月份   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==0) //必须能被4整除

if(year%100==0) 

if(year%400==0)

return 1;//如果以00结尾,还要能被400整除    

else 

return 0;   

}

else 

return 1;   

}

else 

return 0;

}



时钟设置:


//设置时钟

//把输入的时钟转换为秒钟

//以1970年1月1日为基准

//1970~2099年为合法年份

//返回值:0,成功;其他:错误代码.

//月份数据表


u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表   

//平年的月份日期表

const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};


u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)

{

u16 t;

u32 seccount=0;

if(syear<1970||syear>2099)

return 1;    

for(t=1970;t {

if(Is_Leap_Year(t))

seccount += 31622400;//闰年的秒钟数

else 

seccount += 31536000;   //平年的秒钟数

}

smon -= 1;//月份从1开始,数组从0开始计数

for(t=0;t {

seccount += (u32)mon_table[t] * 86400;//月份秒钟数相加

if(Is_Leap_Year(syear)&&t==1)

seccount += 86400;//闰年2月份增加一天的秒钟数    

}

seccount += (u32)(sday-1) * 86400;//把前面日期的秒钟数相加(整天数) 

seccount += (u32)hour * 3600;//小时秒钟数

    seccount += (u32)min * 60; //分钟秒钟数

seccount += sec;//最后的秒钟加上去


RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟  

PWR_BackupAccessCmd(ENABLE); //使能RTC和后备寄存器访问 

RTC_SetCounter(seccount); //设置RTC计数器的值


RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 

RTC_Get();

return 0;     

}


得到当前时间:


//得到当前的时间

//返回值:0,成功;其他:错误代码.

u8 RTC_Get(void)

{

static u16 daycnt=0;

u32 timecount=0; 

u32 temp=0;

u16 temp1=0;

    timecount = RTC_GetCounter();  

//timecount = 86400*4+88;

  temp = timecount / 86400;   //得到天数(秒钟数对应的)

if(daycnt!=temp)//超过一天了

{   

daycnt = temp;

temp1 = 1970; //从1970年开始

while(temp>=365)

{  

if(Is_Leap_Year(temp1))//是闰年

{

if(temp>=366)

temp -= 366;//减去一闰年,还剩下的天数

else 

{

temp1 ++;

break;

}  

}

else 

temp -= 365;   //减去一平年 ,还剩下的天数

temp1 ++;  

}   

calendar.w_year = temp1;//得到年份

temp1=0;

while(temp>=28)//28天,超过了最小的一个月

{

if(Is_Leap_Year(calendar.w_year)&&temp1==1)//当年是不是闰年/2月份

{

if(temp>=29)

temp -= 29;//闰年的秒钟数

else 

break; 

}

else 

{

if(temp>=mon_table[temp1])

temp -= mon_table[temp1];//平年

else 

break;

}

temp1++;  

}

calendar.w_month = temp1+1; //得到月份

calendar.w_date = temp+1;  //得到日期 

}

temp = timecount%86400;      //得到秒钟数       

calendar.hour = temp/3600;      //小时

calendar.min = (temp%3600)/60; //分钟

calendar.sec = (temp%3600)%60; //秒钟

calendar.week = RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);//获取星期 

return 0;

}


得到星期几:


//获得现在是星期几

//功能描述:输入公历日期得到星期(只允许1901-2099年)

//输入参数:公历年月日 

//返回值:星期号  

u8 RTC_GetWeek(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);

}   


对于星期如何转换,可参考如下链接:

1.RTC-由年月日计算星期几


2.基姆拉尔森计算公式


6. 附录:

STM32 时钟树:



参考:


1.[原子教程库函数实现]


2.STM32开发 – RTC详解


关键字:STM32  RTC  实时时钟 引用地址:STM32学习笔记一一RTC实时时钟

上一篇:STM32学习笔记一一触摸屏
下一篇:STM32学习笔记一一HEX文件和BIN文件格式

推荐阅读最新更新时间:2024-11-08 08:47

STM32开发笔记94: 忽略PlatformIO中的特定警告
单片机型号:STM32F091RCT6 尝试在PlatformIO中使用STM32Cube进行项目的开发工作,第1次编译,即出现如下图的警告。 该警告在GCC中有详尽的解释: Allows the compiler to assume the strictest aliasing rules applicable to the language being compiled. For C (and C++), this activates optimizations based on the type of expressions. In particular, an object of one type is ass
[单片机]
<font color='red'>STM32</font>开发笔记94: 忽略PlatformIO中的特定警告
STM32学习笔记—SysTick定时器
简介:Q:什么是SYSTick定时器? SysTick 是一个24 位的倒计数定时器,当计到0 时,将从RELOAD 寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息。 Q:什么是SYSTick定时器? SysTick 是一个24 位的倒计数定时器,当计到0 时,将从RELOAD 寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息。 Q:为什么要设置SysTick定时器? (1)产生操作系统的时钟节拍 SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号:15)。在以前,大多操作系统需要一个硬
[单片机]
STM32 SysTick定时器做延时函数
在STM32中延时函数用的非常广泛,具体延时函数怎么使用,下面我们来进行想详解,本文主要介绍采用SysTick计时器来实验系统延时: 原理介绍: SysTick计时器是一个24位的倒计数定时器,主要用来做操作系统的定时器,每来一个时钟周期计数减1,当计数到0时,他就会自动从LOAD寄存器中自动重装设置的初值,操作时只要不把CTRL寄存器中的ENABLE位清0,它就永远不会停止,即使在睡眠状态他也还是在继续奋斗。 配置代码如下: static u8 fac_us=0;//us延时倍乘数 static u16 fac_ms=0;//ms延时倍乘数 //初始化延迟函数 //SYSTICK的时钟固定为HCLK时钟的1/8 //SYSCLK
[单片机]
STM32实现单麦克风实时神经网络降噪
本文是基于NNoM神经网络框架实现的。NNoM是一个为单片机定制的神经网络框架,可以实现TensorFlow 模型的量化和部署到单片机上,可以在Cortex M4/7/33等ARM内核的单片机上实现加速(STM32,LPC,Nordic nRF 等等)。 NNoM和本文代码可以在后台回复:“麦克风降噪”领取。 STM32实现单麦克风实时神经网络(RNN)降噪演示 硬声创作者:麻博士在科研 这个例子是根据著名的 RNNoise (https://jmvalin.ca/demo/rnnoise/) 的降噪方法进行设计的。整体进行了一些简化和定点化的一些修改。 本例与RNNoise主要的区别如下: 此例子并非从RNN
[单片机]
<font color='red'>STM32</font>实现单麦克风实时神经网络降噪
STM32F0项目进阶之实时时钟DS1307
写在前面的话:STM32F0项目进阶系列是硬件系统工程师星克曼以项目进阶的方式写出的入门教程,以项目开发为蓝底,快速学习进入实战。 STM32 I2C总线在上一讲 STM32F0项目进阶之I2C 中已经讲过了,已经将标准库抽象成hal_i2c.c和hal_i2c.h了,下面的就是适配不同的芯片了,这次我们适配一下DS1307。 芯片手册如下:www.maximintegrated.com/en/DS1307.pdf 电路配置 引脚图如下: 引脚描述如下: 电路配置如下: BCD格式 注意:时间和日期的格式均是BCD格式的。 上电工作 上电即可以工作,时钟使能的话,就没有其它配置;如果没有使能
[单片机]
STM32F0项目进阶之<font color='red'>实时时钟</font>DS1307
STM32获取当前系统时钟
代码如下: RCC_ClocksTypeDef get_rcc_clock; //获取系统时钟状态 RCC_GetClocksFreq(&get_rcc_clock); //仿真的时候就可以在结构体get_rcc_clock中看见各个外设的时钟了 注意: 上述代码中的结构体RCC_ClocksTypeDef原型,可以在库文件stm32f10x_rcc.h中找到,如下: typedef struct { uint32_t SYSCLK_Frequency; /*! returns SYSCLK clock frequency expressed in Hz */ uint32_t HCLK_Frequen
[单片机]
STM32入门学习笔记之低功耗实验
13.1 STM32低功耗模式概述 STM32在系统或电源复位后,芯片处于运行状态,此时HCLK为CPU提供时钟,内核执行程序代码,当CPU不需要继续运行时,可以采用低功耗模块来降低芯片的运行电流,STM32有3种低功耗模式: (1)睡眠模式:内核停止,外设继续运行 (2)待机模式:1.8V的内核电源被关闭,SRAM内容丢失,PLL,HIS,HSE振荡器断电,此模式下最低电流2uA (3)停机模式:停止所有时钟,此模式下最低电流20uA 上述三种模式的配置与唤醒条件如下表所示。 模式 进入操作 唤醒 睡眠 WFI指令 任一中断 WFE指令 唤醒事件 待机 PDDS位+SLEEPDEEP位+WFI或者WFE WKUP
[单片机]
STM32自定义printf实现多串口互用
STM32 串口使用频繁的朋友可能知道一个问题,库自带的 USART_SendData 函数一次只能发送 1 Byte 字符 如果我们要输出一个带格式、带参数的串口数据就只能通过 sprintf 打印到一个临时数组,然后再使用一个for循环调用USART_SendData一个字符一个字符的输出,过程非常麻烦! 姜斌是一个非常怕麻烦的人所以这种方法无疑让我发疯!后来在原子(OpenEDV)那里购买了开发板拿到了例程。里面重定向了printf函数作为串口输出函数,有了这个函数之后就能够很轻松的参数输出,但问题就在这里我们串口1 串口2 都要带参数输出怎么办? 本次实验:自定义printf函数 实验数据来源: 配置usart
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved