7. 时间戳

本章实现时间戳用的是ARM Cortex-M系列内核中的DWT这个外设的功能。

7.1. 时间戳简介

在uC/OS-III中,很多地方的代码都加入了时间测量的功能,比如任务关中断的时间,关调度器的时间等。知道了某段代码的运行时间,就明显地知道该代码的执行效率,如果时间过长就可以优化或者调整代码策略。如果要测量一段代码A的时间,那么可以在代码段A运行前记录一个时间点TimeStart,在代码段A运行完 记录一个时间点TimeEnd,那么代码段A的运行时间TimeUse就等于TimeEnd减去TimeStart。这里面的两个时间点TimeEnd和TimeStart,就叫做时间戳,时间戳实际上就是一个时间点。

7.2. 时间戳的实现

通常执行一条代码是需要多个时钟周期的,即是ns级别。要想准确测量代码的运行时间,时间戳的精度就很重要。通常单片机中的硬件定时器的精度都是us级别,远达不到测量几条代码运行时间的精度。

在ARM Cortex-M系列内核中,有一个DWT的外设,该外设有一个32位的寄存器叫CYCCNT,它是一个向上的计数器,记录的是内核时钟HCLK运行的个数,当CYCCNT溢出之后,会清0重新开始向上计数。该计数器在uC/OS-III中正好被用来实现时间戳的功能。

在i.MX RT系列的单片机中,HCLK时钟最高为528M,单个时钟的周期为1/528us = 0.00189us = 1.9ns,CYCCNT总共能记录的时间为232*1.9=8S。在uC/OS- III中,要测量的时间都是很短的,都是ms级别,根本不需要考虑计时器溢出的问题。如果内核代码执行的时间超过s的级别,那就背离了实时操作系统实时的设计初衷了,没有意义。

7.3. 时间戳代码讲解

7.3.1. CPU_Init()函数

CPU_Init()函数在cpu_core.c(cpu_core.c文件第一次使用需要自行在文件夹uC-CPU中新建并添加到工程的uC- CPU组)中实现,主要做三件事:1、初始化时间戳,2、初始化中断失能时间测量,3、初始化CPU名字。第2和3个功能目前还没有使用到,只实现了第1个初始化时间戳的代码,具体见 代码清单8-1

代码清单‑1 CPU_Init()函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
/* CPU初始化函数 */
void  CPU_Init (void)
{
/* CPU初始化函数中总共做了三件事
    1、初始化时间戳
    2、初始化中断失能时间测量
    3、初始化CPU名字
这里只讲时间戳功能,剩下两个的初始化代码则删除不讲 */

#if ((CPU_CFG_TS_EN     == DEF_ENABLED) || \(1)
    (CPU_CFG_TS_TMR_EN == DEF_ENABLED))
    CPU_TS_Init();(2)
#endif
}

代码清单8-1:(1):CPU_CFG_TS_EN和CPU_CFG_TS_TMR_EN这两个宏在cpu_core.h中定义,用于控制时间戳相关的功能代码,具体定义见 代码清单8-2

代码清单‑2CPU_CFG_TS_EN和CPU_CFG_TS_TMR_EN宏定义
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#if    ((CPU_CFG_TS_32_EN == DEF_ENABLED) || \(1)
    (CPU_CFG_TS_64_EN == DEF_ENABLED))
#define  CPU_CFG_TS_EN                          DEF_ENABLED
#else
#define  CPU_CFG_TS_EN                          DEF_DISABLED
#endif

#if    ((CPU_CFG_TS_EN == DEF_ENABLED) || \
(defined(CPU_CFG_INT_DIS_MEAS_EN)))
#define  CPU_CFG_TS_TMR_EN                      DEF_ENABLED
#else
#define  CPU_CFG_TS_TMR_EN                      DEF_DISABLED
#endif

代码清单8‑2(1):CPU_CFG_TS_32_EN和CPU_CFG_TS_64_EN这两个宏在cpu_cfg.h(cpu_cfg.h文件第一次使用需要自行在文件夹uC-CPU中新建并添加到工程的uC-CPU组)文件中定义,用于控制时间戳是32位还是64位的,默认使能32位,具体见代码清单8‑3。

代码清单‑3CPU_CFG_TS_32_EN和CPU_CFG_TS_64_EN宏定义
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#ifndef  CPU_CFG_MODULE_PRESENT
#define  CPU_CFG_MODULE_PRESENT


#define  CPU_CFG_TS_32_EN                       DEF_ENABLED
#define  CPU_CFG_TS_64_EN                       DEF_DISABLED

#define  CPU_CFG_TS_TMR_SIZE                    CPU_WORD_SIZE_32


#endif/* CPU_CFG_MODULE_PRESENT */

7.3.2. CPU_TS_Init()函数

代码清单8-2:(2):CPU_TS_Init()是时间戳初始化函数,在cpu_core.c中实现,具体见 代码清单8-4

代码清单‑4CPU_TS_Init()函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#if ((CPU_CFG_TS_EN     == DEF_ENABLED) || \
    (CPU_CFG_TS_TMR_EN == DEF_ENABLED))
static  void  CPU_TS_Init (void)
{

#if (CPU_CFG_TS_TMR_EN == DEF_ENABLED)

CPU_TS_TmrFreq_Hz   = 0u;(1)
CPU_TS_TmrInit();(2)
#endif

}
#endif

代码清单8-4:(1):CPU_TS_TmrFreq_Hz是一个在cpu_core.h中定义的全局变量,表示CPU的系统时钟,具体大小跟硬件相关,如果使用i.MX RT系列,那就等于528000000HZ。CPU_TS_TmrFreq_Hz变量的定义和时间戳相关的数据类型的定义具体见 代码清单8-5

代码清单‑5CPU_TS_TmrFreq_Hz和时间戳相关的数据类型定义
 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
/*
*************************************************************************
*                              EXTERNS
*                        在cpu_core.h开头定义
*************************************************************************
*/

#ifdef   CPU_CORE_MODULE/* CPU_CORE_MODULE 只在cpu_core.c文件的开头定义 */
#define  CPU_CORE_EXT
#else
#define  CPU_CORE_EXT  extern
#endif

/*
*******************************************************************
*                            时间戳数据类型
*                        在cpu_core.h文件定义
*******************************************************************
*/

typedef  CPU_INT32U  CPU_TS32;

typedef  CPU_INT32U  CPU_TS_TMR_FREQ;
typedef  CPU_TS32    CPU_TS;
typedef  CPU_INT32U  CPU_TS_TMR;


/*
*******************************************************************
*                           全局变量
*                    在cpu_core.h文件定义
*******************************************************************
*/

#if  (CPU_CFG_TS_TMR_EN   == DEF_ENABLED)
CPU_CORE_EXT  CPU_TS_TMR_FREQ  CPU_TS_TmrFreq_Hz;
#endif

7.3.3. CPU_TS_TmrInit()函数

代码清单8-4:(2):时间戳定时器初始化函数CPU_TS_TmrInit()在cpu_core.c实现,具体见 代码清单8-6

代码清单‑6CPU_TS_TmrInit()函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* 时间戳定时器初始化 */
#if (CPU_CFG_TS_TMR_EN == DEF_ENABLED)
void  CPU_TS_TmrInit (void)
{
    CPU_INT32U  fclk_freq;


    fclk_freq = BSP_CPU_ClkFreq();(2)

/* 使能DWT外设 */
    BSP_REG_DEM_CR     |= (CPU_INT32U)BSP_BIT_DEM_CR_TRCENA;(1)
/* DWT CYCCNT寄存器计数清0 */
    BSP_REG_DWT_CYCCNT  = (CPU_INT32U)0u;
/* 注意:当使用软件仿真全速运行的时候,会先停在这里,
就好像在这里设置了一个断点一样,需要手动运行才能跳过,
当使用硬件仿真的时候却不会 */
/* 使能Cortex-M3 DWT CYCCNT寄存器 */
    BSP_REG_DWT_CR     |= (CPU_INT32U)BSP_BIT_DWT_CR_CYCCNTENA;

    CPU_TS_TmrFreqSet((CPU_TS_TMR_FREQ)fclk_freq);(3)
}
#endif

代码清单8-6:(1):初始化时间戳计数器CYCCNT,使能CYCCNT计数的操作步骤:

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

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

3、使能CYCCNT寄存器,这个由DWT_CTRL(代码上宏定义为DWT_CR)的位0控制,写1使能。这三个步骤里面涉及到的寄存器定义在cpu_core.c文件的开头,具体见 代码清单8-7

代码清单‑7 DWT外设相关寄存器定义
 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
/*
*******************************************************************
*                           寄存器定义
*******************************************************************
*/
#define  BSP_REG_DEM_CR                  (*(CPU_REG32 *)0xE000EDFC)
#define  BSP_REG_DWT_CR                  (*(CPU_REG32 *)0xE0001000)
#define  BSP_REG_DWT_CYCCNT              (*(CPU_REG32 *)0xE0001004)
#define  BSP_REG_DBGMCU_CR               (*(CPU_REG32 *)0xE0042004)

/*
*******************************************************************
*                           寄存器位定义
*******************************************************************
*/

#define  BSP_DBGMCU_CR_TRACE_IOEN_MASK                   0x10
#define  BSP_DBGMCU_CR_TRACE_MODE_ASYNC                  0x00
#define  BSP_DBGMCU_CR_TRACE_MODE_SYNC_01                0x40
#define  BSP_DBGMCU_CR_TRACE_MODE_SYNC_02                0x80
#define  BSP_DBGMCU_CR_TRACE_MODE_SYNC_04                0xC0
#define  BSP_DBGMCU_CR_TRACE_MODE_MASK                   0xC0

#define  BSP_BIT_DEM_CR_TRCENA                          (1<<24)

#define  BSP_BIT_DWT_CR_CYCCNTENA                       (1<<0)

7.3.4. BSP_CPU_ClkFreq()函数

代码清单8-6:(2):BSP_CPU_ClkFreq()是一个用于获取CPU的HCLK时钟的BSP函数,具体跟硬件相关,目前只是使用软件仿真,则把硬件相关的代码注释掉,直接手动设置CPU的HCLK的时钟等于软件仿真的时钟25000000HZ。BSP_CPU_ClkFreq()在cpu_core.c实 现,具体定义见 代码清单8-8

代码清单‑8BSP_CPU_ClkFreq()函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/* 获取CPU的HCLK时钟
这个是跟硬件相关的,目前我们是软件仿真,我们暂时把跟硬件相关的代码屏蔽掉,
直接手动设置CPU的HCLK时钟*/
CPU_INT32U  BSP_CPU_ClkFreq (void)
{
#if 0
    RCC_ClocksTypeDef  rcc_clocks;


    RCC_GetClocksFreq(&rcc_clocks);
return ((CPU_INT32U)rcc_clocks.HCLK_Frequency);
#else
    CPU_INT32U    CPU_HCLK;


/* 目前软件仿真我们使用25M的系统时钟 */
    CPU_HCLK = 25000000;

return CPU_HCLK;
#endif
}

7.3.5. CPU_TS_TmrFreqSet()函数

代码清单8-6:_(3):CPU_TS_TmrFreqSet()函数在cpu_core.c定义,具体的作用是把函数BSP_CPU_ClkFreq()获取到的CPU的HCLK时钟赋值给全局变量CPU_TS_TmrFreq_Hz,具体实现见 代码清单8-9

代码清单‑9CPU_TS_TmrFreqSet()函数
1
2
3
4
5
6
7
/* 初始化CPU_TS_TmrFreq_Hz,这个就是系统的时钟,单位为HZ */
#if (CPU_CFG_TS_TMR_EN == DEF_ENABLED)
void  CPU_TS_TmrFreqSet (CPU_TS_TMR_FREQ  freq_hz)
{
    CPU_TS_TmrFreq_Hz = freq_hz;
}
#endif

7.3.6. CPU_TS_TmrRd()函数

CPU_TS_TmrRd()函数用于获取CYCNNT计数器的值,在cpu_core.c中定义,具体实现见 代码清单8-10

代码清单‑10CPU_TS_TmrRd()函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#if (CPU_CFG_TS_TMR_EN == DEF_ENABLED)
    CPU_TS_TMR  CPU_TS_TmrRd (void)
{
    CPU_TS_TMR  ts_tmr_cnts;


    ts_tmr_cnts = (CPU_TS_TMR)BSP_REG_DWT_CYCCNT;

return (ts_tmr_cnts);
}
#endif

7.3.7. OS_TS_GET()函数

OS_TS_GET()函数用于获取CYCNNT计数器的值,实际上是一个宏定义,将CPU底层的函数CPU_TS_TmrRd()重新取个名字封装,供内核和用户函数使用,在os_cpu.h头文件定义,具体实现见 代码清单8-11

代码清单‑11OS_TS_GET()函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/*
*******************************************************************
*                             时间戳配置
*******************************************************************
*/
/* 使能时间戳,在os_cfg.h头文件中使能 */
#define OS_CFG_TS_EN                    1u

#if      OS_CFG_TS_EN == 1u
#define  OS_TS_GET()               (CPU_TS)CPU_TS_TmrRd()
#else
#define  OS_TS_GET()               (CPU_TS)0u
#endif

7.4. main函数

主函数与上一章区别不大,首先在main函数开头加入CPU_Init()函数,然后在任务1中对延时函数的执行时间进行测量。新加入的代码做了高亮显示,具体见 代码清单8-12

代码清单‑12主函数
 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
uint32_t TimeStart;/* 定义三个全局变量 */
uint32_t TimeEnd;
uint32_t TimeUse;


/*
*******************************************************************
*                            main函数
*******************************************************************
*/

int main(void)
{
    OS_ERR err;


/* CPU初始化:1、初始化时间戳 */
    CPU_Init();

/* 关闭中断 */
    CPU_IntDis();

/* 配置SysTick 10ms 中断一次 */
    OS_CPU_SysTickInit (10);

/* 初始化相关的全局变量 */
    OSInit(&err);

/* 创建任务 */
    OSTaskCreate ((OS_TCB*)      &Task1TCB,
                (OS_TASK_PTR ) Task1,
                (void *)       0,
                (CPU_STK*)     &Task1Stk[0],
                (CPU_STK_SIZE) TASK1_STK_SIZE,
                (OS_ERR *)     &err);

    OSTaskCreate ((OS_TCB*)      &Task2TCB,
                (OS_TASK_PTR ) Task2,
                (void *)       0,
                (CPU_STK*)     &Task2Stk[0],
                (CPU_STK_SIZE) TASK2_STK_SIZE,
                (OS_ERR *)     &err);

/* 将任务加入到就绪列表 */
    OSRdyList[0].HeadPtr = &Task1TCB;
    OSRdyList[1].HeadPtr = &Task2TCB;

/* 启动OS,将不再返回 */
    OSStart(&err);
}

/* 任务1 */
void Task1( void *p_arg )
{
for ( ;; ) {
        flag1 = 1;

        TimeStart = OS_TS_GET();
        OSTimeDly(20);
        TimeEnd = OS_TS_GET();
        TimeUse = TimeEnd - TimeStart;

        flag1 = 0;
        OSTimeDly(2);
    }
}

7.5. 实验想象

时间戳时间测量功能在软件仿真的时候使用不了,只能硬件仿真,这里仅能够讲解代码功能。有关硬件仿真,本书有提供一个测量SysTick定时时间的例程,名称叫“7-SysTick—系统定时器i.MX RT时间戳【硬件仿真】”,在配套的程序源码里面可以找到。