STM32CubeIDE下,串口DMA稳定传输实现

        经过一段时间的学习,总结IDE环境的一些使用方法,纯属个人想法,多有不足,共享以讨论,望指正。

        IDE的串口实现很简单,通过在界面中勾选串口,以及相应的DMA、中断即可完成串口的驱动层配置,使用HAL_UART_Transmit_DMA函数和HAL_UART_Receive_DMA函数即可完成对串口数据的收发。

        但是程序的实现有些问题。在发送过程中,程序不能在任意情况下发送数据,需要等待上一次数据发送完成才能进行下一次发送(废话)。在接收过程中,HAL_UART_Receive_DMA函数必须指定接收数据的长度,当接收端不知道接收数据情况时,当收到的数据未能达到接收数据长度时,会造成始终等待而无法处理数据,做不到实时数据处理。

        为解决以上问题,在借助freertos系统的情况下,接收数据实时处理,发送数据可在程序的任何地方完成。

        发送程序处理思路:

STM32CubeIDE下,串口DMA稳定传输实现_第1张图片

        程序部分:

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),下位机收到数据后原封不动返回上位机,检查上位机数据收发数量是否一致。经过长时间测试,不会丢失任何数据,稳定可靠。

你可能感兴趣的:(STM32CubeIDE下,串口DMA稳定传输实现)