t05 - olikraus/m2tklib GitHub Wiki

Tutorial 5: Simultaneous Operation

Is it possible, that the user interface is still active, while the microcontroller "does" something?

This tutorial will show:

  • How to build a user interface with parallel control tasks.
  • How to design control tasks which can be run in parallel to the user interface.

This tutorial is structured by the following steps:

  1. Specify an example control task.
  2. Build a state machine for this control task.
  3. Create code for this state machine.
  4. Design a user interface to influence the control task.
  5. Setup a scheduler to put user interface and control task together.

Control Task Specification

  • The LED at pin 13 should periodically turned on and off (flashing).
  • The flashing can be turned on and off by the state of the variable is_flashing.
  • The LED should be turned on for 50*``led_on_time milliseconds.
  • The LED should be turned off for 50*``led_off_time milliseconds.

State Machine Design

  • A state machine has several states (represented by circles in the diagram below).
  • There is only exactly one active (current) state.
  • During the execution of the state machine, the current state may change from one state to another state (transition). This change follows the arrows which are shown in the state diagram.
  • Individual conditions decide if and which state change (transition) will occur.

https://github.com/bj0rky/m2tklibwiki/blob/master/led_flashing.png

This is one example state machine for the specification above:

  • As soon as is_flashing is false, the state machine will go to the STOP state.
  • As an additional action, the SETUP states also change the LED state.

State Machine Code

In C state machines are usually implemented as switch case statements. It is also required to apply unique numeric values for the state names.

#define STATE_STOP 0
#define STATE_SETUP_ON 1
#define STATE_WAIT_ON 2
#define STATE_SETUP_OFF 3
#define STATE_WAIT_OFF 4

uint8_t led_state = 0;
uint32_t next_change = 0;
uint8_t is_flashing = 0;
uint8_t led_on_time = 5;
uint8_t led_off_time = 5;

void led_process(void)
{
  switch(led_state) {
    case STATE_STOP:
      if ( is_flashing )
	led_state = STATE_SETUP_ON;
      break;
    case STATE_SETUP_ON:
      next_change = millis() + (led_on_time*50L);
      led_state = STATE_WAIT_ON;
      digitalWrite(13, HIGH);  // set the LED on    
      break;
    case STATE_WAIT_ON:
      if ( is_flashing == 0 )
	led_state = STATE_STOP;
      else if ( next_change < millis() )
	led_state = STATE_SETUP_OFF;
      break;
    case STATE_SETUP_OFF:
      next_change = millis() + (led_off_time*50L);
      led_state = STATE_WAIT_OFF;
      digitalWrite(13, LOW);   // set the LED off
      break;
    case STATE_WAIT_OFF:
      if ( is_flashing == 0 )
	led_state = STATE_STOP;
      else if ( next_change < millis() )
	led_state = STATE_SETUP_ON;
      break;
  }
}

User Interface Design

The user can modify the input variables of the state machine. Input variables are:

  • led_on_time: uint8_t value
  • led_off_time: uint8_t value
  • is_flashing: Boolean value to stop and start the flash procedure.

led_on_time and led_off_time will be modified by a U8NUM element. is_flashing is modified by two BUTTON elements:

LABEL: "On Time:" U8NUM
LABEL: "Off Time:" U8NUM
BUTTON: Stop BUTTON: Start

The user interface can be constructed with a GRIDLIST. Note that the callback procedures for the BUTTON element is also very simple.

M2_LABEL(el_label_on, NULL, "On Time:");
M2_U8NUM(el_u8_on, "c1", 1, 9, &led_on_time);

M2_LABEL(el_label_off, NULL, "Off Time:");
M2_U8NUM(el_u8_off, "c1", 1, 9, &led_off_time);

void fn_start(m2_el_fnarg_p fnarg) { is_flashing = 1; }
void fn_stop(m2_el_fnarg_p fnarg) { is_flashing = 0; }

M2_BUTTON(el_stop, NULL, "Stop", fn_stop);
M2_BUTTON(el_start, NULL, "Start", fn_start);

M2_LIST(list) = { 
    &el_label_on, &el_u8_on, 
    &el_label_off, &el_u8_off, 
    &el_stop, &el_start 
};
M2_GRIDLIST(list_element, "c2",list);

Scheduler

The two processes, menu and flashing, can be called one after the other. Both procedures will return as quick as possible (there is no delay() statement inside the processes):

void loop() {
  // menu management
  m2.checkKey();
  if ( m2.handleKey() ) {
      m2.draw();
  }
  // flashing process
  led_process();
}

Conclusion

It is easy to write software with GUI support, if the controlling part of the software has been implemented as state machine. As a rule of thumb: Never use the delay() statement, instead check if the target time has been reached and return immediately so that the GUI can be updated.

Links