CPU开了一家公司,叫“搬砖有限公司”,刚开始,只有CPU老板会搬砖,每次需要搬砖头的时候,他都要自己把砖头从一个房间搬到另一个房间,影响他做其他事的效率。后面,他招了一个搬砖小能手,名字叫DMA,现在,每次需要搬砖头的时候,他只需要告诉DMA:“一次搬2块,搬1024块砖,从研发部,搬到生产部”就行了,他可以解放双手,做更重要的事。DMA搬完砖头后,会跟老板汇报:“老板,搬完了”。于是,公司效率提高了。
砖头,是数据。房间,是存储器,或者外设(如串口等)。搬砖,就是把数据从存储器传输到存储器,或者从存储器传输到外设,或者从外设传输到存储器。
由此可见,DMA(Direct Memory Access)的用途主要是数据传输。
在《STM32 Uart及其配置》我们讲了 Stm32 Uart 轮询接收数据。
在《STM32 Uart中断接收》我们讲了 Stm32 Uart 中断接收数据。
在这篇,我们来讲 Stm32 Uart DMA方式接收数据。
先配置下串口,参照《STM32 Uart及其配置》,接下来设置DMA,在Configuration页,点DMA,进入DMA配置页面:
在DMA配置页面,点Add,在DMA Request那一栏,会出现Select,点它,既然是接收,当然选择 UART4_RX了。
接下来还有几个参数需要设置:Mode、Increment Address、Use Fifo、Data Width、Burst Size,就先说一下这几个参数的含义吧。
Mode:有两个选项,Normal,Circular,简单地说,Normal模式下,Buffer满了,就算了,不再接收,下次要接收,还得重新设置;而Circular模式下,Buffer满了,就从Buffer头开始继续接收,覆盖上一次的数据。文中结尾做了个测试。
别问我怎么知道的,请参考《RM0033 Reference manual》:
ncrement Address,也就是地址自加,外设也就是个单一的寄存器,地址不自加,而存储器,来一个数据,存储,然后指向下一地址,选择自加。
Data Width,数据宽度,有三个选项,Byte、Half Word、Word。
普及一个 Word、Half Word、Byte吧:
在32位机中,Word = 32bit,Half word = 16bit,Byte = 8bit。
在16位机中,Word = 16bit,Half word = 8bit,Byte = 8bit。
在 8 位机中,Word = 16bit,Half word = 8bit,Byte = 8bit。
这里的外设每次接收一个Byte,当然,就选Byte了。
FIFO:First In First Out,可以把它理解为一个缓冲区,用来缓存数据,它最大有4个WORD,可以设置它的门限(threshold)1个(1/4),2个(1/2),3个(3/4),全用(full)。
FIFO 传输,可以简单理解为 外设->FIFO->存储器,如图:
Burst Size:有Single、4、8、16这四个选项,Burst transfer,就是来一个dma请求,传输1(Single)、4、8、16个beats,而这个1(Single)、4、8、16,就是Burst Size。
注意,beats不是bytes,这个beats,取决于上面设置的数据宽度,如果设置的是word,那1beats = 1word = 32bits,如果设置的是half-word,那1beats = 1half-word = 16bit,如果设置的是Byte,那1beats = 1byte = 8bit。
讲了那么多,然而,我们并不需要用到FIFO,也不需要用到burst,我们只需要用sigle便可,当然,知道得多一点,总归会更好一点,也许下次遇到ADC->Memory,或者Memory->Memory,就用到了呢。
最终,我们的配置是这样的:
生成代码,打开工程,打开代码:
DMA初始化代码就是这一段:
/* UART4 DMA Init */
/* UART4_RX Init */
hdma_uart4_rx.Instance = DMA1_Stream2;
hdma_uart4_rx.Init.Channel = DMA_CHANNEL_4;
hdma_uart4_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; // 外设->存储器
hdma_uart4_rx.Init.PeriphInc = DMA_PINC_DISABLE; // 外设 Increment Address
hdma_uart4_rx.Init.MemInc = DMA_MINC_ENABLE; // 存储器 Increment Address
hdma_uart4_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; // 外设 Data Width
hdma_uart4_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; // 存储器 Data Width
hdma_uart4_rx.Init.Mode = DMA_NORMAL; // Mode = Normal
hdma_uart4_rx.Init.Priority = DMA_PRIORITY_LOW;
hdma_uart4_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; // 不使用fifo,下面三个参数,都没作用;
hdma_uart4_rx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_1QUARTERFULL;
hdma_uart4_rx.Init.MemBurst = DMA_MBURST_SINGLE;
hdma_uart4_rx.Init.PeriphBurst = DMA_PBURST_SINGLE;
if (HAL_DMA_Init(&hdma_uart4_rx) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
DMA的传输过程:来一个Uart数据,来一个DMA请求,DMA获得总线,把数据从外设传输至存储器,待数据传输完成(DMA_SxNDTR = 0),触发DMA完成中断;
有兴趣的同学可以仔细看一下《RM0033 Reference manual》,第9章节,DMA controller。
触发中断后干什么呢?跟下代码,可以看到,在经过一系列的条件判断后,调用了下面这个回调函数:
hdma->XferCpltCallback(hdma);
那这个回调函数在哪设置呢?看一下 这个函数:
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
uint32_t *tmp;
/* Check that a Rx process is not already ongoing */
if(huart->RxState == HAL_UART_STATE_READY)
{
if((pData == NULL ) || (Size == 0))
{
return HAL_ERROR;
}
/* Process Locked */
__HAL_LOCK(huart);
huart->pRxBuffPtr = pData;
huart->RxXferSize = Size;
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->RxState = HAL_UART_STATE_BUSY_RX;
/* Set the UART DMA transfer complete callback */
huart->hdmarx->XferCpltCallback = UART_DMAReceiveCplt; // 传输完成回调函数
/* Set the UART DMA Half transfer complete callback */
huart->hdmarx->XferHalfCpltCallback = UART_DMARxHalfCplt;
/* Set the DMA error callback */
huart->hdmarx->XferErrorCallback = UART_DMAError;
/* Set the DMA abort callback */
huart->hdmarx->XferAbortCallback = NULL;
/* Enable the DMA Stream */
tmp = (uint32_t*)&pData;
HAL_DMA_Start_IT(huart->hdmarx, (uint32_t)&huart->Instance->DR, *(uint32_t*)tmp, Size);
/* Clear the Overrun flag just before enabling the DMA Rx request: can be mandatory for the second transfer */
__HAL_UART_CLEAR_OREFLAG(huart);
/* Process Unlocked */
__HAL_UNLOCK(huart);
/* Enable the UART Parity Error Interrupt */
SET_BIT(huart->Instance->CR1, USART_CR1_PEIE);
/* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
SET_BIT(huart->Instance->CR3, USART_CR3_EIE);
/* Enable the DMA transfer for the receiver request by setting the DMAR bit
in the UART CR3 register */
SET_BIT(huart->Instance->CR3, USART_CR3_DMAR);
return HAL_OK;
}
else
{
return HAL_BUSY;
}
}
跟一下传输完成回调函数 UART_DMAReceiveCplt
static void UART_DMAReceiveCplt(DMA_HandleTypeDef *hdma)
{
UART_HandleTypeDef* huart = ( UART_HandleTypeDef* )((DMA_HandleTypeDef* )hdma)->Parent;
/* DMA Normal mode*/
if((hdma->Instance->CR & DMA_SxCR_CIRC) == 0U)
{
huart->RxXferCount = 0;
/* Disable RXNE, PE and ERR (Frame error, noise error, overrun error) interrupts */
CLEAR_BIT(huart->Instance->CR1, USART_CR1_PEIE);
CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);
/* Disable the DMA transfer for the receiver request by setting the DMAR bit
in the UART CR3 register */
CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);
/* At end of Rx process, restore huart->RxState to Ready */
huart->RxState = HAL_UART_STATE_READY;
}
HAL_UART_RxCpltCallback(huart); // 重写这个函数
}
历经千山万水,最终还是回到重写这个函数上了,参考《STM32 Uart中断接收》。
好了,开始增加我们自己的代码了。
第一步,先声明一个Buffer,用于接收数据:
uint8_t rcvBuffer[1024] = {0xAA};
第二步,确定要接收的字节数:
#define SIZE_RCV 8
第三步,在初始化之后,打开DMA,若有数据,系统会自动把数据写入Buffer里面:
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_GPIO_Init();
MX_DMA_Init();
MX_UART4_Init();
/* USER CODE BEGIN 2 */
HAL_UART_Receive_DMA(&huart4, rcvBuffer, SIZE_RCV); // 这里加这个函数,接收数据
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
第四步,重写回调函数:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
HAL_UART_Transmit(&huart4, rcvBuffer,SIZE_RCV, 1000); // 把数据原封不动还给你
HAL_UART_Receive_DMA(&huart4, rcvBuffer, SIZE_RCV); // 重新打开DMA接收数据
}
好了,编译,烧录,运行,看结果。
看,发8个数据,它就收8个数据。
那么,如果发9个数据呢?它也是收8个数据。如果发7个数据呢?不足8个,不会触发DMA中断。
大家可以自行试一下。
最后,大家可以尝试一下,Mode设置为Circular,结果会是怎么样的?
发送8个字节数据试一下?再发送9个字节试一下?发送第一次?第二次,有什么区别?
只需要把这个配置
hdma_uart4_rx.Init.Mode = DMA_NORMAL; // Mode = Normal
更改为
hdma_uart4_rx.Init.Mode = DMA_CIRCULAR; // Mode = Circular
重新编译,烧录,运行便可;
整个工程及代码呢,请上百度网盘上下载:
链接:https://pan.baidu.com/s/19usUcgZPX8cCRTKt_NPcfg
密码:07on
文件夹:\Stm32CubeMx\Code\UartRx_DMA.rar
上一篇:《STM32 Uart中断接收》
下一篇:《STM32 Uart 接收变长数据》
回目录:《前言》