0-1write MC/OS __Basics2

Thursday, July 22, 2021, 11:04:30

5, Time stamp

What is a timestamp?

A timestamp is actually a point in time.
In the operating system, the code adds the function of time measurement, such as task off interrupt time, scheduler off time, etc.
Knowing the running time of the code can know the execution efficiency of the code.

Record a time point start before the code runs, and record a time point end after the code runs,
The running time of this code is end start, and these two time points are time stamps.

How to implement timestamp?

Generally, the execution of a code requires multiple clock cycles, that is, ns level, and the accuracy of the hardware timer of the single chip microcomputer is us level, which is far from the accuracy of measuring the running time of several codes.
In the ARM Cortex-M series kernel, there is a DWT peripheral. The peripheral has a 32-bit register CYCCNT, which is an upward counter that records the number of kernel clock HCLK running. When CYCCNT overflows, it will be cleared to restart the upward counting. The counter is in μ C/OS-III is just used to realize the function of timestamp.

1. CPU initialization function_ Init()

/* CPU Initialization function */
void  CPU_Init (void)
{
/* CPU A total of three things are done in the initialization function
    1,Initialization timestamp
    2,Initialize interrupt disable time measurement
    3,Initialize CPU name
 Here we only talk about the timestamp function, */

#if ((CPU_CFG_TS_EN     == DEF_ENABLED) || \
    (CPU_CFG_TS_TMR_EN == DEF_ENABLED))    //Used to control whether the timestamp is 32-bit or 64 bit. 32 bit is enabled by default
    CPU_TS_Init();//The functions are as follows,,
#endif

}

2. Timestamp initialization function CPU_TS_Init()

#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;//The global variable represents the system clock of the CPU. The specific size is related to the hardware.
    CPU_TS_TmrInit();//The function is as follows
#endif

}
#endif

3. Timestamp timer initialization function CPU_TS_TmrInit()

/* Timestamp timer initialization */
#if (CPU_CFG_TS_TMR_EN == DEF_ENABLED)
void  CPU_TS_TmrInit (void)
{
    CPU_INT32U  fclk_freq;
    fclk_freq = BSP_CPU_ClkFreq();//The functions are as follows,,
//To initialize the time stamp counter CYCCNT and enable CYCCNT counting:
    /* 1,Enable DWT peripherals */
    BSP_REG_DEM_CR     |= (CPU_INT32U)BSP_BIT_DEM_CR_TRCENA;(1)
    /* 2,DWT CYCCNT Register count reset */
    BSP_REG_DWT_CYCCNT  = (CPU_INT32U)0u;
    /* Note: when using software simulation to run at full speed, it will stop here first,
    It's like setting a breakpoint here. You need to run it manually to skip it,
    Not when using hardware emulation */
    /* 3,Enable Cortex-M3 DWT CYCCNT register */
    BSP_REG_DWT_CR     |= (CPU_INT32U)BSP_BIT_DWT_CR_CYCCNTENA;

    CPU_TS_TmrFreqSet((CPU_TS_TMR_FREQ)fclk_freq);//BSP function_ CPU_ The HCLK clock of the CPU obtained by clkfreq() is assigned to the global variable CPU_TS_TmrFreq_Hz... Codes are as follows,,,
}
#endif

4. Function BSP for obtaining HCLK clock of CPU_ CPU_ ClkFreq()

/* Gets the HCLK clock of the CPU
 This is related to hardware. At present, we are software simulation. We shield the hardware related code temporarily,
Directly manually set the HCLK clock of the CPU*/
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;

    /* At present, we use 25M system clock for software simulation */
    CPU_HCLK = 25000000;

    return CPU_HCLK;
#endif
}

5,CPU_TS_TmrFreqSet() function

/* Initialize CPU_TS_TmrFreq_Hz, this is the system clock, in 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;//BSP function_ CPU_ The HCLK clock of the CPU obtained by clkfreq() is assigned to the global variable CPU_TS_TmrFreq_Hz
}
#endif

6. Get CYCNNT counter function CPU_TS_TmrRd()

#if (CPU_CFG_TS_TMR_EN == DEF_ENABLED)
CPU_TS_TMR  CPU_TS_TmrRd (void)//Gets the value of the CYCNNT counter
{
    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() function

//It is used to obtain the value of CYCNNT counter. In fact, it is a macro definition, which is the underlying function of CPU_TS_TmrRd() is renamed and encapsulated for kernel and user functions
#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

8. main() function

It is not different from the code of four. Add CPU at the beginning of the function_ Init() function, and then measure the execution time of the delay function in task 1.

6, Critical section

What is critical section?

A critical segment is a code segment that cannot be interrupted during execution.
The most common critical segment is the operation of global variables.
Under what circumstances will the critical section be interrupted?
System scheduling or external interrupts will interrupt the critical section. It is necessary to turn off interrupts or lock the scheduler to protect the critical section.
In the system scheduling of ucos, PendSV interrupt is finally generated. Task switching is realized in PendSV Handler, so it can still be attributed to interrupt.
Therefore, the protection of critical section finally returns to the control of interrupt on and off.

1. Cortex-M kernel fast shutdown interrupt instruction

In order to quickly switch interrupts, the Cortex-M kernel has specially set up a CPS instruction, which has four uses

CPSID I ;PRIMASK=1     ;Off interrupt
CPSIE I ;PRIMASK=0     ;Open interrupt
CPSID F ;FAULTMASK=1   ;Guan anomaly
CPSIE F ;FAULTMASK=0   ;Abnormal opening
//PRIMASK and faultmaster are two of the three interrupt mask registers in the Cortex-M kernel, and the other is BASEPRI
//stay μ In C/OS, the interrupt is turned on and off by operating the PRIMASK register. The interrupt can be turned off immediately by using the CPSID I instruction. Very convenient.

2. Off interrupt

CPU function of underlying operation shutdown interrupt_ SR_ Save()

CPU_SR_Save
        MRSR0, PRIMASK              //(1) The value of the special register PRIMASK register is stored in the general register r0 through the MRS instruction. When calling the assembly subroutine in C, r0 is used as function CPU_. SR_ The return value of save().
        CPSID   I                   //(2) Off interrupt
        BX      LR                  //(3) Sub function return

Why is it necessary to execute the MRS command to save the value of PRIMASK before turning off the interrupt?

3. Open interrupt

The function of the bottom operation interrupt is CPU_SR_Restore()

CPU_SR_Restore
        MSR     PRIMASK, R0      //(1) The value of the general register r0 is stored in the special register PRIMASK through the MSR instruction. When the assembly subroutine is called back in C, the first parameter is passed to the general register r0.
                                      So in C Call in CPU_SR_Restore()You need to pass in a formal parameter, which is saved before entering the critical section PRIMASK Value of. 
        BX      LR               //(2) The subroutine returns.

Why is it necessary to execute the MRS instruction to store the value of the general register r0 into the special register PRIMASK before opening the interrupt?

4. Application of critical segment code

;//Implementation of switch interrupt function
;/*
; * void CPU_SR_Save();
; */
CPU_SR_Save
        MRS     R0, PRIMASK
        CPSID   I
        BX      LR

;/*
; * void CPU_SR_Restore(void);
; */
CPU_SR_Restore
        MSR     PRIMASK, R0
        BX      LR

PRIMASK = 0;        /* PRIMASK The initial value is 0, indicating that there is no off interrupt */              (1)

CPU_SR  cpu_sr1 = (CPU_SR)0
CPU_SR  cpu_sr2 = (CPU_SR)0                         (2)

/* Critical segment code */
{
    /* Start of critical section 1 */
    cpu_sr1 = CPU_SR_Save();    /* Off interrupt, cpu_sr1=0,PRIMASK=1 */(3)
    {
        /* Critical section 2 */
        cpu_sr2 = CPU_SR_Save();/*Off interrupt, cpu_sr2=1,PRIMASK=1 */(4)
        {

        }
        CPU_SR_Restore(cpu_sr2); /*Interrupt, cpu_sr2=1,PRIMASK=1 */(5)
    }
    /* End of critical section 1 */
    CPU_SR_Restore(cpu_sr1);    /* Interrupt, cpu_sr1=0,PRIMASK=0 */(6)
}

This method can prevent the interrupt switch from being effective when the critical segment is nested.

5. Measure off interrupt time

The system will start the measurement before each shutdown and end the measurement after the shutdown. The measurement function saves the measured values in two aspects, the total shutdown interruption time and the last shutdown interruption time. Therefore, the user can optimize it according to the obtained off interrupt time. The rate of timestamp depends on the rate of CPU. For example, if the CPU rate is 72MHz and the timestamp rate is 72MHz, the timestamp resolution is 1/72M microseconds, about 13.8 nanoseconds (ns). Obviously, the off interrupt time measured by the system also includes the additional time consumed during measurement, so the measured time minus the time consumed during measurement is the actual off interrupt time. The off interrupt time is closely related to the instruction, speed and memory access speed of the processor.

6. Measurement off interrupt time initialization

#ifdef  CPU_CFG_INT_DIS_MEAS_EN
static  void  CPU_IntDisMeasInit (void)
{
    CPU_TS_TMR  time_meas_tot_cnts;
    CPU_INT16U  i;
    CPU_SR_ALLOC();

    CPU_IntDisMeasCtr         = 0u;
    CPU_IntDisNestCtr         = 0u;
    CPU_IntDisMeasStart_cnts  = 0u;
    CPU_IntDisMeasStop_cnts   = 0u;
    CPU_IntDisMeasMaxCur_cnts = 0u;
    CPU_IntDisMeasMax_cnts    = 0u;
    CPU_IntDisMeasOvrhd_cnts  = 0u;

    time_meas_tot_cnts = 0u;
    CPU_INT_DIS();                        /* Off interrupt */
    for (i = 0u; i < CPU_CFG_INT_DIS_MEAS_OVRHD_NBR; i++)
    {
        CPU_IntDisMeasMaxCur_cnts = 0u;
        CPU_IntDisMeasStart();        /* Perform multiple consecutive start / stop time measurements  */
        CPU_IntDisMeasStop();
        time_meas_tot_cnts += CPU_IntDisMeasMaxCur_cnts; /* Calculate total time */
    }

    CPU_IntDisMeasOvrhd_cnts  = (time_meas_tot_cnts + (CPU_CFG_INT_DIS_MEAS_OVRHD_NBR / 2u))/CPU_CFG_INT_DIS_MEAS_OVRHD_NBR;
    /*The average value is the additional time consumed for each measurement  */
    CPU_IntDisMeasMaxCur_cnts =  0u;
    CPU_IntDisMeasMax_cnts    =  0u;
    CPU_INT_EN();
}
#endif

The off interrupt measurement itself will also take some time. These times are actually added to the maximum off interrupt time measured by us. If this time can be calculated and subtracted in the later calculation, more accurate results can be obtained. The core idea of this code is very simple, that is, start and stop measurement for many times, and then obtain the average value after many times. Then this value can be regarded as the time of starting and stopping measurement and saved in the CPU_IntDisMeasOvrhd_cnts variable.

7. Measure the maximum off interrupt time

/* Start measuring off interrupt time  */
#ifdef  CPU_CFG_INT_DIS_MEAS_EN
void  CPU_IntDisMeasStart (void)
{
    CPU_IntDisMeasCtr++;
    if (CPU_IntDisNestCtr == 0u)                   /* The number of nesting layers is 0   */
    {
        CPU_IntDisMeasStart_cnts = CPU_TS_TmrRd();  /* Save timestamp  */
    }
    CPU_IntDisNestCtr++;
}
#endif

/* Stop measuring off interrupt time  */
#ifdef  CPU_CFG_INT_DIS_MEAS_EN
void  CPU_IntDisMeasStop (void)
{
    CPU_TS_TMR  time_ints_disd_cnts;
    CPU_IntDisNestCtr--;
    if (CPU_IntDisNestCtr == 0u)                /* The number of nesting layers is 0*/
    {
        CPU_IntDisMeasStop_cnts = CPU_TS_TmrRd();    /* Save timestamp  */

        time_ints_disd_cnts = CPU_IntDisMeasStop_cnts -
        CPU_IntDisMeasStart_cnts;/* Get off interrupt time  */
        /* Update maximum off interrupt time  */
        if (CPU_IntDisMeasMaxCur_cnts < time_ints_disd_cnts)
        {
            CPU_IntDisMeasMaxCur_cnts = time_ints_disd_cnts;
        }
        if (CPU_IntDisMeasMax_cnts    < time_ints_disd_cnts)
        {
            CPU_IntDisMeasMax_cnts    = time_ints_disd_cnts;
        }
    }
}
#endif

Subtract the extra time consumed by a measurement from the measured timestamp to obtain the time of this shutdown interrupt, and then compare this time with the time of the largest shutdown interrupt saved in history to refresh the maximum shutdown interrupt time

8. Get the maximum off interrupt time

Now that you have the off interrupt time, then μ C/OS also provides three functions related to obtaining off interrupt time, which are:

  • CPU_IntDisMeasMaxCurReset()

  • CPU_IntDisMeasMaxCurGet()

  • CPU_IntDisMeasMaxGet()

If you want to directly obtain the maximum off interrupt time during the operation of the whole program, call the CPU function directly_ Intdimeasmaxget().
If you want to measure the maximum off interrupt time of a program, call the CPU in front of the program_ The intdimeasmaxcurreset() function sets the CPU_ IntDisMeasMaxCur_ The CNTs variable is cleared to 0, and the CPU function is called at the end of this program_ Intdimeasmaxcurget().

Posted on Tue, 30 Nov 2021 08:20:25 -0500 by anticore