diff --git a/lib/perf_counter.h b/lib/perf_counter.h index e818de2..61cde2e 100644 --- a/lib/perf_counter.h +++ b/lib/perf_counter.h @@ -155,26 +155,21 @@ /*============================ LOCAL VARIABLES ===============================*/ /*============================ PROTOTYPES ====================================*/ -/* Function: initialise cycle counter service - * and don't forget to tell the function whether the systick is already - * used by user applications. - * Don't worry, this cycle counter service won't affect your existing - * systick service. - */ -extern void init_cycle_counter(bool bSysTickIsOccupied); -/* Function : start_time - This function will be called right before starting the timed portion of the benchmark. - Implementation may be capturing a system timer (as implemented in the example code) - or zeroing some system parameters - e.g. setting the cpu clocks cycles to 0. -*/ -extern void start_cycle_counter(void); -/* Function : stop_time - This function will be called right after ending the timed portion of the benchmark. - Implementation may be capturing a system timer (as implemented in the example code) - or other system parameters - e.g. reading the current value of cpu cycles counter. + +/*! \brief try to set a start pointer for the performance counter + *! \retval false the LOAD register is too small + *! \retval true performance counter starts */ +extern bool start_cycle_counter(void); + +/*! \brief calculate the elapsed cycle count since the last start point + *! + *! \note you can have multiple stop_cycle_counter following one start point + *! + *! \return the elapsed cycle count. + */ extern int32_t stop_cycle_counter(void); /* Function : delay specified us with the help from systick @@ -207,4 +202,55 @@ __attribute__((nothrow)) extern int64_t clock(void); #endif + +/*----------------------------------------------------------------------------* + * Please ignore the following APIs unless you have encountered some known * + * special conditions * + *----------------------------------------------------------------------------*/ + + +/*! \brief initialise cycle counter service + *! and don't forget to tell the function whether the systick is already + *! used by user applications. + *! Don't worry, this cycle counter service won't affect your existing + *! systick service. + *! + *! \note Usually the perf_counter can initialise itself with the help of + *! __attribute__((constructor(255))), this works fine in Arm Compiler + *! 5 (armcc), Arm Compiler 6 (armclang), arm gcc and llvm. It doesn't + *! work for IAR. So, when you are using IAR, please call this function + *! manually to initialise the perf_counter service. + *! + *! \note Perf_counter library assumes that: + *! a. Your project has already using SysTick + *! b. It assumes that you have already implemented the SysTick_Handler + *! c. It assumes that you have enabled the exception handling for + *! SysTick. + *! If these are not the case, please: + *! a. Add an empty SysTick_Handler to your project if you don't have + *! one + *! b. Make sure you have the SysTick Exception handling enabled + *! c. And call function init_cycle_counter(false) if you doesn't + *! use SysTick in your project at all. + *! + *! \param bSysTickIsOccupied A boolean value which indicates whether SysTick + *! is already used by user application. + */ +extern void init_cycle_counter(bool bSysTickIsOccupied); + + +/*! \note if you are using a compiler rather than armcc or armclang, e.g. iar, + *! arm gcc etc, the systick_wrapper_ual.o doesn't work with the linker + *! of your target toolchain as it use the $Super$$ which is only supported + *! by armlink. For this condition, you have to manually put this function + *! into your existing SysTick_Handler to make the perf_counter library + *! work. + *! + *! \note if you are using Arm Compiler 5 (armcc) or Arm Compiler 6 (armclang) + *! you do NOT have to insert this function into your SysTick_Handler, + *! the systick_wrapper_ual.s will do the work for you. + */ +extern void user_code_insert_to_systick_handler(void); + + #endif diff --git a/lib/perf_counter.lib b/lib/perf_counter.lib index 1202af2..6b94bb0 100644 Binary files a/lib/perf_counter.lib and b/lib/perf_counter.lib differ diff --git a/perf_counter.c b/perf_counter.c index f60d73d..231277f 100644 --- a/perf_counter.c +++ b/perf_counter.c @@ -22,6 +22,10 @@ #include "cmsis_compiler.h" #include "perf_counter.h" +#ifndef PERF_CNT_COMPENSATION_THRESHOLD +# define PERF_CNT_COMPENSATION_THRESHOLD 16 +#endif + /*============================ MACROS ========================================*/ /* IO definitions (access restrictions to peripheral registers) */ /** @@ -192,6 +196,14 @@ void user_code_insert_to_systick_handler(void) s_lSystemClockCounts += wLoad; } +/*! \brief initialise cycle counter service + *! and don't forget to tell the function whether the systick is already + *! used by user applications. + *! Don't worry, this cycle counter service won't affect your existing + *! systick service. + *! \param bSysTickIsOccupied A boolean value which indicates whether SysTick + *! is already used by user application. + */ void init_cycle_counter(bool bSysTickIsOccupied) { if (!bSysTickIsOccupied) { @@ -205,16 +217,20 @@ void init_cycle_counter(bool bSysTickIsOccupied) __ensure_systick_wrapper(); } -/* Function : start_time - This function will be called right before starting the timed portion of the benchmark. - Implementation may be capturing a system timer (as implemented in the example code) - or zeroing some system parameters - e.g. setting the cpu clocks cycles to 0. +/*! \brief try to start the performance counter + *! \retval false the LOAD register is too small + *! \retval true performance counter starts */ -void start_cycle_counter(void) +bool start_cycle_counter(void) { + if (SysTick->LOAD < PERF_CNT_COMPENSATION_THRESHOLD) { + return false; + } + safe_atom_code(){ s_nCycleCounts = (int32_t)SysTick->VAL - (int32_t)SysTick->LOAD; } + return true; } /*! \note this function should only be called when irq is disabled @@ -225,22 +241,41 @@ static __attribute__((always_inline)) int32_t check_systick(void) { int32_t nTemp = (int32_t)SysTick->LOAD - (int32_t)SysTick->VAL; - /*! \note here is a corner case: SysTick->VAL is zero and SysTick Pending bit is set. - *! we should check this corner condition with (nTemp != SysTick->LOAD) + /*! \note Since we cannot stop counting temporarily, there are several + *! conditions which we should take into consideration: + *! Condition one: when assign nTemp with the register value (LOAD-VAL), + *! the underflow didn't happen and when we check the PENDSTSET bit, + *! the underflow happens, for this condition, we should not + *! do any compensation. When this happens, the (LOAD-nTemp) is + *! smaller than PERF_CNT_COMPENSATION_THRESHOLD (a big value) as + *! long as LOAD is bigger than (or equals to) the + *! PERF_CNT_COMPENSATION_THRESHOLD; + *! Condition two: when assign nTemp with the register value (LOAD-VAL), + *! the VAL is zero and underflow happened and the PENDSTSET bit + *! is set, for this condition, we should not do any compensation. + *! When this happens, the (LOAD-nTemp) is equals to zero. + *! Condition Three: when initialising nTemp with the register value + *! VAL, the underflow has already happened, hence the PENDSTSET + *! is set, for this condition, we should compensate the return + *! value. When this happens, the (LOAD-nTemp) is bigger than (or + *! equals to) PERF_CNT_COMPENSATION_THRESHOLD. + *! The following code implements an equivalent logic. */ - if ( (SCB->ICSR & SCB_ICSR_PENDSTSET_Msk) - && (nTemp != SysTick->LOAD)) { - nTemp += SysTick->LOAD; + if (SCB->ICSR & SCB_ICSR_PENDSTSET_Msk){ + if (((int32_t)SysTick->LOAD - nTemp) >= PERF_CNT_COMPENSATION_THRESHOLD) { + nTemp += SysTick->LOAD; + } } return nTemp; } -/* Function : stop_time - This function will be called right after ending the timed portion of the benchmark. - Implementation may be capturing a system timer (as implemented in the example code) - or other system parameters - e.g. reading the current value of cpu cycles counter. -*/ +/*! \brief calculate the elapsed cycle count since the last start point + *! + *! \note you can have multiple stop_cycle_counter following one start point + *! + *! \return the elapsed cycle count. + */ int32_t stop_cycle_counter(void) { int32_t nTemp = 0; diff --git a/perf_counter.h b/perf_counter.h index e818de2..61cde2e 100644 --- a/perf_counter.h +++ b/perf_counter.h @@ -155,26 +155,21 @@ /*============================ LOCAL VARIABLES ===============================*/ /*============================ PROTOTYPES ====================================*/ -/* Function: initialise cycle counter service - * and don't forget to tell the function whether the systick is already - * used by user applications. - * Don't worry, this cycle counter service won't affect your existing - * systick service. - */ -extern void init_cycle_counter(bool bSysTickIsOccupied); -/* Function : start_time - This function will be called right before starting the timed portion of the benchmark. - Implementation may be capturing a system timer (as implemented in the example code) - or zeroing some system parameters - e.g. setting the cpu clocks cycles to 0. -*/ -extern void start_cycle_counter(void); -/* Function : stop_time - This function will be called right after ending the timed portion of the benchmark. - Implementation may be capturing a system timer (as implemented in the example code) - or other system parameters - e.g. reading the current value of cpu cycles counter. + +/*! \brief try to set a start pointer for the performance counter + *! \retval false the LOAD register is too small + *! \retval true performance counter starts */ +extern bool start_cycle_counter(void); + +/*! \brief calculate the elapsed cycle count since the last start point + *! + *! \note you can have multiple stop_cycle_counter following one start point + *! + *! \return the elapsed cycle count. + */ extern int32_t stop_cycle_counter(void); /* Function : delay specified us with the help from systick @@ -207,4 +202,55 @@ __attribute__((nothrow)) extern int64_t clock(void); #endif + +/*----------------------------------------------------------------------------* + * Please ignore the following APIs unless you have encountered some known * + * special conditions * + *----------------------------------------------------------------------------*/ + + +/*! \brief initialise cycle counter service + *! and don't forget to tell the function whether the systick is already + *! used by user applications. + *! Don't worry, this cycle counter service won't affect your existing + *! systick service. + *! + *! \note Usually the perf_counter can initialise itself with the help of + *! __attribute__((constructor(255))), this works fine in Arm Compiler + *! 5 (armcc), Arm Compiler 6 (armclang), arm gcc and llvm. It doesn't + *! work for IAR. So, when you are using IAR, please call this function + *! manually to initialise the perf_counter service. + *! + *! \note Perf_counter library assumes that: + *! a. Your project has already using SysTick + *! b. It assumes that you have already implemented the SysTick_Handler + *! c. It assumes that you have enabled the exception handling for + *! SysTick. + *! If these are not the case, please: + *! a. Add an empty SysTick_Handler to your project if you don't have + *! one + *! b. Make sure you have the SysTick Exception handling enabled + *! c. And call function init_cycle_counter(false) if you doesn't + *! use SysTick in your project at all. + *! + *! \param bSysTickIsOccupied A boolean value which indicates whether SysTick + *! is already used by user application. + */ +extern void init_cycle_counter(bool bSysTickIsOccupied); + + +/*! \note if you are using a compiler rather than armcc or armclang, e.g. iar, + *! arm gcc etc, the systick_wrapper_ual.o doesn't work with the linker + *! of your target toolchain as it use the $Super$$ which is only supported + *! by armlink. For this condition, you have to manually put this function + *! into your existing SysTick_Handler to make the perf_counter library + *! work. + *! + *! \note if you are using Arm Compiler 5 (armcc) or Arm Compiler 6 (armclang) + *! you do NOT have to insert this function into your SysTick_Handler, + *! the systick_wrapper_ual.s will do the work for you. + */ +extern void user_code_insert_to_systick_handler(void); + + #endif