1 DMA概述
直接存储器访问 (DMA) : 用于在外设与存储器之间以及存储器与存储器之间进行高速数据传输。DMA传输过程的初始化和启动由CPU完成,传输过程由DMA控制器来执行,无需CPU参与,从而节省CPU资源,提高利用率。
DMA数据传输的四个要素:
传输源 :DMA数据传输的来源
传输目标:DMA数据传输的目的
传输数量:DMA传输数据的数量
触发信号:启动一次DMA数据传输的动作
STM32的DMA控制器特点
每个DMA控制器有8个数据流,每个数据流可以映射到8个通道(或请求);
每一个DMA控制器用于管理一个或多个外设的存储器访问请求,并通过总线仲裁器来协调各个DMA请求的优先级;
数据流(stream)是用于连接传输源和传输目标的数据通路,每个数据流可以配置为不同的传输源和传输目标,这些传输源和传输目标称为通道(Channel);
具备16字节的FIFO。使能FIFO功能后,源数据先送入FIFO,达到FIFO的触发阈值后,再传送到目标地址。
DMA数据传输方式
普通模式:传输结束后(即要传输数据的数量达到零),将不再产生DMA操作。若 开始新的DMA传输,需在关闭DMA通道情况下,重新启动DMA传输。
循环模式:可用于处理环形缓冲区和连续数据流(例如ADC扫描模式)。当激活循 环模式后,每轮传输结束时,要传输的数据数量将自动用设置的初始值进行加载,并继续响应DMA请求。
2 DMA方式的接口函数
-
串口DMA方式发送函数:HAL_UART_Transmit_DMA
函数原型 HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_Handle TypeDef *huart, uint 8_t *pData, uint 16_t Size) 功能描述 在DMA方式下发送一定数量的数据 入口参数1 huart:串口句柄的地址 入口参数 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_Handle TypeDef *huart, uint 8_t *pData, uint 16_t Size) 功能描述 在DMA方式下接收一定数量的数据 入口参数1 huart:串口句柄的地址 入口参数 pData:待接收数据的首地址 入口参数3 Size:待接收数据的个数 返回值 HAL状态值:HAL_OK表示发送成功;HAL_ERROR表示参数错误;HAL_BUSY表示串口被占用; 注意事项 1. 该函数将启动DMA方式的串口数据接收2. 完成指定数量的数据接收后,可以触发DMA中断,在中断中将调用接收中断回调函数HAL_UART_ExCpltCallback进行后续处理3. 该函数由用户调用户调用 -
获取未传输数据个数函数:__HAL_DMA_GET_COUNTER
函数原型 __HAL_DMA_GET_COUNTER 功能描述 获取DMA数据流中未传输数据的个数 参数 HANDLE :串口句柄的地址 返回值 NDTR寄存器的内容,即DMA数据流中无传输数据的个数 注意事项 1. 该函数是宏函数,进行宏替换,不发生函数调用2. 该函数需要由用户调用,用于获取未传输数据的个数 -
关闭DMA数据流:__HAL_DMA_DISABLE
函数原型 __HAL_DMA_DISABLE(__HANDLE__) 功能描述 关闭指定的DMA数据流 参数 HANDLE :串口句柄的地址 返回值 无 注意事项 1. 该函数是宏函数,进行宏替换,不发生函数调用2. 该函数需要由用户调用,用于关闭指定的DMA数据流3. 关闭DMA数据流后触发DMA中断,最终调用串口收发的回调函数
任务实践4
不定长数据的收发:利用串口调试助手,从PC上发送任意长度的字符到开发板,开发板收到后原样发回到PC。
空闲中断的特点:
在一帧数据传输结束后,通信线路将会维持高电平,这个状态称为空闲状态;
当CPU检测到通信线路处于空闲状态时,且空闲状态持续时间大于一个字节传输时间时,空闲状态标志IDLE将由硬件置1。如果串口控制寄存器CR1中的IDLEIE位为1,将会触发空闲中断( IDLE中断);
由于空闲标志是在一帧数据传输完成后才置位,在有效数据传输过程中不会置位,因此借助空闲中断,可以实现不定长数据的收发。
设计思路:
使能IDLE中断,在串口2的中断服务程序USART2_IRQHandler中添加对IDLE中断的判断,该函数位于stm32f4xx_it.c文件;
设置传输模式为普通模式,启动DMA传输。串口一旦接收到数据,则触发DMA操作,将数据存放到用户定义的接收缓冲区;
当一帧数据发送完成后,线路处于IDLE状态,将触发IDLE中断,调用IDLE中断回调函数,设置数据接收完成标志;
主程序检测到接收完成标志置位后,将接收的一帧数据原样发回到PC,并禁能DMA,以触发DMA中断。DMA中断将调用接收中断回调函数,在回调函数中重新启动DMA传输。
串口1的DMA配置
DMA数据流的中断使能由CubeMX自动勾选,手动使能串口2中断
编写程序
在stm32f1xx_it.c中添加空闲中断的处理
/**
* @brief This function handles USART1 global interrupt.
*/
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 */
// Add handling of idle interrupts
if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE != RESET))
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1); // Clear the IDLE interrupt flag
HAL_UART_IdleCpltCallback(&huart1); // User-written IDLE interrupt callback function
}
/* USER CODE END USART1_IRQn 1 */
}
在main.c中
添加用户宏变量、变量定义
/* USER CODE BEGIN PM */
#define LENGTH 100 // Receive buffer size
/* USER CODE END PM */
/* USER CODE BEGIN PV */
uint8_t RxBuffer[LENGTH];
uint8_t RecCount = 0;
uint8_t RxFlag = 0;
/* USER CODE END PV */
声明和定义空闲中断回调函数,定义DMA接收中断回调函数
/* USER CODE BEGIN PFP */
void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart)
/* USER CODE END PFP */
/* USER CODE BEGIN 4 */
int fputc (int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart- >Instance == USART1)
{
HAL_UART_Receive_DMA(&huart1, (uint8_t*)RxBuffer, LENGTH);
}
}
void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart)
{
RxFlag = 1;
}
/* USER CODE END 4 */
编写用户应用代码
复制
/* USER CODE BEGIN 2 */
printf("*** UART coummunication using IDLE IT + DMAn");
pringf("PLease enter arbitrary length characters:n");
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
HAL_UART_Receive_DMA(&huart1, (uint8_t*)RxBuffer, LENGTH);
/* USER CODE END 2 */
/* USER CODE BEGIN 3 */
if (RxFlag == 1)
{
RxFlag = 0;
RecCount = LENGTH - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
HAL_UART_Transmit_DMA(&huart1, (uint8_t*)RxBuffer, RecCount);
RecCount = 0;
__HAL_DMA_DISABLE(&hdma_uasrt1_rx);
}
}
/* USER CODE END 3 */