一、实验目标
阅读资料了解 STM32F103的RTC(实时时钟)原理,使用带SPI或IIC接口的OLED屏显模块实现以下功能:
-
读取STM32F103C8T6 内部的时钟(年月日时分秒),日历(星期x),1秒周期,通过串口输出到PC上位机,;
-
读取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>© 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