mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-11-26 17:46:14 +01:00
Compare commits
6 Commits
158cb47474
...
e78c2f9794
Author | SHA1 | Date | |
---|---|---|---|
|
e78c2f9794 | ||
|
7177affad4 | ||
|
c50f3e016c | ||
|
f910e75da0 | ||
|
535dcabcec | ||
|
1d18d5dea5 |
|
@ -377,4 +377,3 @@ void CommandDistributor::setVirtualLCDSerial(Print * stream) {
|
||||||
Print* CommandDistributor::virtualLCDSerial=&USB_SERIAL;
|
Print* CommandDistributor::virtualLCDSerial=&USB_SERIAL;
|
||||||
byte CommandDistributor::virtualLCDClient=0xFF;
|
byte CommandDistributor::virtualLCDClient=0xFF;
|
||||||
byte CommandDistributor::rememberVLCDClient=0;
|
byte CommandDistributor::rememberVLCDClient=0;
|
||||||
|
|
||||||
|
|
|
@ -483,4 +483,3 @@ void DCCACK::checkAck(byte sentResetsSincePacket) {
|
||||||
}
|
}
|
||||||
ackPulseStart=0; // We have detected a too-short or too-long pulse so ignore and wait for next leading edge
|
ackPulseStart=0; // We have detected a too-short or too-long pulse so ignore and wait for next leading edge
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -481,6 +481,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||||
DCC::writeCVByteMain(p[0], p[1], p[2]);
|
DCC::writeCVByteMain(p[0], p[1], p[2]);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
#ifdef HAS_ENOUGH_MEMORY
|
||||||
case 'r': // READ CV on MAIN <r CAB CV> Requires Railcom
|
case 'r': // READ CV on MAIN <r CAB CV> Requires Railcom
|
||||||
if (params != 2)
|
if (params != 2)
|
||||||
break;
|
break;
|
||||||
|
@ -488,6 +489,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||||
if (!stashCallback(stream, p, ringStream)) break;
|
if (!stashCallback(stream, p, ringStream)) break;
|
||||||
DCC::readCVByteMain(p[0], p[1],callback_r);
|
DCC::readCVByteMain(p[0], p[1],callback_r);
|
||||||
return;
|
return;
|
||||||
|
#endif
|
||||||
|
|
||||||
case 'b': // WRITE CV BIT ON MAIN <b CAB CV BIT VALUE>
|
case 'b': // WRITE CV BIT ON MAIN <b CAB CV BIT VALUE>
|
||||||
if (params != 4)
|
if (params != 4)
|
||||||
|
@ -1129,7 +1131,7 @@ bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) {
|
||||||
DCC::setGlobalSpeedsteps(128);
|
DCC::setGlobalSpeedsteps(128);
|
||||||
DIAG(F("128 Speedsteps"));
|
DIAG(F("128 Speedsteps"));
|
||||||
return true;
|
return true;
|
||||||
#if defined(HAS_ENOUGH_MEMORY) && !defined(ARDUINO_ARCH_UNO) && !defined(ARDUINO_ARCH_ESP32)
|
#if defined(HAS_ENOUGH_MEMORY)
|
||||||
case "RAILCOM"_hk:
|
case "RAILCOM"_hk:
|
||||||
{ // <C RAILCOM ON|OFF|DEBUG >
|
{ // <C RAILCOM ON|OFF|DEBUG >
|
||||||
if (params<2) return false;
|
if (params<2) return false;
|
||||||
|
@ -1213,11 +1215,11 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||||
Diag::CMD = onOff;
|
Diag::CMD = onOff;
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
#ifdef HAS_ENOUGH_MEMORY
|
||||||
case "RAILCOM"_hk: // <D RAILCOM ON/OFF>
|
case "RAILCOM"_hk: // <D RAILCOM ON/OFF>
|
||||||
Diag::RAILCOM = onOff;
|
Diag::RAILCOM = onOff;
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
#ifdef HAS_ENOUGH_MEMORY
|
|
||||||
case "WIFI"_hk: // <D WIFI ON/OFF>
|
case "WIFI"_hk: // <D WIFI ON/OFF>
|
||||||
Diag::WIFI = onOff;
|
Diag::WIFI = onOff;
|
||||||
return true;
|
return true;
|
||||||
|
|
19
DCCRMT.cpp
19
DCCRMT.cpp
|
@ -17,6 +17,25 @@
|
||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* RMT has "channels" which us FIFO RAM where you place what you want to send
|
||||||
|
* or receive. Channels can be merged to get more words per channel.
|
||||||
|
*
|
||||||
|
* WROOM: 8 channels total of 512 words, 64 words per channel. We use currently
|
||||||
|
* channel 0+1 for 128 words for DCC MAIN and 2+3 for DCC PROG.
|
||||||
|
*
|
||||||
|
* S3: 8 channels total of 384 words. 4 channels dedicated for TX and 4 channels
|
||||||
|
* dedicated for RX. 48 words per channel. So for TX there are 4 channels and we
|
||||||
|
* could use them with 96 words for MAIN and PROG if DCC data does fit in there.
|
||||||
|
*
|
||||||
|
* C3: 4 channels total of 192 words. As we do not use RX we can use all for TX
|
||||||
|
* so the situation is the same as for the -S3
|
||||||
|
*
|
||||||
|
* C6, H2: 4 channels total of 192 words. 2 channels dedictaed for TX and
|
||||||
|
* 2 channels dedicated for RX. Half RMT capacity compared to the C3.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
#if defined(ARDUINO_ARCH_ESP32)
|
||||||
#include "defines.h"
|
#include "defines.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
|
|
|
@ -190,6 +190,8 @@ void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t f) {
|
||||||
}
|
}
|
||||||
void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t fbits) {
|
void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t fbits) {
|
||||||
#if defined(ARDUINO_AVR_UNO)
|
#if defined(ARDUINO_AVR_UNO)
|
||||||
|
(void)fbits;
|
||||||
|
(void) pin;
|
||||||
// Not worth doin something here as:
|
// Not worth doin something here as:
|
||||||
// If we are on pin 9 or 10 we are on Timer1 and we can not touch Timer1 as that is our DCC source.
|
// If we are on pin 9 or 10 we are on Timer1 and we can not touch Timer1 as that is our DCC source.
|
||||||
// If we are on pin 5 or 6 we are on Timer 0 ad we can not touch Timer0 as that is millis() etc.
|
// If we are on pin 5 or 6 we are on Timer 0 ad we can not touch Timer0 as that is millis() etc.
|
||||||
|
|
|
@ -324,4 +324,3 @@ void ADCee::begin() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif //ESP32
|
#endif //ESP32
|
||||||
|
|
||||||
|
|
|
@ -74,12 +74,14 @@ void DCCWaveform::loop() {
|
||||||
void DCCWaveform::interruptHandler() {
|
void DCCWaveform::interruptHandler() {
|
||||||
// call the timer edge sensitive actions for progtrack and maintrack
|
// call the timer edge sensitive actions for progtrack and maintrack
|
||||||
// member functions would be cleaner but have more overhead
|
// member functions would be cleaner but have more overhead
|
||||||
|
#if defined(HAS_ENOUGH_MEMORY)
|
||||||
if (cutoutNextTime) {
|
if (cutoutNextTime) {
|
||||||
cutoutNextTime=false;
|
cutoutNextTime=false;
|
||||||
railcomSampleWindow=false; // about to cutout, stop reading railcom data.
|
railcomSampleWindow=false; // about to cutout, stop reading railcom data.
|
||||||
railcomCutoutCounter++;
|
railcomCutoutCounter++;
|
||||||
DCCTimer::startRailcomTimer(9);
|
DCCTimer::startRailcomTimer(9);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
byte sigMain=signalTransform[mainTrack.state];
|
byte sigMain=signalTransform[mainTrack.state];
|
||||||
byte sigProg=TrackManager::progTrackSyncMain? sigMain : signalTransform[progTrack.state];
|
byte sigProg=TrackManager::progTrackSyncMain? sigMain : signalTransform[progTrack.state];
|
||||||
|
|
||||||
|
@ -160,6 +162,8 @@ void DCCWaveform::interrupt2() {
|
||||||
reminderWindowOpen=transmitRepeats==0 && remainingPreambles<10 && remainingPreambles>1;
|
reminderWindowOpen=transmitRepeats==0 && remainingPreambles<10 && remainingPreambles>1;
|
||||||
if (remainingPreambles==1)
|
if (remainingPreambles==1)
|
||||||
promotePendingPacket();
|
promotePendingPacket();
|
||||||
|
|
||||||
|
#if defined(HAS_ENOUGH_MEMORY)
|
||||||
else if (isMainTrack && railcomActive) {
|
else if (isMainTrack && railcomActive) {
|
||||||
if (remainingPreambles==(requiredPreambles-1)) {
|
if (remainingPreambles==(requiredPreambles-1)) {
|
||||||
// First look if we need to start a railcom cutout on next interrupt
|
// First look if we need to start a railcom cutout on next interrupt
|
||||||
|
@ -179,6 +183,7 @@ void DCCWaveform::interrupt2() {
|
||||||
DCCTimer::ackRailcomTimer();
|
DCCTimer::ackRailcomTimer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
// Update free memory diagnostic as we don't have anything else to do this time.
|
// Update free memory diagnostic as we don't have anything else to do this time.
|
||||||
// Allow for checkAck and its called functions using 22 bytes more.
|
// Allow for checkAck and its called functions using 22 bytes more.
|
||||||
else DCCTimer::updateMinimumFreeMemoryISR(22);
|
else DCCTimer::updateMinimumFreeMemoryISR(22);
|
||||||
|
@ -274,8 +279,14 @@ DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
|
||||||
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
||||||
RMTChannel *DCCWaveform::rmtMainChannel = NULL;
|
RMTChannel *DCCWaveform::rmtMainChannel = NULL;
|
||||||
RMTChannel *DCCWaveform::rmtProgChannel = NULL;
|
RMTChannel *DCCWaveform::rmtProgChannel = NULL;
|
||||||
volatile bool DCCWaveform::railcomSampleWindow=false; // true during packet transmit
|
|
||||||
|
|
||||||
|
bool DCCWaveform::railcomPossible=false; // High accuracy only
|
||||||
|
volatile bool DCCWaveform::railcomActive=false; // switched on by user
|
||||||
|
volatile bool DCCWaveform::railcomDebug=false; // switched on by user
|
||||||
|
volatile bool DCCWaveform::railcomSampleWindow=false; // true during packet transmit
|
||||||
|
volatile byte DCCWaveform::railcomCutoutCounter=0; // cyclic cutout
|
||||||
|
volatile byte DCCWaveform::railcomLastAddressHigh=0;
|
||||||
|
volatile byte DCCWaveform::railcomLastAddressLow=0;
|
||||||
|
|
||||||
DCCWaveform::DCCWaveform(byte preambleBits, bool isMain) {
|
DCCWaveform::DCCWaveform(byte preambleBits, bool isMain) {
|
||||||
isMainTrack = isMain;
|
isMainTrack = isMain;
|
||||||
|
|
|
@ -377,4 +377,3 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -627,4 +627,3 @@ bool ArduinoPins::fastReadDigital(uint8_t pin) {
|
||||||
#endif
|
#endif
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,4 +65,3 @@ void DCCAccessoryDecoder::_display() {
|
||||||
DIAG(F("DCCAccessoryDecoder Configured on Vpins:%u-%u Addresses %d/%d-%d/%d)"), _firstVpin, _firstVpin+_nPins-1,
|
DIAG(F("DCCAccessoryDecoder Configured on Vpins:%u-%u Addresses %d/%d-%d/%d)"), _firstVpin, _firstVpin+_nPins-1,
|
||||||
ADDRESS(_packedAddress), SUBADDRESS(_packedAddress), ADDRESS(endAddress), SUBADDRESS(endAddress));
|
ADDRESS(_packedAddress), SUBADDRESS(_packedAddress), ADDRESS(endAddress), SUBADDRESS(endAddress));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -141,4 +141,3 @@ const byte _DIR_MASK = 0x30;
|
||||||
void EncoderThrottle::_display() {
|
void EncoderThrottle::_display() {
|
||||||
DIAG(F("DRIVE vpin %d loco %d notch %d"),_firstVpin,_locoid,_notch);
|
DIAG(F("DRIVE vpin %d loco %d notch %d"),_firstVpin,_locoid,_notch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -206,6 +206,7 @@ private:
|
||||||
|
|
||||||
// loop called by HAL supervisor
|
// loop called by HAL supervisor
|
||||||
void _loop(unsigned long currentMicros) override {
|
void _loop(unsigned long currentMicros) override {
|
||||||
|
(void)currentMicros;
|
||||||
if (!_showPendimg) return;
|
if (!_showPendimg) return;
|
||||||
byte showBuffer[]={SEESAW_NEOPIXEL_BASE,SEESAW_NEOPIXEL_SHOW};
|
byte showBuffer[]={SEESAW_NEOPIXEL_BASE,SEESAW_NEOPIXEL_SHOW};
|
||||||
I2CManager.write(_I2CAddress,showBuffer,sizeof(showBuffer));
|
I2CManager.write(_I2CAddress,showBuffer,sizeof(showBuffer));
|
||||||
|
@ -291,7 +292,7 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void transmit(uint16_t pixel, bool show=true) {
|
void transmit(uint16_t pixel) {
|
||||||
byte buffer[]={SEESAW_NEOPIXEL_BASE,SEESAW_NEOPIXEL_BUF,0x00,0x00,0x00,0x00,0x00};
|
byte buffer[]={SEESAW_NEOPIXEL_BASE,SEESAW_NEOPIXEL_BUF,0x00,0x00,0x00,0x00,0x00};
|
||||||
uint16_t offset= pixel * _bytesPerPixel;
|
uint16_t offset= pixel * _bytesPerPixel;
|
||||||
buffer[2]=(byte)(offset>>8);
|
buffer[2]=(byte)(offset>>8);
|
||||||
|
|
|
@ -30,4 +30,3 @@
|
||||||
//
|
//
|
||||||
const uint8_t FLASH Servo::_bounceProfile[30] =
|
const uint8_t FLASH Servo::_bounceProfile[30] =
|
||||||
{0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100};
|
{0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100};
|
||||||
|
|
||||||
|
|
463
IO_TCA8418.h
463
IO_TCA8418.h
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* © 2023, Paul M. Antoine
|
* © 2023-2024, Paul M. Antoine
|
||||||
* © 2021, Neil McKechnie. All rights reserved.
|
* © 2021, Neil McKechnie. All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of DCC-EX API
|
* This file is part of DCC-EX API
|
||||||
|
@ -21,163 +21,350 @@
|
||||||
#ifndef io_tca8418_h
|
#ifndef io_tca8418_h
|
||||||
#define io_tca8418_h
|
#define io_tca8418_h
|
||||||
|
|
||||||
#include "IO_GPIOBase.h"
|
#include "IODevice.h"
|
||||||
|
#include "I2CManager.h"
|
||||||
|
#include "DIAG.h"
|
||||||
#include "FSH.h"
|
#include "FSH.h"
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
/*
|
/*
|
||||||
* IODevice subclass for TCA8418 80-key keypad encoder, which we'll treat as 64 of the possible
|
* IODevice subclass for TCA8418 80-key keypad encoder, which we'll treat as 80 available VPINs where
|
||||||
* 80 inputs for now, in an 8x8 matrix only, although the datasheet says:
|
* 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.
|
* 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
|
* 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
|
* 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
|
* 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).
|
* 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 GPIOBase<uint64_t> {
|
class TCA8418 : public IODevice {
|
||||||
public:
|
public:
|
||||||
static void create(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
|
|
||||||
if (checkNoOverlap(vpin, nPins, i2cAddress))
|
static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
|
||||||
// temporarily use the simple 18-pin GPIO mode - we'll switch to 8x8 matrix once this works
|
if (checkNoOverlap(firstVpin, nPins, i2cAddress))
|
||||||
new TCA8418(vpin, (nPins = (nPins > 18) ? 18 : nPins), i2cAddress, interruptPin);
|
new TCA8418(firstVpin, (nPins = (nPins > 80) ? 80 : nPins), i2cAddress, interruptPin);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Constructor
|
|
||||||
TCA8418(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1)
|
|
||||||
: GPIOBase<uint64_t>((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
|
uint8_t* _digitalInputStates = NULL; // Array of pin states
|
||||||
status = I2CManager.read(_I2CAddress, receiveBuffer, sizeof(receiveBuffer), commandBuffer, sizeof(commandBuffer));
|
uint8_t _digitalPinBytes = 0; // Number of bytes in pin state array
|
||||||
if (status == I2C_STATUS_OK) {
|
|
||||||
DIAG(F("TCA8418 Interrupt status was: %x"), receiveBuffer[0]);
|
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
|
else
|
||||||
DIAG(F("TCA8418 Interrupt status failed to read!"));
|
DIAG(F("TCA8418 I2C: key event %d discarded, outside Vpin range"), key);
|
||||||
// requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
|
_numKeyEvents--; // One less key event to get
|
||||||
// outputBuffer, sizeof(outputBuffer));
|
if (_numKeyEvents != 0)
|
||||||
// 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;
|
|
||||||
(void)temp; // Chris did this so he could see warnings that mattered
|
|
||||||
// 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 {
|
|
||||||
DIAG(F("TCA8418 setupDevice() called"));
|
|
||||||
// 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,
|
// DIAG(F("TCA8418 I2C: more keys in read event queue, # waiting is: %x"), _numKeyEvents);
|
||||||
REG_CFG = 0x01,
|
// We could read each key event waiting in a synchronous loop, which may prove preferable
|
||||||
REG_INT_STAT = 0x02,
|
// but for now, schedule an async read of the first key event in the queue
|
||||||
REG_KEY_LCK_EC = 0x03,
|
_commandBuffer[0] = REG_KEY_EVENT_A;
|
||||||
REG_KEY_EVENT_A = 0x04,
|
I2CManager.read(_I2CAddress, _inputBuffer, 1, _commandBuffer, 1, &_i2crb); // non-blocking read
|
||||||
REG_KEY_EVENT_B = 0x05,
|
}
|
||||||
REG_KEY_EVENT_C = 0x06,
|
else {
|
||||||
REG_KEY_EVENT_D = 0x07,
|
// DIAG(F("TCA8418 I2C: no more keys in read event queue"));
|
||||||
REG_KEY_EVENT_E = 0x08,
|
// Clear any pending interrupts
|
||||||
REG_KEY_EVENT_F = 0x09,
|
I2CManager.write(_I2CAddress, 2, REG_INT_STAT, REG_STAT_K_INT);
|
||||||
REG_KEY_EVENT_G = 0x0A,
|
_readState = RDS_IDLE; // Shift to IDLE
|
||||||
REG_KEY_EVENT_H = 0x0B,
|
return;
|
||||||
REG_KEY_EVENT_I = 0x0C,
|
}
|
||||||
REG_KEY_EVENT_J = 0x0D,
|
}
|
||||||
REG_KP_LCK_TIMER = 0x0E,
|
} else
|
||||||
REG_UNLOCK1 = 0x0F,
|
reportError(status, false); // report eror but don't go offline.
|
||||||
REG_UNLOCK2 = 0x10,
|
}
|
||||||
REG_GPIO_INT_STAT1 = 0x11,
|
|
||||||
REG_GPIO_INT_STAT2 = 0x12,
|
// If we're not doing anything now, check to see if we have an interrupt pin configured and it is low,
|
||||||
REG_GPIO_INT_STAT3 = 0x13,
|
// or if our timer has elapsed and we should check anyway in case the interrupt pin is disconnected.
|
||||||
REG_GPIO_DAT_STAT1 = 0x14,
|
if (_readState == RDS_IDLE) {
|
||||||
REG_GPIO_DAT_STAT2 = 0x15,
|
if ((_gpioInterruptsEnabled && !digitalRead(_gpioInterruptPin)) ||
|
||||||
REG_GPIO_DAT_STAT3 = 0x16,
|
((currentMicros - _lastEventRead) > _eventRefresh))
|
||||||
REG_GPIO_DAT_OUT1 = 0x17,
|
{
|
||||||
REG_GPIO_DAT_OUT2 = 0x18,
|
_commandBuffer[0] = REG_KEY_LCK_EC;
|
||||||
REG_GPIO_DAT_OUT3 = 0x19,
|
I2CManager.read(_I2CAddress, _inputBuffer, 1, _commandBuffer, 1, &_i2crb); // non-blocking read
|
||||||
REG_GPIO_INT_EN1 = 0x1A,
|
_lastEventRead = currentMicros;
|
||||||
REG_GPIO_INT_EN2 = 0x1B,
|
_readState = RDS_EVENT; // Shift to looking for key events!
|
||||||
REG_GPIO_INT_EN3 = 0x1C,
|
}
|
||||||
REG_KP_GPIO1 = 0x1D,
|
}
|
||||||
REG_KP_GPIO2 = 0x1E,
|
}
|
||||||
REG_KP_GPIO3 = 0x1F,
|
|
||||||
REG_GPI_EM1 = 0x20,
|
// Display device information and status
|
||||||
REG_GPI_EM2 = 0x21,
|
void _display() override {
|
||||||
REG_GPI_EM3 = 0x22,
|
DIAG(F("TCA8418 I2C:%s Vpins %u-%u%S"),
|
||||||
REG_GPIO_DIR1 = 0x23,
|
_I2CAddress.toString(),
|
||||||
REG_GPIO_DIR2 = 0x24,
|
_firstVpin, (_firstVpin+_nPins-1),
|
||||||
REG_GPIO_DIR3 = 0x25,
|
_deviceState == DEVSTATE_FAILED ? F(" OFFLINE") : F(""));
|
||||||
REG_GPIO_INT_LVL1 = 0x26,
|
if (_gpioInterruptsEnabled)
|
||||||
REG_GPIO_INT_LVL2 = 0x27,
|
DIAG(F("TCA8418 I2C:Interrupt on pin %d"), _gpioInterruptPin);
|
||||||
REG_GPIO_INT_LVL3 = 0x28,
|
}
|
||||||
REG_DEBOUNCE_DIS1 = 0x29,
|
|
||||||
REG_DEBOUNCE_DIS2 = 0x2A,
|
// Helper function for error handling
|
||||||
REG_DEBOUNCE_DIS3 = 0x2B,
|
void reportError(uint8_t status, bool fail=true) {
|
||||||
REG_GPIO_PULL1 = 0x2C,
|
DIAG(F("TCA8418 I2C:%s Error:%d (%S)"), _I2CAddress.toString(),
|
||||||
REG_GPIO_PULL2 = 0x2D,
|
status, I2CManager.getErrorMessage(status));
|
||||||
REG_GPIO_PULL3 = 0x2E,
|
if (fail)
|
||||||
REG_LAST_RESERVED = 0x2F,
|
_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
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -213,5 +213,3 @@ void TM1638::test(){
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,11 +14,15 @@ Some CPUs require very specific choice of brake pins etc to match their internal
|
||||||
- Nucleo ... TBA
|
- Nucleo ... TBA
|
||||||
|
|
||||||
Enabling the Railcom Cutout requires a `<C RAILCOM ON>` command. This can be added to myAutomation using `PARSE("<C RAILCOM ON>")`
|
Enabling the Railcom Cutout requires a `<C RAILCOM ON>` command. This can be added to myAutomation using `PARSE("<C RAILCOM ON>")`
|
||||||
|
Code to calculate the cutout position and provide synchronization for the sampling is in `DCCWaveform.cpp` (not ESP32)
|
||||||
|
and in general a global search for "railcom" will show all code changes that have been made to support this.
|
||||||
|
|
||||||
|
Code to actually implement the timing of the cutout is hihjly cpu dependent and can be found in gthe various implementations of `DCCTimer.h`. At this time only `DCCTimerAVR.cpp`has implemented this.
|
||||||
|
|
||||||
|
|
||||||
Reading Railcom data:
|
Reading Railcom data:
|
||||||
A new HAL handler has been added to process input from a 2-block railcom reader (Refer Henk) which operates as a 2 channel UART accessible over I2C. The reader(s) sit between the CS and the track and collect railcom data from locos during the cutout.
|
A new HAL handler (`IO_I2CRailcom.h`)has been added to process input from a 2-block railcom reader (Refer Henk) which operates as a 2 channel UART accessible over I2C. The reader(s) sit between the CS and the track and collect railcom data from locos during the cutout.
|
||||||
After the cutout the HAL driver reads the UARTs over I2C and passes the raw data to the CS logic for analysis.
|
After the cutout the HAL driver reads the UARTs over I2C and passes the raw data to the CS logic (`Railcom.cpp`)for analysis.
|
||||||
|
|
||||||
Each 2-block reader is described in myAutomation like `HAL(I2CRailcom,10000,2,0x48)` which will assign 2 channels on i2c address 0x48 with vpin numbers 10000 and 10001. If you only use the first channel in the reader, just asign one pin instead of two.
|
Each 2-block reader is described in myAutomation like `HAL(I2CRailcom,10000,2,0x48)` which will assign 2 channels on i2c address 0x48 with vpin numbers 10000 and 10001. If you only use the first channel in the reader, just asign one pin instead of two.
|
||||||
(Implementation notes.. potentially other readers are possible with suitable HAL drivers. There are however several touch-points with the code DCC Waveform code which helps the HAL driver to understand when the data is safe to sample, and how to interpret responses when the sender is unknown. )
|
(Implementation notes.. potentially other readers are possible with suitable HAL drivers. There are however several touch-points with the code DCC Waveform code which helps the HAL driver to understand when the data is safe to sample, and how to interpret responses when the sender is unknown. )
|
||||||
|
|
44
Release_Notes/TCA8418.md
Normal file
44
Release_Notes/TCA8418.md
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
## TCA8418 ##
|
||||||
|
|
||||||
|
The TCA8418 IC from Texas Instruments is a low cost and very capable GPIO and keyboard scanner. Used as a keyboard scanner, it has 8 rows of 10 columns of IO pins which allow encoding of up to 80 buttons. The IC is available on an Adafruit board with Qwiic I2C interconnect called the "Adafruit TCA8418 Keypad Matrix and GPIO Expander Breakout" and available here for the modest sum of $US6 or so: https://www.adafruit.com/product/4918
|
||||||
|
|
||||||
|
The great advantage of this IC is that the keyboard scanning is done continuously, and it has a 10-element event queue, so even if you don't get to the interrupt immediately, keypress and release events will be held for you. Since it's I2C its very easy to use with any DCC-EX command station.
|
||||||
|
|
||||||
|
The TCA8418 driver presently configures the IC in the full 8x10 keyboard scanning mode, and then maps each key down/key up event to the state of a single vpin for extremely easy use from within EX-RAIL and JMRI as each key looks like an individual sensor.
|
||||||
|
|
||||||
|
This is ideal for mimic panels where you may need a lot of buttons, but with this board you can use just 18 wires to handle as many as 80 buttons.
|
||||||
|
|
||||||
|
By adding a simple HAL statement to myAutomation.h it creates between 1 and 80 buttons it will report back.
|
||||||
|
|
||||||
|
`HAL(TCA8418, firstVpin, numPins, I2CAddress, interruptPin)`
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
`HAL(TCA8418, 300, 80, 0x34)`
|
||||||
|
|
||||||
|
Creates VPINs 300-379 which you can monitor with EX-RAIL, JMRI sensors etc.
|
||||||
|
|
||||||
|
With an 8x10 key event matrix, the events are numbered using the Rn row pins and Cn column pins 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 the first pin definition being VPIN 300, R0/C0 will be 300 + 0, and R7/C9 will be 300+79 or 379.
|
||||||
|
|
||||||
|
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, 21)`
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
|
@ -41,5 +41,3 @@ size_t StringBuffer::write(uint8_t b) {
|
||||||
_buffer[_pos_write]='\0';
|
_buffer[_pos_write]='\0';
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -699,4 +699,3 @@ TRACK_MODE TrackManager::getMode(byte t) {
|
||||||
int16_t TrackManager::returnDCAddr(byte t) {
|
int16_t TrackManager::returnDCAddr(byte t) {
|
||||||
return (trackDCAddr[t]);
|
return (trackDCAddr[t]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -527,4 +527,3 @@
|
||||||
StringFormatter::send(stream, F("<H %d LCN %d>\n"), _turnoutData.id,
|
StringFormatter::send(stream, F("<H %d LCN %d>\n"), _turnoutData.id,
|
||||||
!_turnoutData.closed);
|
!_turnoutData.closed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
|
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
|
|
||||||
#define VERSION "5.2.85"
|
#define VERSION "5.2.86"
|
||||||
|
// 5.2.86 - IO_TCA8418 driver for keypad matrix input now fully functioning, including being able to use an interrupt pin
|
||||||
// 5.2.85 - IO_TM1638 driver, SEG7 Exrail macro and _s7 segment pattern generator.
|
// 5.2.85 - IO_TM1638 driver, SEG7 Exrail macro and _s7 segment pattern generator.
|
||||||
// 5.2.84 - Fix TrackManager setDCCSignal and setPROGSignal for STM32 shadowing of PORTG/PORTH - this time it really is correct!
|
// 5.2.84 - Fix TrackManager setDCCSignal and setPROGSignal for STM32 shadowing of PORTG/PORTH - this time it really is correct!
|
||||||
// 5.2.83 - Various STM32 related fixes for serial ports, I2C pullups now turned off, and shadowing of PORTG/PORTH for TrackManager now correct
|
// 5.2.83 - Various STM32 related fixes for serial ports, I2C pullups now turned off, and shadowing of PORTG/PORTH for TrackManager now correct
|
||||||
|
|
Loading…
Reference in New Issue
Block a user