## DMA F103ZET usart2 DMA ***RX引脚配置为上拉 Pull-up*** ### CubeMX配置DMA #### dma.h dma.c 初始化 ```C void MX_DMA_Init(void) { /* DMA controller clock enable */ __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA interrupt init */ /* DMA1_Channel6_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Channel6_IRQn, 2, 2); HAL_NVIC_EnableIRQ(DMA1_Channel6_IRQn); /* DMA1_Channel7_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Channel7_IRQn, 2, 2); HAL_NVIC_EnableIRQ(DMA1_Channel7_IRQn); } ``` #### usart.c 定义DMA 结构体 DMA_HandleTypeDef hdma_usart2_rx; DMA_HandleTypeDef hdma_usart2_tx; 对应串口 HAL_UART_MspInit ```c /* USART2 DMA Init */ /* USART2_RX Init */ hdma_usart2_rx.Instance = DMA1_Channel6; hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart2_rx.Init.Mode = DMA_NORMAL; hdma_usart2_rx.Init.Priority = DMA_PRIORITY_LOW; if (HAL_DMA_Init(&hdma_usart2_rx) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart2_rx); /* USART2_TX Init */ hdma_usart2_tx.Instance = DMA1_Channel7; hdma_usart2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_usart2_tx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart2_tx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart2_tx.Init.Mode = DMA_NORMAL; hdma_usart2_tx.Init.Priority = DMA_PRIORITY_LOW; if (HAL_DMA_Init(&hdma_usart2_tx) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(uartHandle,hdmatx,hdma_usart2_tx); /* USART2 interrupt Init */ HAL_NVIC_SetPriority(USART2_IRQn, 0, 0); HAL_NVIC_EnableIRQ(USART2_IRQn); ``` 对应的 HAL_UART_MspDeInit HAL_DMA_DeInit(uartHandle->hdmarx); HAL_DMA_DeInit(uartHandle->hdmatx); ### stm32f1xx_it.h .c extern DMA_HandleTypeDef 引入DMA定义 void DMA1_Channel6_IRQHandler(void); void DMA1_Channel7_IRQHandler(void); void DMA1_Channel6_IRQHandler(void) { HAL_DMA_IRQHandler(&hdma_usart2_rx); } void DMA1_Channel7_IRQHandler(void) { HAL_DMA_IRQHandler(&hdma_usart2_tx); } ### main.h main.c #include "dma.h" MX_DMA_Init(); 在串口之前初始化,否则不能输出 ### usart dma 发送接收 HAL_UART_Transmit_DMA();串口DMA模式发送 HAL_UART_Transmit_DMA();串口DMA模式接收 HAL_UART_Receive_DMA(&huart2, Rxbuf, sizeof(Rxbuf)); UART一旦开启DMA之后,DMA收发中断都是强制开启的,所以DMA收发函数也可以编写回调函数。 ```c void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance==USART2) { HAL_UART_Transmit_DMA(&huart2, ACKbuf, sizeof(ACKbuf)); // HAL_UART_Receive_IT(&huart2, Rxbuf, sizeof(Rxbuf)); // 重新使用串口接收中断 } } ``` ### DMA 相关函数 __HAL_DMA_GET_COUNTER 获取DMA剩余未接收数据 HAL_UART_Transmit 串口阻塞方式发送函数 HAL_UART_Transmit_IT 串口中断方式发送函数 HAL_UART_Receive_IT 串口中断方式接收数据 HAL_UART_Transmit_DMA 串口DMA方式发送函数 HAL_UART_Receive_DMA 串口DMA方式接收函数 HAL_UART_TxCpltCallback 串口发送完成回调函数 HAL_UART_RxCpltCallback 串口接收完成回调函数-- 和中断模式函数一样 HAL_UART_RxHalfCpltCallback 串口接收过半回调函数 ### DMA 模式 普通模式:当传输数据量为0时,需要程序中手动将传输数据量重新设置满才能开启下一次的DMA数据传输。 循环模式:则当传输数据量为0时,会自动将传输数据量设置为满的,这样数据就能不断的传输。双缓冲 但有时为了安全,当我们设置的DMA缓冲区大小大于一次完整的数据时,当接收完一次数据后,Counter值还没有变为0,这时不会自动指向另一个缓冲区,所以这时候我们就需要手动更改指向下一个缓冲区。(也就是当第一帧数据传输完成后,Counter值不会自动填满,且内存区域还是指向Memory0,然后我们将剩余数据量保存下来,再将Counter值填满,接着把DMA指向Memory1,最后通过判断剩余数据量来决定是否对数据进行处理) 双缓冲的数据存储格式 uint8_t sbus_rx_buffer[2][18u]; DMA_InitStruct.DMA_BufferSize = 18; DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)&sbus_rx_buffer[0][0]; DMA_DoubleBufferModeConfig(DMA1_Stream1,(uint32_t)&sbus_rx_buffer[1][0],DMA_Memory_0); //first used memory configuration DMA_DoubleBufferModeCmd(DMA1_Stream1, ENABLE); 空闲中断计算 长度,及中间数据处理方法参考 ```c uint32_t temp; if(USART1 == huart1.Instance)//判断是否为串口1中断 { if(RESET != __HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE))//如果为串口1空闲 { __HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除中断标志 HAL_UART_DMAStop(&huart1);//停止DMA接收 temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);//获取DMA当前还有多少未填充 Rx_len_1 = BUFFERSIZE_1 - temp; //计算串口接收到的数据个数 HAL_UART_Transmit_DMA(&huart1,ReceiveBuff_1,Rx_len_1);//发送数据 Rx_len_1=0;//接收数据长度清零 HAL_UART_Receive_DMA(&huart1,ReceiveBuff_1,BUFFERSIZE_1);//开启下一次接收 } } ``` #### DMA 调试 监控半满,满状态。监控满后,指针是否重回开头接收 可以串口监控 huart.Init.BaudRate huart->RxXferSize 为Rx需要接收的大小 __HAL_DMA_GET_COUNTER(huart2.hdmarx) 可以获取剩余的空间 const uint8_t *pTxBuffPtr; /*!< Pointer to UART Tx transfer Buffer */ uint16_t TxXferSize; /*!< UART Tx Transfer size */ __IO uint16_t TxXferCount; /*!< UART Tx Transfer Counter */ uint8_t *pRxBuffPtr; /*!< Pointer to UART Rx transfer Buffer */ uint16_t RxXferSize; /*!< UART Rx Transfer size */ __IO uint16_t RxXferCount; /*!< UART Rx Transfer Counter */ __HAL_DMA_GET_COUNTER(huart2.hdmarx) 可以获取剩余的空间 获得 dma-init-CNDTR值 定义接收数据的结构 ```C typedef struct { uint8_t RxBuffer[RCV_BUF_LENGTH]; /* 接收缓冲区 */ uint16_t RxEndIndex; /* 尾索引值 */ FlagStatus RxEndFlag ; /* 接收结束标识 */ }Usart_BufTypeDef; ``` 半满中断标志: 标准库 :DMA_ITConfig(DMA1_Channel4, DMA_IT_HT, ENABLE); HAL库: __HAL_UART_ENABLE_IT(&huart2, DMA_IT_HT); 可以查DMA中断位置,中断函数要清除半满标记 开启半满中断后,可以看到半满的LED 翻转 如何解决 后续出现的错误?? 获得空余数 // __HAL_DMA_CLEAR_FLAG(&hdma_usart2_rx, DMA_IT_HT); __HAL_DMA_ENABLE_IT(&hdma_usart2_rx, DMA_IT_HT ); __HAL_UART_CLEAR_FLAG(&huart2, DMA_IT_HT); 解决串口发送DMA只能发送一次的问题 注意,在DMA传输过程中,会将串口从READY状态改为发送BUSY状态(软件修改):huart->gState = HAL_UART_STATE_BUSY_TX;但是在DMA完成本次传输工作以后,并没有将串口从发送BUSY状态改回READY状态 DMA 中断函数可以如下处理一些错误 hdma->XferHalfCpltCallback if (hdma->XferErrorCallback != NULL) { /* Transfer error callback */ hdma->XferErrorCallback(hdma); } ### 思路: DMA 采集, 采集立刻复制数据, 轮询处理数据, 判断是否收集完成