Merge pull request #10431 from vic42/ftc_enhance

FTC532: Mitigate 'ghost' switching issues
This commit is contained in:
Theo Arends 2021-01-06 16:27:52 +01:00 committed by GitHub
commit a95fed30ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 107 additions and 52 deletions

View File

@ -25,7 +25,7 @@
select pin (GPIO_FTC532) select pin (GPIO_FTC532)
attach interrupt to pin DONE attach interrupt to pin DONE
ISR updating all 8 inputs DONE ISR updating all 8 inputs DONE
de-bouncing for 50 ms NOT REQUIRED de-bouncing for 50 ms DONE
change report every 250 ms REPORTS EVERY 50MS change report every 250 ms REPORTS EVERY 50MS
Webserver display "00001001" DONE & REMOVED Webserver display "00001001" DONE & REMOVED
MQTT message DONE MQTT message DONE
@ -49,17 +49,49 @@
Timing of an ALL OFF frame in clock cycles T=377µs, triggering on rising edge: Timing of an ALL OFF frame in clock cycles T=377µs, triggering on rising edge:
IDLE-2222444422224444-IDLE IDLE-2222444422224444-IDLE
*********************
* About this driver *
*********************
This driver implements the reverse engineered protocol of the FTC532 touch controller.
The protocol encodes the bitmap of touched keys in variable length pulses comprising a
fixed length frame. These frames are then sent out continuously from the FTC532 chip.
The first version of this driver was working fine on well behaved hardware. After being
released to the field and installed on crappy hardware in noisy environments it developed
a habit of random 'ghost' switchings at night, much to the chagrin of some users.
This is almost a re-write containing more timing (and other) checks in order to detect
and mitigate various incarnations of noise.
If you should still experience 'ghost switching' issues a solution may be increasing
FTC532_DEBOUNCE to 2 or higher. That will enable the de-bouncing code, at the expense of
'snappiness' of touch reactions. Higher values will accumulate more samples in 50 ms steps
before actually firing the rules trigger. It will eat a few bytes off your RAM and Flash
budget, too.
Usage:
------
This driver does not actually switch anything. It is a pure "rules" driver that solely emits
{"FTC532":{"KEYS":"XX"}} JSON messages to be used in a rule or by an MQTT broker. "XX" stands
for the hexadecimal (big endian) representation of a bitmap of keys currently touched, where
e.g. "00" means "no key touched" while "03" means "keys 1 and 2 touched simultaneously".
Selecting "FTC532" on a GPIO will awake the driver. This driver can only be selected once.
\*********************************************************************************************/ \*********************************************************************************************/
#define XDRV_47 47 #define XDRV_47 47
#define FTC532_KEYS_MAX 8 #define FTC532_DEBOUNCE 0 // no. of cycles, < 2 disables the code
#define FTC532_KEYS 4 // number of key pins on chip
#define FTC532_KEYS_MAX 8 // number of key slots in protocol
#define FTC532_STATE_WAITING false #define FTC532_STATE_WAITING 0x1
#define FTC532_STATE_READING true #define FTC532_STATE_READING 0x2
#define FTC532_STATE_COMPLETE 0x4
// Rising edge timing in microseconds // Rising edge timing in microseconds
#define FTC532_BIT 377 #define FTC532_BIT 377
#define FTC532_NOISE (FTC532_BIT * 3 / 2)
#define FTC532_SHORT (FTC532_BIT * 2) #define FTC532_SHORT (FTC532_BIT * 2)
#define FTC532_LONG (FTC532_BIT * 4) #define FTC532_LONG (FTC532_BIT * 4)
#define FTC532_IDLE (FTC532_BIT * 10) #define FTC532_IDLE (FTC532_BIT * 10)
@ -67,16 +99,22 @@
struct FTC532 { struct FTC532 {
volatile uint32_t rxtime; // ISR timer memory volatile uint32_t rxtime; // ISR timer memory
volatile uint16_t sample = 0xF0F0; // buffer for bit-coded time samples volatile uint16_t tsmp = 0; // buffer for bit-coded time samples
volatile uint16_t sample = 0xF0F0; // valid samples
volatile uint16_t rxbit; // ISR bit counter volatile uint16_t rxbit; // ISR bit counter
uint8_t keys = 0; // bitmap of active keys volatile uint16_t state; // ISR state
uint8_t old_keys = 0; // previously active keys uint8_t keys = 0; // bitmap of active keys
volatile bool state; // ISR state uint8_t old_keys = 0; // previously active keys
bool present = false; bool present = false; // driver active
#ifdef DEBUG_TASMOTA_DRIVER #if FTC532_DEBOUNCE > 1
volatile uint16_t errors; // error counter uint8_t key_cnt = 0; // used to de-bounce
volatile bool valid; // did we ever receive valid data? #endif // FTC532_DEBOUNCE > 1
#endif // DEBUG_TASMOTA_DRIVER #ifdef DEBUG_FTC532
volatile uint16_t e_inv = 0; // inverted key error counter
volatile uint16_t e_frame = 0; // frame error counter
volatile uint16_t e_noise = 0; // noise detection counter
volatile bool valid = 0; // did we ever receive valid data?
#endif // DEBUG_FTC532
} Ftc532; } Ftc532;
const char ftc532_json[] PROGMEM = "\"FTC532\":{\"KEYS\":\""; const char ftc532_json[] PROGMEM = "\"FTC532\":{\"KEYS\":\"";
@ -86,73 +124,90 @@ void ICACHE_RAM_ATTR ftc532_ISR(void) { // Hardware interrupt routine, trigger
uint32_t time_diff = time - Ftc532.rxtime; uint32_t time_diff = time - Ftc532.rxtime;
Ftc532.rxtime = time; Ftc532.rxtime = time;
if (Ftc532.state == FTC532_STATE_WAITING) { if (Ftc532.state & (FTC532_STATE_WAITING | FTC532_STATE_COMPLETE)) {
if (time_diff > FTC532_LONG + FTC532_SHORT) { // new frame if (time_diff > FTC532_LONG + FTC532_SHORT) { // new frame
Ftc532.rxbit = 0; Ftc532.rxbit = 0;
if (Ftc532.state & FTC532_STATE_COMPLETE) {
Ftc532.sample = Ftc532.tsmp; // copy completed frame
#ifdef DEBUG_FTC532
Ftc532.valid = true;
#endif // DEBUG_FTC532
}
Ftc532.state = FTC532_STATE_READING; Ftc532.state = FTC532_STATE_READING;
Ftc532.tsmp = 0;
} else {
Ftc532.state = FTC532_STATE_WAITING;
} }
return; return;
} // FTC532_STATE_READING starts here }
// FTC532_STATE_READING starts here
if (time_diff > FTC532_LONG + FTC532_BIT) { if (time_diff > FTC532_LONG + FTC532_BIT) {
#ifdef DEBUG_TASMOTA_DRIVER #ifdef DEBUG_FTC532
++Ftc532.errors; // frame error ++Ftc532.e_frame; // frame error
#endif // DEBUG_TASMOTA_DRIVER #endif // DEBUG_FTC532
Ftc532.state = FTC532_STATE_WAITING; Ftc532.state = FTC532_STATE_WAITING;
return; return;
} }
if (time_diff > FTC532_SHORT + FTC532_BIT) { if (time_diff > FTC532_SHORT + FTC532_BIT) {
Ftc532.sample |= (1 << Ftc532.rxbit); // LONG Ftc532.tsmp |= (1 << Ftc532.rxbit); // LONG
} else { } else if (time_diff < FTC532_NOISE) { // NOISE (SHORT now implicitly default)
Ftc532.sample &= ~(1 << Ftc532.rxbit); // SHORT #ifdef DEBUG_FTC532
++Ftc532.e_noise;
#endif // DEBUG_FTC532
Ftc532.state = FTC532_STATE_WAITING;
return;
} }
++Ftc532.rxbit; ++Ftc532.rxbit;
if (Ftc532.rxbit == FTC532_KEYS_MAX * 2) { // frame complete if (Ftc532.rxbit == FTC532_KEYS_MAX * 2) { // frame complete
Ftc532.rxbit = 0; Ftc532.state = FTC532_STATE_COMPLETE;
#ifdef DEBUG_TASMOTA_DRIVER
Ftc532.valid = true;
#endif // DEBUG_TASMOTA_DRIVER
Ftc532.state = FTC532_STATE_WAITING;
} }
} }
void ftc532_init(void) { // Initialize void ftc532_init(void) { // Initialize
if (!PinUsed(GPIO_FTC532)) { return; } if (!PinUsed(GPIO_FTC532)) { return; }
#ifdef DEBUG_TASMOTA_DRIVER
Ftc532.errors = 0;
Ftc532.valid = false;
#endif // DEBUG_TASMOTA_DRIVER
Ftc532.state = FTC532_STATE_WAITING; Ftc532.state = FTC532_STATE_WAITING;
Ftc532.rxtime = micros();
pinMode(Pin(GPIO_FTC532), INPUT_PULLUP); pinMode(Pin(GPIO_FTC532), INPUT_PULLUP);
Ftc532.rxtime = micros();
attachInterrupt(Pin(GPIO_FTC532), ftc532_ISR, RISING); attachInterrupt(Pin(GPIO_FTC532), ftc532_ISR, RISING);
Ftc532.present = true; Ftc532.present = true;
} }
void ftc532_update(void) { // Usually called every 50 ms void ftc532_update(void) { // Usually called every 50 ms
#ifdef DEBUG_TASMOTA_DRIVER if ((Ftc532.sample & 0xF0F0) == ((~Ftc532.sample & 0x0F0F) << 4) && (Ftc532.sample >> 8) == 0xF0) {
// WARNING: Reduce callback frequency if this code is enabled Ftc532.keys = Ftc532.sample & 0xF;
// if ((Ftc532.sample & 0xF) != ((~Ftc532.sample >> 4) & 0xF) || ((Ftc532.sample >> 8) & 0xF) != ((~Ftc532.sample >> 12) & 0xF)) { if (Ftc532.keys != Ftc532.old_keys) {
// AddLog_P(LOG_LEVEL_DEBUG, PSTR("FTC: inverted sample does not match %x %x %x %x"), #if FTC532_DEBOUNCE > 1
// Ftc532.sample & 0xF, (~Ftc532.sample >> 4) & 0xF, (Ftc532.sample >> 8) & 0xF, (~Ftc532.sample >> 12) & 0xF); if (++Ftc532.key_cnt >= FTC532_DEBOUNCE) {
// } #endif // FTC532_DEBOUNCE > 1
#endif // DEBUG_TASMOTA_DRIVER #ifdef DEBUG_FTC532
Ftc532.keys = (Ftc532.sample & 0xF) | ((Ftc532.sample >> 4) & 0xF0); AddLog_P(LOG_LEVEL_DEBUG, PSTR("FTC: SAM=%04X KEY=%X OLD=%X INV=%u NOI=%u FRM=%u OK=%u TIME=%lu Pin=%u"),
if (Ftc532.keys != Ftc532.old_keys) { Ftc532.sample, Ftc532.keys, Ftc532.old_keys, Ftc532.e_inv, Ftc532.e_noise, Ftc532.e_frame,
#ifdef DEBUG_TASMOTA_DRIVER Ftc532.valid, Ftc532.rxtime, Pin(GPIO_FTC532));
AddLog_P(LOG_LEVEL_DEBUG, PSTR("FTC: SAM=%04X KEY=%02X OLD=%02X ERR=%u OK=%u TIME=%lu Pin=%u"), #endif // DEBUG_FTC532
Ftc532.sample, Ftc532.keys, Ftc532.old_keys, Ftc532.errors, Ftc532.valid, Ftc532.rxtime, Pin(GPIO_FTC532)); ftc532_publish();
#endif // DEBUG_TASMOTA_DRIVER Ftc532.old_keys = Ftc532.keys;
ftc532_publish(); #if FTC532_DEBOUNCE > 1
Ftc532.old_keys = Ftc532.keys; Ftc532.key_cnt = 0;
}
} else {
Ftc532.key_cnt = 0;
#endif // FTC532_DEBOUNCE > 1
}
} }
#ifdef DEBUG_FTC532
else {
++Ftc532.e_inv;
AddLog_P(LOG_LEVEL_DEBUG, PSTR("FTC: ILL SAM=%04X"), Ftc532.sample);
}
#endif // DEBUG_FTC532
} }
void ftc532_show() { void ftc532_show() {
ResponseAppend_P(PSTR(",%s%02X\"}"), ftc532_json, Ftc532.keys); // Hex keys need JSON quotes ResponseAppend_P(PSTR(",%s%02X\"}"), ftc532_json, Ftc532.keys); // Hex keys need JSON quotes
} }
void ftc532_publish(void) { void ftc532_publish(void) {
Response_P(PSTR("{%s%02X\"}}"), ftc532_json, Ftc532.keys); // Hex keys need JSON quotes Response_P(PSTR("{%s%02X\"}}"), ftc532_json, Ftc532.keys); // Hex keys need JSON quotes
MqttPublishTeleSensor(); MqttPublishTeleSensor();
} }