【Mini-F5265-OB】4、串口不定长收发-电子产品世界论坛
在上一篇中,我分享了如何使用串口接收中断+空闲中断来实现不定长数据的收发。虽然做到了高效的数据收发,但是会频繁进入中断,那么这一篇我将采用数据搬动工DMA来实现高效的串口不定长接收。
【开发环境】
灵动微【Mini-F5265-OB】开发板。
【灵动微DMA简介】
Mini-F5265-OB所使用的主控MM32F5265,内置 2 个 8 路通用 DMA 可以管理存储器到存储器、设备到存储器和存储器到设备 的数据传输;DMA 控制器支持环形缓冲区的管理,避免了控制器传输到达缓冲区结尾时所 产生的中断。 每个通道都有专门的硬件 DMA 请求逻辑,同时可以由软件触发每个通道;传输的长度、 传输的源地址和目标地址都可以通过软件单独设置。
【DMA通道选择】
通过用户手,我这里使用uart3的DMA通道为DMA1的通道2、通道3。
【程序设计】
1、配置串口,开启DMA接收,并开启中断发送与接收。
2、配置串口,开启空闲中断,当进入空闲中断时,视为接收完一次完整的数据。通知数据可以进行处理。本篇这里直接使用串口DMA回传给串口。
【代码】
1、串口初始化:
/*********************************************************************************************************************** * @brief Initialize console for printf * @note none * @param Baudrate : UART3 communication baudrate * @retval none *********************************************************************************************************************/ void PLATFORM_InitConsole(uint32_t Baudrate) { GPIO_InitTypeDef GPIO_InitStruct; UART_InitTypeDef UART_InitStruct; NVIC_InitTypeDef NVIC_InitStruct; RCC_APB1PeriphClockCmd(DEBUG_UART_RCC, ENABLE); UART_StructInit(&UART_InitStruct); UART_InitStruct.BaudRate = Baudrate; UART_InitStruct.WordLength = UART_WordLength_8b; UART_InitStruct.StopBits = UART_StopBits_1; UART_InitStruct.Parity = UART_Parity_No; UART_InitStruct.HWFlowControl = UART_HWFlowControl_None; UART_InitStruct.Mode = UART_Mode_Tx | UART_Mode_Rx;; UART_Init(DEBUG_UART, &UART_InitStruct); UART_DMACmd(UART3, UART_DMAReq_EN, ENABLE); //开启DMA UART_ITConfig(DEBUG_UART, UART_IT_RXIDLE, ENABLE); //开启空闲中断 RCC_AHBPeriphClockCmd(DEBUG_UART_TX_GPIO_RCC, ENABLE); GPIO_PinAFConfig(DEBUG_UART_TX_PORT, DEBUG_UART_TX_PIN_SOURC, DEBUG_UART_GPIO_AF); GPIO_PinAFConfig(DEBUG_UART_RX_PORT, DEBUG_UART_RX_PIN_SOURC, DEBUG_UART_GPIO_AF); GPIO_StructInit(&GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = DEBUG_UART_TX_PIN; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(DEBUG_UART_TX_PORT, &GPIO_InitStruct); GPIO_StructInit(&GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = DEBUG_UART_RX_PIN; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(DEBUG_UART_RX_PORT, &GPIO_InitStruct); NVIC_InitStruct.NVIC_IRQChannel = DEBUG_UART_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); UART_ClearITPendingBit(UART3, 0x7FF); UART_Cmd(DEBUG_UART, ENABLE); }
在初始中,特别值得注意,就是需要开启DMA数据接收与发送。
2、串口DMA发送代码:
/*********************************************************************************************************************** * @brief * @note none * @param none * @retval none *********************************************************************************************************************/ void UART_TxData_DMA_Interrupt(uint8_t *Buffer, uint8_t Length) { DMA_InitTypeDef DMA_InitStruct; NVIC_InitTypeDef NVIC_InitStruct; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_DeInit(DMA1_Channel2); DMA_StructInit(&DMA_InitStruct); DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&(UART3->TDR); DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)Buffer; DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralDST; DMA_InitStruct.DMA_BufferSize = Length; DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; DMA_InitStruct.DMA_Priority = DMA_Priority_VeryHigh; DMA_InitStruct.DMA_M2M = DMA_M2M_Disable; DMA_InitStruct.DMA_Auto_Reload = DMA_Auto_Reload_Disable; DMA_Init(DMA1_Channel2, &DMA_InitStruct); DMA_ClearFlag(DMA1_FLAG_TC2); DMA_ITConfig(DMA1_Channel2, DMA_IT_TC, ENABLE); NVIC_InitStruct.NVIC_IRQChannel = DMA1_CH2_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); UART_TX_DMA_InterruptFlag = 0; DMA_Cmd(DMA1_Channel2, ENABLE); }
这个函数中,我们将发送的缓冲区与长度传入,最后开启DMA传输。
3、DMA数据接收。
/*********************************************************************************************************************** * @brief * @note none * @param none * @retval none *********************************************************************************************************************/ void UART_RxData_DMA_Interrupt(uint8_t *Buffer, uint8_t Length) { DMA_InitTypeDef DMA_InitStruct; NVIC_InitTypeDef NVIC_InitStruct; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_DeInit(DMA1_Channel3); DMA_StructInit(&DMA_InitStruct); DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&(UART3->RDR); DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)Buffer; DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStruct.DMA_BufferSize = Length; DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; DMA_InitStruct.DMA_Priority = DMA_Priority_VeryHigh; DMA_InitStruct.DMA_M2M = DMA_M2M_Disable; DMA_InitStruct.DMA_Auto_Reload = DMA_Auto_Reload_Disable; DMA_Init(DMA1_Channel3, &DMA_InitStruct); DMA_ClearFlag(DMA1_FLAG_TC3); DMA_ITConfig(DMA1_Channel3, DMA_IT_TC, ENABLE); NVIC_InitStruct.NVIC_IRQChannel = DMA1_CH3_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); UART_RX_DMA_InterruptFlag = 0; DMA_Cmd(DMA1_Channel3, ENABLE); }
这里我们传入接收到的缓冲区地址,使用外设到内存的方式。开启DMA空闲接收。
4、空闲中断回调函数。
/*********************************************************************************************************************** * @brief This function handles UART1 Handler * @note none * @param none * @retval none *********************************************************************************************************************/ void UART3_IRQHandler(void) { if (SET == UART_GetITStatus(UART3, UART_IT_RXIDLE)) { UART_ClearITPendingBit(UART3, UART_IT_RXIDLE); UART_RxLength = DMA_RX_MAX_LEN - DMA_GetCurrDataCounter(DMA1_Channel3);; } }
在空头接收中断中,我这里首先查询到接收缓冲区的剩余空间,计算出已接收到的字符数。通过UART_RxLength来传递已经收到的数。
5、空闲发送与接收代码:
/*********************************************************************************************************************** * @brief This function handles UART1 Handler * @note none * @param none * @retval none *********************************************************************************************************************/ void UART3_IRQHandler(void) { if (SET == UART_GetITStatus(UART3, UART_IT_RXIDLE)) { UART_ClearITPendingBit(UART3, UART_IT_RXIDLE); UART_RxLength = DMA_RX_MAX_LEN - DMA_GetCurrDataCounter(DMA1_Channel3);; } } /*********************************************************************************************************************** * @brief This function handles DMA1_CH4 Handler * @note none * @param none * @retval none *********************************************************************************************************************/ void DMA1_CH2_IRQHandler(void) { if (SET == DMA_GetITStatus(DMA1_IT_TC2)) { DMA_Cmd(DMA1_Channel2, DISABLE); UART_TX_DMA_InterruptFlag = 1; DMA_ClearITPendingBit(DMA1_IT_TC2); } }
在发送DMA中断,如果进入回调函数,说明发送已结束,清除中断,并更新发送完毕的参数。
在接收DMA中断回调中,我这里会设计比较长的接收buff,使他不会进入回调函数。
【测试代码】
/*********************************************************************************************************************** * @brief This function is main entrance * @note main * @param none * @retval none *********************************************************************************************************************/ int main(void) { PLATFORM_Init(); UART_RxLength = 0; // GPIO_LED_Toggle_Sample(); UART_RxData_DMA_Interrupt(UART_RxBuffer,DMA_RX_MAX_LEN); while (1) { if (UART_RxLength) { UART_TxData_DMA_Interrupt((uint8_t *)UART_RxBuffer, UART_RxLength); while (0 == UART_TX_DMA_InterruptFlag) { } UART_RxLength = 0; UART_RxData_DMA_Interrupt((uint8_t *)UART_RxBuffer,DMA_RX_MAX_LEN); } } }
在主程序中,我们判断接收的长度是否为零,如果不为零,则使用DMA回传给串口。
在发送结速之后,我们又重新开启DMA接收。
【实验现象】