STM32
直播中

一曲作罢

9年用户 853经验值
擅长:可编程逻辑 电源/新能源 MEMS/传感技术
私信 关注
[问答]

DMA函数怎样去完成对串口数据的收发呢

IDE的串口该如何去实现呢?
DMA函数怎样去完成对串口数据的收发呢?

回帖(1)

仇国林

2021-12-8 09:26:33
        经过一段时间的学习,总结IDE环境的一些使用方法,纯属个人想法,多有不足,共享以讨论,望指正。
        IDE的串口实现很简单,通过在界面中勾选串口,以及相应的DMA、中断即可完成串口的驱动层配置,使用HAL_UART_Transmit_DMA函数和HAL_UART_Receive_DMA函数即可完成对串口数据的收发。
        但是程序的实现有些问题。在发送过程中,程序不能在任意情况下发送数据,需要等待上一次数据发送完成才能进行下一次发送(废话)。在接收过程中,HAL_UART_Receive_DMA函数必须指定接收数据的长度,当接收端不知道接收数据情况时,当收到的数据未能达到接收数据长度时,会造成始终等待而无法处理数据,做不到实时数据处理。
        为解决以上问题,在借助freertos系统的情况下,接收数据实时处理,发送数据可在程序的任何地方完成。
        发送程序处理思路:





        程序部分:



void  SendData(unsigned char *data, unsigned int length)
{
        osStatus status;
        unsigned char *p, *pSend_data;
        pTXBuffer pTx;
        unsigned int l;

        pSend_data = data;
        while(length)
        {
        //_UART_BUFFER_ 串口DMA buffer大小,数据长度超过buffer大小则自动分包
                l = (length > _UART_BUFFER_)?_UART_BUFFER_:length;
                p = get_memory(sizeof(TXBuffer) + l);//申请内存
                if(p)
                {
                        pTx = (pTXBuffer)p;
                        pTx->MsgLength = l;
                        pTx->pBuf = p + sizeof(TXBuffer);
                        memcpy(pTx->pBuf, pSend_data, l);//保存需要发送的数据

                        status = osMessagePut(UARTSendQueueHandle, (uint32_t)p, 0);//数据指针加入队列
                        if(status != osOK)
                        {
                                free_memory(p);//加入队列失败,释放内存
                        }
                        else ChipUartSend();//调用发送函数
                }
                else
                {

                }
        }
}
unsigned char uart3_send_buffer[_UART_BUFFER_] = {0};//DMA缓冲区
unsigned short uart3_length = 0;//记录发送数据长度,同时充当发送完成标志位

//该函数即会在中断中调用也会在任务中调用
void ChipUartSend(void)
{
        osEvent evt;
        pTXBuffer pTx;

        if(uart_length == 0)
        {
                evt = osMessageGet(UARTSendQueueHandle, 0);//从队列中获取消息
                if(evt.status == osEventMessage)//消息有效
                {
                        pTx = (pTXBuffer)evt.value.p;

            //注意发送函数中的数据指针会直接作为dma地址,因此需要将数据拷贝出来,以防函数结束造成内存问题
                        memcpy(uart3_send_buffer, pTx->pBuf, pTx->MsgLength);
                        uart_length = pTx->MsgLength;
                        HAL_UART_Transmit_DMA(&huart, uart3_send_buffer, uart3_length);
                        free_memory(evt.value.p);//无法发送是否正确均释放内存
                }
        }
}
//该函数会在中断中调用
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
        if(huart == &huart1)
        {
                uart_length = 0;//清除标志位
                ChipUartSend();
        }
}
      接收程序比较麻烦,即需要解决收到数据即触发中断又用到dma以降低CPU消耗,又需要解决接收到数据长度大于HAL_UART_Receive_DMA函数给出的长度造成的数据丢失。
        stm32芯片提供了很好的中断解决DMA下的数据实时接收问题,即UART_IT_IDLE中断,该中断在一包数据接收完成后触发,但是找遍了整个可函数均为发现对该中断的处理,只好自己实现。为不对库函数进行修改(免得每次生成代码时导致程序覆盖),通过串口中断回调函数处理。处理过程中需要停止DMA中断,以便置位库函数状态并重新调用HAL_UART_Receive_DMA函数开始新数据的接收,但是库函数提供的HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart)同时停止了发送DMA,会对发送造成影响,因此重写了对DMA的接收停止函数。
        当接收数据长度大于buffer长度时,需要调用接收回调函数将数据接收并重新开启新的接收过程。
        当所有数据接收完成时,在任务中处理就可以了。



HAL_StatusTypeDef HAL_UART_RX_DMAStop(UART_HandleTypeDef *huart)
{
        uint32_t dmarequest = 0x00U;

        /* Stop UART DMA Rx request if ongoing */
        dmarequest = HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR);
        if ((huart->RxState == HAL_UART_STATE_BUSY_RX) && dmarequest)
        {
                CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);

                /* Abort the UART DMA Rx stream */
                if (huart->hdmarx != NULL)
                {
                        HAL_DMA_Abort(huart->hdmarx);
                }
                /* Disable RXNE, PE and ERR (Frame error, noise error, overrun error) interrupts */
                CLEAR_BIT(huart->Instance->CR1, (USART_CR1_RXNEIE | USART_CR1_PEIE));
                CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);

                /* At end of Rx process, restore huart->RxState to Ready */
                huart->RxState = HAL_UART_STATE_READY;
        }

        return HAL_OK;
}
unsigned char g_rev_uart1_buffer[_UART_BUFFER_] = {0};//数据接收dma buffer

//中断回调函数
void L_User_UART_Irq(void)
{
        osStatus status;
        unsigned int rx_len = 0;
        unsigned int temp;

        if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) == SET) //获取IDLE标志位
        {
                __HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志位
                temp = huart1.Instance->SR;  //清除状态寄存器SR,读取SR寄存器可以实现清除SR寄存器的功能
                temp = huart1.Instance->DR; //读取数据寄存器中的数据
                HAL_UART_RX_DMAStop(&huart1);//停止DMA
                rx_len =  sizeof(g_rev_uart1_buffer) - hdma_usart1_rx.Instance->NDTR; //总计数减去未传输的数据个数,得到已经接收的数据个数

                if(rx_len)
                {
                        unsigned char *p;
                        pRXBuffer pRx;

                        p = get_memory(sizeof(RXBuffer) + rx_len);//获取内存
                        if(p)
                        {
                                pRx = (pRXBuffer)p;
                                pRx->MsgLength = rx_len;
                                pRx->pBuf = p + sizeof(RXBuffer);
                                memcpy(pRx->pBuf, g_rev_uart1_buffer, rx_len);//拷贝收到的数据
                                status = osMessagePut(ReceiveGSMQueueHandle, (uint32_t)p, 0);//将数据发送到队列中
                                if(status != osOK)
                                {
                                        free_memory(p);
                                }
                        }
                }
                HAL_UART_Receive_DMA(&huart1, g_rev_uart1_buffer, sizeof(g_rev_uart1_buffer));//重新开启数据接收
        }
//接收回调,处理过程与中断处理相同
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
        unsigned char *p;
        pRXBuffer pRx;
        unsigned int rx_len = 0;
        osStatus status;

        if(huart == &huart1)
        {
                HAL_UART_RX_DMAStop(&huart1); //停止DMA
                rx_len = sizeof(g_rev_uart1_buffer) - hdma_usart1_rx.Instance->NDTR;//可不计算,长度即为buffer长度
                p = get_memory(sizeof(RXBuffer) + rx_len);
                if(p)
                {
                        pRx = (pRXBuffer)p;
                        pRx->MsgLength = rx_len;
                        pRx->pBuf = p + sizeof(RXBuffer);
                        memcpy(pRx->pBuf, g_rev_uart1_buffer, rx_len);
                        status = osMessagePut(ReceiveGSMQueueHandle, (uint32_t)p, 0);
                        if(status != osOK)
                        {
                                free_memory(p);
                        }
                }
                HAL_UART_Receive_DMA(&huart1, g_rev_uart1_buffer, sizeof(g_rev_uart1_buffer));
        }
}


程序开始需要调用以下两句话,确保中断和DMA均开启,虽然我看可函数默认开启了所有中断。
        HAL_UART_Receive_DMA(&huart3, g_rev_uart3_buffer, sizeof(g_rev_uart3_buffer));
        __HAL_UART_ENABLE_IT(&huart3, UART_IT_IDLE);//使能idle中断

        测试采用循环发送进行,上位机发送一个大于DMA接收buffer长度的数据,并定时发送(1ms),下位机收到数据后原封不动返回上位机,检查上位机数据收发数量是否一致。经过长时间测试,不会丢失任何数据,稳定可靠。
举报

更多回帖

发帖
×
20
完善资料,
赚取积分