diff --git a/book/06:串口通信/01.md b/book/06:串口通信/01.md index bb7f0eb..8387b23 100644 --- a/book/06:串口通信/01.md +++ b/book/06:串口通信/01.md @@ -548,4 +548,161 @@ int main(void) { 1. 这个简单的格式解析是通信当中的语法和意义;但是还有些不完善,会存在什么缺陷? 2. 应该如何解决这些问题? -# 5. DMA方式的串口通信 \ No newline at end of file +# 5. DMA方式的串口通信 + +轮询方式是主动的查询串口的状态,根据寄存器状态来进行收发的操作;这种方式效率比较低。中断方式效率较高,串口当有状态变化的时候会产生中断(中断函数的优先级永远高于主函数),进行对数据进行处理。但是中断方式其实还是一个字节一个字节的收发(看看[STM32串口通信](STM32串口通信)),没发送或者是接收一个字节都会产生中断。CPU在每个字节的收发后都要进行处理,这样的方式效率还是不够高。因此有了DMA方式。简单说来,DMA(Direct Memory Access)方式是外设与内存之间建立了一个联系,当有一组数据接收或者发送完成的时候才通知CPU进行处理(中断),这样会大家提高效率,减少CPU的占用时间。 + +## 5.1. DMA控制器概述 + +![image-20241005085938773](./img/image-20241005085938773.png) + +参考手册143页,描述了芯片有2个DMA控制器。 + +![image-20241005090300061](./img/image-20241005090300061.png) + +1. 具备两个DMA控制器:DMA1和DMA2,每个控制器有8个数据流,每个数据流可以响应16个来自外设或存储器的 DMA 请求。 +2. 数据流(stream)是用于连接传输源和传输目标的数据通路,每个数据流可以配置为不同的传输源和传输目标。 +3. DMA 控制器通过总线仲裁器来协调各个 DMA 请求的优先级。 +4. 支持三种数据传输方向:从外设到存储器、从存储器到外设以及从存储器到存储器。 +5. 具备16字节的 FIFO。使能 FIFO功能后,源数据先送人FIFO,达到 FIFO 的触发阈值后,再传送到目标地址。 + +## 5.2. DMA的接口函数 + +### 5.2.1. 串口DMA 方式发送函数(HAL_UART_Transmit_DMA) + +![image-20241005091434964](./img/image-20241005091434964.png) + +### 5.2.2. 串口DMA方式接收函数(HAL_UART_Receive_DMA) + +![image-20241005091528957](./img/image-20241005091528957.png) + +### 5.2.3. DMA数据项数读取函数(__HAL_DMA_GET_COUNTER) + +![image-20241005091634050](./img/image-20241005091634050.png) + +### 5.2.4. 串口DMA传输停止函数(HAL_UART_DMAStop + +![image-20241005091816300](./img/image-20241005091816300.png) + +## 5.3. 利用空闲中断和 DMA 实现不定长数据的接收 + +什么是空闲中断? +![alt text](img/idle.drawio.png) + +串口拉低进行通信;每个字符间有一个停止位(高);当检查到停止位有两个以上的长度时表示线路Idle;或者说数据不连续。 + +任务描述: + +使用空闲中断,接收一组连续的数据,然后把这些数据返回。因为框架并没有提供idle中断,因此需要修改一下中断处理函数。 + +### 5.3.1. 开发板配置 + +1. 设置串口,与以前的一致,需要使能串口2: + +![image-20241005100538695](./img/image-20241005100538695.png) + +2. 设置DMA + +![image-20241005100851227](./img/image-20241005100851227.png) + +点击1位置,添加RX的DMA支持(2条目),该配置后面的(Channel,Direction,Priority)保持缺省就可以了。Channel是固定的,这里不能调整;Direction,因为是读取,肯定是外设到内存,因此也不能调整;Priority是用于DMA仲裁的,因为这里只有一个DMA,因此任意值都没有关系。 + +3位置用于设置 DMA 工作模式,分为Normal和 Circular 两种模式。在Normal模式下,当DMA传输结束时,即指定数量的数据已经完成传输,将不再产生 DMA操作。如果需要再次传输数据,需要在关闭DMA数据流的情况下,重新启动DMA 传输。这种方式相当于单次传输方式。 + +在 Circular 模式下,当前的 DMA 传输结束时,传输数据的数量将自动重载为初值,数据缓冲区重新指向首地址,只要有数据就会继续DMA传输。这种方式相当于连续传输方式。Circular模式主要用于连续、大批量的数据传递,如 A/D转换中的连续转换模式。而这里的不定长数据接收任务,无法预知一帧数据的字节数。当利用 IDLE 中断接收完一帧数据后,需要暂停 DMA 传输,获取该帧数据的字节数,然后重新启动 DMA 传输,因此选择Normal 模式。 + +4、5位置的Increment Address:用于设置地址递增。外设作为单一设备,地址固定,因此,地址设置为不递增。而存储器包括多个存储单元,完成一个数据传输后,地址应该自动递增,指向下一个待传输数据的地址,因此设置为自动增加。 + +5位置的数据宽度:用于设置数据宽度,有Byte(字节)HalfWord(2字节)和Word(4字节)三种选择。由于串口一次只能收发一字节,因此选择 Byte。 + +3. 设置串口2的全局中断,以便可以捕获IDLE中断。 + +![image-20241005101755877](./img/image-20241005101755877.png) + +### 5.3.2. 代码 + +在main.c的大约22行加入: + +```c +/* USER CODE BEGIN Includes */ +#include +/* USER CODE END Includes */ +``` + +因为等一下要用到 strlen函数,因此加入 string.h 的引用。 + + + +在main.c的大约60行加入: + +```c +/* USER CODE BEGIN 0 */ +#define LENGTH 100 // 接收缓冲的长度,需要不小于最大帧(连续数据)的长度 +uint8_t RxBuffer[LENGTH]; // 接收缓冲,也用于发送 +uint8_t RxCount = 0; // 接收到的数据长度 + +volatile uint8_t RxFlag = 0; // 自定义 Idle 中断标志 + +const char *MSG1 = "Hello"; + +/* USER CODE END 0 */ +``` + +这里主要是变量的定义,注意 RxFlag 这个变量设置成 volatile ,因为在中断中需要对这个变量进行写,在main.c中需要对这个变量进行读。 + +main.c的大约100行的main函数中: + +```c + /* USER CODE BEGIN 2 */ + HAL_UART_Transmit(&huart2, (uint8_t*) MSG1, strlen(MSG1), 100); // 发送提示 + __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE); + HAL_UART_Receive_DMA(&huart2, RxBuffer, LENGTH); // 开启DMA接收 + /* USER CODE END 2 */ +``` + +首先发送欢迎消息,然后启动串口2的IDLE中断,最后启用串口的DMA的接收。 + +在main函数的循环中: + +```c + while (1) { + /* USER CODE END WHILE */ + + /* USER CODE BEGIN 3 */ + if (RxFlag) { // 坚持到IDLE中断,表示一帧数据接收完成 + RxFlag = 0; // 重新设置自定义中断标志 + HAL_UART_DMAStop(&huart2); // 停止DMA 接收,因为DMA可能没有接收满 + RxCount = LENGTH - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);// 计算收到数据的长度 + HAL_UART_Transmit(&huart2, RxBuffer, RxCount, 100); // 发送接收到的数据 + // 书上有把缓冲区重新填写为0的代码,其实是不需要的 + HAL_UART_Receive_DMA(&huart2, RxBuffer, LENGTH); // 重新开启DMA接收 + } + } +``` + +循环判断 RxFlag 标志,该标志是在串口中断服务中设置;检查到该标识表示已经接收完连续的一组数据;然后关闭DMA,发送刚刚收到的数据,最后在开始DMA读取。 + +以上是main.c的代码,下面看看stm32f1xx_it.c的代码。 + +大约在60行左右: + +```c +/* USER CODE BEGIN EV */ +extern volatile uint8_t RxFlag; // RxFlag 在 main.c 中定义,这里只是引用 +/* USER CODE END EV */ +``` + +因为FxFlag是在main.c中定义的,这里也是需要使用的,所以声明成外部变量。 + +在大约204行的串口中断服务中加入以下代码: + +```c + /* USER CODE BEGIN USART2_IRQn 1 */ + if (__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE) != RESET) { + __HAL_UART_CLEAR_IDLEFLAG(&huart2); + RxFlag = 1; + } + /* USER CODE END USART2_IRQn 1 */ +``` + +这里判断串口的IDLE标志,如果是真,就设置FxFlag为1,以便main函数使用。 \ No newline at end of file diff --git a/book/06:串口通信/img/idle.drawio.png b/book/06:串口通信/img/idle.drawio.png new file mode 100644 index 0000000..2800c15 Binary files /dev/null and b/book/06:串口通信/img/idle.drawio.png differ diff --git a/book/06:串口通信/img/image-20241005085938773.png b/book/06:串口通信/img/image-20241005085938773.png new file mode 100644 index 0000000..bce3037 Binary files /dev/null and b/book/06:串口通信/img/image-20241005085938773.png differ diff --git a/book/06:串口通信/img/image-20241005090300061.png b/book/06:串口通信/img/image-20241005090300061.png new file mode 100644 index 0000000..58ec1c1 Binary files /dev/null and b/book/06:串口通信/img/image-20241005090300061.png differ diff --git a/book/06:串口通信/img/image-20241005091434964.png b/book/06:串口通信/img/image-20241005091434964.png new file mode 100644 index 0000000..4520c55 Binary files /dev/null and b/book/06:串口通信/img/image-20241005091434964.png differ diff --git a/book/06:串口通信/img/image-20241005091528957.png b/book/06:串口通信/img/image-20241005091528957.png new file mode 100644 index 0000000..786d182 Binary files /dev/null and b/book/06:串口通信/img/image-20241005091528957.png differ diff --git a/book/06:串口通信/img/image-20241005091634050.png b/book/06:串口通信/img/image-20241005091634050.png new file mode 100644 index 0000000..937a953 Binary files /dev/null and b/book/06:串口通信/img/image-20241005091634050.png differ diff --git a/book/06:串口通信/img/image-20241005091816300.png b/book/06:串口通信/img/image-20241005091816300.png new file mode 100644 index 0000000..6802c16 Binary files /dev/null and b/book/06:串口通信/img/image-20241005091816300.png differ diff --git a/book/06:串口通信/img/image-20241005100538695.png b/book/06:串口通信/img/image-20241005100538695.png new file mode 100644 index 0000000..c5ce191 Binary files /dev/null and b/book/06:串口通信/img/image-20241005100538695.png differ diff --git a/book/06:串口通信/img/image-20241005100851227.png b/book/06:串口通信/img/image-20241005100851227.png new file mode 100644 index 0000000..68dce1b Binary files /dev/null and b/book/06:串口通信/img/image-20241005100851227.png differ diff --git a/book/06:串口通信/img/image-20241005101755877.png b/book/06:串口通信/img/image-20241005101755877.png new file mode 100644 index 0000000..d7306d4 Binary files /dev/null and b/book/06:串口通信/img/image-20241005101755877.png differ