前言
在第七章中,我们采用查询和中断的方式进行UART的发送和接收,查询和纯中断方式的弊端如下:
1.查询方式:
CPU不断轮询UART的状态标志位(如TXE, RXNE),效率极低,CPU完全被阻塞,无法执行其他任务。
2.中断方式:
发送: 每发送完一个字节,产生一个中断,CPU需要响应中断并写入下一个字节。对于大量数据发送,中断频率非常高,CPU频繁进出中断,额外开销很大。
接收: 每接收到一个字节,产生一个中断,CPU需要立刻读取数据寄存器,否则下一个数据可能会覆盖当前数据(Overrun Error)。如果CPU正在处理高优先级中断任务,可能导致数据丢失。
为了解决上面这两种方式的缺点,本章采用DMA方式来进行数据发送和接收:
DMA发送:CPU只需一次性将要发送的数据数组地址、长度等信息配置给DMA,DMA就会自动地将内存中的数据逐个搬运到UART的发送数据寄存器中,直到发送完成才通知CPU。整个过程CPU几乎可以完全不管。
DMA接收:CPU只需提前开辟一个接收缓冲区(数组),并将其地址和长度配置给DMA。此后,UART收到的每一个字节都会由DMA自动搬运到指定的内存数组中。只有当整个缓冲区接收满(或一半时,利用半传输中断)时,DMA才通知CPU来处理数据。极大降低了中断频率, 避免了数据丢失。
DMA通讯的优点:
- 高效: 极大减少了CPU开销,让CPU可以专注于数据处理、算法等核心任务。
- 低功耗: CPU在数据传输期间可以进入睡眠模式,降低系统功耗。
- 高可靠性: 尤其对于高速数据流,DMA方式能更可靠地处理数据,避免因中断延迟导致的数据丢失。
一、功能
使用UART1模块与USB转TTL模块进行串口通讯,UART1发送和接收采用DMA。
二、硬件
通过USB转TTL模块连接PC与Startkit板, Startkit板与USB转TTL模块的连线方式如下,通过串口将信息发送到串口助手。
Startkit 开发板 | USB转TTL模块 |
Tx(PA0) | Rx |
Rx(PA1) | Tx |
GND | GND |
三、PY32Studio配置
3.1 创建新的工程F032_UART_DMA
3.2 配置时钟参数
UART通讯需要高精度时钟,我们采用外部HSE,系统时钟通过PLL倍频为72MHz.
3.3 配置UART1的参数
第一步:激活UART1, 配置参数:
第二步:配置UART的发送和接收的DMA通道:
第三步:使能中断:
第四步:点击生成工程
四、编写应用代码
4.1 应用代码
在PY32Studio生成的代码的基础上,添加以下代码:
在main.c文件定义一个接收长度宏,放在Private define, USER CODE BEGIN PD后面:
/* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ #define RX_FIXED_LEN 10U // 接收固定为10字节 /* USER CODE END PD */增加四个变量rx_buf、tx_buf、dma_tx_complete_flag和dma_rx_complete_flag,放在Private variables,USER CODE BEGIN PV的后面
/* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ uint8_t rx_buf[RX_FIXED_LEN] = {0};//接收数组 uint8_t tx_buf[] = "\r\nPlease input 10 characters: \r\n"; //发送的提示语 uint8_t dma_tx_complete_flag = 0; // DMA发送完成标志 uint8_t dma_rx_complete_flag = 0; // DMA接收完成标志 /* USER CODE END PV */在main.c文件中,重写发送和接收完成的回调函数,放在USER CODE BEGIN 4后面:
/* USER CODE BEGIN 4 */ /** @brief DMA发送完成回调 */ void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == UART1) { dma_tx_complete_flag = 1; //发送完成标志位置位 } } /** @brief DMA接收完成回调:收满RX_FIXED_LEN字节触发 */ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == UART1) { // 收到固定长度10字节,置标记 // 注意:回调里面不要做发送,只标记,业务放到while(1) dma_rx_complete_flag = 1; //接收完成标志位置位 // 重启DMA接收,等待下一帧10字节 HAL_UART_Receive_DMA(&huart1, rx_buf, RX_FIXED_LEN); } } /* USER CODE END 4 */在main()函数中
- 在USER CODE BEGIN 1段中,初始化发送和接收标志位;
- 在USER CODE BEGIN 2段中,启动DMA接收数据;
- 在while(1)循环中,加入以下代码:
- 发送提示语(发送之前,判断发送标志位是否空闲);
- 判断是否接收到10个字符数据,如果接收到,把接收到的数据发回去。
int main(void) { /* USER CODE BEGIN 1 */ dma_tx_complete_flag = 1; // 初始状态发送空闲 dma_rx_complete_flag = 0; //初始接收状态空闲 /* 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 */ Studio_RCC_Init(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ Studio_GPIO_Init(); Studio_DMA_Init(); Studio_UART1_Init(); /* USER CODE BEGIN 2 */ // 启动固定长度DMA接收 HAL_UART_Receive_DMA(&huart1, rx_buf, RX_FIXED_LEN); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while(1) { if(dma_tx_complete_flag == 1) { dma_tx_complete_flag = 0; // 发送tx_buf提示语 HAL_UART_Transmit_DMA(&huart1, tx_buf, sizeof(tx_buf)-1); } HAL_Delay(3000); // 如果检测收到固定10字节帧,把接收到的字符发送回去 if(dma_rx_complete_flag == 1) { dma_rx_complete_flag = 0; if(dma_tx_complete_flag == 1) //上一次发送任务是否完成 { dma_tx_complete_flag = 0; HAL_UART_Transmit_DMA(&huart1, rx_buf, RX_FIXED_LEN); //把接收到的数据发送回去 } } HAL_Delay(1000); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }4.2 设置
在选项卡中,选择安装的编译器版本和Debug类型。
4.3 编译,下载
- 点击编译图标,开始编译程序;
- 编译成功后,开始下载程序;
- 下载成功后,开始调试程序;
4.4调试
点击调试图标,进入调试界面,运行程序,把rx_buf加到Watch1窗口中。在View菜单中,选择Periodic Window Update命令,全速运行代码,观察变量的值(接收到的数据)。
打开串口助手,选择正确的端口名,波特率,数据位,校验位和停止位,打开串口,开始接收数据。右面的窗口可以显示出从MCU发送过来的数据,然后输入10个字符,点击发送。发送完成后,可以看到接收到返回的字符。
五、总结
本文用UART+DMA实现报文发送和接收,整个过程主要步骤如下:
- 通过PY32Studio配置时钟,UART, DMA;
- 重写发送和接收的回调函数;
- 在while循环中,判断是否发送完成,如果发送完成,调用HAL_UART_Transmit_DMA(&huart1, tx_buf, sizeof(tx_buf)-1);发送提示语;如果接收完成,调用HAL_UART_Transmit_DMA(&huart1, tx_buf, sizeof(tx_buf)-1);发送接收到的数据;
- 在接收完成的回调函数中,再次启用接收函数HAL_UART_Receive_DMA(&huart1, rx_buf, RX_FIXED_LEN)。
注意:
连续快速发送前,一定要判断dma_tx_complete_flag,防止HAL_BUSY。