graphics and "threading"

Status
Not open for further replies.

christoph

Well-known member
I have an application where I'd like to use "modal" graphics windows that accept user input. I can create widgets using my own classes on top of ugfx just fine, but that ends up with the following pseudo-code loop:
Code:
app::run()
  update_input();
  update_gfx();
so whatever I actually want to do in a modal window has to fit into that scheme. For example, I can't simply
Code:
  btn = getButtonWindow("Press a button"); // open a window showing the given message and wait until a button has been pressed
because that would need to update input and graphics in the background. Using an RTOS comes to mind, but that seems to be overkill. I've read a bit about coroutines and their siblings, but what pattern or library can you actually recommend? Or should I really go for an RTOS?

Regards

Christoph
 
RTOS seems to be heavy overkill only for modal windows.
Did you consider a simple state-machine ?
 
Well I kind of did that, but the result was tough on the eyes and mind, and hard to maintain. Maybe I just didn't take a clean and rigorous approach. Consider a kind of application start-up wizard that collects user input during start-up, while the same settings can be also be tweaked later, among other things. I'd have two phases:
Code:
start-up:
  getABC()
  getXYZ()
mainloop:
  doMainStuff() // has the option to open modal windows to getABC, getXYZ, or getSomeString

With a state machine, the main loop would be in one of the states SETUP or LOOP.
SETUP would have the states WAIT_FOR_ABC and WAIT_FOR_XYZ.
LOOP would have the states IDLE, WAIT_FOR_ABC, WAIT_FOR_XYZ and WAIT_FOR_STRING.
This smells already, because I'd have to repeat states in both phases which are basically the same, and all of the states would need to update inputs (buttons and whatever else there is) and graphics. That just feels wrong, or I just can't wrap my head around it in a way that makes it more straight forward. All of the objects that rely on any kind of input and output would need to "know" what needs to be done in the background.
 
With some help on fibers and the Zilch library I now stuck with the Zilch lib and was able to create some sort of very simple demo, which would look far more complicated with state machines:
Code:
#include <example-ssd1351-ugfx-config.h>
#include <uwdg-simple.h>
#include <SPI.h>
#include <zilch.h>

Zilch task(2000);

static void task1(void*);

void setup() {
  SPI.begin();

  gfxInit();
  uwdg::Widget::init();
  
  task.create(task1, 1000, 0);
  
  static uwdg::Label rootLabel("root", nullptr);
  rootLabel.setAlignment(uwdg::Label::Alignment::center);
}

void loop() {
  uwdg::Widget::drawWidgets();
  delay(100);
}

static void task1(void* arg)
{
  char buf[33+4] = "dlg  ";
  uwdg::Label label(buf, nullptr);
  label.setAlignment(uwdg::Label::Alignment::left);
  uint32_t count = 0;
  while ( count < 5 ) {
    itoa(count, &buf[4], 10);
    label.setText(buf);
    count++;
    delay(1000);
  }
}
What the app does: it creates a background widget ("rootLabel" in setup) and then, in task1, another ("label") that is supposed to disappear after 5 seconds. It does disappear indeed, and it is also redrawn by the main task. I'll now try to create some sort of modal dialogs based on this.

If anyone is interested in those widgets, I can upload them.
 
YAY! I'm really getting somewhere:
Code:
#include <example-ssd1351-ugfx-config.h>
#include <uwdg-simple.h>
#include <SPI.h>
#include <zilch.h>

Zilch task(2000);

class WaitDlg
{
  public:
    WaitDlg(uint8_t seconds)
      : label_(""),
      millis_(0),
      secondsLeft_(seconds)
    {
      label_.setAlignment(uwdg::Label::Alignment::center);
      setText();
    }
    void update()
    {
      if((secondsLeft_ > 0) && (millis_ >= 1000))
      {
        millis_ -= 1000;
        secondsLeft_--;
        setText();
      }
    }
    bool done()
    {
      return (secondsLeft_ == 0);
    }
    static void runModal(uint8_t seconds)
    {
      WaitDlg dlg(seconds);
      while(!dlg.done())
      {
        dlg.update();
        yield();
      }
    }
  private:
    void setText()
    {
      char buf[33+5] = "wait  ";
      itoa(secondsLeft_, &buf[4], 10);
      label_.setText(buf);
    }
    uwdg::Label label_;
    elapsedMillis millis_;
    uint8_t secondsLeft_;
};

void uiLoop(void*)
{
  while(1)
  {
    WaitDlg::runModal(2);
    delay(500);
  }
}

void setup() {
  SPI.begin();

  gfxInit(); // this is just uGFX
  uwdg::Widget::init();
  
  static uwdg::Label rootLabel("root", nullptr);
  rootLabel.setAlignment(uwdg::Label::Alignment::center);

  task.create(uiLoop, 1000, 0);
}

void loop() {
  uwdg::Widget::drawWidgets();
  delay(40);
}
 
I'm now combining a few libraries and the result feels a bit like programming a UI on a "real" computer:
  • ugfx: just the low-level graphics part (drawing geometry and text, display driver model)
  • ugfx-arduino: wrapper for ugfx that turns it into a set of arduino library modules
  • uwdg-simple: simple widgets library because I don't like the ugfx widgets (they are in C)
  • fibers: multitasking. I've created a fork because I might change a few bits
  • signals: observer pattern for connecting otherwise unrelated parts of the application
 
Status
Not open for further replies.
Back
Top