main函数
/**
* @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_RTC_Init();
MX_USART2_UART_Init();
MX_TIM2_Init();
MX_TIM3_Init();
/* USER CODE BEGIN 2 */
DelayInit();
GpioInit();
LCD_ST7920_Init();
LED_35V_CTL_L;//LCD受3.5v电源芯片控(使能)
LCDClrDisplay();
USART2_BUF_INIT(&rxbuf_struct);
LOW_PWR_Init(LOW_PWR_COUNTDOWN);//1min
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
//不断获取新的Time和Date
HAL_RTC_GetTime(&hrtc, &Time, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &Date, RTC_FORMAT_BIN);
if(!low_pwr_struct.low_pwr_flag)//当不在休眠模式的时候才输出和显示
{
/* Display date Format : yy/mm/dd */
printf("DATE: %02d/%02d/%02d\r\nWEEKDAY: %02d\r\n",2000 + Date.Year, Date.Month, Date.Date,Date.WeekDay);
/* Display time Format : hh:mm:ss */
printf("TIME: %02d:%02d:%02d\r\n",Time.Hours, Time.Minutes, Time.Seconds);
printf("\r\n");
LCD_Display();
}
LED_TOGGLE();
// USART2_BUF_DEINIT(rxbuf_struct);
}
/* USER CODE END 3 */
}
USART2串口配置
//usart.h
/* USER CODE BEGIN Includes */
#include "sys.h"
#include "stdio.h"
/* USER CODE END Includes */
extern UART_HandleTypeDef huart2;
/* USER CODE BEGIN Private defines */
#define USART2_BUF_SIZE 20
typedef struct
{
int rx_init_flag;
int rx_count;
u8 rxbuf[USART2_BUF_SIZE];
}USART2_RXBUF;
typedef struct
{
u8 year;
u8 month;
u8 date;
u8 weekday;
u8 hour;
u8 min;
u8 sec;
}DATE_TIME;
extern USART2_RXBUF rxbuf_struct;
extern DATE_TIME dt_struct;
/* USER CODE END Private defines */
void MX_USART2_UART_Init(void);
/* USER CODE BEGIN Prototypes */
void USART2_BUF_INIT(USART2_RXBUF*);
void USART2_BUF_DEINIT(USART2_RXBUF*);
void USART2Write(u8 *data,u16 len);
void USART2SendByte(u8 data);
void USART2_CLEAR_BUF(USART2_RXBUF*);
void Protocal_DATE(USART2_RXBUF* rx,DATE_TIME* dt);
void Protocal_TIME(USART2_RXBUF* rx,DATE_TIME* dt);
void USART2_Modify_DATE(DATE_TIME* dest);
void USART2_Modify_TIME(DATE_TIME* dest);
/* USER CODE END Prototypes */
串口重定向,接收缓冲区初始化
//usart.c
/* USER CODE BEGIN 0 */
USART2_RXBUF rxbuf_struct={0};//实例接收缓存区
DATE_TIME dt_struct={0};
//串口重定向
int fputc(int ch, FILE *f)
{
while((USART2->ISR & 0X40) == 0); //USART1乱码
USART2->TDR = (u8) ch;
// HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
void USART2_BUF_INIT(USART2_RXBUF* p)
{
p->rx_init_flag=1;
p->rx_count=0;
//初始化缓冲区数组
for(int i=0;i<USART2_BUF_SIZE;i++)
{
p->rxbuf[i]=0;
}
}
//数组初始化失能
void USART2_BUF_DEINIT(USART2_RXBUF* p)
{
p->rx_init_flag=0;
}
void USART2_CLEAR_BUF(USART2_RXBUF* p)
{
//清空缓冲区数组
for(int i=0;i<p->rx_count;i++)
{
p->rxbuf[i]=0;
}
}
/* USER CODE END 0 */
为了打开串口接收寄存器非空中断和串口空闲中断要记得手动在MX_USART2_UART_Init里加上三行代码
//usart.c
/* USER CODE BEGIN USART2_Init 2 */
__HAL_UART_ENABLE_IT(&huart2,UART_IT_RXNE);//开启USART2接收寄存器非空中断
__HAL_UART_ENABLE_IT(&huart2,UART_IT_IDLE);
HAL_UARTEx_ReceiveToIdle_IT(&huart2,rxbuf_struct.rxbuf,USART2_BUF_SIZE);
/* USER CODE END USART2_Init 2 */
空闲中断回调函数,相比于在非空中断里处理数据,空闲中断效率更高。因为往往串口收到的数据是不定长的,所以串口空闲中断在串口空闲时触发也就相当于一帧数据发送完毕之后再在回调函数中处理数据更加合理也更加高效。
//stm32l4xx_it.c
//空闲中断回调函数,参数Size为串口实际接收到数据字节数
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if(huart->Instance==USART2)
{
if(rxbuf_struct.rx_init_flag)
{
//把收到的一包数据通过串口回传
// HAL_UART_Transmit(&huart2,rxbuf_struct.rxbuf,Size,0xff);
printf("raw data:");
USART2Write(rxbuf_struct.rxbuf,rxbuf_struct.rx_count);
if(rxbuf_struct.rx_count==12)
{
Protocal_DATE(&rxbuf_struct,&dt_struct);//解析日期数据包
USART2_Modify_DATE(&dt_struct);//更改日期
}
else if(rxbuf_struct.rx_count==8)
{
Protocal_TIME(&rxbuf_struct,&dt_struct);//解析时间数据包
USART2_Modify_TIME(&dt_struct);//更改时间
}
else
{
printf("wrong format\r\n");
}
printf("\r\n");
USART2_CLEAR_BUF(&rxbuf_struct);//清空缓冲区
rxbuf_struct.rx_count=0;//重置count
//再次开启空闲中断接收,不然只会接收一次数据
HAL_UARTEx_ReceiveToIdle_IT(&huart2,rxbuf_struct.rxbuf,USART2_BUF_SIZE);
}
}
}
我的USART2_IRQHandler里面只负责计数收到了多少个字符,每进一次IRQ count数值加一。在空闲中断回调函数中直接读取这个count值就知道缓冲区数组的有效长度了
//stm32l4xx_it.c
/**
* @brief This function handles USART2 global interrupt.
*/
void USART2_IRQHandler(void)
{
/* USER CODE BEGIN USART2_IRQn 0 */
串口回显
// if(__HAL_UART_GET_FLAG(&huart2,UART_FLAG_RXNE) != RESET)
// {
// Res = (uint16_t)READ_REG(huart2.Instance ->RDR);//读取接收寄存器
// WRITE_REG(huart2.Instance -> TDR,Res);//回显代码
// }
if(__HAL_UART_GET_FLAG(&huart2,UART_FLAG_RXNE) != RESET)
{
rxbuf_struct.rx_count++;//计算接受缓冲数组里收到字符的个数
}
/* USER CODE END USART2_IRQn 0 */
HAL_UART_IRQHandler(&huart2);
/* USER CODE BEGIN USART2_IRQn 1 */
/* USER CODE END USART2_IRQn 1 */
}
自定义的串口发送函数
/**********************************************
功能 : 串口2发送1个字符
入口参数:
返回值 :
***********************************************/
void USART2SendByte(u8 data)
{
while((USART2->ISR & 0X40) == 0){}; //循环发送,直到发送完毕
USART2->TDR = (u8) data;
}
/**********************************************
功能 : 串口2发送1串字符
入口参数: 字符串首地址 字符串长度
返回值 :
***********************************************/
void USART2Write(u8 *data,u16 len)
{
u16 i;
for (i=0; i<len; i++){
USART2SendByte(data[i]);
}
}
串口接收数据解析
串口接收到数据后存到接收缓冲区USART2_RXBUF结构体里,DATE_TIME结构体用来存解析出来的BCD码,注意在printf输出时需要先把BCD码转成u8类型才能进行输出,不然BCD码0x17不转换就输出的话直接就输出17了,不是人类习惯的十进制数。其中RTC_Bcd2ToByte()函数是HAL库自带的在RTC可以通过HAL_RTC_GetTime函数找到。
串口助手勾上发送新行时会在raw data后面自动加上0D 0A两个字符ASCII码表示实际上就是\r \n,这样串口助手显示的时候就会自动换行好看一点。
通过debug观察接收缓冲区
/**********************************************
功能 : 串口输入年月日解析协议
输入格式: 年月日星期无空格(串口助手勾上发送新行) EX:2024072303实际上数据帧长度是len(EX)+2
返回值 :
***********************************************/
void Protocal_DATE(USART2_RXBUF* src,DATE_TIME* dest)
{
//将byte转为bcd码
dest->year=(((src->rxbuf[2]&0x0f)<<4)|(src->rxbuf[3]&0x0f));
dest->month=(((src->rxbuf[4]&0x0f)<<4)|(src->rxbuf[5]&0x0f));
dest->date=(((src->rxbuf[6]&0x0f)<<4)|(src->rxbuf[7]&0x0f));
dest->weekday=(((src->rxbuf[8]&0x0f)<<4)|(src->rxbuf[9]&0x0f));
printf("DATE: %02d/%02d/%02d\r\nWEEKDAY: %02d\r\n",2000 + (uint8_t)RTC_Bcd2ToByte(dest->year), \
(uint8_t)RTC_Bcd2ToByte(dest->month),\
(uint8_t)RTC_Bcd2ToByte(dest->date), \
(uint8_t)RTC_Bcd2ToByte(dest->weekday));
}
/**********************************************
功能 : 串口输入时分秒解析协议
输入格式: 24小时制时分秒中间无空格(串口助手勾上发送新行) EX:152020实际上数据帧长度是len(EX)+2
返回值 :
***********************************************/
void Protocal_TIME(USART2_RXBUF* src,DATE_TIME* dest)
{
//将byte转为bcd码
dest->hour=(((src->rxbuf[0]&0x0f)<<4)|(src->rxbuf[1]&0x0f));
dest->min=(((src->rxbuf[2]&0x0f)<<4)|(src->rxbuf[3]&0x0f));
dest->sec=(((src->rxbuf[4]&0x0f)<<4)|(src->rxbuf[5]&0x0f));
printf("TIME: %02d:%02d:%02d\r\n",(uint8_t)RTC_Bcd2ToByte(dest->hour), \
(uint8_t)RTC_Bcd2ToByte(dest->min), \
(uint8_t)RTC_Bcd2ToByte(dest->sec));
}
修改日期时间
DATE_TIME结构体里存的都是BCD码,HAL库支持直接往HAL_RTC_Set****函数里直接写BCD码。这里面的Time和Date结构体在rtc.c文件里定义了。
有个细节要注意就是在修改RTC值的时候要先将RTC失能再使能,因为main函数里在非休眠状态下是不断读取RTC值的,而串口修改RTC值的时候如果不先失能的话可能会出现同时读写冲突的情况。
void USART2_Modify_DATE(DATE_TIME* dest)
{
HAL_RTC_MspDeInit(&hrtc);//失能rtc
Date.WeekDay = dest->weekday;
Date.Month = dest->month;
Date.Date = dest->date;
Date.Year = dest->year;
HAL_RTC_MspInit(&hrtc);//使能rtc
if (HAL_RTC_SetDate(&hrtc, &Date, RTC_FORMAT_BCD) != HAL_OK)
{
Error_Handler();
}
}
void USART2_Modify_TIME(DATE_TIME* dest)
{
HAL_RTC_MspDeInit(&hrtc);//失能rtc
Time.Hours = dest->hour;
Time.Minutes = dest->min;
Time.Seconds = dest->sec;
Time.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
Time.StoreOperation = RTC_STOREOPERATION_RESET;
HAL_RTC_MspInit(&hrtc);//使能rtc
if (HAL_RTC_SetTime(&hrtc, &Time, RTC_FORMAT_BCD) != HAL_OK)
{
Error_Handler();
}
}