Friday, May 22, 2020

ATOM Matrix: Advanced Button Usage

With only one user button, it seems to be impossible to build a full application on the ATOM Matrix! The purpose of this post is demonstrate one way of doing so, by making use of the available button events beyond the onRelease event.

The goal of this project is to build a menu system. Here are the desired properties of our application:

  1. The menu choices will be displayed as the digits 0 - 9
  2. The only action each of those choices will perform is to print the digit in the Serial monitor window
  3. When the button is pressed, the app "wakes up" and displays the current choice
  4. To advance to the next choice, release the button
  5. Advancing past choice 9 sets the choice back to 0
  6. To select the current choice, hold the button down for 2 seconds
  7. The action for the selected choice must fire only once - no repeated actions when we hold it down for more than 2 seconds!
  8. After the button has been released for 2 seconds, the display goes "to sleep"
  9. If the button has been released for 30 seconds, the current choice returns to 0
  10. Otherwise, the choice is unchanged

How do we implement this? We have all the concepts and tools from the previous posts in this series. Briefly, here is how we will implement each of the above requirements:

  1. Store the digits in a length 25 array, and use the drawArray to display these single characters
  2. Serial.println statements will do this
  3. Use the wasPressed event to "wake up" the device and turn on the display
  4. The wasReleased event will advance to the next choice
  5. Use modular arithmetic to cycle through the choices
  6. To select the current choice, use the pressedFor(2000) method
  7. Use a boolean to prevent pressedFor from firing if the button is held down for an extended time
  8. Use releasedFor(2000) to turn-off display
  9. Resetting the current choice to 0 is done using releasedFor(30000)
  10. The choice is unchanged otherwise, since we'll store it in a variable

Here is the code, in all its detail!

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
/*
 * Button04.ino
 * 
 * By: Mike Klepper
 * Date: 26 April 2020
 * 
 * Demonstrates how to build a menu system with just one button!
 * 
 * See post on patriot-geek.blogspot.com
 * for details
 */


#include "M5Atom.h"

int GRB_COLOR_WHITE = 0xffffff;
int GRB_COLOR_BLACK = 0x000000;
int GRB_COLOR_RED = 0x00ff00;
int GRB_COLOR_ORANGE = 0xa5ff00;
int GRB_COLOR_YELLOW = 0xffff00;
int GRB_COLOR_GREEN = 0xff0000;
int GRB_COLOR_BLUE = 0x0000ff;
int GRB_COLOR_PURPLE = 0x008080;

int colorList[] = {GRB_COLOR_BLACK, GRB_COLOR_BLUE};
int activeColorList[] = {GRB_COLOR_BLACK, GRB_COLOR_GREEN};

int zero[25] = 
{
  0,0,1,1,0,
  0,1,0,0,1,
  0,1,0,0,1,
  0,1,0,0,1,
  0,0,1,1,0
};


int one[25] = 
{
  0,0,1,0,0,
  0,1,1,0,0,
  0,0,1,0,0,
  0,0,1,0,0,
  0,1,1,1,0
};

int two[25] = 
{
  0,1,1,1,0,
  0,0,0,0,1,
  0,0,1,1,0,
  0,1,0,0,0,
  0,1,1,1,1
};

int three[25] = 
{
  0,1,1,1,0,
  0,0,0,0,1,
  0,0,1,1,0,
  0,0,0,0,1,
  0,1,1,1,0
};

int four[25] = 
{
  0,0,0,1,0,
  0,1,0,1,0,
  0,1,1,1,1,
  0,0,0,1,0,
  0,0,0,1,0
};

int five[25] = 
{
  0,1,1,1,1,
  0,1,0,0,0,
  0,1,1,1,0,
  0,0,0,0,1,
  0,1,1,1,0
};

int six[25] = 
{
  0,0,1,1,0,
  0,1,0,0,0,
  0,1,1,1,0,
  0,1,0,0,1,
  0,0,1,1,0
};

int seven[25] = 
{
  0,1,1,1,1,
  0,0,0,1,0,
  0,0,1,0,0,
  0,0,1,0,0,
  0,0,1,0,0
};

int eight[25] = 
{
  0,0,1,1,0,
  0,1,0,0,1,
  0,0,1,1,0,
  0,1,0,0,1,
  0,0,1,1,0
};

int nine[25] = 
{
  0,0,1,1,0,
  0,1,0,0,1,
  0,0,1,1,1,
  0,0,0,0,1,
  0,0,1,1,0
};

int *displayNumbers[10] = { zero, one, two, three, four, five, six, seven, eight, nine };

int currentState = 0;
bool isAsleep = false;
bool wasLongPressed = false;
bool commandExecuted = false;

void setup() 
{
  M5.begin(true, false, true);
  delay(20);

  drawArray(displayNumbers[currentState], colorList);
}

void loop() 
{
  if(M5.Btn.wasPressed())
  {
    // Display state number
    drawArray(displayNumbers[currentState], colorList);

    commandExecuted = false;
  }
  
  if(M5.Btn.wasReleased())
  {
    Serial.print("isAsleep = ");
    Serial.println(isAsleep);
    Serial.print("wasLongPressed = ");
    Serial.println(wasLongPressed);
    
    if(wasLongPressed == false)
    {
      if(isAsleep == false)
      {
        // Advance to next state in the cycle
        currentState = (currentState + 1) % 10;
      }
      
      isAsleep = false;
      commandExecuted = false;
      Serial.println(currentState);
  
      // Display state number
      drawArray(displayNumbers[currentState], colorList);
    }

    wasLongPressed = false;
  }

  // Long press
  if(M5.Btn.pressedFor(1000))
  {
    wasLongPressed = true;
    
    // Display state number as active
    drawArray(displayNumbers[currentState], activeColorList);
    
    // Perform corresponding action
    if(commandExecuted == false)
    {
      switch(currentState)
      {
        case 0:
        {
          performActionZero();
          break;
        }
        case 1:
        {
          performActionOne();
          break;
        }
        case 2:
        {
          performActionTwo();
          break;
        }
        case 3:
        {
          performActionThree();
          break;
        }
        case 4:
        {
          performActionFour();
          break;
        }
        case 5:
        {
          performActionFive();
          break;
        }
        case 6:
        {
          performActionSix();
          break;
        }
        case 7:
        {
          performActionSeven();
          break;
        }
        case 8:
        {
          performActionEight();
          break;
        }
        case 9:
        {
          performActionNine();
          break;
        }
      }

      commandExecuted = true;

      Serial.println("pressedFor");
    }
  }

  // Clear display after period of inactivity
  if(M5.Btn.releasedFor(2000))
  {
    M5.dis.clear();
    isAsleep = true;
    commandExecuted = false;
  }

  // Reset to initial state after LONG period of inactivity
  if(M5.Btn.releasedFor(30000))
  {
    currentState = 0;
    M5.dis.clear();
    isAsleep = true;
    commandExecuted = false;
    // Serial.println("Reset currentState to 0");
  }

  delay(50);
  M5.update();
}

void performActionZero()
{
  Serial.println("In performActionZero");
}

void performActionOne()
{
  Serial.println("In performActionOne");
}

void performActionTwo()
{
  Serial.println("In performActionTwo");
}

void performActionThree()
{
  Serial.println("In performActionThree");
}

void performActionFour()
{
  Serial.println("In performActionFour");
}

void performActionFive()
{
  Serial.println("In performActionFive");
}

void performActionSix()
{
  Serial.println("In performActionSix");
}

void performActionSeven()
{
  Serial.println("In performActionSeven");
}

void performActionEight()
{
  Serial.println("In performActionEight");
}

void performActionNine()
{
  Serial.println("In performActionNine");
}

void drawArray(int arr[], int colors[])
{
  for(int i = 0; i < 25; i++)
  {
      M5.dis.drawpix(i, colors[arr[i]]);
  }
}

Here's the walk-through:

Line Comment
16 - 23 GRB color constants
25 Color scheme for the unselected menu item
26 Color scheme for when the menu item is selected
28 - 117 Definition of the digits corresponding to each menu item
119 Pointer to all those digit arrays for easy reference
121 The current menu item, will be an integer between 0 and 9 inclusive
122 Is the display off?
123 Has the menu item been selected?
124 Has the corresponding menu command been executed?
131 Show the initial value of the currentState, which is zero
136 When button is pressed...
139 Show the current state
141 The command has not YET been executed
242 - 247 If there has been 2 seconds of inactivity, clear the display
250 - 257 If there has been 30 seconds of inactivity, reset currentState to 0
171 If there was a long press (1 second)...
176 Draw the digit as "selected"
179 If the corresponding command has not yet been executed...
181 - 233 Perform the corresponding command
235 Set flag indicating that the command has been performed
260 Poll for button events
263 - 311 Command implementations (currently trivial)
313 - 318 Our friend, the drawArray function

So, that is how to implement a menu system using only one button! We will use this framework to build a DVD IR remote control in the next post!

Click here to go to the table of contents for this series.

No comments:

Post a Comment