Skip to content

ag88/stm32duino-eventloop

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 

Repository files navigation

A stm32duino event loop

This is an event loop written for Arduino, it is mainly targetted at Arduino_STM32 the platform on which it is developed.

This may possibly work on other Arduinos but it hasn't been tested on those

try the demo:

In bin there is a pre-compiled flash image for maple mini bin/STM32duinoEventloop.bin

prerequisites

You need a maple mini (or compatible board) maple mini link to ebay

The flash image is targetted for the maple mini (and clones) (i.e. the led pin on pin 33 PB1) and should use the stm32duino bootloader (i.e. image installed at 0x8002000) You can install it to maple mini using dfu-util

dfu-util -a 2 -RD STM32duinoEventloop.bin

The full details is beyond the scope here, you may find more details on Arduino STM32 wiki wiki

running the demo
  • You need a serial console, you can use the Arduino IDE's serial console or apps like putty etc.
  • Connect to maple mini over usb serial with the serial console, type '?' or 'h' and you should see the prompt: f - faster, s - slower, m - memory
  • You can change the blink rate of the led by keying f - faster or s - slower

how it works:

mainloop.cpp is the normal arduino/wiring setup() and loop(), all the codes there (which is the sketch itself) is needed mainloop.cpp

In setup(), it registers the event handlers mainloop.cpp

In loop(), it calls the actual eventloop() codes, i.e. dispatches events. There is a particular catch about this which sort of makes it Arduino STM32 Libmaple core specific, Libmaple sets up a 1 ms systick interrupt which is used in the delay() timings. This implementation simply wait for this systick interrupt. It also hook the systick interrupt so that it increment a variable systick_fired to indicate that systick interrupt has occurred. mainloop.cpp

This makes the loop() run every milliseconds, in effect making it a scheduler, i.e. loop() dispatches events every millisecond or so (it may take longer than a single millisecond when other interrupts occur / etc)

In this demo there are 2 event handler classes:

  • key sender CKeySenderTask.cpp
    In key sender, it handles the systick event, poll usbserial for inputs and handles key inputs src
    the interesting parts is where it post key input events to the event queue: src src

  • led task CLedTask.cpp
    The event loop then calls the handleEvent method of the CLedTask (this is the main event handler callback for all the event handler classes) src
    this is where the led task (or your apps handle the events)

how did the led blink?

There is a utility class AsyncWait, AsyncWait basically 'sleeps' for the specified miliseconds (non-blocking) before it post an event in the event queue src src

Hence CLedTask handles 2 events ledon() which calls AsyncWait to enqueue a ledoff() evenf after the specified delay and vice versa, so the led blinks

how to use this, or how to write my own event handler:

  • TL;DR: inherit a class from CEventHandler and implement the handleEvent() method

The examples are in Tasks

The main steps are:

  1. In the arduino/wiring setup(), register the event handler object. Note that it is an object not simply a class, hence you need to instantiate that as a global variable src
  2. Implement the handleEvent() in the class, the eventloop() calls this. src
    When the sketch runs setup() post a EventID::AppStart event to all the event handlers src
    This is so that the event handlers can initialize after Arduino setup() has run but just before arduino loop() runs
  3. Events:
    You need to enumerate your events in events.h src
    The event handlers need to be enumerated as well in events.h
  4. Passing parameters in events
    Events has 2 parameters which are declared as int src
    the key sender task post keystrokes to the event queue as follows: src
event.handle_id = EHandleID::BroadCast;
event.event = EventID::KeyEvent;
event.param1 = c;
EventLoop.post(event);

hence in an event handler you could handle the key like

case EventID::KeyEvent:
  int key = event.param1;
  //do something when this key is entered

you can cast pointers in the parameters e.g. in a calling code:

char *message = "hello world";
Event event;
event.handle_id = EHandleID::LCDTask;
event.event = EventID::ShowText;
event.param1 = reinterpret_cast<int>(message);
EventLoop.post(event);

then in the event handler

case EventID::ShowText:
  char *string = reinterpret_cast<char *>(event.param1);
  // codes to show the text 
  1. Limits:
    The event queue is a 128 entry ring buffer, if it is full, CEventLoop::post(event) returns -1 event not added ! hence be careful about posting too many events if they aren't handled) 20 event handler (classes/objects)) src
  2. Async wait:
    Async wait works by posting an event after a delay, there are 32 wait slots (evaluated every systick i.e. every 1 ms, so that is multiplied if you want more slots) src
    To use them first get a handle to a wait slot src
    When you want to do an async wait:
  • TL;DR: once off wait WaitMgr.await(int duration, Event &event, bool imm) duration in msec e.g.
Event event;
event.handle_id = EHandleID::LedTask;
event.event = EventID::LedTaskLedOn;
WaitMgr.await(1000, event, false); 

old way:

  1. create an event
  2. call the wait manager with the handle obtained earlier to get a pointer to one of the slots (think take the alarm clock)
  3. call CAsyncWait.setwait(uint32_t duration, bool repeat, Event& event, bool imm); If you set repeat the event would be posted repeatedly every same duration. Event is the event you've created, imm (immediate) does not mean that it blocks, rather when the wait manager sees that the time is up, it can call the event handler immediately bypassing the event queue. This may make some things more prompt, nevertheless it is async, not real time. Duration strictly speaking is the number of systicks on Libmaple, i.e. 1ms each if nothing changed. or tl;dr: milliseconds.
  4. after calling setwait() note that the wait does not immediately take effect, you need to call CAsyncWait.await(); to start that wait timer. This design is necessary due to the repeat option in setting the wait slot, i.e. the same wait slot can keep firing events periodically src

License

Copyright(C) 2018 Andrew Goh
portions derived other works
RingBuffer (Copyright (c) 2014 Anders Kalør)
FreeStack (Copyright (c) 2011-2018 Bill Greiman)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

About

A stm32duino event loop

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages