>>>Longan Nano GD32VF103<<<
>>>Longan Nano Chrono Class and Scheduler<<<
Chrono C++ Class uses the 64bit 27MHz systick timer to provide high resolution elapsed time and eaccumulate time HAL methods.
The Demo shows how to implement a fixed function scheduler with overrun and profiling using the Chrono C++ Class.
1Introduction
I use microcontrollers to perform fixed hard real time functions, in which a given list of tasks are executed always at the same rate in the same order, with little variation due to communication with the master.
With the AT4809 and previous microcontroller, I used an internal timer to issue the fast tick as an interrupt service routine, and from that tick, generate all the flags to execute slower tasks.
E.G. The PID running at 4KHz started by a fast tick at 4KHz, the LED running at 2Hz started by prescaling the 4KHz fast tick by 2000.
Illustration 1: Fast Tick issue execution of fixed tasks
With the Longan Nano I have a 64bit 27MHz fast tick timer at my disposal.
2Chrono Class
I decided to create an abstraction layer in class form since I'm practising with C++.
The class embeds two high resolution 64 bit timestamps.
The class uses scoped enums to encapsulate the configurations and definitions inside the namespace and class name e.g. the time unit is: Longan_nano::Chrono::Unit::microseconds
2.1Time Elapsed Mode
One of the core function of the class is to compute how much time has elapsed between two instants of time.
Use:
A timer has to be instantiated. Longan_nano::Chrono my_timer;
my_timer.start() followed by my_timer.stop() fill the timestamps. my_timer.get_elapsed( Unit ) returns the DeltaT between stop and start
my_timer.start() followed by my_timer.stop( Unit ) returns the DeltaT between stop and start
2.2Time Accumulation mode
Another thing the user may want to do is to integrate the time spent doing something to profile execution times or measure something happening in bursts.
The accumulation method has a special flag to use the internal 64 bit timestamps to do this integration at full sub microsecond resolution. This is much better than having the user integrate the rounded results of the elapsed time by hand.
NOTE: the 64bit operation needs a libc call. Use -fno-exceptions compile flag to avoid code size to blow up by 30KB for no reason.
Use:
A timer has to be instantiated. Longan_nano::Chrono my_timer;
my_timer.start() followed by my_timer.accumulate() fill the timestamps. my_timer.get_accumulator( Unit ) returns all the sum of all DeltaT occurred between start and accumulate
my_timer.start() followed by my_timer.accumulate ( Unit ) returns all the sum of all DeltaT occurred between start and accumulate
2.3Chrono Class Source Code
/********************************************************************************** | |
BSD 3-Clause License | |
Copyright (c) 2020, Orso Eric | |
All rights reserved. | |
Redistribution and use in source and binary forms, with or without | |
modification, are permitted provided that the following conditions are met: | |
1. Redistributions of source code must retain the above copyright notice, this | |
list of conditions and the following disclaimer. | |
2. Redistributions in binary form must reproduce the above copyright notice, | |
this list of conditions and the following disclaimer in the documentation | |
and/or other materials provided with the distribution. | |
3. Neither the name of the copyright holder nor the names of its | |
contributors may be used to endorse or promote products derived from | |
this software without specific prior written permission. | |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
**********************************************************************************/ | |
/********************************************************************************** | |
** ENVIROMENT VARIABILE | |
**********************************************************************************/ | |
#ifndef LONGAN_NANO_CHRONO_H_ | |
#define LONGAN_NANO_CHRONO_H_ | |
/********************************************************************************** | |
** GLOBAL INCLUDES | |
**********************************************************************************/ | |
#include <gd32vf103.h> | |
/********************************************************************************** | |
** DEFINES | |
**********************************************************************************/ | |
/********************************************************************************** | |
** MACROS | |
**********************************************************************************/ | |
/********************************************************************************** | |
** NAMESPACE | |
**********************************************************************************/ | |
//! @namespace Longan_nano namespace encapsulating all related drivers and HAL | |
namespace Longan_nano | |
{ | |
/********************************************************************************** | |
** TYPEDEFS | |
**********************************************************************************/ | |
/********************************************************************************** | |
** PROTOTYPE: STRUCTURES | |
**********************************************************************************/ | |
/********************************************************************************** | |
** PROTOTYPE: GLOBAL VARIABILES | |
**********************************************************************************/ | |
/********************************************************************************** | |
** PROTOTYPE: CLASS | |
**********************************************************************************/ | |
/************************************************************************************/ | |
//! @class Chrono | |
/************************************************************************************/ | |
//! @author Orso Eric | |
//! @version 2020-07-28 | |
//! @brief Deals with busy delays, elapsed time and accumulated time | |
//! @bug None | |
//! @copyright BSD 3-Clause License Copyright (c) 2020, Orso Eric | |
//! @details | |
//! SysTick | |
//! Use 64b 27MHz SysTick timer. | |
//! History Version | |
//! 2020-07-20 | |
//! Rework Utils library into Chrono library to add start/stop timer | |
//! "start" snaps the start timestamp at full resolution | |
//! "stop" snaps the stop timestamp at full resolution | |
//! "elapsed" returns the elapsed time between stop and start if valid. zero otherwise | |
//! 2020-07-28 | |
//! I need a method to accumulate execution time and profile how long an activity has taken | |
//! "accumulate" add stop-start to an internal accumulator at full resolution | |
//! Add combined "stop" "elapsed" implementation with better performance and fewer calls needed | |
//! I can combine the stop and accumulator counters since i use one or the other | |
//! I use a flag to handle initialization and invalid when switching between modes and automatically reset the accumulator | |
/************************************************************************************/ | |
class Chrono | |
{ | |
//Visible to all | |
public: | |
//-------------------------------------------------------------------------- | |
// ENUM | |
//-------------------------------------------------------------------------- | |
//Configurations of the SysTick | |
typedef enum _Config | |
{ | |
PEDANTIC_CHECKS = false, //Pedantic checks inside the functions | |
SYSTICK_INVALID = 0xFFFFFFFF, //Invalid timer value | |
SYSTICK_PRE = 4, //Prescaler for the systick timer | |
TIME_INVALID = -1, //Return time in case time is invalid | |
} Config; | |
//What time unit to use | |
typedef enum _Unit | |
{ | |
milliseconds, | |
microseconds, | |
} Unit; | |
//-------------------------------------------------------------------------- | |
// CONSTRUCTORS | |
//-------------------------------------------------------------------------- | |
/***************************************************************************/ | |
//! @brief Empty Constructor | |
//! Chrono | void | |
/***************************************************************************/ | |
//! @return void | |
//! @details \n | |
//! Initialize timestamps to invalid | |
/***************************************************************************/ | |
Chrono( void ) | |
{ | |
//Initialize timestamps to invalid | |
this -> g_systick_start = Config::SYSTICK_INVALID; | |
this -> g_systick_stop = Config::SYSTICK_INVALID; | |
//Regular timer mode | |
this -> g_f_accumulator_mode = false; | |
//---------------------------------------------------------------- | |
// RETURN | |
//---------------------------------------------------------------- | |
return; | |
} //End Constructor: Chrono | void | |
//-------------------------------------------------------------------------- | |
// DESTRUCTORS | |
//-------------------------------------------------------------------------- | |
/***************************************************************************/ | |
//! @brief Empty Destructor | |
/***************************************************************************/ | |
//! ~Chrono | void | |
//! @return void | |
/***************************************************************************/ | |
~Chrono( void ) | |
{ | |
//---------------------------------------------------------------- | |
// RETURN | |
//---------------------------------------------------------------- | |
return; | |
} | |
//-------------------------------------------------------------------------- | |
// OPERATORS | |
//-------------------------------------------------------------------------- | |
//-------------------------------------------------------------------------- | |
// SETTERS | |
//-------------------------------------------------------------------------- | |
/***************************************************************************/ | |
//! @brief public setter | |
//! start | void | |
/***************************************************************************/ | |
//! @return void | |
//! @details \n | |
//! Start the timer | |
/***************************************************************************/ | |
void start( void ) | |
{ | |
//---------------------------------------------------------------- | |
// BODY | |
//---------------------------------------------------------------- | |
//Snap timer start | |
this -> g_systick_start = get_timer_value(); | |
//---------------------------------------------------------------- | |
// RETURN | |
//---------------------------------------------------------------- | |
return; | |
} //End setter: start | void | |
/***************************************************************************/ | |
//! @brief public setter | |
//! stop | void | |
/***************************************************************************/ | |
//! @return void | |
//! @details \n | |
//! Stop the timer. Snap the stop time | |
/***************************************************************************/ | |
void stop( void ) | |
{ | |
//---------------------------------------------------------------- | |
// BODY | |
//---------------------------------------------------------------- | |
//Snap timer start | |
this -> g_systick_stop = get_timer_value(); | |
//Regular timer mode | |
this -> g_f_accumulator_mode = false; | |
//---------------------------------------------------------------- | |
// RETURN | |
//---------------------------------------------------------------- | |
return; | |
} //End setter: stop | void | |
/***************************************************************************/ | |
//! @brief public setter | |
//! stop | Unit | | |
/***************************************************************************/ | |
//! @param unit | |
//! @return void | |
//! @details \n | |
//! Stop the timer. Snap the stop time | |
//! Return the elapsed time between stop and start in the given unit | |
/***************************************************************************/ | |
int32_t stop( Unit unit ) | |
{ | |
//---------------------------------------------------------------- | |
// CHECK | |
//---------------------------------------------------------------- | |
//Get start time | |
uint64_t start_tmp = this -> g_systick_start; | |
//If: bad timestamp | |
if ((Config::PEDANTIC_CHECKS == true) && (start_tmp == Config::SYSTICK_INVALID)) | |
{ | |
return Config::TIME_INVALID; //FAIL | |
} | |
//---------------------------------------------------------------- | |
// BODY | |
//---------------------------------------------------------------- | |
//Snap the timestamp | |
uint64_t stop_tmp = get_timer_value(); | |
//Record the stop timestamp inside the timer | |
this -> g_systick_stop = stop_tmp; | |
//Regular timer mode | |
this -> g_f_accumulator_mode = false; | |
//---------------------------------------------------------------- | |
// RETURN | |
//---------------------------------------------------------------- | |
//return elapsed time | |
return this -> compute_elapsed( start_tmp, stop_tmp, unit ); | |
} //End setter: stop | Unit | | |
/***************************************************************************/ | |
//! @brief public method | |
//! accumulate | void | | |
/***************************************************************************/ | |
//! @return unsigned int | frequency of the SysTick timer | |
//! @details \n | |
//! Snap the stop time | |
//! Accumulate the difference between stop and start inside the accumulator | |
//! Swap the stop and start, invalidate the stop. Prepare for next cycle | |
//! Use stop counter as accumulator | |
//! If timer was in timer mode, reset the accumulator | |
/***************************************************************************/ | |
bool accumulate( void ) | |
{ | |
//---------------------------------------------------------------- | |
// CHECK | |
//---------------------------------------------------------------- | |
//Get start time | |
uint64_t start_tmp = this -> g_systick_start; | |
//If: bad timestamp | |
if ((Config::PEDANTIC_CHECKS == true) && (start_tmp == Config::SYSTICK_INVALID)) | |
{ | |
return true; //FAIL | |
} | |
//Temp accumulator | |
uint64_t accumulator_tmp; | |
//if: Regular timer mode | |
if (this -> g_f_accumulator_mode == false) | |
{ | |
//reset the accumulator | |
accumulator_tmp = 0; | |
//go into accumulator mode | |
this -> g_f_accumulator_mode = true; | |
} | |
//If: accumulator mode | |
else | |
{ | |
//Fetch the current accumulator count | |
accumulator_tmp = this -> g_systick_stop; | |
} | |
//---------------------------------------------------------------- | |
// BODY | |
//---------------------------------------------------------------- | |
//Snap the timestamp | |
uint64_t stop_tmp = get_timer_value(); | |
//Record the stop timestamp inside the start timestamp and invalidate the stop timestamp | |
this -> g_systick_start = stop_tmp; | |
//Accumulate the DeltaT inside the accumulator at full resolution | |
accumulator_tmp += stop_tmp -start_tmp; | |
//Store the accumulator value | |
this -> g_systick_stop = accumulator_tmp; | |
//---------------------------------------------------------------- | |
// RETURN | |
//---------------------------------------------------------------- | |
return false; //OK | |
} //End public method: accumulate | void | | |
/***************************************************************************/ | |
//! @brief public method | |
//! accumulate | Unit unit | | |
/***************************************************************************/ | |
//! @return unsigned int | frequency of the SysTick timer | |
//! @details \n | |
//! Snap the stop time | |
//! Accumulate the difference between stop and start inside the accumulator | |
//! Swap the stop and start, invalidate the stop. Prepare for next cycle | |
/***************************************************************************/ | |
int32_t accumulate( Unit unit ) | |
{ | |
//---------------------------------------------------------------- | |
// CHECK | |
//---------------------------------------------------------------- | |
//Get start time | |
uint64_t start_tmp = this -> g_systick_start; | |
//If: bad timestamp | |
if ((Config::PEDANTIC_CHECKS == true) && (start_tmp == Config::SYSTICK_INVALID)) | |
{ | |
return true; //FAIL | |
} | |
//Temp accumulator | |
uint64_t accumulator_tmp; | |
//if: Regular timer mode | |
if (this -> g_f_accumulator_mode == false) | |
{ | |
//reset the accumulator | |
accumulator_tmp = 0; | |
//go into accumulator mode | |
this -> g_f_accumulator_mode = true; | |
} | |
//If: accumulator mode | |
else | |
{ | |
//Fetch the current accumulator count | |
accumulator_tmp = this -> g_systick_stop; | |
} | |
//---------------------------------------------------------------- | |
// BODY | |
//---------------------------------------------------------------- | |
//Snap the timestamp | |
uint64_t stop_tmp = get_timer_value(); | |
//Record the stop timestamp inside the start timestamp and invalidate the stop timestamp | |
this -> g_systick_start = stop_tmp; | |
//Accumulate the DeltaT inside the accumulator at full resolution | |
accumulator_tmp += stop_tmp -start_tmp; | |
//Store the accumulator value | |
this -> g_systick_stop = accumulator_tmp; | |
//---------------------------------------------------------------- | |
// RETURN | |
//---------------------------------------------------------------- | |
return this -> compute_accumulator( accumulator_tmp, unit ); | |
} //End public method: accumulate | void | | |
/***************************************************************************/ | |
//! @brief public getter | |
//! get_elapsed | Unit | | |
/***************************************************************************/ | |
//! @return int | elapsed milliseconds. negative mean the timer was uninitialized | |
//! @details \n | |
//! Time elapsed between start and stop | |
/***************************************************************************/ | |
int32_t get_elapsed( Unit unit ) | |
{ | |
//---------------------------------------------------------------- | |
// VARS | |
//---------------------------------------------------------------- | |
//Fetch timestamps | |
uint64_t start_tmp = this -> g_systick_start; | |
uint64_t stop_tmp = this -> g_systick_stop; | |
//---------------------------------------------------------------- | |
// CHECKS | |
//---------------------------------------------------------------- | |
//If: a timetamp is invalid | |
if ((start_tmp == Config::SYSTICK_INVALID) || (stop_tmp == Config::SYSTICK_INVALID)) | |
{ | |
return Config::TIME_INVALID; //Invalid | |
} | |
//If: accumulator mode | |
if (this -> g_f_accumulator_mode == true) | |
{ | |
return Config::TIME_INVALID; //Invalid | |
} | |
//---------------------------------------------------------------- | |
// RETURN | |
//---------------------------------------------------------------- | |
//return elapsed time | |
return compute_elapsed( start_tmp, start_tmp, unit ); | |
} //End public getter: get_elapsed | Unit | | |
/***************************************************************************/ | |
//! @brief public getter | |
//! get_accumulator | Unit | | |
/***************************************************************************/ | |
//! @return int | accumulators. negative mean the timer was uninitialized | |
//! @details \n | |
//! return the DeltaT accumulated by the accumulate function in the given time unit | |
/***************************************************************************/ | |
inline int32_t get_accumulator( Unit unit ) | |
{ | |
//---------------------------------------------------------------- | |
// CHECK | |
//---------------------------------------------------------------- | |
//If: accumulator mode | |
if (this -> g_f_accumulator_mode == false) | |
{ | |
return Config::TIME_INVALID; //Invalid | |
} | |
//---------------------------------------------------------------- | |
// RETURN | |
//---------------------------------------------------------------- | |
//return accumulated time | |
return this -> compute_accumulator( this -> g_systick_stop, unit ); | |
} //End public getter: get_elapsed | Unit | | |
/***************************************************************************/ | |
//! @brief public static method | |
//! get_systick_freq | void | |
/***************************************************************************/ | |
//! @return unsigned int | frequency of the SysTick timer | |
//! @details \n | |
//! The SysTick timer is tied to the CPU clock prescaled by four | |
/***************************************************************************/ | |
static unsigned int get_systick_freq( void ) | |
{ | |
//---------------------------------------------------------------- | |
// RETURN | |
//---------------------------------------------------------------- | |
return SystemCoreClock /Config::SYSTICK_PRE; | |
} //End static method: get_systick_freq | void | |
//-------------------------------------------------------------------------- | |
// TESTERS | |
//-------------------------------------------------------------------------- | |
//-------------------------------------------------------------------------- | |
// PUBLIC METHODS | |
//-------------------------------------------------------------------------- | |
//-------------------------------------------------------------------------- | |
// PUBLIC STATIC METHODS | |
//-------------------------------------------------------------------------- | |
/***************************************************************************/ | |
//! @brief public static method | |
//! delay | Unit | unsigned int | | |
/***************************************************************************/ | |
//! @param delay | unsigned int | how long to wait for in milliseconds | |
//! @return void | | |
//! @details \n | |
//! Use the SysTick timer counter to busy wait for the correct number of microseconds | |
//! The CPU SysTick timer is clocked by the ABH clock/4 = 27MHz | |
//! SystemCoreClock defines the frequency of the CPU in Hz | |
/***************************************************************************/ | |
static bool delay( Unit unit, unsigned int delay_tmp ) | |
{ | |
//---------------------------------------------------------------- | |
// VARS | |
//---------------------------------------------------------------- | |
//Temp timestamp | |
uint64_t systick_tmp; | |
//Compute final timestamp | |
uint64_t systick_stop; | |
//Ticks required to count 1mS | |
uint32_t numticks = compute_tick_per_time_unit( unit ); | |
//If: bad unit | |
if (numticks == 0) | |
{ | |
return true; //fail | |
} | |
//---------------------------------------------------------------- | |
// BODY | |
//---------------------------------------------------------------- | |
// Wait for the correct number of ticks | |
//Snap start | |
systick_stop = get_timer_value(); | |
//Compute stop time. | |
systick_stop += numticks *delay_tmp; | |
//Wait an additional tick for current tick | |
systick_stop++; | |
//Busy wait for time to pass | |
do | |
{ | |
//Snap timestamp | |
systick_tmp = get_timer_value(); | |
} | |
while( systick_tmp < systick_stop ); | |
//---------------------------------------------------------------- | |
// RETURN | |
//---------------------------------------------------------------- | |
return false; //OK | |
} //End public static method: delay | Unit | unsigned int | | |
/***************************************************************************/ | |
//! @brief public static method | |
//! delay | unsigned int | | |
/***************************************************************************/ | |
//! @param delay | unsigned int | how long to wait for in milliseconds | |
//! @return void | | |
//! @details \n | |
//! wrapper of delay | |
//! its assumed that without arguments the user want a millisecond busy delay function | |
/***************************************************************************/ | |
static inline bool delay( unsigned int delay_tmp ) | |
{ | |
//---------------------------------------------------------------- | |
// RETURN | |
//---------------------------------------------------------------- | |
return delay( Longan_nano::Chrono::Unit::milliseconds, (unsigned int)delay_tmp ); | |
} //End public static method: delay | unsigned int | | |
//-------------------------------------------------------------------------- | |
// PUBLIC VARS | |
//-------------------------------------------------------------------------- | |
//Visible only inside the class | |
private: | |
//-------------------------------------------------------------------------- | |
// PRIVATE METHODS | |
//-------------------------------------------------------------------------- | |
/***************************************************************************/ | |
//! @brief private method | |
//! compute_tick_per_time_unit | Unit | | |
/***************************************************************************/ | |
//! @return uint32_t | number of systick counts needed to count one time unit | |
//! @details | |
//! Compute the number of systick counts needed to count one time unit | |
/***************************************************************************/ | |
static inline uint32_t compute_tick_per_time_unit( Unit unit ) | |
{ | |
//---------------------------------------------------------------- | |
// BODY | |
//---------------------------------------------------------------- | |
// Compute del | |
//Switch: Time unit | |
switch( unit ) | |
{ | |
case Unit::milliseconds: | |
{ | |
return SystemCoreClock /1000 /Config::SYSTICK_PRE; | |
break; | |
} | |
case Unit::microseconds: | |
{ | |
return SystemCoreClock /1000000 /Config::SYSTICK_PRE; | |
break; | |
} | |
//Unhandled time unit | |
default: | |
{ | |
return 0; //Invalid number of systick counts. Using it will yield infinite time | |
} | |
}; //End switch: Time unit | |
//---------------------------------------------------------------- | |
// RETURN | |
//---------------------------------------------------------------- | |
return 0; //Invalid number of systick counts. Using it will yield infinite time | |
} //End private method: compute_tick_per_time_unit | Unit | | |
/***************************************************************************/ | |
//! @brief private method | |
//! compute_elapsed | uint64_t | uint64_t | Unit | | |
/***************************************************************************/ | |
//! @return int32_t | negative = invalid | zero or positive = elapsed time in the given time unit | |
//! @details \n | |
//! use start and stop timestamp to compute the elapsed time in a given time unit | |
/***************************************************************************/ | |
inline int32_t compute_elapsed( uint64_t start, uint64_t stop, Unit unit ) | |
{ | |
//---------------------------------------------------------------- | |
// VARS | |
//---------------------------------------------------------------- | |
//If: causality violation | |
if ((Config::PEDANTIC_CHECKS == true) && (start > stop)) | |
{ | |
//Hopefully the timestamps are wrong and the universe still works as intended | |
return Config::TIME_INVALID; //FAIL | |
} | |
//---------------------------------------------------------------- | |
// BODY | |
//---------------------------------------------------------------- | |
//SysTick counts in one time unit | |
uint32_t numticks_time_unit = this -> compute_tick_per_time_unit( unit ); | |
//If: bad unit was provided | |
if ((Config::PEDANTIC_CHECKS == true) && (numticks_time_unit == 0)) | |
{ | |
return TIME_INVALID; | |
} | |
//Compute DeltaT in system ticks as stop-start | |
uint64_t deltat = stop -start; | |
//Compute DeltaT in time units | |
deltat /= numticks_time_unit; | |
//Demote | |
int32_t ret = deltat; | |
//---------------------------------------------------------------- | |
// RETURN | |
//---------------------------------------------------------------- | |
return ret; | |
} //End private method: compute_elapsed | uint64_t | uint64_t | Unit | | |
/***************************************************************************/ | |
//! @brief private method | |
//! compute_accumulator | uint64_t | Unit | | |
/***************************************************************************/ | |
//! @return int32_t | negative = invalid | zero or positive = elapsed time in the given time unit | |
//! @details \n | |
//! use start and stop timestamp to compute the elapsed time in a given time unit | |
/***************************************************************************/ | |
int32_t compute_accumulator( uint64_t accumulator, Unit unit ) | |
{ | |
//---------------------------------------------------------------- | |
// VARS | |
//---------------------------------------------------------------- | |
//If: accumulator | |
if ((Config::PEDANTIC_CHECKS == true) && (accumulator == Config::SYSTICK_INVALID)) | |
{ | |
return TIME_INVALID; | |
} | |
//---------------------------------------------------------------- | |
// BODY | |
//---------------------------------------------------------------- | |
//SysTick counts in one time unit | |
uint32_t numticks_time_unit = this -> compute_tick_per_time_unit( unit ); | |
//If: bad unit was provided | |
if ((Config::PEDANTIC_CHECKS == true) && (numticks_time_unit == 0)) | |
{ | |
return TIME_INVALID; | |
} | |
//Compute DeltaT in time units | |
accumulator /= numticks_time_unit; | |
//Demote | |
int32_t ret = accumulator; | |
//---------------------------------------------------------------- | |
// RETURN | |
//---------------------------------------------------------------- | |
return ret; | |
} //End private method: compute_accumulator | uint64_t | Unit | | |
//-------------------------------------------------------------------------- | |
// PRIVATE VARS | |
//-------------------------------------------------------------------------- | |
//true = accumulator mode | false = regular start stop mode | |
bool g_f_accumulator_mode; | |
//Systick Timestamps | |
uint64_t g_systick_start; | |
//Combined stop and accumulator counter | |
uint64_t g_systick_stop; | |
}; //End Class: Chrono | |
/********************************************************************************** | |
** NAMESPACE | |
**********************************************************************************/ | |
} //End Namespace: Longan_nano | |
#else | |
#warning "Multiple inclusion of hader file LONGAN_NANO_CHRONO_H_" | |
#endif |
3Demo
This demo shows a practical implementation of an hardwired scheduler that issue the toggle of the RED led at 250mS, and the GREEN led at 750ms, by prescaling the fast tick for the RED led.
This scheduler monitors the overrun of tasks, and light a blue led if error occurs.
Illustration 2: Demo Tasks
Video 1: Demo Tasks
3.1Demo Source Code
Code for the demo to shows the hardwired scheduler, the prescaler, the overrun detection, the uptime measurement and the cpu time measurement.
/**************************************************************************** | |
** OrangeBot Project | |
***************************************************************************** | |
** / | |
** / | |
** / | |
** ______ \ | |
** \ | |
** \ | |
***************************************************************************** | |
** Longan Nano Chrono Scheduler Demo | |
***************************************************************************** | |
** Show how to use the Chrono class to build a fixed function scheduler | |
** and use the Chrono class to measure elapsed time and accumulated time | |
****************************************************************************/ | |
/**************************************************************************** | |
** INCLUDES | |
****************************************************************************/ | |
//Longan Nano HAL | |
#include <gd32vf103.h> | |
//LED class | |
#include "longan_nano_led.hpp" | |
//Time class | |
#include "longan_nano_chrono.hpp" | |
/**************************************************************************** | |
** NAMESPACES | |
****************************************************************************/ | |
/**************************************************************************** | |
** DEFINES | |
****************************************************************************/ | |
//forever | |
#define EVER (;;) | |
/**************************************************************************** | |
** MACROS | |
****************************************************************************/ | |
/**************************************************************************** | |
** ENUM | |
****************************************************************************/ | |
//Configurations | |
typedef enum _Config | |
{ | |
//Microseconds between led toggles | |
RED_LED_BLINK_US = 250000, | |
GREEN_LED_BLINK_US = 750000, | |
} Config; | |
/**************************************************************************** | |
** STRUCT | |
****************************************************************************/ | |
//flags used by the hardwired scheduler | |
typedef struct _Scheduler | |
{ | |
bool f_red_led : 1; | |
bool f_green_led : 1; | |
bool f_overrun : 1; | |
} Scheduler; | |
/**************************************************************************** | |
** PROTOTYPES | |
****************************************************************************/ | |
/**************************************************************************** | |
** GLOBAL VARIABILES | |
****************************************************************************/ | |
//Scheduler for the tasks | |
Scheduler g_scheduler = { 0 }; | |
/**************************************************************************** | |
** FUNCTIONS | |
****************************************************************************/ | |
/**************************************************************************** | |
** @brief main | |
** main | void | |
****************************************************************************/ | |
//! @return int | | |
//! @details Entry point of program | |
/***************************************************************************/ | |
int main( void ) | |
{ | |
//---------------------------------------------------------------- | |
// VARS | |
//---------------------------------------------------------------- | |
//Systick timer used to schedule activities | |
Longan_nano::Chrono timer_scheduler; | |
//Systick timers to profile resource use | |
Longan_nano::Chrono timer_uptime; | |
Longan_nano::Chrono timer_cpu_use; | |
//Prescale the fastest task to generate other flags | |
static uint16_t scheduler_cnt; | |
//---------------------------------------------------------------- | |
// INIT | |
//---------------------------------------------------------------- | |
//Initialize LEDs | |
Longan_nano::Leds::init(); | |
Longan_nano::Leds::set_color( Longan_nano::Leds::Color::BLACK ); | |
//Snap start | |
timer_scheduler.start(); | |
timer_uptime.start(); | |
//---------------------------------------------------------------- | |
// BODY | |
//---------------------------------------------------------------- | |
for EVER | |
{ | |
//---------------------------------------------------------------- | |
// Scheduler | |
//---------------------------------------------------------------- | |
// Hardwired scheduler to release tasks | |
// Thanks to the Longan Nano SysTick timer there is no need to use peripherals for timekeeping | |
//Snap stop and get time since last start in microseconds | |
int elapsed_us = timer_scheduler.stop( Longan_nano::Chrono::Unit::microseconds ); | |
//If: for some reason, the timing is invalid. Algorithmic error | |
if (elapsed_us < 0) | |
{ | |
//Blue led means error | |
Longan_nano::Leds::set( Longan_nano::Leds::Color::BLUE ); | |
} | |
//If: enough time has passed between screen executions | |
else if (elapsed_us >= Config::RED_LED_BLINK_US) | |
{ | |
//---------------------------------------------------------------- | |
// LED | |
//---------------------------------------------------------------- | |
// The screen is the fastest task | |
//If: the previous task was not cleared | |
if (g_scheduler.f_red_led == true) | |
{ | |
//There was an overrun. Not enough CPU to keep up | |
g_scheduler.f_overrun = true; | |
} | |
else | |
{ | |
//Issue a LED BLINK update | |
g_scheduler.f_red_led = true; | |
} | |
//Snap start. Restart the timer | |
timer_scheduler.start(); | |
//---------------------------------------------------------------- | |
// Prescaler | |
//---------------------------------------------------------------- | |
// A prescaler is used to schedule the execution of slow tasks without the need of additional timers | |
//Prescaler counter | |
scheduler_cnt++; | |
//---------------------------------------------------------------- | |
// LED Blink | |
//---------------------------------------------------------------- | |
//If: enough ticks of the prescaler counter have elapsed | |
if (scheduler_cnt%(Config::GREEN_LED_BLINK_US /Config::RED_LED_BLINK_US) == 0) | |
{ | |
//If: the previous task was not cleared | |
if (g_scheduler.f_green_led == true) | |
{ | |
//There was an overrun. Not enough CPU to keep up | |
g_scheduler.f_overrun = true; | |
} | |
else | |
{ | |
//Issue a LED BLINK update | |
g_scheduler.f_green_led = true; | |
} | |
} | |
} //If: enough time has passed between screen executions | |
//Default | |
else | |
{ | |
//Nothing to do | |
} | |
//---------------------------------------------------------------- | |
// OVERRUN | |
//---------------------------------------------------------------- | |
// Triggered when a task is not completed before the next issue | |
//If: overrun | |
if (g_scheduler.f_overrun == true) | |
{ | |
//Clear error | |
g_scheduler.f_overrun = false; | |
//Blue led means error | |
Longan_nano::Leds::set( Longan_nano::Leds::Color::BLUE ); | |
} | |
//---------------------------------------------------------------- | |
// TASK: RED LED Blink | |
//---------------------------------------------------------------- | |
// If the RED LED Blink task is authorized to run | |
if (g_scheduler.f_red_led == true) | |
{ | |
//Reset flag | |
g_scheduler.f_red_led = false; | |
//Snap start to profile the CPU time spent in this task | |
timer_cpu_use.start(); | |
//Task code: in this calse blink the red led | |
Longan_nano::Leds::toggle( Longan_nano::Leds::Color::RED ); | |
//Accumulate DeltaT into the CPU timer accumulator | |
timer_cpu_use.accumulate(); | |
} | |
//---------------------------------------------------------------- | |
// TASK: GREEN LED Blink | |
//---------------------------------------------------------------- | |
// If the GREEN LED Blink task is authorized to run | |
if (g_scheduler.f_green_led == true) | |
{ | |
//Reset flag | |
g_scheduler.f_green_led = false; | |
//Snap start to profile the CPU time spent in this task | |
timer_cpu_use.start(); | |
//Task code: in this calse blink the red led | |
Longan_nano::Leds::toggle( Longan_nano::Leds::Color::GREEN ); | |
//Accumulate DeltaT into the CPU timer accumulator | |
timer_cpu_use.accumulate(); | |
} | |
//---------------------------------------------------------------- | |
// UPTIME | |
//---------------------------------------------------------------- | |
// At any moment the application can access to the time since application start | |
timer_uptime.stop( Longan_nano::Chrono::Unit::microseconds ); | |
//---------------------------------------------------------------- | |
// CPU use | |
//---------------------------------------------------------------- | |
// At any moment the application can access to the total time the CPU spent running tasks | |
timer_cpu_use.get_accumulator( Longan_nano::Chrono::Unit::microseconds ); | |
} //End forever | |
//---------------------------------------------------------------- | |
// RETURN | |
//---------------------------------------------------------------- | |
return 0; | |
} //end function: main |
4Conclusions
Time measurement and scheduling are fundamental to the operation of a fixed function hard real time microcontroller application.
In this document I laid out my solution and my implementation in the form of the Chrono class and fixed scheduler with execution flags. This solution does not use timer peripherals inside the microcontroller, perform unit conversion and works at full resolution and using C++ constructs.
This architecture will serve as the basis for all my applications based on the Longan nano GD32VF103.
No comments:
Post a Comment