A couple of months ago, I bought a not very new KTM 250EXC motorcycle, unscrewed the throttle stick, pulled the moth into the sky, and sat on my ass and broke something in my back. As a result, the motorcycle does not sit down for at least two months. Why am I doing this? Yes. A slightly tired moped had a faulty dashboard and I got ready to make a new homemade one while I was at home.

Quickly assembled the layout, tsiferki run, watch go, odometers are remembered in FRAM - beauty, but ... buttons were needed to control this beauty.
')
Today I will talk about the buttons, then about the ignition sensor, and only then about the tidy itself, OK?
It is easy to draw on a 16x2 Chinese screen via i2c, the speed sensors and motor revolutions have become external interrupts, the temperature is read from the analog port, the info is stored in the FRAM, and the Chinese watch is also stuck. All this is spinning asynchronously, something like
SmartDelay , about which he wrote recently here.
Yes, the buttons!
Making one button to slow down the blinking of the LED turned out to be easy, like other toys. It’s impossible to stick a huge keyboard to the enduro motorcycle dashboard, there’s no room. I had to break my head and confine myself to four buttons:
- Mode
- Up
- Way down
- OK / Reset
To fit into this menu and management, it is necessary to recognize tyk, tykyk and tyyyyk. That is, pressing the buttons of different duration. I wrote a big footwoman from switch and if, I realized that I could not read it in a couple of months and took up the advantages again.
The task turned out to be similar to
the SmartDelay library :
- Maximum hide the code in the library.
- The button processing code should not interfere with the programming "on the case."
- It should be possible to use elsewhere in other follow-up projects.
- It must be beautiful or something.
If you know different things, please tell us in the comments what you are using.
At first I drew a paper finite automaton. With a touch did not work, without paper.

Then I googled that instead of switch / if you can make a sign. I last addressed the topic of the
ICA about 30 years ago, it took to refresh the theory.

As a result, I gave birth to the abstract class
SmartButton . This creation hides the ICA inside itself, listens to digital ports and pulls empty abstract methods on click, hold and long hold. To use this class, you need to create your own and override the necessary methods.
#include <SmartButton.h> byte menuMode = 0; // SmartButton class modeSmartButton: public SmartButton { public: modeSmartButton(int p) : SmartButton(p) {} virtual void onClick(); // virtual void offClick(); // , . }; // : . void modeSmartButton::onClick() { Serial.println("Key pressed."); if (menuMode) { Serial.println("Menu mode off."); } else { Serial.println("Menu mode on."); } menuMode^=1; } // . . void modeSmartButton::offClick() { Serial.println("Key depressed."); } // , 6 . modeSmartButton btMode(6); void setup() { Serial.begin(9600); Serial.println("Ready"); } void loop() { btMode.run(); // loop(). }
It is clear that the code is a little bit, everything is more or less clear. There are no callbacks directly described like this. In loop () there is only one call to run () for each button, somewhere the class and the button itself are defined. It is possible to create, the terrible stairs of the ICA for the processing of pumpkins of buttons with style C do not interfere.
Let's look at the code. The whole project lies on the
githaba .
Without inventing anything better, I made the settings for time intervals available outside. Here, respectively, are the delays for a click, a hold, a long hold, and so long that you should ignore such a click at all. In SmartButton.h, define these constants carefully so that they can be redefined before #include.
#ifndef SmartButton_debounce #define SmartButton_debounce 10 #endif #ifndef SmartButton_hold #define SmartButton_hold 1000 #endif #ifndef SmartButton_long #define SmartButton_long 5000 #endif #ifndef SmartButton_idle #define SmartButton_idle 10000 #endif
The states and impacts I made as enum in particular, and because the numbers of StatesNumber and InputsNumber automatically received them.
enum state {Idle = 0, PreClick, Click, Hold, LongHold, ForcedIdle, StatesNumber}; enum input {Release = 0, WaitDebounce, WaitHold, WaitLongHold, WaitIdle, Press, InputsNumber};
Slightly breaking his head, I painted this type. This is a pointer to a method of this class. Do not laugh, the pros somehow passed me by, I am not a master in them.
typedef void (SmartButton::*FSM)(enum state st, enum input in);
Here I had to tinker. This is a conversion table. There was a fuss with references to methods, how to write them so that the compiler did not swear and the references were to methods of a particular class instance. Not on a static method, not just a left function, but on a method, so that it has access to private class variables.
FSM action[StatesNumber][InputsNumber] = { {NULL, NULL, NULL, NULL, NULL, &SmartButton::ToPreClick}, {&SmartButton::ToIdle, &SmartButton::ToClick, NULL, NULL, NULL, NULL}, {&SmartButton::ToIdle, NULL, &SmartButton::ToHold, NULL, NULL, NULL}, {&SmartButton::ToIdle, NULL, NULL, &SmartButton::ToLongHold, NULL, NULL}, {&SmartButton::ToIdle, NULL, NULL, NULL, &SmartButton::ToForcedIdle, NULL}, {&SmartButton::ToIdle, NULL, NULL, NULL, NULL, NULL} };
All methods were declared as private, and in public there were only run () and empty stubs to override in the generated classes.
inline virtual void onClick() {}; // On click. inline virtual void onHold() {}; // On hold. inline virtual void onLongHold() {}; // On long hold. inline virtual void onIdle() {}; // On timeout with too long key pressing. inline virtual void offClick() {}; // On depress after click. inline virtual void offHold() {}; // On depress after hold. inline virtual void offLongHold() {}; // On depress after long hold. inline virtual void offIdle() {}; // On depress after too long key pressing.
I use the pinMode mode (pin, INPUT_PULLUP) because the scheme is compiled for this, but in the near future I am going to add the ability to select the mode.
The run () method simply translates time intervals to spacecraft input effects.
void SmartButton::run() { unsigned long mls = millis(); if (!digitalRead(btPin)) { if (btState == Idle) { DoAction(btState, Press); return; } if (mls - pressTimeStamp > SmartButton_debounce) { DoAction(btState, WaitDebounce); } if (mls - pressTimeStamp > SmartButton_hold) { DoAction(btState, WaitHold); } if (mls - pressTimeStamp > SmartButton_long) { DoAction(btState, WaitLongHold); } if (mls - pressTimeStamp > SmartButton_idle) { DoAction(btState, WaitIdle); } return; } else { if (btState != Idle) { DoAction(btState, Release); return; } } }
The private DoAction method (state, impact) simply calls a function from the table if there is an address there.
void SmartButton::DoAction(enum state st, enum input in) { if (action[st][in] == NULL) return; (this->*(action[st][in]))(st, in); }
Most actions look simple enough. There the state is simply set and an abstract method is called that can be redefined in the child class. This is such an analogue kolbek.
void SmartButton::ToClick(enum state st, enum input in) { btState = Click; onClick();
The bold handler turned out for the Idle state because it comes from various other states, and we wanted to make abstract methods for such events.
void SmartButton::ToIdle(enum state st, enum input in) { btState = Idle; switch (st) { case Click: offClick(); break; case Hold: offHold(); break; case LongHold: offLongHold(); break; case WaitIdle: onIdle(); break; } }
With such a tool, I am ready to generate classes for the display mode selection buttons mentioned at the beginning of the article, the navigation up and down, the overloaded selection / reset button.
It is clear that I face another SC, much more complex. Buttons are few, but many actions. If interested, I will write next time as an example of real practical application of the library that has just been described.