当前位置: 首页 > news >正文

【STM32单片机】#13 RTC实时时钟

主要参考学习资料:

B站@江协科技

STM32入门教程-2023版 细致讲解 中文字幕

开发资料下载链接:https://pan.baidu.com/s/1h_UjuQKDX9IpP-U1Effbsw?pwd=dspb

单片机套装:STM32F103C8T6开发板单片机C6T6核心板 实验板最小系统板套件科协

目录

  • Unix时间戳
    • UTC/GMT
    • 时间戳转换
  • BKP简介
    • BKP基本结构
  • RTC简介
    • RTC框图
    • RTC基本结构
    • 硬件电路
    • RTC操作注意事项
  • 函数详解
    • BKP库函数
      • BKP_DeInit函数
      • BKP_TamperPinLevelConfig函数
      • BKP_TamperPinCmd函数
      • BKP_ITConfig函数
      • BKP_RTCOutputConfig函数
      • BKP_SetRTCCalibrationValue函数
      • BKP_WriteBackupRegister函数
      • BKP_ReadBackupRegister函数
      • 中断标志位函数
    • PWR_BackupAccessCmd函数
    • RCC库函数
      • RCC_LSEConfig函数
      • RCC_LSICmd函数
      • RCC_RTCCLKConfig函数
      • RCC_RTCCLKCmd函数
      • RCC_GetFlagStatus函数
    • RTC库函数
      • RTC_ITConfig函数
      • RTC_EnterConfigMode函数
      • RTC_ExitConfigMode函数
      • RTC_GetCounter函数
      • RTC_SetCounter函数
      • RTC_SetPrescaler函数
      • RTC_SetAlarm函数
      • RTC_GetDivider函数
      • RTC_WaitForLastTask函数
      • RTC_WaitForSynchro函数
      • 中断标志位函数
  • 实验28 读写备份寄存器
    • 接线图
    • 主程序
  • 实验29 实时时钟
    • MyRTC驱动
    • 主程序

Unix时间戳

  • Unix时间戳定义为从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数,不考虑闰秒。
  • 时间戳存储在一个秒计数器中,秒计数器为32位/64位的整型变量。只使用秒而不采取进位的优点是硬件电路简单,仅需秒寄存器和一个变量,且便于计算时间间隔。
  • 世界上所有时区的秒计数器相同,不同时区通过添加偏移来得到当地时间。
  • 32位(有符号)时间戳将在2038年1月19日溢出,现今的电子设备基本采用64位时间戳,可以运作上千亿年。

UTC/GMT

  • GMT(Greenwich Mean Time)格林尼治标准时间是一种以地球自转为基础的时间计量系统,它将地球自转一周的时间间隔等分为24小时,以此确定计时标准。但由于地球自转周期不固定,GMT定义的时间基准也将不断变化,不利于科学研究,因此UTC作为新的时间系统被提出。
  • UTC(Universal Time Coordinated)协调世界时是一种以原子钟为基础的时间计量系统,它规定铯133原子基态的两个超精细能级间在零磁场下跃迁辐射9192631770周所持续的时间为1秒。当原子钟计时一天的时间与地球自转一周的时间相差超过0.9秒时,UTC会执行闰秒来保证其计时与地球自转协调一致。
  • 日常生活中大多不会追求极致的严谨,GMT和UTC可视作相同的时间系统。

时间戳转换

C语言的time.h模块提供了时间获取和时间戳转换的相关函数,可以方便地进行秒计数器、日期时间和字符串之间的转换。本节使用表中localtime和mktime函数。

其中time_t默认为有符号64位整型,char*指向表示时间的字符串,struct tm结构体成员如下:

struct tm {int tm_sec;         /* 秒,范围从 0 到 59        */int tm_min;         /* 分,范围从 0 到 59        */int tm_hour;        /* 小时,范围从 0 到 23        */int tm_mday;        /* 一月中的第几天,范围从 1 到 31    */int tm_mon;         /* 月,范围从 0 到 11        */int tm_year;        /* 自 1900 年起的年数        */int tm_wday;        /* 一周中的第几天,范围从 0 到 6    */int tm_yday;        /* 一年中的第几天,范围从 0 到 365    */int tm_isdst;       /* 夏令时                */
};

BKP简介

  • BKP(Backup Registers)备份寄存器
  • BKP可用于存储用户应用程序数据。当VDD(2.0~3.6V)电源被切断,它们仍由VBAT(1.8~3.6V)维持供电。当系统在待机模式下被唤醒,或系统/电源复位时,它们也不会被复位。
  • 以下是BKP的主要功能,其中后两个与RTC关联:
    • TAMPER引脚(PC13)产生的侵入事件将所有备份寄存器内容清除。
    • RTC引脚(PC13)输出RTC校准时钟、RTC闹钟脉冲或者秒脉冲。外部设备测量RTC校准脉冲可以对其内部RTC微小误差进行校准,闹钟脉冲和秒脉冲可以为别的设备提供信号。
    • 存储RTC时钟校准寄存器。
  • 用户数据存储容量:20字节(中小容量)/84字节(大容量和互联型)

BKP基本结构

图中橙色部分为后备区域,包括BKP和RTC相关电路。后备区域在VDD主电源掉电时,仍然可以由VBAT的备用电池供电;当VDD主电源上电时,后备区域供电会由VBAT切换到VDD。

BKP的数据寄存器用于存储数据,每个DR可存储两个字节,中小容量有10个DR,大容量和互联型有42个DR。侵入检测从TAMPER引脚引入一个检测信号,当TAMPER产生上升/下降沿时,BKP所有内容被清除以保证安全。时钟输出将RTC相关时钟从RTC引脚输出供外部使用,输出校准时钟时配合RTC时钟校准寄存器可以对RTC的误差进行校准。

RTC简介

  • RTC(Real Time Clock)实时时钟
  • RTC是一个独立的定时器,可为系统提供时钟和日历功能。
  • RTC和时钟配置系统处于后备区域,系统复位时数据不清零,VDD断电后可借助VBAT供电继续走时。
  • 32位可编程计数器,可对应Unix时间戳的秒计数器。
  • 20位可编程预分频器,可适配不同频率的输入时钟,分频为1Hz可用于驱动秒计数器。
  • 可选择三种RTC时钟源:
    • HSE时钟(高速外部时钟信号,8MHz)除以128
    • LSE振荡器时钟(低速外部时钟信号,通常为32.768KHz)
    • LSI振荡器时钟(低速内部时钟信号,40KHz)
    • 最常用的时钟源为LSE,32.768KHz经 2 15 2^{15} 215分频即可得到1Hz,且只有LSE可以通过VBAT备用电池供电。

RTC框图

框图左侧为核心的分频计数计时部分,右侧为中断输出使能和NVIC部分,上方为APB1总线读写部分,下方为PWR关联部分,灰色背景部分均处于后备区域。睡眠、停机、待机等低功耗相关内容将在下一节PWR部分讲解。

输入时钟为RTCCLK,由于可选时钟源的频率各不相同,且远大于所需的1Hz秒计数频率,因此RTCCLK需要首先经过RTC预分频器进行分频。分频器由重装载寄存器RTC_PRL和余数寄存器RTC_DIV(自减计数器)组成,本质上和定时器中的重装值ARR和计数器CNT作用相同,计几个数溢出一次即为几分频,由于计数器包含0,因此分频系数为重装值加一。

32位可编程计数器RTC_CNT为计时最核心的部分,可视为Unix时间戳的秒计数器。RTC_ALR为32位闹钟寄存器,可在ALR写入一个秒数设定闹钟,当CNT的值与ALR设定值相等时产生RTC_Alarm信号通往中断系统,或使STM32退出待机模式。其余两个中断信号,秒中断RTC_Second每秒触发,溢出中断RTC_Overflow在32位CNT计满溢出时触发。RTC_CR中,F后缀为中断标志位,IE后缀为中断使能,三个中断信号通过或门前往NVIC中断控制器。

上方APB1总线和APB1接口为程序读写寄存器的地方。下方闹钟信号和WKUP引脚(PA0)均可唤醒STM32,将在下一节学习。

RTC基本结构

除去多余内容,本实验所用到RTC结构如下:

硬件电路

为了配合STM32的RTC,需要连接备用电池供电和外部低速晶振两个外部电路。本节实验最小系统板上已包含外部低速晶振,备用电池使用STLINK的3.3V供电。

备用电池供电中,简单连接直接使用3V电池,负极和系统共地,正极连接VBAT引脚;芯片参考手册的推荐连接同时使用电池和3.3V主电源通过二极管(防止电流倒灌)向VBAT供电,同时使用滤波电容。实验使用简单连接即可,板子绘制和产品设计使用推荐连接更保险。

根据参考手册,外部低速晶振中,X1为32.768KHz晶振,两端分别接在OSC32两个引脚上,并再各自接一个起振电容到GND。

RTC操作注意事项

  • 执行以下操作将使能对BKP和RTC的访问:
    • 设置RCC_APB1ENR的PWREN和BKPEN,使能PWR和BKP时钟(RTC无单独开启时钟选项)
    • 设置PWR_CR的DBP,使能对BKP和RTC的访问
  • 若在读取RTC寄存器时,RTC的APB1接口曾经处于禁止状态,则软件首先必须等待RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置1。这是由于RTC由RTCCLK(32.768KHz)驱动,而读取RTC寄存器的APB1总线由PCLK1(36MHz)驱动,存在时钟不同步问题,在APB1刚开启时立刻读取RTC寄存器,有可能RTC寄存器尚未更新到APB1总线上。
  • 必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入RTC_PRL、RTC_CNT、RTC_ALR寄存器。该操作在每个写寄存器的库函数中自动配置。
  • 对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。仅当RTOFF状态位是1时,才可以写入RTC寄存器。这也是由于时钟不同步导致APB1写入的值无法立刻更新到RTC寄存器中。

函数详解

BKP库函数

BKP_DeInit函数

简介:恢复缺省配置(手动清空数据寄存器)。

参数:void

BKP_TamperPinLevelConfig函数

简介:配置TAMPER引脚有效电平。

参数:高/低电平

BKP_TamperPinLevel_High/Low

BKP_TamperPinCmd函数

简介:侵入检测使能。

参数:使能/失能

BKP_ITConfig函数

简介:BKP侵入中断配置。

参数:使能/失能

BKP_RTCOutputConfig函数

简介:RTC引脚输出配置。

参数:输出信号

BKP_RTCOutputSource_None(无)
BKP_RTCOutputSource_CalibClock(校准时钟)
BKP_RTCOutputSource_Alarm(闹钟脉冲)
BKP_RTCOutputSource_Second(秒脉冲)

BKP_SetRTCCalibrationValue函数

简介:设置RTC校准值(写入RTC校准寄存器)。

参数:0~0x7F校准值。

BKP_WriteBackupRegister函数

简介:写备份寄存器。

参数一:数据寄存器

BKP_DR1, ..., BKP_DR42

参数二:写入数据

BKP_ReadBackupRegister函数

简介:读备份寄存器。

参数:数据寄存器

中断标志位函数

BKP_GetFlagStatus函数

BKP_ClearFlag函数

BKP_GetITStatus函数

BKP_ClearITPendingBit函数

参数:void(BKP只有侵入检测中断)

PWR_BackupAccessCmd函数

PWR库函数在下一节讲解,本节只使用其中的该函数。

简介:备份寄存器访问使能。

参数:使能/失能

RCC库函数

RCC库函数包含部分RTC时钟相关函数。

RCC_LSEConfig函数

简介:配置外部低速时钟。

参数:LSE工作状态

RCC_LSE_OFF/ON, 
RCC_LSE_ByPass(使用OSC32_IN引脚输入的外部时钟信号)

RCC_LSICmd函数

简介:配置内部低速时钟。

参数:使能/失能

RCC_RTCCLKConfig函数

简介:配置RCCCLK。

参数:RCCCLK时钟源

RCC_RTCCLKSource_LSE/LSI
RCC_RTCCLKSource_HSE_Div128

RCC_RTCCLKCmd函数

简介:使能RTCCLK。

参数:使能/失能

RCC_GetFlagStatus函数

简介:获取标志位。

参数:标志位,本节只用到一个。

RCC_FLAG_LSERDY(LSE启动完成)

RTC库函数

RTC_ITConfig函数

简介:配置中断输出。

参数一:RTC中断源

RTC_IT_OW/ALE/SEC(溢出/闹钟/秒)

参数二:使能/失能

RTC_EnterConfigMode函数

简介:进入配置模式(对应注意事项第三点)。

参数:void

RTC_ExitConfigMode函数

简介:退出配置模式。

参数:void

RTC_GetCounter函数

简介:读取CNT计数器(读取时钟)。

参数:void

RTC_SetCounter函数

简介:写入CNT计数器(设置时钟)。

参数:计数值

RTC_SetPrescaler函数

简介:写入预分频器中的重装载寄存器。

参数:重装值(分频系数减一)

RTC_SetAlarm函数

简介:写入闹钟。

参数:闹钟值

RTC_GetDivider函数

简介:读取预分频器中的余数寄存器(得到比秒更细致的时间)。

参数:void

RTC_WaitForLastTask函数

简介:等待上次操作完成(对应注意事项第四点)。

参数:void

RTC_WaitForSynchro函数

简介:等待同步(对应注意事项第二点)。

参数:void

中断标志位函数

RTC_GetFlagStatus函数

RTC_ClearFlag函数

参数:RTC标志位

RTC_FLAG_RTOFF(写完成)
RTC_FLAG_RSF(同步)
RTC_FLAG_OW
RTC_FLAG_ALR
RTC_FLAG_SEC

RTC_GetITStatus函数

RTC_ClearITPendingBit函数

参数:RTC中断源

实验28 读写备份寄存器

接线图

主程序

BKP的代码较少,因此不单独进行封装。

#include "stm32f10x.h" 
#include "Delay.h"
#include "OLED.h"
#include "Key.h"uint8_t KeyNum;uint16_t ArrayWrite[] = {0x1234, 0x5678};
uint16_t ArrayRead[2];int main(void)
{OLED_Init();Key_Init();OLED_ShowString(1, 1, "W:");OLED_ShowString(2, 1, "R:");//开启时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);//使能访问PWR_BackupAccessCmd(ENABLE);while(1){KeyNum = Key_GetNum();if(KeyNum){//按下按键后变换测试数据并写入ArrayWrite[0] ++;ArrayWrite[1] ++;BKP_WriteBackupRegister(BKP_DR1, ArrayWrite[0]);BKP_WriteBackupRegister(BKP_DR2, ArrayWrite[1]);OLED_ShowHexNum(1, 3, ArrayWrite[0], 4);OLED_ShowHexNum(1, 8, ArrayWrite[1], 4);}//读取数据ArrayRead[0] = BKP_ReadBackupRegister(BKP_DR1);ArrayRead[1] = BKP_ReadBackupRegister(BKP_DR2);OLED_ShowHexNum(2, 3, ArrayRead[0], 4);OLED_ShowHexNum(2, 8, ArrayRead[1], 4);}
}

实验29 实时时钟

接线在实验28的基础上去掉按键。

MyRTC驱动

驱动文件放在System中。

MyRTC.h

#ifndef __MYRTC_H
#define __MYRTC_Hextern uint16_t MyRTC_Time[];void MyRTC_Init(void);
void MyRTC_SetTime(void);
void MyRTC_ReadTime(void);#endif

MyRTC.c

#include "stm32f10x.h"
#include <time.h>//年月日时分秒
uint16_t MyRTC_Time[] = {2025, 5, 5, 17, 29, 00};
//提前声明设置时间函数用于初始化
void MyRTC_SetTime(void);void MyRTC_Init(void)
{//开启外设时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);//使能访问PWR_BackupAccessCmd(ENABLE);//利用BKP特性判断系统是否完全断电过//仅在完全断电后的复位操作重置时间//自定义一个初始化时保存在BKP中的值if(BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5){//开启LSE时钟RCC_LSEConfig(RCC_LSE_ON);//等待LSE准备完成while(!RCC_GetFlagStatus(RCC_FLAG_LSERDY));//选择RTCCLK时钟源RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);RCC_RTCCLKCmd(ENABLE);//等待同步RTC_WaitForSynchro();//等待上一次操作完成RTC_WaitForLastTask();//配置预分频器//配置函数自带进入/退出配置模式代码,无需额外调用RTC_SetPrescaler(32768 - 1);RTC_WaitForLastTask();//设置时间MyRTC_SetTime();//写入BKPBKP_WriteBackupRegister(BKP_DR1, 0xA5A5);}else{//不初始化时调用等待代码防止意外RTC_WaitForSynchro();RTC_WaitForLastTask();}
}//设置时间
void MyRTC_SetTime(void)
{time_t time_cnt;struct tm time_date;//将数组时间信息转移到结构体//time.h中年份从1900开始计算,月份为0-11time_date.tm_year = MyRTC_Time[0] - 1900;time_date.tm_mon = MyRTC_Time[1] - 1;time_date.tm_mday = MyRTC_Time[2];time_date.tm_hour = MyRTC_Time[3];time_date.tm_min = MyRTC_Time[4];time_date.tm_sec = MyRTC_Time[5];//转换为秒计数值time_cnt = mktime(&time_date);//设置时间RTC_SetCounter(time_cnt);RTC_WaitForLastTask();
}//读取时间,流程与设置时间相反
void MyRTC_ReadTime(void)
{time_t time_cnt;struct tm time_date;time_cnt = RTC_GetCounter();time_date = *localtime(&time_cnt);MyRTC_Time[0] = time_date.tm_year + 1900;MyRTC_Time[1] = time_date.tm_mon + 1;MyRTC_Time[2] = time_date.tm_mday;MyRTC_Time[3] = time_date.tm_hour;MyRTC_Time[4] = time_date.tm_min;MyRTC_Time[5] = time_date.tm_sec;
}

如果严格按照Unix时间戳的规则,需要在伦敦时间的基础上添加偏移来得到不同时区的时间。直接对struct tm结构体的tm_hour成员操作可能导致进位错误,可以通过time_cnt转换到time_date时加上8*60*60(北京时间UTC+8)实现偏移(逆向转换则减去相应的值)。

主程序

#include "stm32f10x.h" 
#include "Delay.h"
#include "OLED.h"
#include "MyRTC.h"int main(void)
{OLED_Init();MyRTC_Init();OLED_ShowString(1, 1, "Date:XXXX-XX-XX");OLED_ShowString(2, 1, "Time:XX:XX:XX");OLED_ShowString(3, 1, "CNT :");OLED_ShowString(3, 1, "DIV :");while(1){MyRTC_ReadTime();OLED_ShowNum(1, 6, MyRTC_Time[0], 4);OLED_ShowNum(1, 11, MyRTC_Time[1], 2);OLED_ShowNum(1, 14, MyRTC_Time[2], 2);OLED_ShowNum(2, 6, MyRTC_Time[3], 2);OLED_ShowNum(2, 9, MyRTC_Time[4], 2);OLED_ShowNum(2, 12, MyRTC_Time[5], 2);OLED_ShowNum(3, 6, RTC_GetCounter(), 10);//通过对余数寄存器线性变换得到毫秒OLED_ShowNum(4, 6, (32767 - RTC_GetDivider()) / 32767.0 * 999, 10);}
}
http://www.xdnf.cn/news/293635.html

相关文章:

  • cpp学习笔记1--class
  • CloudCompare 中 ccDrawableObject
  • 在Java中,什么是checked exception(即compile-time exception、编译时异常)?
  • 基于DGI框架的最佳实践
  • 数字化工厂中央控制室驾驶舱系统 - Windows 部署笔记
  • 计算机体系结构 第九章
  • 不小心把当前的环境变量路径覆盖掉怎么办
  • Gemini 解释蓝图节点的提示词
  • Lesson 15 Good news
  • 功率放大器设计
  • 大模型基础(五):transformers库(下):快速分词器、自动配置类、快速微调
  • pytorch checkpointing
  • 交换机工作原理(MAC地址表、VLAN)
  • P4168 [Violet] 蒲公英 Solution
  • 生物化学笔记:神经生物学概论10 运动节律的控制 运动时脑内活动 运动系统疾病及其治疗(帕金森、亨廷顿)
  • 【OSPF协议深度解析】从原理到企业级网络部署
  • 第15章:双星入侵与时间的迷雾
  • AIGC工具平台-图片转换线稿
  • 「OC」源码学习——对象的底层探索
  • 混搭文化数字社会学家解读,创新理解AI社会学网络社会学与数字人类学最新研究进展社会结构社会分层数字文化数字经济
  • 网络编程套接字(一)
  • PriorityQueue
  • 使用 Semantic Kernel 快速对接国产大模型实战指南(DeepSeek/Qwen/GLM)
  • Web前端开发:Grid 布局(网格布局)
  • ts学习(1)
  • 2024年408真题及答案
  • C++ 外观模式详解
  • php8 枚举使用教程
  • 稀疏性预测算法初步
  • 健康养生:从微小改变开始