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

实时时钟

接线图

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启动成功,才使用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 */

实现效果

image

posted @ 2025-08-30 23:54  秦瑞迁  阅读(3)  评论(0)    收藏  举报