一、收发主要逻辑
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_DMAPause
DMA暂停和HAL_UART_DMAResume
DMA恢复。
如果使用HAL_UART_DMAStop
DMA停止和HAL_UART_Receive_DMA
DMA接收启用,会导致DMA一直从循环数组的开头存放数据。
这里Usart3Temp.offset_last
是本次数据的尾地址偏移量。
当然,DMA参数配置中要把模式设置为循环模式。
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:队列操作函数使用xQueueReceive
和xQueueSend
而不是osMessageGet
和osMessagePut
的原因:
由于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.发送数据守护任务
使用一个唯一的串口输出守护任务进行输出,任何任务的输出都需要将数据地址和长度通过结构体传入输出队列。
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 */
}