The port of the MBED Timeout works.
In this blog you can see the internals of this class and how to set up configuration and handling.
My port is for a Hercules TMS570LC43. I use the HAL utility and api for that controller where possible. |
This was the most difficult part. The MBED Timeout inherits from Ticker. And that one inherits from TimerEvent.
There's good reasons for this. A Timeout is a Ticker that fires once. A Ticker is TimerEvent that can call a callbac method. A TimerEvent is a specialised class that only deals with setting and handling hardware timers and their interupts.
Like the majority of the MBED code, well designed, well abstracted.
I haven't done the same. I implemented everything in a Timeout class. My goal is to get SemTech's LoRa MBED example working on Hercules and I focus on that.
I spent some effort getting a flexible set of timers running with the Hercules high end timer module. I'm reusing that development here. The main take-away for this post is that I've created a function huiSetTimeout(index, us). It activates one of the available timers, addressed by index that will throw an interrupt every given period (us, in microseconds). When you call that same function with 0 microseconds, it stops that timer. |
The Timeout Class
The class has two API methods; attach_us() and detach();
#include "Callback.h" #include "timeout_api.h" class Timeout { public: Timeout(); void detach(); void attach_us(mbed::Callback<void()> func, us_timestamp_t t); protected: static void _timeouthandler(uint32_t id); mbed::Callback<void()> _function; /**< Callback. */ };
attach_us() sets up the timeout with the given period (t), registers the callback method and starts the timer.
The callback mechanism is the one from MBED. Very nice. It allows to set a particular method of a particular instance of a particular object.
You'll see in the implementation that this method gets executed when the period t expired.
detach()is a mechanism to stop an active timeout. If you set a timeout of 1 minute and you call detach()after 30 seconds, the callback method will not get called.
Because our Timeout is a one-off mechanism, detach()is also called in the Timeout class itself when the timeout period expired. That's in line with MBED's design.
The protected method _timeouthandler() is for the implementation part. That one gets called when the host controller (Hercules) interrupt is triggered.
It then calls the callback _function that was passed and stored in attach_us().
Here's the implementation:
#include <Timeout.h> #include "timeout_api.h" Timeout::Timeout(): _function() { } void Timeout::detach() { _function = nullptr; timeout_stop((uint32_t)this); }; void Timeout::attach_us(mbed::Callback<void()> func, us_timestamp_t t) { _function = func; timeout_init((&Timeout::_timeouthandler), (uint32_t)this, t); }; void Timeout::_timeouthandler(uint32_t id) { Timeout *handler = (Timeout*)id; mbed::Callback<void()> local = handler->_function; handler->detach(); local.call(); }
The timeout_xxx() functions are discussed a little later. They serve as bridge between OO and procedural world, and as hardware abstractors.
attach_us() stores the callback in a protected member and then calls a function to activate a (we don't knwo which one, see later) Hercules free timer.
detach()is removes the callback and disables the allocated Hercules timer.
Note to self. I should create a destructor that calls detach too.
edit: done:
Timeout::~Timeout() { detach(); // the functions called in this method can safely be called, even if attach_us() was never called }
_timeouthandler() is static. It's a helper function, not related to an instance of the Timeout class, that is called from the lower non-object-oriented world.
The mechanism works so that the underlying level passes it the address of the object that's linked to the interrupt as the parameter.
That object is first deactivated, by calling detach(). Then the callback associated with that object is executed.
The timeout_xxx() Helpers
These bridge functions help our class to work with the timers that are allocated on the Hercules timer.
#ifdef __cplusplus extern "C" { #endif #define MAXTIMEOUTS 8 typedef uint64_t us_timestamp_t; typedef void (*timeout_handler)(uint32_t id); uint32_t timeout_init(timeout_handler handler, uint32_t id, us_timestamp_t t); uint32_t timeout_stop(uint32_t id); void timeoutfired(uint32_t id); #ifdef __cplusplus } #endif
The timestamp is defined as 64 bit. But then I use 32 bits downstream. I've used 64 bits to be compliant with MBED. Discuss.
typedef void (*timeout_handler)(uint32_t id);
This is how the c code sees the c++ static void _timeouthandler(uint32_t id).
timeout_init() and timeout_stop() are the ones called by the Timeout class. They use the Hercules HET interupt API I created to activate and deactivate timers.
timeoutfired() gets called by the Hercules interrupt handler and will call the static method _timeouthandler().of the Timeout class.
Implementation:
#include "timeout_api.h" #include "het_interrupt_utils.h" typedef struct { timeout_handler handler; uint32_t id; boolean used; } timeoutarray; static timeoutarray __timeout_ids [MAXTIMEOUTS] = {0}; uint32_t timeout_init(timeout_handler handler, uint32_t id, us_timestamp_t t) { // find first free location uint32_t i; for (i = 0; i < MAXTIMEOUTS; i++) { if (! __timeout_ids[i].used) { break; // found } } if (i < MAXTIMEOUTS) { __timeout_ids[i].handler = handler; __timeout_ids[i].id = id; __timeout_ids[i].used = true; huiSetTimeout(i, (uint32_t) t); } return i; } uint32_t timeout_stop(uint32_t id) { // find the right timer: uint32_t i; for (i = 0; i < MAXTIMEOUTS; i++) { if (__timeout_ids[i].id == id) { break; } } if (i < MAXTIMEOUTS) { huiSetTimeout(i, 0); __timeout_ids[i].handler = 0; __timeout_ids[i].id = 0; __timeout_ids[i].used = false; } return i; } void timeoutfired(uint32_t id) { __timeout_ids[id].handler(__timeout_ids[id].id); return; }
This layer has an array to register what timers are in use and free.
Its attributes get set each time a timeout_init() is called.
That array holds a pointer to the static method discussed above. I could as well not store this, because its the same for every timer. Discuss.
It also holds the object that asked for the timer. It's later passed back as the parameter for the handler, and also the key we use to find the relevant entry when detach() is called.
Weak point: when you call timeout_init() before the timeout period expired, I get duplicates and detach() would call the first one only.
edit: fixed:
uint32_t timeout_init(timeout_handler handler, uint32_t id, us_timestamp_t t) { // do we already use this object: uint32_t i; for (i = 0; i < MAXTIMEOUTS; i++) { if (__timeout_ids[i].id == id) { break; } } if (i == MAXTIMEOUTS) { // no, find first free location for (i = 0; i < MAXTIMEOUTS; i++) { if (! __timeout_ids[i].used) { break; // found } } } if (i < MAXTIMEOUTS) { // ...
There's not too much code in these functions. I hope they are understandable as they are now.
Test Bed
I've created a class called Engine that will set a timeout and that has a handler method for it. It's instantiated and run at program startup.
gioInit(); hetInit(); huiInit(); _enable_interrupt_(); Engine *engine = new Engine(); engine->run();
Here's the declaration:
class Engine { public: Engine(); virtual ~Engine(); void run(); protected: virtual void OnTimeoutLedA( void ); };
and the implementation:
Timeout ledA_TimeoutTimer; Engine::Engine() { // TODO Auto-generated constructor stub } Engine::~Engine() { // TODO Auto-generated destructor stub } void Engine::run() { ledA_TimeoutTimer.attach_us( mbed::callback( this, &Engine::OnTimeoutLedA ), 1000000 ); } void Engine::OnTimeoutLedA(void) { gioToggleBit(gioPORTB, 6); // reprime. This is for testing purpose, so a flashing led is fun ledA_TimeoutTimer.attach_us( mbed::callback( this, &Engine::OnTimeoutLedA ), 1000000 ); }
The timer is created as a static variable, common in MBED and Arduino. Should it be a class member? Discuss.
A stack capture can help to understand the wind-down from low level interrupt handler to the handler method in our Engine object:
The result of this exercise is a Blinky that's driven by the Timeout class. The project and all code is attached.
Top Comments