stm32|STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)


STM32CubeIDE开发笔记 MK.III - UART串口通信(查询/中断/DMA)

  • 前言
    • 更新日志
    • 简介
  • 查看原理图
  • CubeMX的配置
  • UART库与代码
    • 方案A printf 重定向
    • 方案B 函数
      • 查询模式
        • 串口错误置位 标志位清除函数
        • WriteData部分代码
        • ReadData部分代码
        • main部分
      • 中断模式
        • 使能中断
        • 调用一次receive使能中断
        • Rx接收回调函数
        • main测试部分
      • DMA模式
        • 添加DMA配置
        • USART.C部分
        • 回调函数部分
    • 全方案完整参考代码
      • USART.h
      • USART.c
      • MAIN.H
      • MAIN.C(测试部分)
  • 构建Debug与调试
  • 后记

前言 更新日志 版本:2022.03.08 Ver1.0.0
版本:2022.03.16 Ver1.1.0 完成查询模式函数部分
版本:2022.03.17 Ver1.2.0 完成中断模式函数部分
版本:2022.03.18 Ver1.3.0 完成DMA模式函数部分
简介 对于STM32的调试来说,通过串口打印数据往往是一个不错的选择。
那么,就让小编我们来编写一个初步的串口通信。
博主使用的是自研制的STM32L431RCT6试作型板。
其中板载了CH340G模块,可以直接通过type-c口连接PC USB来进行通信。
如果核心板或开发板中没有CH340G/TTL转USB模块,需要备一个CH340G /TTL转USB模块。
一般USART1 板件都是 PA9发射端 PA10接收端
与串口模块的连接示意图:
stm32|STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)
文章图片

stm32|STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)
文章图片

查看原理图 这步跳过吧
STM32的USART1都是PA9发射端 PA10接收端
除非有特殊处理或者说明
CubeMX的配置 配置USART1
stm32|STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)
文章图片

配置 外部晶振 、下载方式 与 时钟
具体参考STM32集成开发环境 STM32CubeIDE 安装与配置指南
生成代码
UART库与代码 首先建立USART.h 和 USART.h
具体参考STM32CubeIDE开发笔记 MK.II - ST-LINK调试 与 建立用户驱动库
方案A printf 重定向 使用Keil可以参考以下文章的重定向方法
在勾选Keil的Use MicroLib选项后即可
STM32的printf函数重定向

STM32 Uart及其配置

【STM32】HAL库 STM32CubeMX教程四—UART串口通信详解
由于GCC中没有MicroLib,不能对fputc()进行重定向
在参考以下文章后,得知需要对__write()进行重定向
重定向printf函数到串口输出的多种方法
【STM32Cube笔记】10-异步串口收发USART

那么GCC下的应该是这样
stm32|STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)
文章图片

首先引入huart1
extern UART_HandleTypeDef huart1; //引用main.c中的串口声明

再进行重定向
// //CHANNEL A重定向printf法 //extern UART_HandleTypeDef huart1; //引用main.c中的串口声明//重定向 printf int _write(int file, char *ptr, int len) { HAL_UART_Transmit(&huart1,(uint8_t *)ptr,len, 0x10); return len; }int _read(int file, char *ptr, int len) { HAL_UART_Receive(&huart1,(uint8_t *)ptr,len,0x10); return len; }// //CHANNEL A //

在main中添加输出测试
#define user_buffersize 24 char user_buffer[user_buffersize]=""; //channel 1/ printf("\r\n"); printf("NND\r\n"); sprintf(user_buffer,"%s\r\n","WSM?"); printf("%s",user_buffer); memset(user_buffer,0,user_buffersize); scanf("%s",user_buffer); printf("%s",user_buffer); //channel 1/

stm32|STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)
文章图片

这边建议别用这种方法,经测试printf可正常使用,scanf有点问题哈,用起来有点不对劲(读不到直接跳)(直接瞎读) 或者直接卡死(keil里面用MicroLib就很正常),如果只使用串口输出是一点问题都没有的。建议回去转KEIL
方案B 函数 但是与其重定向这么麻烦,各个编译器之间还不同……我为什么不在HAL库基础上自己写一个通用呢???在前面浪费了那么多时间的我怕不是个傻子吧???
第二种方法不采用重定义,在USART.c/.h内编写相应改版换皮函数
查询模式
首先我们来说件事
那个……先加个这个函数
串口错误置位 标志位清除函数
void UART_FlagClear(UART_HandleTypeDef *huart_num) { //解决串口错误 恢复置位 清除flag __HAL_UART_CLEAR_FLAG(huart_num, UART_FLAG_PE); __HAL_UART_CLEAR_FLAG(huart_num, UART_FLAG_FE); __HAL_UART_CLEAR_FLAG(huart_num, UART_FLAG_NE); __HAL_UART_CLEAR_FLAG(huart_num, UART_FLAG_ORE); }

事情的起因是这样的,按照普通的串口HAL_UART_Transmit和HAL_UART_Receive,在使用串口助手调试的时候,在Transmit未结束前,向mcu发送数据时,会卡死!会卡死!会卡死!这个问题困了我三天我是真的吐了。原因是这样做会使串口进入错误回馈函数,并且把相关标志位置位,这之后无论往串口塞什么数据,全都没反应!全都没反应!全都没反应!
这种情况下,就等超时结束。但是如果你把 Transmit或Receive 塞进了while里面……抱歉,你可能出不来了,会一直卡在里面。
感谢这位大佬,成功解决上述问题,即清除标志位。
STM32 HAL_UART_Receive HAL库串口接受清空错误标志

WriteData部分代码
void USART_WriteData(UART_HandleTypeDef *huart_num, const uint8_t *wData) { int len = strlen((const char *)wData); while(HAL_UART_Transmit(huart_num,wData,len, 0x10)!= HAL_OK) { //解决串口错误 恢复置位 清除flag UART_FlagClear(huart_num); } }

ReadData部分代码
void USART_ReadData(UART_HandleTypeDef *huart_num,uint8_t *rData, uint16_t len) { USART_WriteData(huart_num,(uint8_t *)"Ready to receive data!\r\n",1); while(HAL_UART_Receive(huart_num,rData,len,0x10)!= HAL_OK) { //解决串口错误 恢复置位 清除flag UART_FlagClear(huart_num); //清空接收数组 重新接收 memset(rData,0,len); } }

之后在usart.c中添加下头文件(stdio.h,stm32l4xx_hal.h,string.h),在usart.h中声明下这两个函数
main部分
#define user_buffersize 24uint8_t RXdata_size; char user_buffer[user_buffersize]="";

//channel 2/ memset(user_buffer,0,user_buffersize); sprintf(user_buffer,"\r\nStr:%s\r\n"," Test is ready!"); USART_WriteData(&huart1,(uint8_t *)user_buffer,1); memset(user_buffer,0,user_buffersize); sprintf(user_buffer,"%s\r\n","Ready to Receive!"); USART_WriteData(&huart1,(uint8_t *)user_buffer,1); RXdata_size=12; memset(user_buffer,0,user_buffersize); USART_ReadData(&huart1,(uint8_t *)user_buffer,RXdata_size,1); USART_WriteData(&huart1,(uint8_t *)user_buffer,1); //channel 2/

正常收发的情况是这样的 唯一的缺点可能就是指定长度接收吧 = =
stm32|STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)
文章图片

经压力测试,串口不会卡死
stm32|STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)
文章图片

中断模式
使能中断 stm32|STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)
文章图片

调用一次receive使能中断
//user_buffersize 在main.h中 #define user_buffersize 26 extern char user_buffer[user_buffersize]; #define IT_MODE0X01 uint8_t USART_TX_MODE; uint8_t USART_RX_MODE;

void UART_IT_MODE(UART_HandleTypeDef *huart_num) { USART_RX_MODE=IT_MODE; USART_TX_MODE=IT_MODE; HAL_UART_Receive_IT(huart_num, (uint8_t *)user_buffer,user_buffersize); HAL_UART_Transmit_IT(huart_num, (uint8_t *)"USART_IT_Mode_Ready!\r\n",22); memset(user_buffer,0,user_buffersize); }

Rx接收回调函数 HAL_UART_RxCpltCallback在hal库中被弱定义,在USART.c中编写这个函数
stm32|STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)
文章图片

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { charIT_RecTMPbuffer[user_buffersize]=""; if(huart->Instance == USART1) { if(USART_RX_MODE== IT_MODE) { //ITRX-CALLBACK strcpy(IT_RecTMPbuffer,user_buffer); HAL_UART_Transmit_IT(huart, (uint8_t *)IT_RecTMPbuffer,user_buffersize); //ITRX-CALLBACK memset(user_buffer,0,sizeof(user_buffer)); //再次使能中断 HAL_UART_Receive_IT(huart, (uint8_t *)user_buffer,user_buffersize); } } }

这里的IT_RecTMPbuffer是为了传递接受的user_buffer数据,随后清空user_buffer数据并使能下一次接收中断。
其实这个有没有都可以,一般来说
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART1) { if(USART_RX_MODE== IT_MODE) { //ITRX-CALLBACK HAL_UART_Transmit_IT(huart, (uint8_t *)user_buffer,user_buffersize); //ITRX-CALLBACK //再次使能中断 HAL_UART_Receive_IT(huart, (uint8_t *)user_buffer,user_buffersize); } } }

这样已经足够
main测试部分
//channel 2B UART_IT_MODE(&huart1); //channel 2Bwhile(1) { }

stm32|STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)
文章图片

DMA模式
添加DMA配置 stm32|STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)
文章图片

这里以TX normal RX circular为例
USART.C部分
//DMA// void USART_WriteData_DMA(UART_HandleTypeDef *huart_num, const uint8_t *wData) { USART_RX_MODE=DMA_MODE; HAL_UART_Transmit_DMA(huart_num,wData, strlen((const char *)wData)); return; }void USART_ReadData_DMA(UART_HandleTypeDef *huart_num,uint8_t *rData,uint8_t len) { HAL_UART_Receive_DMA(huart_num,rData,len); return; }

回调函数部分
#define DMA_MODE0X02 uint8_t USART_TX_MODE; uint8_t USART_RX_MODE;

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { charIT_RecTMPbuffer[user_buffersize]=""; if(huart->Instance == USART1) { if(USART_RX_MODE == DMA_MODE) { //DMA RX-CALLBACK //DMA RX-CALLBACK //回显接收内容 USART_WriteData_DMA(huart,(uint8_t*)user_buffer); } } }

//channel 2C strcpy(user_buffer,"DMA_Trans_Ready!\r\n"); USART_WriteData_DMA(&huart1,(uint8_t*)user_buffer); USART_ReadData_DMA(&huart1,(uint8_t*)user_buffer,user_buffersize); //channel 2Cwhile(1) { osDelay(100); }

stm32|STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)
文章图片

全方案完整参考代码 注意,完整部分与上面部分有所不同,不可无脑直接套入
USART.h
/* * USART.h * *Created on: Mar 8, 2022 *Author: KeeganKei */#ifndef INC_USART_H_ #define INC_USART_H_#define IT_MODE0X01 #define DMA_MODE0X02//重定向 int _write(int file, char *ptr, int len); int _read(int file, char *ptr, int len); void UART_FlagClear(UART_HandleTypeDef *huart_num); //EX 函数 void USART_WriteData(UART_HandleTypeDef *huart_num, const uint8_t *wData); void USART_ReadData(UART_HandleTypeDef *huart_num,uint8_t *rData,uint8_t len); void UART_IT_MODE(UART_HandleTypeDef *huart_num); void USART_WriteData_DMA(UART_HandleTypeDef *huart_num, const uint8_t *wData); void USART_ReadData_DMA(UART_HandleTypeDef *huart_num,uint8_t *rData,uint8_t len); void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); #endif /* INC_USART_H_ */

USART.c
/* * USART.c * *Created on: Mar 8, 2022 *Author: KeeganKei */#include #include #include//import HAL_UART_Transmit #include #include // // //CHANNEL A重定向printf法 // //extern UART_HandleTypeDef huart1; //引用main.c中的串口声明 // // //重定向 printf //int _write(int file, char *ptr, int len) //{ // HAL_UART_Transmit(&huart1,(uint8_t *)ptr,len, 0x0A); // return len; //} // //int _read(int file, char *ptr, int len) //{ // HAL_UART_Receive(&huart1,(uint8_t *)ptr,len,0x0A); // return len; //}// // //CHANNEL A // // // //CHANNEL B // extern char User_Tx_Buffer[User_TX_BufferSize]; extern char User_Rx_Buffer[User_RX_BufferSize]; uint8_t USART_TX_MODE; uint8_t USART_RX_MODE; // FLAG_CLEAR void UART_FlagClear(UART_HandleTypeDef *huart_num) { //解决串口错误 恢复置位 清除flag __HAL_UART_CLEAR_FLAG(huart_num, UART_FLAG_PE); __HAL_UART_CLEAR_FLAG(huart_num, UART_FLAG_FE); __HAL_UART_CLEAR_FLAG(huart_num, UART_FLAG_NE); __HAL_UART_CLEAR_FLAG(huart_num, UART_FLAG_ORE); }// // // // // //NORMAL// void USART_WriteData(UART_HandleTypeDef *huart_num, const uint8_t *wData) { while(HAL_UART_Transmit(huart_num,wData,strlen((const char *)wData), 0x10)!= HAL_OK) { //解决串口错误 恢复置位 清除flag UART_FlagClear(huart_num); } return; }void USART_ReadData(UART_HandleTypeDef *huart_num,uint8_t *rData, uint8_t len) { while(HAL_UART_Receive(huart_num,rData,len,0x10)!= HAL_OK) { //解决串口错误 恢复置位 清除flag UART_FlagClear(huart_num); //清空接收数组 重新接收 memset(rData,0,len); } return; }// IT void UART_IT_MODE(UART_HandleTypeDef *huart_num) { USART_RX_MODE=IT_MODE; USART_TX_MODE=IT_MODE; HAL_UART_Receive_IT(huart_num, (uint8_t *)User_Rx_Buffer,User_RX_BufferSize); HAL_UART_Transmit_IT(huart_num, (uint8_t *)"USART_IT_Mode_Ready!\r\n",22); memset(User_Rx_Buffer,0,User_RX_BufferSize); }// //DMA// void USART_WriteData_DMA(UART_HandleTypeDef *huart_num, const uint8_t *wData) { USART_TX_MODE=DMA_MODE; HAL_UART_Transmit_DMA(huart_num,wData, strlen((const char *)wData)); return; }void USART_ReadData_DMA(UART_HandleTypeDef *huart_num,uint8_t *rData,uint8_t len) { USART_RX_MODE=DMA_MODE; HAL_UART_Receive_DMA(huart_num,rData,len); return; }// // // //void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART1) { if(USART_TX_MODE == IT_MODE) { // //ITTX-CALLBACK // //ITTX-CALLBACK } else if(USART_TX_MODE == DMA_MODE) { // //DMA TX-CALLBACK // //DMA TX-CALLBACK } } }void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { charIT_RecTMPbuffer[User_RX_BufferSize]=""; if(huart->Instance == USART1) { if(USART_RX_MODE == IT_MODE) { // //ITRX-CALLBACK //回显接收数据,有小bug strcpy(IT_RecTMPbuffer,User_Rx_Buffer); HAL_UART_Transmit_IT(huart, (uint8_t *)IT_RecTMPbuffer,User_RX_BufferSize); // //ITRX-CALLBACK memset(User_Rx_Buffer,0,User_RX_BufferSize); HAL_UART_Receive_IT(huart, (uint8_t *)User_Rx_Buffer,User_RX_BufferSize); } else if(USART_RX_MODE == DMA_MODE) { // //DMA RX-CALLBACK // //DMA RX-CALLBACK //回显接收数据 USART_WriteData_DMA(huart,(uint8_t*)User_Rx_Buffer); } } }// // //CHANNEL B //

MAIN.H
// 24 char +\r\n = 26 #define User_TX_BufferSize 26 #define User_RX_BufferSize 26

MAIN.C(测试部分)
PART.A
#include #include #include char User_Tx_Buffer[User_TX_BufferSize]=""; char User_Rx_Buffer[User_RX_BufferSize]=""; extern uint8_t USART_TX_MODE; extern uint8_t USART_RX_MODE;

PART.B
测试时CHANNEL 1/2A/2B/2C 选择测试时注释掉其他三大块的内容
// ///channel 1/ //不推荐 //memset(User_Tx_Buffer,0,User_TX_BufferSize); //printf("\r\n"); //printf("NND\r\n"); //sprintf(User_Tx_Buffer,"%s\r\n","WSM?"); //printf("%s",User_Tx_Buffer); // //memset(User_Rx_Buffer,0,User_RX_BufferSize); //scanf("%s",User_Rx_Buffer); //printf("%s",User_Rx_Buffer); // ///channel 1/// /channel 2A //新手推荐,且足够一般情况使用 //memset(User_Tx_Buffer,0,User_TX_BufferSize); //sprintf(User_Tx_Buffer,"\r\nStr:%s\r\n"," Test is ready!"); //USART_WriteData(&huart1,(uint8_t *)User_Tx_Buffer); // //memset(User_Tx_Buffer,0,User_TX_BufferSize); //sprintf(User_Tx_Buffer,"%s\r\n","Ready to Receive!"); //USART_WriteData(&huart1,(uint8_t *)User_Tx_Buffer); // //uint8_t RXdata_size = 12; //memset(User_Rx_Buffer,0,User_RX_BufferSize); //USART_ReadData(&huart1,(uint8_t *)User_Rx_Buffer,RXdata_size); //USART_WriteData(&huart1,(uint8_t *)User_Rx_Buffer); // /channel 2A// /channel 2B //有小bug //UART_IT_MODE(&huart1); //while(1) //{ //osDelay(100); //} // /channel 2B// /channel 2C //按指定长度输送不会有Bug //USART_TX_MODE=DMA_MODE; //USART_RX_MODE=DMA_MODE; // //memset(User_Tx_Buffer,0,User_TX_BufferSize); //strcpy(User_Tx_Buffer,"DMA_Trans_Ready!\r\n"); // //USART_WriteData_DMA(&huart1,(uint8_t*)User_Tx_Buffer); //USART_ReadData_DMA(&huart1,(uint8_t*)User_Rx_Buffer,User_RX_BufferSize); //while(1) //{ //osDelay(100); //} // /channel 2C

存在部分细微BUG = =,待施工完毕
构建Debug与调试 参考STM32CubeIDE开发笔记 MK.II - ST-LINK调试 与 建立用户驱动库进行debug与调试
后记 【stm32|STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)】搞着搞着,开始怀疑我到底会不会串口了= =
这部分类型转换的锅比较多 嗯。。。
测试了几天总算完毕了
那…就这样吧

    推荐阅读