uC/OS-III


Compared to evaluating whether one has the ability to develop projects using uC/OS-III, a more fundamental question is whether uC/OS-III is suitable for the current project. In this article, I will first compare the application scenarios of uC/OS-III and bare-metal development, focusing on MCU resource requirements and development time. After that, I will delve into key concepts related to uC/OS-III, which may involve interpreting uC/OS-III kernel files. Initially, the goal is not to systematically and comprehensively grasp the knowledge, but rather to focus on absorbing and understanding the key concepts. This point-by-point approach will help gradually develop a comprehensive understanding of uC/OS-III. The background of this article is that the author has already conducted a first demo of uC/OS-III on the STM32F103C8T6 MCU.

In uC/OS-III (uCOS3), tasks are divided based on the priority system, where each task has a unique priority. Task division in a real-time operating system (RTOS) like uCOS3 depends on the requirements of the project, such as timing constraints, task functionality, and system complexity.

Task Division in uCOS-III:

  1. Priority-based Scheduling: Each task is assigned a unique priority, and the OS schedules tasks based on priority. Higher-priority tasks preempt lower-priority tasks.
  2. Task Functions: Tasks are typically divided based on their functions, such as:
    • Control Tasks: Responsible for managing critical system operations, like motor control or sensor data processing.
    • Communication Tasks: Handle communication protocols like UART, SPI, or I2C for data exchange.
    • UI/Display Tasks: Manage user interface updates, such as an LCD screen.
    • Background Tasks: Handle lower-priority activities like logging data, housekeeping, or monitoring battery status.
  3. Preemptive Multitasking: uCOS3 supports preemptive multitasking, meaning high-priority tasks can interrupt lower-priority tasks, ensuring critical operations are handled on time.
  4. Task Stacks: Each task has its own stack, and the system switches between them during context switching.

Example of Task Division in a Real-World Project:

Project Example: Let’s consider an industrial motor control system using uCOS-III.

  • Task 1: Motor Control (High Priority)
    This task is responsible for real-time control of the motor, ensuring precise speed and position control.
  • Task 2: Sensor Data Acquisition (Medium Priority)
    Reads sensor data like temperature, voltage, and current from the motor. It updates every few milliseconds to ensure data is available for control algorithms.
  • Task 3: Communication (Medium-Low Priority)
    Manages communication over UART or CAN bus with a central system to send status updates and receive commands.
  • Task 4: User Interface (Low Priority)
    Updates the LCD or other interfaces to show motor status, logs, and other non-critical information.
  • Task 5: System Monitoring (Lowest Priority)
    Background task to log data, check battery levels, and perform system diagnostics.

When to Use uCOS-III:

uCOS-III is ideal for time-critical and embedded systems that require predictable task scheduling and management. Some typical use cases include:

  1. Industrial Automation: Motor control systems, PLCs, and robots where precise timing is essential.
  2. Medical Devices: For example, ventilators or infusion pumps where system reliability and response time are critical.
  3. Automotive Systems: Managing various in-car systems like ADAS (Advanced Driver Assistance Systems), real-time sensor data processing, and communication between systems.
  4. IoT Devices: Real-time data acquisition, communication with servers, and local control for sensors and actuators in smart devices.

Example:
A smart thermostat system with uCOS-III might have tasks such as:

  • Temperature Control Task (high priority) to regulate the HVAC system.
  • Sensor Monitoring Task to track temperature, humidity, and air quality.
  • Wi-Fi Communication Task to send data to the cloud.
  • User Interface Task for updating the display.

In such systems, the ability of uCOS-III to handle real-time constraints, multitasking, and inter-task communication makes it an excellent choice.

一、互斥量和二值信号量的异同

在uC/OS-III中,互斥量(Mutex)二值信号量(Binary Semaphore) 都是用于任务间同步和资源保护的机制,但它们的设计目标和使用场景有所不同。下面是它们在uC/OS-III中的异同点:

相同点:

  1. 同步与资源保护:两者都可以用于任务之间的同步,防止多个任务同时访问共享资源,避免数据不一致或竞态条件。
  2. 二值状态:互斥量和二值信号量都只有两种状态,类似于“锁定”和“解锁”。
    • 互斥量:锁定或未锁定。
    • 二值信号量:0或1,表示信号可用或不可用。
  3. Pend和Post操作:两者都通过类似的机制来操作:
    • Pend:等待获取互斥量或信号量。
    • Post:释放互斥量或发送信号。

不同点:

特征互斥量(Mutex)二值信号量(Binary Semaphore)
设计目标专为任务间互斥而设计,主要用于保护共享资源。用于信号传递和简单同步,可用于任务间或中断与任务间同步。
优先级继承支持优先级继承机制,防止优先级反转问题。不支持优先级继承,可能导致优先级反转问题。
拥有者概念互斥量有任务拥有者的概念,只有获得互斥量的任务可以释放它。没有拥有者概念,任何任务或中断都可以释放信号量。
典型使用场景主要用于防止竞态条件,例如保护共享数据或硬件资源。用于任务间信号传递或简单的同步,适合中断与任务之间通信。
递归使用允许任务递归地获取同一个互斥量(任务可以多次获取它)。不允许递归获取,任务不能多次获取同一个信号量。
中断安全性不能在中断上下文中使用,因为它与任务调度有关。可以在中断上下文中使用,适合中断与任务的同步。
性能开销一般情况下,互斥量的开销较高,尤其是优先级继承机制。二值信号量的开销较小,适合快速同步操作。

互斥量(Mutex)的特性:

  1. 优先级继承:这是互斥量的一个重要特性。如果一个高优先级任务等待一个被低优先级任务占用的互斥量,系统会暂时将低优先级任务的优先级提升到高优先级任务的级别,以防止高优先级任务长时间被阻塞。这有效解决了优先级反转问题。
  2. 所有者概念:只有占有互斥量的任务可以释放它。这使得它特别适合在多任务中保护共享资源。

二值信号量(Binary Semaphore)的特性:

  1. 无优先级继承:二值信号量不提供优先级继承,因此可能出现优先级反转的问题。
  2. 简单的同步:二值信号量没有“拥有者”概念,因此任何任务或中断都可以释放它。适合于任务之间的简单同步或信号传递,尤其是中断与任务之间的通信。

适用场景对比:

  • 互斥量:当多个任务需要访问共享资源(如全局变量、硬件资源)时,应使用互斥量。尤其是需要防止竞态条件和优先级反转时,互斥量更合适。
  • 二值信号量:适用于任务之间的同步或中断与任务的信号传递。例如,某个任务需要等待一个中断事件的发生。

代码示例:

互斥量示例:

c复制代码OS_MUTEX MyMutex;

void Task1(void *p_arg) {
    OS_ERR err;

    while (1) {
        // 获取互斥量
        OSMutexPend(&MyMutex, 0, OS_OPT_PEND_BLOCKING, NULL, &err);
        
        // 访问共享资源
        // ... 
        
        // 释放互斥量
        OSMutexPost(&MyMutex, OS_OPT_POST_NONE, &err);
    }
}

二值信号量示例:

c复制代码OS_SEM MySem;

void Task1(void *p_arg) {
    OS_ERR err;

    while (1) {
        // 等待信号量
        OSSemPend(&MySem, 0, OS_OPT_PEND_BLOCKING, NULL, &err);
        
        // 收到信号后执行操作
        // ... 
    }
}

void ISR_Handler(void) {
    OS_ERR err;

    // 中断服务例程中发送信号量
    OSSemPost(&MySem, OS_OPT_POST_1, &err);
}

总结:

  • 互斥量适用于多任务保护共享资源,尤其是在避免优先级反转的场景下。
  • 二值信号量适用于简单的任务间同步或中断和任务间的通信。

二、互斥量的优先级继承机制

在uC/OS-III中,互斥量支持优先级继承,其主要作用是防止优先级反转问题。优先级反转是指一个高优先级任务因为等待低优先级任务释放资源而被阻塞,而低优先级任务却可能被其他更高优先级的任务抢占,导致高优先级任务长时间无法运行。

优先级继承机制通过在低优先级任务持有互斥量时,将该任务的优先级暂时提升到与等待该互斥量的最高优先级任务相同的级别。这样,持有互斥量的低优先级任务能够尽快完成并释放资源,减少高优先级任务的等待时间。

优先级继承的实现

在uC/OS-III中,当一个低优先级任务持有互斥量时,且一个更高优先级的任务等待该互斥量:

  1. 提升持有互斥量的任务优先级:uC/OS-III会临时将低优先级任务的优先级提升到与等待任务的优先级相同。
  2. 任务执行并释放互斥量:当持有互斥量的任务完成其工作并释放互斥量后,系统将其优先级恢复到原来的级别。
  3. 高优先级任务继续执行:高优先级任务接管互斥量,继续执行它的工作。

三、uC/OS-III操作系统在后台自动处理和管理优先级继承机制的代码

uC/OS-III的os_mutex.c文件中包含了互斥量的相关实现代码,包括创建、获取(OSMutexPend())、释放(OSMutexPost())等操作。

在uC/OS-III中,优先级继承机制是操作系统内核自动处理的,它并不需要用户在应用代码中手动编写。优先级继承的逻辑实际上已经在uC/OS-III的互斥量相关的内核代码中实现了,主要体现在以下几个关键函数中:

核心机制:

  1. OSMutexPend():任务尝试获取互斥量的函数。如果互斥量已经被其他任务占用,操作系统会决定是否要提升占用互斥量任务的优先级(如果等待任务的优先级更高)。
  2. OSMutexPost():当任务释放互斥量时,操作系统会恢复该任务的原始优先级,并让等待的高优先级任务接管互斥量。

具体实现思路

1. OSMutexPend():等待互斥量的任务

当任务调用OSMutexPend()时,如果互斥量已经被其他任务占用,系统会进行以下操作:

  • 检查占用互斥量任务的优先级:如果占用互斥量的任务的优先级低于当前等待任务的优先级,系统会临时提升占用互斥量任务的优先级到与等待任务相同的优先级(即优先级继承)。
  • 任务进入等待状态:等待任务进入阻塞状态,直到互斥量被释放。

相关代码的核心逻辑位于OSMutexPend()函数中:

void OSMutexPend(OS_MUTEX *p_mutex, OS_TICK timeout, OS_OPT opt, OS_TICK *p_ts, OS_ERR *p_err) {
    ...
    if (p_mutex->OwnerTCB != (OS_TCB *)0) {  // 互斥量已被占用
        if (p_mutex->OwnerTCB->Prio > p_tcb->Prio) {  // 任务的优先级比持有互斥量的任务高
            p_mutex->OwnerTCB->Prio = p_tcb->Prio;  // 提升持有任务的优先级
        }
        ...
        // 当前任务进入等待队列,等待互斥量被释放
        OS_Pend(&p_mutex->PendList, (OS_TICK)timeout, (OS_OPT)opt, (OS_TICK *)p_ts, (OS_ERR *)&err);
    }
    ...
}

2. OSMutexPost():释放互斥量的任务

当任务完成了对共享资源的访问并调用OSMutexPost()释放互斥量时,系统会进行以下操作:

  • 恢复任务原始优先级:如果该任务的优先级曾经因为优先级继承而被提升,系统会将它的优先级恢复为原始优先级。
  • 唤醒等待的高优先级任务:如果有任务在等待互斥量,操作系统会将互斥量分配给优先级最高的任务,并让它继续执行。

相关代码的核心逻辑位于OSMutexPost()函数中:

c复制代码void OSMutexPost(OS_MUTEX *p_mutex, OS_OPT opt, OS_ERR *p_err) {
    ...
    if (p_mutex->OwnerTCB != (OS_TCB *)0) {  // 检查互斥量持有者
        if (p_mutex->OwnerTCB->Prio != p_mutex->OwnerOriginalPrio) {  // 检查是否有优先级继承
            p_mutex->OwnerTCB->Prio = p_mutex->OwnerOriginalPrio;  // 恢复任务的原始优先级
        }
        ...
        // 唤醒等待任务
        OS_PendListPost(&p_mutex->PendList, (void *)p_mutex, (OS_OPT)opt, (OS_ERR *)&err);
    }
    ...
}

3. 优先级继承的主要逻辑

  • 当低优先级任务占用互斥量,高优先级任务试图获取时,低优先级任务的优先级被临时提升到高优先级任务的级别。这个过程由OSMutexPend()内部自动完成。
  • 当低优先级任务释放互斥量时,它的优先级会被恢复到初始值。这个过程在OSMutexPost()中完成。

四、µC/OS-III® to FreeRTOS Migration Guide Introduction

以下网址介绍了如何将 µC/OS -III 迁移到 FreeRTOS,适用于希望将基于 µC/OS -III 的应用程序迁移到 FreeRTOS 的客户。您会发现将 µC/OS -III 应用程序迁移到 FreeRTOS 非常简单,在大多数情况下只需几个小时。

https://wiki.analog.com/resources/tools-software/freertos/migration-guide/ucos-to-freertos-migration-guide#ucos-iii_to_freertos_migration_guide

Leave a Reply

您的电子邮箱地址不会被公开。 必填项已用 * 标注

Related Post