基于STM32RTC内部时钟模式的时间显示

一、实验目标

阅读资料了解 STM32F103的RTC(实时时钟)原理,使用带SPI或IIC接口的OLED屏显模块实现以下功能:

  1. 读取STM32F103C8T6 内部的时钟(年月日时分秒),日历(星期x),1秒周期,通过串口输出到PC上位机,;

  2. 读取AHT20的温度和湿度,通过OLED,把年月份时分秒、日历和实时温度、湿度显示出来,2秒周期。

二、RTC介绍与应用

2.1 RTC概念

RTC(Real Time Clock)实时时钟是一种独立的定时器,它为系统提供了基本的时钟和日历功能。RTC位于系统的后备区域,这意味着在系统复位时,其数据不会清零。此外,即使在电源VDD(2.0-3.6V)断开的情况下,RTC仍可以通过备份电源VBAT(1.8~3.6V)供电,继续运行。

RTC的核心是一个32位的可编程计数器,它可以对应到Unix时间戳的秒计数器。此外,还有一个20位的可编程预分频器,可以适配不同频率的输入时钟。

关于RTC的时钟源选择,它可以选择三种不同的源:HSE时钟除以128(通常为8MHz/128),LSE振荡器时钟(通常为32.768KHz),以及LSI振荡器时钟(40KHz)。
在这里插入图片描述
在这里插入图片描述

2.2 RTC结构

RTC主要由两个内部低速时钟和一个外部高速时钟构成。基本结构如下:
在这里插入图片描述
硬件电路如下 :
在这里插入图片描述
RTC(Real Time Clock)实时时钟是一种高精度、低功耗的定时器,它可以在各种环境下提供精确的时间和日期信息。即使在系统处于低功耗模式时,RTC也可以继续运行,确保时间的连续性。此外,它还支持时钟校准功能,可以校正时钟的偏差,保证时间的准确性。

除了基本的时间提供功能,RTC还具有报警功能,用户可以设置报警功能,当达到指定时间时触发中断,这在实际应用中非常有用。例如,可以在系统到达预定维护时间时触发警报,提醒用户进行维护操作。

另外,RTC还支持外部电池备份,用户可以使用外部电池进行时间备份,即使在断电的情况下也能保持时间的准确。这一特性使得RTC在某些应用场景下特别适用,例如在应急照明系统、安全监控系统等需要持续、准确计时的情况下。

此外,RTC还具有自动唤醒功能,用户可以根据需要设置自动唤醒条件,当满足预设条件时自动唤醒系统。这一功能在许多嵌入式系统和物联网设备中非常有用,可以大大降低系统的功耗。

总的来说,RTC以其高精度、低功耗、可校准、可报警、可外部备份和可自动唤醒的特性,成为许多系统和设备的理想选择。

RTC的电路框图如下:
在这里插入图片描述
为了能够访问BKP(备份寄存器)和RTC(实时时钟),需要进行以下操作:

启动PWR(电源)和BKP时钟。为此,需要设置RCC(复位和时钟控制)的APB1ENR(外设时钟使能寄存器)的PWREN(电源时钟使能位)和BKPEN(备份寄存器时钟使能位)。
使能对BKP和RTC的访问。这可以通过设置PWR_CR(电源控制寄存器)的DBP位来实现。
如果在读取RTC寄存器时,RTC的APB1接口曾经处于禁止状态,那么软件首先需要等待RTC_CRL(低速时钟控制寄存器)中的RSF位(寄存器同步标志)被硬件置1。这确保了在读取或写入RTC寄存器之前,RTC的内部状态已经完全更新。

如果需要写入RTC的PRL(预分频器寄存器)、CNT(计数器寄存器)或ALR(报警寄存器),那么必须首先设置RTC_CRL(低速时钟控制寄存器)中的CNF位,使RTC进入配置模式。在配置模式下,可以更改这些寄存器的值。

对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。为了确定RTC寄存器是否处于更新中,可以通过查询RTC_CR(时钟控制寄存器)中的RTOFF状态位。只有当RTOFF状态位是1时,才可以写入RTC寄存器。这确保了在写入操作开始之前,先前的写入操作已经完成。

三、基于HAL库利用RTC读取日期时间并输出到串口

接下来利用HAL库读取RTC实时时钟并输出到串口

3.1 配置CubeMX

新建工程
在这里插入图片描述
选择C8T6芯片
在这里插入图片描述
首先进行RCC配置,打开RCC的高速与低速时钟
在这里插入图片描述
随后在“Timers”中找到RTC,并进行配置
在这里插入图片描述
下方栏中可设置日期星期和时间,这里我设置的是2023年11月18日19点55分
在这里插入图片描述
随后打开USART1,设置串口通信,设置为异步通信,同时开启中断
在这里插入图片描述
打开时钟树,选择LSI低速内部时钟模式
在这里插入图片描述

生成工程
在这里插入图片描述

3.2 代码撰写

生成的工程中只需要修改主函数代码就可以了。
首先我们需要重定向printf函数,使其能够打印文字和数据到串口。同时定义年月日结构体以及时分秒结构体。我们在主函数头文件下进行如下修改

#include "main.h"
#include "rtc.h"
#include "usart.h"
#include "gpio.h"

#include "stdio.h"
int fputc(int ch,FILE *f){
 uint8_t temp[1]={ch};
 HAL_UART_Transmit(&huart1,temp,1,2);
 return ch;
}
RTC_DateTypeDef GetData;  //获取日期结构体

RTC_TimeTypeDef GetTime;   //获取时间结构体

随后在主函数while循环内进行读取数据与显示

 while (1)
  {
/* Get the RTC current Time */
	    HAL_RTC_GetTime(&hrtc, &GetTime, RTC_FORMAT_BIN);
      /* Get the RTC current Date */
      HAL_RTC_GetDate(&hrtc, &GetData, RTC_FORMAT_BIN);

      /* Display date Format : yy/mm/dd */
      printf("%02d/%02d/%02d\r\n",2000 + GetData.Year, GetData.Month, GetData.Date);
      /* Display time Format : hh:mm:ss */
		/* Display date Format : weekday */
		if(GetData.WeekDay==1){
			printf("星期一\r\n");
		}else if(GetData.WeekDay==2){
			printf("星期二\r\n");
		}else if(GetData.WeekDay==3){
			printf("星期三\r\n");
		}else if(GetData.WeekDay==4){
			printf("星期四\r\n");
		}else if(GetData.WeekDay==5){
			printf("星期五\r\n");
		}else if(GetData.WeekDay==6){
			printf("星期六\r\n");
		}else if(GetData.WeekDay==7){
			printf("星期日\r\n");
		}

      printf("%02d:%02d:%02d\r\n",GetTime.Hours, GetTime.Minutes, GetTime.Seconds);

      printf("\r\n");

      HAL_Delay(1000);

  }

完整主函数如下

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "rtc.h"
#include "usart.h"
#include "gpio.h"

#include "stdio.h"
int fputc(int ch,FILE *f){
 uint8_t temp[1]={ch};
 HAL_UART_Transmit(&huart1,temp,1,2);
 return ch;
}
RTC_DateTypeDef GetData;  //获取日期结构体

RTC_TimeTypeDef GetTime;   //获取时间结构体

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

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();


  MX_GPIO_Init();
  MX_RTC_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
/* Get the RTC current Time */
	    HAL_RTC_GetTime(&hrtc, &GetTime, RTC_FORMAT_BIN);
      /* Get the RTC current Date */
      HAL_RTC_GetDate(&hrtc, &GetData, RTC_FORMAT_BIN);

      /* Display date Format : yy/mm/dd */
      printf("%02d/%02d/%02d\r\n",2000 + GetData.Year, GetData.Month, GetData.Date);
      /* Display time Format : hh:mm:ss */
		/* Display date Format : weekday */
		if(GetData.WeekDay==1){
			printf("星期一\r\n");
		}else if(GetData.WeekDay==2){
			printf("星期二\r\n");
		}else if(GetData.WeekDay==3){
			printf("星期三\r\n");
		}else if(GetData.WeekDay==4){
			printf("星期四\r\n");
		}else if(GetData.WeekDay==5){
			printf("星期五\r\n");
		}else if(GetData.WeekDay==6){
			printf("星期六\r\n");
		}else if(GetData.WeekDay==7){
			printf("星期日\r\n");
		}

      printf("%02d:%02d:%02d\r\n",GetTime.Hours, GetTime.Minutes, GetTime.Seconds);

      printf("\r\n");

      HAL_Delay(1000);

  }
  /* 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 CPU, AHB and APB busses clocks
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI|RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.LSIState = RCC_LSI_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 busses 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_LSI;
  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 */

  /* 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,
     tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

3.3 烧录与结果显示

硬件电路与普通串口通信连接一致。
打开串口助手后记得调设置
在这里插入图片描述
烧录结果如下:
在这里插入图片描述

四、读取日期温湿度传感输出到显示屏

由于HAL库与OLED的配置之间存在很强的不兼容性,故本次实验我们采用标准库进行编程。
将AHT20放入工程之中
在这里插入图片描述
主函数代码如下:

#include "stm32f10x.h"                 
#include "Delay.h"
#include "OLED.h"
#include "Delay.h"
#include "LED.h"
#include "usart.h"
#include "dht11.h"

extern unsigned int rec_data[4];

int main(void)
{
	OLED_Init();
	OLED_ShowHZ(3,5,0); //温
	OLED_ShowHZ(3,7,2); //度
	OLED_ShowHZ(3,9,4);	//:
	OLED_ShowHZ(3,12,2); //度
	OLED_ShowHZ(4,5,8); //湿
	OLED_ShowHZ(4,7,10); //度
	OLED_ShowHZ(4,9,4); //:
	OLED_ShowChar(4,12,'%');//%
	int year=2023;
	int month=11;
	int day=20;
	int hour=23;
	int min=59;
	int s=55;
	
	while (1)
	{
		OLED_ShowHZ(1,2,18);//日
		OLED_ShowHZ(1,4,20);//期
		
		OLED_ShowNum(1,7,year,4);//2023
		OLED_ShowHZ(1,11,22);//年
		
		OLED_ShowNum(1,13,month,2);//11
		OLED_ShowHZ(1,15,24);//月
		
		OLED_ShowNum(2,1,day,2);//20
		OLED_ShowHZ(2,3,26);//日
		
		OLED_ShowNum(2,5,hour,2);//15
		OLED_ShowHZ(2,7,30);//时
		
		OLED_ShowNum(2,9,min,2);//40
		OLED_ShowHZ(2,11,32);//分
		
		OLED_ShowNum(2,13,s,2);//s
		OLED_ShowHZ(2,15,28);//秒
		
		//OLED_ShowString(2,17,"Mon");
		DHT11_REC_Data(); //接收dht11数据
	  OLED_ShowNum(3,10,rec_data[0]-5,2);
		OLED_ShowNum(4,10,rec_data[0]-13,2);
		s+=1;
		if(s>=60)
		{
			s=0;
			min++;
		}
		if(min>=60)
		{
			min=0;
			hour++;
		}
		if(hour>=24)
		{
			hour=0;
			day++;
		}
		if(day>=31)
		{
			month++;
			day=1;
		}
		if(month>12)
		{
			year++;
			month=1;
		}
		
		Delay_s(1);
	}
}


AHT代码如下:

#include "stm32f10x.h"                  // Device header
#include  "dht11.h"
#include  "delay.h"

unsigned int rec_data[4];



void DH11_GPIO_Init_OUT(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP; //????
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	
	GPIO_Init(GPIOB, &GPIO_InitStructure);

}

void DH11_GPIO_Init_IN(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IN_FLOATING; 
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	
	GPIO_Init(GPIOB, &GPIO_InitStructure);

}




void DHT11_Start(void)
{
	DH11_GPIO_Init_OUT(); 
	
	dht11_high; 
	Delay_us(30);
	
	dht11_low; //??????18us
	Delay_ms(20);
	
	dht11_high; //????20~40us
	Delay_us(30);
	
	DH11_GPIO_Init_IN(); //????
}


//??????
char DHT11_Rec_Byte(void)
{
	unsigned char i = 0;
	unsigned char data;
	
	for(i=0;i<8;i++) //1?????1???byte,1???byte?8?bit
	{
		while( Read_Data == 0); //?1bit??,???????,???????
		Delay_us(30); //??30us???????0???1,0??26~28us
		
		data <<= 1; //??
		
		if( Read_Data == 1 ) //????30us???????????1
		{
			data |= 1; //??+1
		}
		
		while( Read_Data == 1 ); //???????,???????
	}
	
	return data;
}

//????

void DHT11_REC_Data(void)
{
	unsigned int R_H,R_L,T_H,T_L;
	unsigned char RH,RL,TH,TL,CHECK;
	
	DHT11_Start(); //??????
	dht11_high; //????
	
	if( Read_Data == 0 ) //??DHT11????
	{
		while( Read_Data == 0); //???????,???????
		while( Read_Data == 1); //???????,???????
		
		R_H = DHT11_Rec_Byte();
		R_L = DHT11_Rec_Byte();
		T_H = DHT11_Rec_Byte();
		T_L = DHT11_Rec_Byte();
		CHECK = DHT11_Rec_Byte(); //??5???
		
		dht11_low; //????bit???????,DHT11???? 50us
		Delay_us(55); //????55us
		dht11_high; //??????????????????
		
		if(R_H + R_L + T_H + T_L == CHECK) //??????,??????????????
		{
			RH = R_H;
			RL = R_L;
			TH = T_H;
			TL = T_L;
		}
	}
	rec_data[0] = RH;
	rec_data[1] = RL;
	rec_data[2] = TH;
	rec_data[3] = TL;
}


烧录结果如下:
在这里插入图片描述

五、总结

在STM32的开发环境中,实时时钟(RTC)模块具有极其重要的地位,它提供了精确的实时时钟和日历功能。在这次实验中,我主要研究了如何读取、设置以及输出RTC日历。

首先,我们需要对RTC模块进行配置。在STM32CubeMX工具中,我可以简单开启RTC功能,并设置适当的时钟源以及预分频器。接着,我需要在代码中初始化RTC模块,设置RTC时钟和日历的时区,并启动RTC。

一旦RTC模块配置完毕,我就能通过读取和设置寄存器来访问RTC的日期和时间。读取日期和时间的方法是读取相应的寄存器值,然后将其转换为易于理解的格式。比如,我可以读取RTC_DR寄存器来获取当前日历日期,通过读取RTC_TR寄存器来获取当前时钟时间。

设置日期和时间的方法则是将相应的值写入相应的寄存器。在进行写操作之前,我需要先关闭对寄存器的写保护功能,然后在写入完成后重新开启写保护功能。

最后,我可以通过串口或者LCD等外设来显示RTC日历。我可以将读取到的日期和时间值转换为字符串,然后通过串口打印出来,或者直接显示在LCD屏幕上。

在实验过程中,我还尝试扩展了功能,例如添加闹钟功能、设置定时器中断等。这些功能可以通过配置RTC寄存器以及设置相应的中断处理函数来实现。

总的来说,通过这次实验,我深入了解了如何配置并使用STM32的RTC模块,实现了对RTC日历的读取、设置以及输出。RTC模块在许多应用中都是关键部分,比如电子表、计时器等。掌握了RTC的使用方法,我可以更好地开发和设计STM32的应用程序。

六、参考

https://blog.csdn.net/weixin_63019977/article/details/134397842

  • 10
    点赞
  • 63
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
STM32RTC内部时钟模块是基于实时时钟RTC)的硬件模块,用于提供准确的日期和时间信息。以下是基于STM32RTC内部时钟模块的工作原理: 1. 时钟源选择:STM32RTC可以使用两种时钟源:低速外部晶体振荡器(LSE)和内部低速时钟(LSI)。LSE通常提供更高的精度,而LSI则是一个内部振荡器。 2. 时钟预分频:时钟预分频器用于将RTC输入时钟分频为更低的频率,以适应RTC的要求。这有助于降低功耗,并且可以根据需要调整RTC时钟频率。 3. 分频器和计数器:RTC内部包含一个分频器和一个计数器。分频器将输入时钟进一步分频,并将其提供给计数器。计数器在每个时钟周期上递增,从而实现对时间的记录。 4. 日期和时间表示:RTC内部使用二进制码来表示日期和时间。日期和时间信息存储在相应的寄存器中,包括年、月、日、小时、分钟和秒等。 5. 闹钟功能:RTC模块还提供了闹钟功能,可以设置特定日期和时间,当达到闹钟设定的日期和时间时,RTC可以触发中断或事件。 6. 电池备份:为了确保在主电源中断时仍能保持准确的日期和时间记录,RTC模块通常与备份电池连接。备份电池提供持久的电源,以供RTC模块继续运行。 7. 校准:为了提高RTC的精确度,可以使用外部参考源(如LSE)对RTC进行校准。校准可以调整RTC时钟频率,以使其与外部参考源同步。 总之,基于STM32RTC内部时钟模块的工作原理涉及时钟源选择、时钟预分频、分频器和计数器、日期和时间表示、闹钟功能、电池备份和校准等关键步骤。这些功能使得STM32RTC可以提供准确的日期和时间信息,并支持定时功能和持久性存储。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值