本文档创建于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小旋的博客-优快云博客
在CubeMX中设定了RTC初始时间后,下载进去之后就从CubeMX中设置的初始时间开始,复位或重新下载都会回到CubeMX中设定的初始值。
这里我得到当前时间采取的方法是在CubeMX中设定初始时间,然后通过串口修改时间和日期。
首先进行RTC的CubeMX配置:
设置了USART1作为串口进行通讯:
NVIC中使能中断:
这里我们使用串口进行时间和日期的修改,通过串口输入特定格式的数据来修改数据。具体来说,通过串口接收数据,在接收中断中处理数据,将特定格式的数据转换为日期或时间,并调用HAL库的RTC时间/日期设定函数。
串口接收中断回调函数如下:(串口代码参考了这个博客:(12条消息) 【STM32】HAL库 STM32CubeMX教程四—UART串口通信详解_hal_uart_transmit详解_Z小旋的博客-优快云博客)
#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的博客-优快云博客
数码管的驱动我直接复用了博客里面的库。
数码管显示时间与日期的思路如下:
在主循环中进行操作
- 获取RTC当前时间或日期
- 从时间/日期中得到数码管当前位需要的数字
- 将数字写入数码管对应位
- 数码管位++,并限制在0~7之间
- 延时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;
}
}
这样通过按下按键,就可以实现数码管显示时间/日期的切换。效果如下: