STM32CubeMX学习笔记-USART_DMA
- 一、DMA的概念
- 二、数据传输方式
- 普通模式
- 循环模式
- 三、以串口方式讲解
- 串口DMA方式发送函数:HAL_UART_Transmit_DMA
- 串口DMA方式接收函数:HAL_UART_Receive_DMA
- 获取未传输数据个数函数:__HAL_DMA_GET_COUNTER
- 关闭DMA数据流:__HAL_DMA_DISABLE
- 四、DMA2
- 4.1 配置串口2
- 4.2 配置DMA
- 4.3 生成代码
- 4.4 USART+DMA数据发送
- 4.5 USART+DMA数据接收
- 4.6 串口IDLE空闲中断+DMA数据接收
一、DMA的概念
直接存储器访问(DMA):用于在外设与存储器之间以及存储器与存储器之间进行高速数据传输。DMA传输过程的初始化和启动由CPU完成,传输过程由DMA控制器来执行,无需CPU参与,从而节省CPU资源,提高利用率。
DMA数据传输的四个要素:
①传输源:DMA数据传输的来源
②传输目标:DMA数据传输的目的
③传输数量:DMA传输数据的数量
④触发信号:启动一次DMA数据传输的动作
STM32的DMA控制器特点
1.STM32F411微控制器具备两个DMA控制器:DMA1和DMA2,每个控制器有8个数据流,每个数据流可以映射到8个通道(或请求);
2.每一个DMA控制器用于管理一个或多个外设的存储器访问请求,并通过总线仲裁器来协调各个DMA请求的优先级;
3.数据流(stream)是用于连接传输源和传输目标的数据通路,每个数据流可以配置为不同的传输源和传输目标,这些传输源和传输目标称为通道(Channel);
4.具备16字节的FIFO。使能FIFO功能后,源数据先送入FIFO,达到FIFO的触发阈值后,再传送到目标地址。
二、数据传输方式
普通模式
传输结束后(即要传输数据的数量达到零),将不再产生DMA操作。若开始新的DMA传输,需在关闭DMA通道情况下,重新启动DMA传输。
循环模式
可用于处理环形缓冲区和连续数据流(例如ADC扫描模式)。当激活循环模式后,每轮传输结束时,要传输的数据数量将自动用设置的初始值进行加载,并继续响应DMA请求。
三、以串口方式讲解
串口DMA方式发送函数:HAL_UART_Transmit_DMA
函 数 原 型 | HAL_StatusTypeDef HAL_UART_Transmit_DMA (UART_HandleTypeDef *huart,uint8_t *pData, uint16_t Size)
------------------ | ---------------DMA
功能描述 | 在DMA方式下发送一定数量的数据
入口参数1 | huart:串口句柄的地址
入口参数2 | pData:待发送数据的首地址
入口参数3 | Size:待发送数据的个数
返回值 | HAL状态值:HAL_OK表示发送成功;HAL_ERROR表示参数错误;HAL_BUSY表示串口被占用
注意事项 | 1.该函数将启动DMA方式的串口数据发送2.完成指定数量的数据发送后,可以触发DMA中断,在中断中将调用发送中断回调函数HAL_UART_TxCpltCallback进行后续处理3.该函数由用户调用
串口DMA方式接收函数:HAL_UART_Receive_DMA
函 数 原 型 | HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart,uint8_t *pData, uint16_t Size) |
---|---|
功能描述 | 在DMA方式下接收一定数量的数据 |
入口参数1 | huart:串口句柄的地址 |
入口参数2 | pData:待收数数据的首地址 |
入口参数3 | Size:待收数数据的个数 |
返回值 | HAL状态值:HAL_OK表示发送成功;HAL_ERROR表示参数错误;HAL_BUSY表示串口被占用 |
注意事项 | 1.该函数将启动DMA方式的串口数据接收2.完成指定数量的数据接收后,可以触发DMA中断,在中断中将调用接收中断回调函数HAL_UART_RxCpltCallback进行后续处理3.该函数由用户调用 |
获取未传输数据个数函数:__HAL_DMA_GET_COUNTER
函 数 原 型 | HAL_DMA_GET_COUNTER(HANDLE) |
---|---|
功能描述 | 获取DMA数据流中未传输数据的个数 |
参数 | HANDLE_:串口句柄的地址 |
返回值 | NDTR寄存器的内容,即DMA数据流中无传输数据的个数 |
注意事项 | 1.该函数是宏函数,进行宏替换,不发生函数调用2.该函数需要由用户调用,用于获取未传输数据的个数 |
关闭DMA数据流:__HAL_DMA_DISABLE
函数原型 | HAL_DMA_DISABLE(HANDLE_) |
---|---|
功能描述 | 关闭指定的DMA数据流 |
参数 | HANDLE_:串口句柄的地址 |
返回值 | 无 |
注意事项 | 1.该函数是宏函数,进行宏替换,不发生函数调用2.该函数需要由用户调用,用于关闭指定的DMA数据流3.关闭DMA数据流后触发DMA中断,最终调用串口收发的回调函数 |
四、DMA2
4.1 配置串口2
在 Connectivity
中选择 USART2
设置,并选择 Asynchronous
异步通信
基础参数:波特率为115200 Bits/s
。传输数据长度为8 Bit
。奇偶检验无,停止位1 接收和发送都使能
NVIC Settings
一栏使能接收中断
4.2 配置DMA
根据DMA通道预览可以知道,点击 DMA Settings 添加 USART2 TX 和 USART2 RX 分别对应DMA1 的通道5和通道6。
●Direction: 传输方向
外设到内存 Peripheral To Memory
内存到外设 Memory To Peripheral
内存到内存 Memory To Memory
外设到外设 Peripheral To Peripheral
●Priority: 传输速度
最高优先级 Very Hight
高优先级 Hight
中等优先级 Medium
低优先级;Low
当发生多个 DMA 通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器也管理。仲裁器管理 DMA 通道请求分为两个阶段。第一阶段属于软件阶段,可以在 DMA_CCRx 寄存器中设置,有 4 个等级:非常高、高、中和低四个优先级。第二阶段属于硬件阶段,如果两个或以上的 DMA 通道请求设置的优先级一样,则他们优先级取决于通 道编号,编号越低优先权越高,比如通道 0 高于通道 1。在大容量产品和互联型产品中,DMA1 控制器拥有高于 DMA2 控制器的优先级。
■DMA Request Setting: DMA传输的对应外设
●Mode:传输模式
Normal 正常模式。表示当一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次。
Circular 循环模式。表示传输完成后又重新开始继续传输,不断循环永不停止。
●Increment Address:
Peripheral 表示外设地址自增。
Memory 表示内存地址自增。
串口发送数据是将数据不断存进串口的发送数据寄存器(USARTx_TDR)。所以外接的地址是不递增。而内存储器存储的是要发送的数据,所以地址指针要递增才能将所以的数据发送出去。
●Data Width:
Byte 一个字节。
Half Word 半个字,等于两字节。
Word 一个字,等于四字节。
串口数据发送寄存器只能存储8bit,每次发送一个字节,所以数据长度选择Byte。
4.3 生成代码
输入项目名和项目路径
选择应用的 IDE 开发环境 MDK-ARM V5
每个外设生成独立的 ’.c/.h’ 文件
不勾:所有初始化代码都生成在 main.c
勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。
点击 GENERATE CODE 生成代码
4.4 USART+DMA数据发送
新建一个变量
uint8_t sendBuff[] = "USART test by DMA\r\n";
在 man.c 中的主循环添加以下代码:
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
HAL_UART_Transmit_DMA(&huart2, (uint8_t *)sendBuff, sizeof(sendBuff));
HAL_Delay(1000);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
通过串口助手可以看到在接收区有数据不断的打印输出
注意:如果不开启串口中断,则程序只能发送一次数据,程序不能判断DMA传输是否完成,USART一直处于busy状态。
4.5 USART+DMA数据接收
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
uint8_t rxSize = 1;
/* 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 */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_DMA_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_UART_Receive_DMA(&huart2, (uint8_t *)Buffer, rxSize);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
在 stm32f1xx_it.c
这个文件的最下面添加 HAL_UART_RxCpltCallback()
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART2)
{
// 在这里处理接收到的数据,例如将数据存储到缓冲区或者显示在UI上
memcpy(Buffer, (uint8_t *)&huart2->Instance->DR, rxSize);
HAL_UART_Receive_DMA(&huart2, (uint8_t *)Buffer, rxSize);
}
}
通过串口助手发送 OK,可以看到接收到 O,这是因为设置的接收数据是一个字符,如果要接收更多字符,请加大 Buffer。
4.6 串口IDLE空闲中断+DMA数据接收
特点:
●可以实现任意字符串接收并输出。
●在串口无数据接收的情况下,不会产生,当清除IDLE标志位后,必须有接收到第一个数据后,才开始触发,一但接收的数据断流,没有接收到数据,即产生IDLE中断。
在 main.c 中添加以下变量:
uint8_t recvBuff[BUFFER_SIZE]; //接收数据缓存数组
volatile uint8_t recvLength = 0; //接收一帧数据的长度
volatile uint8_t recvDndFlag = 0; //一帧数据接收完成标志
在 main.h 中添加以下宏定义与变量:
#define BUFFER_SIZE 256
extern uint8_t recvBuff[BUFFER_SIZE]; //接收数据缓存
extern volatile uint8_t recvLength; //接收一帧数据的长度
extern volatile uint8_t recvDndFlag; //一帧数据接收完成标志
在 main.c 中,while 循环前,串口初始化后,添加空闲中断和DMA接收开启函数,这样在第一次接收到数据的时候才会触发中断。
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* 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 */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_DMA_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //使能IDLE中断
HAL_UART_Receive_DMA(&huart1, recvBuff, BUFFER_SIZE);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
在 stm32f1xx_it.c
这个文件的最下面修改 USART1_IRQHandler()
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
uint32_t tmpFlag = 0;
uint32_t temp;
tmpFlag =__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE); //获取IDLE标志位
if((tmpFlag != RESET))//idle标志被置位
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志位
HAL_UART_DMAStop(&huart1); //
temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);// 获取DMA中未传输的数据个数
recvLength = BUFFER_SIZE - temp; //总计数减去未传输的数据个数,得到已经接收的数据个数
recvDndFlag = 1; // 接受完成标志位置1
HAL_UART_Transmit_DMA(&huart1, recvBuff, recvLength);
recvLength = 0;//清除计数
recvDndFlag = 0;//清除接收结束标志位
memset(recvBuff,0,recvLength);
HAL_UART_Receive_DMA(&huart1, recvBuff, BUFFER_SIZE);//重新打开DMA接收,不然只能接收一次数据
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
通过串口助手发送不定长数据