有关嵌入式FreeRTOS,各组件的小知识点

在嵌入式中现在越来越多的使用RTOS,来进行功能的完成,也陆续出现了个类的实时操作系统(RTOS),大概有μClinux、μC/OS-II、eCos、FreeRTOS、mbed OS、RTX、Vxworks、QNX、NuttX,国产的嵌入式操作系统包括都江堰操作系统(djyos)、Alios Things、Huawei LiteOS、RT-Thread、SylixOS。而FreeRTOS是这些在FreeRTOS中,主要就是对于,各个组件的运用使用,比如消息队列,信号量,软件定时器,事件等等。下面是关于FreeRTOS的一些小的知识点。

一、消息队列

重要点:只有消息队列可以传输内容,需要清楚,他的读写。        

        通过消息队列服务,任务或中断服务例程可以将一条或多条消息放入消息队列中。同 样,一个或多个任务可以从消息队列中获得消息。当有多个消息发送到消息队列时,通常 是将先进入消息队列的消息先传给任务,也就是说,任务先得到的是最先进入消息队列的 消息,即先进先出原则(FIFO),但是也支持后进先出原则(LIFO)。

        FreeRTOS 中使用队列数据结构实现任务异步通信工作,具有如下特性:

        1、 消息支持先进先出方式排队,支持异步读写工作方式。

        2、 读写队列均支持超时机制。  消息支持后进先出方式排队,往队首发送消息(LIFO)。

        3、 可以允许不同长度(不超过队列节点最大值)的任意类型消息。

        4、一个任务能够从任意一个消息队列接收和发送消息。

        5、多个任务能够从同一个消息队列接收和发送消息。

        6、当队列使用结束后,可以通过删除队列函数进行删除。

应用场景

        消息队列可以应用于发送不定长消息的场合,包括任务与任务间的消息交换,队列是 FreeRTOS 主要的任务间通讯方式,可以在任务与任务间、中断和任务间传送信息,发送到 队列的消息是通过拷贝方式实现的,这意味着队列存储的数据是原数据,而不是原数据的 引用。

消息队列相关API函数 

1、消息队列创建函数xQueueCreate()

 QueueHandle_t Test_Queue =NULL;
 
 #define QUEUE_LEN 4 /* 队列的长度,最大可包含多少个消息 */
 #define QUEUE_SIZE 4 /* 队列中每个消息大小(字节) */
  
 BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
 taskENTER_CRITICAL(); //进入临界区
 
 /* 创建 Test_Queue */ 
 Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息队列的长度 */ 
 (UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */ 
 if (NULL != Test_Queue) 
 printf("创建 Test_Queue 消息队列成功!\r\n"); 
 
 taskEXIT_CRITICAL(); //退出临界区

2、消息队列静态创建函数 xQueueCreateStatic()

/* 创建一个可以最多可以存储 10 个 64 位变量的队列 */
 #define QUEUE_LENGTH 10
 #define ITEM_SIZE sizeof( uint64_t )
 
 /* 该变量用于存储队列的数据结构 */
 static StaticQueue_t xStaticQueue;
 
 /* 该数组作为队列的存储区域,大小至少有 uxQueueLength * uxItemSize 个字节 */
 uint8_t ucQueueStorageArea[ QUEUE_LENGTH * ITEM_SIZE ]; 
 
 void vATask( void *pvParameters )
 {
     QueueHandle_t xQueue;
     
     /* 创建一个队列 */ 
     xQueue = xQueueCreateStatic( QUEUE_LENGTH, /* 队列深度 */ 
                                  ITEM_SIZE, /* 队列数据单元的单位 */ 
                                  ucQueueStorageArea,/* 队列的存储区域 */ 
                                  &xStaticQueue ); /* 队列的数据结构 */ 
     /* 剩下的其他代码 */
 }

3、消息队列删除函数 vQueueDelete()

#define QUEUE_LENGTH 5
#define QUEUE_ITEM_SIZE 4
 
 int main( void )
 {
     QueueHandle_t xQueue;
     /* 创建消息队列 */
     xQueue = xQueueCreate( QUEUE_LENGTH, QUEUE_ITEM_SIZE );
     
     if ( xQueue == NULL ) {
         /* 消息队列创建失败 */
     } else {
         /* 删除已创建的消息队列 */ 
         vQueueDelete( xQueue ); 
     }
 }

4、向消息队列发送消息函数

4.1. xQueueSend()与 xQueueSendToBack()

xQueueSend()用于向队列尾部发送一个队列消息。

 static void Send_Task(void* parameter)
 {
     BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
     uint32_t send_data1 = 1;
     uint32_t send_data2 = 2;
     while (1) {
         if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) {
             /* K1 被按下 */
             printf("发送消息 send_data1!\n"); 
             xReturn = xQueueSend( Test_Queue, /* 消息队列的句柄 */ 
             &send_data1,/* 发送的消息内容 */ 
             0 ); /* 等待时间 0 */ 
             if (pdPASS == xReturn) 
                 printf("消息 send_data1 发送成功!\n\n"); 
         }
         if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) {
             /* K2 被按下 */
             printf("发送消息 send_data2!\n"); 
             xReturn = xQueueSend( Test_Queue, /* 消息队列的句柄 */ 
             &send_data2,/* 发送的消息内容 */ 
             0 ); /* 等待时间 0 */ 
             if (pdPASS == xReturn) 
                 printf("消息 send_data2 发送成功!\n\n"); 
         }
         vTaskDelay(20);/* 延时 20 个 tick */
     }
 }
4.2. xQueueSendFromISR()与 xQueueSendToBackFromISR()

 void vBufferISR( void )
 {
     char cIn;
     BaseType_t xHigherPriorityTaskWoken; 
     
     /* 在 ISR 开始的时候,我们并没有唤醒任务 */
     xHigherPriorityTaskWoken = pdFALSE; 
     
     /* 直到缓冲区为空 */
     do {
         /* 从缓冲区获取一个字节的数据 */
         cIn = portINPUT_BYTE( RX_REGISTER_ADDRESS );
         /* 发送这个数据 */ 
         xQueueSendFromISR( xRxQueue, &cIn, &xHigherPriorityTaskWoken ); 
         
     } while ( portINPUT_BYTE( BUFFER_COUNT ) );
     
     /* 这时候 buffer 已经为空,如果需要则进行上下文切换 */ 
     if ( xHigherPriorityTaskWoken ) { 
         /* 上下文切换,这是一个宏,不同的处理器,具体的方法不一样 */ 
         taskYIELD_FROM_ISR (); 
     } 
 }
4.3. xQueueSendToFront()

xQueueSendToFront()用于向队列队首发送一个消息

4.4. xQueueSendToFrontFromISR()

4.5. 通用消息队列发送函数 xQueueGenericSend()(任务)
4.6. 消息队列发送函数 xQueueGenericSendFromISR()(中断)

5、从消息队列读取消息函数

5.1. xQueueReceive()与 xQueuePeek()

xQueueReceive()用于从一个队列中接收消息并把消息从队列中删除

        其实这个函数与 xQueueReceive()函数的实现方式一样,连使用方法都一样,只不过xQueuePeek()函数接收消息完毕不会删除消息队列中的消息而已

 static void Receive_Task(void* parameter)
 {
     BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为 pdPASS */
     uint32_t r_queue; /* 定义一个接收消息的变量 */
     while (1) {
         xReturn = xQueueReceive( Test_Queue, /* 消息队列的句柄 */ 
                                  &r_queue, /* 发送的消息内容 */ 
                                  portMAX_DELAY); /* 等待时间 一直等 */ 
         if (pdTRUE== xReturn) 
             printf("本次接收到的数据是:%d\n\n",r_queue); 
         else 
             printf("数据接收出错,错误代码: 0x%lx\n",xReturn); 
     }
 }

5.2. xQueueReceiveFromISR()与 xQueuePeekFromISR()

xQueueReceiveFromISR()

xQueuePeekFromISR()

从队列读取消息函数 xQueueGenericReceive()

注意事项

1. 使用 xQueueSend()、xQueueSendFromISR()、xQueueReceive()等这些函数之前应先

创建需消息队列,并根据队列句柄进行操作。

2. 队列读取采用的是先进先出(FIFO)模式,会先读取先存储在队列中的数据。当

然也 FreeRTOS 也支持后进先出(LIFO)模式,那么读取的时候就会读取到后进

队列的数据。

3. 在获取队列中的消息时候,我们必须要定义一个存储读取数据的地方,并且该数

据区域大小不小于消息大小,否则,很可能引发地址非法的错误。

4. 无论是发送或者是接收消息都是以拷贝的方式进行,如果消息过于庞大,可以将

消息的地址作为消息进行发送、接收。

5. 队列是具有自己独立权限的内核对象,并不属于任何任务。所有任务都可以向同

一队列写入和读出。一个队列由多任务或中断写入是经常的事,但由多个任务读

出倒是用的比较少。

二、软件定时器

基本流程

软件定时器创建函数 xTimerCreate()

软件定时器启动函数 xTimerStart()
在中断中启动一个软件定时器 xTimerStartFromISR()

软件定时器停止函数 xTimerStop()
在中断中停止一个软件定时器,让其进入休眠态 xTimerStopFromISR()

软件定时器删除函数 xTimerDelete()

基本的概念

定时器有硬件定时器和软件定时器之分:
    硬件定时器是芯片本身提供的定时功能。一般是由外部晶振提供给芯片输入时钟,芯片向软件模块提供一组配置寄存器,接受控制输入,到达设定时间值后芯片中断控制器产生时钟中断。硬件定时器的精度一般很高,可以达到纳秒级别,并且是中断触发方式。
    软件定时器,软件定时器是由操作系统提供的一类系统接口,它构建在硬件定时器基础之上,使系统能够提供不受硬件定时器资源限制的定时器服务,它实现的功能与硬件定时器也是类似的。
    使用硬件定时器时,每次在定时时间到达之后就会自动触发一个中断,用户在中断中处理信息;而使用软件定时器时,需要我们在创建软件定时器时指定时间到达后要调用的函数(也称超时函数/回调函数,为了统一,下文均用回调函数描述),在回调函数中处理信息。
    注意:软件定时器回调函数的上下文是任务,下文所说的定时器均为软件定时器。
    软件定时器在被创建之后,当经过设定的时钟计数值后会触发用户定义的回调函数。定时精度与系统时钟的周期有关。一般系统利用 SysTick 作为软件定时器的基础时钟,软件定时器的回调函数类似硬件的中断服务函数,所以,回调函数也要快进快出,而且回调函数中不能有任何阻塞任务运行的情况(软件定时器回调函数的上下文环境是任务),比如 vTaskDelay()以及 其它能阻 塞任务运 行的函数 ,两次触发回调函数的时间间隔xTimerPeriodInTicks 叫定时器的定时周期。
    FreeRTOS 操作系统提供软件定时器功能,软件定时器的使用相当于扩展了定时器的数
量,允许创建更多的定时业务。FreeRTOS 软件定时器功能上支持:
    1、裁剪:能通过宏关闭软件定时器功能。
    2、软件定时器创建。
    3、软件定时器启动。
    4、软件定时器停止。
    5、软件定时器复位。
    6、软件定时器删除。
   FreeRTOS 提供的软件定时器支持单次模式和周期模式,单次模式和周期模式的定时时间到之后都会调用软件定时器的回调函数,用户可以在回调函数中加入要执行的工程代码。

    单次模式:当用户创建了定时器并启动了定时器后,定时时间到了,只执行一次回调函数之后就将该定时器进入休眠状态,不再重新执行。
    周期模式:这个定时器会按照设置的定时时间循环执行回调函数,直到用户将定时器删除。

应用场景

        在很多应用中,我们需要一些定时器任务,硬件定时器受硬件的限制,数量上不足以满足用户的实际需求,无法提供更多的定时器,那么可以采用软件定时器来完成,由软件定时器代替硬件定时器任务。但需要注意的是软件定时器的精度是无法和硬件定时器相比的,而且在软件定时器的定时过程中是极有可能被其它中断所打断,因为软件定时器的执行上下文环境是任务。所以,软件定时器更适用于对时间精度要求不高的任务,一些辅助型的任务。

相关API函数

1. 软件定时器创建函数 xTimerCreate()

        软件定时器与 FreeRTOS 内核其他资源一样,需要创建才允许使用的,FreeRTOS 为我们提供了两种创建方式,一种是动态创建软件定时器 xTimerCreate(),另一种是静态创建方式 xTimerCreateStatic(),创建过程基本差不多。

 static TimerHandle_t Swtmr1_Handle =NULL; /* 软件定时器句柄 */
 static TimerHandle_t Swtmr2_Handle =NULL; /* 软件定时器句柄 */
 /* 周期模式的软件定时器 1,定时器周期 1000(tick)*/
 Swtmr1_Handle=xTimerCreate((const char*)"AutoReloadTimer", 
                             (TickType_t)1000,/* 定时器周期 1000(tick) */ 
                             (UBaseType_t)pdTRUE,/* 周期模式 */ 
                             (void* )1,/* 为每个计时器分配一个索引的唯一 ID */ 
                             (TimerCallbackFunction_t)Swtmr1_Callback); /* 回调函数 */ 
 if (Swtmr1_Handle != NULL)
 {
         /********************************************************************
         * xTicksToWait:如果在调用 xTimerStart()时队列已满,则以 tick 为单位指定调用任务应保持
         * 在 Blocked(阻塞)状态以等待 start 命令成功发送到 timer 命令队列的时间。
         * 如果在启动调度程序之前调用 xTimerStart(),则忽略 xTicksToWait。在这里设置等待时间为 0.
         **********************************************************************/
     xTimerStart(Swtmr1_Handle,0); 
    //开启周期定时器
 }
 
 /* 单次模式的软件定时器 2,定时器周期 5000(tick)*/
 Swtmr2_Handle=xTimerCreate((const char* )"OneShotTimer", 
                             (TickType_t)5000,/* 定时器周期 5000(tick) */ 
                             (UBaseType_t )pdFALSE,/* 单次模式 */ 
                             (void*)2,/* 为每个计时器分配一个索引的唯一 ID */ 
                             (TimerCallbackFunction_t)Swtmr2_Callback); 
 if (Swtmr2_Handle != NULL)
 {
     xTimerStart(Swtmr2_Handle,0); 
    //开启单次定时器
 }
 
 static void Swtmr1_Callback(void* parameter) 
 {
     /* 软件定时器的回调函数,用户自己实现 */
 }
 
 static void Swtmr2_Callback(void* parameter) 
 {
     /* 软件定时器的回调函数,用户自己实现 */
 }
 

2. 软件定时器启动函数

2.1 xTimerStart()

        软件定时器在创建完成的时候是处于休眠状态的,需要用 FreeRTOS 的相关函数将软件定时器活动起来,而 xTimerStart()函数就是可以让处于休眠的定时器开始工作

2.2 xTimerStartFromISR()

        当然除在任务启动软件定时器之外,还有在中断中启动软件定时器的函数xTimerStartFromISR()。xTimerStartFromISR()是函数 xTimerStart()的中断版本,用于启动一个先前由函数 xTimerCreate() / xTimerCreateStatic()创建的软件定时器。

 /* 这个方案假定软件定时器 xBacklightTimer 已经创建,
 定时周期为 5s,执行次数为一次,即定时时间到了之后
 就进入休眠态。
 程序说明:当按键按下,打开液晶背光,启动软件定时器,
 5s 时间到,关掉液晶背光*/
 
 /* 软件定时器回调函数 */
 void vBacklightTimerCallback( TimerHandle_t pxTimer )
 {
     /* 关掉液晶背光 */
     vSetBacklightState( BACKLIGHT_OFF );
 } 
 
 /* 按键中断服务程序 */
 void vKeyPressEventInterruptHandler( void )
 {
     BaseType_t xHigherPriorityTaskWoken = pdFALSE;
     
     /* 确保液晶背光已经打开 */
     vSetBacklightState( BACKLIGHT_ON );
     
     /* 启动软件定时器 */
     if ( xTimerStartFromISR( xBacklightTimer, &xHigherPriorityTaskWoken ) != pdPASS ) { 
         /* 软件定时器开启命令没有成功执行 */ 
     } 
     
     /* ...执行其他的按键相关的功能代码 */
     
     if ( xHigherPriorityTaskWoken != pdFALSE ) { 
         /* 执行上下文切换 */ 
     }
 }

3. 软件定时器停止函数

3.1 xTimerStop()

        xTimerStop() 用于停止一个已经启动的软件定时器,该函数的实现也是通过“定时器命令队列”发送一个停止命令给软件定时器任务,从而唤醒软件定时器任务去将定时器停止。要想使函数 xTimerStop()必须在头文件 FreeRTOSConfig.h 中把宏 configUSE_TIMERS定义为 1。

 static TimerHandle_t Swtmr1_Handle =NULL; /* 软件定时器句柄 */
 
 /* 周期模式的软件定时器 1,定时器周期 1000(tick)*/
 Swtmr1_Handle=xTimerCreate((const char* )"AutoReloadTimer",
                             (TickType_t )1000,/* 定时器周期 1000(tick) */
                             (UBaseType_t )pdTRUE,/* 周期模式 */
                             (void*)1,/* 为每个计时器分配一个索引的唯一 ID */
                             (TimerCallbackFunction_t)Swtmr1_Callback); /* 回调函数 */
 if (Swtmr1_Handle != NULL)
 {
         /********************************************************************
         * xTicksToWait:如果在调用 xTimerStart()时队列已满,则以 tick 为单位指定调用任务应保持
         * 在 Blocked(阻塞)状态以等待 start 命令成功发送到 timer 命令队列的时间。
         * 如果在启动调度程序之前调用 xTimerStart(),则忽略 xTicksToWait。在这里设置等待时间为 0.
         *******************************************************************/
     xTimerStart(Swtmr1_Handle,0); 
    //开启周期定时器 
 }
 
 static void test_task(void* parameter)
 {
     while (1) {
         /* 用户自己实现任务代码 */
         xTimerStop(Swtmr1_Handle,0); 
        //停止定时器 
     }
 
 }
3.2 xTimerStopFromISR()

        xTimerStopFromISR()是函数 xTimerStop()的中断版本,用于停止一个正在运行的软件定时器,让其进入休眠态,实现过程也是通过“定时器命令队列”向软件定时器任务发送停止命令。

 /* 这个方案假定软件定时器 xTimer 已经创建且启动。
 当中断发生时,停止软件定时器 */
 
 /* 停止软件定时器的中断服务函数*/
 void vAnExampleInterruptServiceRoutine( void )
 {
     BaseType_t xHigherPriorityTaskWoken = pdFALSE;
     
     if (xTimerStopFromISR(xTimer,&xHigherPriorityTaskWoken)!=pdPASS ) { 
         /* 软件定时器停止命令没有成功执行 */
     }
      
     if ( xHigherPriorityTaskWoken != pdFALSE ) { 
         /* 执行上下文切换 */
     }
 }

4. 软件定时器删除函数 xTimerDelete()

        xTimerDelete()用于删除一个已经被创建成功的软件定时器,删除之后就无法使用该定时器,并且定时器相应的资源也会被系统回收释放。要想使函数 xTimerStop()必须在头文件 FreeRTOSConfig.h 中把宏 configUSE_TIMERS 定义为 1

 static TimerHandle_t Swtmr1_Handle =NULL; /* 软件定时器句柄 */
 
 /* 周期模式的软件定时器 1,定时器周期 1000(tick)*/
 Swtmr1_Handle=xTimerCreate((const char* )"AutoReloadTimer",
                             (TickType_t )1000,/* 定时器周期 1000(tick) */
                             (UBaseType_t)pdTRUE,/* 周期模式 */
                             (void* )1,/* 为每个计时器分配一个索引的唯一 ID */
                             (TimerCallbackFunction_t)Swtmr1_Callback); /* 回调函数 */
 if (Swtmr1_Handle != NULL)
 {
         /****************************************************************
         * xTicksToWait:如果在调用 xTimerStart()时队列已满,则以 tick 为单位指定调用任务应保持
         * 在 Blocked(阻塞)状态以等待 start 命令成功发送到 timer 命令队列的时间。
         * 如果在启动调度程序之前调用 xTimerStart(),则忽略 xTicksToWait。在这里设置等待时间为 0.
         *****************************************************************/
     xTimerStart(Swtmr1_Handle,0); 
    //开启周期定时器
 }
 
 static void test_task(void* parameter)
 {
     while (1) {
     /* 用户自己实现任务代码 */
     xTimerDelete(Swtmr1_Handle,0); //删除软件定时器 
     }
 }

5. 改变定时器周期函数 xTimerChangePeriod()

xTimerChangePeriod( gps_led_timer, 500, 0 );

        这段代码是使用GPS模块的SDK,调用了一个名为xTimerChangePeriod的函数,该函数用于改变定时器的周期。

        具体来说,xTimerChangePeriod函数用于修改定时器的周期,使其在指定的时间间隔内触发一次。在这个例子中,gps_led_timer是一个定时器对象,500表示新的周期为500毫秒,0表示不使用计时器中断。

        需要注意的是,这段代码中的变量名和函数名都是自定义的,可能与实际的SDK中的函数和变量名有所不同。此外,这段代码可能是在嵌入式系统中使用的,因此需要了解该系统的具体硬件和软件环境。

注意事项

使用软件定时器时候要注意以下几点:

1、软件定时器的回调函数中应快进快出,绝对不允许使用任何可能引软件定时器起任务挂起或者阻塞的 API 接口,在回调函数中也绝对不允许出现死循环。

2、软件定时器使用了系统的一个队列和一个任务资源,软件定时器任务的优先级默认为configTIMER_TASK_PRIORITY,为了更好响应,该优先级应设置为所有任务中最高的优先级。

3、创建单次软件定时器,该定时器超时执行完回调函数后,系统会自动删除该软件

定时器,并回收资源。

4、定时器任务的堆栈大小默认为 configTIMER_TASK_STACK_DEPTH 个字节。

由于知识点比较的多,这里介绍的消息队列和软件定时器,都是比较常用的,其中信号量也是比较常用的。可以访问下面的连接,里面有相关笔记。

FreeRTOS相关知识点

FreeRTOS官网

相关推荐

  1. 嵌入学习-FreeRTOS-Day2

    2024-07-12 11:46:02       39 阅读
  2. 嵌入基础知识

    2024-07-12 11:46:02       27 阅读
  3. 嵌入中间_2.嵌入中间分类

    2024-07-12 11:46:02       28 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-12 11:46:02       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-12 11:46:02       72 阅读
  3. 在Django里面运行非项目文件

    2024-07-12 11:46:02       58 阅读
  4. Python语言-面向对象

    2024-07-12 11:46:02       69 阅读

热门阅读

  1. 【Linux Git入门】Git的介绍

    2024-07-12 11:46:02       22 阅读
  2. 【git命令大全】

    2024-07-12 11:46:02       25 阅读
  3. 代付是什么意思呢

    2024-07-12 11:46:02       23 阅读
  4. 初阶数据结构(顺序表的实现)

    2024-07-12 11:46:02       25 阅读
  5. 一键优雅为Ubuntu20.04服务器挂载新磁盘

    2024-07-12 11:46:02       19 阅读
  6. ubuntu22.04 编译freetype动态库

    2024-07-12 11:46:02       23 阅读
  7. Uniapp 使用 Leaflet

    2024-07-12 11:46:02       26 阅读
  8. 使用Python绘制旭日图

    2024-07-12 11:46:02       23 阅读
  9. Qt/QML学习-GridView

    2024-07-12 11:46:02       19 阅读
  10. bug定位策略

    2024-07-12 11:46:02       21 阅读