Modern Embedded Software – Conception. Chapter 2

The software system conception is ESP32 based device. It should receive date from GPS and accelerometer via UART and I2C. System should output data to epaper display as well as SD card. The user can interact with the device via hardware buttons, on-off switch and user button. System is battery powered, therefore it is idling as often as possible and use hardware control flow of data.

Software is using Event Driven Programming and Hierarchical Machine State as programming patterns.

UI Design

The most important point of software from users perspective is UI. It also can be a point of entry for modeling software.

Those are designs of UI. Epaper does not allow rich graphic, but in this case, UI should be as clean and readable as possible.

The most important information is current track time and one of arrows, indicating progress or regress.
Rest of information are not as important and can be smaller.

Lap Timer screen designs
Lap Timer screen designs

This simple UI can provide us in natural way system states and transitions between them. These states are link to Display component, but also defines some of the states in the rest of the system. E.g. Idle indicates that the system could be sleeping, waiting for any input.

System Structure:

Getting every aspect of the system I can see at this moment, the system structure should be as described above. I have divided software for several components, enumerate events and states with substates.

UART module
  • Gathering data from GPS
  • Use queue to read data from UART
  • Generate event GpsDataReceived when buffer is full to further process the buffer data
Accelerometer module
  • Gathering data from Accelerometer
  • Use queue to read data from I2C
  • Generate event AccelDataReceived when buffer is full to further process the buffer data
  • Generate event MotionDetected when device is moved
  • Generate event InactiveDetected when device is not moved by certain of time
Display module
  • Print parsed data on display
  • Listen for event DisplayUpdate and receive data for display
SD card module
  • Write session data into SD card file
  • Listen for event LogData and receive data for write
Switch module
  • Takes user button pressed actions
  • Generate event ShortButtonPressed when user short press the button
  • Generate event LongButtonPressed when user long press the button
Power module
  • Supervise system activity and put processor into sleep state when inactive
  • Wakes up system when any event occurs

States:

  • Root: top state, root for other states, handles sleeping processor
  • Idle: Device is waiting for an event, processor could be slept from here
  • GPSDataReceived: Parsing NMEA GPS data
  • AccelDataReceived: Parsing accelerometer data
  • DisplayUpdate: Updating data on display
    • SetLapPoint: Update lap point by user using current GPS position
      • Settings: Display settings
      • Success: Display success message
      • Error: Display error message
    • LapTime: Display lap timer data
    • Summary: Summary of session
    • Settings: Allow user to setup device
      • CalibrateAccel: Calibrate acceleration to correct 0 positions
        • Settings: Display settings
        • Success: Display success message
        • Error: Display error message
      • SetTrackSize: Setup track size, to proper handle overlaping GPS positions when track line are close together
        • Settings: Display settings
        • Success: Display success message
        • Error: Display error message
  • LogData: Put data into file
  • DeviceIdle: Transition device into idle state
  • DeviceActive: Wake up device and peripherals

Events:

  • GpsDataReceived
  • AccelDataReceived
  • DisplayUpdate
  • LogData
  • ShortButtonPressed
  • LongButtonPressed
  • MotionDetected
  • DeviceInactiveDetectedoved

Event Library

ESP32 IDF defines library for Event Driven Programming. This is the Event Loop Library.
Event defines the occurrence of something, like a parsed NMEA line. Event Loop is a bridge, between events and event handlers. Event Handler is a function which execute action, when an event occurs. Event source publishes event into the loop using provided API.
Event handler is a function which signature of esp_event_handler_t.
Event loop is created using esp_event_loop_create() and provide handle to the loop (esp_event_loop_handle_t).
Component should register event handler using esp_event_handler_register_with().
Event source post an event to the loop using esp_event_post_to().

In our case, we want to have only one event loop, global for synchronizing and blocking threads in one place.
Therefore, we need some code, in .ino file:

ESP_EVENT_DEFINE_BASE(COMPONENT_EVENT);

esp_event_loop_handle_t event_loop_handle;

void setup()
{
    // ...
    esp_event_loop_args_t loop_args = 
    {
        .queue_size = 10,
        .task_name = "event_task",
        .task_priority = uxTaskPriorityGet(NULL),
        .task_stack_size = 3072,
        .task_core_id = tskNO_AFFINITY
    };

    esp_event_loop_create(&loop_args, &event_loop_handle);
    // ...
}

This is configuration for our events mediator. In component code, we need to register to this event loop handle and we can receive and post events:

esp_event_handler_register_with(event_loop_handle, COMPONENT_EVENT, ESP_EVENT_ANY_ID, &Component::eventHandler, this);

void Component::eventHandler(void *handler_args, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
    Component *self = static_cast<Component *>(handler_args);
    self->handleEvent(event_base, event_id, event_data);
}

eventHandler is a function, that handles any type of event (ESP_EVENT_ANY_ID). The idea is to filter events inside this handler for proper events for the component. This way we have whole business logic inside the component and can easily extend handling for new events. We also pass this as parameter, therefore we have access to the component object.

esp_event_post_to(event_loop_handle, COMPONENT_EVENT, EVENT_WE_ARE_PASSING, nullptr, 0, 100 / portTICK_PERIOD_MS);

Sending new event is as simple as this code. We are passing to handle what event this is and pass data if needed.