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的USART1都是PA9发射端 PA10接收端
除非有特殊处理或者说明
CubeMX的配置 配置USART1
文章图片
配置 外部晶振 、下载方式 与 时钟
具体参考STM32集成开发环境 STM32CubeIDE 安装与配置指南
生成代码
UART库与代码 首先建立USART.h 和 USART.h
具体参考STM32CubeIDE开发笔记 MK.II - ST-LINK调试 与 建立用户驱动库
方案A printf 重定向 使用Keil可以参考以下文章的重定向方法
在勾选Keil的Use MicroLib选项后即可
STM32的printf函数重定向由于GCC中没有MicroLib,不能对fputc()进行重定向
STM32 Uart及其配置
【STM32】HAL库 STM32CubeMX教程四—UART串口通信详解
在参考以下文章后,得知需要对__write()进行重定向
重定向printf函数到串口输出的多种方法那么GCC下的应该是这样
【STM32Cube笔记】10-异步串口收发USART
文章图片
首先引入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/
文章图片
这边建议别用这种方法,经测试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/
正常收发的情况是这样的 唯一的缺点可能就是指定长度接收吧 = =
文章图片
经压力测试,串口不会卡死
文章图片
中断模式
使能中断
文章图片
调用一次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中编写这个函数
文章图片
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)
{
}
文章图片
DMA模式
添加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);
}
文章图片
全方案完整参考代码 注意,完整部分与上面部分有所不同,不可无脑直接套入
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)】搞着搞着,开始怀疑我到底会不会串口了= =
这部分类型转换的锅比较多 嗯。。。
测试了几天总算完毕了
那…就这样吧
推荐阅读
- 单片机|STM32学习总结之------串口通信USART
- STM32cubeIDE|STM32cubeIDE学习汇总(六)---串口,DMA
- stm32cube|STM32CUBEMX开发GD32F303(8)----USART收发配置
- stm32cube|STM32CUBEMX开发GD32F303(9)----USART通过DMA收发
- STM32—cubeIDE+DMA+USART 接收任意长度的数据
- 单片机|STM32CUBEIDE(7)----USART收发配置
- STM32实现HID键盘
- 单片机|**51 stm8 arduino esp8266 esp32 stm32 树莓派 等单片机使用对比**
- stm32|一些常见的处理器如arm,arduino,stm32,51,树莓派的联系和区别,还有各自的长短板()