1、freertos数据传递简介
在freertos中,各个模块都是独立的任务,那么任务之间怎么进行大量的数据通信呢?在V10版本给出了三种方法。
- 队列queue,发送固定长度的数据串
- stream buffer,为新增的特性,发送不定长度的数据串
- message buffer,为新增的特性,发送不定长度的数据串,同时带有发送长度信息
以上三者,都可以用于任务-任务,任务-中断,都遵循FIFO先进先出原则,数据传递的方式为拷贝,像ucos中数据传递采用的传输指针,拷贝的方式效率有所降低,而好处也很明显,避免了同一数据可能存在的同时读写造成的问题。只要拷贝完成,源数据的改变不影响接收方数据的有效性,只不过接收方接收到的数据可能并非最新数据。
传输的中的数据只要被成功接收,该数据就会消亡。这种在1发多收的情况下会出现,比如,task1 不停的在发送数据,task1,task2在接收task1的数据。 一发多收的情况下,同一个接收方不能完全接收到所有的信息。信息一旦被接收后,就消亡了,要个新消息腾出空间。
2、队列 stream buffer message buffer区别
队列可以引用到各种场合,是最基础的数据传递方式。
官方网站给出了stream buffers 和message buffers的简介https://www.freertos.org/FreeRTOS-V10.html stream buffers主要应用于一个发送者一个接收者。比如从中断发送给task,或者从一个cpu核发送到另一个cpu核。
message buffers是基于streambuffers实现的,stream buffers传输连续的数据,而message buffers 传送带离散的带有长度的消息,接收方可以读取到当前消息的长度。
这三者的初步对比分析,由于资料及应用时间有限,可能存在不到位的地方,后续根据使用情况继续完善。
对象 | 特点 | 优势 | 缺陷 |
---|
queue | task-task,中断-task,固定长度,传输的是拷贝,可以1对多,多对1,多对多 | 应用场合多 | 长度固定 | stream buffers | task-task,中断-task,不固定长度,传输的是拷贝,传送持续的数据(文件,图片),建议1对1 | 传输量大,可自定义接收长度 | 场景有限制 | queue | task-task,中断-task,不固定长度,自身带有长度信息,传输的是拷贝拷贝 | 非固定长度,带有长度标签,适合传输协议帧数据 | |
通过以上的简单分析,个人觉得最好的方式是message buffer,牛的地方是可以发送不定长数据,接收的时候可以读到数据的当前长度,可以根据长度来区分不同的信息。当在传输一些协议的时候,比较优势,相比于queue,灵活性强太多了
3、api接口
3.1 queue
创建
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,
UBaseType_t uxItemSize );
参数含义
uxQueueLength: 队列可存储消息的最大数量 uxItemSize :单个消息的长度,为字节数量
发送
BaseType_t xQueueSend( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );
参数含义:
xQueue:队列句柄 pvItemToQueue:要传输的消息的指针 xTicksToWait :消息满的最大等待时间,如果消息满了,改值非0的话,任务会阻塞,直到时间到达portMAX_DELAY无限等待
返回值含义:
pdPASS:队列发送成功 errQUEUE_FULL:队列满了
从中断函数发送
BaseType_t xQueueSendFromISR( QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken );
参数含义:
xQueue:队列句柄 pvItemToQueue:要传输的的消息的指针 pxHigherPriorityTaskWoken : 是否需要切换上下文的标志位,需要先定义一个变量来存贮这个值,pdTRUE 或者pdFALSE
返回值:
pdPASS:队列发送成功 errQUEUE_FULL:队列满了
注意在使用的使用的时候,清除完中断标志位后,需要调用切换上下文的函数。
void vBufferISR( void )
{
char cIn;
BaseType_t xHigherPriorityTaskWoken;
xHigherPriorityTaskWoken = pdFALSE;
do
{
cIn = INPUT_BYTE( RX_REGISTER_ADDRESS );
xQueueSendToBackFromISR( xRxQueue, &cIn, &xHigherPriorityTaskWoken );
} while( INPUT_BYTE( BUFFER_COUNT ) );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
接收函数
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void *pvBuffer,
TickType_t xTicksToWait );
参数说明:
xQueue:队列句柄 pvBuffer:接收消息buffer的指针 xTicksToWait :最大等待时间,等待的时候可以,当前任务被阻塞portMAX_DELAY无限等待
返回值:
pdPASS:数据接收完毕 errQUEUE_EMPTY:队列为空
从中断接收函数
BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue,
void *pvBuffer,
BaseType_t *pxHigherPriorityTaskWoken );
参数含义:
xQueue:队列句柄 pvItemToQueue:要传输的的消息的指针 pxHigherPriorityTaskWoken : 是否需要切换上下文的标志位,需要先定义一个变量来存贮这个值,pdTRUE 或者pdFALSE
返回值:
pdPASS:队列发送成功 errQUEUE_FULL:队列满了
注意在使用的使用的时候,清除完中断标志位后,需要调用切换上下文的函数。同从中断发送队列函数
3.2 stream buffers
创建函数
StreamBufferHandle_t xStreamBufferCreate( size_t xBufferSizeBytes,
size_t xTriggerLevelBytes );
参数说明:
xBufferSizeBytes :buffer的最大容量字节数 xTriggerLevelBytes:最小有效触发字节,意思是,当buffer至少有大于等于这个值的字节数时,消息才能被接收,该值最小为1,如果设置为0,内部会自动将其设置为1
返回值:
NULL:创建失败
发送函数
size_t xStreamBufferSend( StreamBufferHandle_t xStreamBuffer,
const void *pvTxData,
size_t xDataLengthBytes,
TickType_t xTicksToWait );
参数说明:
xStreamBuffer :句柄 pvTxData:待发送消息的指针 xDataLengthBytes:拷贝到stream buffer的字节数量 xTicksToWait:stream buffer 满了后发送的等待时间
返回值:
最终拷贝到stream buffer的字节数量
接收函数
size_t xStreamBufferReceive( StreamBufferHandle_t xStreamBuffer,
void *pvRxData,
size_t xBufferLengthBytes,
TickType_t xTicksToWait );
参数说明:
xStreamBuffer:句柄 pvRxData:接收buffer的指针 xBufferLengthBytes:一次最大的接收长度 xTicksToWait:等待时间
返回值:
实际接收到的长度
从中断发送
size_t xStreamBufferSendFromISR( StreamBufferHandle_t xStreamBuffer,
const void *pvTxData,
size_t xDataLengthBytes,
BaseType_t *pxHigherPriorityTaskWoken );
参数说明:
xStreamBuffer:句柄 pvTxData:待发送消息的指针 xDataLengthBytes:拷贝到stream buffer的字节数量 pxHigherPriorityTaskWoken :上下文切换的标志位
返回值:
实际写入的字节数量
从中断接收
size_t xStreamBufferReceiveFromISR( StreamBufferHandle_t xStreamBuffer,
void *pvRxData,
size_t xBufferLengthBytes,
BaseType_t *pxHigherPriorityTaskWoken );
参数说明:
xStreamBuffer:句柄 pvRxData:接收buffer的指针 xBufferLengthBytes:一次最大接收长度 pxHigherPriorityTaskWoken :上下文切换的标志位
3.3message buffers
创建
MessageBufferHandle_t xMessageBufferCreate( size_t xBufferSizeBytes );
参数说明:
xBufferSizeBytes :buffer的最大容量,buffer占用的空间为xBufferSizeBytes+4.4字节uint32存放实际buff有效数据的长度
返回值:
NULL:创建失败,一般是堆空间不够
发送
size_t xMessageBufferSend( MessageBufferHandle_t xMessageBuffer,
const void *pvTxData,
size_t xDataLengthBytes,
TickType_t xTicksToWait );
参数说明:
xMessageBuffer:句柄 pvTxData:发送消息的指针 xDataLengthBytes:发送消息的长度 xTicksToWait :buffer满了等待时间
返回值:
实际写入buffer的数量,如果messagebuff没有足够的长度存储pvTxData,则返回值将会是0
接收
size_t xMessageBufferReceive( MessageBufferHandle_t xMessageBuffer,
void *pvRxData,
size_t xBufferLengthBytes,
TickType_t xTicksToWait );
参数说明:
xMessageBuffer:句柄 pvTxData:接收消息的指针 xDataLengthBytes:接收buffer的长度,如果长度小于messge的长度,返回值为0 xTicksToWait :等待接收时间
返回值
读取到的长度,如果message超过接收buff的长度,返回值将会是0,消息仍然存在消息buffer中。如果message buffer 空,接收超过了xTicksToWait时间,则返回的也是0.
从中断发送
size_t xMessageBufferSendFromISR( MessageBufferHandle_t xMessageBuffer,
const void *pvTxData,
size_t xDataLengthBytes,
BaseType_t *pxHigherPriorityTaskWoken )
参数说明
xMessageBuffer:句柄 pvTxData:发送消息的指针 xDataLengthBytes:发送消息的长度 pxHigherPriorityTaskWoken :上下文切换的标志位,具体见queue章节
返回值
写入到messagebuff的长度。如果messagebuff没有足够的长度存储pvTxData,则返回值将会是0
从中断接收
size_t xMessageBufferReceiveFromISR( MessageBufferHandle_t xMessageBuffer,
void *pvRxData,
size_t xBufferLengthBytes,
BaseType_t *pxHigherPriorityTaskWoken );
参数说明
xMessageBuffer:句柄 pvTxData:接收消息的指针 xDataLengthBytes:接收buffer的长度 pxHigherPriorityTaskWoken :上下文切换的标志位,具体见queue章节
返回值
读取到的长度
4、queue实验
4.1一发二收
void que_tx(void *pvParameters)
{
uint8_t tx[ 8 ]={0,1,2,3,4,5,6,7};
uint8_t tx1[ 8 ]={2,3,4,5,6,7,8,9};
uint32_t err;
while(1)
{
err = xQueueSend(queue_1,tx,0);
if(err == pdPASS)
printf("que_tx1:queue sende\r\n");
err = xQueueSend(queue_1,tx1,0);
if(err == pdPASS)
printf("que_tx1:queue sende\r\n");
vTaskDelay(400);
}
}
void que_rx(void *pvParameters)
{
uint8_t rx[ 8 ]={0};
uint32_t err;
while(1)
{
err = xQueueReceive(queue_1,rx,0);
xSemaphoreTake(mutex1,portMAX_DELAY);
if(err == pdPASS)
{
printf("que_rx:rx_data<-- ,");
for (int i = 0;i<8;i++)
printf("%d ",rx[i]);
printf("\r\n");
}
xSemaphoreGive(mutex1);
vTaskDelay(800);
}
}
void que_rx1(void *pvParameters)
{
uint8_t rx[ 8 ]={0};
uint32_t err;
while(1)
{
err = xQueueReceive(queue_1,rx,0);
xSemaphoreTake(mutex1,portMAX_DELAY);
if(err == pdPASS)
{
printf("que_rx1:rx_data<-- ,");
for (int i = 0;i<8;i++)
printf("%d ",rx[i]);
printf("\r\n");
}
xSemaphoreGive(mutex1);
vTaskDelay(800);
}
}
最终结果如图,如果不采用互斥信号量,打印的数据会乱。可见最好使用1发1收
4.2二发一收
void que_tx(void *pvParameters)
{
uint8_t tx[ 8 ]={0,1,2,3,4,5,6,7};
uint8_t tx1[ 8 ]={2,3,4,5,6,7,8,9};
uint32_t err;
while(1)
{
err = xQueueSend(queue_1,tx,0);
if(err == pdPASS)
printf("que_tx1:queue sende\r\n");
err = xQueueSend(queue_1,tx1,0);
if(err == pdPASS)
printf("que_tx1:queue sende\r\n");
vTaskDelay(400);
}
}
void que_tx1(void *pvParameters)
{
uint8_t tx[ 8 ]={11,12,13,14,15,16,17,18};
uint32_t err;
while(1)
{
err = xQueueSend(queue_1,tx,0);
if(err == pdPASS)
printf("que_tx1:queue sende\r\n");
vTaskDelay(400);
}
}
void que_rx(void *pvParameters)
{
uint8_t rx[ 8 ]={0};
uint32_t err;
while(1)
{
err = xQueueReceive(queue_1,rx,0);
xSemaphoreTake(mutex1,portMAX_DELAY);
if(err == pdPASS)
{
printf("que_rx:rx_data<-- ,");
for (int i = 0;i<8;i++)
printf("%d ",rx[i]);
printf("\r\n");
}
xSemaphoreGive(mutex1);
vTaskDelay(800);
}
}
最终结果如图,收发都比较正常
4.3中断发送
static uint8_t test[ 8 ]={22,23,24,25,26,27,28,29};
void TIMER0_UP_IRQHandler(void)
{
BaseType_t flag = pdFALSE;
if( timer_interrupt_flag_get(TIMER0,TIMER_INT_FLAG_UP) == SET)
{
if(queue_1!=NULL)
xQueueSendFromISR(queue_1,test,&flag);
}
timer_interrupt_flag_clear(TIMER0,TIMER_INT_FLAG_UP);
portYIELD_FROM_ISR(flag);
}
这里需要注意的是判断queue_1是否为空,要确定队列已经初始化了才能进行发送。可能进入中断了,队列还没完成初始化,这样会造成硬件故障。 结果如上图所示,可见,多发单收是没有任何问题的。
5、stream buffers实验
5.1 单发单收
stream_1 = xStreamBufferCreate(100,12);
void que_tx1(void *pvParameters)
{
uint8_t tx[ 8 ]={21,22,23,24,25,26,27,28};
uint32_t err;
while(1)
{
err = xStreamBufferSend(stream_1,tx,sizeof(tx),portMAX_DELAY);
if(err >0)
printf("que_tx1 %d:queue sende\r\n",err);
vTaskDelay(400);
}
}
void que_rx(void *pvParameters)
{
uint8_t rx[ 11 ]={0};
uint32_t err;
while(1)
{
err = xStreamBufferReceive(stream_1,rx,sizeof(rx),portMAX_DELAY);
xSemaphoreTake(mutex1,portMAX_DELAY);
if(err >0)
{
printf("que_rx:rx_data %d <-- ,",err);
for (int i = 0;i<sizeof(rx);i++)
{
printf("%d ",rx[i]);
rx[i] = 0;
}
printf("\r\n");
}
xSemaphoreGive(mutex1);
}
}
运行结果如下: 这个实验没有理解,一开始设定的stream buffer 触发长度为12,按理收当收完第一个11字节数据后,message buffer中的数据只有5字节,5<15这时候接收到的数据应该是0啊。反复测试都是这个结果,问题还未知,先放着。
6、message buffers实验
6.1单发单收
message_1 = xMessageBufferCreate(100);
void que_tx1(void *pvParameters)
{
uint8_t tx[ 8 ]={21,22,23,24,25,26,27,28};
uint32_t err;
while(1)
{
err = xMessageBufferSend(message_1,tx,sizeof(tx),portMAX_DELAY);
if(err >0)
printf("que_tx1 %d:queue sende\r\n",err);
vTaskDelay(400);
}
}
void que_rx(void *pvParameters)
{
uint8_t rx[ 11 ]={0};
uint32_t err;
while(1)
{
err = xMessageBufferReceive(message_1,rx,sizeof(rx),portMAX_DELAY);
xSemaphoreTake(mutex1,portMAX_DELAY);
if(err >0)
{
printf("que_rx:rx_data %d <-- ,",err);
for (int i = 0;i<sizeof(rx);i++)
{
printf("%d ",rx[i]);
rx[i] = 0;
}
printf("\r\n");
}
xSemaphoreGive(mutex1);
}
}
设置messagebuf的最大容量为100字节。发送任务每次发8字节,接收任务最大每次接收11字节
结果如上图,发送接收比较正常。
6.2多发单收
在6.1中增加一个发送任务
void que_tx(void *pvParameters)
{
uint8_t tx[ 10 ]={1,2,3,4,5,6,7,8,9,10};
uint8_t tx1[ 8 ]={2,3,4,5,6,7,8,9};
uint32_t err;
while(1)
{
err = xMessageBufferSend(message_1,tx,sizeof(tx),portMAX_DELAY);
if(err >0)
printf("que_tx %d:queue sende\r\n",err);
vTaskDelay(400);
}
}
接收结果也比较正常,可以分别收到8字节和10字节长度的数据。比queue应用范围更广。
6.3中断发送
在6.2中增加如下代码,
static uint8_t test[ 9 ]={24,25,26,27,28,29,30,31,32};
void TIMER0_UP_IRQHandler(void)
{
BaseType_t flag = pdFALSE;
if( timer_interrupt_flag_get(TIMER0,TIMER_INT_FLAG_UP) == SET)
{
if(message_1!=NULL)
xMessageBufferSendFromISR(message_1,test,sizeof(test),&flag);
}
timer_interrupt_flag_clear(TIMER0,TIMER_INT_FLAG_UP);
portYIELD_FROM_ISR(flag);
}
从中断发送9字节数据。运行结果如下: 三个发送端的数据都可以收到。实际应用的时候,可以根据接收数据的长度来判读发送方。
|