完成串口的DMA

master
高宏宇 11 months ago
parent 650c2aab19
commit de8ff10590

@ -548,4 +548,161 @@ int main(void) {
1. 这个简单的格式解析是通信当中的语法和意义;但是还有些不完善,会存在什么缺陷?
2. 应该如何解决这些问题?
# 5. DMA方式的串口通信
# 5. DMA方式的串口通信
轮询方式是主动的查询串口的状态,根据寄存器状态来进行收发的操作;这种方式效率比较低。中断方式效率较高,串口当有状态变化的时候会产生中断(中断函数的优先级永远高于主函数),进行对数据进行处理。但是中断方式其实还是一个字节一个字节的收发(看看[STM32串口通信](STM32串口通信)没发送或者是接收一个字节都会产生中断。CPU在每个字节的收发后都要进行处理这样的方式效率还是不够高。因此有了DMA方式。简单说来DMADirect 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条目该配置后面的ChannelDirectionPriority保持缺省就可以了。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 <string.h>
/* 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函数使用。

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Loading…
Cancel
Save