【自学嵌入式:stm32单片机】实时时钟

目录
  • 实时时钟
    • 接线图
    • 代码实现
      • 标准库实现
        • MYRTC.h
        • MYRTC.c
        • main.c
      • HAL库实现
        • rtc.h
        • rtc.c
        • main.c
    • 实现效果

实时时钟

接线图

image
我使用的是硬件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启动成功,才使用LSERCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//使能时钟RCC_RTCCLKCmd(ENABLE);//等待同步,等待上一次写入操作完成//防止因为时钟不同步造成bugRTC_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 */

实现效果

image