Menus - but not for Arduboy

I’m trying to get better at coding menus, as it seems like a pretty useful thing to be better at in general. To start with, I’ve read around the web about state machines, lookup tables vs switch case and nothing makes as much sense as the advice that gets offered on this forum.

My application is a temperature sensing device which has only two buttons for input (space constraints) and an oled for an output (not a SH1107 and not on my own PCB!). Ideally it’d use a small microcontroller, think ATtiny, so compiled code size is important to keep small. Speed is not important at all. Readings will be around 1Hz and the menu only needs to avoid annoying levels of lag so refresh rate can be 10Hz or perhaps even slower.

The main display modes are 1) digital readout of the current temperature or 2) a graph of a rolling window of readings or, 3) an optional countdown to an estimated time when a cooling body will reach a target temperature. There are also visual alarms when a reading is over or under an upper or lower limit, set by the user. I need a menu to set all this and so far this is what I have come up with:


I thought that incrementing individual digits of the 3 digit numbers was a faster way to set the target temperatures than incrementing a single number by 0.1 degree, and the code to do this, convert the values into a single float could be reused by three submenus.

I have seen the likes of @Pharap encourage enum classes in the context of menus before and my general impression is that C++ leads to less code than C to implement a menu but has anyone got any tips, tutorials, tricks or great examples of how to go about making a very lightweight menu system? I’m inclined to trust the compiler to optimise but I don’t want to ask it to do anything unnecessary (and most of the other menu libraries I’ve scanned, such as for LCD character displays, are a bit bloated for what I think I need).

I published my schematic not so someone might “do my homework” but it will hopefully give you a steer on the degree of complexity I’m aiming for. I realise it’s not showing the alarm state and doesn’t show the menu items being incremented by button2 but hopefully its enough to help you help me!

Thanks

In my mind, this is just a big state machine where each state saves its own state before progressing to the next or reverting to the ‘Display Digits’ if a timeout occurs. Even the tens / ones / tenths for the three values that use them can be considered a state in their own right or as a ‘mode’ in a single state as I assume you are going to write a change to a ‘tens’ unit out as soon as the user clicks the button rather than require them to edit the three components (tens, units and tenths) before saving.

I would create an enum class of state values and have a big dirty switch for the various states - as per @Pharap’s post.

1 Like

That’s a good decision to think about. I think I’d rather let this be decided by the compile code size of converting the tens, ones, tenths to a float. It may be more efficient to say take temperature readings and then convert them to three bytes representing tens, ones and tenths. This would perhaps make parsing for screen print easier. Although I haven’t compared.

No love for the array / LUT of pointers to the required functions? I appreciate with only three events I can think of (button1, button2, timeout), it might be a close run with the switch case.

Also, am I right in thinking that it is good practice to separate into three activities, e.g. handle input (maybe button debouncing), update variables (e.g. increment current menu variable) and update display? Or are there other good ways of breaking up menu management code?

Also, this article makes me think that there may be a really lightweight way of doing this without the switch case in C++ but it’s way over my head at this stage. This is the paragraph that makes me optimistic:

Incidentally, the smallest, fastest, and arguably the most natural technique for implementing state machines in C/C++ isn’t widely publicized (although I suspect it’s in every pro’s bag of tricks). The technique hinges on pointers-to-functions in C (and pointers-to-member-functions in C++).

They also have a very basic worked example that also makes me think it would be achievable by novices like me.

I looked at the pointers-to-functions trick early on and found it took more memory than a direct call. Your mileage may vary.

1 Like

Essentially the reason state machines are useful is because they model a mutually exclusive set of states,
and often simple menu systems (like the kind you’d find on a watch, a digital clock or an electronic toy) usually involve each menu being a mutually exclusive state.

Also if your menu system is modelled as a state machine then it’s easy to express the design with a state diagram, which makes designing it easier and makes it easier to express the design to other people.

Lookup tables and switch statements are just two possible implementations of a state machine,
and even then, there’s more than one approach to each technique.

A switch statement is by far one of the simplest and cheapest methods.
They’re not used so much in modern desktop programs because they don’t scale well,
but for embedded systems, especially systems that don’t need to be updated often, switch statements fit the bill quite well.

It depends on how you choose to implement it.
For a lot of simpler systems the code would be very similar,
but C++ would make more complex systems simpler to implement.
C++ has other benefits though, like improved type safety.

My recommendation to use scoped enumerations (enum classes) is as much about type safety, error prevention and code readability as it is about simplicity or memory contraints.

It would be easier to provide direction if we had a better idea of the spec of your setup.
In partciular CPU architecture, RAM size, ROM size and screen dimensions.

Having only 2 buttons is already a pretty crazy constraint.

If you genuinely need this thing to be as tiny as possible,
you might want to seriously consider not using floats at all.

If a chip doesn’t have an FPU then floats have to be simulated in software,
and that takes a fair chunk of memory.
(On the Arduboy it’s about 2-3% of progmem (about 1KB) at least.)

It depends on the architecture (among other things).
On AVR an array of function pointers tends to use more memory than a switch statement (as @filmote notes) .
(There’s various reasons for this, but it’s at least partly because the switch statement permits function inlining.)

It is good practice,
but it can also sometimes use more memory than necessary depending on how it’s done.
(Again, it’s hard to recommend anything without knowing what chipset you’re using.)


This is mainly about event driven systems, which you probably can’t afford and would be a waste of time on a 2-button system anyway because you’d only ever have about 3 or 4 events.

Also if you had a hard time making sense of the C code in that article,
don’t worry, you’re not thick, it genuinely is written in quite a cryptic way (as C often is).

I happen to know of a GCC compiler extension that can produce less code than a switch statement on AVR, but I’d only suggest using it if you really were on the bones of your arse in terms of memory usage and were confident that you’d only ever need the code to run on this one chip and never on anything else.

This is using virtual functions and inheritance,
which is a very easy way to implement a state machine,
however (on AVR at least) they chew up a fair bit of memory so you probably can’t afford to do that.


Ultimately, without knowing more details of your setup,
my advice would be to stick to a switch-statement state machine,
representing the state identifiers as a scoped enumeration.

In fact looking at your diagram you might even want a 2-tier system (i.e. 2 swtich statements),
or even a 3-tier system (with tier 3 being your number entry system - you’re right to notice that the redundancy there is a potential opportunity to reuse code).

Also, don’t underestimate the possibility of having a list of options.
You can have one button for ‘next option’ and one button for ‘select option’.


As ever, if you want some more hands-on help or just have any questions (technical or otherwise) I’d be happy to oblige.

1 Like

I’m looking at the ATtiny 0 and 1 series. I’m probably being overly dramatic about the “lightweight” as the tiny1614 has 16kB flash, 2kB SRAM and 256B EEPROM. But I figured I’d use this as an exercise to educate myself and it seems to be working! Screen is 128x32 atm and it’s only likely to get as big as 128x64.

Fun, eh!

I’m keen on this. Will give it a crack.

Well let’s keep that in the back pocket for extra credits but I think I have enough to be getting on with. Thank you.

This would be what I see referred to as nested switch statements and would result in treating each tier as submenus?

Of course, I should have thought the buttons can change between select and increment depending on the menu mode/level.

I’m so grateful, thank you. I will try something and post here, if people don’t mind (I think I’m sticking to the spirit of the category).

1 Like

That’s still plenty. I was expecting something like only 1KB of RAM to work with.

(For comparison, the ATtiny11 has 1KB flash, no SRAM, no EEPROM.
At which point I’d have been handing you an assembly programming guide. :P)

In which case you’ll probably be able to afford a full frame buffer, which should make life easier.
(Just make sure to leave at least about 256 bytes for the call stack.)

Naturally you’ll need some way of representing fractional values (be it actual fractions or with alternative radix point formats),
which is something I can probably help with as I have experience with fixed point arithmetic.

More or less, though you wouldn’t be having the switches directly inside each other as that example shows.
The ‘inner’ switch statements would most likely inside other functions which the outer switches would call.

I think so at least.
It’s still Arduino and it’s most likely going to be generalisable to the Arduboy.
In fact if the Arduboy had a temperature sensor your (currently theoretical) system could probably have been ported to it.

1 Like

I believe the ATMEGA32U4 does have an internal temperature sensor. Although not easily usable in this application.

So I have put the enum class together and prepared a switch for it:

enum class Options : uint8_t
{
  digits,
  graph,
  countdown,
  units,
  alarmOnOff,
  alarmLoLimTens,
  alarmLoLimOnes,
  alarmLoLimTenths,
  alarmHiLimTens,
  alarmHiLimOnes,
  alarmHiLimTenths,
  coolDownOnOff,
  coolDownTens,
  coolDownOnes,
  coolDownTenths
};

Options o = Options::digits; // initialise the first state

and the switch:

  switch(o)
  {
    case Options::digits:
      // code to display current reading in digits
      break;

    case Options::graph:
      // code to display graph of current and past readings
      break;

    case Options::countdown:
      // code to display countdown estimate to target temperature
      break;

    case Options::units:
      // code to toggle between Centigrade and Farenheit
      break;

    case Options::alarmOnOff:
      // code to toggle between on and off
      break;

    case Options::alarmLoLimTens:
      // code to increment and select the tens unit of low limit temperature
      break;

    case Options::alarmLoLimOnes:
      // code to increment and select the ones unit of low limit temperature
      break;

    case Options::alarmLoLimTenths:
      // code to increment and select the tenths unit of low limit temperature
      break;

    case Options::alarmHiLimTens:
      // code to increment and select the tens unit of high limit temperature
      break;

    case Options::alarmHiLimOnes:
      // code to increment and select the ones unit of high limit temperature
      break;

    case Options::alarmHiLimTenths:
      // code to increment and select the tenths unit of high limit temperature
      break;

    case Options::coolDownOnOff:
      // code to toggle between on and off
      break;

    case Options::coolDownTens:
      // code to increment and select the tens unit of cooling target temperature
      break;

    case Options::coolDownOnes:
      // code to increment and select the ones unit of cooling target temperature
      break;

    case Options::coolDownTenths:
      // code to increment and select the tenths unit of cooling target temperature
      break;
  }

Is this the sort of thing we’ve been talking about (for a single level approach)?


EDIT: so if we can’t increment through an enum class the progression through options seems like it would need to be done similarly to this example in the thread linked in the OP, which is @Pharap’s suggestion:

Menu & operator++(Menu & menu)
{
	menu = static_cast<Menu>(static_cast<uint8_t>(menu) + 1);
	return menu;
}

Menu & operator--(Menu & menu)
{
	menu = static_cast<Menu>(static_cast<uint8_t>(menu) - 1);
	return menu;
} 

Based on the proliferation of terms/uses of the word Menu / menu (and my lack of expertise in C++) in that thread, I’m confused about how it’s applied to my example. Please could I have a steer/hint?

1 Like

wait, I think I have it!

o= static_cast<Options>(static_cast<uint8_t>(o) + 1);
return o;

…but I don’t understand the wrapper around the function involving the ampersand and operator++ etc


…so we’re defining our own “type safe” operators?


…sorry if this is spoiling parts of your reply.


And finally,

Options & operator++ (Options & o)
{
    o = static_cast<Options>(static_cast<uint8_t>(o)+1);
    return o;
}

Allows me to o++ with impunity!

Indeed it does, but as you say it’s of little use for the Arduboy.
Also an on-chip temperature sensor is usually going to be higher than an off-chip sensor simply because of the heat the chip itself generates by running.

Yes.

I’d be tempted to recommend some name changes,
but functionally everything’s correct.

By default enum classes only have equality (==), inequality (!=) and assignment (=) operators.

But that’s a feature, not a bug.

The idea is to allow the programmer to choose which operators should be implemented because different enumerations may represent different concepts.
E.g. in some cases the bitwise operators (^, &, | et cetera) may make sense, in other cases you may want increment (++) and decrement (--) and in other cases you may just want a named free-function.

In the example code I used the -- and ++ because the code I was adapting already relied on being able to use those operators.
(In fact, looking back at it now I didn’t actually need to add the ++ and -- for Menu, only for MenuChoice.)

Having an increment/decrement facility is only really useful if your states will always (or at least usually) have a forwards-backwards relationship.
Even then you may be better off just doing a manual assignment.

Usually I don’t bother with ++ and -- for enums representing states.
For something like compass directions I would use that technique for progressing the directions (e.g Direction::North to Direction::NorthEast, then to Direct::East),
but even then I’d prefer to use a named function (e.g. turnRight).

I’m not sure what you mean by wrapper, the whole thing is a function.
Operators are functions. (Or at least the ones that don’t operate on the fundamental/built-in types are.)

The & is because it’s actually a reference to a Menu object.
References are a safer, easier alternative to pointers.
I can explain those if you want, but you probably won’t need them at the moment.

I only needed them in that example because of how ++ and -- work.
I.e. if you write ++a then you expect a to change, you don’t expect to have to write a = ++a;

Who is this ‘we’ you speak of? :P

But yes, in the example case I was creating type-safe operator overloads.

Edit:
(Also it’s nice to see a tutorial that demonstrates an application of SFINAE.
Though that static const should be static constexpr and I frown at the lazy macro usage.)

Actually it allows you to ++o, if you want to o++ then you have to write a second operator ++. :P


In case you genuinely need to, prefer to use these functions:

inline Options next(Options option)
{
	return static_cast<Options>(static_cast<uint8_t>(option) + 1);
}

inline Options previous(Options option)
{
	return static_cast<Options>(static_cast<uint8_t>(option) - 1);
}

Note that the return value must be captured as these functions don’t modify their arguments.
E.g.

Options nextOption = next(currentOption);
Options previousOption = previous(currentOption);
option = next(option);

Also note that the following laws are true:

  • option == next(previous(option))
  • option == previous(next(option))

Essentially these functions work by converting the enumeration to its underlying integer representation (uint8_t), doing an integer increment/decrement (i.e. adding/subtracting 1) and then converting back to the enumeration type.

Note that there’s absolutely no bounds checking involved,
so previous(Options::digits) and next(Options::coolDownTenths) will lead to undefined behaviour.
If you’re willing to declare what should happen in those circumstances (e.g. wrapping around or clamping/saturating) then I can provide you with the code to do so.


But honestly, you’re probably fine just doing o = Options::graph when someone presses button1 and you’re in the Options::digits case.
At least one of your transitions (the timeout) isn’t going to be a linear transition anyway.

1 Like

I’m all ears - I realise o isn’t great. The others?

As in making the o = static_cast<Options>(static_cast<uint8_t>(o)+1); part of the button handling functions? Or something else?

me neither. But I managed to adapt it.

Even knowing what it’s called will be helpful - then I can try and learn before asking for more help. Thanks.

That’s not the behaviour I observe when I print o cast to uint8_t and increment it with o++ and print again. It doesn’t ask me for a ++o.

Prefer over o++ and o--? Other than the name changes, does the inline help reduce compiled code size? And what are the other benefits? I note there’s no reference & involved.

I hope to get around to this at some point. I think wrapping is best.

For a two and three tier system, would we be looking at a separate enum class for each level or does something else dictate what number of enum classes we have?

I forgot to say, I would like to get this into a state diagram but I’m still digesting them. I don’t see how they handle multiple events particularly well without getting into a big mess of untraceable lines. I’m probably thinking too much flow chart and not enough state diagram…

Yeah, that should ideally be something like menuState or currentState.

Two important things:

  • Enumerations should be singular when they represent a single value and plural only when they represent a bitset/a group of values (i.e. Option, not Options)
  • Option isn’t a particularly descriptive name, something like MenuState would be better

One minor thing:

  • Using contractions like ‘Hi’, ‘Lo’ and ‘Lim’ generally leads to poor readability

(Also, personally I prefer to have my enumeration values use Pascal case rather than camel case, but that’s pure preference, there’s no real logic behind it.)

Bog standard variable assignment.
I.e. o = Options::digits;, o = Options::graph;.

When it comes down to it, they’re likely to be as cheap as each other,
both in terms of memory and CPU cycles,
but doing the o + 1 approach forces you into making sure your state enumerators are all linearly progressive (i.e. the next state is always + 1 from the current state) which seems like an unnecessary and perhaps even unhelpful constraint.

You know which state you want to swtich to next, just say it by name.

https://www.learncpp.com/cpp-tutorial/611-references/

(You can skip the l-value and r-value stuff unless you’re planning to try to become a C++ pro and/or start programming for chips with enough RAM to support dynamic allocation.)

Example code?

As far as I’m aware it ought to, I might have to check the rules,
either there’s a clause about generating a post-increment that I haven’t encountered or there’s some compiler extension shenanigans going on.

Yes.

That’s a bit of a minefield of a question.

Technically speaking inline is supposed to just be about linkage.
(Internal linkage vs external linkage.)

Historically it was a hint to the compiler that “I would like this function inlined please” (the history of how it came to mean this is a tale in itself - early compilers were stupid and messy), but it’s never been a guarantee.
Whether it’s actually still used as a hint or not is a moot point,
but usually the compiler can do a better job at knowing what should be inlined than a human can.

(Humans have a tendancy to think every function is a good candidate for being inlined as if it’s some magic trick that will always make the code faster.)

The bottom line - use it for linkage reasons only.
In terms of linkage you should use inline if (and only if) you plan to define a function in a header.

Improved readability mainly.
‘Incrementing’ and ‘decrementing’ a state doesn’t really make much sense,
but getting the ‘next’ and ‘previous’ states do.

(Though ‘next’ and ‘previous’ are typically relative to which state you’re current in and which event has occurred, so even that is of limited usefulness.)

Yes, that’s because functions that modify their arguments are generally frowned upon because they’re suprising and thus (non-const) reference should only be used in certain specific cases.

Avoiding references might result in some space saving,
but that would depend on the compiler’s decisions.
Depending on the order that the compiler decides to do things in,
it’s likely to end up seeing more or less the same set of operations for both approaches.

Again, I think just manually saying “I would like to transition to state X next please” is better than messing around with trying to force an arithmetical progression.

“Explicit is better than implicit.”

The only reason I can think for enforcing such a progression is when you have a single linear path through all states,
or when you want to be able to do a range check on states for some reason.

A separate one for each level would be simplest.
You’d essentially be looking at state machines within state machines.
(“Hierarchical state machines.”)

That’s not as crazy as it sounds though, it’s quite common in video games.
E.g. you often have the high level state machine of splashscreen -> titlescreen -> gameplay,
and then the gameplay state has one or more state machines within itself.
(My Minesweeper game does precisely this.)

Your original image wasn’t far off being a state diagram.

Essentially all you need are circles representing states and arrows representing events.

I found this state diagram of a stopwatch that might help:


(Borrowed from here, which also demonstrates a state machine,
but does so in more horrendous C code that I can’t recommend copying.)

And here’s one demonstrating hierarchical states:


(I think it’s supposed to be a traffic light.)

You can make the arrows different colours. :P

Flowcharts have a similar problem in terms of having lots of arrows.

The main difference between a flow chart and a state diagram is that a state diagram is a higher-level system.
A flow chart shows the exact flow of program execution.
A state diagram is just states, transitions and the events that cause those transitions.

1 Like

Lovely, thank you.

Yes, I see.

Well it’s not a minimal example but this increments o in the serial monitor on an Arduino Nano (168P) with Arduino IDE 1.87:

Summary
/* Menu Test for memory-limited microcontrollers
    This aims to see how much memory is required to implement different techniques of providing a menu system
   Changelog:
    - V01 changes some names for better readability and removes conditional compilation
*/

uint8_t button1Pressed = 0;
uint8_t button2Pressed = 0;

constexpr uint8_t button1 = 2;
constexpr uint8_t button2 = 3;
constexpr uint8_t led1 = 4;
constexpr uint8_t led2 = 5;

enum class Options : uint8_t
{
  digits,
  graph,
  countdown,
  units,
  alarmOnOff,
  alarmLoLimTens,
  alarmLoLimOnes,
  alarmLoLimTenths,
  alarmHiLimTens,
  alarmHiLimOnes,
  alarmHiLimTenths,
  coolDownOnOff,
  coolDownTens,
  coolDownOnes,
  coolDownTenths
};

Options o = Options::digits; // initialise the first state

void setup() {
  Serial.begin(115200);
  // set up real buttons and state LEDs
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
}


void loop() {
  switch (o)
  {
    case Options::digits:
      // code to display current reading in digits
      break;

    case Options::graph:
      // code to display graph of current and past readings
      break;

    case Options::countdown:
      // code to display countdown estimate to target temperature
      break;

    case Options::units:
      // code to toggle between Centigrade and Farenheit
      break;

    case Options::alarmOnOff:
      // code to toggle between on and off
      break;

    case Options::alarmLoLimTens:
      // code to increment and select the tens unit of low limit temperature
      break;

    case Options::alarmLoLimOnes:
      // code to increment and select the ones unit of low limit temperature
      break;

    case Options::alarmLoLimTenths:
      // code to increment and select the tenths unit of low limit temperature
      break;

    case Options::alarmHiLimTens:
      // code to increment and select the tens unit of high limit temperature
      break;

    case Options::alarmHiLimOnes:
      // code to increment and select the ones unit of high limit temperature
      break;

    case Options::alarmHiLimTenths:
      // code to increment and select the tenths unit of high limit temperature
      break;

    case Options::coolDownOnOff:
      // code to toggle between on and off
      break;

    case Options::coolDownTens:
      // code to increment and select the tens unit of cooling target temperature
      break;

    case Options::coolDownOnes:
      // code to increment and select the ones unit of cooling target temperature
      break;

    case Options::coolDownTenths:
      // code to increment and select the tenths unit of cooling target temperature
      break;
  }
  buttonCheck();
  Serial.println(static_cast<uint8_t>(o));
  delay(100);
}

void buttonCheck()
{
  button1Pressed = !digitalRead(button1);
  button2Pressed = !digitalRead(button2);

  if (button1Pressed)
  {
    Blink(led1);
    Serial.println("Button 1 pressed");
    o++; // can we increment o?
    button1Pressed = 0;
  }
  if (button2Pressed)
  {
    Blink(led2);
    Serial.println("Button 2 pressed");
    button2Pressed = 0;
  }
}

void Blink(uint8_t led)
{
  digitalWrite(led, HIGH);
  delay(100);
  digitalWrite(led, LOW);
}

Options & operator++ (Options & o)
{
  o = static_cast<Options>(static_cast<uint8_t>(o) + 1);
  return o;
}

Options & operator-- (Options & o)
{
  o = static_cast<Options>(static_cast<uint8_t>(o) - 1);
  return o;
}

Thanks. And to that end, do you think default is helpful in switches?

Bingo, I stumbled on HSMs this morning and I think that’s what would solve what I imagine would be the spaghetti mess of wires if an FSM tried to document menu systems of even minor complexity. Incidentally, I’m surprised that traffic light systems (and bank ATMs?!) are among the most common examples given of state machines / state charts/diagrams. I just can’t imagine someone needing to design an ATM or pedestrian crossing as much as they’d need to implement even a very simple menu. And then examples could be extended to show how additional options and submenus are added. It’s not like they ever extend the traffic light example to show e.g. how a whole junction is controlled. A gap in the market but not for me to fill!

It is very helpful. I suppose what I’m struggling with is which parts of my imagined system translate to states and events/transitions. For example is display graph a state and, if so, is it a state that is distinct from display digits if they are mutually exclusive in that they both would use the same pixel-estate? And is button1Press an event or is menuSelected the event (which would happen to be changed by a button press…)? Their menu is pretty small.

When you use enums with switches, the compiler will give you a warning if you do not cover all possible enum values in the switch. This is a nice feature that you (obviously) do not get when you just use an integer to represent state. Some switches though may not need to enumerate all values and the use of a default (even if it just breaks) can remove that warning.

2 Likes


So I think this is getting closer to a state chart. I’m still not sure about what element is what (ie is a menu option a state?). The lines from states in the menuState superstate to the toggleInputState and numberInput “substates”(?) are messy and I wonder if in reality they’d be separate substates of e.g. Units menu shown a layer down. Then just reuse functions when it came to coding them.

And the return events/transitions to the next menu item are messy looking. Wonder if that means they’re not a good idea. Just trying to save the user a few clicks.

So although this might be a three tier state machine/chart, would it likely need four enum classes: displayState, menuState, toggleInputState and numberInputState? Or is it a bit ridiculous to have an enum class for numberInputState (boolean) and maybe toggleInputState?

Helpfully, I found a menu example for an alarm clock in a lesson slide deck pdf, which is much closer to my application than a pedestrian crossing! Here’s an extract, which I know I’m not following, but was helpful in seeing how a menu might be represented and may be useful to others looking into menus.

1 Like

Yep, compiler voodoo as I expected:

I:\Documents\Arduino\sketch_apr18a\sketch_apr18a.ino:125:6: warning: no 'operator++(int)' declared for postfix '++', trying prefix operator instead [-fpermissive]

     o++; // can we increment o?

     ~^~

This code only compiles because of that god-awful -fpermissive flag that the Arduino IDE insists on using.
I.e. This is not legal C++.

The prefix/postfix dichotomy

Basically in C++, the prefix increment operator (++o) and the postfix increment operator (o++) are two different operators.
The latter is differentiated by an unused int parameter.
Also postfix (o++) tends to be implemented as either:

Type Type::operator++(int)
{
	// Create a copy of the object
	auto copy = *this;
	
	// Call the prefix increment operator
	this->operator++();
	
	// Return the copy
	return copy;
}

Or:

Type operator++(Type & object, int)
{
	// Create a copy of the object
	auto copy = object;
	
	// Call the prefix increment operator
	object.operator++();
	
	// Return the copy
	return copy;
}

This also applies to the decrement operator (--).

(By the way, make sure you have ‘Compiler warnings’ set to ‘All’ in the ‘Preferences’ dialogue.
It’ll help you a lot with deciding what you shouldn’t be doing.)

It depends.

On the one hand it can be useful for:

  • When there genuinely is a default behaviour for the logic in question, and the case statements effectively act as exceptions to the rule
  • Detecting errors in the program
    • I.e. when the default case should never be reached if the program is working correctly, signalling an error is better than silently failing

On the other hand, if you’re switching on an enumeration,
the compiler will warn you if there are any enumerator values that you haven’t handled, which can be quite useful.
Adding a default case effectively ‘handles’ all the states that don’t have specific cases.
(As @filmote also noted.)

(Really it’s a shame that adding the default disables that warning,
sometimes it would make sense to have a default but also still have the warning.)

I think they’re mainly chosen because they’re something everyone is at least somewhat familiar with.

A state diagram is a good way to get one’s thoughts in order and to realise what needs to be done at each state and each transition.

Precisely, most of the examples you’ll find online will be quite simplified.


Yes, it certainly looks more long the right lines.

I think in actuality ‘units menu’, ‘alarm on menu’ and ‘cool down on menu’ would effectively contain a copy of ‘toggle input state’ each rather than transitioning to/from ‘toggle input state’.
So effectively you’d have a copy of ‘toggle input state’ inside each of those other states.

(As I’ll demonstrate momentarily.)

It depends how finely you want to divide things.
The different ‘menus’ like ‘units menu’, ‘alarm menu’ et cetera are definitely states.
The actual input features like ‘toggle input state’ and ‘number input state’ could be argued either way.

More or less, yes.

I’m not sure quite which ones you mean,
but the messiness is probably as much to do with how things are presented.

Here’s an adapted version of your diagram that I’ve throw together:
States

In this case rather than drawing lines to and from the substates (as you have been doing),
the substates are nested within the other states,
and then each substate has its own diagram separate from the others.

This way the diagram ends up looking cleaner whilst still displaying the same information/relationships.
(This relationship is similar to the relationships between functions/subroutines.)

‘Toggle input state’ most likely won’t need an enumeration because it’s a very simple ‘on-off’ system.

‘Number input state’ is slightly more complex and has three distinct states,
so that distinction will have to be represented somehow.
There may be alternatives but an enumeration seems the most obvious option.

Whether ‘menu state’ and ‘display state’ should also use another enumeration probably depends on how ‘menu timeout’ is intended to work.
I would think that having the extra layer might make it easier to implement,
but that’s the kind of thing you probably won’t know until you start writing the code and try a few things out.
Best start by trying to be as close to your diagram as possible,
and if that doesn’t work out you can revise your decisions later.

Yes, good find.

Note that the ‘blink’ states would in fact involve a kind of boolean state system (flicking between ‘display’ and ‘do not display’ states), but that isn’t mentioned by the diagram, merely implied.
This is precisely what I mean about “It depends how finely you want to divide things.”.

Also note that on the following page they’ve gone ‘up a level’ to something more abstract.


Now that you have your diagram to guide you,
your next step should probably be to start writing some more code.
(Start by making an update function for each state and then see where that takes you.)

It doesn’t matter if you mess things up along the way,
you’ll probably have to revise your code and your diagram a few times as you realise new obstacles.

1 Like

I’m embarrassed at the effort you’re putting in to help. I wish that one could give more than a single like to a post! Bless you.

I’ve started writing code and so far am working through populating the cases. I’ve attached a pair of buttons to the ATMEGA168P and I’m using the serial monitor as the screen. So far down to setting the units. Will post when I’ve run down to the bottom. Still using the single enum class but will shift when I’ve worked the first version through.

1 Like

Honestly the typing took much longer than the diagram.
Though even then I didn’t do all the typing in one go, I typed the first half a few hours before the second.

I have a program called Dia that’s pretty good for making diagrams.
(Though they’ve since switched to SourceForge for hosting,
so I’d be reluctant to recommend it now.)


Honestly the best way to thank me is to just remember to give me a mention in the credits if you publish the end result.
(That and making sure you’ve learnt something of course.)


I’ll patiently await the results.

1 Like