一、收发主要逻辑

1、接收:利用DMA和空闲中断一次接收一帧的不定长数据,接收到数据后“暂停”(之后分析为何要暂停而不是停止)DMA,快速利用结构体存储接收到数据的地址和长度,再利用队列能够传递结构体的特点,将所收数据的地址和长度入队,然后打开DMA接收。数据处理任务检测队列状态,只要队列中有有效数据就开始数据处理。
2.发送:先将要发送的数据存入循环数组(伪)中,再利用队列将数据的地址和长度入队,利用一个串口输出的守护任务来保证数据传输的完整性,这个守护任务对地址和长度出队并使用DMA传输。

二、接收代码

接收和发送结构体

typedef struct
{
	uint8_t	lenth;
	uint8_t	offset_last;
}Usart3RXTempTypedef;

typedef struct
{
	uint8_t	lenth;
	uint8_t offset_last;
	uint8_t *addr;
}Usart3TxTypedef;

1.中断

void USART3_IRQHandler(void)
{
  /* USER CODE BEGIN USART3_IRQn 0 */
	uint32_t tmp_flag = 0;
	uint16_t temp;
	portBASE_TYPE xHigherPriorityTaskWoken= pdFALSE;
	tmp_flag =__HAL_UART_GET_FLAG(&huart3,UART_FLAG_IDLE);
	if((tmp_flag != RESET))
	{
		__HAL_UART_CLEAR_IDLEFLAG(&huart3);
		temp = huart3.Instance->SR;
		temp = huart3.Instance->DR;
		HAL_UART_DMAPause(&huart3);
		temp  = hdma_usart3_rx.Instance->CNDTR;

		if(temp	>=	(USART3_RxBuffer_Size - Usart3Temp.offset_last))//判断是否到达循环数组终点
			Usart3Temp.lenth =  2*USART3_RxBuffer_Size - temp - Usart3Temp.offset_last;
		else
			Usart3Temp.lenth =  USART3_RxBuffer_Size - temp - Usart3Temp.offset_last;
		Usart3Temp.offset_last = USART3_RxBuffer_Size - temp;
		xQueueSendFromISR(QueueUsart3RevHandle,(void *)&Usart3Temp,&xHigherPriorityTaskWoken);
		HAL_UART_DMAResume(&huart3);
	}
  /* USER CODE END USART3_IRQn 0 */
  HAL_UART_IRQHandler(&huart3);
  /* USER CODE BEGIN USART3_IRQn 1 */

  /* USER CODE END USART3_IRQn 1 */
}

这里DMA操作要使用HAL_UART_DMAPauseDMA暂停和HAL_UART_DMAResumeDMA恢复。

如果使用HAL_UART_DMAStopDMA停止和HAL_UART_Receive_DMADMA接收启用,会导致DMA一直从循环数组的开头存放数据。

这里Usart3Temp.offset_last是本次数据的尾地址偏移量。

当然,DMA参数配置中要把模式设置为循环模式。

freertos消息队列的使用案例 freertos消息队列串口_循环数组


2.接收数据任务:数据处理方式为收到数据后原样返回

void usart3RxTask(void const * argument)
{
  /* USER CODE BEGIN usart3RxTask */
  /* Infinite loop */
  for(;;)
  {
	  uint16_t	i;
	  uint8_t	RevCnt;
	  Usart3RxTypedef Usart3TempRev;
	  Usart3TxTypedef	Usart3Tx;
		/* Infinite loop */
		for(;;)
		{
			xQueueReceive(QueueUsart3RevHandle,(void*)&Usart3TempRev,portMAX_DELAY);
			if(Usart3TempRev.offset_last>=Usart3TempRev.lenth)
			{
				RevCnt=0;
				for(i=(Usart3TempRev.offset_last-Usart3TempRev.lenth);i<Usart3TempRev.offset_last;i++)
				{
					Usart3RevBuf[RevCnt] = USART3_Rx_Buffer[i];
					RevCnt++;
				}
			}
			else
			{
				RevCnt=0;
				for(i=(USART3_RxBuffer_Size	- (Usart3TempRev.lenth - Usart3TempRev.offset_last));i<USART3_RxBuffer_Size;i++)
				{
					Usart3RevBuf[RevCnt] = USART3_Rx_Buffer[i];
					RevCnt++;
				}
				for(i=0;i<Usart3TempRev.offset_last;i++)
				{
					Usart3RevBuf[RevCnt] = USART3_Rx_Buffer[i];
					RevCnt++;
				}
			}
			//将接收到的信息�?�过循环数组送出
			if((Usart3Tx.offset_last + RevCnt) > USART3_TxBuffer_Size)//如果溢出,则从数组的第一个地�?�?始储�?
				Usart3Tx.offset_last = 0;
			for(i=0;i<RevCnt;i++)
			{
				USART3_Tx_Buffer[Usart3Tx.offset_last]	= Usart3RevBuf[i];
				Usart3Tx.offset_last++;
			}
			Usart3Tx.lenth = RevCnt;
			xQueueSend(QueueUsart3TxHandle,&Usart3Tx,0);
			osThreadYield();
		}
  }
  /* USER CODE END usart3RxTask */
}

ps:队列操作函数使用xQueueReceivexQueueSend而不是osMessageGetosMessagePut的原因:
由于os封装的队列函数传递队列时只能传输uint32_t 类型的数据,明明定义结构体时定义了.v和.p还有.signal三中类型的数据接收

typedef struct  {
  osStatus                 status;     ///< status code: event or error information
  union  {
    uint32_t                    v;     ///< message as 32-bit value
    void                       *p;     ///< message or mail as void pointer
    int32_t               signals;     ///< signal flags
  } value;                             ///< event value
  union  {
    osMailQId             mail_id;     ///< mail id obtained by \ref osMailCreate
    osMessageQId       message_id;     ///< message id obtained by \ref osMessageCreate
  } def;                               ///< event definition
} osEvent;

到了队列出队函数封装里却只使用了.v来存储出队数据(虽然省去了区分xQueueReceiveFromISR在xQueueReceive的问题,但感觉过于鸡肋)

osEvent osMessageGet (osMessageQId queue_id, uint32_t millisec)
{
  portBASE_TYPE taskWoken;
  TickType_t ticks;
  osEvent event;
  
  event.def.message_id = queue_id;
  event.value.v = 0;
  
  if (queue_id == NULL) {
    event.status = osErrorParameter;
    return event;
  }
  
  taskWoken = pdFALSE;
  
  ticks = 0;
  if (millisec == osWaitForever) {
    ticks = portMAX_DELAY;
  }
  else if (millisec != 0) {
    ticks = millisec / portTICK_PERIOD_MS;
    if (ticks == 0) {
      ticks = 1;
    }
  }
  
  if (inHandlerMode()) {
    if (xQueueReceiveFromISR(queue_id, &event.value.v, &taskWoken) == pdTRUE) {
      /* We have mail */
      event.status = osEventMessage;
    }
    else {
      event.status = osOK;
    }
    portEND_SWITCHING_ISR(taskWoken);
  }
  else {
    if (xQueueReceive(queue_id, &event.value.v, ticks) == pdTRUE) {
      /* We have mail */
      event.status = osEventMessage;
    }
    else {
      event.status = (ticks == 0) ? osOK : osEventTimeout;
    }
  }
  
  return event;
}

3.发送数据守护任务

使用一个唯一的串口输出守护任务进行输出,任何任务的输出都需要将数据地址和长度通过结构体传入输出队列。

freertos消息队列的使用案例 freertos消息队列串口_i++_02

DMA模式要选择普通模式

守护任务:USART3_Tx_Buffer为串口发送缓存数组

void Usart3TxTask(void const * argument)
{
  /* USER CODE BEGIN Usart3TxTask */
	Usart3TxTypedef	Usart3Tx;
  /* Infinite loop */
  for(;;)
  {
	  xQueueReceive(QueueUsart3TxHandle,&Usart3Txtemp,portMAX_DELAY);
	  HAL_UART_Transmit_DMA(&huart3,(uint8_t*)(USART3_Tx_Buffer	+ Usart3Txtemp.offset_last - Usart3Txtemp.lenth),Usart3Txtemp.lenth);
	  osThreadYield();
  }
  /* USER CODE END Usart3TxTask */
}