STM32HAL库实现数码管显示日期时间

本文档创建于2023年3月12日

本文记录了用STM32HAL库开发实现数码管显示当前日期与时间的方法,按键切换日期和时间的显示。

作者:RobotFreak

硬件

  • 正点原子NANO STM32F103RCT6实验板
  • USB转Type-C

软件

获取实时时间

NANO板没有连接wifi模块,无法通过网络获取时间,只能通过自己的设置和内部的时钟实现得到当前时间。

一开始参考的一篇博客使用<stdio.h>中宏定义的__DATE__和__TIME__,但用<stdio.h>宏定义的__DATE__和__TIME__只能获取把程序下载进去板子的时候的时间,无法实时更新。

看了一下正点原子的例程,RTC的例程就是显示实时时间的,正点原子的开发参考手册中重点讲解了RTC的寄存器和原理,对CubeMX中RTC的配置没有设置,参考了这篇博客进行CubeMX中RTC的配置:【STM32】HAL库 STM32CubeMX教程十三—RTC时钟_stm32 rtc_Z小旋的博客-CSDN博客

在CubeMX中设定了RTC初始时间后,下载进去之后就从CubeMX中设置的初始时间开始,复位或重新下载都会回到CubeMX中设定的初始值。

这里我得到当前时间采取的方法是在CubeMX中设定初始时间,然后通过串口修改时间和日期。

首先进行RTC的CubeMX配置:

在这里插入图片描述

设置了USART1作为串口进行通讯:

在这里插入图片描述

NVIC中使能中断:

在这里插入图片描述

这里我们使用串口进行时间和日期的修改,通过串口输入特定格式的数据来修改数据。具体来说,通过串口接收数据,在接收中断中处理数据,将特定格式的数据转换为日期或时间,并调用HAL库的RTC时间/日期设定函数。

串口接收中断回调函数如下:(串口代码参考了这个博客:(12条消息) 【STM32】HAL库 STM32CubeMX教程四—UART串口通信详解_hal_uart_transmit详解_Z小旋的博客-CSDN博客

#include "rtc.h"
#include <string.h>
#define RV_BUFFER_SIZE 256
char rvBuffer[RV_BUFFER_SIZE];
uint8_t aRvBuffer;
uint8_t rvCount;

uint8_t convertChar2Num(uint8_t ch)
{
    
    
	switch(ch)
	{
    
    
		case '0':
			return 0;
		case '1':
			return 1;
		case '2':
			return 2;
		case '3':
			return 3;
		case '4':
			return 4;
		case '5':
			return 5;
		case '6':
			return 6;
		case '7':
			return 7;
		case '8':
			return 8;
		case '9':
			return 9;
	}
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    
    
	if(rvCount >= 255)  //溢出判断
	{
    
    
		rvCount = 0;
		memset(rvBuffer,0x00,sizeof(rvBuffer));
		//HAL_UART_Transmit(&huart1, (uint8_t *)"数据溢出", 10,0xFFFF); 	
		printf("数据溢出\r\n");
        
	}
	else
	{
    
    
		rvBuffer[rvCount++] = aRvBuffer;   //接收数据转存
	
		if((rvBuffer[rvCount-1] == 0x0A)&&(rvBuffer[rvCount-2] == 0x0D)) //判断结束位
		{
    
    
			//printf("%s",rvBuffer); //发送过来的数据就有换行,直接原样发送回去就有换行
			if(rvBuffer[0] == 't') //修改时间time
			{
    
    
				RTC_TimeTypeDef tempTime;
				tempTime.Hours = convertChar2Num(rvBuffer[1])*10 + convertChar2Num(rvBuffer[2]); //小时
				tempTime.Minutes = convertChar2Num(rvBuffer[3]) * 10 + convertChar2Num(rvBuffer[4]); //分钟
				tempTime.Seconds = convertChar2Num(rvBuffer[5])*10 + convertChar2Num(rvBuffer[6]); //秒
				HAL_RTC_SetTime(&hrtc, &tempTime, RTC_FORMAT_BIN); //CubeMX中设置时用BCD,设置出来就是正确的时间.在代码中设置和读取时用BIN格式,BCD格式在代码中设置或读取需要遵循BCD的规则,但平常都是按照二进制的规则来的,所以用BIN更方便.
			}
			else if(rvBuffer[0] == 'd') //修改日期date
			{
    
    
				RTC_DateTypeDef tempDate;
				tempDate.Year = convertChar2Num(rvBuffer[3])*10 + convertChar2Num(rvBuffer[4]); //年,去除了百位和千位
				tempDate.Month = convertChar2Num(rvBuffer[5])*10 + convertChar2Num(rvBuffer[6]); //月
				tempDate.Date = convertChar2Num(rvBuffer[7])*10 + convertChar2Num(rvBuffer[8]); //日
				HAL_RTC_SetDate(&hrtc, &tempDate, RTC_FORMAT_BIN);
			}
			//printf("%d\r\n",rvBuffer[2]);
      while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);//检测UART发送结束
			rvCount = 0;
			memset(rvBuffer,0x00,sizeof(rvBuffer)); //清空数组
		}
	}
	
	HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRvBuffer, 1);   //再开启接收中断(在main中调用了一次中断接收)
}

设定的格式是d/t + 日期/时间。d表示date,修改日期;t表示time,修改时间。

例如,如果我想将日期改为2023年6月12日,则串口输入给单片机的数据为"d20230612";如果我想将时间修改为晚上8点(20点)6分20秒,则输入的数据为"t200620"。

效果如下:
在这里插入图片描述

数码管显示时间与日期

数码管的基本使用参考这篇博客:(5条消息) STM32HAL库驱动数码管_RobotFreak的博客-CSDN博客

数码管的驱动我直接复用了博客里面的库。

数码管显示时间与日期的思路如下:

在主循环中进行操作

  1. 获取RTC当前时间或日期
  2. 从时间/日期中得到数码管当前位需要的数字
  3. 将数字写入数码管对应位
  4. 数码管位++,并限制在0~7之间
  5. 延时1ms

由于两次点亮同一个数码管间隔非常短,所以看起来是一直亮的。

main()函数内容如下:

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_RTC_Init();
  MX_TIM2_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
	RTC_TimeTypeDef time;
	RTC_DateTypeDef date;
	displayMode = 0; //显示模式,0为显示日期,1为显示时间
	smgBit = 0;
	smgSeg = 0; //段码索引
	uint8_t num; //数字的段码
	uint16_t temp;
	aRvBuffer = 0;
	rvCount = 0;
	HAL_UART_Receive_IT(&huart1, (uint8_t*)&aRvBuffer, 1); //定长接收,接收的数据不够长度的话无法进入中断

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    
    
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		//HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BCD); //MX中设置时间用BCD格式,因为BIN格式(binary)有bug.读取时用BCD格式读取出来与设置的相差6h,比如设置的17:00:00,BCD读取出来是23:00:00
		HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BIN);
		HAL_RTC_GetDate(&hrtc, &date, RTC_FORMAT_BIN);
		if(displayMode) //显示time
		{
    
    
			switch(smgBit)
			{
    
    
				case 0:
					smgSeg = time.Hours / 10; //时的十位
					num = smgNum[smgSeg];
					break;
				case 1:
					smgSeg = time.Hours % 10; //时的个位
					num = smgNum[smgSeg];
					break;
				case 2:
				case 5:
					num = 0x02;
					break;
				case 3:
					smgSeg = time.Minutes / 10; //分的十位
					num = smgNum[smgSeg];
					break;
				case 4:
					smgSeg = time.Minutes % 10; //分的个位
					num = smgNum[smgSeg];
					break;
				case 6:
					smgSeg = time.Seconds / 10; //秒的十位
					num = smgNum[smgSeg];
					break;
				case 7:
					smgSeg = time.Seconds % 10;
					num = smgNum[smgSeg];
					break;
				
			}
			
		}
		else //显示日期
		{
    
    
			switch(smgBit)
			{
    
    
				case 0:
					smgSeg = (date.Year+2000) / 1000; //年的千位
					num = smgNum[smgSeg]; //得到段码
					break;
				case 1:
					temp = date.Year + 2000;
					temp /= 100; //去掉最低两位
					smgSeg = temp % 10; //年的百位
					num = smgNum[smgSeg];
					break;
				case 2:
					temp = date.Year + 2000;
					temp /= 10;
					smgSeg = temp % 10; //年的十位
					num = smgNum[smgSeg];
					break;
				case 3:
					smgSeg = (date.Year+2000) % 10; //年的个位
					num = smgNum[smgSeg];
					break;
				case 4:
					smgSeg = date.Month / 10; //月的十位
					num = smgNum[smgSeg];
					break;
				case 5:
					smgSeg = date.Month % 10; //月的个位
					num = smgNum[smgSeg];
					break;
				case 6:
					smgSeg = date.Date / 10; //日的十位
					num = smgNum[smgSeg];
					break;
				case 7:
					smgSeg = date.Date % 10; //日的个位
					num = smgNum[smgSeg];
					break;
				
			}
		}
		writeData2ShiftRegister(num, smgBit);
		writeData2DigitalTube();
		smgBit++;
		if(smgBit == 8) smgBit = 0;
		HAL_Delay(1);
  }
  /* USER CODE END 3 */
}

除此之外,由于NANO的数码管只有8位的限制,我选择只显示时间/日期,通过按键进行切换,在KEY0的外部中断中写如下内容:

uint8_t displayMode;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    
    
	delayNus(10000); //10ms延时
	switch(GPIO_Pin)
	{
    
    
		case GPIO_PIN_8:
			if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_8) == GPIO_PIN_RESET) //确实按下
			{
    
    
				displayMode = !displayMode; //显示模式切换
			}
			break;
	}
}

这样通过按下按键,就可以实现数码管显示时间/日期的切换。效果如下:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/svfsvadfv/article/details/129539898