FreeRTOS-串口DMA收发不定长数据+队列
本帖最后由 zero949079783 于 2024-11-10 23:39 编辑FreeRTOS-串口DMA收发不定长数据+队列
#include "Usartapp.c"
#include "main.h"
#define Usart1_RX_TASK_PRIO 3 /* 任务优先级 */
#define Usart1_RX_STK_SIZE 512 /* 任务堆栈大小 */
TaskHandle_t Usart1_RXTask_Handler; /* 任务句柄 */
void Usart1_RX_task(void *pvParameters);
#define Usart1_TX_TASK_PRIO 4 /* 任务优先级 */
#define Usart1_TX_STK_SIZE 512 /* 任务堆栈大小 */
TaskHandle_t Usart1_TX_Task_Handler; /* 任务句柄 */
void Usart1_TX_task(void *pvParameters);
/*队列句柄*/
QueueHandle_t RX_queue; //串口RX队列
QueueHandle_t TX_queue; //串口RX队列
uint16_t bufflen = 0; //接收数据长度
void Usart1_RX_Isr_FunCb(uint8_t *data,uint16_t datalen)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
bufflen = datalen;
xQueueSendFromISR(RX_queue,&data,&xHigherPriorityTaskWoken); //将数据放入消息队列
if(xHigherPriorityTaskWoken == pdTRUE)
{
portYIELD_FROM_ISR(xHigherPriorityTaskWoken); //如果需要就切换任务
}
}
void Usart1_RX_task(void *pvParameters)
{
pvParameters=pvParameters;
Usart1_RX_FunCb(Usart1_RX_Isr_FunCb);
uint8_t res = 0;
uint8_t * rev_data ;
while(1)
{
res=xQueueReceive(RX_queue,&rev_data,portMAX_DELAY); //接收队列的数据
if(res == pdPASS)
{
Usart1_Txdata(rev_data,bufflen);//向队列放入数据
}
}
}
void Usart1_TX_Fun(uint8_t *data,uint16_t datalen)
{
xQueueSend(TX_queue,&data,portMAX_DELAY); //发送队列的数据
}
void Usart1_TX_task(void *pvParameters)
{
pvParameters=pvParameters;
uint8_t res = 0;
uint8_t *tx_buff;
Usart1_TX_FunCb(Usart1_TX_Fun);
while(1)
{
res = xQueueReceive(TX_queue,&tx_buff,portMAX_DELAY); //接收TX队列的数据
if(res == pdPASS)
{
Uart1_DMA_ENABLE(tx_buff,bufflen); //启动DMA
}
}
}
void UsartAPP_Task_Init(void)
{
RX_queue = xQueueCreate(10,sizeof(uint8_t *)); //串口RX队列,接收为一桢数据 存放地址就可以
if(RX_queue != NULL)
{
printf("\r\n串口RX队列创建成功\r\n");
}
TX_queue = xQueueCreate(10,sizeof(uint8_t *)); //串口TX队列,接收为一桢数据 存放地址就可以
if(TX_queue != NULL)
{
printf("\r\n串口TX队列创建成功\r\n");
}
/* Usart1_RX 任务 */
xTaskCreate((TaskFunction_t )Usart1_RX_task, /* 任务函数 */
(const char* )"Usart1_RX_task", /* 任务名称 */
(uint16_t )Usart1_RX_STK_SIZE, /* 任务堆栈大小 */
(void* )NULL, /* 传递给任务函数的参数 */
(UBaseType_t )Usart1_RX_TASK_PRIO, /* 任务优先级 */
(TaskHandle_t*)&Usart1_RX_task); /* 任务句柄 */
xTaskCreate((TaskFunction_t )Usart1_TX_task, /* 任务函数 */
(const char* )"Usart1_TX_task", /* 任务名称 */
(uint16_t )Usart1_TX_STK_SIZE, /* 任务堆栈大小 */
(void* )NULL, /* 传递给任务函数的参数 */
(UBaseType_t )Usart1_TX_TASK_PRIO, /* 任务优先级 */
(TaskHandle_t*)&Usart1_TX_task); /* 任务句柄 */
}
usart_drv.c#include "usart_drv.h"
UCB uart1; //串口1控制结构体
uint8_t Usart1_RxBuff; //串口1接收缓冲区
uint8_t Usart1_TxBuff; //串口1发送缓冲区
static void(*USART1_TXFun)(uint8_t *data,uint16_t data_len); //函数指针
void Usart1_TX_FunCb(void(*pFunc)(uint8_t *data,uint16_t datalen))
{
USART1_TXFun = pFunc;
}
void Usart1_Txdata(uint8_t *data,uint32_t data_len)
{
if(data_len == 0) //防止发送0个数据
{
uart1.TxState = 0;
uart1.TxCounter = 0;
return ;
}
if((U1_TX_SIZE - uart1.TxCounter) >= data_len)
{
USART1_TXFun(data,data_len);
}
}
void Uart1_DMA_ENABLE(uint8_t *data,uint32_t data_len)
{
HAL_UART_Transmit_DMA(&uart1.uart,data,data_len);
}
/*************************************************************************
* 函 数 名: UART1_Init
* 功能说明: 串口1初始化
* 形 参:无
* 返 回 值: 无
**************************************************************************/
void UART1_Init(uint32_t baudrate)
{
memset(&uart1.uart,0,sizeof(uart1.uart));
uart1.uart.Instance = USART1;
uart1.uart.Init.BaudRate = baudrate;
uart1.uart.Init.WordLength =UART_WORDLENGTH_8B;
uart1.uart.Init.StopBits=UART_STOPBITS_1;
uart1.uart.Init.Parity=UART_PARITY_NONE;
uart1.uart.Init.Mode=UART_MODE_TX_RX;
uart1.uart.Init.HwFlowCtl=UART_HWCONTROL_NONE;
HAL_UART_Init(&uart1.uart);
__HAL_UART_CLEAR_IDLEFLAG(&uart1.uart);
__HAL_UART_ENABLE_IT(&uart1.uart,UART_IT_IDLE);
HAL_UART_Receive_DMA(&uart1.uart,Usart1_RxBuff,U1_RX_MAX);
}
uint8_t tempbuff1={0};
void Usart1_printf(char *fmt,...)
{
uint16_t i;
va_listap;
va_start(ap,fmt);
vsprintf((char *)tempbuff1,fmt,ap);
va_end(ap);
for(i=0;i<strlen((char *)tempbuff1);i++)
{
while(!__HAL_UART_GET_FLAG(&uart1.uart,UART_FLAG_TXE))
{
}
uart1.uart.Instance->TDR = tempbuff1;
}
while(!__HAL_UART_GET_FLAG(&uart1.uart,UART_FLAG_TC))
{
}
}
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
GPIO_InitTypeDef GPIO_InitTypeDefStructur;
if(huart ->Instance == USART1)
{
__HAL_RCC_DMA2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_USART1_CLK_ENABLE();
GPIO_InitTypeDefStructur.Pin =GPIO_PIN_9;
GPIO_InitTypeDefStructur.Mode = GPIO_MODE_AF_PP;
GPIO_InitTypeDefStructur.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitTypeDefStructur.Pull=GPIO_PULLUP;
GPIO_InitTypeDefStructur.Alternate=GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA,&GPIO_InitTypeDefStructur);
GPIO_InitTypeDefStructur.Pin =GPIO_PIN_10;
GPIO_InitTypeDefStructur.Alternate=GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA,&GPIO_InitTypeDefStructur);
HAL_NVIC_SetPriority(USART1_IRQn,7,0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
//TX
uart1.dmatx.Instance = DMA2_Stream7;
uart1.dmatx.Init.Request= DMA_REQUEST_USART1_TX; /* USART1发送DMA */
uart1.dmatx.Init.Direction = DMA_MEMORY_TO_PERIPH; /* 存储器到外设 */
uart1.dmatx.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设非增量模式 */
uart1.dmatx.Init.MemInc = DMA_MINC_ENABLE; /* 存储器增量模式 */
uart1.dmatx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
uart1.dmatx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
uart1.dmatx.Init.Mode = DMA_NORMAL;
uart1.dmatx.Init.Priority=DMA_PRIORITY_MEDIUM;
uart1.dmatx.Init.FIFOThreshold=DMA_FIFOMODE_DISABLE;
HAL_DMA_Init(&uart1.dmatx);
__HAL_LINKDMA(huart,hdmatx,uart1.dmatx); //连接DMA通道
HAL_NVIC_SetPriority(DMA2_Stream7_IRQn,7,0);
HAL_NVIC_EnableIRQ(DMA2_Stream7_IRQn);
//RX
uart1.dmarx.Instance = DMA2_Stream2; /* 数据流选择 */
uart1.dmarx.Init.Request= DMA_REQUEST_USART1_RX; /* USART1发送DMA */
uart1.dmarx.Init.Direction = DMA_PERIPH_TO_MEMORY;
uart1.dmarx.Init.PeriphInc = DMA_PINC_DISABLE;
uart1.dmarx.Init.MemInc = DMA_MINC_ENABLE;
uart1.dmarx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
uart1.dmarx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
uart1.dmarx.Init.Mode = DMA_NORMAL;
uart1.dmarx.Init.Priority=DMA_PRIORITY_MEDIUM;
uart1.dmarx.Init.FIFOThreshold=DMA_FIFOMODE_DISABLE;
__HAL_LINKDMA(huart,hdmarx,uart1.dmarx); //连接DMA通道
HAL_DMA_Init(&uart1.dmarx);
HAL_NVIC_SetPriority(DMA2_Stream2_IRQn,7,0);
HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);
}
}
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart ->Instance == USART1)
{
uart1.TxState=0;
}
}
void Usart1_Send_Data(uint8_t *data,uint16_t datalen)
{
uint16_t i;
for(i=0;i<datalen;i++)
{
while(!__HAL_UART_GET_FLAG(&uart1.uart,UART_FLAG_TXE));
uart1.uart.Instance->TDR = data;
}
while(!__HAL_UART_GET_FLAG(&uart1.uart,UART_FLAG_TC));
}
static void(*USART1_RXFun)(uint8_t *data,uint16_t datalen); //函数指针
void Usart1_RX_FunCb(void(*pFunc)(uint8_t *data,uint16_t datalen))
{
USART1_RXFun = pFunc;
}
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&uart1.uart);
if(__HAL_UART_GET_FLAG(&uart1.uart,UART_FLAG_IDLE))
{
__HAL_UART_CLEAR_IDLEFLAG(&uart1.uart); //清除空闲中断标志
__HAL_DMA_DISABLE_IT(&uart1.dmarx,DMA_IT_HT);//禁止DMA传输过半中断
uart1.RxCounter = ((U1_RX_MAX) - __HAL_DMA_GET_COUNTER(&uart1.dmarx)); //计算本次的接收数据量
if(USART1_RXFun != NULL)
{
USART1_RXFun(Usart1_RxBuff,uart1.RxCounter);
}
HAL_UART_DMAStop(&uart1.uart); //停止DMA
HAL_UART_Receive_DMA(&uart1.uart,Usart1_RxBuff,U1_RX_MAX);
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart ->Instance == USART1)
{
if(__HAL_DMA_GET_FLAG(&uart1.dmarx, DMA_FLAG_TCIF2_6) == RESET)
{
__HAL_DMA_CLEAR_FLAG(&uart1.dmarx, DMA_FLAG_TCIF2_6); //清除DMA2_Steam2传输完成标志
HAL_UART_DMAStop(&uart1.uart); //停止DMA
HAL_UART_Receive_DMA(&uart1.uart,Usart1_RxBuff,U1_RX_MAX+1);
}
if(__HAL_DMA_GET_FLAG(&uart1.dmarx, DMA_FLAG_TCIF3_7) == RESET)
{
__HAL_DMA_CLEAR_FLAG(&uart1.dmarx, DMA_FLAG_TCIF3_7); //清除DMA2_Steam7传输完成标志
}
}
}
void DMA2_Stream2_IRQHandler(void)
{
HAL_DMA_IRQHandler(&uart1.dmarx);
}
void DMA2_Stream7_IRQHandler(void)
{
HAL_DMA_IRQHandler(&uart1.dmatx);
}
#if 1
#if (__ARMCC_VERSION >= 6010050) /* 使用AC6编译器时 */
__asm(".global __use_no_semihosting\n\t");/* 声明不使用半主机模式 */
__asm(".global __ARM_use_no_argv \n\t"); /* AC6下需要声明main函数为无参数格式,否则部分例程可能出现半主机模式 */
#else
/* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
#pragma import(__use_no_semihosting)
struct __FILE
{
int handle;
/* Whatever you require here. If the only file you are using is */
/* standard output using printf() for debugging, no file handling */
/* is required. */
};
#endif
/* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
ch = ch;
return ch;
}
/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
x = x;
}
char *_sys_command_string(char *cmd, int len)
{
return NULL;
}
/* FILE 在 stdio.h里面定义. */
FILE __stdout;
/* 重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 */
int fputc(int ch, FILE *f)
{
while ((uart1.uart.Instance->ISR & 0X40) == 0); /* 等待上一个字符发送完成 */
uart1.uart.Instance->TDR = (uint8_t)ch; /* 将要发送的字符 ch 写入到DR寄存器 */
return ch;
}
#endif
/******************************************************************************************/
usart_drv.h#ifndef __USART_DRV_H
#define __USART_DRV_H
#include "stm32h7xx_hal.h"
#include "string.h"
#include "signal.h"
#include "stdarg.h"
#include "stdio.h"
#defineU1_RX_SIZE 257
#defineU1_TX_SIZE 256
#defineU1_RX_MAX 257
#defineU1_TX_MAX 256
typedef struct{
uint8_t *start;
uint8_t *end;
}LCB;
typedef struct{
uint32_t RxCounter;
uint32_t TxCounter;
uint32_t TxState;
UART_HandleTypeDef uart;
DMA_HandleTypeDef dmatx;
DMA_HandleTypeDef dmarx;
}UCB;
void UART1_Init(uint32_t baudrate);
void Usart1_PtrInit(void);
void Usart1_Txdata(uint8_t *data,uint32_t data_len);
void Uart1_DMA_ENABLE(uint8_t *data,uint32_t data_len);
void Usart1_printf(char *fmt,...);
void Usart1_Send_Data(uint8_t *data,uint16_t datalen);
void Usart1_RX_FunCb(void(*pFunc)(uint8_t *data,uint16_t datalen));
void Usart1_TX_FunCb(void(*pFunc)(uint8_t *data,uint16_t datalen));
#endif
当接收到一帧完整的数据后,如果一段时间内没有新的数据到来,会触发串口空闲中断。这个中断用于通知处理器已经接收到完整的数据帧。 接收到完整数据帧之后进行中断处理 在某些情况下,可以使用DMA的循环模式,这样当指定长度的串口数据通过DMA接收完成后,DMA硬件会自动重新设置传输长度,并开启下一个接收过程。但需要注意,在循环模式下,必须确保数据处理能够及时完成,以避免新接收的数据覆盖正在处理的数据。 mnynt121 发表于 2024-11-11 15:36
当接收到一帧完整的数据后,如果一段时间内没有新的数据到来,会触发串口空闲中断。这个中断用于通知处理器 ...
有数据,都会接收到乱码,这时候就需要做CRC了,这个只是使用DMA+环形队列的操作而已 AdaMaYun 发表于 2024-11-11 16:05
接收到完整数据帧之后进行中断处理
数据接收后,在任务中处理数据。数据先放进队列 dspmana 发表于 2024-11-11 16:34
在某些情况下,可以使用DMA的循环模式,这样当指定长度的串口数据通过DMA接收完成后,DMA硬件会自动重新设 ...
队列就是一个环形接收的,FIFO数据结构
如果长时间没有接收到新的数据帧,可能需要进行超时检测,以确保系统稳定运行。 在空闲中断处理函数中,将DMA接收缓冲区中的数据拷贝到队列中,实现数据的入队操作。
在应用程序需要处理数据时,从队列中取出数据进行处理,实现数据的出队操作。 定义一个循环队列用于存储接收到的串口数据。循环队列是一种将数组的首位在逻辑上连接起来的环形结构,可以高效地利用存储空间。
初始化队列的头指针和尾指针,以及队列的长度等参数。 通过合理设置DMA传输长度和双缓冲机制,可以减少中断次数,提高系统响应速度。 DMA缓冲区足够大以容纳可能的最大数据包。如果数据包长度不固定,可能需要动态调整缓冲区大小或使用多个缓冲区。 使用环形队列(Circular Buffer)来管理接收到的数据,可以有效提高数据处理的效率。 为接收队列分配足够的内存空间,并定期清理不再使用的内存,防止内存溢出。 在接收过程中,可能会遇到各种错误情况,如接收超时、数据溢出等。 通过空闲中断,可以实现接收不定长数据的功能。无论串口接收了多少字节的数据,只要接收停止并产生空闲中断,就意味着有一组完整的数据已经接收完成。 接收的是不定长数据,需要在数据处理过程中加入适当的完整性校验机制 合理设置DMA中断和其他相关中断的优先级,确保关键任务能够及时得到处理。 实施数据校验机制(如CRC校验),确保接收到的数据完整无误。 优化DMA的配置和缓冲区大小,以提高数据传输效率。
页:
[1]
2