完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
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 重新编译,烧录,运行便可; |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1632 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1559 浏览 1 评论
985 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
688 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1605 浏览 2 评论
1869浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
652浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
522浏览 3评论
539浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
508浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-25 11:16 , Processed in 0.782526 second(s), Total 79, Slave 62 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号