FreeRTOS

zzy 2022-01-09 15:42:36
Categories: Tags:

preliminary

之前已经开始过一版了,但限于种种原因,没有坚持学完,这次希望以项目驱动,demo驱动,减少引用来自网页tutorial 的图片,常需要使用的函数另做整理,只看只记录必要的信息。

目前使用RTOS的主要动机是因为原本的代码结构太过丑陋,不便于调试及整理合并。

对架构的问题:

  • 多个Task之间是内存保护的么?
    • 内存保护是RTOS建立在MPU(Memory Protection Unit)基础之上的,为可选项,由IC提供硬件,RTOS提供软件Support
    • 特别的,我们所选用的STM32G431RBT6是Cortex-M4 附有MPU的
  • processor存在内核态,用来进行系统中断。
  • Task 的上下文包括栈(还有什么?

总结:

  • 应该时刻注意RTOS是实时嵌入式系统,其目的与一般的操作系统不同。

Demos

CORTEX_STM32F103_Keil

共含有三个task:

引出的问题:

正文

程序主体 Task

Co-routine 主要是为了降低对RAM的要求,随着目前设备的发展,已经很少会用,如果进一步有需求,再说。

一个RTOS系统可以看作由多个TASK构成,其中有一个负责分发时间片以及切换上下文的TASK,称为Scheduler。

Task state共有四种,分别为running, ready, blocked 以及suspended。其中挂起vTaskSuspend()可以挂起任意Task(当前Task或其他任意状态的Task)。当configUSE_TIME_SLICING未定义或者被定义为1时,才会同一优先级的多个Task时分复用。而Task scheduling 共面对三种情况:

Task Implementation

  void vATaskFunction( void *pvParameters )
  {
      for( ;; )
      {
          /* Psudeo code showing a task waiting for an event 
            with a block time. If the event occurs, process it.  
            If the timeout expires before the event occurs, then 
            the system may be in an error state, so handle the
            error.  Here the pseudo code "WaitForEvent()" could 
            replaced with xQueueReceive(), ulTaskNotifyTake(), 
            xEventGroupWaitBits(), or any of the other FreeRTOS 
            communication and synchronisation primitives. */
          if( WaitForEvent( EventObject, TimeOut ) == pdPASS )
          {
              -- Handle event here. --
          }
          else
          {
              -- Clear errors, or take actions here. --
          }
      }

      /* If the task needs to be terminated, use vTaskDelete instead of return */
      vTaskDelete( NULL );
  }
//PS: use event-driven instead of looping 

intermediary for inter-task communications: Queue, Semaphore, Mutex

Queue

用于在task之间,以及task与interrupt之间传递消息。(Interrupt 与 Task 采用不同抽象)。

Semaphore Vs. Mutex

TIP: 'Task Notifications' can provide a light weight alternative to binary semaphores in many situations

因为Mutex 实现了优先级机制,并且Mutex不可用于中断,而semaphore 并没有。

Priority Inheritance: 当一个更高优先级的task申请mutex时,将目前正占有该mutex的task的优先级暂时升高到相同。以避免出现priority inversion.

Deferred Interrupt: The Peripheral interrupt will immediately send data to a queue and block, the post processing will occur when the processor is free. 简单说,这样可以保证各task按时正常进行。

Task Notification

TIP: 如果使用Stream and Message Buffers, the task notification at array index 0 is preserved.

相较于前一章所提到的用于传输信息的实例,Task Notification 更快且更节省空间。

代替Binary Semaphore

/* This is an example of a transmit function in a generic
peripheral driver.  An RTOS task calls the transmit function,
then waits in the Blocked state (so not using an CPU time)
until it is notified that the transmission is complete.  The
transmission is performed by a DMA, and the DMA end interrupt
is used to notify the task. */

/* Stores the handle of the task that will be notified when the
transmission is complete. */
static TaskHandle_t xTaskToNotify = NULL;

/* The index within the target task's array of task notifications
to use. */
const UBaseType_t xArrayIndex = 1;

/* The peripheral driver's transmit function. */
void StartTransmission( uint8_t *pcData, size_t xDataLength )
{
    /* At this point xTaskToNotify should be NULL as no transmission
    is in progress.  A mutex can be used to guard access to the
    peripheral if necessary. */
    configASSERT( xTaskToNotify == NULL );

    /* Store the handle of the calling task. */
    xTaskToNotify = xTaskGetCurrentTaskHandle();

    /* Start the transmission - an interrupt is generated when the
    transmission is complete. */
    vStartTransmit( pcData, xDatalength );
}
/*-----------------------------------------------------------*/

/* The transmit end interrupt. */
void vTransmitEndISR( void )
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    /* At this point xTaskToNotify should not be NULL as
    a transmission was in progress. */
    configASSERT( xTaskToNotify != NULL );

    /* Notify the task that the transmission is complete. */
    vTaskNotifyGiveIndexedFromISR( xTaskToNotify,
                                   xArrayIndex,
                                   &xHigherPriorityTaskWoken );
    //不是信号量给过去之后这个就被抢占了?
    //所以中断不会被抢占,可为什么要切换上下文
    
    /* There are no transmissions in progress, so no tasks
    to notify. */
    xTaskToNotify = NULL;

    /* If xHigherPriorityTaskWoken is now set to pdTRUE then a
    context switch should be performed to ensure the interrupt
    returns directly to the highest priority task.  The macro used
    for this purpose is dependent on the port in use and may be
    called portEND_SWITCHING_ISR(). */
    // 为什么要手动切换上下文??
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/*-----------------------------------------------------------*/

/* The task that initiates the transmission, then enters the
Blocked state (so not consuming any CPU time) to wait for it
to complete. */
void vAFunctionCalledFromATask( uint8_t ucDataToTransmit,
                                size_t xDataLength )
{
uint32_t ulNotificationValue;
const TickType_t xMaxBlockTime = pdMS_TO_TICKS( 200 );

    /* Start the transmission by calling the function shown above. */
    StartTransmission( ucDataToTransmit, xDataLength );

    /* Wait to be notified that the transmission is complete.  Note
    the first parameter is pdTRUE, which has the effect of clearing
    the task's notification value back to 0, making the notification
    value act like a binary (rather than a counting) semaphore.  */
    ulNotificationValue = ulTaskNotifyTakeIndexed( xArrayIndex,
                                                   pdTRUE,
                                                   xMaxBlockTime );

    if( ulNotificationValue == 1 )
    {
        /* The transmission ended as expected. */
    }
    else
    {
        /* The call to ulTaskNotifyTake() timed out. */
    }
}

Stream & Message Buffers

正常只支持单个task读,单个task写,如果需要多个,自行上锁,这两个功能并不是默认开启的。

Software Timers

Efficiency Consideration:

  • does not execute timer callback functions from an interrupt context
  • does not consume any processing time unless a timer has actually expired
  • does not add any processing overhead to the tick interrupt
  • does not walk any link list structures while interrupts are disabled

这个功能也是可选的,将Timer 抽象为Timer Service Task,功能主要经由API移交给该task。

memory allocating

考虑到各式的嵌入式系统有不同的需求,将heap management schemes的实现方式交由用户决定,同时提供了五种可选方案:

同样的,应对栈溢出的解决方案也留待用户自行决定。

Secondary Docs

Idle Tasks

系统自动创建,为全场最低优先级,用于Garbage Collection。为了保证系统时刻有可以运行的Task,Idle Task千不能万不能陷入Block状态,同样,其他任何Task 都不能设置做Loop。

Tips: 使用vTaskDelete() 结束Task,会自动被Idle Task 清理。

Hook Functions

挂钩函数,可以由某些Task的特定生命进程唤醒,例如:

大概是若Idle Task 都没有Garbage to collect时,会触发Idle Hook Function,例如可将系统进入低功耗模式

MPU Support

FreeRTOS 共提供两种方式使得应用更安全:

综上,FreeRTOS的解决方式更类似于权限管理,而非细粒度的每个Task存在其私有空间。

Thread Local Storage Pointers (TLS)

可以获取其他Task 的某个index 的TLS。

Blocking on Multiple RTOS objects

Enable tasks to be blocked on multiple queues and/or semaphores.

Deferred Interrupt Handling

为减少中断所占用的时长,将数据后处理的工作放在一些优先级较低(较ISR而言)的Task中进行。

  1. Centralised Deferred Interrupt Handling:

  2. Application Controlled Deferred Interrupt Handling

https://www.freertos.org/RTOS_Task_Notification_As_Counting_Semaphore.html

第二个实例是否可以用于监测stopper?

本质区别是,第一类是每次interrupt后由daemon task 新建一个function来处理数据,第二类则是存在一个Task,一直在等待这个信号量。

Task implementation

Real Time Operating System 和普通操作系统对多线程的需求不同,需要保证某些Task 在某时刻之前完成,引入了优先级的概念,而时分复用只会出现在相同优先级的task之间。