一.关于DMA
1.什么是DMA?
答: DMA(Direct Memory Access,直接存储器访问) 是所有现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于CPU的大量中断负载。否则,CPU需要从来源把每一片段的资料复制到暂存器,然后把它们再次写回到新的地方。在这个时间中,CPU对于其他的工作来说就无法使用。*
2.DMA的意义是什么?
答: 简单的来说,能控制主存内部读写,这样有利于减轻CPU负担,加快读取速度。
3.串口使用DMA与不使用DMA有什么区别?
答: 区别可大了。通俗的讲:在没有DMA之前,串口每次发送数据时都要由CPU将源地址上的数据拷贝到串口发送的相关寄存器上;串口每次接收数据时都要由CPU将发送来的数据拷贝到主存上。而加了DMA后,只需要告诉DMA源地址和目标地址,DMA通道就能够自动进行数据的转移,即CPU只需要告诉DMA:串口需要发送的数据在哪里,串口接收到的数据应该存在哪里,运输的工作则交由DMA去做,运输期间CPU就可以去处理别的事情,这就大大提高了CPU的运行效率。
二.DMA的应用场景
DMA用在只需要传输数据,不需要处理数据的地方,有三种传输方式:
- 外设→存储器 (例如:将串口RDR寄存器写入某数据buf)
- 存储器→外设 (例如:将某数据buf写入串口TDR寄存器)
- 存储器→存储器(例如:复制某特别大的数据buf)
三.DMA控制器结构
Stm32F4最多有:2个DMA控制器,各8个数据流,每个数据流有8个通道(或请求),每个通道有一个仲裁器,用于处理请求的优先级。
四.Stm32实现串口DMA传输
开发环境:CubeMX Vesion 5.4.0
Keil Vesion 5.28
1.CubeMX配置串口DMA
打开串口一,同时打开串口接收中断、DMA发送、DMA接收。
2.DMA串口数据发送
/* @brief DMA串口发送函数(非阻塞)
* @param huart 串口句柄
* @param pData 发送的数据指针
* @param Size 数据量(数据的字节数)
* @retval HAL status HAL_OK、HAL_ERROR、HAL_BUSY、HAL_TIMEOUT
*/
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
在需要的地方调用HAL_UART_Transmit_DMA(······)即可完成数据发送,例如:
uint8_t data_16[4]={0x11,0x22,0x33,0x44};
uint8_t data_character[]="hello! I am 马云";
HAL_UART_Transmit_DMA(&huart1,data_16,4);
HAL_Delay(1);//等待上一次发送完毕后再开启下一次发送
HAL_UART_Transmit_DMA(&huart1,data_character, sizeof(data_character));
测试结果:
3.DMA串口数据接收
/* @brief 串口DMA接收函数
* @param huart 串口句柄
* @param pData 数据指针
* @param Size 数据量(数据字节数)
* @retval HAL status HAL_OK、HAL_ERROR、HAL_BUSY、HAL_TIMEOUT
*/
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
4.串口空闲中断(IDLE)
当DMA串口接收开始后,DMA通道会不断的将发送来的数据转移到主存,那么问题来了,该如何判断串口接收是否完成从而及时关闭DMA通道?如何知道接收到数据的长度?答案便是使用串口空闲中断。
串口空闲中断,对应事件标志为IDLE
检测到串口空闲线路时,该位由硬件置 1。如果 USART_CR1寄存器中 IDLEIE=1,则会生成中断。
该位由软件序列清零(读入 USART_SR 寄存器,然后读入 USART_DR 寄存器)
利用串口空闲中断,可以用如下流程实现DMA控制的任意长数据接收:
1.开启串口DMA接收
2.串口收到数据,DMA不断传输数据到存储buf
3.一帧数据发送完毕,串口暂时空闲,触发串口空闲中断
4.在中断服务函数中,可以计算刚才收到了多少个字节的数据
5.解码存储buf,清除标志位,开始下一帧接收
举个例子,如果要实现串口DMA不定长接收:
/*1.首先定义3个全局变量*/
uint8_t rx_buffer[100];//接收数组
volatile uint8_t rx_len = 0; //接收到的数据长度
volatile uint8_t recv_end_flag = 0; //接收结束标志位
/*2.在main中开启IDLE中断以及串口DMA接收*/
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
HAL_UART_Receive_DMA(&huart1,rx_buffer,100);
/*在CubeMX生成的 UART1中断服务函数中判断接收是否结束,如果结束就计算出接收到的数据长度*/
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 */
uint8_t tmp_flag =__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE); //获取IDLE标志位
if((tmp_flag != RESET))//通过标志位判断接收是否结束
{
recv_end_flag = 1; //置1表明接收结束
__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志位
HAL_UART_DMAStop(&huart1);
uint8_t temp=__HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
rx_len =100-temp; //计算出数据长度
HAL_UART_Transmit_DMA(&huart1, rx_buffer,rx_len);//将受到的数据发送出去
HAL_UART_Receive_DMA(&huart1,rx_buffer,100);//开启DMA接收,方便下一次接收数据
}
/* USER CODE END USART1_IRQn 1 */
}
在中断中加上HAL_UART_Transmit_DMA(&huart1, rx_buffer,rx_len)实现将受到的不定长数据发送出去,测试结果如下:
五.示例源码
1. HAL库+CubeMX+Stm32F405实现串口DMA不定长收发
六.同系列博客
一.关于DMA
1.什么是DMA?
答: DMA(Direct Memory Access,直接存储器访问) 是所有现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于CPU的大量中断负载。否则,CPU需要从来源把每一片段的资料复制到暂存器,然后把它们再次写回到新的地方。在这个时间中,CPU对于其他的工作来说就无法使用。*
2.DMA的意义是什么?
答: 简单的来说,能控制主存内部读写,这样有利于减轻CPU负担,加快读取速度。
3.串口使用DMA与不使用DMA有什么区别?
答: 区别可大了。通俗的讲:在没有DMA之前,串口每次发送数据时都要由CPU将源地址上的数据拷贝到串口发送的相关寄存器上;串口每次接收数据时都要由CPU将发送来的数据拷贝到主存上。而加了DMA后,只需要告诉DMA源地址和目标地址,DMA通道就能够自动进行数据的转移,即CPU只需要告诉DMA:串口需要发送的数据在哪里,串口接收到的数据应该存在哪里,运输的工作则交由DMA去做,运输期间CPU就可以去处理别的事情,这就大大提高了CPU的运行效率。
二.DMA的应用场景
DMA用在只需要传输数据,不需要处理数据的地方,有三种传输方式:
- 外设→存储器 (例如:将串口RDR寄存器写入某数据buf)
- 存储器→外设 (例如:将某数据buf写入串口TDR寄存器)
- 存储器→存储器(例如:复制某特别大的数据buf)
三.DMA控制器结构
Stm32F4最多有:2个DMA控制器,各8个数据流,每个数据流有8个通道(或请求),每个通道有一个仲裁器,用于处理请求的优先级。
四.Stm32实现串口DMA传输
开发环境:CubeMX Vesion 5.4.0
Keil Vesion 5.28
1.CubeMX配置串口DMA
打开串口一,同时打开串口接收中断、DMA发送、DMA接收。
2.DMA串口数据发送
/* @brief DMA串口发送函数(非阻塞)
* @param huart 串口句柄
* @param pData 发送的数据指针
* @param Size 数据量(数据的字节数)
* @retval HAL status HAL_OK、HAL_ERROR、HAL_BUSY、HAL_TIMEOUT
*/
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
在需要的地方调用HAL_UART_Transmit_DMA(······)即可完成数据发送,例如:
uint8_t data_16[4]={0x11,0x22,0x33,0x44};
uint8_t data_character[]="hello! I am 马云";
HAL_UART_Transmit_DMA(&huart1,data_16,4);
HAL_Delay(1);//等待上一次发送完毕后再开启下一次发送
HAL_UART_Transmit_DMA(&huart1,data_character, sizeof(data_character));
测试结果:
3.DMA串口数据接收
/* @brief 串口DMA接收函数
* @param huart 串口句柄
* @param pData 数据指针
* @param Size 数据量(数据字节数)
* @retval HAL status HAL_OK、HAL_ERROR、HAL_BUSY、HAL_TIMEOUT
*/
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
4.串口空闲中断(IDLE)
当DMA串口接收开始后,DMA通道会不断的将发送来的数据转移到主存,那么问题来了,该如何判断串口接收是否完成从而及时关闭DMA通道?如何知道接收到数据的长度?答案便是使用串口空闲中断。
串口空闲中断,对应事件标志为IDLE
检测到串口空闲线路时,该位由硬件置 1。如果 USART_CR1寄存器中 IDLEIE=1,则会生成中断。
该位由软件序列清零(读入 USART_SR 寄存器,然后读入 USART_DR 寄存器)
利用串口空闲中断,可以用如下流程实现DMA控制的任意长数据接收:
1.开启串口DMA接收
2.串口收到数据,DMA不断传输数据到存储buf
3.一帧数据发送完毕,串口暂时空闲,触发串口空闲中断
4.在中断服务函数中,可以计算刚才收到了多少个字节的数据
5.解码存储buf,清除标志位,开始下一帧接收
举个例子,如果要实现串口DMA不定长接收:
/*1.首先定义3个全局变量*/
uint8_t rx_buffer[100];//接收数组
volatile uint8_t rx_len = 0; //接收到的数据长度
volatile uint8_t recv_end_flag = 0; //接收结束标志位
/*2.在main中开启IDLE中断以及串口DMA接收*/
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
HAL_UART_Receive_DMA(&huart1,rx_buffer,100);
/*在CubeMX生成的 UART1中断服务函数中判断接收是否结束,如果结束就计算出接收到的数据长度*/
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 */
uint8_t tmp_flag =__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE); //获取IDLE标志位
if((tmp_flag != RESET))//通过标志位判断接收是否结束
{
recv_end_flag = 1; //置1表明接收结束
__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志位
HAL_UART_DMAStop(&huart1);
uint8_t temp=__HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
rx_len =100-temp; //计算出数据长度
HAL_UART_Transmit_DMA(&huart1, rx_buffer,rx_len);//将受到的数据发送出去
HAL_UART_Receive_DMA(&huart1,rx_buffer,100);//开启DMA接收,方便下一次接收数据
}
/* USER CODE END USART1_IRQn 1 */
}
在中断中加上HAL_UART_Transmit_DMA(&huart1, rx_buffer,rx_len)实现将受到的不定长数据发送出去,测试结果如下:
五.示例源码
1. HAL库+CubeMX+Stm32F405实现串口DMA不定长收发
六.同系列博客
举报