4. uCOS III的启动流程

在目前的RTOS中,主要有两种比较流行的启动方式,暂时还没有看到第三种,接下来我将通过伪代码的方式来讲解下这两种启动方式的区别,然后再具体分析下uCOS的启动流程。

4.1. 万事俱备,只欠东风

第一种我称之为万事俱备,只欠东风法。这种方法是在main函数中将硬件初始化,RTOS系统初始化,所有任务的创建这些都弄好,这个我称之为万事都已经准备好。最后只欠一道东风,即启动RTOS的调度器,开始多任务的调度,具体的伪代码实现见 代码清单18-1

代码清单‑1万事俱备,只欠东风法伪代码实现
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    int main (void)
    {
    /* 硬件初始化 */
            HardWare_Init();(1)

    /* RTOS 系统初始化 */
            RTOS_Init();(2)

    /* 创建任务1,但任务1不会执行,因为调度器还没有开启 */(3)
            RTOS_TaskCreate(Task1);
    /* 创建任务2,但任务2不会执行,因为调度器还没有开启 */
    RTOS_TaskCreate(Task2);

    /* ......继续创建各种任务 */

    /* 启动RTOS,开始调度 */
            RTOS_Start();(4)
    }

    voidTask1( void *arg )(5)
    {
    while (1)
            {
    /* 任务实体,必须有阻塞的情况出现 */
            }
    }

    voidTask1( void *arg )(6)
    {
    while (1)
            {
    /* 任务实体,必须有阻塞的情况出现 */
            }
    }

代码清单18-1: (1):硬件初始化。硬件初始化这一步还属于裸机的范畴,我们可以把需要使用到的硬件都初始化好而且测试好,确保无误。

代码清单18-1: (2):RTOS系统初始化。比如RTOS里面的全局变量的初始化,空闲任务的创建等。不同的RTOS,它们的初始化有细微的差别。

代码清单18-1: (3):创建各种任务。这里把所有要用到的任务都创建好,但还不会进入调度,因为这个时候RTOS的调度器还没有开启。

代码清单18-1: (4):启动RTOS调度器,开始任务调度。这个时候调度器就从刚刚创建好的任务中选择一个优先级最高的任务开始运行。

代码清单18-1:(5)(6):任务实体通常是一个不带返回值的无限循环的C函数,函数体必须有阻塞的情况出现,不然任务(如果优先权恰好是最高)会一直在while循环里面执行,导致其它任务没有执行的机会。

4.2. 小心翼翼,十分谨慎

第二种我称之为小心翼翼,十分谨慎法。这种方法是在main函数中将硬件和RTOS系统先初始化好,然后创建一个启动任务后就启动调度器,然后在启动任务里面创建各种应用任务,当所有任务都创建成功后,启动任务把自己删除,具体的伪代码实现见 代码清单18-2

代码清单‑2小心翼翼,十分谨慎法伪代码实现
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
    int main (void)
    {
    /* 硬件初始化 */
            HardWare_Init();(1)

    /* RTOS 系统初始化 */
            RTOS_Init();(2)

    /* 创建一个任务 */
            RTOS_TaskCreate(AppTaskCreate);(3)

    /* 启动RTOS,开始调度 */
            RTOS_Start();(4)
    }

    /* 起始任务,在里面创建任务 */
    voidAppTaskCreate( void *arg )(5)
    {
    /* 创建任务1,然后执行 */
            RTOS_TaskCreate(Task1);(6)
            /* 当任务1阻塞时,继续创建任务2,然后执行 */
            RTOS_TaskCreate(Task2);

    /* ......继续创建各种任务 */

    /* 当任务创建完成,删除起始任务 */
            RTOS_TaskDelete(AppTaskCreate);(7)
    }

    void Task1( void *arg )(8)
    {
    while (1)
            {
    /* 任务实体,必须有阻塞的情况出现 */
            }
    }

    void Task2( void *arg )(9)
    {
    while (1)
            {
    /* 任务实体,必须有阻塞的情况出现 */
            }
    }

代码清单18-2: (1):硬件初始化。来到硬件初始化这一步还属于裸机的范畴,我们可以把需要使用到的硬件都初始化好而且测试好,确保无误。

代码清单18-2: (2):RTOS系统初始化。比如RTOS里面的全局变量的初始化,空闲任务的创建等。不同的RTOS,它们的初始化有细微的差别。

代码清单18-2: (3):创建一个开始任务。然后在这个初始任务里面创建各种应用任务。

代码清单18-2: (4):启动RTOS调度器,开始任务调度。这个时候调度器就去执行刚刚创建好的初始任务。

代码清单18-2: (5):我们通常说任务是一个不带返回值的无限循环的C函数,但是因为初始任务的特殊性,它不能是无限循环的,只执行一次后就关闭。在初始任务里面我们创建我们需要的各种任务。

代码清单18-2: (6):创建任务。每创建一个任务后它都将进入就绪态,系统会进行一次调度,如果新创建的任务的优先级比初始任务的优先级高的话,那将去执行新创建的任务,当新的任务阻塞时再回到初始任务被打断的地方继续执行。反之,则继续往下创建新的任务,直到所有任务创建完成。

代码清单18-2: (7):各种应用任务创建完成后,初始任务自己关闭自己,使命完成。

代码清单18-2: (8)(9):任务实体通常是一个不带返回值的无限循环的C函数,函数体必须有阻塞的情况出现,不然任务(如果优先权恰好是最高)会一直在while循环里面执行,其它任务没有执行的机会。

4.3. 孰优孰劣

那有关这两种方法孰优孰劣?我暂时没发现,我个人还是比较喜欢使用第二种。COS和LiteOS第一种和第二种都可以使用,由用户选择,RT-Thread和FreeRTOS则默认使用第二种。接下来我们详细讲解下uCOS的启动流程。

4.4. 系统的启动

我们知道,在系统上电的时候第一个执行的是启动文件里面由汇编编写的复位函数Reset_Handler,具体见 代码清单18-3。复位函数的最后会调用C库函数__main, 具体见 代码清单18-3 。__main函数的主要工作是初始化系统的堆和栈,最后调用C中的main函数,从而去到C的世界。

代码清单‑3Reset_Handler函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
    Reset_Handler   PROC
    EXPORT  Reset_Handler             [WEAK]
    IMPORT  SystemInit
    IMPORT  __main

    CPSID   I               ;
    Mask interrupts
    LDR     R0, =0xE000ED08
                            LDR     R1, =__Vectors
                                                    STR     R1, [R0]
                                                    LDR     R2, [R1]
                                                    MSR     MSP, R2
                                                    LDR     R0, =SystemInit
                                                    BLX     R0
                                                    CPSIE   i
    Unmask interrupts
    LDR     R0, =__main
                            BX      R0
                            ENDP

4.4.1. 系统初始化

在调用创建任务函数之前,我们必须要对系统进行一次初始化,而系统的初始化是根据我们配置宏定义进行初始化的,有一些则是系统必要的初始化,如空闲任务,时钟节拍任务等, 下面我们来看看系统初始化的源码,具体见 代码清单18-4

代码清单‑4系统初始化(已删减)
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
    void  OSInit (OS_ERR  *p_err)
    {
    #if (OS_CFG_ISR_STK_SIZE > 0u)
            CPU_STK      *p_stk;
            CPU_STK_SIZE  size;
    #endif
    #ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0) {
                    OS_SAFETY_CRITICAL_EXCEPTION();
    return;
            }
    #endif
            OSInitHook();/*初始化钩子函数相关的代码*/
            OSIntNestingCtr       =           0u;/*清除中断嵌套计数器*/
            OSRunning             =  OS_STATE_OS_STOPPED; /*未启动多任务处理*/
            OSSchedLockNestingCtr =           0u;/* 清除锁定计数器*/
            OSTCBCurPtr           = (OS_TCB *)0;/* 将OS_TCB指针初始化为已知状态  */
            OSTCBHighRdyPtr       = (OS_TCB *)0;
            OSPrioCur             =           0u;/*将优先级变量初始化为已知状态*/
            OSPrioHighRdy         =           0u;
    #if (OS_CFG_SCHED_LOCK_TIME_MEAS_EN == DEF_ENABLED)
OSSchedLockTimeBegin  =           0u;
OSSchedLockTimeMax    =           0u;
OSSchedLockTimeMaxCur =           0u;
    #endif
    #ifdef OS_SAFETY_CRITICAL_IEC61508
            OSSafetyCriticalStartFlag = DEF_FALSE;
    #endif
    #if (OS_CFG_SCHED_ROUND_ROBIN_EN == DEF_ENABLED)
            OSSchedRoundRobinEn             = DEF_FALSE;
            OSSchedRoundRobinDfltTimeQuanta = OSCfg_TickRate_Hz / 10u;
    #endif
    #if (OS_CFG_ISR_STK_SIZE > 0u)
            p_stk = OSCfg_ISRStkBasePtr;/* 清除异常堆栈以进行堆栈检查 */
    if (p_stk != (CPU_STK *)0) {
                    size  = OSCfg_ISRStkSize;
    while (size > 0u) {
                            size--;
                            *p_stk = 0u;
                            p_stk++;
                    }
            }
    #if (OS_CFG_TASK_STK_REDZONE_EN == DEF_ENABLED)/* 初始化Redzoned ISR堆栈

            OS_TaskStkRedzoneInit(OSCfg_ISRStkBasePtr, OSCfg_ISRStkSize);
    #endif
    #endif
    #if (OS_CFG_APP_HOOKS_EN == DEF_ENABLED)/* 清除应用程序钩子指针*/
    #if (OS_CFG_TASK_STK_REDZONE_EN == DEF_ENABLED)
            OS_AppRedzoneHitHookPtr = (OS_APP_HOOK_TCB )0;
    #endif
            OS_AppTaskCreateHookPtr = (OS_APP_HOOK_TCB )0;
            OS_AppTaskDelHookPtr    = (OS_APP_HOOK_TCB )0;
            OS_AppTaskReturnHookPtr = (OS_APP_HOOK_TCB )0;

            OS_AppIdleTaskHookPtr   = (OS_APP_HOOK_VOID)0;
            OS_AppStatTaskHookPtr   = (OS_APP_HOOK_VOID)0;
            OS_AppTaskSwHookPtr     = (OS_APP_HOOK_VOID)0;
            OS_AppTimeTickHookPtr   = (OS_APP_HOOK_VOID)0;
    #endif
    #if (OS_CFG_TASK_REG_TBL_SIZE > 0u)
            OSTaskRegNextAvailID = 0u;
    #endif
            OS_PrioInit();  /* 初始化优先级位图表 */
            OS_RdyListInit();/*初始化就绪列表*/
    #if (OS_CFG_FLAG_EN == DEF_ENABLED)/* 初始化事件标志模块*/
    #if (OS_CFG_DBG_EN == DEF_ENABLED)
            OSFlagDbgListPtr = (OS_FLAG_GRP *)0;
            OSFlagQty        =                0u;
    #endif
    #endif
    #if (OS_CFG_MEM_EN == DEF_ENABLED)/* 初始化内存管理器模块*/
            OS_MemInit(p_err);
    if (*p_err != OS_ERR_NONE) {
    return;
            }
    #endif
    #if (OS_MSG_EN == DEF_ENABLED)/* 初始化OS_MSG的空闲列表*/
            OS_MsgPoolInit(p_err);
    if (*p_err != OS_ERR_NONE) {
    return;
            }
    #endif
    #if (OS_CFG_MUTEX_EN == DEF_ENABLED)/* 初始化Mutex Manager模块 */\
    #if (OS_CFG_DBG_EN == DEF_ENABLED)
OSMutexDbgListPtr = (OS_MUTEX *)0;
OSMutexQty        =             0u;
    #endif
    #endif
    #if (OS_CFG_Q_EN == DEF_ENABLED)/* 初始化Message Queue Manager模块
    #if (OS_CFG_DBG_EN == DEF_ENABLED)
            OSQDbgListPtr = (OS_Q *)0;
            OSQQty        =         0u;
    #endif
    #endif
    #if (OS_CFG_SEM_EN == DEF_ENABLED)/*初始化信号量管理器模块*/
    #if (OS_CFG_DBG_EN == DEF_ENABLED)
            OSSemDbgListPtr = (OS_SEM *)0;
            OSSemQty        =           0u;
    #endif
    #endif
    #if defined(OS_CFG_TLS_TBL_SIZE) && (OS_CFG_TLS_TBL_SIZE > 0u)
            OS_TLS_Init(p_err);/*在创建任务之前初始化任务本地存储*/
    if (*p_err != OS_ERR_NONE) {
    return;
            }
    #endif
            OS_TaskInit(p_err); /*初始化任务管理器 */
    if (*p_err != OS_ERR_NONE) {
    return;
            }
    #if (OS_CFG_TASK_IDLE_EN == DEF_ENABLED)
            OS_IdleTaskInit(p_err); /* 初始化空闲任务  */
    if (*p_err != OS_ERR_NONE) {
    return;
            }
    #endif
    #if (OS_CFG_TASK_TICK_EN == DEF_ENABLED)
            OS_TickTaskInit(p_err); /* 初始化时钟节拍任务 */
    if (*p_err != OS_ERR_NONE) {
    return;
            }
    #endif
    #if (OS_CFG_STAT_TASK_EN == DEF_ENABLED)/* 初始化统计任务*/
            OS_StatTaskInit(p_err);
    if (*p_err != OS_ERR_NONE) {
    return;
            }
    #endif
    #if (OS_CFG_TMR_EN == DEF_ENABLED)/* 初始化Timer Manager模块*/
            OS_TmrInit(p_err);
    if (*p_err != OS_ERR_NONE) {
    return;
            }
    #endif
    #if (OS_CFG_DBG_EN == DEF_ENABLED)
            OS_Dbg_Init();
    #endif
            OSCfg_Init();
            OSInitialized = DEF_TRUE;/* 内核已初始化*/
    }

在这个系统初始化中,我们主要看两个地方,一个是空闲任务的初始化,一个是时钟节拍任务的初始化,这两个任务是必须存在的任务,否则系统无法正常运行。

4.4.1.1. 空闲任务

代码清单18-4: (1):其实初始化就是创建一个空闲任务,空闲任务的相关信息由系统默认指定,用户不能修改,OS_IdleTaskInit()源码具体见 代码清单18-5

代码清单‑5 OS_IdleTaskInit()源码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    void  OS_IdleTaskInit (OS_ERR  *p_err)
    {
    #if (OS_CFG_DBG_EN == DEF_ENABLED)
            OSIdleTaskCtr = 0u;
    #endif
    /* --------------- CREATE THE IDLE TASK --------------- */
            OSTaskCreate(&OSIdleTaskTCB,
    #if  (OS_CFG_DBG_EN == DEF_DISABLED)
                                    (CPU_CHAR   *)0,(1)
    #else
                                    (CPU_CHAR   *)"uC/OS-III Idle Task",
    #endif
                                    OS_IdleTask,
                                    (void       *)0,
                                    (OS_PRIO     )(OS_CFG_PRIO_MAX - 1u),
                                    OSCfg_IdleTaskStkBasePtr,
                                    OSCfg_IdleTaskStkLimit,
                                    OSCfg_IdleTaskStkSize,
                                    0u,
                                    0u,
                                    (void       *)0,
                                    (OS_OPT_TASK_STK_CHK | (OS_OPT)(OS_OPT_TASK_STK_CLR |
    OS_OPT_TASK_NO_TLS)),
                                    p_err);(2)
    }

代码清单18-5: (1):OSIdleTaskCtr在os.h头文件中定义,是一个32位无符号整型变量,该变量的作用是用于统计空闲任务的运行的,怎么统计呢,我们在空闲任务中讲解。现在初始化空闲任务,系统就将OSIdleTaskCtr清零。

代码清单18-5: (2):我们可以很容易看到系统只是调用了OSTaskCreate()函数来创建一个任务,这个任务就是空闲任务,任务优先级为OS_CFG_PRIO_MAX-1,OS_CFG_PRIO_MAX是一个宏,该宏定义表示uCOS的任务优先级数值的最大值,我们知道,在uCOS系统 中,任务的优先级数值越大,表示任务的优先级越低,所以空闲任务的优先级是最低的。空闲任务堆栈大小为OSCfg_IdleTaskStkSize,它也是一个宏,在os_cfg_app.c文件中定义,默认为128,则空闲任务堆栈默认为128*4=512字节。

空闲任务其实就是一个函数,其函数入口是OS_IdleTask,源码具体见 代码清单18-6

代码清单‑6 OS_IdleTask()源码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    void  OS_IdleTask (void  *p_arg)
    {
    #if ((OS_CFG_DBG_EN == DEF_ENABLED) || (OS_CFG_STAT_TASK_EN == DEF_ENABLED))
            CPU_SR_ALLOC();
    #endif
    /* Prevent compiler warning for not using 'p_arg'       */
            (void)p_arg;
    for (;;) {
    #if ((OS_CFG_DBG_EN == DEF_ENABLED) || (OS_CFG_STAT_TASK_EN == DEF_ENABLED))
                            CPU_CRITICAL_ENTER();
    #if (OS_CFG_DBG_EN == DEF_ENABLED)
                    OSIdleTaskCtr++;
    #endif
    #if (OS_CFG_STAT_TASK_EN == DEF_ENABLED)
                    OSStatTaskCtr++;
    #endif
                    CPU_CRITICAL_EXIT();
    #endif
    #if (OS_CFG_APP_HOOKS_EN == DEF_ENABLED)
    /* Call user definable HOOK*/
                    OSIdleTaskHook();
    #endif
            }
    }

空闲任务的作用还是很大的,它是一个无限的死循环,因为其优先级是最低的,所以任何优先级比它高的任务都能抢占它从而取得CPU的使用权,为什么系统要空闲任务呢?因为CPU是不会停下来的,即使啥也不干,CPU也不会停下来,此时系统就必须保证有一个随时处于就绪态的任务,而且这个任务不会抢占其他任务,当且仅当系 统的其他任务处于阻塞中,系统才会运行空闲任务,这个任务可以做很多事情,任务统计,钩入用户自定义的钩子函数实现用户自定义的功能等,但是需要注意的是,在钩子函数中用户不允许调用任何可以使空闲任务阻塞的函数接口,空闲任务是不允许被阻塞的。

代码清单18-4: (2):同样的,OS_TickTaskInit()函数也是创建一个时钟节拍任务,具体见 代码清单18-7

代码清单‑7 OS_TickTaskInit()源码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
    void  OS_TickTaskInit (OS_ERR  *p_err)
    {
    /* Clear the tick counter*/
            OSTickCtr                    = 0u;

    #if (OS_CFG_DYN_TICK_EN == DEF_ENABLED)
            OSTickCtrStep                = (OS_TICK)-1;
            OSTickCtrPend                = 0u;
    #endif

            OSTickListDly.TCB_Ptr        = (OS_TCB *)0;
            OSTickListTimeout.TCB_Ptr    = (OS_TCB *)0;

    #if (OS_CFG_DBG_EN == DEF_ENABLED)
            OSTickListDly.NbrEntries     = 0u;
            OSTickListDly.NbrUpdated     = 0u;

            OSTickListTimeout.NbrEntries = 0u;
            OSTickListTimeout.NbrUpdated = 0u;
    #endif

    /* --------------- CREATE THE TICK TASK --------------- */
    if (OSCfg_TickTaskStkBasePtr == (CPU_STK *)0) {
                    *p_err = OS_ERR_TICK_STK_INVALID;
    return;
            }

    if (OSCfg_TickTaskStkSize < OSCfg_StkSizeMin) {
                    *p_err = OS_ERR_TICK_STK_SIZE_INVALID;
                    return;
}
    /* Only one task at the 'Idle Task' priority */
    if (OSCfg_TickTaskPrio >= (OS_CFG_PRIO_MAX - 1u)) {
                    *p_err = OS_ERR_TICK_PRIO_INVALID;
    return;
            }

            OSTaskCreate(&OSTickTaskTCB,
    #if  (OS_CFG_DBG_EN == DEF_DISABLED)
                                    (CPU_CHAR   *)0,
    #else
                                    (CPU_CHAR   *)"uC/OS-III Tick Task",
    #endif
                                    OS_TickTask,
                                    (void       *)0,
                                    OSCfg_TickTaskPrio,
                                    OSCfg_TickTaskStkBasePtr,
                                    OSCfg_TickTaskStkLimit,
                                    OSCfg_TickTaskStkSize,
                                    0u,
                                    0u,
                                    (void       *)0,
                                    (OS_OPT_TASK_STK_CHK | (OS_OPT)(OS_OPT_TASK_STK_CLR |
    OS_OPT_TASK_NO_TLS)),
                                    p_err);
    }

当然啦,系统的初始化远远不止这两个任务,系统的其他资源也是会进行初始化的,我们在这里就暂时不讲解,有兴趣的图像可以自行查看系统初始化的源码。

4.4.2. CPU初始化

在main()函数中,我们除了需要对板级硬件进行初始化,还需要进行一些系统相关的初始化,如CPU的初始化,在uCOS 中,有一个很重要的功能就是时间戳, 它的精度高达ns级别,是CPU内核的一个资源,所以使用的时候要对CPU进行相关的初始化,具体见 代码清单18-8

代码清单‑8CPU初始化源码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
    void  CPU_Init (void)
    {
    /* --------------------- INIT TS ---------------------- */
    #if ((CPU_CFG_TS_EN     == DEF_ENABLED) || \
            (CPU_CFG_TS_TMR_EN == DEF_ENABLED))
            CPU_TS_Init();/* 时间戳测量的初始化   */
    #endif
    /* -------------- INIT INT DIS TIME MEAS -------------- */
    #ifdef  CPU_CFG_INT_DIS_MEAS_EN
            CPU_IntDisMeasInit(); /* 最大关中断时间测量初始化     */
    #endif
    /* ------------------ INIT CPU NAME ------------------- */
    #if (CPU_CFG_NAME_EN == DEF_ENABLED)
            CPU_NameInit();//CPU 名字初始化
    #endif

    #if (CPU_CFG_CACHE_MGMT_EN == DEF_ENABLED)
            CPU_Cache_Init();
    #endif
    }

我们重点来介绍一下uCOS的时间戳。

在Cortex-M(注意:M0内核不可用)内核中有一个外设叫DWT(Data Watchpoint and Trace),是用于系统调试及跟踪,它有一个32位的寄存器叫CYCCNT,它是一个向上的计数器,记录的是内核时钟运行的个数,内核时钟跳动一次,该计数器就加1,当CYCCNT溢出之后,会清0重新 开始向上计数。CYCCNT的精度非常高,其精度取决于内核的频率是多少,如果是STM32F1系列,内核时钟是72M,那精度就是1/72M = 14ns,而程序的运行时间都是微秒级别的,所以14ns的精度是远远够的。最长能记录的时间为:60s=2的32次方/72000000(假设内核频率为72M,内核跳 一次的时间大概为1/72M=14ns),而如果是STM32H7系列这种400M主频的芯片,那它的计时精度高达2.5ns(1/400000000 = 2.5),而如果是 i.MX RT1052这种比较厉害的处理器,最长能记录的时间为: 8.13s=2的32次方/528000000 (假设内核频率为528M,内核跳一次的时间大概为1/528M=1.9ns) 。

想要使能DWT外设,需要由另外的内核调试寄存器DEMCR的位24控制,写1使能,DEMCR的地址是0xE000 EDFC。

图片没有找到

使能DWT_CYCCNT寄存器之前,先清0。让我们看看DWT_CYCCNT的基地址,从ARM-Cortex-M手册中可以看到其基地址是0xE000 1004,复位默认值是0,而且它的类型是可读可写的,我们往0xE000 1004这个地址写0就将DWT_CYCCNT清0了。

图片没有找到

关于CYCCNTENA,它是DWT控制寄存器的第一位,写1使能,则启用CYCCNT计数器,否则CYCCNT计数器将不会工作,它的地址是0xE000EDFC。

图片没有找到

所以想要使用DWT的CYCCNT步骤:

  1. 先使能DWT外设,这个由另外内核调试寄存器DEMCR的位24控制,写1使能

  2. 在使能CYCCNT寄存器之前,先清0。

  3. 使能CYCCNT寄存器,这个由DWT的CYCCNTENA 控制,也就是DWT控制寄存器的位0控制,写1使能

这样子,我们就能去看看uCOS的时间戳的初始化了,具体见 代码清单18-9

代码清单‑9 CPU_TS_TmrInit()源码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    #define  CPU_BSP_REG_DEMCR             (*(CPU_REG32 *)(0xE000EDFCu))
    #define  CPU_BSP_REG_DWT_CR            (*(CPU_REG32 *)(0xE0001000u))
    #define  CPU_BSP_REG_DWT_CYCCNT        (*(CPU_REG32 *)(0xE0001004u))
    #define  CPU_BSP_REG_DWT_LAR           (*(CPU_REG32 *)(0xE0001FB0u))
    #define  CPU_BSP_REG_DWT_LSR           (*(CPU_REG32 *)(0xE0001FB4u))

    #define  CPU_BSP_BIT_DWT_LSR_SLK        DEF_BIT_01
    #define  CPU_BSP_BIT_DWT_LSR_SLI        DEF_BIT_00

    #define  CPU_BSP_DWT_LAR_KEY            0xC5ACCE55u

    #if (CPU_CFG_TS_TMR_EN == DEF_ENABLED)
    void  CPU_TS_TmrInit (void)
    {
            CPU_INT32U  fclk_freq;
            CPU_INT32U  reg_val;

    /* ---- DWT WRITE ACCESS UNLOCK (CORTEX-M7 ONLY!!) ---- */
            reg_val = CPU_BSP_REG_DWT_LSR;
    if ((reg_val & CPU_BSP_BIT_DWT_LSR_SLI) != 0) {
    if ((reg_val & CPU_BSP_BIT_DWT_LSR_SLK) != 0) {
                            CPU_BSP_REG_DWT_LAR = CPU_BSP_DWT_LAR_KEY;
                    }
            }

            fclk_freq = BSP_ClkFreqGet(kCLOCK_CpuClk);

            CPU_BSP_REG_DEMCR  |= DEF_BIT_24;  /* 设置 DEM_CR_TRCENA      */
            CPU_BSP_REG_DWT_CR |= DEF_BIT_00;  /* 设置 DWT_CR_CYCCNTENA   */

            CPU_TS_TmrFreqSet((CPU_TS_TMR_FREQ)fclk_freq);
    }
    #endif

时钟节拍的频率表示操作系统每1秒钟产生多少个tick,tick即是操作系统节拍的时钟周期,时钟节拍就是系统以固定的频率产生中断(时基中断),并在中断中处理与时间相关的事件,推动所有任务向前运行。时钟节拍需要依赖于硬件定时器,在i.MX RT裸机程序中经常使用的SysTick 时钟是MCU的内核定时器,通常都使用该定时器产生操作系统的时钟节拍。用户需要先在“ os_cfg_app.h”中设定时钟节拍的频率,该频率越高,操作系统检测事件就越频繁,可以增强任务的实时性,但太频繁也会增加操作系统内核的负担加重,所以用户需要权衡该频率的设置。我们在这里采用默认的 1000 Hz(本书之后若无特别声明,均采用 1000 Hz),也就是时钟节拍的周期为 1 ms。

函数OS_CPU_SysTickInit()用于初始化时钟节拍中断,初始化中断的优先级,SysTick中断的使能等等,这个函数要跟不同的CPU 进行编写,并且在系统任务的第一个任务开始的时候进行调用,如果在此之前进行调用,可能会造成系统奔溃,因为系统还没有初始化好就进入中断,可能在进入和退出中断的时候会调用系统未初始化好的一些模块,具体见代码 清单18-10_

代码清单‑10SysTick初始化
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    void  BSP_OS_TickInit (void)
    {
            CPU_INT32U  cpu_freq;
            CPU_SR_ALLOC();
    /* 确定SysTick参考频率。*/
            cpu_freq = BSP_ClkFreqGet(kCLOCK_CpuClk);
            CPU_CRITICAL_ENTER();
    /*初始化uC / OS周期时间src(SysTick)*/
            OS_CPU_SysTickInitFreq(cpu_freq);
            BSP_OS_TickDisable();
            CPU_CRITICAL_EXIT();
    }

4.4.3. 内存初始化

我们都知道,内存在嵌入式中是很珍贵的存在,而一个系统它是软件,则必须要有一块内存属于系统所管理的,所以在系统创建任务之前,就必须将系统必要的东西进行初始化,uCOS采用一块连续的大数组作为系统管理的内存,CPU_INT08U Mem_Heap[LIB_MEM_CFG_HEAP_SIZE],在使用之前就需要先将管理的内存进行初始化,具体见 代码清单18-11

代码清单‑11内存初始化
1
    Mem_Init(); /\* Initialize Memory Management Module \*/

4.4.4. OSStart()

在创建完任务的时候,我们需要开启调度器,因为创建仅仅是把任务添加到系统中,还没真正调度,那怎么才能让系统支持运行呢,uCOS为我们提供一个系统启动的函数接口——OSStart(),我们使用OSStart()函数就能让系统开始运行,具体见代码 清单18-12_

代码清单‑12vTaskStartScheduler()函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
    void  OSStart (OS_ERR  *p_err)
    {
            OS_OBJ_QTY  kernel_task_cnt;
    #ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0) {
                    OS_SAFETY_CRITICAL_EXCEPTION();
    return;
            }
    #endif
    if (OSInitialized != DEF_TRUE) {
                    *p_err = OS_ERR_OS_NOT_INIT;
    return;
}
kernel_task_cnt = 0u; /* Calculate the number of kernel tasks */
    #if (OS_CFG_STAT_TASK_EN == DEF_ENABLED)
            kernel_task_cnt++;
    #endif
    #if (OS_CFG_TASK_TICK_EN  == DEF_ENABLED)
            kernel_task_cnt++;
    #endif
    #if (OS_CFG_TMR_EN == DEF_ENABLED)
            kernel_task_cnt++;
    #endif
    #if (OS_CFG_TASK_IDLE_EN == DEF_ENABLED)
            kernel_task_cnt++;
    #endif
    if (OSTaskQty <= kernel_task_cnt) {/* No application task created*/
                    *p_err = OS_ERR_OS_NO_APP_TASK;
    return;
            }
    if (OSRunning == OS_STATE_OS_STOPPED) {
                    OSPrioHighRdy   = OS_PrioGetHighest(); /* Find the highest priority */
                    OSPrioCur       = OSPrioHighRdy;
                    OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
                    OSTCBCurPtr     = OSTCBHighRdyPtr;
                    OSRunning       = OS_STATE_OS_RUNNING;
                    OSStartHighRdy();                      /* Execute target specific code to
    rt task */
                    *p_err           = OS_ERR_FATAL_RETURN; /* OSStart() is not supposed to
    urn */
            } else {
                    *p_err           = OS_ERR_OS_RUNNING;   /* OS is already running */
            }
    }

关于任务切换的详细过程在第一部分已经讲解完毕,此处就不再重复赘述。

4.4.5. main.c

当我们拿到一个移植好uCOS的例程的时候,不出意外,你首先看到的是main函数,当你认真一看main函数里面只是让系统初始化和硬件初始化,然后创建并启动一些任务,具体见 代码清单18-13。因为这样子高度封装的函数让我们使用起来非常方便,防止用户一不小心忘了初始化系统的某些必要资源,造成系统启动失败, 而作为用户,如果只是单纯使用uCOS的话,无需太过于关注uCOS 接口函数里面的实现过程,但是我们还是建议需要深入了解uCOS然后再去使用,避免出现问题。

代码清单‑13 main函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
    int  main (void)
    {
            OS_ERR  err;


            BSP_ClkInit();                       /* 初始化系统时钟       */
            BSP_IntInit();                       /* 初始化RAM中断向量表. */
            BSP_OS_TickInit();                   /* 初始化内核计时器     */

            Mem_Init();                          /* 初始化内存管理模块   */
            CPU_IntDis();                        /* 禁用所有中断         */
            CPU_Init();                          /* 初始化uC/CPU相关     */

            OSInit(&err);                        /*初始化uC / OS-III     */
    if (err != OS_ERR_NONE) {
    while (1);
            }(1)

            App_OS_SetAllHooks(); //设置所有应用程序钩子函数

    /* 创建起始任务 */
            OSTaskCreate((OS_TCB     *)&AppTaskStartTCB,//任务控制块地址
                                    (CPU_CHAR   *)"App Task Start",//任务名称
                                    (OS_TASK_PTR ) AppTaskStart, //任务函数
                                    (void       *) 0, //传递给任务函数(形参p_arg)的实参
                                    (OS_PRIO     ) APP_TASK_START_PRIO, //任务的优先级
                                    (CPU_STK    *)&AppTaskStartStk[0], //任务堆栈的基地址
                                    (CPU_STK_SIZE) APP_TASK_START_STK_SIZE / 10,//任务堆栈空间剩下1/10时
    其增长
                                    (CPU_STK_SIZE) APP_TASK_START_STK_SIZE, //任务堆栈空间(单位:
    eof(CPU_STK))
                                    (OS_MSG_QTY  ) 5u, //任务可接收的最大消息数
                                    (OS_TICK     ) 0u, //任务的时间片节拍数(0表默认值OSCfg_TickRate_Hz/10)
                                    (void       *) 0,  //任务扩展(0表不扩展)
                                    (OS_OPT      )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), //任务选

                                    (OS_ERR     *)&err);
    if (err != OS_ERR_NONE) {
    while (1);
            }

            OSStart(&err);  //启动多任务管理(交由uC/OS-III控制)(2)

    while (DEF_ON) {
                    ;
            }
    }

代码清单18-13: (1):系统初始化完成,就创建一个AppTaskStart任务,在AppTaskStart任务中创建各种应用任务,具体见 代码清单18-14

代码清单‑14 AppTaskCreate函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
            static  void  AppTaskStart (void *p_arg)
    {
            OS_ERR  err;

            (void)p_arg;

            OS_TRACE_INIT();                               //初始化uC / OS-III跟踪记录器

            BSP_OS_TickEnable();                           //启用滴答计时器和中断
            BSP_Init();                                    //板级初始化

    #if OS_CFG_STAT_TASK_EN > 0u
            OSStatTaskCPUUsageInit(&err);                  //无需任务运行即可计算CPU容量
    #endif

    #ifdef CPU_CFG_INT_DIS_MEAS_EN
            CPU_IntDisMeasMaxCurReset();
    #endif

    /* 创建Led1任务 */
            OSTaskCreate((OS_TCB     *)&AppTaskLed1TCB,//任务控制块地址
                      (CPU_CHAR   *)"App Task Led1",//任务名称
                                    (OS_TASK_PTR ) AppTaskLed1,   //任务函数
                                    (void       *) 0,             //传递给任务函数(形参p_arg)的实参
                                    (OS_PRIO     ) APP_TASK_LED1_PRIO,//任务的优先级
                                    (CPU_STK    *)&AppTaskLed1Stk[0],//任务堆栈的基地址
                                    (CPU_STK_SIZE) APP_TASK_LED1_STK_SIZE / 10, //任务堆栈空间剩下1/10时
    制其增长
                                    (CPU_STK_SIZE) APP_TASK_LED1_STK_SIZE, //任务堆栈空间(单位:
    zeof(CPU_STK))
                                    (OS_MSG_QTY  ) 5u, //任务可接收的最大消息数
                                    (OS_TICK     ) 0u, //任务的时间片节拍数(0表默认值OSCfg_TickRate_Hz/10)
                                    (void       *) 0,  //任务扩展(0表不扩展)
                                    (OS_OPT      )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), //任务选

                                    (OS_ERR     *)&err);                                       //返回错
    类型


    /* 创建Led2任务 */
            OSTaskCreate((OS_TCB     *)&AppTaskLed2TCB,//任务控制块地址
                                    (CPU_CHAR   *)"App Task Led2",//任务名称
                                    (OS_TASK_PTR ) AppTaskLed2,   //任务函数
                                    (void       *) 0,             //传递给任务函数(形参p_arg)的实参
                                    (OS_PRIO     ) APP_TASK_LED2_PRIO,//任务的优先级
                                    (CPU_STK    *)&AppTaskLed2Stk[0], //任务堆栈的基地址
                                    (CPU_STK_SIZE) APP_TASK_LED2_STK_SIZE / 10, //任务堆栈空间剩下1/10时
    制其增长
                                    (CPU_STK_SIZE) APP_TASK_LED2_STK_SIZE, //任务堆栈空间(单位:
    zeof(CPU_STK))
                                    (OS_MSG_QTY  ) 5u,//任务可接收的最大消息数
                                    (OS_TICK     ) 0u,//任务的时间片节拍数(0表默认值OSCfg_TickRate_Hz/10)
                                    (void       *) 0, //任务扩展(0表不扩展)
                                    (OS_OPT      )(OS_OPT_TASK_STK_CHK |
    OS_OPT_TASK_STK_CLR), //任务选项
                                    (OS_ERR     *)&err);
    while (1) {
                    OSTimeDly ( 1000, OS_OPT_TIME_DLY, & err );  //延时1s
            }

    }

当在AppTaskStart中创建的应用任务的优先级比AppTaskStart任务的优先级高、低或者相等时候,程序是如何执行的?假如像我们代码一样在临界区创建任务,任务只能在退出临界区的时候才执行最高优先级任务。假如没使用临界区的话,就会分三种情况:

  1. 应用任务的优先级比初始任务的优先级高,那创建完后立马去执行刚刚创建的应用任务,当应用任务被阻塞时,继续回到初始任务被打断的地方继续往下执行,直到所有应用任务创建完成,最后初始任务把自己删除,完成自己的使命;

  2. 应用任务的优先级与初始任务的优先级一样,那创建完后根据任务的时间片来执行,直到所有应用任务创建完成,最后初始任务把自己删除,完成自己的使命;

  3. 应用任务的优先级比初始任务的优先级低,那创建完后任务不会被执行,如果还有应用任务紧接着创建应用任务,如果应用任务的优先级出现了比初始任务高或者相等的情况,请参考1和2的处理方式,直到所有应用任务创建完成,最后初始任务把自己删除,完成自己的使命。

代码清单18-13: (2):在启动任务调度器的时候,假如启动成功的话,任务就不会有返回了,假如启动没成功,则通过LR寄存器指定的地址退出,在创建AppTaskStart任务的时候,任务栈对应LR寄存器指向是任务退出函数OS_TaskReturn(),当系统启动没能成功的话,系统就不会运行。