-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDigitalInputCard.cpp
401 lines (383 loc) · 13 KB
/
DigitalInputCard.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
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
#include <DigitalInputCard.hpp>
/**
* @brief Create a new Digital Input Card object with the specified address
* @note If you are using the ESPMegaI/O board, you should use the dip switch constructor
*
* @param address_a The ESPMegaI/O address of bank A
* @param address_b The ESPMegaI/O address of bank B
*/
DigitalInputCard::DigitalInputCard(uint8_t address_a, uint8_t address_b) : callbacks()
{
this->address_a = address_a;
this->address_b = address_b;
this->callbacks_handler_index = 0;
}
/**
* @brief Create a new Digital Input Card object with the specified position on the dip switch
*
* @note The bit 0 are at the left of the dip switch
*
* @warning There are 6 switches on the dip switch, 3 for bank A and 3 for bank B, They should be unique for each bank accross all the cards
*
* @param bit0 The position of the first switch on the dip switch
* @param bit1 The position of the second switch on the dip switch
* @param bit2 The position of the third switch on the dip switch
* @param bit3 The position of the fourth switch on the dip switch
* @param bit4 The position of the fifth switch on the dip switch
* @param bit5 The position of the sixth switch on the dip switch
*/
DigitalInputCard::DigitalInputCard(bool bit0, bool bit1, bool bit2, bool bit3, bool bit4, bool bit5)
{
this->address_a = 0x20;
this->address_b = 0x20;
this->inputBufferA = 0;
this->inputBufferB = 0;
if (bit0)
this->address_a += 1;
if (bit1)
this->address_a += 2;
if (bit2)
this->address_a += 4;
if (bit3)
this->address_b += 1;
if (bit4)
this->address_b += 2;
if (bit5)
this->address_b += 4;
}
/**
* @brief Initialize the Digital Input Card
*
* @return True if the initialization is successful, false otherwise
*/
bool DigitalInputCard::begin()
{
this->inputBankA = PCF8574(this->address_a);
this->inputBankB = PCF8574(this->address_b);
if (!this->inputBankA.begin())
{
ESP_LOGE("DigitalInputCard", "Input Card ERROR: Failed to install input bank A");
this->initOk = false;
return false;
}
if (!this->inputBankB.begin())
{
ESP_LOGE("DigitalInputCard", "Input Card ERROR: Failed to install input bank B");
this->initOk = false;
return false;
}
// Set the debounce time for all pins to 50ms
for (int i = 0; i < 16; i++)
{
this->debounceTime[i] = 50;
this->lastDebounceTime[i] = 0;
}
// Initialize the pin map to the default values
for (int i = 0; i < 16; i++)
{
this->pinMap[i] = i;
this->virtualPinMap[i] = i;
}
this->initOk = true;
return true;
}
/**
* @brief Read the input from the specified pin, always refresh the input buffers
*
* @param pin The pin to read from
* @return True if the pin is HIGH, false if the pin is LOW
*/
bool DigitalInputCard::digitalRead(uint8_t pin)
{
return this->digitalRead(pin, true);
}
/**
* @brief Read the input from the specified pin, also refresh the input buffers if refresh is true
*
* @param pin The pin to read from
* @param refresh If true, the input buffers will be refreshed before reading the pin
* @return True if the pin is HIGH, false if the pin is LOW
*/
bool DigitalInputCard::digitalRead(uint8_t pin, bool refresh)
{
if(!this->initOk)
{
ESP_LOGE("DigitalInputCard", "Input Card ERROR: Card not initialized");
return false;
}
pin = pinMap[pin];
// First check if the pin is in bank A or B
if (pin >= 0 && pin <= 7)
{
// Refresh the input buffers if refresh is true
if (refresh)
refreshInputBankA();
// Extract the bit from the buffer
return ((inputBufferA >> (7 - pin)) & 1);
}
else if (pin >= 8 && pin <= 15)
{
// Refresh the input buffers if refresh is true
if (refresh)
refreshInputBankB();
// Extract the bit from the buffer
return ((inputBufferB >> (15 - pin)) & 1);
}
return false;
}
/**
* @brief Check if the specified pin changed since the last call to this function
*
* @note This function compares the current input buffer with the previous input buffer to detect changes
*
* @param pin The pin to check
* @param currentBuffer The current input buffer
* @param previousBuffer The previous input buffer
*/
void DigitalInputCard::handlePinChange(int pin, uint8_t ¤tBuffer, uint8_t &previousBuffer)
{
// Get the index of the pin in the pin map
uint8_t virtualPin = virtualPinMap[pin];
if (pin < 8)
{
// Handle Bank A
if (((previousBuffer >> (7 - pin)) & 1) != ((currentBuffer >> (7 - pin)) & 1))
{
// When the pin first change, store the time
if (!pinChanged[virtualPin])
{
ESP_LOGD("DigitalInputCard", "Pin %d changed", virtualPin);
pinChanged[virtualPin] = true;
lastDebounceTime[virtualPin] = millis();
}
else
{
ESP_LOGD("DigitalInputCard", "Pin %d (%d>%d) debounce time: %d", virtualPin, ((previousBuffer >> (7 - pin)) & 1), ((currentBuffer >> (7 - pin)) & 1), millis() - lastDebounceTime[virtualPin]);
// If the pin was already changed, check if the debounce time has passed
if ((millis() - lastDebounceTime[virtualPin]) > debounceTime[virtualPin])
{
ESP_LOGD("DigitalInputCard", "Pin %d triggered", virtualPin);
// Call the callback function
for (auto const &callback : callbacks)
{
callback.second(virtualPin, ((currentBuffer >> (7 - pin)) & 1));
}
// Store the previous buffer at the specified pin (bitwise operation)
// new value : (currentBuffer >> (7 - pin)) & 1)
previousBuffer = (previousBuffer & ~(1 << (7 - pin))) | (((currentBuffer >> (7 - pin)) & 1) << (7 - pin));
// Reset the pin changed flag
pinChanged[virtualPin] = false;
}
}
}
else
{
// Pin bounce back to previous state, reset the debounce time
lastDebounceTime[virtualPin] = millis();
pinChanged[virtualPin] = false;
}
}
else
{
// Handle Bank B
if (((previousBuffer >> (15 - pin)) & 1) != ((currentBuffer >> (15 - pin)) & 1))
{
// When the pin first change, store the time
if (!pinChanged[virtualPin])
{
ESP_LOGD("DigitalInputCard", "Pin %d changed", virtualPin);
pinChanged[virtualPin] = true;
lastDebounceTime[virtualPin] = millis();
}
else
{
ESP_LOGD("DigitalInputCard", "Pin %d (%d>%d) debounce time: %d", virtualPin, ((previousBuffer >> (15 - pin)) & 1), ((currentBuffer >> (15 - pin)) & 1), millis() - lastDebounceTime[virtualPin]);
// If the pin was already changed, check if the debounce time has passed
if ((millis() - lastDebounceTime[virtualPin]) > debounceTime[virtualPin])
{
ESP_LOGD("DigitalInputCard", "Pin %d triggered", virtualPin);
// Call the callback function
for (auto const &callback : callbacks)
{
callback.second(virtualPin, ((currentBuffer >> (15 - pin)) & 1));
}
// Store the previous buffer at the specified pin (bitwise operation)
// new value : (currentBuffer >> (15 - pin)) & 1)
previousBuffer = (previousBuffer & ~(1 << (15 - pin))) | (((currentBuffer >> (15 - pin)) & 1) << (15 - pin));
// Reset the pin changed flag
pinChanged[virtualPin] = false;
}
}
}
else
{
// Pin bounce back to previous state, reset the debounce time
lastDebounceTime[virtualPin] = millis();
pinChanged[virtualPin] = false;
}
}
}
/**
* @brief A loop to refresh the input buffers and check for pin changes
*
* @note Although this function can be called in the main loop, it is recommended install the card in ESPMega to automatically manage the loop
*/
// Preform a loop to refresh the input buffers
void DigitalInputCard::loop()
{
// Refresh the input buffers
refreshInputBankA();
refreshInputBankB();
// Iterate over all pins and check if they changed
for (int i = 0; i < 16; i++)
{
// Check which bank the pin is in
if (i < 8)
{
handlePinChange(i, inputBufferA, previousInputBufferA);
}
else if (i >= 8 && i <= 15)
{
handlePinChange(i, inputBufferB, previousInputBufferB);
}
}
}
/**
* @brief Get the input buffer for bank A (the first 8 pins)
*
* @return The input buffer for bank A where the first bit is the first pin and the last bit is the last pin
*/
uint8_t DigitalInputCard::getInputBufferA()
{
// Rearrange the bits to match the pin map
uint8_t inputBufferA_rearranged = 0;
for (int i = 0; i < 8; i++)
{
inputBufferA_rearranged |= ((inputBufferA >> i) & 1) << (7 - i);
}
return inputBufferA_rearranged;
}
/**
* @brief Get the input buffer for bank B (the last 8 pins)
*
* @return The input buffer for bank B where the first bit is the first pin and the last bit is the last pin
*/
uint8_t DigitalInputCard::getInputBufferB()
{
// Rearrange the bits to match the pin map
uint8_t inputBufferB_rearranged = 0;
for (int i = 0; i < 8; i++)
{
inputBufferB_rearranged |= ((inputBufferB >> i) & 1) << (7 - i);
}
return inputBufferB_rearranged;
}
/**
* @brief Register a callback function to be called when a pin changes
*
* @param callback The callback function to be called
* @return The handler of the callback function
*/
uint8_t DigitalInputCard::registerCallback(std::function<void(uint8_t, bool)> callback)
{
callbacks[this->callbacks_handler_index] = callback;
return this->callbacks_handler_index++;
}
/**
* @brief Read the input state from the input ic and store it in the input buffer for bank A
*/
void DigitalInputCard::refreshInputBankA()
{
if(!this->initOk)
{
ESP_LOGE("DigitalInputCard", "Input Card ERROR: Card not initialized");
return;
}
inputBufferA = inputBankA.read8();
}
/**
* @brief Read the input state from the input ic and store it in the input buffer for bank B
*/
void DigitalInputCard::refreshInputBankB()
{
if(!this->initOk)
{
ESP_LOGE("DigitalInputCard", "Input Card ERROR: Card not initialized");
return;
}
inputBufferB = inputBankB.read8();
}
/**
* @brief Set the debounce time for the specified pin
*
* Debounce is the time in milliseconds that the pin should be stable before the callback function is called
* This is useful to prevent false triggers when the input is noisy
* An example of this is when the input is connected to a mechanical switch
*
* @param pin The pin to set the debounce time for
* @param debounceTime The debounce time in milliseconds
*/
void DigitalInputCard::setDebounceTime(uint8_t pin, uint32_t debounceTime)
{
pin = pinMap[pin];
this->debounceTime[pin] = debounceTime;
}
/**
* @brief Unregister a callback function
*
* @param handler The handler of the callback function to unregister
*/
void DigitalInputCard::unregisterCallback(uint8_t handler)
{
callbacks.erase(handler);
}
/**
* @brief Load the pin map for the card
*
* A pin map is an array of 16 elements that maps the physical pins to virtual pins
* The virtual pins are the pins that are used in the callback functions and are used for all the functions in this class
* The physical pins are the pins on the Input IC, This can be found on the schematic of the ESPMegaI/O board
* This function is useful if you want to change the number identification of the pins to match your project needs
*
* @param pinMap The pin map to load
*/
void DigitalInputCard::loadPinMap(uint8_t pinMap[16])
{
for (int i = 0; i < 16; i++)
{
// Load the pin map (physical pin to virtual pin)
this->pinMap[i] = pinMap[i];
// Load the virtual pin map (virtual pin to physical pin)
this->virtualPinMap[pinMap[i]] = i;
}
}
/**
* @brief Get the type of the card
*
* @return The type of the card
*/
uint8_t DigitalInputCard::getType()
{
return CARD_TYPE_DIGITAL_INPUT;
}
/**
* @brief Preload the previous input buffer and the input buffer
*
* @note This function is useful if you want to preload the input buffers with a run-time value
*/
void DigitalInputCard::preloadInputBuffer()
{
refreshInputBankA();
refreshInputBankB();
previousInputBufferA = inputBufferA;
previousInputBufferB = inputBufferB;
}
/**
* @brief Get the status of the card
*
* @return True if the card is initialized, false otherwise
*/
bool DigitalInputCard::getStatus()
{
return this->initOk;
}