写在前面:
由于时间的不足与学习的碎片化,写博客变得有些奢侈。
但是对于记录学习(忘了以后能快速复习)的渴望一天天变得强烈。
既然如此
不如以天为单位,以时间为顺序,仅仅将博客当做一个知识学习的目录,记录笔者认为最通俗、最有帮助的资料,并尽量总结几句话指明本质,以便于日后搜索起来更加容易。
标题的结构如下:“类型”:“知识点”——“简短的解释”
部分内容由于保密协议无法上传。
点击此处进入学习日记的总目录
2024.03.06
- 二十一、UCOSIII:时间戳
- 1、时间戳简介
- 2、时间戳的实现
- 3、时间戳代码讲解
- 1. CPU_Init()函数
- 2. CPU_TS_Init()函数
- 3. CPU_TS_TmrInit()函数
- 4. BSP_CPU_ClkFreq()函数
- 5. CPU_TS_TmrFreqSet()函数
- 6. CPU_TS_TmrRd()函数
- 7. OS_TS_GET()函数
- 二十二、UCOSIII:修改main()函数
- 1、修改代码
- 2、仿真
二十一、UCOSIII:时间戳
1、时间戳简介
本章实现时间戳用的是ARM Cortex-M系列内核中的DWT这个外设的功能, 有关这个外设的功能和寄存器说明具体见手册“STM32F10xxx Cortex-M3 programming manual”
在μC/OS-III中,很多地方的代码都加入了时间测量的功能,比如任务关中断的时间,关调度器的时间等。
知道了某段代码的运行时间,就明显地知道该代码的执行效率,如果时间过长就可以优化或者调整代码策略。
如果要测量一段代码A的时间,那么可以在代码段A运行前记录一个时间点TimeStart, 在代码段A运行完记录一个时间点TimeEnd,那么代码段A的运行时间TimeUse就等于TimeEnd减去TimeStart。
这里面的两个时间点TimeEnd和TimeStart,就叫作时间戳,时间戳实际上就是一个时间点。
2、时间戳的实现
通常执行一条代码是需要多个时钟周期的,即是ns级别。要想准确测量代码的运行时间,时间戳的精度就很重要。 通常单片机中的硬件定时器的精度都是us级别,远达不到测量几条代码运行时间的精度。
在ARM Cortex-M系列内核中,有一个DWT的外设,该外设有一个32位的寄存器叫CYCCNT,它是一个向上的计数器, 记录的是内核时钟HCLK运行的个数,当CYCCNT溢出之后,会清零重新开始向上计数。该计数器在μC/OS-III中正好被用来实现时间戳的功能。
在STM32F103系列的单片机中,HCLK时钟最高为72M,单个时钟的周期为1/72us = 0.0139us = 14ns, CYCCNT总共能记录的时间为232*14=60S。在μC/OS-III中,要测量的时间都是很短的,都是ms级别, 根本不需要考虑定时器溢出的问题。如果内核代码执行的时间超过s的级别,那就背离了实时操作系统实时的设计初衷了,没有意义。
3、时间戳代码讲解
1. CPU_Init()函数
CPU_Init()函数在cpu_core.c中实现
/* CPU初始化函数 */
void CPU_Init (void)
{
/* CPU初始化函数中总共做了三件事
1、初始化时间戳
2、初始化中断失能时间测量
3、初始化CPU名字
这里只讲时间戳功能,剩下两个的初始化代码则删除不讲 */
#if ((CPU_CFG_TS_EN == DEF_ENABLED) || \
(CPU_CFG_TS_TMR_EN == DEF_ENABLED))
CPU_TS_Init();
#endif
}
CPU_Init()函数在cpu_core.c中实现, 主要做三件事:
- 1、初始化时间戳,
- 2、初始化中断禁用时间测量,
- 3、初始化CPU名字。
目前第2和3个功能目前还没有使用到, 只实现了第1个初始化时间戳的代码
CPU_CFG_TS_EN和CPU_CFG_TS_TMR_EN这两个宏在cpu_core.h中定义, 用于控制时间戳相关的功能代码
CPU_CFG_TS_32_EN和CPU_CFG_TS_64_EN这两个宏在cpu_cfg.h文件中定义, 用于控制时间戳是32位还是64位的,默认启用32位
2. CPU_TS_Init()函数
CPU_TS_Init()是时间戳初始化函数,在cpu_core.c中实现
CPU_TS_TmrFreq_Hz是一个在cpu_core.h中定义的全局变量,表示CPU的系统时钟, 具体大小跟硬件相关,如果使用STM32F103系列,那就等于72000000HZ。
CPU_TS_TmrFreq_Hz变量的定义和时间戳相关的数据类型的定义具体见
/*
**********************************************************************************************************
* 宏定义
**********************************************************************************************************
*/
#if ((CPU_CFG_TS_32_EN == DEF_ENABLED) || \
(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
/*
*********************************************************************************************************
* 时间戳数据类型
*********************************************************************************************************
*/
typedef CPU_INT32U CPU_TS32;
typedef CPU_INT32U CPU_TS_TMR_FREQ;
typedef CPU_TS32 CPU_TS;
typedef CPU_INT32U CPU_TS_TMR;
/*
*********************************************************************************************************
* 全局变量
*********************************************************************************************************
*/
#if (CPU_CFG_TS_TMR_EN == DEF_ENABLED)
CPU_CORE_EXT CPU_TS_TMR_FREQ CPU_TS_TmrFreq_Hz;
#endif
3. CPU_TS_TmrInit()函数
时间戳定时器初始化函数CPU_TS_TmrInit()在cpu_core.c实现
/* 时间戳定时器初始化 */
#if (CPU_CFG_TS_TMR_EN == DEF_ENABLED)
void CPU_TS_TmrInit (void)
{
CPU_INT32U fclk_freq;
fclk_freq = BSP_CPU_ClkFreq();
/* 使能DWT外设 */
BSP_REG_DEM_CR |= (CPU_INT32U)BSP_BIT_DEM_CR_TRCENA;
/* 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);
}
#endif
初始化时间戳计数器CYCCNT,启用CYCCNT计数的操作步骤:
1、先启用DWT外设,这个由另外内核调试寄存器DEMCR的位24控制,写1启用。
2、启用CYCCNT寄存器之前,先清零。
3、启用CYCCNT寄存器,这个由DWT_CTRL(代码上宏定义为DWT_CR)的位0控制,写1启用。 这三个步骤里面涉及的寄存器定义在cpu_core.c文件的开头
/*
*********************************************************************************************************
* 寄存器定义
*********************************************************************************************************
*/
/*
在Cortex-M内核里面有一个外设叫DWT(Data Watchpoint and Trace),该外设有一个32位的寄存器叫CYCCNT,
它是一个向上的 计数器,记录的是内核时钟运行的个数,当CYCCNT溢出之后,会清0重新开始向上计数。
使能CYCCNT计数的操作步骤:
1、先使能DWT外设,这个由另外内核调试寄存器DEMCR的位24控制,写1使能
2、使能CYCCNT寄存器之前,先清0
3、使能CYCCNT寄存器,这个由DWT_CTRL(代码上宏定义为DWT_CR)的位0控制,写1使能
*/
#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)
4. BSP_CPU_ClkFreq()函数
BSP_CPU_ClkFreq()是一个用于获取CPU的HCLK时钟的BSP函数,具体跟硬件相关, 目前只是使用软件仿真,则把硬件相关的代码注释掉,直接手动设置CPU的HCLK的时钟等于软件仿真的时钟25000000HZ。
BSP_CPU_ClkFreq()在cpu_core.c实现
/* 获取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
}
5. CPU_TS_TmrFreqSet()函数
CPU_TS_TmrFreqSet()函数在cpu_core.c定义, 具体的作用是把函数BSP_CPU_ClkFreq()获取到的CPU的HCLK时钟赋值给全局变量CPU_TS_TmrFreq_Hz
/* 初始化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
6. CPU_TS_TmrRd()函数
CPU_TS_TmrRd()函数用于获取CYCNNT计数器的值,在cpu_core.c中定义
#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. OS_TS_GET()函数
OS_TS_GET()函数用于获取CYCNNT计数器的值,实际上是一个宏定义,将CPU底层的函数CPU_TS_TmrRd()重新取个名字封装, 供内核和用户函数使用,在os_cpu.h头文件定义
#include "cpu_core.h" //CPU_TS在cpu_core.h定义
/*
*********************************************************************************************************
* 时间戳配置
*********************************************************************************************************
*/
#if OS_CFG_TS_EN == 1u
#define OS_TS_GET() (CPU_TS)CPU_TS_TmrRd()
#else
#define OS_TS_GET() (CPU_TS)0u
#endif
启用时间戳,在os_cfg.h头文件中启用
二十二、UCOSIII:修改main()函数
1、修改代码
主函数与上一章区别不大,首先在main()函数开头加入CPU_Init()函数,然后在任务1中对延时函数的执行时间进行测量。
/*
************************************************************************************************************************
* 包含的头文件
************************************************************************************************************************
*/
#include "os.h"
#include "ARMCM3.h"
/*
************************************************************************************************************************
* 宏定义
************************************************************************************************************************
*/
/*
************************************************************************************************************************
* 全局变量
************************************************************************************************************************
*/
uint32_t TimeStart;/* 定义三个全局变量 */
uint32_t TimeEnd;
uint32_t TimeUse;
uint32_t flag1;
uint32_t flag2;
/*
************************************************************************************************************************
* TCB & STACK & 任务声明
************************************************************************************************************************
*/
#define TASK1_STK_SIZE 20
#define TASK2_STK_SIZE 20
static CPU_STK Task1Stk[TASK1_STK_SIZE];
static CPU_STK Task2Stk[TASK2_STK_SIZE];
static OS_TCB Task1TCB;
static OS_TCB Task2TCB;
void Task1( void *p_arg );
void Task2( void *p_arg );
/*
************************************************************************************************************************
* 函数声明
************************************************************************************************************************
*/
void delay(uint32_t count);
/*
************************************************************************************************************************
* main函数
************************************************************************************************************************
*/
/*
* 注意事项:1、该工程使用软件仿真,debug需选择 Ude Simulator
* 2、在Target选项卡里面把晶振Xtal(Mhz)的值改为25,默认是12,
* 改成25是为了跟system_ARMCM3.c中定义的__SYSTEM_CLOCK相同,确保仿真的时候时钟一致
*/
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);
}
/*
************************************************************************************************************************
* 函数实现
************************************************************************************************************************
*/
/* 软件延时
void delay (volatile uint32_t count)
{
for(; count!=0; count--);
}
*/
/* 任务1 */
void Task1( void *p_arg )
{
for ( ;; ) {
flag1 = 1;
//delay( 100 );
TimeStart = OS_TS_GET();
OSTimeDly(20);
TimeEnd = OS_TS_GET();
TimeUse = TimeEnd - TimeStart;
flag1 = 0;
//delay( 100 );
OSTimeDly(2);
/* 任务切换,这里是手动切换 */
//OSSched();
}
}
/* 任务2 */
void Task2( void *p_arg )
{
for ( ;; ) {
flag2 = 1;
//delay( 100 );
OSTimeDly(2);
//延时函数均替代为阻塞延时,延时时间均为2个SysTick中断周期,即20ms。
flag2 = 0;
//delay( 100 );
OSTimeDly(2);
/* 任务切换,这里是手动切换 */
//OSSched();
}
}
2、仿真
时间戳时间测量功能在软件仿真的时候使用不了,只能硬件仿真,这里仅能够讲解代码功能。