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