Several Things at Once, But Multiple Threads Could Work Better

Hi all.

I have a situation that I’m having trouble fitting into the preferred Arduino “do several things at the same time” paradigm.

For 99.999% of the time my sketch is flying through the loop() function using the preferred technique to go about its business -- serving a web page, reading sensors, servicing RFM69 radio, sending Blynks, etc.

The exception comes when the user wants to make changes using the configuration menu. I use a 2-line LCD display along with 3 buttons (UP, DOWN, SELECT) to navigate through the multi-level, hierarchical menu structure. So, most of the time spent servicing the menu is simply waiting for the user the press the next button. Trouble is, the menu structure is complex and deep enough that I’d have to save a lot of state information if I were to simply return to loop() while waiting for the next button press. I’d have to save not only the current state but state information for all previous levels of the hierarchy so that I could traverse back up through the menu levels when finished.

So, instead of returning to loop() from my menu subsystem, I’m explicitly calling all my background task functions (sensors, RFM69, Blynk, etc) right from the menu handing functions while waiting for the next button press. I only return to loop() after exiting the top most menu.

I’m wondering if there’s a philosophically cleaner way to do this. In a multithreaded environment, I’d put the entire menu subsystem into its own thread. I’d put this thread to sleep every time it gets to the point of waiting for user input and only wake it up again when the next button is pressed. It would start up from the same point, service the button press, change states, and then go back to sleep. I’ve seen a couple examples in the forums that purport to simulate multithreading. But, they all rely on the threaded function finishing to completion and then returning. That doesn’t work for this situation because of the need to save a lot of state information.

So, I’d be interested in hearing about any clever techniques others have developed.

Thanks.

Greg

Sounds like you'd want to use something more powerful than a 16 MHz, 8 bit microcontroller. Try out Raspberry Pi with an Arduino hat to handle the digital io. The best of both worlds.

ChrisTenone:
Sounds like you'd want to use something more powerful than a 16 MHz, 8 bit microcontroller. Try out Raspberry Pi with an Arduino hat to handle the digital io. The best of both worlds.

It's 32-bit ESP8266 running at 80 MHz. So, processing power isn't the issue.

Depends on how you are saving the menu hierarchy data. Could you use a stack based structure where you 'push' the current state to save? Unwinding is then a matter of popping off the stack. The stack is of finite size and you know what it is (max menu depth).

An alternative is to just simplify the menu structure.

gfvalvo:
It's 32-bit ESP8266 running at 80 MHz. So, processing power isn't the issue.

I missed that.

[the menu system] structure is complex and deep enough that I'd have to save a lot of state information if I were to simply return to loop()
:
In a multithreaded environment, I'd put the entire menu subsystem into its own thread.

I'm not sure what's wrong with "saving a lot of state information." In the multi-threaded environment, you'd still have all the menu state "somewhere", PLUS all the state needed to implement the separate thread. I guess a thread could have essentially a relatively-invisible "recursive descent" parser to it that would be a bit easier on the programmer...

I don't know if anyone has done a multi-threading library for the ESP8266; it might interfere with the multi-tasking that is already going on to service the wireless/etc...

ChrisTenone:
I missed that.

No, my bad, I neglected to put it in the original post.

Just focusing on the title

Several Things at Once, But Multiple Threads Could Work Better

I can see how Threads might possibly make the program easier for the programmer to understand. But they would make life harder for the Arduino because of all the extra code needed to manage the Threads. But, of course the programmer would also have to write and test the Thread management code as well.

What would be wrong with code in loop() that is like this (which seems very close to the concept of Threads without the complexity)

void loop() {
   doMenu();
   doOtherStuff();
}

...R

Trouble is, as currently implemented, doMenu() does not return until the user has selected 'Exit' from the top-level menu. That way, the menu subsystem doesn't have to save where it is in the menu hierarchy (i.e. its state) while it waits for the next user input.

I've already considered the stack idea proposed by @marco_c and may go that route.

gfvalvo:
Trouble is, as currently implemented, doMenu() does not return until the user has selected 'Exit' from the top-level menu.

That's the problem. Rewrite the menu so that it doesn't block like that and you have no problem.

Delta_G:
That's the problem. Rewrite the menu so that it doesn't block like that and you have no problem.

Thanks Captain Obvious. Point being, I was looking for a way to do that without building a state machine with dozens of possible states to remember where the user was in the menu hierarchy, what is currently displayed on the LCD screen, the path to wind back up through the hierarchical structure upon exiting the lower levels, etc.

Couldnt you store for each level of menu heirarchy, the parent menu id?

eg Main: Settings, run, blah blah

Settings: Units, Display, blah, blah, [Main]

?

You need to read up on Finite State Machines, and implement your UI as one. No matter how complex the UI, multi-tasking is unnecessary, and if processor power or memory are issues, you've done something horribly wrong. I have UIs with dozens of menus, some several levels deep, and each level has it's own state machine, so the whole thing is very simple, very manageable, and easy to maintain and extend, since the code for each menu is rarely more than a page or two long, and all follow a common structure, which supports call and return, programmed delays between states, timeouts on any/all states, etc.

Regards,
Ray L.

RayLivingston:
and if processor power or memory are issues, you've done something horribly wrong.

Never said it was.

I think your approach is the best and the most Arduino-ful.

I was looking for a way to do that without building a state machine with dozens of possible states ...

It wouldn't be so bad, designed ground-up to be non-blocking. I assume the current code is essentially using the call stack, with one level of menu calling a submenu, and it calling another submenu, etc, and finally getting some numeric argument or something, and then popping back up and out. Rather than using the normal call stack, you could build a separate stack structure into the saved state for the menu (I assume this is the same thing that Marco is talking about.)
This would replace the calls to sub-menus with something like do_submenu(submenu_name), and the "many states" would be handled pretty invisibly.

I've noticed that a lot of people get bogged down in overcomplicating menus. First thing they get wrong is that they think that there need to be levels of menus and submenus. Sure the end product may be like that, but each individual menu is just a 1 dimensional list of possibilities. You can keep track of the whole state with one integer, which selection is active. If one selection takes you to a different menu then so be it, but there's no reason that anything from the "parent" menu need to be involved or saved there. You know the state of the parent that could send you into the child therefore you don't need to save the state of the parent on going to the child because it's already known at that point.

The second thing is waiting. Don't wait around for things, especially humans. Humans are slow. Check on them occasionally and keep on going about the rest of your jobs.

All the menu code needs to be able to do is:

  1. Check for new input, like a button press and take whatever action needed for that
  2. Display the menu to whatever display device
  3. Return so that next time it gets called it can check for input again.

If there is some ton of information that needs to be saved between calls to your menu then you either have your menu put together in a horribly inefficient way or you need to add some context and perhaps some code to this question so we can see why that isn't the case for you.

westfw:
It wouldn't be so bad, designed ground-up to be non-blocking. I assume the current code is essentially using the call stack, with one level of menu calling a submenu, and it calling another submenu, etc, and finally getting some numeric argument or something, and then popping back up and out. Rather than using the normal call stack, you could build a separate stack structure into the saved state for the menu (I assume this is the same thing that Marco is talking about.)
This would replace the calls to sub-menus with something like do_submenu(submenu_name), and the "many states" would be handled pretty invisibly.

Yes, I'm heading toward the technique that you and @marco_c suggest. Thanks.

Make up a set of UI objects that can hold their own state and call for actions. Put as much of the intelligence into a base class and let them take the load of watching the user.

Well, that and a base idler class is how I do it.

-jim lee

Yep, the current state could be implemented as

  • a menu stack (FIFO), where selecting a submenu pushes, and exiting a submenu pops (marco_c)
  • a “current menu” that points to a static menu structure with a parent menu and children submenus (Delta_G)
  • a “current state” in a FSM (a graph), with transitions to other states (menus) based on events like time or user input (RayLivingston)

The first two usually implement a pure tree of menus. The last one can implement a graph of menus, where the transitions are not just top-to-bottom, but may also be sideways. This may also require the “breadcrumbs” stack, to find your way back along the path you came.

You might try drawing a diagram of each approach to see which one matches your mental model, or to see which one is the easiest for you to implement. This sort of exercise almost always improves the system. Drawing a diagram may reveal cases you never considered, and implementing the stack/tree/graph may make your UI more consistent.

Cheers,
/dev