-
-
Notifications
You must be signed in to change notification settings - Fork 112
/
IO_TCA8418.h
371 lines (333 loc) · 15.5 KB
/
IO_TCA8418.h
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
365
366
367
368
369
370
371
/*
* © 2023-2024, Paul M. Antoine
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC-EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef io_tca8418_h
#define io_tca8418_h
#include "IODevice.h"
#include "I2CManager.h"
#include "DIAG.h"
#include "FSH.h"
/////////////////////////////////////////////////////////////////////////////////////////////////////
/*
* IODevice subclass for TCA8418 80-key keypad encoder, which we'll treat as 80 available VPINs where
* key down == 1 and key up == 0 by configuring just as an 8x10 keyboard matrix. Users can opt to use
* up to all 80 of the available VPINs for now, allowing memory to be saved if not all events are required.
*
* The datasheet says:
*
* The TCA8418 can be configured to support many different configurations of keypad setups.
* All 18 GPIOs for the rows and columns can be used to support up to 80 keys in an 8x10 key pad
* array. Another option is that all 18 GPIOs be used for GPIs to read 18 buttons which are
* not connected in an array. Any combination in between is also acceptable (for example, a
* 3x4 keypad matrix and using the remaining 11 GPIOs as a combination of inputs and outputs).
*
* With an 8x10 key event matrix, the events are numbered as such:
*
* C0 C1 C2 C3 C4 C5 C6 C7 C8 C9
* ========================================
* R0| 0 1 2 3 4 5 6 7 8 9
* R1| 10 11 12 13 14 15 16 17 18 19
* R2| 20 21 22 23 24 25 26 27 28 29
* R3| 30 31 32 33 34 35 36 37 38 39
* R4| 40 41 42 43 44 45 46 47 48 49
* R5| 50 51 52 53 54 55 56 57 58 59
* R6| 60 61 62 63 64 65 66 67 68 69
* R7| 70 71 72 73 74 75 76 77 78 79
*
* So if you start with VPIN 300, R0/C0 will be 300, and R7/C9 will be 379.
*
* HAL declaration for myAutomation.h is:
* HAL(TCA8418, firstVpin, numPins, I2CAddress, interruptPin)
*
* Where numPins can be 1-80, and interruptPin can be any spare Arduino pin.
*
* Configure using the following on the main I2C bus:
* HAL(TCA8418, 300, 80, 0x34)
*
* Use something like this on a multiplexor, and with up to 8 of the 8-way multiplexors you could have 64 different TCA8418 boards:
* HAL(TCA8418, 400, 80, {SubBus_1, 0x34})
*
* And if needing an Interrupt pin to speed up operations:
* HAL(TCA8418, 300, 80, 0x34, D21)
*
* Note that using an interrupt pin speeds up button press acquisition considerably (less than a millisecond vs 10-100),
* but even with interrupts enabled the code presently checks every 100ms in case the interrupt pin becomes disconnected.
* Use any available Arduino pin for interrupt monitoring.
*/
class TCA8418 : public IODevice {
public:
static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
if (checkNoOverlap(firstVpin, nPins, i2cAddress))
new TCA8418(firstVpin, (nPins = (nPins > 80) ? 80 : nPins), i2cAddress, interruptPin);
}
private:
uint8_t* _digitalInputStates = NULL; // Array of pin states
uint8_t _digitalPinBytes = 0; // Number of bytes in pin state array
uint8_t _numKeyEvents = 0; // Number of outsanding key events waiting for us
unsigned long _lastEventRead = 0;
unsigned long _eventRefresh = 10000UL; // Delay refreshing events for 10ms
const unsigned long _eventRefreshSlow = 100000UL; // Delay refreshing events for 100ms
bool _gpioInterruptsEnabled = false;
uint8_t _inputBuffer[1];
uint8_t _commandBuffer[1];
I2CRB _i2crb;
enum {RDS_IDLE, RDS_EVENT, RDS_KEYCODE}; // Read operation states
uint8_t _readState = RDS_IDLE;
// Constructor
TCA8418(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
if (nPins > 0)
{
_firstVpin = firstVpin;
_nPins = nPins;
_I2CAddress = i2cAddress;
_gpioInterruptPin = interruptPin;
addDevice(this);
}
}
void _begin() {
I2CManager.begin();
if (I2CManager.exists(_I2CAddress)) {
// Default all GPIO pins to INPUT
I2CManager.write(_I2CAddress, 2, REG_GPIO_DIR_1, 0x00);
I2CManager.write(_I2CAddress, 2, REG_GPIO_DIR_2, 0x00);
I2CManager.write(_I2CAddress, 2, REG_GPIO_DIR_3, 0x00);
// Remove all GPIO pins from events
I2CManager.write(_I2CAddress, 2, REG_GPI_EM_1, 0x00);
I2CManager.write(_I2CAddress, 2, REG_GPI_EM_2, 0x00);
I2CManager.write(_I2CAddress, 2, REG_GPI_EM_3, 0x00);
// Set all pins to FALLING interrupts
I2CManager.write(_I2CAddress, 2, REG_GPIO_INT_LVL_1, 0x00);
I2CManager.write(_I2CAddress, 2, REG_GPIO_INT_LVL_2, 0x00);
I2CManager.write(_I2CAddress, 2, REG_GPIO_INT_LVL_3, 0x00);
// Remove all GPIO pins from interrupts
I2CManager.write(_I2CAddress, 2, REG_GPIO_INT_EN_1, 0x00);
I2CManager.write(_I2CAddress, 2, REG_GPIO_INT_EN_2, 0x00);
I2CManager.write(_I2CAddress, 2, REG_GPIO_INT_EN_3, 0x00);
// Set up an 8 x 10 matrix by writing 0xFF to all the row and column configs
// Row config is maximum of 8, and in REG_KP_GPIO_1
I2CManager.write(_I2CAddress, 2, REG_KP_GPIO_1, 0xFF);
// Column config is maximum of 10, lower 8 bits in REG_KP_GPIO_2, upper in REG_KP_GPIO_3
// Set first 8 columns
I2CManager.write(_I2CAddress, 2, REG_KP_GPIO_2, 0xFF);
// Turn on cols 9/10
I2CManager.write(_I2CAddress, 2, REG_KP_GPIO_3, 0x03);
// // Set all pins to Enable Debounce
I2CManager.write(_I2CAddress, 2, REG_DEBOUNCE_DIS_1, 0x00);
I2CManager.write(_I2CAddress, 2, REG_DEBOUNCE_DIS_2, 0x00);
I2CManager.write(_I2CAddress, 2, REG_DEBOUNCE_DIS_3, 0x00);
// Let's assume an 8x10 matrix for now, and configure
_digitalPinBytes = (_nPins + 7) / 8;
if ((_digitalInputStates = (byte *)calloc(_digitalPinBytes, 1)) == NULL) {
DIAG(F("TCA8418 I2C: Unable to alloc %d bytes"), _digitalPinBytes);
return;
}
// Configure pin used for GPIO extender notification of change (if allocated)
// and configure TCA8418 to produce key event interrupts
if (_gpioInterruptPin >= 0) {
DIAG(F("TCA8418 I2C: interrupt pin configured on %d"), _gpioInterruptPin);
_gpioInterruptsEnabled = true;
_eventRefresh = _eventRefreshSlow; // Switch to slower manual refreshes in case the INT pin isn't connected!
pinMode(_gpioInterruptPin, INPUT_PULLUP);
I2CManager.write(_I2CAddress, 2, REG_CFG, REG_CFG_KE_IEN);
// Clear any pending interrupts
I2CManager.write(_I2CAddress, 2, REG_INT_STAT, REG_STAT_K_INT);
}
#ifdef DIAG_IO
_display();
#endif
}
}
int _read(VPIN vpin) override {
if (_deviceState == DEVSTATE_FAILED)
return 0;
int pin = vpin - _firstVpin;
bool result = _digitalInputStates[pin / 8] & (1 << (pin % 8));
return result;
}
// Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads)
void _loop(unsigned long currentMicros) override {
if (_deviceState == DEVSTATE_FAILED) return; // If device failed, return
// Request block is used for key event reads from the TCA8418, which are performed
// on a cyclic basis.
if (_readState != RDS_IDLE) {
if (_i2crb.isBusy()) return; // If I2C operation still in progress, return
uint8_t status = _i2crb.status;
if (status == I2C_STATUS_OK) { // If device request ok, read input data
// First check if we have any key events waiting
if (_readState == RDS_EVENT) {
if ((_numKeyEvents = (_inputBuffer[0] & 0x0F)) != 0) {
// We could read each key event waiting in a synchronous loop, which may prove preferable
// but for now, schedule an async read of the first key event in the queue
_commandBuffer[0] = REG_KEY_EVENT_A;
I2CManager.read(_I2CAddress, _inputBuffer, 1, _commandBuffer, 1, &_i2crb); // non-blocking read
_readState = RDS_KEYCODE; // Shift to reading key events!
}
else // We found no key events waiting, return to IDLE
_readState = RDS_IDLE;
}
else {
// RDS_KEYCODE
uint8_t key = _inputBuffer[0] & 0x7F;
bool keyDown = _inputBuffer[0] & 0x80;
// Check for just keypad events
key--; // R0/C0 is key #1, so subtract 1 to create an array offset
// We only want to record key events we're configured for, as we have calloc'd an
// appropriately sized _digitalInputStates array!
if (key < _nPins) {
if (keyDown)
_digitalInputStates[key / 8] |= (1 << (key % 8));
else
_digitalInputStates[key / 8] &= ~(1 << (key % 8));
}
else
DIAG(F("TCA8418 I2C: key event %d discarded, outside Vpin range"), key);
_numKeyEvents--; // One less key event to get
if (_numKeyEvents != 0)
{
// DIAG(F("TCA8418 I2C: more keys in read event queue, # waiting is: %x"), _numKeyEvents);
// We could read each key event waiting in a synchronous loop, which may prove preferable
// but for now, schedule an async read of the first key event in the queue
_commandBuffer[0] = REG_KEY_EVENT_A;
I2CManager.read(_I2CAddress, _inputBuffer, 1, _commandBuffer, 1, &_i2crb); // non-blocking read
}
else {
// DIAG(F("TCA8418 I2C: no more keys in read event queue"));
// Clear any pending interrupts
I2CManager.write(_I2CAddress, 2, REG_INT_STAT, REG_STAT_K_INT);
_readState = RDS_IDLE; // Shift to IDLE
return;
}
}
} else
reportError(status, false); // report eror but don't go offline.
}
// If we're not doing anything now, check to see if we have an interrupt pin configured and it is low,
// or if our timer has elapsed and we should check anyway in case the interrupt pin is disconnected.
if (_readState == RDS_IDLE) {
if ((_gpioInterruptsEnabled && !digitalRead(_gpioInterruptPin)) ||
((currentMicros - _lastEventRead) > _eventRefresh))
{
_commandBuffer[0] = REG_KEY_LCK_EC;
I2CManager.read(_I2CAddress, _inputBuffer, 1, _commandBuffer, 1, &_i2crb); // non-blocking read
_lastEventRead = currentMicros;
_readState = RDS_EVENT; // Shift to looking for key events!
}
}
}
// Display device information and status
void _display() override {
DIAG(F("TCA8418 I2C:%s Vpins %u-%u%S"),
_I2CAddress.toString(),
_firstVpin, (_firstVpin+_nPins-1),
_deviceState == DEVSTATE_FAILED ? F(" OFFLINE") : F(""));
if (_gpioInterruptsEnabled)
DIAG(F("TCA8418 I2C:Interrupt on pin %d"), _gpioInterruptPin);
}
// Helper function for error handling
void reportError(uint8_t status, bool fail=true) {
DIAG(F("TCA8418 I2C:%s Error:%d (%S)"), _I2CAddress.toString(),
status, I2CManager.getErrorMessage(status));
if (fail)
_deviceState = DEVSTATE_FAILED;
}
enum tca8418_registers
{
// REG_RESERVED = 0x00
REG_CFG = 0x01, // Configuration register
REG_INT_STAT = 0x02, // Interrupt status
REG_KEY_LCK_EC = 0x03, // Key lock and event counter
REG_KEY_EVENT_A = 0x04, // Key event register A
REG_KEY_EVENT_B = 0x05, // Key event register B
REG_KEY_EVENT_C = 0x06, // Key event register C
REG_KEY_EVENT_D = 0x07, // Key event register D
REG_KEY_EVENT_E = 0x08, // Key event register E
REG_KEY_EVENT_F = 0x09, // Key event register F
REG_KEY_EVENT_G = 0x0A, // Key event register G
REG_KEY_EVENT_H = 0x0B, // Key event register H
REG_KEY_EVENT_I = 0x0C, // Key event register I
REG_KEY_EVENT_J = 0x0D, // Key event register J
REG_KP_LCK_TIMER = 0x0E, // Keypad lock1 to lock2 timer
REG_UNLOCK_1 = 0x0F, // Unlock register 1
REG_UNLOCK_2 = 0x10, // Unlock register 2
REG_GPIO_INT_STAT_1 = 0x11, // GPIO interrupt status 1
REG_GPIO_INT_STAT_2 = 0x12, // GPIO interrupt status 2
REG_GPIO_INT_STAT_3 = 0x13, // GPIO interrupt status 3
REG_GPIO_DAT_STAT_1 = 0x14, // GPIO data status 1
REG_GPIO_DAT_STAT_2 = 0x15, // GPIO data status 2
REG_GPIO_DAT_STAT_3 = 0x16, // GPIO data status 3
REG_GPIO_DAT_OUT_1 = 0x17, // GPIO data out 1
REG_GPIO_DAT_OUT_2 = 0x18, // GPIO data out 2
REG_GPIO_DAT_OUT_3 = 0x19, // GPIO data out 3
REG_GPIO_INT_EN_1 = 0x1A, // GPIO interrupt enable 1
REG_GPIO_INT_EN_2 = 0x1B, // GPIO interrupt enable 2
REG_GPIO_INT_EN_3 = 0x1C, // GPIO interrupt enable 3
REG_KP_GPIO_1 = 0x1D, // Keypad/GPIO select 1
REG_KP_GPIO_2 = 0x1E, // Keypad/GPIO select 2
REG_KP_GPIO_3 = 0x1F, // Keypad/GPIO select 3
REG_GPI_EM_1 = 0x20, // GPI event mode 1
REG_GPI_EM_2 = 0x21, // GPI event mode 2
REG_GPI_EM_3 = 0x22, // GPI event mode 3
REG_GPIO_DIR_1 = 0x23, // GPIO data direction 1
REG_GPIO_DIR_2 = 0x24, // GPIO data direction 2
REG_GPIO_DIR_3 = 0x25, // GPIO data direction 3
REG_GPIO_INT_LVL_1 = 0x26, // GPIO edge/level detect 1
REG_GPIO_INT_LVL_2 = 0x27, // GPIO edge/level detect 2
REG_GPIO_INT_LVL_3 = 0x28, // GPIO edge/level detect 3
REG_DEBOUNCE_DIS_1 = 0x29, // Debounce disable 1
REG_DEBOUNCE_DIS_2 = 0x2A, // Debounce disable 2
REG_DEBOUNCE_DIS_3 = 0x2B, // Debounce disable 3
REG_GPIO_PULL_1 = 0x2C, // GPIO pull-up disable 1
REG_GPIO_PULL_2 = 0x2D, // GPIO pull-up disable 2
REG_GPIO_PULL_3 = 0x2E, // GPIO pull-up disable 3
// REG_RESERVED = 0x2F
};
enum tca8418_config_reg_fields
{
// Config Register #1 fields
REG_CFG_AI = 0x80, // Auto-increment for read/write
REG_CFG_GPI_E_CGF = 0x40, // Event mode config
REG_CFG_OVR_FLOW_M = 0x20, // Overflow mode enable
REG_CFG_INT_CFG = 0x10, // Interrupt config
REG_CFG_OVR_FLOW_IEN = 0x08, // Overflow interrupt enable
REG_CFG_K_LCK_IEN = 0x04, // Keypad lock interrupt enable
REG_CFG_GPI_IEN = 0x02, // GPI interrupt enable
REG_CFG_KE_IEN = 0x01, // Key events interrupt enable
};
enum tca8418_int_status_fields
{
// Interrupt Status Register #2 fields
REG_STAT_CAD_INT = 0x10, // Ctrl-alt-del seq status
REG_STAT_OVR_FLOW_INT = 0x08, // Overflow interrupt status
REG_STAT_K_LCK_INT = 0x04, // Key lock interrupt status
REG_STAT_GPI_INT = 0x02, // GPI interrupt status
REG_STAT_K_INT = 0x01, // Key events interrupt status
};
enum tca8418_lock_ec_fields
{
// Key Lock Event Count Register #3
REG_LCK_EC_K_LCK_EN = 0x40, // Key lock enable
REG_LCK_EC_LCK_2 = 0x20, // Keypad lock status 2
REG_LCK_EC_LCK_1 = 0x10, // Keypad lock status 1
REG_LCK_EC_KLEC_3 = 0x08, // Key event count bit 3
REG_LCK_EC_KLEC_2 = 0x04, // Key event count bit 2
REG_LCK_EC_KLEC_1 = 0x02, // Key event count bit 1
REG_LCK_EC_KLEC_0 = 0x01, // Key event count bit 0
};
};
#endif