microbit: Refactor of periodc timer callbacks

- Factored periodic timer funcitonality out of fiber scheduler and into a new SystemTimer module module.
- Enapsulated "ticks" variable within SystemTimer module, with accessor funciton.
- Added accessor/mutator functions to get and set the tick period.
- Added a class wrapper to permit periodic callbacks to both C and C++
  functions.
- Updated all references in microbit-dal to use this refactored API.
This commit is contained in:
Joe Finney 2016-03-10 17:01:17 +00:00
parent 8a06a4e3be
commit 9218e647e0
13 changed files with 269 additions and 162 deletions

View file

@ -7,6 +7,7 @@
#include "MicroBitHeapAllocator.h"
#include "MicroBitPanic.h"
#include "ErrorNo.h"
#include "MicroBitSystemTimer.h"
#include "Matrix4.h"
#include "MicroBitCompat.h"
#include "MicroBitComponent.h"

View file

@ -88,8 +88,8 @@
// Scheduling quantum (milliseconds)
// Also used to drive the micro:bit runtime system ticker.
#ifndef FIBER_TICK_PERIOD_MS
#define FIBER_TICK_PERIOD_MS 6
#ifndef SYSTEM_TICK_PERIOD_MS
#define SYSTEM_TICK_PERIOD_MS 6
#endif
//

View file

@ -79,21 +79,6 @@ extern Fiber *currentFiber;
*/
void scheduler_init(MicroBitMessageBus *_messageBus);
/*
* Reconfigures the system wide timer to the given period in milliseconds.
*
* @param speedMs the new period of the timer in milliseconds
* @return MICROBIT_OK on success. MICROBIT_INVALID_PARAMETER is returned if speedMs < 1
*
* @note this will also modify the value that is added to ticks in MiroBitFiber:scheduler_tick()
*/
int scheduler_set_tick_period(int speedMs);
/*
* Returns the currently used tick speed in milliseconds
*/
int scheduler_get_tick_period();
/**
* Determines if the fiber scheduler is operational.
* @return 1 if the fber scheduler is running, 0 otherwise.
@ -163,7 +148,7 @@ void schedule();
void fiber_sleep(unsigned long t);
/**
* Timer callback. Called from interrupt context, once every FIBER_TICK_PERIOD_MS milliseconds.
* Timer callback. Called from interrupt context, once every SYSTEM_TICK_PERIOD_MS milliseconds by default.
* Simply checks to determine if any fibers blocked on the sleep queue need to be woken up
* and made runnable.
*/
@ -265,21 +250,6 @@ void idle();
*/
void idle_task();
/**
* add a component to the array of system components which invocate the systemTick member function during a systemTick
*
* @param component The component to add.
* @return MICROBIT_OK on success. MICROBIT_NO_RESOURCES is returned if further components cannot be supported.
*/
int fiber_add_system_component(MicroBitComponent *component);
/**
* remove a component from the array of system components
* @param component The component to remove.
* @return MICROBIT_OK on success. MICROBIT_INVALID_PARAMETER is returned if the given component has not been previous added.
*/
int fiber_remove_system_component(MicroBitComponent *component);
/**
* add a component to the array of of idle thread components.
* isIdleCallbackNeeded is polled during a systemTick to determine if the idle thread should jump to the front of the queue
@ -313,10 +283,4 @@ extern "C" void save_context(Cortex_M0_TCB *tcb, uint32_t stack);
extern "C" void save_register_context(Cortex_M0_TCB *tcb);
extern "C" void restore_register_context(Cortex_M0_TCB *tcb);
/**
* Time since power on. Measured in milliseconds.
* When stored as an unsigned long, this gives us approx 50 days between rollover, which is ample. :-)
*/
extern unsigned long ticks;
#endif

97
inc/MicroBitSystemTimer.h Normal file
View file

@ -0,0 +1,97 @@
/**
* Definitions for the MicroBit system timer.
*
* This module provides:
*
* 1) a concept of global system time since power up
* 2) a simple periodic multiplexing API for the underlying mbed implementation.
*
* The latter is useful to avoid costs associated with multiple mbed Ticker instances
* in microbit-dal components, as each incurs a significant additional RAM overhead (circa 80 bytes).
*/
#ifndef MICROBIT_SYSTEM_TIMER_H
#define MICROBIT_SYSTEM_TIMER_H
#include "mbed.h"
#include "MicroBitConfig.h"
#include "MicroBitComponent.h"
/**
* Initialises the system wide timer.
* This must be called before any components register to receive periodic periodic callbacks.
*
* @param timer_period The initial period between interrupts, in millseconds.
* @return MICROBIT_OK on success.
*/
int system_timer_init(int period);
/*
* Reconfigures the system wide timer to the given period in milliseconds.
*
* @param period the new period of the timer in milliseconds
* @return MICROBIT_OK on success. MICROBIT_INVALID_PARAMETER is returned if period < 1
*/
int system_timer_set_period(int period);
/*
* Provides the current tick period in milliseconds
* @return the current tick period in milliseconds
*/
int system_timer_get_period();
/*
* Determines the time since the device was powered on.
* @return the current time since poweron in milliseconds
*/
unsigned long system_timer_current_time();
/**
* Timer callback. Called from interrupt context, once per period.
* Simply checks to determine if any fibers blocked on the sleep queue need to be woken up
* and made runnable.
*/
void system_timer_tick();
/**
* add a component to the array of system components. This component will then receive
* period callbacks, once every tick period.
*
* @param component The component to add.
* @return MICROBIT_OK on success. MICROBIT_NO_RESOURCES is returned if further components cannot be supported.
*/
int system_timer_add_component(MicroBitComponent *component);
/**
* remove a component from the array of system components. this component will no longer receive
* period callbacks.
* @param component The component to remove.
* @return MICROBIT_OK on success. MICROBIT_INVALID_PARAMETER is returned if the given component has not been previous added.
*/
int system_timer_remove_component(MicroBitComponent *component);
/**
* A simple C/C++ wrapper to allow periodic callbacks to standard C functions transparently.
*/
class MicroBitSystemTimerCallback : MicroBitComponent
{
void (*fn)(void);
/**
* Creates an object that receives periodic callbacks from the system timer,
* and, in turn, calls a plain C function as provided as a parameter.
*/
public:
MicroBitSystemTimerCallback(void (*function)(void))
{
fn = function;
system_timer_add_component(this);
}
void systemTick()
{
fn();
}
};
#endif

View file

@ -16,6 +16,7 @@ set(YOTTA_AUTO_MICROBIT-DAL_CPP_FILES
"MicroBitCompass.cpp"
"MicroBitEvent.cpp"
"MicroBitFiber.cpp"
"MicroBitSystemTimer.cpp"
"ManagedString.cpp"
"Matrix4.cpp"
"MicroBitAccelerometer.cpp"

View file

@ -112,8 +112,12 @@ void MicroBit::init()
// Bring up the heap allocator, and reclaim as much memory from SoftDevice as possible.
microbit_heap_init();
// Bring up fiber scheduler. Wait a little while for state to stabilise.
// Bring up the system timer.
system_timer_init(SYSTEM_TICK_PERIOD_MS);
// Bring up fiber scheduler.
scheduler_init(&messageBus);
// Load any stored calibration data from persistent storage.
MicroBitStorage s = MicroBitStorage();
MicroBitConfigurationBlock *b = s.getConfigurationBlock();
@ -536,7 +540,7 @@ void MicroBit::seedRandom(uint32_t seed)
*/
int MicroBit::addSystemComponent(MicroBitComponent *component)
{
return fiber_add_system_component(component);
return system_timer_add_component(component);
}
/**
@ -547,7 +551,7 @@ int MicroBit::addSystemComponent(MicroBitComponent *component)
*/
int MicroBit::removeSystemComponent(MicroBitComponent *component)
{
return fiber_remove_system_component(component);
return system_timer_remove_component(component);
}
/**
@ -581,7 +585,7 @@ int MicroBit::removeIdleComponent(MicroBitComponent *component)
*/
unsigned long MicroBit::systemTime()
{
return ticks;
return system_timer_current_time();
}

View file

@ -28,7 +28,7 @@ MicroBitButton::MicroBitButton(uint16_t id, PinName name, MicroBitButtonEventCon
this->eventConfiguration = eventConfiguration;
this->downStartTime = 0;
this->sigma = 0;
fiber_add_system_component(this);
system_timer_add_component(this);
}
/**
@ -84,7 +84,7 @@ void MicroBitButton::systemTick()
MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_DOWN);
//Record the time the button was pressed.
downStartTime=ticks;
downStartTime = system_timer_current_time();
}
// Check to see if we have on->off state change.
@ -96,7 +96,7 @@ void MicroBitButton::systemTick()
if (eventConfiguration == MICROBIT_BUTTON_ALL_EVENTS)
{
//determine if this is a long click or a normal click and send event
if((ticks - downStartTime) >= MICROBIT_BUTTON_LONG_CLICK_TIME)
if((system_timer_current_time() - downStartTime) >= MICROBIT_BUTTON_LONG_CLICK_TIME)
MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_LONG_CLICK);
else
MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_CLICK);
@ -104,7 +104,7 @@ void MicroBitButton::systemTick()
}
//if button is pressed and the hold triggered event state is not triggered AND we are greater than the button debounce value
if((status & MICROBIT_BUTTON_STATE) && !(status & MICROBIT_BUTTON_STATE_HOLD_TRIGGERED) && (ticks - downStartTime) >= MICROBIT_BUTTON_HOLD_TIME)
if((status & MICROBIT_BUTTON_STATE) && !(status & MICROBIT_BUTTON_STATE_HOLD_TRIGGERED) && (system_timer_current_time() - downStartTime) >= MICROBIT_BUTTON_HOLD_TIME)
{
//set the hold triggered event flag
status |= MICROBIT_BUTTON_STATE_HOLD_TRIGGERED;
@ -128,5 +128,5 @@ int MicroBitButton::isPressed()
*/
MicroBitButton::~MicroBitButton()
{
fiber_remove_system_component(this);
system_timer_remove_component(this);
}

View file

@ -50,7 +50,7 @@ MicroBitDisplay::MicroBitDisplay(uint16_t id, uint8_t x, uint8_t y) :
if (!this->defaultDisplay)
this->defaultDisplay = this;
fiber_add_system_component(this);
system_timer_add_component(this);
status |= MICROBIT_COMPONENT_RUNNING;
}
@ -153,7 +153,7 @@ void MicroBitDisplay::render()
//timer does not have enough resolution for brightness of 1. 23.53 us
if(brightness != MICROBIT_DISPLAY_MAXIMUM_BRIGHTNESS && brightness > MICROBIT_DISPLAY_MINIMUM_BRIGHTNESS)
renderTimer.attach_us(this, &MicroBitDisplay::renderFinish, (((brightness * 950) / (MICROBIT_DISPLAY_MAXIMUM_BRIGHTNESS)) * scheduler_get_tick_period()));
renderTimer.attach_us(this, &MicroBitDisplay::renderFinish, (((brightness * 950) / (MICROBIT_DISPLAY_MAXIMUM_BRIGHTNESS)) * system_timer_get_period()));
//this will take around 23us to execute
if(brightness <= MICROBIT_DISPLAY_MINIMUM_BRIGHTNESS)
@ -247,7 +247,7 @@ MicroBitDisplay::animationUpdate()
if (animationMode == ANIMATION_MODE_NONE)
return;
animationTick += FIBER_TICK_PERIOD_MS;
animationTick += system_timer_get_period();
if(animationTick >= animationDelay)
{
@ -939,17 +939,12 @@ void MicroBitDisplay::setDisplayMode(DisplayMode mode)
if(mode == DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE)
{
//to reduce the artifacts on the display - increase the tick
if(scheduler_get_tick_period() != MICROBIT_LIGHT_SENSOR_TICK_PERIOD)
scheduler_set_tick_period(MICROBIT_LIGHT_SENSOR_TICK_PERIOD);
if(system_timer_get_period() != MICROBIT_LIGHT_SENSOR_TICK_PERIOD)
system_timer_set_period(MICROBIT_LIGHT_SENSOR_TICK_PERIOD);
}
if(this->mode == DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE && mode != DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE)
{
//if we previously were in light sense mode - return to our default.
if(scheduler_get_tick_period() != FIBER_TICK_PERIOD_MS)
scheduler_set_tick_period(FIBER_TICK_PERIOD_MS);
delete this->lightSensor;
this->lightSensor = NULL;
@ -1216,5 +1211,5 @@ int MicroBitDisplay::readLightLevel()
*/
MicroBitDisplay::~MicroBitDisplay()
{
fiber_remove_system_component(this);
system_timer_remove_component(this);
}

View file

@ -21,7 +21,7 @@ MicroBitEvent::MicroBitEvent(uint16_t source, uint16_t value, MicroBitEventLaunc
{
this->source = source;
this->value = value;
this->timestamp = ticks;
this->timestamp = system_timer_current_time();
if(mode != CREATE_ONLY)
this->fire(mode);
@ -34,7 +34,7 @@ MicroBitEvent::MicroBitEvent()
{
this->source = 0;
this->value = 0;
this->timestamp = ticks;
this->timestamp = system_timer_current_time();
}
/**

View file

@ -25,13 +25,6 @@ static Fiber *sleepQueue = NULL; // The list of blocked fibers
static Fiber *waitQueue = NULL; // The list of blocked fibers waiting on an event.
static Fiber *fiberPool = NULL; // Pool of unused fibers, just waiting for a job to do.
/*
* Time since power on. Measured in milliseconds.
* When stored as an unsigned long, this gives us approx 50 days between rollover, which is ample. :-)
*/
unsigned long ticks = 0;
static unsigned int tickPeriod = FIBER_TICK_PERIOD_MS;
/*
* Scheduler wide flags
*/
@ -43,15 +36,9 @@ static uint8_t fiber_flags = 0;
*/
static MicroBitMessageBus *messageBus = NULL;
// Array of components which are iterated during a system tick
static MicroBitComponent* systemTickComponents[MICROBIT_SYSTEM_COMPONENTS];
// Array of components which are iterated during idle thread execution, isIdleCallbackNeeded is polled during a systemTick.
static MicroBitComponent* idleThreadComponents[MICROBIT_IDLE_COMPONENTS];
// Periodic callback interrupt
static Ticker fiberSchedulerTicker;
/**
* Utility function to add the currenty running fiber to the given queue.
* Perform a simple add at the head, to avoid complexity,
@ -190,39 +177,11 @@ void scheduler_init(MicroBitMessageBus *_messageBus)
}
// register a period callback to drive the scheduler and any other registered components.
fiberSchedulerTicker.attach_us(scheduler_tick, tickPeriod * 1000);
new MicroBitSystemTimerCallback(scheduler_tick);
fiber_flags |= MICROBIT_SCHEDULER_RUNNING;
}
/*
* Reconfigures the system wide timer to the given period in milliseconds.
*
* @param speedMs the new period of the timer in milliseconds
* @return MICROBIT_OK on success. MICROBIT_INVALID_PARAMETER is returned if speedMs < 1
*
* @note this will also modify the value that is added to ticks in MiroBitFiber:scheduler_tick()
*/
int scheduler_set_tick_period(int speedMs)
{
if(speedMs < 1)
return MICROBIT_INVALID_PARAMETER;
tickPeriod = speedMs;
fiberSchedulerTicker.detach();
fiberSchedulerTicker.attach_us(scheduler_tick, tickPeriod * 1000);
return MICROBIT_OK;
}
/*
* Returns the currently used tick speed in milliseconds
*/
int scheduler_get_tick_period()
{
return tickPeriod;
}
/**
* Determines if the fiber scheduler is operational.
* @return 1 if the fber scheduler is running, 0 otherwise.
@ -236,7 +195,7 @@ int fiber_scheduler_running()
}
/**
* Timer callback. Called from interrupt context, once every FIBER_TICK_PERIOD_MS milliseconds, by default.
* Timer callback. Called from interrupt context, once every SYSTEM_TICK_PERIOD_MS milliseconds, by default.
* Simply checks to determine if any fibers blocked on the sleep queue need to be woken up
* and made runnable.
*/
@ -245,15 +204,12 @@ void scheduler_tick()
Fiber *f = sleepQueue;
Fiber *t;
// increment our real-time counter.
ticks += scheduler_get_tick_period();
// Check the sleep queue, and wake up any fibers as necessary.
while (f != NULL)
{
t = f->next;
if (ticks >= f->context)
if (system_timer_current_time() >= f->context)
{
// Wakey wakey!
dequeue_fiber(f);
@ -262,11 +218,6 @@ void scheduler_tick()
f = t;
}
// Update any components registered for a callback
for(int i = 0; i < MICROBIT_SYSTEM_COMPONENTS; i++)
if(systemTickComponents[i] != NULL)
systemTickComponents[i]->systemTick();
}
/**
@ -355,7 +306,7 @@ void fiber_sleep(unsigned long t)
}
// Calculate and store the time we want to wake up.
f->context = ticks + t;
f->context = system_timer_current_time() + t;
// Remove fiber from the run queue
dequeue_fiber(f);
@ -785,50 +736,6 @@ void schedule()
}
}
/**
* Add a component to to the collection of those invoked when the scheduler's periodic timer is triggered.
* The given component is called on each interrupt, within interrupt context.
*
* @param component The component to add.
* @return MICROBIT_OK on success. MICROBIT_NO_RESOURCES is returned if further components cannot be supported.
* @note this will be converted into a dynamic list of components
*/
int fiber_add_system_component(MicroBitComponent *component)
{
int i = 0;
while(systemTickComponents[i] != NULL && i < MICROBIT_SYSTEM_COMPONENTS)
i++;
if(i == MICROBIT_SYSTEM_COMPONENTS)
return MICROBIT_NO_RESOURCES;
systemTickComponents[i] = component;
return MICROBIT_OK;
}
/**
* remove a component from the array of components
* @param component The component to remove.
* @return MICROBIT_OK on success. MICROBIT_INVALID_PARAMTER is returned if the given component has not been previous added.
* @note this will be converted into a dynamic list of components
*/
int fiber_remove_system_component(MicroBitComponent *component)
{
int i = 0;
while(systemTickComponents[i] != component && i < MICROBIT_SYSTEM_COMPONENTS)
i++;
if(i == MICROBIT_SYSTEM_COMPONENTS)
return MICROBIT_INVALID_PARAMETER;
systemTickComponents[i] = NULL;
return MICROBIT_OK;
}
/**
* add a component to the array of components which invocate the systemTick member function during a systemTick
* @param component The component to add.

View file

@ -265,7 +265,7 @@ int MicroBitMessageBus::isIdleCallbackNeeded()
*
* Example:
* @code
* MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_DOWN,ticks,CREATE_ONLY);
* MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_DOWN,CREATE_ONLY);
* evt.fire();
*
* //OR YOU CAN DO THIS...

View file

@ -0,0 +1,138 @@
/**
* Definitions for the MicroBit system timer.
*
* This module provides:
*
* 1) a concept of global system time since power up
* 2) a simple periodic multiplexing API for the underlying mbed implementation.
*
* The latter is useful to avoid costs associated with multiple mbed Ticker instances
* in microbit-dal components, as each incurs a significant additional RAM overhead (circa 80 bytes).
*/
#include "MicroBit.h"
/*
* Time since power on. Measured in milliseconds.
* When stored as an unsigned long, this gives us approx 50 days between rollover, which is ample. :-)
*/
static unsigned long ticks = 0;
static unsigned int tick_period = 0;
// Array of components which are iterated during a system tick
static MicroBitComponent* systemTickComponents[MICROBIT_SYSTEM_COMPONENTS];
// Periodic callback interrupt
static Ticker timer;
/**
* Initialises the system wide timer.
* This must be called before any components register to receive periodic periodic callbacks.
*
* @param timer_period The initial period between interrupts, in millseconds.
* @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER if period is illegal.
*/
int system_timer_init(int period)
{
return system_timer_set_period(period);
}
/*
* Reconfigures the system wide timer to the given period in milliseconds.
*
* @param period the new period of the timer in milliseconds
* @return MICROBIT_OK on success. MICROBIT_INVALID_PARAMETER is returned if period < 1
*/
int system_timer_set_period(int period)
{
if (period < 1)
return MICROBIT_INVALID_PARAMETER;
// If a timer is already running, ensure it is disabled before reconfiguring.
if (tick_period)
timer.detach();
// register a period callback to drive the scheduler and any other registered components.
tick_period = period;
timer.attach_us(system_timer_tick, period * 1000);
return MICROBIT_OK;
}
/*
* Provides the current tick period in milliseconds
* @return the current tick period in milliseconds
*/
int system_timer_get_period()
{
return tick_period;
}
/*
* Determines the time since the device was powered on.
* @return the current time since poweron in milliseconds
*/
unsigned long system_timer_current_time()
{
return ticks;
}
/**
* Timer callback. Called from interrupt context, once per period.
* Simply checks to determine if any fibers blocked on the sleep queue need to be woken up
* and made runnable.
*/
void system_timer_tick()
{
// increment our real-time counter.
ticks += system_timer_get_period();
// Update any components registered for a callback
for(int i = 0; i < MICROBIT_SYSTEM_COMPONENTS; i++)
if(systemTickComponents[i] != NULL)
systemTickComponents[i]->systemTick();
}
/**
* Add a component to the array of system components. This component will then receive
* period callbacks, once every tick period.
*
* @param component The component to add.
* @return MICROBIT_OK on success. MICROBIT_NO_RESOURCES is returned if further components cannot be supported.
*/
int system_timer_add_component(MicroBitComponent *component)
{
int i = 0;
while(systemTickComponents[i] != NULL && i < MICROBIT_SYSTEM_COMPONENTS)
i++;
if(i == MICROBIT_SYSTEM_COMPONENTS)
return MICROBIT_NO_RESOURCES;
systemTickComponents[i] = component;
return MICROBIT_OK;
}
/**
* remove a component from the array of system components. this component will no longer receive
* period callbacks.
* @param component The component to remove.
* @return MICROBIT_OK on success. MICROBIT_INVALID_PARAMETER is returned if the given component has not been previous added.
*/
int system_timer_remove_component(MicroBitComponent *component)
{
int i = 0;
while(systemTickComponents[i] != component && i < MICROBIT_SYSTEM_COMPONENTS)
i++;
if(i == MICROBIT_SYSTEM_COMPONENTS)
return MICROBIT_INVALID_PARAMETER;
systemTickComponents[i] = NULL;
return MICROBIT_OK;
}

View file

@ -98,12 +98,12 @@ void MicroBitThermometer::idleTick()
}
/**
* Determines if we're due to take another temeoratur reading
* Determines if we're due to take another temperature reading
* @return 1 if we're due to take a temperature reading, 0 otherwise.
*/
int MicroBitThermometer::isSampleNeeded()
{
return ticks >= sampleTime;
return system_timer_current_time() >= sampleTime;
}
/**
@ -167,7 +167,7 @@ void MicroBitThermometer::updateTemperature()
temperature = processorTemperature / 4;
// Schedule our next sample.
sampleTime = ticks + samplePeriod;
sampleTime = system_timer_current_time() + samplePeriod;
// Send an event to indicate that we'e updated our temperature.
MicroBitEvent e(id, MICROBIT_THERMOMETER_EVT_UPDATE);