[uCOS] uCOS II system startup and clock

Posted by Jean-Yves on Sat, 05 Mar 2022 08:15:00 +0100

All ideas come from this blog:
[chuangkehai] [operating system principle and embedded UCOS-II teaching video collection] [one week series]
And µ C/OS-II tutorials

Analysis of startup process of UCOS II system

In embedded system, startup is divided into hardware startup and software startup. For example, there are many ARM cores.

Hard start mainly refers to the initialization and start-up of hardware system (including various functional components);

Soft Start refers to the initialization of the operating system kernel and the startup of multitasking user programs, such as init.

Initialization of uCOS kernel:

Mainly run OSInit to initialize the uCOS system, and then you can create a user task.
Then run osstrat(); Let the TCB of the operating system manage the CPU.

System initialization function: OSInit()

Initialize all data structures. For example, the buffer pool of task readiness table of task readiness group is 0 The control block is also 0

Some global variables are fully initialized. For example, OSrunning is set to FALSE to prevent it from running.

Also, make all the linked lists generated by FreeList point to 0

Code in the system:

*                                             INITIALIZATION
* Description: this function is used to initialize the internal components of uC/OS-II and must be called before that.
*      Create any uC / OS-II object before calling OSStart().
* Arguments  : none
* Returns    : none

void  OSInit (void)
#if OS_VERSION >= 204
    OSInitHookBegin();                                           /* Call port specific initialization code   */

    OS_InitMisc();                                               /* Initialize miscellaneous variables       */

    OS_InitRdyList();                                            /* Initialize the Ready List                */

    OS_InitTCBList();                                            /* Initialize the free list of OS_TCBs      */

    OS_InitEventList();                                          /* Initialize the free list of OS_EVENTs    */

#if (OS_VERSION >= 251) && (OS_FLAG_EN > 0) && (OS_MAX_FLAGS > 0)
    OS_FlagInit();                                               /* Initialize the event flag structures     */

#if (OS_MEM_EN > 0) && (OS_MAX_MEM_PART > 0)
    OS_MemInit();                                                /* Initialize the memory manager            */

#if (OS_Q_EN > 0) && (OS_MAX_QS > 0)
    OS_QInit();                                                  /* Initialize the message queue structures  */

    OS_InitTaskIdle();                                           /* Create the Idle Task                     */
    OS_InitTaskStat();                                           /* Create the Statistic Task                */

#if OS_VERSION >= 204
    OSInitHookEnd();                                             /* Call port specific init. code            */

#if OS_VERSION >= 270 && OS_DEBUG_EN > 0

Startup function: OSStrat()

Code in the system:

void  OSStart (void)
    INT8U y;
    INT8U x;
    if (OSRunning == FALSE) {
        y             = OSUnMapTbl[OSRdyGrp];        /* Find highest priority's task priority number   */
        x             = OSUnMapTbl[OSRdyTbl[y]];
        OSPrioHighRdy = (INT8U)((y << 3) + x);
        OSPrioCur     = OSPrioHighRdy;
        OSTCBHighRdy  = OSTCBPrioTbl[OSPrioHighRdy]; /* Point to highest priority task ready to run    */
        OSTCBCur      = OSTCBHighRdy;
        OSStartHighRdy();                            /* Execute target specific code to start task     */

When OSStart() is called, OSStart() finds the task control block of the task with the highest priority established by the user from the task readiness table.
Then, OSStart() calls the high priority ready task startup function OSStartHighRdy(), (see the assembly language file OS_CPU_A.ASM), which is related to the selected microprocessor.

PS: OSStartHighRdy is a function written in assembly. Its functions are:
Note : OSStartHighRdy() MUST:
; a) Call OSTaskSwHook() then,
; b) Set OSRunning to TRUE to set running to true
; c) Switch to the highest priority task. Select the highest priority task

In essence, the function OSStartHighRdy() bounces the value saved in the task stack back to the CPU register, and then executes an interrupt return instruction, which enforces the task code.
Note that OSStartHighRdy() will never return to OSStart().

UCOS II clock

The clock plays an important role in an operating system kernel. It is a "pacemaker" that drives an OS kernel, which is equivalent to the heart of uCOS. The clock comes from hardware (crystal oscillator).

Function of clock:

Record timestamp

How to use it?

It should be called by the task after OSStart.

ps: ST_Enable is the clock enable function (my own)

Implementation function of clock OSTickISR ()

Most of them are implemented by assembly, which can directly operate the internal registers of the chip, and the operation efficiency is high.
Otime() function called inside:
1. Add 1 to the task counter OSTime
2: Traverse all task control blocks in the TCB linked list of task control blocks (start from OSTCBList, search along the OS_TCB linked list, and always find idle tasks), and reduce the time delay OSTCBDly() variable of each task control block by 1 When the time delay item OSTCBDly in the task control block of a task is reduced to zero, the task enters the ready state.

User API interface:

See Chapter 6 communication and synchronization between tasks for details

Time management examples and related source code analysis

The example is confidential. I'm sorry I can't show you
Buy your own books

Source code analysis:

Task delay function, OSTimeDly()

The functions mentioned in this chapter can be found in OS_ TIME. Found in C file.
µ C/OS - Ⅱ provides such a system service: the task applying for the service can be delayed for a period of time, which is determined by the number of clock beats.
The function that implements this system service is called OSTimeDly().
Calling this function will make µ C/OS - Ⅱ schedule a task and execute the next ready task with the highest priority.
After a task calls OSTimeDly(), it will immediately enter the ready state once the specified time expires or other tasks cancel the delay by calling OSTimeDlyResume().
Note that the task will run immediately only if it has the highest priority among all ready tasks.

As follows, the user's application calls this function by providing the number of delayed clock beats - a number between 1 and 65535.
If the user specifies a value of 0 [(1)], it indicates that the user does not want to delay the task, and the function will immediately return to the caller.
A value other than 0 will cause the task delay function OSTimeDly() to remove the current task from the ready table [(2)].
Then, the delay beat number will be saved in the OS of the current task_ In TCB [(3)], and the number of delay beats is reduced every other clock beat through OSTimeTick().
Finally, since the task is no longer ready, the task scheduler will execute the next highest priority ready task.

void OSTimeDly (INT16U ticks)
    if (ticks > 0) {//(1)
        OS_ENTER_CRITICAL(); //Off interrupt
        if ((OSRdyTbl[OSTCBCur->OSTCBY] &= ~OSTCBCur->OSTCBBitX) == 0) {//(2) 	 Removes the current task from the ready table
            OSRdyGrp &= ~OSTCBCur->OSTCBBitY;
        OSTCBCur->OSTCBDly = ticks;//(3) Save the delay ticks to ostcbcur - > ostcbdly
        OS_EXIT_CRITICAL();	//Open interrupt

**It is very important to clearly recognize 0 the delay process between beats** In other words, if the user only wants to delay one clock beat, but actually ends the delay between 0 and one beat.
Even if the user's processor load is not very heavy, this situation still exists.
Figure F5 1 describes the whole process in detail. Clock beat interrupt occurs every 10ms in the system [F5.1(1)]. If the user does not execute other interrupts and the interrupt is on at this time, the clock beat interrupt service will occur [F5.1(2)].
Maybe the user has several high priority tasks (HPT) waiting for the delay to expire, and they will then execute [F5.1(3)].
Next, the low priority task (LPT) shown in Figure 5.1 will get the opportunity to execute. After the task is executed, it will call OSTimeDly(1) shown in [F5.1(4)].
µ C/OS - Ⅱ will make the task sleep until the next beat comes. After the next beat arrives, the clock beat interrupt service subroutine will execute [F5.1(5)], but this time, because no high priority task is executed, µ C/OS - II will immediately execute the task of applying for a delay of one clock beat [F5.1(6)].
As users can see, the actual delay of this task is less than one beat! In a heavily loaded system, the task may even call OSTimeDly(1) when the clock interrupt is about to occur. In this case, the task hardly gets any delay because the task is rescheduled immediately.
If the user's application has to delay at least one beat, you must call OSTimeDly(2) to specify a delay of two beats!

The number of times the application needs to delay is a very useful function for the user. You can use to define the global constant OS_ TICKS_ PER_ The SEC (see OS_CFG.H) method converts time into clock segments, but this method sometimes seems stupid.

Time delay function ostimedlyhmmsm ()

After the author of ucos added the ostimedlyhmmsm() function, users can define time by hour (H), minute (m), second (S) and millisecond (m), which will be more natural.
Like OSTimeDly(), calling ostimedlyhmmsm() function will also cause µ C/OS - Ⅱ to schedule a task and execute the next ready task with the highest priority.
After the task calls ostimedlyhmmsm(), it will be ready immediately once the specified time expires or other tasks cancel the delay by calling OSTimeDlyResume() (see 5.02, resume the delayed task OSTimeDlyResume()).
Similarly, the task will run immediately only when it has the highest priority among all ready tasks.

INT8U OSTimeDlyHMSM (INT8U hours, INT8U minutes, INT8U seconds, INT16U milli)
    INT32U ticks;
    INT16U loops;

    if (hours > 0 || minutes > 0 || seconds > 0 || milli > 0) {	        (1)
        if (minutes > 59) {
            return (OS_TIME_INVALID_MINUTES);
        if (seconds > 59) {
            return (OS_TIME_INVALID_SECONDS);
        If (milli > 999) {
            return (OS_TIME_INVALID_MILLI);
        ticks = (INT32U)hours    * 3600L * OS_TICKS_PER_SEC	            (2)
              + (INT32U)minutes  *   60L * OS_TICKS_PER_SEC
              + (INT32U)seconds  *         OS_TICKS_PER_SEC
              + OS_TICKS_PER_SEC * ((INT32U)milli
              + 500L/OS_TICKS_PER_SEC) / 1000L;	(3)
        loops = ticks / 65536L;	                                    	(4)
        ticks = ticks % 65536L;	                                        (5)
        OSTimeDly(ticks);	                                            (6)
        while (loops > 0) {	                                            (7)
            OSTimeDly(32768);	                                        (8)
        return (OS_NO_ERR);
    } else {
        return (OS_TIME_ZERO_DLY);	                                       (9)

It can be seen that the application calls the function by specifying the delay in hours, minutes, seconds and milliseconds.
In practical applications, users should avoid delaying tasks for too long, because it is often good to get some feedback from tasks (such as reducing counters, clearing LED s, etc.). However, if the user really needs to delay for a long time, µ C/OS - Ⅱ can delay the task for up to 256 hours (nearly 11 days).

At the beginning of ostimedlyhmmsm(), check whether the user has defined a valid value for the parameter [L5.2(1)]. Like OSTimeDly(), ostimedlyhmmsm() exists even if the user does not define a delay [L5.2(9)]. Because µ C/OS - Ⅱ only knows the beat, the total number of beats is calculated from the specified time [L5.2(3)]. Obviously, listing L5 The procedure in 2 is not very effective. I just use this method to tell you a formula, so that users can know how to calculate the total number of beats. What really matters is OS_ TICKS_ PER_ SEC. [L5.2(3)] determines the total number of clock beats closest to the time to be delayed. 500/OS_ TICKS_ PER_ The value of second is basically the same as the number of milliseconds corresponding to 0.5 beats. For example, if the clock frequency (OS_TICKS_PER_SEC) is set to 100Hz(10ms), a delay of 4ms will not produce any delay! A delay of 5ms is equal to a delay of 10ms.
µ C/OS - Ⅱ supports a maximum delay of 65535 beats. To support longer delay, such as L5 As shown in 2 (2), ostimedlyhmmsm () determines how many times the user wants to delay more than 65535 beats [L5.2(4)] and the remaining beats [L5.2(5)]. For example, if OS_ TICKS_ PER_ The SEC value is 100. If the user wants to delay 15 minutes, ostimedlyhmmsm() will delay 15x60x100 = 90000 clocks. This delay will be divided into two 32768 beat delays (because users can only delay 65535 beats instead of 65536 beats) and one 24464 beat delay. In this case, ostimedlyhmmsm () first considers the remaining beats, and then the number of beats exceeding 65535 L5.2(7) and (8).

because OSTimeDlyHMSM()The user cannot end the delayed call OSTimeDlyHMSM()Tasks requiring a delay of more than 65535 beats. In other words, if the frequency of the clock beat is 100 Hz,The user cannot make the call OSTimeDlyHMSM(0,10,55,350)Or a longer delay.

Delay the end of the task in the deferred period, OSTimeDlyResume()

µ C/OS - Ⅱ allows users to end tasks whose delay is in the delay period.
The delayed task can not wait for the expiration of the delay, but cancel the delay by other tasks to make itself ready. This can be done by calling OSTimeDlyResume() and specifying the priority of the task to be recovered.
In fact, OSTimeDlyResume() can also wake up tasks that are waiting for events (see Chapter 6 - Communication and synchronization between tasks), although this is not mentioned.

In this case, the task waiting for the event will consider whether to terminate the waiting event.
The code of OSTimeDlyResume() is shown in listing L5 As shown in Figure 3, it first needs to ensure that the specified task priority is valid [L5.3(1)].
Then, OSTimeDlyResume() confirms that the task to end the delay does exist [L5.3(2)].
If the task exists, OSTimeDlyResume() will check whether the task is waiting for the delay to expire [L5.3(3)].
As long as OS_ If OSTCBDly in the TCB domain contains a non-zero value, it indicates that the task is waiting for the delay to expire, because the task calls OSTimeDly(), ostimedlyhmmsm() or other PEND functions described in Chapter 6.
Then the delay can be cancelled by forcing the command OSTCBDly to 0 [L5.3(4)].
The delayed task may have been suspended. In this case, the task can be ready only if it is not suspended [L5.3(5)].
When the above conditions are met, the task will be placed in the ready table [L5.3(6)].
At this time, OSTimeDlyResume() will call the task scheduler to see if the recovered task has a higher priority than the current task [L5.3(7)].
This will lead to task switching.

Resume deferred tasks:

INT8U OSTimeDlyResume (INT8U prio)
    OS_TCB *ptcb;

    if (prio >= OS_LOWEST_PRIO) {	                                     (1)
        return (OS_PRIO_INVALID);
    ptcb = (OS_TCB *)OSTCBPrioTbl[prio];	
    if (ptcb != (OS_TCB *)0) {	                                        (2)
        if (ptcb->OSTCBDly != 0) {	                                    (3)
            ptcb->OSTCBDly  = 0;	                                      (4)
            if (!(ptcb->OSTCBStat & OS_STAT_SUSPEND)) {	               (5)
                OSRdyGrp               |= ptcb->OSTCBBitY;	            (6)
                OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
                OSSched();	                                            (7)
            } else {
            return (OS_NO_ERR);
        } else {
            return (OS_TIME_NOT_DLY); 
    } else {
        return (OS_TASK_NOT_EXIST); 

Note that the user's task may be delayed by temporarily waiting for semaphores, mailboxes or message queues (see Chapter 6). Such tasks can be resumed simply by controlling semaphores, mailboxes, or message queues. The only problem with this situation is that it requires the user to allocate event control blocks (see 6.00), so the user's application will occupy more RAM.

System time OSTimeGet() and OSTimeSet()

No matter when the clock beat occurs, µ C/OS - Ⅱ will increase a 32-bit counter by 1. This counter starts counting from 0 when the user calls OSStart() to initialize multitasking and execute 4294967295 beats. When the clock beat frequency is equal to 100Hz, the 32-bit counter starts counting again every 497 days. The user can get the current value of this counter by calling OSTimeGet(). You can also change the value of this counter by calling OSTimeSet(). The code of OSTimeGet() and OSTimeSet() functions is shown in listing L5 4. Note that the interrupt is turned off when accessing OSTime. This is because adding and copying a 32-bit number on most 8-bit processors requires several instructions. These instructions generally need to be executed at one time and cannot be interrupted by factors such as interruption.

When the clock goes wrong, it's all over

INT32U OSTimeGet (void)
    INT32U ticks;

    ticks = OSTime;
    return (ticks);

void OSTimeSet (INT32U ticks)
    OSTime = ticks;

Topics: Single-Chip Microcomputer IoT ARM ucos