FreeRTOS分辨数据源

        当有多个发送任务,通过同一个队列发出数据,接收任务如何分辨数据来源?数据本身带有"来源"信息,比如写入队列的数据是一个结构体,结构体中的lDataSouceID用来表示数据来源:

typedef struct {
ID_t eDataID;
int32_t lDataValue;
}Data_t;

        不同的发送任务,先构造好结构体,填入自己的eDataID ,再写队列;接收任务读出数据后,根据eDataID 就可以知道数据来源了,如下图所示:

CAN任务发送的数据:eDataID=eMotorSpeed
HMI任务发送的数据:eDataID=eSpeedSetPoint

FreeRTOS分辨数据源_第1张图片

​​​​​​​结构体和结构体数组_数组结构体-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/xiaochenxiaoren/article/details/104818066        创建的队列,用来发送结构体:数据大小是结构体的大小
        发送任务优先级为2,分别往队列中写入自己的结构体,结构体中会标明数据来源
        接收任务优先级为1,读队列、根据数据来源打印信息

/* 定义2种数据来源(ID) */
typedef enum
{
    eMotorSpeed,
    eSpeedSetPoint
} ID_t;
/* 定义在队列中传输的数据的格式 */
typedef struct 
{
    ID_t eDataID;
    int32_t lDataValue;
}Data_t;
/* 定义2个结构体 */
static const Data_t xStructsToSend[ 2 ] =
{
    { eMotorSpeed, 10 }, /* CAN任务发送的数据 */
    { eSpeedSetPoint, 5 } /* HMI任务发送的数据 */
};
/* vSenderTask被用来创建2个任务,用于写队列
* vReceiverTask被用来创建1个任务,用于读队列
*/
static void vSenderTask( void *pvParameters );
static void vReceiverTask( void *pvParameters );
/*-----------------------------------------------------------*/
/* 队列句柄, 创建队列时会设置这个变量 */
QueueHandle_t xQueue;
int main( void )
{
    prvSetupHardware();
    /* 创建队列: 长度为5,数据大小为4字节(存放一个整数) */
    xQueue = xQueueCreate( 5, sizeof( Data_t ) );
    if( xQueue != NULL )
    {
    /* 创建2个任务用于写队列, 传入的参数是不同的结构体地址
    * 任务函数会连续执行,向队列发送结构体
    * 优先级为2
    */
    xTaskCreate(vSenderTask, "CAN Task", 1000, 
                (void *) & (xStructsToSend[0]), 2, NULL);
    xTaskCreate(vSenderTask, "HMI Task", 1000, 
                (void *) &(xStructsToSend[1]), 2, NULL);
    /* 创建1个任务用于读队列
    * 优先级为1, 低于上面的两个任务
    * 这意味着发送任务优先写队列,队列常常是满的状态
    */
    xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
    /* 启动调度器 */
    vTaskStartScheduler();
    }
    else
    {
        /* 无法创建队列 */
    }
    /* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
    return 0;
}
static void vSenderTask( void *pvParameters )
{
BaseType_t xStatus;
const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );
/* 无限循环 */
for( ;; )
{
/* 写队列
* xQueue: 写哪个队列
* pvParameters: 写什么数据? 传入数据的地址, 会从这个地址把数据复制进队列
* xTicksToWait: 如果队列满的话, 阻塞一会
*/
xStatus = xQueueSendToBack( xQueue, pvParameters, xTicksToWait );
if( xStatus != pdPASS )
{
printf( "Could not send to the queue.\r\n" );
}
}
}
static void vReceiverTask( void *pvParameters )
{
/* 读取队列时, 用这个变量来存放数据 */
Data_t xReceivedStructure;
BaseType_t xStatus;
/* 无限循环 */
for( ;; )
{
/* 读队列
* xQueue: 读哪个队列
* &xReceivedStructure: 读到的数据复制到这个地址
* 0: 没有数据就即刻返回,不阻塞
*/
xStatus = xQueueReceive( xQueue, &xReceivedStructure, 0 );
if( xStatus == pdPASS )
{
/* 读到了数据 */
if( xReceivedStructure.eDataID == eMotorSpeed )
{
printf( "From CAN, MotorSpeed = %d\r\n",
xReceivedStructure.lDataValue );
}
else if( xReceivedStructure.eDataID == eSpeedSetPoint )
{
printf( "From HMI, SpeedSetPoint = %d\r\n",
xReceivedStructure.lDataValue );
}
}
else

{
    /* 没读到数据 */
    printf( "Could not receive from the queue.\r\n" );
}
}
}

FreeRTOS分辨数据源_第2张图片

任务调度情况如下图所示:
        t1:HMI是最后创建的最高优先级任务,它先执行,一下子向队列写入5个数据,把队列都写满了
        t2:队列已经满了,HMI任务再发起第6次写操作时,进入阻塞状态。这时CAN任务是最高优先级的就绪态任务,它开始执行
        t3:CAN任务发现队列已经满了,进入阻塞状态;接收任务变为最高优先级的就绪态任务,它开始运行
        t4:现在,HMI任务、CAN任务的优先级都比接收任务高,它们都在等待队列有空闲的空间;一旦接收任务读出1个数据,会马上被抢占。被谁抢占?谁等待最久?HMI任务!所以在t4时刻,切换到HMI任务。
        t5:HMI任务向队列写入第6个数据,然后再次阻塞,这时CAN任务已经阻塞很久了。接收任务变为最高优先级的就绪态任务,开始执行。
        t6:现在,HMI任务、CAN任务的优先级都比接收任务高,它们都在等待队列有空闲的空间;一旦接收任务读出1个数据,会马上被抢占。被谁抢占?谁等待最久?CAN任务!所以在t6时刻,切换到CAN任务。
        t7:CAN任务向队列写入数据,因为仅仅有一个空间供写入,所以它马上再次进入阻塞状态。这时HMI任务、CAN任务都在等待空闲空间,只有接收任务可以继续执行。

FreeRTOS分辨数据源_第3张图片

FreeRTOS队列---传输大块数据与邮箱      

  FreeRTOS的队列使用拷贝传输,也就是要传输uint32_t时,把4字节的数据拷贝进队列;要传输一个8字节的结构体时,把8字节的数据拷贝进队列。
        如果要传输1000字节的结构体呢?写队列时拷贝1000字节,读队列时再拷贝1000字节?不建议这么做,影响效率!这时候,我们要传输的是这个巨大结构体的地址:把它的地址写入队列,对方从队列得到这个地址,使用地址去访问那1000字节的数据。

        使用地址来间接传输数据时,这些数据放在RAM里,对于这块RAM,要保证这几点:
                RAM的所有者、操作者,必须清晰明了
                这块内存,就被称为"共享内存"。要确保不能同时修改RAM。比如,在写队列之前只由发送者修改这块RAM,在读队列之后只能由接收者访问这块RAM。
                 RAM要保持可用
                 这块RAM应该是全局变量,或者是动态分配的内存。对于动态分配的内存,要确保它不能提前释放:要等到接收者用完后再释放。另外,不能是局部变量。

        FreeRTOS的邮箱概念跟别的RTOS不一样,这里的邮箱称为"橱窗"也许更恰当:
        它是一个队列,队列长度只有1
        写邮箱:新数据覆盖旧数据,在任务中使用xQueueOverwrite() ,在中断中使用
                xQueueOverwriteFromISR() 。
        既然是覆盖,那么无论邮箱中是否有数据,这些函数总能成功写入数据。
        读邮箱:读数据时,数据不会被移除;在任务中使用xQueuePeek() ,在中断中使用
                xQueuePeekFromISR() 。
        这意味着,第一次调用时会因为无数据而阻塞,一旦曾经写入数据,以后读邮箱时总能成功。

 

你可能感兴趣的:(51单片机,c语言)