STM32篇——串口DMA通信
STM32篇——串口DMA通信
本文详细介绍了STM32单片机的串口DMA通信的实现方法。文章从前期准备、创建项目、编辑代码、编译下载等多个步骤,逐步讲解了如何通过DMA实现串口数据的发送和接收。此外,还介绍了如何添加printf_DMA函数以及处理不定长串口数据的方法。
1. 前期准备
- 安装好STM32CubeMX
- 安装好clion
串口DMA通信的优点:
串口接收数据时,主程序跳转至串口中断函数中完成数据的接收和存储。如果接收数据比较频繁时,会占用CPU大量的时间来接收这些数据。而DMA外设的作用就是为了解放CPU,由DMA来接收数据,CPU可以干别的重要事情,当DMA接收完成数据后告诉CPU即可。
2. 创建项目
- 设置SWD调试端口
- 设置串口
- 设置串口DMA
- 使能串口全局中断
- 设置系统时钟为72MHz
生成项目即可。
3. 编辑代码
实现功能为:
通过DMA串口发送字符串。
打开创建的工程,找到源文件夹Src的main.c,并在while(1)循环中添加添加串口发送语句。
main()函数中,其他代码均为系统生成的初始化代码,无需理会。
//main.c添加数组声明
#define Buff_Size 100
uint8_t rx_buff[Buff_Size] = "hell0,uart DMArn";
//main函数添加DMA发送代码
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)rx_buff, sizeof(rx_buff));
HAL_Delay(1000);
4. 编译下载
将程序下载至开发板,并将开发板连接至PC,打开串口调试助手RYCOM,并设置为:
115200+8+N+1,接收结果如下。
5. 添加printf_DMA函数
在代码调试过程中,经常使用printf函数输出调试信息。我们加printf_DMA函数,通过串口输出调试信息。
- main.c文件中添加头文件及函数声明
#include "stdio.h"
#include "stdarg.h"
#include "stm32f1xx_hal_uart.h"
void printf_DMA(const char *format,...);
- 编写printf_DMA函数,代码添加至main.c
//添加DMA串口printf函数
uint8_t _dbg_Buff[150];
void printf_DMA(const char *format,...)
{
uint32_t length;
va_list args;
uint8_t temp=0;
va_start(args, format);
length = vsnprintf((char*)_dbg_Buff, sizeof(_dbg_Buff)+1, (char*)format, args);
va_end(args);
HAL_UART_Transmit_DMA(&huart1,_dbg_Buff,length);
//等待串口发送完成,注意是串口发送完成,不是DMA传输完成
while(!__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC));
//如果没有上面这条语句,连续调用printf_DMA时可能输出错误。
}
- printf函数输出示例
main.c文件main()函数添加代码如下:
printf_DMA("DMA UART Printf test!rn");
输出结果如下:
6. 接收处理不定长的串口数据
单片机串口接收不定长数据时,必须面对的一个问题为,怎么判断这一包数据接收完成了呢?常见的方法主要有以下两种:
- 1.在接收数据时启动一个定时器,在指定时间间隔内没有接收到新数据,则认为数据接收完成;
- 2.在数据中加入帧头、帧尾,通过在程序中判断是否接收到帧尾来确定数据接收完毕。
这两种方法的缺点为,需要主程序来判断和处理,对主程序造成不小压力。
STM32单片机空闲检测中断可以很好的解决这个问题,他的工作原理为:
当STM32的串口接收完一包数据后,会产生一个空闲中断。这个中断在串口其他任何状态都不产生,只会在接收完一包数据后才会产生,一包数据可以是1个字节或者多个字节。因此,我们可以在这个空闲中断函数中,设置一个接收完成标志位。那么,我们只需要在主程序中检测这个标志位就知道数据是否接收完成了。具体应该怎么操作呢?在上述工程基础上,添加相应代码实现接收功能。
- 使能串口接收、空闲中断
在main.c的static void MX_USART1_UART_Init(void)函数最后添加代码如下:
/* USER CODE BEGIN USART1_Init 2 */
//开启空闲中断
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);
//初始化时打开DMA接收,确保第一包数据能够收到!
HAL_UART_Receive_DMA(&huart1,rx_buff,Buff_Size);
/* USER CODE END USART1_Init 2 */
- 添加串口接收全局变量
main.h中声明变量:
/* USER CODE BEGIN ET */
//全局变量定义
#define Buff_Size 100
extern uint8_t rx_buff[100]; //接收缓存
extern uint8_t rx_done; //接收完成标志
extern uint8_t rx_cnt;//接收数据长度
/* USER CODE END ET */
main.c中变量初始化:
/* USER CODE BEGIN PTD */
uint8_t rx_buff[Buff_Size] = "hell0,uart DMArn";
uint8_t rx_done = 0; //接收完成标志
uint8_t rx_cnt = 0;//接收数据长度
/* USER CODE END PTD */
- 修改串口中断处理函数
stm32f1xx_it.c文件中找到串口中断函数void USART1_IRQHandler(void),修改代码如下:
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
uint32_t tmp_flag = 0;
uint32_t temp;
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE)==SET) //获取IDLE标志位
{
rx_done = 1; // 接受完成标志位置1
__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志位
temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);// 获取DMA中未传输的数据个数
rx_cnt = Buff_Size - temp; //总计数减去未传输的数据个数,得到已经接收的数据个数
HAL_UART_DMAStop(&huart1); //关闭DMA
HAL_UART_Receive_DMA(&huart1,rx_buff,Buff_Size);//重新打开DMA接收
}
/* USER CODE END USART1_IRQn 1 */
}
- 处理不定长数据示例
main函数中打印接收到的数据以及长度,代码如下:
while (1)
{
/* USER CODE END WHILE */
if(rx_done == 1)//判读是否接收完成
{
rx_done = 0;//清除接收标志
//数据处理,打印接收长度、接收的数据
printf_DMA("length of rx data: %d!rn",rx_cnt);
for(int i = 0;i<rx_cnt;i++) printf_DMA("%c",rx_buff[i]);
printf_DMA("rn");
rx_cnt =0;//清除接收长度
}
/* USER CODE BEGIN 3 */
}
使用串口调试助手发送任意数据,查看接收情况如下:
如上图所示,通RYCOM助手发送数据给单片机,单片机正确接收,并正确打印数据及长度。
7. 小结
本章实现了单片机串口USART1发送数据,并定义printf_DMA函数,后续调试代码可直接使用。学习了如何通过串口接收不定长数据,并实现数据的处理。