diff --git a/I2CManager.cpp b/I2CManager.cpp
index 2c115fa..d13e081 100644
--- a/I2CManager.cpp
+++ b/I2CManager.cpp
@@ -46,14 +46,22 @@
// Helper function for listing device types
static const FSH * guessI2CDeviceType(uint8_t address) {
- if (address >= 0x20 && address <= 0x26)
+ if (address == 0x1A)
+ // 0x09-0x18 selectable, but for now handle the default
+ return F("Piicodev 865/915MHz Transceiver");
+ else if (address == 0x1C)
+ return F("QMC6310 Magnetometer");
+ else if (address >= 0x20 && address <= 0x26)
return F("GPIO Expander");
else if (address == 0x27)
return F("GPIO Expander or LCD Display");
else if (address == 0x29)
return F("Time-of-flight sensor");
+ else if (address == 0x34)
+ return F("TCA8418 keypad scanner");
else if (address >= 0x3c && address <= 0x3d)
- return F("OLED Display");
+ // 0x3c can also be an HMC883L magnetometer
+ return F("OLED Display or HMC583L Magnetometer");
else if (address >= 0x48 && address <= 0x57) // SC16IS752x UART detection
return F("SC16IS75x UART");
else if (address >= 0x48 && address <= 0x4f)
@@ -62,6 +70,8 @@ static const FSH * guessI2CDeviceType(uint8_t address) {
return F("PWM");
else if (address >= 0x50 && address <= 0x5f)
return F("EEPROM");
+ else if (address == 0x60)
+ return F("Adafruit NeoPixel Driver");
else if (address == 0x68)
return F("Real-time clock");
else if (address >= 0x70 && address <= 0x77)
diff --git a/IODevice.h b/IODevice.h
index 6c70f5f..31e2eb2 100644
--- a/IODevice.h
+++ b/IODevice.h
@@ -547,6 +547,7 @@ protected:
#include "IO_duinoNodes.h"
#include "IO_EXIOExpander.h"
#include "IO_trainbrains.h"
+#include "IO_TCA8418.h"
#endif // iodevice_h
diff --git a/IO_TCA8418.h b/IO_TCA8418.h
new file mode 100644
index 0000000..4ca143e
--- /dev/null
+++ b/IO_TCA8418.h
@@ -0,0 +1,181 @@
+/*
+ * © 2023, 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 .
+ */
+
+#ifndef io_tca8418_h
+#define io_tca8418_h
+
+#include "IO_GPIOBase.h"
+#include "FSH.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * IODevice subclass for TCA8418 80-key keypad encoder, which we'll treat as 64 of the possible
+ * 80 inputs for now, in an 8x8 matrix only, although 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).
+ */
+
+class TCA8418 : public GPIOBase {
+public:
+ static void create(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
+ if (checkNoOverlap(vpin, nPins, i2cAddress))
+ new TCA8418(vpin, (nPins = (nPins > 18) ? 18 : nPins), i2cAddress, interruptPin);
+ }
+
+private:
+ // Constructor
+ TCA8418(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1)
+ : GPIOBase((FSH *)F("TCA8418"), vpin, nPins, i2cAddress, interruptPin)
+ {
+ uint8_t receiveBuffer[1];
+ uint8_t commandBuffer[1];
+ uint8_t status;
+
+ commandBuffer[0] = REG_INT_STAT; // Check interrupt status
+ status = I2CManager.read(_I2CAddress, receiveBuffer, sizeof(receiveBuffer), commandBuffer, sizeof(commandBuffer));
+ if (status == I2C_STATUS_OK) {
+ DIAG(F("TCA8418 Interrupt status was: %x"), receiveBuffer[0]);
+ }
+ else
+ DIAG(F("TCA8418 Interrupt status failed to read!"));
+ // requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
+ // outputBuffer, sizeof(outputBuffer));
+ // outputBuffer[0] = REG_GPIOA;
+ }
+ void _writeGpioPort() override {
+ // I2CManager.write(_I2CAddress, 3, REG_GPIOA, _portOutputState, _portOutputState>>8);
+ }
+ void _writePullups() override {
+ // Set pullups only for in-use pins. This prevents pullup being set for a pin that
+ // is intended for use as an output but hasn't been written to yet.
+ uint32_t temp = _portPullup & _portInUse;
+ // I2CManager.write(_I2CAddress, 3, REG_GPPUA, temp, temp>>8);
+ }
+ void _writePortModes() override {
+ // Write 0 to each GPIO_DIRn for in-use pins that are inputs, 1 for outputs
+ uint64_t temp = _portMode & _portInUse;
+ DIAG(F("TCA8418 writing Port Mode: %x, to GPIO_DIRs"), temp);
+ DIAG(F("TCA8418 writing Port Mode: %x, to GPIO_DIR1"), (temp&0xFF));
+ I2CManager.write(_I2CAddress, 2, REG_GPIO_DIR1, (temp&0xFF));
+ DIAG(F("TCA8418 writing Port Mode: %x, to GPIO_DIR2"), ((temp&0xFF00)>>8));
+ I2CManager.write(_I2CAddress, 2, REG_GPIO_DIR2, ((temp&0xFF00)>>8));
+ DIAG(F("TCA8418 writing Port Mode: %x, to GPIO_DIR3"), (temp&0x30000)>>16);
+ I2CManager.write(_I2CAddress, 2, REG_GPIO_DIR3, ((temp&0x30000)>>16));
+
+ // Enable interrupt for in-use pins which are inputs (_portMode=0)
+ // TCA8418 has interrupt enables per pin, but must be configured for low->high
+ // or high->low... unlike the MCP23017
+ temp = ~_portMode & _portInUse;
+ DIAG(F("TCA8418 writing interrupt Port Mode: %x, to GPIO_INT_ENs"), temp);
+ DIAG(F("TCA8418 writing interrupt Port Mode: %x, to GPIO_INT_EN1"), (temp&0xFF));
+ I2CManager.write(_I2CAddress, 2, REG_GPIO_INT_EN1, (temp&0xFF));
+ DIAG(F("TCA8418 writing interrupt Port Mode: %x, to GPIO_INT_EN2"), ((temp&0xFF00)>>8));
+ I2CManager.write(_I2CAddress, 2, REG_GPIO_INT_EN2, ((temp&0xFF00)>>8));
+ DIAG(F("TCA8418 writing interrupt Port Mode: %x, to GPIO_INT_EN3"), (temp&0x30000)>>16);
+ I2CManager.write(_I2CAddress, 2, REG_GPIO_INT_EN3, ((temp&0x30000)>>16));
+ // I2CManager.write(_I2CAddress, 3, REG_INTCONA, 0x00, 0x00);
+ // I2CManager.write(_I2CAddress, 3, REG_GPINTENA, temp, temp>>8);
+ }
+ void _readGpioPort(bool immediate) override {
+ // if (immediate) {
+ // uint8_t buffer[2];
+ // I2CManager.read(_I2CAddress, buffer, 2, 1, REG_GPIOA);
+ // _portInputState = ((uint16_t)buffer[1]<<8) | buffer[0] | _portMode;
+ // } else {
+ // // Queue new request
+ // requestBlock.wait(); // Wait for preceding operation to complete
+ // // Issue new request to read GPIO register
+ // I2CManager.queueRequest(&requestBlock);
+ // }
+ }
+ // This function is invoked when an I/O operation on the requestBlock completes.
+ void _processCompletion(uint8_t status) override {
+ // if (status == I2C_STATUS_OK)
+ // _portInputState = (((uint16_t)inputBuffer[1]<<8) | inputBuffer[0]) | _portMode;
+ // else
+ // _portInputState = 0xffff;
+ }
+
+ void _setupDevice() override {
+ // IOCON is set MIRROR=1, ODR=1 (open drain shared interrupt pin)
+ // I2CManager.write(_I2CAddress, 2, REG_IOCON, 0x44);
+ _writePortModes();
+ _writePullups();
+ _writeGpioPort();
+ }
+
+ enum
+ {
+ REG_FIRST_RESERVED = 0x00,
+ REG_CFG = 0x01,
+ REG_INT_STAT = 0x02,
+ REG_KEY_LCK_EC = 0x03,
+ REG_KEY_EVENT_A = 0x04,
+ REG_KEY_EVENT_B = 0x05,
+ REG_KEY_EVENT_C = 0x06,
+ REG_KEY_EVENT_D = 0x07,
+ REG_KEY_EVENT_E = 0x08,
+ REG_KEY_EVENT_F = 0x09,
+ REG_KEY_EVENT_G = 0x0A,
+ REG_KEY_EVENT_H = 0x0B,
+ REG_KEY_EVENT_I = 0x0C,
+ REG_KEY_EVENT_J = 0x0D,
+ REG_KP_LCK_TIMER = 0x0E,
+ REG_UNLOCK1 = 0x0F,
+ REG_UNLOCK2 = 0x10,
+ REG_GPIO_INT_STAT1 = 0x11,
+ REG_GPIO_INT_STAT2 = 0x12,
+ REG_GPIO_INT_STAT3 = 0x13,
+ REG_GPIO_DAT_STAT1 = 0x14,
+ REG_GPIO_DAT_STAT2 = 0x15,
+ REG_GPIO_DAT_STAT3 = 0x16,
+ REG_GPIO_DAT_OUT1 = 0x17,
+ REG_GPIO_DAT_OUT2 = 0x18,
+ REG_GPIO_DAT_OUT3 = 0x19,
+ REG_GPIO_INT_EN1 = 0x1A,
+ REG_GPIO_INT_EN2 = 0x1B,
+ REG_GPIO_INT_EN3 = 0x1C,
+ REG_KP_GPIO1 = 0x1D,
+ REG_KP_GPIO2 = 0x1E,
+ REG_KP_GPIO3 = 0x1F,
+ REG_GPI_EM1 = 0x20,
+ REG_GPI_EM2 = 0x21,
+ REG_GPI_EM3 = 0x22,
+ REG_GPIO_DIR1 = 0x23,
+ REG_GPIO_DIR2 = 0x24,
+ REG_GPIO_DIR3 = 0x25,
+ REG_GPIO_INT_LVL1 = 0x26,
+ REG_GPIO_INT_LVL2 = 0x27,
+ REG_GPIO_INT_LVL3 = 0x28,
+ REG_DEBOUNCE_DIS1 = 0x29,
+ REG_DEBOUNCE_DIS2 = 0x2A,
+ REG_DEBOUNCE_DIS3 = 0x2B,
+ REG_GPIO_PULL1 = 0x2C,
+ REG_GPIO_PULL2 = 0x2D,
+ REG_GPIO_PULL3 = 0x2E,
+ REG_LAST_RESERVED = 0x2F,
+ };
+};
+
+#endif
\ No newline at end of file
diff --git a/platformio.ini b/platformio.ini
index a03ff61..a2ff2b4 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -218,14 +218,14 @@ monitor_echo = yes
; Commented out by default as the F446ZE needs variant files
; installed before you can let PlatformIO see this
;
-; [env:Nucleo-F446ZE]
-; platform = ststm32
-; board = nucleo_f446ze
-; framework = arduino
-; lib_deps = ${env.lib_deps}
-; build_flags = -std=c++17 -Os -g2 -Wunused-variable
-; monitor_speed = 115200
-; monitor_echo = yes
+[env:Nucleo-F446ZE]
+platform = ststm32
+board = nucleo_f446ze
+framework = arduino
+lib_deps = ${env.lib_deps}
+build_flags = -std=c++17 -Os -g2 -Wunused-variable
+monitor_speed = 115200
+monitor_echo = yes
; Commented out by default as the F412ZG needs variant files
; installed before you can let PlatformIO see this
diff --git a/version.h b/version.h
index dcc7c6e..aa13e35 100644
--- a/version.h
+++ b/version.h
@@ -3,7 +3,8 @@
#include "StringFormatter.h"
-#define VERSION "5.2.36"
+#define VERSION "5.2.36i2c"
+// 5.2.36i2c - Add various I2C drivers, including TCA8418 and Adafruit NeoPixel Driver initially
// 5.2.36 - Variable frequency for DC mode
// 5.2.35 - Bugfix: Make DCC Extended Accessories follow RCN-213
// 5.2.34 - Command fopr DCC Extended Accessories