-
Notifications
You must be signed in to change notification settings - Fork 37
/
Copy pathTM16xxButtons.cpp
364 lines (328 loc) · 12 KB
/
TM16xxButtons.cpp
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
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
/*
TM16xxButtons.cpp - Buttons class for TM16xx.
The TM16xxButtons class supports the key-scanning features of TM16xx chips, such as TM1637 or TM1638.
It extends the getButtons() function of the base class and provides these features:
- setting callback functions
- multi-state keys (similar to OneButton): Press, LongPress, Click, Doubleclick
- tracking button state of combined key presses
These are some TM16xx chips that support key-scanning:
TM1628 10 x 2 multi DIO/CLK/STB
TM1630 7 x 1 multi DIO/CLK/STB
TM1637 8 x 2 single DIO/CLK (no support for combined key pressing according datasheet)
TM1638 8 x 3 multi DIO/CLK/STB
TM1650 7 x 4 single DIO/CLK
TM1668 10 x 2 multi DIO/CLK/STB
Made by Maxint R&D. See https://github.com/maxint-rd/
Partially based on OneButton library by Matthias Hertel. See https://github.com/mathertel/OneButton
*/
//#define TM16XX_DEBUG 1
#include "TM16xxButtons.h"
// constructor
TM16xxButtons::TM16xxButtons(TM16xx *pTM16xx, byte nMaxButtons) : _pTM16xx(pTM16xx), _nMaxButtons(nMaxButtons)
{ // Note: to avoid "warning: 'TM16xxButtons::_nMaxButtons' will be initialized after [-Wreorder]", parameters should be in same order as in class definition
// TM16XX_OPT_BUTTONS_MALLOC: reduce memory by using dynamic memory allocation instead of static arrays for button states
// requires additional constructor parameter to allow less than TM16XX_BUTTONS_MAXBUTTONSLOTS
#if(TM16XX_OPT_BUTTONS_MALLOC)
_ButtonSlots=(TM16xxButtonSlot *)malloc(_nMaxButtons*sizeof(TM16xxButtonSlot));
#endif
reset();
}
// explicitly set the number of millisec that have to pass by before a click is
// detected.
void TM16xxButtons::setClickTicks(int ticks)
{
_clickTicks = ticks;
} // setClickTicks
// explicitly set the number of millisec that have to pass by before a long
// button press is detected.
void TM16xxButtons::setLongPressTicks(int ticks)
{
_longPressTicks = ticks;
} // setLongPressTicks
#if(TM16XX_OPT_BUTTONS_EVENT)
// set function for release event
void TM16xxButtons::attachEventHandler(callbackTM16xxButtonsEvent newFunction)
{
_eventFunc = newFunction;
} // attachEventHandler
#else
void TM16xxButtons::attachRelease(callbackTM16xxButtons newFunction)
{
_releaseFunc = newFunction;
} // attachRelease
// set function for click event
void TM16xxButtons::attachClick(callbackTM16xxButtons newFunction)
{
_clickFunc = newFunction;
} // attachClick
// set function for doubleClick event
void TM16xxButtons::attachDoubleClick(callbackTM16xxButtons newFunction)
{
_doubleClickFunc = newFunction;
} // attachDoubleClick
// set function for longPressStart event
void TM16xxButtons::attachLongPressStart(callbackTM16xxButtons newFunction)
{
_longPressStartFunc = newFunction;
} // attachLongPressStart
// set function for longPressStop event
void TM16xxButtons::attachLongPressStop(callbackTM16xxButtons newFunction)
{
_longPressStopFunc = newFunction;
} // attachLongPressStop
// set function for during longPress event
void TM16xxButtons::attachDuringLongPress(callbackTM16xxButtons newFunction)
{
_duringLongPressFunc = newFunction;
} // attachDuringLongPress
#endif
// For active buttons we use slots to track the state
// Using slots allows more buttons in less memory
byte TM16xxButtons::findSlot(byte nButton, byte nStateFind)
{
for(byte n=0; n<_nMaxButtons; n++)
{
if(_ButtonSlots[n].button==nButton && (_ButtonSlots[n].state==nStateFind || nStateFind==TM16XX_BUTTONS_SLOT_ANYSTATE))
{ //Serial.print(F("."));
return(n); }
if(_ButtonSlots[n].button==nButton && nButton==TM16XX_BUTTONS_SLOT_UNUSED && (_ButtonSlots[n].state==nStateFind || nStateFind==TM16XX_BUTTONS_SLOT_ANYSTATE))
{ //Serial.print(F("^"));
return(n); }
if(nButton==TM16XX_BUTTONS_SLOT_ANYBUTTON && (_ButtonSlots[n].state==nStateFind || nStateFind==TM16XX_BUTTONS_SLOT_ANYSTATE))
{ //Serial.print(F("~"));
return(n); }
//return(n);
}
//Serial.print(F("*"));
return(TM16XX_BUTTONS_SLOT_NOTFOUND);
}
// function to get the current long pressed state
bool TM16xxButtons::isPressed(byte nButton)
{
//if(nButton>=_nMaxButtons) return(false);
nButton=findSlot(nButton);
if(nButton==TM16XX_BUTTONS_SLOT_NOTFOUND) return(false);
return(_ButtonSlots[nButton].state==TM16XX_BUTTONS_STATE_PRESSED || _ButtonSlots[nButton].state==TM16XX_BUTTONS_STATE_DBLPRESS || _ButtonSlots[nButton].state==TM16XX_BUTTONS_STATE_LPRESS);
}
// function to get the current long pressed state
bool TM16xxButtons::isLongPressed(byte nButton)
{
nButton=findSlot(nButton);
if(nButton==TM16XX_BUTTONS_SLOT_NOTFOUND) return(false);
return(_ButtonSlots[nButton].state==TM16XX_BUTTONS_STATE_LPRESS);
}
int TM16xxButtons::getPressedTicks(byte nButton)
{
nButton=findSlot(nButton);
if(nButton==TM16XX_BUTTONS_SLOT_NOTFOUND) return(0);
return((_ButtonSlots[nButton].stopTime - _ButtonSlots[nButton].startTime)); // uint16_t subtraction may overflow, but is still fine 0x01 - 0xFC = 0x05
}
void TM16xxButtons::reset(void)
{
for(byte n=0; n<_nMaxButtons; n++)
{
_ButtonSlots[n].button = TM16XX_BUTTONS_SLOT_UNUSED; // reset.
_ButtonSlots[n].state = TM16XX_BUTTONS_STATE_START; // restart.
_ButtonSlots[n].startTime = 0;
_ButtonSlots[n].stopTime = 0;
}
}
/**
* @brief Check input of the configured pin and then advance the finite state
* machine (FSM).
*/
uint32_t TM16xxButtons::tick(void)
{ // update the state of each button and call callback functions as needed
uint32_t dwButtons=_pTM16xx->getButtons();
#ifdef TM16XX_DEBUG
Serial.print(F("TM16xxButtons: "));
Serial.print(dwButtons, HEX);
Serial.print(F(", slots: "));
for(byte n=0; n<_nMaxButtons; n++)
{
Serial.print(n);
Serial.print(F("="));
Serial.print(_ButtonSlots[n].button, HEX);
Serial.print(F(", s:"));
Serial.print(_ButtonSlots[n].state);
Serial.print(F(" "));
}
// do a tick for each possible button
for(byte n=0; n<32; n++)
{
tick(n, dwButtons&bit(n)); // MMOLE 181103: _BV only works on 16-bit values!
//Serial.print(_state[n]);
//Serial.print(_state[n]);
}
for(byte n=0; n<_nMaxButtons; n++)
Serial.print(_ButtonSlots[n].state);
// Serial.print(_state[n]);
//Serial.print(F(" "));
Serial.println("");
#else
// do a tick for each possible button
for(byte n=0; n<32; n++) // uint32_t dwButtons has 32 buttons at max
tick(n, dwButtons&bit(n)); // MMOLE 181103: _BV only works on 16-bit values!
#endif
return(dwButtons);
}
/**
* @brief Advance the finite state machine (FSM) using the given level.
*/
void TM16xxButtons::tick(byte nButtonNum, bool activeLevel)
{
uint16_t now = (uint16_t) millis(); // current (relative) time in msecs. To safe RAM we only use the bottom word (16 bits for instead of 32 for approx. 50 days)
// find the slot of the button used, or prepare new slot
byte nSlot=findSlot(nButtonNum);
if(nSlot==TM16XX_BUTTONS_SLOT_NOTFOUND)
{ // no slot for same button, find a new one
if(!activeLevel) return; // new slots only get occupied when activated
//Serial.print(nButtonNum, HEX);
//Serial.print("@");
nSlot=findSlot(TM16XX_BUTTONS_SLOT_UNUSED);
if(nSlot==TM16XX_BUTTONS_SLOT_NOTFOUND)
{ // no new slot, reuse any slot of state TM16XX_BUTTONS_STATE_START
//Serial.print("N");
nSlot=findSlot(TM16XX_BUTTONS_SLOT_ANYBUTTON, TM16XX_BUTTONS_STATE_START);
if(nSlot==TM16XX_BUTTONS_SLOT_NOTFOUND)
{ // no more slots!
//Serial.print("!");
return;
}
else
{ // found used slot in start state, reuse that slot
//Serial.print(nSlot, HEX);
//Serial.print("R");
_ButtonSlots[nSlot].button=nButtonNum;
}
}
else
{ // unused slot found, use it
//Serial.print(nSlot, HEX);
//Serial.print("U");
_ButtonSlots[nSlot].button=nButtonNum;
}
}
else
{
//Serial.print(nSlot, HEX);
//Serial.print("F");
}
// Implementation of the state machine
switch(_ButtonSlots[nSlot].state)
{
case TM16XX_BUTTONS_STATE_START: // waiting for button being pressed.
if (activeLevel)
{
_ButtonSlots[nSlot].state = TM16XX_BUTTONS_STATE_PRESSED; // step to pressed state
_ButtonSlots[nSlot].startTime = now; // remember starting time
} // if
break;
case TM16XX_BUTTONS_STATE_PRESSED: // waiting for button being released.
if (!activeLevel)
{
_ButtonSlots[nSlot].state = TM16XX_BUTTONS_STATE_RELEASED; // step to released state
_ButtonSlots[nSlot].stopTime = now; // remember stopping time
#if(TM16XX_OPT_BUTTONS_EVENT)
if (_eventFunc)
_eventFunc(TM16XX_BUTTONS_EVENT_RELEASE, nButtonNum);
#else
if (_releaseFunc)
_releaseFunc(nButtonNum);
#endif
}
else if ((activeLevel) && ((unsigned long)(now - _ButtonSlots[nSlot].startTime) > _longPressTicks))
{
_ButtonSlots[nSlot].state = TM16XX_BUTTONS_STATE_LPRESS; // step to long press state
_ButtonSlots[nSlot].stopTime = now; // remember stopping time
#if(TM16XX_OPT_BUTTONS_EVENT)
if (_eventFunc)
{
_eventFunc(TM16XX_BUTTONS_EVENT_LONGPRESSSTART, nButtonNum);
_eventFunc(TM16XX_BUTTONS_EVENT_LONGPRESSBUSY, nButtonNum);
}
#else
if (_longPressStartFunc)
_longPressStartFunc(nButtonNum);
if (_duringLongPressFunc)
_duringLongPressFunc(nButtonNum);
#endif
} else {
// wait. Stay in this state.
} // if
break;
case TM16XX_BUTTONS_STATE_RELEASED: // waiting for button being pressed the second time or timeout.
#if(TM16XX_OPT_BUTTONS_EVENT)
if ((unsigned long)(now - _ButtonSlots[nSlot].startTime) > _clickTicks)
#else
if (_doubleClickFunc == NULL || (unsigned long)(now - _ButtonSlots[nSlot].startTime) > _clickTicks)
#endif
{
// this was only a single short click
#if(TM16XX_OPT_BUTTONS_EVENT)
if (_eventFunc)
_eventFunc(TM16XX_BUTTONS_EVENT_CLICK, nButtonNum);
#else
if (_clickFunc)
_clickFunc(nButtonNum);
#endif
_ButtonSlots[nSlot].state = TM16XX_BUTTONS_STATE_START; // restart.
}
else if ((activeLevel))
{
_ButtonSlots[nSlot].state = TM16XX_BUTTONS_STATE_DBLPRESS; // step to doubleclick state
_ButtonSlots[nSlot].startTime = now; // remember starting time
} // if
break;
case TM16XX_BUTTONS_STATE_DBLPRESS: // waiting for button being released finally.
if ((!activeLevel))
{
// this was a 2 click sequence.
_ButtonSlots[nSlot].state = TM16XX_BUTTONS_STATE_START; // restart.
_ButtonSlots[nSlot].stopTime = now; // remember stopping time
#if(TM16XX_OPT_BUTTONS_EVENT)
if (_eventFunc)
{
_eventFunc(TM16XX_BUTTONS_EVENT_RELEASE, nButtonNum);
_eventFunc(TM16XX_BUTTONS_EVENT_DOUBLECLICK, nButtonNum);
}
#else
if (_releaseFunc)
_releaseFunc(nButtonNum);
if (_doubleClickFunc)
_doubleClickFunc(nButtonNum);
#endif
} // if
break;
case TM16XX_BUTTONS_STATE_LPRESS: // waiting for button being released after long press.
if (!activeLevel)
{
_ButtonSlots[nSlot].state = TM16XX_BUTTONS_STATE_START; // restart.
_ButtonSlots[nSlot].stopTime = now; // remember stopping time
#if(TM16XX_OPT_BUTTONS_EVENT)
if (_eventFunc)
{
_eventFunc(TM16XX_BUTTONS_EVENT_RELEASE, nButtonNum);
_eventFunc(TM16XX_BUTTONS_EVENT_LONGPRESSSTOP, nButtonNum);
}
#else
if (_releaseFunc)
_releaseFunc(nButtonNum);
if (_longPressStopFunc)
_longPressStopFunc(nButtonNum);
#endif
}
else
{
// button is being long pressed
#if(TM16XX_OPT_BUTTONS_EVENT)
if (_eventFunc)
_eventFunc(TM16XX_BUTTONS_EVENT_LONGPRESSBUSY, nButtonNum);
#else
if (_duringLongPressFunc)
_duringLongPressFunc(nButtonNum);
#endif
} // if
break;
} // switch
} // TM16xxButtons.tick(nButton)