【自学嵌入式:stm32单片机】实时时钟
实时时钟
接线图
我使用的是硬件I2C,但是我和江科大的代码有所不同,我的代码在读RTC的时候,有一个等待同步的过程,如果不等待同步,我这里就会出现stm32死机现象
这个代码折腾了一大天,太痛苦了,太折磨了
代码实现
标准库实现
已开源到:https://gitee.com/qin-ruiqian/jiangkeda-stm32
MYRTC.h
#ifndef __MYRTC_H
#define __MYRTC_H
#include <time.h>
extern struct tm time_date;
void MYRTC_Init(void);
void MYRTC_SetTime(void);
void MYRTC_ReadTime(void);
#endif
MYRTC.c
#include "stm32f10x.h" // Device header
#include "MYRTC.h"
//#include <time.h>
//#include "Serial.h"
struct tm time_date = {
0, // tm_sec: 秒
24, // tm_min: 分
3, // tm_hour: 时
30, // tm_mday: 日
8 - 1, // tm_mon: 月
2025 - 1900, // tm_year: 年
0, // tm_wday: 星期(0代表周日,可留空)
0, // tm_yday: 年内天数(可留空)
0 // tm_isdst: 不使用夏令时
// 联合体成员无需手动赋值,系统自动处理
};
//初始化RTC
void MYRTC_Init(void)
{
//开启PWR和BKP时钟,使能BKP和RTC的访问
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); //开启PWR时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE); //开启BKP时钟
PWR_BackupAccessCmd(ENABLE); //使能对BKP和RTC的访问
//如果备份寄存器没清零,说明之前初始化过,不用初始化,否则初始化,随便指定一个当标志位
if(BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)
{
//开启LSE时钟,并等待LSE时钟启动完成
RCC_LSEConfig(RCC_LSE_ON); //启动外部晶振
//等待RCC LSE标志位准备完成
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);
//选择RCC_CLK时钟源
// 如果LSE启动成功,才使用LSE
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
//使能时钟
RCC_RTCCLKCmd(ENABLE);
//等待同步,等待上一次写入操作完成
//防止因为时钟不同步造成bug
RTC_WaitForSynchro();
RTC_WaitForLastTask();
//设置预分频器
//LSE频率是32.768KHz,也就是32768Hz
//进行32768分频就是1Hz,但是要给32768-1,因为计数值从0开始
// 根据实际时钟源设置预分频器(LSI通常是40kHz,需要重新计算)
//进入配置模式的代码,也自带退出配置模式
RTC_SetPrescaler(32768 - 1);
//等待一下写操作完成
RTC_WaitForLastTask();
//设置初始时间
MYRTC_SetTime();
BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);
}
else
{
RTC_WaitForSynchro();
RTC_WaitForLastTask();
}
}
//设置时间
void MYRTC_SetTime(void)
{
time_t time_cnt;
time_cnt = mktime(&time_date) - 8 * 60 * 60; //加东8区时间偏移,北京时间
RTC_SetCounter(time_cnt);
RTC_WaitForLastTask();
}
//读取时间
void MYRTC_ReadTime(void)
{
RTC_WaitForSynchro(); //等待同步
RTC_WaitForLastTask(); //等待上一次操作完成
//我的电脑,加了这个等APB1与RTC的同步才正常工作,江科大的代码没加
time_t time_cnt;
time_cnt = RTC_GetCounter() + 8 * 60 * 60; //加东8区时间偏移,北京时间
time_date = *localtime(&time_cnt);
time_date.tm_year += 1900;
time_date.tm_mon++;
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "MYOLED.h"
#include "MYRTC.h"
//#include "Serial.h"
//uint16_t y = 1234;
int main(void)
{
MYOLED_Init();
MYRTC_Init();
MYOLED_ShowString(0,0,"Date:XXXX-XX-XX");
MYOLED_ShowString(0,1,"Time:XX:XX:XX");
MYOLED_ShowString(0,2,"CNT :");
MYOLED_ShowString(0,3,"DIV :");
while (1)
{
MYRTC_ReadTime();
MYOLED_ShowNum(5,0, time_date.tm_year, 4);
MYOLED_ShowNum(10,0, time_date.tm_mon, 2);
MYOLED_ShowNum(13,0, time_date.tm_mday, 2);
MYOLED_ShowNum(5,1, time_date.tm_hour, 2);
MYOLED_ShowNum(8,1, time_date.tm_min, 2);
MYOLED_ShowNum(11,1, time_date.tm_sec, 2);
MYOLED_ShowNum(5,2, RTC_GetCounter(), 10);
MYOLED_ShowNum(5,3, RTC_GetDivider(), 10);
}
}
HAL库实现
已开源到:https://gitee.com/qin-ruiqian/jiangkeda-stm32-hal
HAL库实现参考了:https://blog.csdn.net/weixin_45682654/article/details/136591610
我用的unix时间戳实现,并魔改了自动生成的rtc模块代码,因为HAL库自带的库函数只能存储时分秒,不能存储年月日,只能自己造相关函数
rtc.h
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file rtc.h
* @brief This file contains all the function prototypes for
* the rtc.c file
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __RTC_H__
#define __RTC_H__
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include <time.h>
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
extern RTC_HandleTypeDef hrtc;
/* USER CODE BEGIN Private defines */
extern struct tm time_date;
/* USER CODE END Private defines */
void MX_RTC_Init(void);
/* USER CODE BEGIN Prototypes */
void RTC_INIT_USER(void);
void RTC_Set(void);
void RTC_Get(void);
uint32_t RTC_GetUnixTime(void);
uint32_t RTC_GetDIV(void);
/* USER CODE END Prototypes */
#ifdef __cplusplus
}
#endif
#endif /* __RTC_H__ */
rtc.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file rtc.c
* @brief This file provides code for the configuration
* of the RTC instances.
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "rtc.h"
/* USER CODE BEGIN 0 */
#include <time.h>
struct tm time_date = {
0, // tm_sec: 秒
24, // tm_min: 分
3, // tm_hour: 时
30, // tm_mday: 日
8 - 1, // tm_mon: 月
2025 - 1900, // tm_year: 年
0, // tm_wday: 星期(0代表周日,可留空)
0, // tm_yday: 年内天数(可留空)
0 // tm_isdst: 不使用夏令时
// 联合体成员无需手动赋值,系统自动处理
};
time_t time_cnt;
//向RTC CNT寄存器里写入时间
void RTC_Set(void)
{
//time_t time_cnt;
time_cnt = mktime(&time_date) - 8 * 60 * 60; //加东8区时间偏移,北京时间
// 设置时钟
RCC->APB1ENR |= 1 << 28; // 使能电源时钟
RCC->APB1ENR |= 1 << 27; // 使能备份时钟
PWR->CR |= 1 << 8; // 取消备份区写保护
// 上面三步是必须的!
RTC->CRL |= 1 << 4; // 允许配置
RTC->CNTL = time_cnt & 0xffff;
RTC->CNTH = time_cnt >> 16;
RTC->CRL &= ~(1 << 4); // 配置更新
while (!(RTC->CRL & (1 << 5))); // 等待RTC寄存器操作完成
RTC_Get();
}
//获取RTC CNT寄存器里的时间
void RTC_Get(void)
{
//time_t time_cnt;
time_cnt = RTC->CNTH; // 得到计数器中的值(unix时间戳秒钟数)
time_cnt <<= 16;
time_cnt += RTC->CNTL;
time_cnt = time_cnt + 8 * 60 * 60; //加东8区时间偏移,北京时间
time_date = *localtime(&time_cnt);
time_date.tm_year += 1900;
time_date.tm_mon++;
}
//获得Unix时间戳
uint32_t RTC_GetUnixTime(void)
{
return time_cnt;
}
//获取DIV寄存器的值
uint32_t RTC_GetDIV(void)
{
uint32_t DIV_value = 0;
DIV_value = RTC->DIVH;
DIV_value <<= 16;
DIV_value += RTC->DIVL;
return DIV_value;
}
/* USER CODE END 0 */
RTC_HandleTypeDef hrtc;
/* RTC init function */
void MX_RTC_Init(void)
{
/* USER CODE BEGIN RTC_Init 0 */
//如果之前初始化了,直接退出函数
//HAL库只是把时间(时分秒)写入寄存器RTC_CNT,但是日期(年月日)没有存入寄存器
//所以最后年月日写入备份寄存器
if(HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1) == 0xA5A5)
{
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
if (HAL_RTC_Init(&hrtc) != HAL_OK)
{
Error_Handler();
}
return;
}
/* USER CODE END RTC_Init 0 */
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef DateToUpdate = {0};
/* USER CODE BEGIN RTC_Init 1 */
// __HAL_RCC_BKP_CLK_ENABLE(); //开启后备区域时钟
// __HAL_RCC_PWR_CLK_ENABLE(); //开启电源时钟
/* USER CODE END RTC_Init 1 */
/** Initialize RTC Only
*/
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_ALARM;
if (HAL_RTC_Init(&hrtc) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN Check_RTC_BKUP */
/* USER CODE END Check_RTC_BKUP */
/** Initialize RTC and set the Time and Date
*/
sTime.Hours = 3;
sTime.Minutes = 24;
sTime.Seconds = 0;
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
{
Error_Handler();
}
DateToUpdate.WeekDay = RTC_WEEKDAY_MONDAY;
DateToUpdate.Month = RTC_MONTH_AUGUST;
DateToUpdate.Date = 30;
DateToUpdate.Year = 25;
if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN RTC_Init 2 */
//初始化过了,让备份寄存器存储0x5A5A,表示初始化过了
RTC_Set();
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0xA5A5);
/* USER CODE END RTC_Init 2 */
}
void HAL_RTC_MspInit(RTC_HandleTypeDef* rtcHandle)
{
if(rtcHandle->Instance==RTC)
{
/* USER CODE BEGIN RTC_MspInit 0 */
/* USER CODE END RTC_MspInit 0 */
HAL_PWR_EnableBkUpAccess();
/* Enable BKP CLK enable for backup registers */
__HAL_RCC_BKP_CLK_ENABLE();
/* RTC clock enable */
__HAL_RCC_RTC_ENABLE();
/* USER CODE BEGIN RTC_MspInit 1 */
/* USER CODE END RTC_MspInit 1 */
}
}
void HAL_RTC_MspDeInit(RTC_HandleTypeDef* rtcHandle)
{
if(rtcHandle->Instance==RTC)
{
/* USER CODE BEGIN RTC_MspDeInit 0 */
/* USER CODE END RTC_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_RTC_DISABLE();
/* USER CODE BEGIN RTC_MspDeInit 1 */
/* USER CODE END RTC_MspDeInit 1 */
}
}
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
main.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "i2c.h"
#include "rtc.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "MYOLED.h"
#include "Key.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_I2C2_Init();
MX_RTC_Init();
/* USER CODE BEGIN 2 */
MYOLED_SetI2CHandleBeforeInit(&hi2c2);
MYOLED_Init();
MYOLED_ShowString(0,0,"Date:XXXX-XX-XX");
MYOLED_ShowString(0,1,"Time:XX:XX:XX");
MYOLED_ShowString(0,2,"CNT :");
MYOLED_ShowString(0,3,"DIV :");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
RTC_Get();
MYOLED_ShowNum(5,0, time_date.tm_year, 4);
MYOLED_ShowNum(10,0, time_date.tm_mon, 2);
MYOLED_ShowNum(13,0, time_date.tm_mday, 2);
MYOLED_ShowNum(5,1, time_date.tm_hour, 2);
MYOLED_ShowNum(8,1, time_date.tm_min, 2);
MYOLED_ShowNum(11,1, time_date.tm_sec, 2);
MYOLED_ShowNum(5,2, RTC_GetUnixTime(), 10);
MYOLED_ShowNum(5,3, RTC_GetDIV(), 10);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE|RCC_OSCILLATORTYPE_LSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.LSEState = RCC_LSE_ON;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RTC;
PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */