2020-08-02

2020-07-31 Longan Nano GD32VF103 Chrono Class and Scheduler

>>>Longan Nano GD32VF103<<<

Longan Nano GD32VF103 Risc-V 108MHz 32b MCU toolchain, libraries and applications

>>>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: