1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-07-29 02:13:45 +02:00

Compare commits

...

58 Commits

Author SHA1 Message Date
Asbelos
2bbb5c1119 EXRAIL use neopixel range instead of loop 2024-09-20 12:13:21 +01:00
Asbelos
3aabb51888 Neopixel signals with blue-tint
See Release Notes file
2024-09-18 17:06:00 +01:00
Asbelos
8e6fe6df21 HAL write range 2024-09-12 08:35:26 +01:00
Asbelos
66e57b5ab2 Killblink on neopixel set. 2024-09-08 09:26:37 +01:00
Asbelos
360c426675 Merge branch 'devel' into devel-pauls-i2c-devices 2024-09-07 16:45:29 +01:00
Asbelos
dd16e0da97 Notes 2024-09-07 13:00:26 +01:00
Asbelos
e823db3d24 Neopixel change to 8,8,8 2024-09-07 11:16:30 +01:00
Asbelos
d3d6cc97fb Neopixel <o> cmd 2024-09-06 13:25:44 +01:00
Harald Barth
b026417efb EXTRAIL: Propagate a failed loco addr read to EXRAIL so it can be used as IFLOCO(-1) 2024-09-06 09:28:40 +02:00
Asbelos
03d8d5f93d Its working!! 2024-09-06 08:08:18 +01:00
Asbelos
235f3c82b5 Update IO_NeoPixel.h 2024-09-05 22:02:29 +01:00
Harald Barth
7ffbd9d0e8 Use port variable 2024-09-05 13:01:54 +02:00
Harald Barth
6fa5511670 version 2024-09-04 09:13:52 +02:00
Harald Barth
c07ac38ab1 EXRAIL: Catch CV read errors in the callback 2024-09-04 09:11:51 +02:00
Asbelos
530b77bbab NEOPIXEL driver and macros 2024-09-03 15:04:40 +01:00
Asbelos
2a895fbbd5 First compile neopixel driver 2024-09-03 11:26:17 +01:00
Asbelos
c6f2db7909 Merge branch 'devel' into devel-pauls-i2c-devices 2024-09-03 10:07:12 +01:00
Asbelos
a7df84b01c NEOPIXEL EXRAIL 2024-09-03 09:56:05 +01:00
Asbelos
ead6e5afa1 NEOPIXEL EXRAIL 2024-09-03 09:55:36 +01:00
Harald Barth
7395aa4af8 version 2024-08-29 13:46:44 +02:00
Harald Barth
2397b773d7 Bugfix: Enable CommandDistributor even for serials 4 to 6 2024-08-29 13:44:51 +02:00
Harald Barth
9a08f2df63 ESP32: Make Serial2 possible for commands 2024-08-29 13:41:37 +02:00
Harald Barth
ed853eef1d version 5.2.74 2024-08-08 10:49:59 +02:00
Harald Barth
05e77c924e Revert momentum additions, squashed
commit 4e57a80265.
2024-08-08 10:45:44 +02:00
Harald Barth
c5c5609fc6 ESP32: Turn always on the JOINed PROG track when it acts as MAIN 2024-08-06 07:30:01 +02:00
pmantoine
9c263062e4 STM32 bugfix PORTG and PORTH shadow ports 2024-08-04 18:08:27 +08:00
pmantoine
f39fd89fbd STM32 bugfix for PORTG and PORTH with thanks to Ash 2024-07-25 13:58:04 +08:00
Asbelos
4e57a80265 Squashed commit of the following:
commit 3ac2fff70d
Author: Asbelos <asbelos@btinternet.com>
Date:   Tue Jul 23 15:40:36 2024 +0100

    Create momentum.md

commit a08195332f
Author: Asbelos <asbelos@btinternet.com>
Date:   Mon Jul 22 21:57:47 2024 +0100

    Cleanup of DCC Class reminders

commit 002ec5f176
Author: Asbelos <asbelos@btinternet.com>
Date:   Mon Jul 22 12:42:43 2024 +0100

    Cleaning access to speedByte

commit 854ddb0c6c
Author: Asbelos <asbelos@btinternet.com>
Date:   Sun Jul 21 10:15:07 2024 +0100

    Fix momentum algorithm

commit 916d3baf63
Merge: ab72a75 27dc805
Author: Asbelos <asbelos@btinternet.com>
Date:   Fri Jul 19 10:14:06 2024 +0100

    Merge branch 'devel' into devel_momentum

commit ab72a75d8f
Author: Asbelos <asbelos@btinternet.com>
Date:   Fri Jul 19 08:33:50 2024 +0100

    EXRAIL MOMENTUM

commit 8a623aa1cb
Author: Asbelos <asbelos@btinternet.com>
Date:   Thu Jul 18 20:31:58 2024 +0100

     Momentum
2024-07-23 15:42:35 +01:00
Asbelos
27dc8059d7 Broadcast loco forgets. 2024-07-19 09:29:43 +01:00
Asbelos
dc2eae499f RocoDriver->EncoderThrottle 2024-07-18 09:39:32 +01:00
Asbelos
c518dcdc0b IO_RocoDriver 2024-07-12 13:18:26 +01:00
Asbelos
e6047f6693 Revert <l> for F31 2024-07-12 10:25:11 +01:00
Asbelos
96c4757cc6 EXTAIL AFTER debounce time 2024-07-10 10:58:22 +01:00
Asbelos
60e564df51 SETFREQ and <F DCFREQ 2024-07-10 09:57:03 +01:00
Asbelos
a8b4e39733 Speedup SETFREQ
Avoid calling the DCC packets for setfreq macro.
2024-07-04 17:20:37 +01:00
Ash-4
d705626f4a <0 PROG> updated to undo JOIN
Update of the commit message for 5.2.64
2024-06-30 21:29:34 -05:00
Ash-4
c97284c15f inrush overfault on stm32EC-Ash 2024-06-30 20:32:08 -05:00
pmantoine
df1f365c1e Add WIFI_LED option for ESP32, edits for config.example.h 2024-06-29 16:22:23 +08:00
Harald Barth
023c004842 version 5.2.62 2024-06-18 22:21:51 +02:00
Harald Barth
2481f1c5d6 Allow acks longer than 65535us and specify ack length in the <C ACK MAX 20 MS> format 2024-06-18 22:18:23 +02:00
Asbelos
7dadecb5df Typo in KeywordHasher 2024-06-13 16:09:28 +01:00
Asbelos
6ef312b510 5.2.61 2024-06-13 13:08:40 +01:00
Asbelos
97f9fb4813 Squashed commit of the following:
commit a2b3ee8b5d52c2eefa461ace8f95c7f782a58efc
Merge: fc1217b 3d6c935
Author: Asbelos <asbelos@btinternet.com>
Date:   Thu Jun 13 11:58:00 2024 +0100

    Merge branch 'devel' into devel_merg

commit fc1217b8fa27a83174a4cf3bb82666f075103637
Author: Asbelos <asbelos@btinternet.com>
Date:   Thu Jun 13 11:57:12 2024 +0100

    Update EXRAIL2Parser.cpp

commit b89508671c
Author: Asbelos <asbelos@btinternet.com>
Date:   Wed Jun 12 16:25:17 2024 +0100

    Separate <L> polling cycle

commit 9f1257bc6c
Merge: a2fb585 5f65fd5
Author: Asbelos <asbelos@btinternet.com>
Date:   Wed Jun 12 10:57:09 2024 +0100

    Merge branch 'devel' into devel_merg

commit a2fb58584f
Author: Asbelos <asbelos@btinternet.com>
Date:   Fri May 31 19:49:39 2024 +0100

    ACON/ACOF 32 bit + 1=OFF

commit fca4ea052e
Author: Asbelos <asbelos@btinternet.com>
Date:   Fri May 31 12:09:38 2024 +0100

    Rename to ACON/ACOF terminology

commit 0d07aa6271
Author: Asbelos <asbelos@btinternet.com>
Date:   Thu May 30 19:59:29 2024 +0100

    MERG macris in exrail
2024-06-13 13:01:33 +01:00
Harald Barth
3d6c935308 EXCSB1 motor driver definitions 2024-06-11 23:10:18 +02:00
Harald Barth
fba9a30813 ESP32: Espressif deprecated ADC_ATTEN_DB_11 2024-06-11 23:09:41 +02:00
Harald Barth
5f65fd5944 tag with date 2024-06-02 21:45:43 +02:00
Harald Barth
a26610bc7f ESP32: More version locking 2024-06-02 21:44:25 +02:00
Harald Barth
264a53dacf ESP32: Refuse IDF5 2024-06-02 21:10:57 +02:00
Harald Barth
0c96d4ffc2 version 5.2.60 2024-05-23 22:46:47 +02:00
Harald Barth
843fa42692 Remove inrush throttle after half good time so that we go to mode overload if problem persists 2024-05-23 22:41:50 +02:00
Harald Barth
b17dc5a0dd Bugfix: Opcode AFTEROVERLOAD does not have an argument that is a pin and needs to be initialized 2024-05-23 22:39:43 +02:00
pmantoine
449a5f1670 STM32 updates for serial ports etc. 2024-05-22 07:16:25 +08:00
Asbelos
06b8995861 ALIAS(named pins) 2024-05-21 20:04:11 +01:00
Harald Barth
2172d2e175 make WDT time longer to work around bootloader bug 2024-05-11 08:46:25 +02:00
Harald Barth
86291cbec4 signal id of 0 does not work 2024-05-11 07:45:28 +02:00
Harald Barth
66791b19f5 remove stupid comment 2024-05-11 07:43:24 +02:00
pmantoine
0cb175544e More TCA8418 2024-02-24 17:29:10 +08:00
pmantoine
2082051801 TCA8418 initial HAL driver scaffolding 2024-02-24 13:02:34 +08:00
36 changed files with 1405 additions and 242 deletions

View File

@@ -37,7 +37,7 @@ int16_t lastclocktime;
int8_t lastclockrate;
#if WIFI_ON || ETHERNET_ON || defined(SERIAL1_COMMANDS) || defined(SERIAL2_COMMANDS) || defined(SERIAL3_COMMANDS)
#if WIFI_ON || ETHERNET_ON || defined(SERIAL1_COMMANDS) || defined(SERIAL2_COMMANDS) || defined(SERIAL3_COMMANDS) || defined(SERIAL4_COMMANDS) || defined(SERIAL5_COMMANDS) || defined(SERIAL6_COMMANDS)
// use a buffer to allow broadcast
StringBuffer * CommandDistributor::broadcastBufferWriter=new StringBuffer();
template<typename... Targs> void CommandDistributor::broadcastReply(clientType type, Targs... msg){
@@ -248,6 +248,10 @@ void CommandDistributor::broadcastLoco(byte slot) {
#endif
}
void CommandDistributor::broadcastForgetLoco(int16_t loco) {
broadcastReply(COMMAND_TYPE, F("<l %d 0 1 0>\n<- %d>\n"), loco,loco);
}
void CommandDistributor::broadcastPower() {
char pstr[] = "? x";
for(byte t=0; t<TrackManager::MAX_TRACKS; t++)

View File

@@ -47,6 +47,7 @@ private:
public :
static void parse(byte clientId,byte* buffer, RingStream * ring);
static void broadcastLoco(byte slot);
static void broadcastForgetLoco(int16_t loco);
static void broadcastSensor(int16_t id, bool value);
static void broadcastTurnout(int16_t id, bool isClosed);
static void broadcastTurntable(int16_t id, uint8_t position, bool moving);

20
DCC.cpp
View File

@@ -271,6 +271,20 @@ uint32_t DCC::getFunctionMap(int cab) {
return (reg<0)?0:speedTable[reg].functions;
}
// saves DC frequency (0..3) in spare functions 29,30,31
void DCC::setDCFreq(int cab,byte freq) {
if (cab==0 || freq>3) return;
auto reg=lookupSpeedTable(cab,true);
// drop and replace F29,30,31 (top 3 bits)
auto newFunctions=speedTable[reg].functions & 0x1FFFFFFFUL;
if (freq==1) newFunctions |= (1UL<<29); // F29
else if (freq==2) newFunctions |= (1UL<<30); // F30
else if (freq==3) newFunctions |= (1UL<<31); // F31
if (newFunctions==speedTable[reg].functions) return; // no change
speedTable[reg].functions=newFunctions;
CommandDistributor::broadcastLoco(reg);
}
void DCC::setAccessory(int address, byte port, bool gate, byte onoff /*= 2*/) {
// onoff is tristate:
// 0 => send off packet
@@ -728,11 +742,15 @@ void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
if (reg>=0) {
speedTable[reg].loco=0;
setThrottle2(cab,1); // ESTOP if this loco still on track
CommandDistributor::broadcastForgetLoco(cab);
}
}
void DCC::forgetAllLocos() { // removes all speed reminders
setThrottle2(0,1); // ESTOP all locos still on track
for (int i=0;i<MAX_LOCOS;i++) speedTable[i].loco=0;
for (int i=0;i<MAX_LOCOS;i++) {
if (speedTable[i].loco) CommandDistributor::broadcastForgetLoco(speedTable[i].loco);
speedTable[i].loco=0;
}
}
byte DCC::loopStatus=0;

1
DCC.h
View File

@@ -70,6 +70,7 @@ public:
static void changeFn(int cab, int16_t functionNumber);
static int8_t getFn(int cab, int16_t functionNumber);
static uint32_t getFunctionMap(int cab);
static void setDCFreq(int cab,byte freq);
static void updateGroupflags(byte &flags, int16_t functionNumber);
static void setAccessory(int address, byte port, bool gate, byte onoff = 2);
static bool setExtendedAccessory(int16_t address, int16_t value, byte repeats=3);

View File

@@ -27,8 +27,8 @@
#include "DCCWaveform.h"
#include "TrackManager.h"
unsigned int DCCACK::minAckPulseDuration = 2000; // micros
unsigned int DCCACK::maxAckPulseDuration = 20000; // micros
unsigned long DCCACK::minAckPulseDuration = 2000; // micros
unsigned long DCCACK::maxAckPulseDuration = 20000; // micros
MotorDriver * DCCACK::progDriver=NULL;
ackOp const * DCCACK::ackManagerProg;
@@ -50,8 +50,8 @@ volatile uint8_t DCCACK::numAckSamples=0;
uint8_t DCCACK::trailingEdgeCounter=0;
unsigned int DCCACK::ackPulseDuration; // micros
unsigned long DCCACK::ackPulseStart; // micros
unsigned long DCCACK::ackPulseDuration; // micros
unsigned long DCCACK::ackPulseStart; // micros
volatile bool DCCACK::ackDetected;
unsigned long DCCACK::ackCheckStart; // millis
volatile bool DCCACK::ackPending;
@@ -127,7 +127,7 @@ bool DCCACK::checkResets(uint8_t numResets) {
void DCCACK::setAckBaseline() {
int baseline=progDriver->getCurrentRaw();
ackThreshold= baseline + progDriver->mA2raw(ackLimitmA);
if (Diag::ACK) DIAG(F("ACK baseline=%d/%dmA Threshold=%d/%dmA Duration between %uus and %uus"),
if (Diag::ACK) DIAG(F("ACK baseline=%d/%dmA Threshold=%d/%dmA Duration between %lus and %lus"),
baseline,progDriver->raw2mA(baseline),
ackThreshold,progDriver->raw2mA(ackThreshold),
minAckPulseDuration, maxAckPulseDuration);
@@ -146,7 +146,7 @@ void DCCACK::setAckPending() {
byte DCCACK::getAck() {
if (ackPending) return (2); // still waiting
if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%uuS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%luS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
ackMaxCurrent,progDriver->raw2mA(ackMaxCurrent), ackPulseDuration, numAckSamples, numAckGaps);
if (ackDetected) return (1); // Yes we had an ack
return(0); // pending set off but not detected means no ACK.

View File

@@ -79,10 +79,10 @@ class DCCACK {
static inline void setAckLimit(int mA) {
ackLimitmA = mA;
}
static inline void setMinAckPulseDuration(unsigned int i) {
static inline void setMinAckPulseDuration(unsigned long i) {
minAckPulseDuration = i;
}
static inline void setMaxAckPulseDuration(unsigned int i) {
static inline void setMaxAckPulseDuration(unsigned long i) {
maxAckPulseDuration = i;
}
@@ -126,11 +126,11 @@ class DCCACK {
static unsigned long ackCheckStart; // millis
static unsigned int ackCheckDuration; // millis
static unsigned int ackPulseDuration; // micros
static unsigned long ackPulseDuration; // micros
static unsigned long ackPulseStart; // micros
static unsigned int minAckPulseDuration ; // micros
static unsigned int maxAckPulseDuration ; // micros
static unsigned long minAckPulseDuration ; // micros
static unsigned long maxAckPulseDuration ; // micros
static MotorDriver* progDriver;
static volatile uint8_t numAckGaps;
static volatile uint8_t numAckSamples;

View File

@@ -2,7 +2,7 @@
* © 2022 Paul M Antoine
* © 2021 Neil McKechnie
* © 2021 Mike S
* © 2021 Herb Morton
* © 2021-2024 Herb Morton
* © 2020-2023 Harald Barth
* © 2020-2021 M Steve Todd
* © 2020-2021 Fred Decker
@@ -72,7 +72,7 @@ Once a new OPCODE is decided upon, update this list.
M, Write DCC packet
n, Reserved for SensorCam
N, Reserved for Sensorcam
o,
o, Neopixel driver (see also IO_NeoPixel.h)
O, Output broadcast
p, Broadcast power state
P, Write DCC packet
@@ -394,7 +394,36 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
return;
break;
case 'z': // direct pin manipulation
#ifndef IO_NO_HAL
case 'o': // Neopixel pin manipulation
if (p[0]==0) break;
{
VPIN vpin=p[0]>0 ? p[0]:-p[0];
bool setON=p[0]>0;
if (params==1) { // <o [-]vpin>
IODevice::write(vpin,setON);
return;
}
if (params==2) { // <o [-]vpin count>
IODevice::writeRange(vpin,setON,p[1]);
return;
}
if (params==4 || params==5) { // <z [-]vpin r g b [count]>
auto count=p[4]?p[4]:1;
if (p[1]<0 || p[1]>0xFF) break;
if (p[2]<0 || p[2]>0xFF) break;
if (p[3]<0 || p[3]>0xFF) break;
// strange parameter mangling... see IO_NeoPixel.h NeoPixel::_writeAnalogue
int colour_RG=(p[1]<<8) | p[2];
uint16_t colour_B=p[3];
IODevice::writeAnalogueRange(vpin,colour_RG,setON,colour_B,count);
return;
}
}
break;
#endif
case 'z': // direct pin manipulation
if (p[0]==0) break;
if (params==1) { // <z vpin | -vpin>
if (p[0]>0) IODevice::write(p[0],HIGH);
@@ -563,6 +592,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
}
#ifndef DISABLE_PROG
else if (p[0]=="PROG"_hk) { // <0 PROG>
TrackManager::setJoin(false);
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::OFF);
}
@@ -641,6 +671,13 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
case 'F': // New command to call the new Loco Function API <F cab func 1|0>
if(params!=3) break;
if (p[1]=="DCFREQ"_hk) { // <F cab DCFREQ 0..3>
if (p[2]<0 || p[2]>3) break;
DCC::setDCFreq(p[0],p[2]);
return;
}
if (Diag::CMD)
DIAG(F("Setting loco %d F%d %S"), p[0], p[1], p[2] ? F("ON") : F("OFF"));
if (DCC::setFn(p[0], p[1], p[2] == 1)) return;
@@ -1073,15 +1110,24 @@ bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) {
#ifndef DISABLE_PROG
case "ACK"_hk: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX|RETRY] Value>
if (params >= 3) {
long duration;
if (p[1] == "LIMIT"_hk) {
DCCACK::setAckLimit(p[2]);
LCD(1, F("Ack Limit=%dmA"), p[2]); // <D ACK LIMIT 42>
LCD(1, F("Ack Limit=%dmA"), p[2]); // <D ACK LIMIT 42>
} else if (p[1] == "MIN"_hk) {
DCCACK::setMinAckPulseDuration(p[2]);
LCD(0, F("Ack Min=%uus"), p[2]); // <D ACK MIN 1500>
if (params == 4 && p[3] == "MS"_hk)
duration = p[2] * 1000L;
else
duration = p[2];
DCCACK::setMinAckPulseDuration(duration);
LCD(0, F("Ack Min=%lus"), duration); // <D ACK MIN 1500>
} else if (p[1] == "MAX"_hk) {
DCCACK::setMaxAckPulseDuration(p[2]);
LCD(0, F("Ack Max=%uus"), p[2]); // <D ACK MAX 9000>
if (params == 4 && p[3] == "MS"_hk) // <D ACK MAX 80 MS>
duration = p[2] * 1000L;
else
duration = p[2];
DCCACK::setMaxAckPulseDuration(duration);
LCD(0, F("Ack Max=%lus"), duration); // <D ACK MAX 9000>
} else if (p[1] == "RETRY"_hk) {
if (p[2] >255) p[2]=3;
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); // <D ACK RETRY 2>

View File

@@ -185,8 +185,10 @@ int DCCTimer::freeMemory() {
}
void DCCTimer::reset() {
wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms
delay(50); // wait for the prescaller time to expire
// 250ms chosen to circumwent bootloader bug which
// hangs at too short timepout (like 15ms)
wdt_enable( WDTO_250MS); // set Arduino watchdog timer for 250ms
delay(500); // wait for it to happen
}

View File

@@ -76,8 +76,13 @@ int DCCTimer::freeMemory() {
#endif
////////////////////////////////////////////////////////////////////////
#ifdef ARDUINO_ARCH_ESP32
#include "esp_idf_version.h"
#if ESP_IDF_VERSION_MAJOR > 4
#error "DCC-EX does not support compiling with IDF version 5.0 or later. Downgrade your ESP32 library to a version that contains IDE version 4. Arduino ESP32 library 3.0.0 is too new. Downgrade to one of 2.0.9 to 2.0.17"
#endif
#include "DIAG.h"
#include <driver/adc.h>
#include <soc/sens_reg.h>
@@ -292,7 +297,12 @@ void DCCTimer::DCCEXInrushControlOn(uint8_t pin, int duty, bool inverted) {
int ADCee::init(uint8_t pin) {
pinMode(pin, ANALOG);
adc1_config_width(ADC_WIDTH_BIT_12);
// Espressif deprecated ADC_ATTEN_DB_11 somewhere between 2.0.9 and 2.0.17
#ifdef ADC_ATTEN_11db
adc1_config_channel_atten(pinToADC1Channel(pin),ADC_ATTEN_11db);
#else
adc1_config_channel_atten(pinToADC1Channel(pin),ADC_ATTEN_DB_11);
#endif
return adc1_get_raw(pinToADC1Channel(pin));
}
int16_t ADCee::ADCmax() {

View File

@@ -36,7 +36,20 @@
#include "DIAG.h"
#include <wiring_private.h>
#if defined(ARDUINO_NUCLEO_F401RE) || defined(ARDUINO_NUCLEO_F411RE)
#if defined(ARDUINO_NUCLEO_F401RE)
// Nucleo-64 boards don't have additional serial ports defined by default
// Serial1 is available on the F401RE, but not hugely convenient.
// Rx pin on PB7 is useful, but all the Tx pins map to Arduino digital pins, specifically:
// PA9 == D8
// PB6 == D10
// of which D8 is needed by the standard and EX8874 motor shields. D10 would be used if a second
// EX8874 is stacked. So only disable this if using a second motor shield.
HardwareSerial Serial1(PB7, PB6); // Rx=PB7, Tx=PB6 -- CN7 pin 17 and CN10 pin 17
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
// Let's define Serial6 as an additional serial port (the only other option for the F401RE)
HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F401RE
#elif defined(ARDUINO_NUCLEO_F411RE)
// Nucleo-64 boards don't have additional serial ports defined by default
HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
@@ -54,7 +67,7 @@ HardwareSerial Serial3(PC11, PC10); // Rx=PC11, Tx=PC10 -- USART3 - F446RE
HardwareSerial Serial5(PD2, PC12); // Rx=PD2, Tx=PC12 -- UART5 - F446RE
// On the F446RE, Serial4 and Serial6 also use pins we can't readily map while using the Arduino pins
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F446ZE) || \
defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F439ZI)
defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F439ZI) || defined(ARDUINO_NUCLEO_F4X9ZI)
// Nucleo-144 boards don't have Serial1 defined by default
HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6
HardwareSerial Serial5(PD2, PC12); // Rx=PD2, Tx=PC12 -- UART5

View File

@@ -73,6 +73,7 @@ RMFT2 * RMFT2::pausingTask=NULL; // Task causing a PAUSE.
byte RMFT2::flags[MAX_FLAGS];
Print * RMFT2::LCCSerial=0;
LookList * RMFT2::routeLookup=NULL;
LookList * RMFT2::signalLookup=NULL;
LookList * RMFT2::onThrowLookup=NULL;
LookList * RMFT2::onCloseLookup=NULL;
LookList * RMFT2::onActivateLookup=NULL;
@@ -207,16 +208,28 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
// Second pass startup, define any turnouts or servos, set signals red
// add sequences onRoutines to the lookups
if (compileFeatures & FEATURE_SIGNAL) {
onRedLookup=LookListLoader(OPCODE_ONRED);
onAmberLookup=LookListLoader(OPCODE_ONAMBER);
onGreenLookup=LookListLoader(OPCODE_ONGREEN);
for (int sigslot=0;;sigslot++) {
int16_t sighandle=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
if (sighandle==0) break; // end of signal list
VPIN sigid = sighandle & SIGNAL_ID_MASK;
doSignal(sigid, SIGNAL_RED);
}
}
// Load the signal lookup with slot numbers in the signal table
int signalCount=0;
for (int16_t slot=0;;slot++) {
SIGNAL_DEFINITION signal=getSignalSlot(slot);
DIAG(F("Signal s=%d id=%d t=%d"),slot,signal.id,signal.type);
if (signal.type==sigtypeNoMoreSignals) break;
if (signal.type==sigtypeContinuation) continue;
signalCount++;
}
signalLookup=new LookList(signalCount);
for (int16_t slot=0;;slot++) {
SIGNAL_DEFINITION signal=getSignalSlot(slot);
if (signal.type==sigtypeNoMoreSignals) break;
if (signal.type==sigtypeContinuation) continue;
signalLookup->add(signal.id,slot);
doSignal(signal.id, SIGNAL_RED);
}
}
int progCounter;
for (progCounter=0;; SKIPOP){
@@ -228,7 +241,6 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
case OPCODE_AT:
case OPCODE_ATTIMEOUT2:
case OPCODE_AFTER:
case OPCODE_AFTEROVERLOAD:
case OPCODE_IF:
case OPCODE_IFNOT: {
int16_t pin = (int16_t)operand;
@@ -479,10 +491,15 @@ bool RMFT2::skipIfBlock() {
/* static */ void RMFT2::readLocoCallback(int16_t cv) {
if (cv <= 0) {
DIAG(F("CV read error"));
progtrackLocoId = -1;
return;
}
if (cv & LONG_ADDR_MARKER) { // maker bit indicates long addr
progtrackLocoId = cv ^ LONG_ADDR_MARKER; // remove marker bit to get real long addr
if (progtrackLocoId <= HIGHEST_SHORT_ADDR ) { // out of range for long addr
DIAG(F("Long addr %d <= %d unsupported\n"), progtrackLocoId, HIGHEST_SHORT_ADDR);
DIAG(F("Long addr %d <= %d unsupported"), progtrackLocoId, HIGHEST_SHORT_ADDR);
progtrackLocoId = -1;
}
} else {
@@ -629,14 +646,16 @@ void RMFT2::loop2() {
skipIf=blinkState!=at_timeout;
break;
case OPCODE_AFTER: // waits for sensor to hit and then remain off for 0.5 seconds. (must come after an AT operation)
case OPCODE_AFTER: // waits for sensor to hit and then remain off for x mS.
// Note, this must come after an AT operation, which is
// automatically inserted by the AFTER macro.
if (readSensor(operand)) {
// reset timer to half a second and keep waiting
// reset timer and keep waiting
waitAfter=millis();
delayMe(50);
return;
}
if (millis()-waitAfter < 500 ) return;
if (millis()-waitAfter < getOperand(1) ) return;
break;
case OPCODE_AFTEROVERLOAD: // waits for the power to be turned back on - either by power routine or button
@@ -717,41 +736,7 @@ void RMFT2::loop2() {
case OPCODE_SETFREQ:
// Frequency is default 0, or 1, 2,3
//if (loco) DCC::setFn(loco,operand,true);
switch (operand) {
case 0: // default - all F-s off
if (loco) {
DCC::setFn(loco,29,false);
DCC::setFn(loco,30,false);
DCC::setFn(loco,31,false);
}
break;
case 1:
if (loco) {
DCC::setFn(loco,29,true);
DCC::setFn(loco,30,false);
DCC::setFn(loco,31,false);
}
break;
case 2:
if (loco) {
DCC::setFn(loco,29,false);
DCC::setFn(loco,30,true);
DCC::setFn(loco,31,false);
}
break;
case 3:
if (loco) {
DCC::setFn(loco,29,false);
DCC::setFn(loco,30,false);
DCC::setFn(loco,31,true);
}
break;
default:
; // do nothing
break;
}
DCC::setDCFreq(loco,operand);
break;
case OPCODE_RESUME:
@@ -954,11 +939,10 @@ void RMFT2::loop2() {
delayMe(100);
return; // still waiting for callback
}
if (progtrackLocoId<0) {
kill(F("No Loco Found"),progtrackLocoId);
return; // still waiting for callback
}
// At failed read will result in loco == -1
// which is intended so it can be checked
// from within EXRAIL
loco=progtrackLocoId;
speedo=0;
forward=true;
@@ -1001,6 +985,14 @@ void RMFT2::loop2() {
if ((compileFeatures & FEATURE_LCC) && LCCSerial)
StringFormatter::send(LCCSerial,F("<L x%h>"),(uint16_t)operand);
break;
case OPCODE_ACON: // MERG adapter
case OPCODE_ACOF:
if ((compileFeatures & FEATURE_LCC) && LCCSerial)
StringFormatter::send(LCCSerial,F("<L x%c%h%h>"),
opcode==OPCODE_ACON?'0':'1',
(uint16_t)operand,getOperand(progCounter,1));
break;
case OPCODE_LCCX: // long form LCC
if ((compileFeatures & FEATURE_LCC) && LCCSerial)
@@ -1022,8 +1014,18 @@ void RMFT2::loop2() {
return;
}
break;
#ifndef IO_NO_HAL
case OPCODE_NEOPIXEL:
// OPCODE_NEOPIXEL,V([-]vpin),OPCODE_PAD,V(colour_RG),OPCODE_PAD,V(colour_B),OPCODE_PAD,V(count)
{
VPIN vpin=operand>0?operand:-operand;
auto count=getOperand(3);
killBlinkOnVpin(vpin,count);
IODevice::writeAnalogueRange(vpin,getOperand(1),operand>0,getOperand(2),count);
}
break;
case OPCODE_WAITFORTT: // OPCODE_WAITFOR,V(turntable_id)
if (Turntable::ttMoving(operand)) {
delayMe(100);
@@ -1089,6 +1091,8 @@ void RMFT2::loop2() {
case OPCODE_PINTURNOUT: // Turnout definition ignored at runtime
case OPCODE_ONCLOSE: // Turnout event catchers ignored here
case OPCODE_ONLCC: // LCC event catchers ignored here
case OPCODE_ONACON: // MERG event catchers ignored here
case OPCODE_ONACOF: // MERG event catchers ignored here
case OPCODE_ONTHROW:
case OPCODE_ONACTIVATE: // Activate event catchers ignored here
case OPCODE_ONDEACTIVATE:
@@ -1143,26 +1147,11 @@ void RMFT2::kill(const FSH * reason, int operand) {
delete this;
}
int16_t RMFT2::getSignalSlot(int16_t id) {
if (id >= 0) {
int sigslot = 0;
int16_t sighandle = 0;
// Trundle down the signal list until we reach the end
while ((sighandle = GETHIGHFLASHW(RMFT2::SignalDefinitions, sigslot * 8)) != 0)
{
// sigid is the signal id used in RED/AMBER/GREEN macro
// for a LED signal it will be same as redpin
// but for a servo signal it will also have SERVO_SIGNAL_FLAG set.
VPIN sigid = sighandle & SIGNAL_ID_MASK;
if (sigid == (VPIN)id) // cast to keep compiler happy but id is positive
return sigslot; // found it
sigslot++; // keep looking
};
}
// If we got here, we did not find the signal
DIAG(F("EXRAIL Signal %d not defined"), id);
return -1;
SIGNAL_DEFINITION RMFT2::getSignalSlot(int16_t slot) {
SIGNAL_DEFINITION signal;
COPYHIGHFLASH(&signal,SignalDefinitions,slot*sizeof(SIGNAL_DEFINITION),sizeof(SIGNAL_DEFINITION));
return signal;
}
/* static */ void RMFT2::doSignal(int16_t id,char rag) {
@@ -1175,81 +1164,97 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
else if (rag==SIGNAL_GREEN) onGreenLookup->handleEvent(F("GREEN"),id);
else onAmberLookup->handleEvent(F("AMBER"),id);
int16_t sigslot=getSignalSlot(id);
auto sigslot=signalLookup->find(id);
if (sigslot<0) return;
// keep track of signal state
setFlag(sigslot,rag,SIGNAL_MASK);
// Correct signal definition found, get the rag values
int16_t sigpos=sigslot*8;
int16_t sighandle=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos);
VPIN redpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+2);
VPIN amberpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+4);
VPIN greenpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+6);
//if (diag) DIAG(F("signal %d %d %d %d %d"),sigid,id,redpin,amberpin,greenpin);
VPIN sigtype=sighandle & ~SIGNAL_ID_MASK;
VPIN sigid = sighandle & SIGNAL_ID_MASK;
if (sigtype == SERVO_SIGNAL_FLAG) {
// A servo signal, the pin numbers are actually servo positions
// Note, setting a signal to a zero position has no effect.
int16_t servopos= rag==SIGNAL_RED? redpin: (rag==SIGNAL_GREEN? greenpin : amberpin);
auto signal=getSignalSlot(sigslot);
switch (signal.type) {
case sigtypeSERVO:
{
auto servopos = rag==SIGNAL_RED? signal.redpin: (rag==SIGNAL_GREEN? signal.greenpin : signal.amberpin);
//if (diag) DIAG(F("sigA %d %d"),id,servopos);
if (servopos!=0) IODevice::writeAnalogue(id,servopos,PCA9685::Bounce);
return;
}
}
if (sigtype== DCC_SIGNAL_FLAG) {
case sigtypeDCC:
{
// redpin,amberpin are the DCC addr,subaddr
DCC::setAccessory(redpin,amberpin, rag!=SIGNAL_RED);
DCC::setAccessory(signal.redpin,signal.amberpin, rag!=SIGNAL_RED);
return;
}
if (sigtype== DCCX_SIGNAL_FLAG) {
case sigtypeDCCX:
{
// redpin,amberpin,greenpin are the 3 aspects
byte value=redpin;
if (rag==SIGNAL_AMBER) value=amberpin;
if (rag==SIGNAL_GREEN) value=greenpin;
DCC::setExtendedAccessory(sigid, value);
auto value=signal.redpin;
if (rag==SIGNAL_AMBER) value=signal.amberpin;
if (rag==SIGNAL_GREEN) value=signal.greenpin;
DCC::setExtendedAccessory(id, value);
return;
}
case sigtypeNEOPIXEL:
{
// redpin,amberpin,greenpin are the 3 RG values but with no blue permitted. . (code limitation hack)
auto colour_RG=signal.redpin;
if (rag==SIGNAL_AMBER) colour_RG=signal.amberpin;
if (rag==SIGNAL_GREEN) colour_RG=signal.greenpin;
// blue channel is in followng signal slot (a continuation)
auto signal2=getSignalSlot(sigslot+1);
auto colour_B=signal2.redpin;
if (rag==SIGNAL_AMBER) colour_B=signal2.amberpin;
if (rag==SIGNAL_GREEN) colour_B=signal2.greenpin;
IODevice::writeAnalogue(id, colour_RG,true,colour_B);
return;
}
case sigtypeSIGNAL:
case sigtypeSIGNALH:
{
// LED or similar 3 pin signal, (all pins zero would be a virtual signal)
// If amberpin is zero, synthesise amber from red+green
const byte SIMAMBER=0x00;
if (rag==SIGNAL_AMBER && (amberpin==0)) rag=SIMAMBER; // special case this func only
if (rag==SIGNAL_AMBER && (signal.amberpin==0)) rag=SIMAMBER; // special case this func only
// Manage invert (HIGH on) pins
bool aHigh=sighandle & ACTIVE_HIGH_SIGNAL_FLAG;
bool aHigh=signal.type==sigtypeSIGNALH;
// set the three pins
if (redpin) {
if (signal.redpin) {
bool redval=(rag==SIGNAL_RED || rag==SIMAMBER);
if (!aHigh) redval=!redval;
killBlinkOnVpin(redpin);
IODevice::write(redpin,redval);
killBlinkOnVpin(signal.redpin);
IODevice::write(signal.redpin,redval);
}
if (amberpin) {
if (signal.amberpin) {
bool amberval=(rag==SIGNAL_AMBER);
if (!aHigh) amberval=!amberval;
killBlinkOnVpin(amberpin);
IODevice::write(amberpin,amberval);
killBlinkOnVpin(signal.amberpin);
IODevice::write(signal.amberpin,amberval);
}
if (greenpin) {
if (signal.greenpin) {
bool greenval=(rag==SIGNAL_GREEN || rag==SIMAMBER);
if (!aHigh) greenval=!greenval;
killBlinkOnVpin(greenpin);
IODevice::write(greenpin,greenval);
killBlinkOnVpin(signal.greenpin);
IODevice::write(signal.greenpin,greenval);
}
}
case sigtypeVIRTUAL: break;
case sigtypeContinuation: break;
case sigtypeNoMoreSignals: break;
}
}
/* static */ bool RMFT2::isSignal(int16_t id,char rag) {
if (!(compileFeatures & FEATURE_SIGNAL)) return false;
int16_t sigslot=getSignalSlot(id);
int16_t sigslot=signalLookup->find(id);
if (sigslot<0) return false;
return (flags[sigslot] & SIGNAL_MASK) == rag;
}
@@ -1261,26 +1266,23 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
// Otherwise false so the parser should send the command directly
bool RMFT2::signalAspectEvent(int16_t address, byte aspect ) {
if (!(compileFeatures & FEATURE_SIGNAL)) return false;
int16_t sigslot=getSignalSlot(address);
auto sigslot=signalLookup->find(address);
if (sigslot<0) return false; // this is not a defined signal
int16_t sigpos=sigslot*8;
int16_t sighandle=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos);
VPIN sigtype=sighandle & ~SIGNAL_ID_MASK;
VPIN sigid = sighandle & SIGNAL_ID_MASK;
if (sigtype!=DCCX_SIGNAL_FLAG) return false; // not a DCCX signal
auto signal=getSignalSlot(sigslot);
if (signal.type!=sigtypeDCCX) return false; // not a DCCX signal
// Turn an aspect change into a RED/AMBER/GREEN setting
if (aspect==GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+2)) {
doSignal(sigid,SIGNAL_RED);
if (aspect==signal.redpin) {
doSignal(address,SIGNAL_RED);
return true;
}
if (aspect==GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+4)) {
doSignal(sigid,SIGNAL_AMBER);
if (aspect==signal.amberpin) {
doSignal(address,SIGNAL_AMBER);
return true;
}
if (aspect==GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+6)) {
doSignal(sigid,SIGNAL_GREEN);
if (aspect==signal.greenpin) {
doSignal(address,SIGNAL_GREEN);
return true;
}
@@ -1334,19 +1336,24 @@ void RMFT2::powerEvent(int16_t track, bool overload) {
// This function is used when setting pins so that a SET or RESET
// will cause any blink task on that pin to terminate.
// It will be compiled out of existence if no BLINK feature is used.
void RMFT2::killBlinkOnVpin(VPIN pin) {
void RMFT2::killBlinkOnVpin(VPIN pin, uint16_t count) {
if (!(compileFeatures & FEATURE_BLINK)) return;
RMFT2 * stoptask=loopTask; // stop when we get back to here
RMFT2 * task=loopTask;
VPIN lastPin=pin+count-1;
while(task) {
auto nextTask=task->next;
if (
(task->blinkState==blink_high || task->blinkState==blink_low)
&& task->blinkPin==pin) {
&& task->blinkPin>=pin
&& task->blinkPin<=lastPin
) {
if (diag) DIAG(F("kill blink %d"),task->blinkPin,lastPin);
task->kill();
return;
}
task=task->next;
if (task==loopTask) return;
}
task=nextTask;
if (task==stoptask) return;
}
}

View File

@@ -69,11 +69,14 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,OPCODE_TOGGLE_TURNOUT,
OPCODE_TTADDPOSITION,OPCODE_DCCTURNTABLE,OPCODE_EXTTTURNTABLE,
OPCODE_ONROTATE,OPCODE_ROTATE,OPCODE_WAITFORTT,
OPCODE_LCC,OPCODE_LCCX,OPCODE_ONLCC,
OPCODE_ACON, OPCODE_ACOF,
OPCODE_ONACON, OPCODE_ONACOF,
OPCODE_ONOVERLOAD,
OPCODE_ROUTE_ACTIVE,OPCODE_ROUTE_INACTIVE,OPCODE_ROUTE_HIDDEN,
OPCODE_ROUTE_DISABLED,
OPCODE_STASH,OPCODE_CLEAR_STASH,OPCODE_CLEAR_ALL_STASH,OPCODE_PICKUP_STASH,
OPCODE_ONBUTTON,OPCODE_ONSENSOR,
OPCODE_ONBUTTON,OPCODE_ONSENSOR,
OPCODE_NEOPIXEL,
// OPcodes below this point are skip-nesting IF operations
// placed here so that they may be skipped as a group
// see skipIfBlock()
@@ -107,6 +110,23 @@ enum BlinkState: byte {
blink_high, // blink task running with pin high
at_timeout // ATTIMEOUT timed out flag
};
enum SignalType {
sigtypeVIRTUAL,
sigtypeSIGNAL,
sigtypeSIGNALH,
sigtypeDCC,
sigtypeDCCX,
sigtypeSERVO,
sigtypeNEOPIXEL,
sigtypeContinuation, // neopixels require a second line
sigtypeNoMoreSignals
};
struct SIGNAL_DEFINITION {
SignalType type;
VPIN id;
VPIN redpin,amberpin,greenpin;
};
// Flag bits for compile time features.
static const byte FEATURE_SIGNAL= 0x80;
@@ -168,12 +188,7 @@ class LookList {
static void rotateEvent(int16_t id, bool change);
static void powerEvent(int16_t track, bool overload);
static bool signalAspectEvent(int16_t address, byte aspect );
static const int16_t SERVO_SIGNAL_FLAG=0x4000;
static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000;
static const int16_t DCC_SIGNAL_FLAG=0x1000;
static const int16_t DCCX_SIGNAL_FLAG=0x3000;
static const int16_t SIGNAL_ID_MASK=0x0FFF;
// Throttle Info Access functions built by exrail macros
// Throttle Info Access functions built by exrail macros
static const byte rosterNameCount;
static const int16_t HIGHFLASH routeIdList[];
static const int16_t HIGHFLASH automationIdList[];
@@ -187,7 +202,8 @@ class LookList {
static const FSH * getTurntablePositionDescription(int16_t turntableId, uint8_t positionId);
static void startNonRecursiveTask(const FSH* reason, int16_t id,int pc);
static bool readSensor(uint16_t sensorId);
static bool isSignal(int16_t id,char rag);
static bool isSignal(int16_t id,char rag);
static SIGNAL_DEFINITION getSignalSlot(int16_t slotno);
private:
static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
@@ -197,7 +213,6 @@ private:
static bool getFlag(VPIN id,byte mask);
static int16_t progtrackLocoId;
static void doSignal(int16_t id,char rag);
static int16_t getSignalSlot(int16_t id);
static void setTurnoutHiddenState(Turnout * t);
#ifndef IO_NO_HAL
static void setTurntableHiddenState(Turntable * tto);
@@ -205,7 +220,7 @@ private:
static LookList* LookListLoader(OPCODE op1,
OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL);
static uint16_t getOperand(int progCounter,byte n);
static void killBlinkOnVpin(VPIN pin);
static void killBlinkOnVpin(VPIN pin,uint16_t count=1);
static RMFT2 * loopTask;
static RMFT2 * pausingTask;
void delayMe(long millisecs);
@@ -221,10 +236,11 @@ private:
static bool diag;
static const HIGHFLASH3 byte RouteCode[];
static const HIGHFLASH int16_t SignalDefinitions[];
static const HIGHFLASH SIGNAL_DEFINITION SignalDefinitions[];
static byte flags[MAX_FLAGS];
static Print * LCCSerial;
static LookList * routeLookup;
static LookList * signalLookup;
static LookList * onThrowLookup;
static LookList * onCloseLookup;
static LookList * onActivateLookup;

View File

@@ -99,6 +99,13 @@
#undef LCCX
#undef LCN
#undef MOVETT
#undef NEOPIXEL
#undef NEOPIXEL_OFF
#undef NEOPIXEL_SIGNAL
#undef ACON
#undef ACOF
#undef ONACON
#undef ONACOF
#undef MESSAGE
#undef ONACTIVATE
#undef ONACTIVATEL
@@ -191,7 +198,7 @@
#ifndef RMFT2_UNDEF_ONLY
#define ACTIVATE(addr,subaddr)
#define ACTIVATEL(addr)
#define AFTER(sensor_id)
#define AFTER(sensor_id,timer...)
#define AFTEROVERLOAD(track_id)
#define ALIAS(name,value...)
#define AMBER(signal_id)
@@ -265,6 +272,12 @@
#define LCN(msg)
#define MESSAGE(msg)
#define MOVETT(id,steps,activity)
#define NEOPIXEL(id,r,g,b,count...)
#define NEOPIXEL_SIGNAL(sigid,redcolour,ambercolour,greencolour)
#define ACON(eventid)
#define ACOF(eventid)
#define ONACON(eventid)
#define ONACOF(eventid)
#define ONACTIVATE(addr,subaddr)
#define ONACTIVATEL(linear)
#define ONAMBER(signal_id)
@@ -326,7 +339,7 @@
#define SET_TRACK(track,mode)
#define SET_POWER(track,onoff)
#define SETLOCO(loco)
#define SETFREQ(loco,freq)
#define SETFREQ(freq)
#define SIGNAL(redpin,amberpin,greenpin)
#define SIGNALH(redpin,amberpin,greenpin)
#define SPEED(speed)

View File

@@ -61,47 +61,85 @@ void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16
case 'L':
// This entire code block is compiled out if LLC macros not used
if (!(compileFeatures & FEATURE_LCC)) return;
static int lccProgCounter=0;
static int lccEventIndex=0;
if (paramCount==0) { //<L> LCC adapter introducing self
LCCSerial=stream; // now we know where to send events we raise
opcode=0; // flag command as intercepted
// loop through all possible sent events
for (int progCounter=0;; SKIPOP) {
byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) break;
if (opcode==OPCODE_LCC) StringFormatter::send(stream,F("<LS x%h>\n"),getOperand(progCounter,0));
if (opcode==OPCODE_LCCX) { // long form LCC
StringFormatter::send(stream,F("<LS x%h%h%h%h>\n"),
// loop through all possible sent/waited events
for (int progCounter=lccProgCounter;; SKIPOP) {
byte exrailOpcode=GET_OPCODE;
switch (exrailOpcode) {
case OPCODE_ENDEXRAIL:
stream->print(F("<LR>\n")); // ready to roll
lccProgCounter=0; // allow a second pass
lccEventIndex=0;
return;
case OPCODE_LCC:
StringFormatter::send(stream,F("<LS x%h>\n"),getOperand(progCounter,0));
SKIPOP;
lccProgCounter=progCounter;
return;
case OPCODE_LCCX: // long form LCC
StringFormatter::send(stream,F("<LS x%h%h%h%h>\n"),
getOperand(progCounter,1),
getOperand(progCounter,2),
getOperand(progCounter,3),
getOperand(progCounter,0)
);
}}
);
SKIPOP;SKIPOP;SKIPOP;SKIPOP;
lccProgCounter=progCounter;
return;
case OPCODE_ACON: // CBUS ACON
case OPCODE_ACOF: // CBUS ACOF
StringFormatter::send(stream,F("<LS x%c%h%h>\n"),
exrailOpcode==OPCODE_ACOF?'1':'0',
getOperand(progCounter,0),getOperand(progCounter,1));
SKIPOP;SKIPOP;
lccProgCounter=progCounter;
return;
// we stream the hex events we wish to listen to
// and at the same time build the event index looku.
int eventIndex=0;
for (int progCounter=0;; SKIPOP) {
byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) break;
if (opcode==OPCODE_ONLCC) {
onLCCLookup[eventIndex]=progCounter; // TODO skip...
case OPCODE_ONLCC:
StringFormatter::send(stream,F("<LL %d x%h%h%h:%h>\n"),
eventIndex,
lccEventIndex,
getOperand(progCounter,1),
getOperand(progCounter,2),
getOperand(progCounter,3),
getOperand(progCounter,0)
);
eventIndex++;
}
SKIPOP;SKIPOP;SKIPOP;SKIPOP;
// start on handler at next
onLCCLookup[lccEventIndex]=progCounter;
lccEventIndex++;
lccProgCounter=progCounter;
return;
case OPCODE_ONACON:
case OPCODE_ONACOF:
StringFormatter::send(stream,F("<LL %d x%c%h%h>\n"),
lccEventIndex,
exrailOpcode==OPCODE_ONACOF?'1':'0',
getOperand(progCounter,0),getOperand(progCounter,1)
);
SKIPOP;SKIPOP;
// start on handler at next
onLCCLookup[lccEventIndex]=progCounter;
lccEventIndex++;
lccProgCounter=progCounter;
return;
default:
break;
}
}
StringFormatter::send(stream,F("<LR>\n")); // Ready to rumble
opcode=0;
break;
}
if (paramCount==1) { // <L eventid> LCC event arrived from adapter
int16_t eventid=p[0];
@@ -214,13 +252,13 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
// do the signals
// flags[n] represents the state of the nth signal in the table
for (int sigslot=0;;sigslot++) {
int16_t sighandle=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
if (sighandle==0) break; // end of signal list
VPIN sigid = sighandle & SIGNAL_ID_MASK;
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id
SIGNAL_DEFINITION slot=getSignalSlot(sigslot);
if (slot.type==sigtypeNoMoreSignals) break; // end of signal list
if (slot.type==sigtypeContinuation) continue; // continueation of previous line
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this ids
StringFormatter::send(stream,F("\n%S[%d]"),
(flag == SIGNAL_RED)? F("RED") : (flag==SIGNAL_GREEN) ? F("GREEN") : F("AMBER"),
sigid);
slot.id);
}
}

View File

@@ -71,11 +71,13 @@
//const byte TRACK_POWER_0=0, TRACK_POWER_OFF=0;
//const byte TRACK_POWER_1=1, TRACK_POWER_ON=1;
// NEOPIXEL RG generator for NEOPIXEL_SIGNAL
#define NeoRGB(red,green,blue) (((uint32_t)(red & 0xff)<<16) | ((uint32_t)(green & 0xff)<<8) | (uint32_t)(blue & 0xff))
// Pass 1 Implements aliases
#include "EXRAIL2MacroReset.h"
#undef ALIAS
#define ALIAS(name,value...) const int name= 1##value##0 ==10 ? -__COUNTER__ : value##0/10;
#define ALIAS(name,value...) const int name= #value[0] ? value+0: -__COUNTER__ ;
#include "myAutomation.h"
// Pass 1d Detect sequence duplicates.
@@ -180,6 +182,8 @@ bool exrailHalSetup() {
#define DCC_SIGNAL(id,addr,subaddr) | FEATURE_SIGNAL
#undef DCCX_SIGNAL
#define DCCX_SIGNAL(id,redAspect,amberAspect,greenAspect) | FEATURE_SIGNAL
#undef NEOPIXEL_SIGNAL
#define NEOPIXEL_SIGNAL(sigid,redcolour,ambercolour,greencolour) | FEATURE_SIGNAL
#undef VIRTUAL_SIGNAL
#define VIRTUAL_SIGNAL(id) | FEATURE_SIGNAL
@@ -189,6 +193,14 @@ bool exrailHalSetup() {
#define LCCX(senderid,eventid) | FEATURE_LCC
#undef ONLCC
#define ONLCC(senderid,eventid) | FEATURE_LCC
#undef ACON
#define ACON(eventid) | FEATURE_LCC
#undef ACOF
#define ACOF(eventid) | FEATURE_LCC
#undef ONACON
#define ONACON(eventid) | FEATURE_LCC
#undef ONACOF
#define ONACOF(eventid) | FEATURE_LCC
#undef ROUTE_ACTIVE
#define ROUTE_ACTIVE(id) | FEATURE_ROUTESTATE
#undef ROUTE_INACTIVE
@@ -413,26 +425,35 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) {
// Pass 8 Signal definitions
#include "EXRAIL2MacroReset.h"
#undef SIGNAL
#define SIGNAL(redpin,amberpin,greenpin) redpin,redpin,amberpin,greenpin,
#define SIGNAL(redpin,amberpin,greenpin) {sigtypeSIGNAL,redpin,redpin,amberpin,greenpin},
#undef SIGNALH
#define SIGNALH(redpin,amberpin,greenpin) redpin | RMFT2::ACTIVE_HIGH_SIGNAL_FLAG,redpin,amberpin,greenpin,
#define SIGNALH(redpin,amberpin,greenpin) {sigtypeSIGNALH,redpin,redpin,amberpin,greenpin},
#undef SERVO_SIGNAL
#define SERVO_SIGNAL(vpin,redval,amberval,greenval) vpin | RMFT2::SERVO_SIGNAL_FLAG,redval,amberval,greenval,
#define SERVO_SIGNAL(vpin,redval,amberval,greenval) {sigtypeSERVO,vpin,redval,amberval,greenval},
#undef DCC_SIGNAL
#define DCC_SIGNAL(id,addr,subaddr) id | RMFT2::DCC_SIGNAL_FLAG,addr,subaddr,0,
#define DCC_SIGNAL(id,addr,subaddr) {sigtypeDCC,id,addr,subaddr,0},
#undef DCCX_SIGNAL
#define DCCX_SIGNAL(id,redAspect,amberAspect,greenAspect) id | RMFT2::DCCX_SIGNAL_FLAG,redAspect,amberAspect,greenAspect,
#define DCCX_SIGNAL(id,redAspect,amberAspect,greenAspect) {sigtypeDCCX,id,redAspect,amberAspect,greenAspect},
#undef NEOPIXEL_SIGNAL
#define NEOPIXEL_SIGNAL(id,redRGB,amberRGB,greenRGB) \
{sigtypeNEOPIXEL,id,((VPIN)((redRGB)>>8)), ((VPIN)((amberRGB)>>8)), ((VPIN)((greenRGB)>>8))},\
{sigtypeContinuation,id,((VPIN)((redRGB) & 0xff)), ((VPIN)((amberRGB) & 0xFF)), ((VPIN)((greenRGB) & 0xFF))},
#undef VIRTUAL_SIGNAL
#define VIRTUAL_SIGNAL(id) id,0,0,0,
#define VIRTUAL_SIGNAL(id) {sigtypeVIRTUAL,id,0,0,0},
const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
const HIGHFLASH SIGNAL_DEFINITION RMFT2::SignalDefinitions[] = {
#include "myAutomation.h"
0,0,0,0 };
{sigtypeNoMoreSignals,0,0,0,0}
};
// Pass 9 ONLCC counter and lookup array
// Pass 9 ONLCC/ ONMERG counter and lookup array
#include "EXRAIL2MacroReset.h"
#undef ONLCC
#define ONLCC(sender,event) +1
#undef ONACON
#define ONACON(event) +1
#undef ONACOF
#define ONACOF(event) +1
const int RMFT2::countLCCLookup=0
#include "myAutomation.h"
@@ -451,7 +472,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#define ACTIVATE(addr,subaddr) OPCODE_DCCACTIVATE,V(addr<<3 | subaddr<<1 | 1),
#define ACTIVATEL(addr) OPCODE_DCCACTIVATE,V((addr+3)<<1 | 1),
#define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id),
#define AFTER(sensor_id,timer...) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id),OPCODE_PAD,V(#timer[0]?timer+0:500),
#define AFTEROVERLOAD(track_id) OPCODE_AFTEROVERLOAD,V(TRACK_NUMBER_##track_id),
#define ALIAS(name,value...)
#define AMBER(signal_id) OPCODE_AMBER,V(signal_id),
@@ -529,6 +550,10 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
OPCODE_PAD,V((((uint64_t)sender)>>32)&0xFFFF),\
OPCODE_PAD,V((((uint64_t)sender)>>16)&0xFFFF),\
OPCODE_PAD,V((((uint64_t)sender)>>0)&0xFFFF),
#define ACON(eventid) OPCODE_ACON,V(((uint32_t)eventid >>16) & 0xFFFF),OPCODE_PAD,V(eventid & 0xFFFF),
#define ACOF(eventid) OPCODE_ACOF,V(((uint32_t)eventid >>16) & 0xFFFF),OPCODE_PAD,V(eventid & 0xFFFF),
#define ONACON(eventid) OPCODE_ONACON,V((uint32_t)(eventid) >>16),OPCODE_PAD,V(eventid & 0xFFFF),
#define ONACOF(eventid) OPCODE_ONACOF,V((uint32_t)(eventid) >>16),OPCODE_PAD,V(eventid & 0xFFFF),
#define LCD(id,msg) PRINT(msg)
#define SCREEN(display,id,msg) PRINT(msg)
#define STEALTH(code...) PRINT(dummy)
@@ -536,6 +561,12 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#define LCN(msg) PRINT(msg)
#define MESSAGE(msg) PRINT(msg)
#define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0),
#define NEOPIXEL(id,r,g,b,count...) OPCODE_NEOPIXEL,V(id),\
OPCODE_PAD,V(((r & 0xff)<<8) | (g & 0xff)),\
OPCODE_PAD,V((b & 0xff)),\
OPCODE_PAD,V(#count[0]?(count+0):1),
#define NEOPIXEL_SIGNAL(sigid,redcolour,ambercolour,greencolour)
#define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr),
#define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3),
#define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id),
@@ -604,7 +635,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#define SET_TRACK(track,mode) OPCODE_SET_TRACK,V(TRACK_MODE_##mode <<8 | TRACK_NUMBER_##track),
#define SET_POWER(track,onoff) OPCODE_SET_POWER,V(TRACK_POWER_##onoff),OPCODE_PAD, V(TRACK_NUMBER_##track),
#define SETLOCO(loco) OPCODE_SETLOCO,V(loco),
#define SETFREQ(loco,freq) OPCODE_SETLOCO,V(loco), OPCODE_SETFREQ,V(freq),
#define SETFREQ(freq) OPCODE_SETFREQ,V(freq),
#define SIGNAL(redpin,amberpin,greenpin)
#define SIGNALH(redpin,amberpin,greenpin)
#define SPEED(speed) OPCODE_SPEED,V(speed),

6
FSH.h
View File

@@ -60,6 +60,8 @@ typedef __FlashStringHelper FSH;
#define GETFARPTR(data) pgm_get_far_address(data)
#define GETHIGHFLASH(data,offset) pgm_read_byte_far(GETFARPTR(data)+offset)
#define GETHIGHFLASHW(data,offset) pgm_read_word_far(GETFARPTR(data)+offset)
#define COPYHIGHFLASH(target,base,offset,length) \
memcpy_PF(target,GETFARPTR(base) + offset,length)
#else
// AVR_UNO/NANO runtime does not support _far functions so just use _near equivalent
// as there is no progmem above 32kb anyway.
@@ -68,6 +70,8 @@ typedef __FlashStringHelper FSH;
#define GETFARPTR(data) ((uint32_t)(data))
#define GETHIGHFLASH(data,offset) pgm_read_byte_near(GETFARPTR(data)+(offset))
#define GETHIGHFLASHW(data,offset) pgm_read_word_near(GETFARPTR(data)+(offset))
#define COPYHIGHFLASH(target,base,offset,length) \
memcpy_P(target,(byte *)base + offset,length)
#endif
#else
@@ -87,6 +91,8 @@ typedef char FSH;
#define GETFLASH(addr) (*(const byte *)(addr))
#define GETHIGHFLASH(data,offset) (*(const byte *)(GETFARPTR(data)+offset))
#define GETHIGHFLASHW(data,offset) (*(const uint16_t *)(GETFARPTR(data)+offset))
#define COPYHIGHFLASH(target,base,offset,length) \
memcpy(target,(byte *)&base + offset,length)
#define STRCPY_P strcpy
#define STRCMP_P strcmp
#define STRNCPY_P strncpy

View File

@@ -1 +1 @@
#define GITHUB_SHA "devel-202404091507Z"
#define GITHUB_SHA "devel-202409040713Z"

View File

@@ -46,27 +46,37 @@
// Helper function for listing device types
static const FSH * guessI2CDeviceType(uint8_t address) {
if (address == 0x1A)
// 0x09-0x18 selectable, but for now handle the default
return F("Piicodev 865/915MHz Transceiver");
if (address == 0x1C)
return F("QMC6310 Magnetometer");
if (address >= 0x20 && address <= 0x26)
return F("GPIO Expander");
else if (address == 0x27)
if (address == 0x27)
return F("GPIO Expander or LCD Display");
else if (address == 0x29)
if (address == 0x29)
return F("Time-of-flight sensor");
else if (address >= 0x3c && address <= 0x3d)
return F("OLED Display");
else if (address >= 0x48 && address <= 0x57) // SC16IS752x UART detection
if (address == 0x34)
return F("TCA8418 keypad scanner");
if (address >= 0x3c && address <= 0x3d)
// 0x3c can also be an HMC883L magnetometer
return F("OLED Display or HMC583L Magnetometer");
if (address >= 0x48 && address <= 0x57) // SC16IS752x UART detection
return F("SC16IS75x UART");
else if (address >= 0x48 && address <= 0x4f)
if (address >= 0x48 && address <= 0x4f)
return F("Analogue Inputs or PWM");
else if (address >= 0x40 && address <= 0x4f)
if (address >= 0x40 && address <= 0x4f)
return F("PWM");
else if (address >= 0x50 && address <= 0x5f)
if (address >= 0x50 && address <= 0x5f)
return F("EEPROM");
else if (address == 0x68)
if (address >= 0x60 && address <= 0x68)
return F("Adafruit NeoPixel Driver");
if (address == 0x68)
return F("Real-time clock");
else if (address >= 0x70 && address <= 0x77)
if (address >= 0x70 && address <= 0x77)
return F("I2C Mux");
else
// Unknown type
return F("?");
}

View File

@@ -251,6 +251,26 @@ void IODevice::write(VPIN vpin, int value) {
#endif
}
// Write value to count virtual pin(s).
// these may be within one driver or separated over several drivers
void IODevice::writeRange(VPIN vpin, int value, int count) {
while(count) {
auto dev = findDevice(vpin);
if (dev) {
auto vpinBefore=vpin;
// write to driver, driver will return next vpin it cant handle
vpin=dev->_writeRange(vpin, value,count);
count-= vpin-vpinBefore; // decrement by number of vpins changed
}
else {
// skip a vpin if no device handler
vpin++;
count--;
}
}
}
// Write analogue value to virtual pin(s). If multiple devices are allocated
// the same pin then only the first one found will be used.
//
@@ -270,6 +290,24 @@ void IODevice::writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t para
#endif
}
//
void IODevice::writeAnalogueRange(VPIN vpin, int value, uint8_t param1, uint16_t param2,int count) {
while(count) {
auto dev = findDevice(vpin);
if (dev) {
auto vpinBefore=vpin;
// write to driver, driver will return next vpin it cant handle
vpin=dev->_writeAnalogueRange(vpin, value, param1, param2,count);
count-= vpin-vpinBefore; // decrement by number of vpins changed
}
else {
// skip a vpin if no device handler
vpin++;
count--;
}
}
}
// isBusy, when called for a device pin is always a digital output or analogue output,
// returns input feedback state of the pin, i.e. whether the pin is busy performing
// an animation or fade over a period of time.

View File

@@ -128,9 +128,11 @@ public:
// write invokes the IODevice instance's _write method.
static void write(VPIN vpin, int value);
static void writeRange(VPIN vpin, int value,int count);
// write invokes the IODevice instance's _writeAnalogue method (not applicable for digital outputs)
static void writeAnalogue(VPIN vpin, int value, uint8_t profile=0, uint16_t duration=0);
static void writeAnalogueRange(VPIN vpin, int value, uint8_t profile, uint16_t duration, int count);
// isBusy returns true if the device is currently in an animation of some sort, e.g. is changing
// the output over a period of time.
@@ -177,11 +179,29 @@ public:
virtual void _write(VPIN vpin, int value) {
(void)vpin; (void)value;
};
// Method to write new state (optionally implemented within device class)
// This will, by default just write to one vpin and return whet to do next.
// the real power comes where a single driver can update many vpins in one call.
virtual VPIN _writeRange(VPIN vpin, int value, int count) {
(void)count;
_write(vpin,value);
return vpin+1; // try next vpin
};
// Method to write an 'analogue' value (optionally implemented within device class)
virtual void _writeAnalogue(VPIN vpin, int value, uint8_t param1=0, uint16_t param2=0) {
(void)vpin; (void)value; (void) param1; (void)param2;
};
// Method to write an 'analogue' value to a VPIN range (optionally implemented within device class)
// This will, by default just write to one vpin and return whet to do next.
// the real power comes where a single driver can update many vpins in one call.
virtual VPIN _writeAnalogueRange(VPIN vpin, int value, uint8_t param1, uint16_t param2, int count) {
(void) count;
_writeAnalogue(vpin, value, param1, param2);
return vpin+1;
};
// Method to read digital pin state (optionally implemented within device class)
virtual int _read(VPIN vpin) {
@@ -547,6 +567,9 @@ protected:
#include "IO_duinoNodes.h"
#include "IO_EXIOExpander.h"
#include "IO_trainbrains.h"
#include "IO_EncoderThrottle.h"
#include "IO_TCA8418.h"
#include "IO_NeoPixel.h"
#endif // iodevice_h

144
IO_EncoderThrottle.cpp Normal file
View File

@@ -0,0 +1,144 @@
/*
* © 2024, Chris Harlow. All rights reserved.
*
* This file is part of EX-CommandStation
*
* 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/>.
*/
/*
* The IO_EncoderThrottle device driver uses a rotary encoder connected to vpins
* to drive a loco.
* Loco id is selected by writeAnalog.
*/
#include "IODevice.h"
#include "DIAG.h"
#include "DCC.h"
const byte _DIR_CW = 0x10; // Clockwise step
const byte _DIR_CCW = 0x20; // Counter-clockwise step
const byte transition_table[5][4]= {
{0,1,3,0}, // 0: 00
{1,1,1,2 | _DIR_CW}, // 1: 00->01
{2,2,0,2}, // 2: 00->01->11
{3,3,3,4 | _DIR_CCW}, // 3: 00->10
{4,0,4,4} // 4: 00->10->11
};
const byte _STATE_MASK = 0x07;
const byte _DIR_MASK = 0x30;
void EncoderThrottle::create(VPIN firstVpin, int dtPin, int clkPin, int clickPin, byte notch) {
if (checkNoOverlap(firstVpin)) new EncoderThrottle(firstVpin, dtPin,clkPin,clickPin,notch);
}
// Constructor
EncoderThrottle::EncoderThrottle(VPIN firstVpin, int dtPin, int clkPin, int clickPin, byte notch){
_firstVpin = firstVpin;
_nPins = 1;
_I2CAddress = 0;
_dtPin=dtPin;
_clkPin=clkPin;
_clickPin=clickPin;
_notch=notch;
_locoid=0;
_stopState=xrSTOP;
_rocoState=0;
_prevpinstate=4; // not 01..11
IODevice::configureInput(dtPin,true);
IODevice::configureInput(clkPin,true);
IODevice::configureInput(clickPin,true);
addDevice(this);
_display();
}
void EncoderThrottle::_loop(unsigned long currentMicros) {
if (_locoid==0) return; // not in use
// Clicking down on the roco, stops the loco and sets the direction as unknown.
if (IODevice::read(_clickPin)) {
if (_stopState==xrSTOP) return; // debounced multiple stops
DCC::setThrottle(_locoid,1,DCC::getThrottleDirection(_locoid));
_stopState=xrSTOP;
DIAG(F("DRIVE %d STOP"),_locoid);
return;
}
// read roco pins and detect state change
byte pinstate = (IODevice::read(_dtPin) << 1) | IODevice::read(_clkPin);
if (pinstate==_prevpinstate) return;
_prevpinstate=pinstate;
_rocoState = transition_table[_rocoState & _STATE_MASK][pinstate];
if ((_rocoState & _DIR_MASK) == 0) return; // no value change
int change=(_rocoState & _DIR_CW)?+1:-1;
// handle roco change -1 or +1 (clockwise)
if (_stopState==xrSTOP) {
// first move after button press sets the direction. (clockwise=fwd)
_stopState=change>0?xrFWD:xrREV;
}
// when going fwd, clockwise increases speed.
// but when reversing, anticlockwise increases speed.
// This is similar to a center-zero pot control but with
// the added safety that you cant panic-spin into the other
// direction.
if (_stopState==xrREV) change=-change;
// manage limits
int oldspeed=DCC::getThrottleSpeed(_locoid);
if (oldspeed==1)oldspeed=0; // break out of estop
int newspeed=change>0 ? (min((oldspeed+_notch),126)) : (max(0,(oldspeed-_notch)));
if (newspeed==1) newspeed=0; // normal decelereated stop.
if (oldspeed!=newspeed) {
DIAG(F("DRIVE %d notch %S %d %S"),_locoid,
change>0?F("UP"):F("DOWN"),_notch,
_stopState==xrFWD?F("FWD"):F("REV"));
DCC::setThrottle(_locoid,newspeed,_stopState==xrFWD);
}
}
// Selocoid as analog value to start drive
// use <z vpin locoid [notch]>
void EncoderThrottle::_writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) {
(void) param2;
_locoid=value;
if (param1>0) _notch=param1;
_rocoState=0;
// If loco is moving, we inherit direction from it.
_stopState=xrSTOP;
if (_locoid>0) {
auto speedbyte=DCC::getThrottleSpeedByte(_locoid);
if ((speedbyte & 0x7f) >1) {
// loco is moving
_stopState= (speedbyte & 0x80)?xrFWD:xrREV;
}
}
_display();
}
void EncoderThrottle::_display() {
DIAG(F("DRIVE vpin %d loco %d notch %d"),_firstVpin,_locoid,_notch);
}

53
IO_EncoderThrottle.h Normal file
View File

@@ -0,0 +1,53 @@
/*
* © 2024, Chris Harlow. All rights reserved.
*
* This file is part of EX-CommandStation
*
* 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/>.
*/
/*
* The IO_EncoderThrottle device driver uses a rotary encoder connected to vpins
* to drive a loco.
* Loco id is selected by writeAnalog.
*/
#ifndef IO_EncoderThrottle_H
#define IO_EncoderThrottle_H
#include "IODevice.h"
class EncoderThrottle : public IODevice {
public:
static void create(VPIN firstVpin, int dtPin, int clkPin, int clickPin, byte notch=10);
private:
int _dtPin,_clkPin,_clickPin, _locoid, _notch,_prevpinstate;
enum {xrSTOP,xrFWD,xrREV} _stopState;
byte _rocoState;
// Constructor
EncoderThrottle(VPIN firstVpin, int dtPin, int clkPin, int clickPin, byte notch);
void _loop(unsigned long currentMicros) override ;
// Selocoid as analog value to start drive
// use <z vpin locoid [notch]>
void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override;
void _display() override ;
};
#endif

333
IO_NeoPixel.h Normal file
View File

@@ -0,0 +1,333 @@
/*
* © 2024, Chris Harlow. All rights reserved.
*
* This file is part of EX-CommandStation
*
* 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/>.
*/
/*
* The IO_NEOPIXEL.h device driver integrates with one or more Adafruit neopixel drivers.
* This device driver will configure the device on startup, along with
* interacting with the device for all input/output duties.
*
* To create NEOPIXEL devices, these are defined in myAutomation.h:
* (Note the device driver is included by default)
*
* HAL(NEOPIXEL,first vpin, number of pixels,mode, i2c address)
* e.g. HAL(NEOPIXEL,1000,64,NEO_RGB,0x60)
* This gives each pixel in the chain an individual vpin
* The number of pixels must match the physical pixels in the chain.
*
* This driver maintains a colour (rgb value in 5,5,5 bits only) plus an ON bit.
* This can be written/read with an analog write/read call.
* The ON bit can be set on and off with a digital write. This allows for
* a pixel to be preset a colour and then turned on and off like any other light.
*/
#ifndef IO_EX_NeoPixel_H
#define IO_EX_NeoPixel_H
#include "IODevice.h"
#include "I2CManager.h"
#include "DIAG.h"
#include "FSH.h"
// The following macros to define the Neopixel String type
// have been copied from the Adafruit Seesaw Library under the
// terms of the GPL.
// Credit to: https://github.com/adafruit/Adafruit_Seesaw
// The order of primary colors in the NeoPixel data stream can vary
// among device types, manufacturers and even different revisions of
// the same item. The third parameter to the seesaw_NeoPixel
// constructor encodes the per-pixel byte offsets of the red, green
// and blue primaries (plus white, if present) in the data stream --
// the following #defines provide an easier-to-use named version for
// each permutation. e.g. NEO_GRB indicates a NeoPixel-compatible
// device expecting three bytes per pixel, with the first byte
// containing the green value, second containing red and third
// containing blue. The in-memory representation of a chain of
// NeoPixels is the same as the data-stream order; no re-ordering of
// bytes is required when issuing data to the chain.
// Bits 5,4 of this value are the offset (0-3) from the first byte of
// a pixel to the location of the red color byte. Bits 3,2 are the
// green offset and 1,0 are the blue offset. If it is an RGBW-type
// device (supporting a white primary in addition to R,G,B), bits 7,6
// are the offset to the white byte...otherwise, bits 7,6 are set to
// the same value as 5,4 (red) to indicate an RGB (not RGBW) device.
// i.e. binary representation:
// 0bWWRRGGBB for RGBW devices
// 0bRRRRGGBB for RGB
// RGB NeoPixel permutations; white and red offsets are always same
// Offset: W R G B
#define NEO_RGB ((0 << 6) | (0 << 4) | (1 << 2) | (2))
#define NEO_RBG ((0 << 6) | (0 << 4) | (2 << 2) | (1))
#define NEO_GRB ((1 << 6) | (1 << 4) | (0 << 2) | (2))
#define NEO_GBR ((2 << 6) | (2 << 4) | (0 << 2) | (1))
#define NEO_BRG ((1 << 6) | (1 << 4) | (2 << 2) | (0))
#define NEO_BGR ((2 << 6) | (2 << 4) | (1 << 2) | (0))
// RGBW NeoPixel permutations; all 4 offsets are distinct
// Offset: W R G B
#define NEO_WRGB ((0 << 6) | (1 << 4) | (2 << 2) | (3))
#define NEO_WRBG ((0 << 6) | (1 << 4) | (3 << 2) | (2))
#define NEO_WGRB ((0 << 6) | (2 << 4) | (1 << 2) | (3))
#define NEO_WGBR ((0 << 6) | (3 << 4) | (1 << 2) | (2))
#define NEO_WBRG ((0 << 6) | (2 << 4) | (3 << 2) | (1))
#define NEO_WBGR ((0 << 6) | (3 << 4) | (2 << 2) | (1))
#define NEO_RWGB ((1 << 6) | (0 << 4) | (2 << 2) | (3))
#define NEO_RWBG ((1 << 6) | (0 << 4) | (3 << 2) | (2))
#define NEO_RGWB ((2 << 6) | (0 << 4) | (1 << 2) | (3))
#define NEO_RGBW ((3 << 6) | (0 << 4) | (1 << 2) | (2))
#define NEO_RBWG ((2 << 6) | (0 << 4) | (3 << 2) | (1))
#define NEO_RBGW ((3 << 6) | (0 << 4) | (2 << 2) | (1))
#define NEO_GWRB ((1 << 6) | (2 << 4) | (0 << 2) | (3))
#define NEO_GWBR ((1 << 6) | (3 << 4) | (0 << 2) | (2))
#define NEO_GRWB ((2 << 6) | (1 << 4) | (0 << 2) | (3))
#define NEO_GRBW ((3 << 6) | (1 << 4) | (0 << 2) | (2))
#define NEO_GBWR ((2 << 6) | (3 << 4) | (0 << 2) | (1))
#define NEO_GBRW ((3 << 6) | (2 << 4) | (0 << 2) | (1))
#define NEO_BWRG ((1 << 6) | (2 << 4) | (3 << 2) | (0))
#define NEO_BWGR ((1 << 6) | (3 << 4) | (2 << 2) | (0))
#define NEO_BRWG ((2 << 6) | (1 << 4) | (3 << 2) | (0))
#define NEO_BRGW ((3 << 6) | (1 << 4) | (2 << 2) | (0))
#define NEO_BGWR ((2 << 6) | (3 << 4) | (1 << 2) | (0))
#define NEO_BGRW ((3 << 6) | (2 << 4) | (1 << 2) | (0))
// If 400 KHz support is enabled, the third parameter to the constructor
// requires a 16-bit value (in order to select 400 vs 800 KHz speed).
// If only 800 KHz is enabled (as is default on ATtiny), an 8-bit value
// is sufficient to encode pixel color order, saving some space.
#define NEO_KHZ800 0x0000 // 800 KHz datastream
#define NEO_KHZ400 0x0100 // 400 KHz datastream
/////////////////////////////////////////////////////////////////////////////////////////////////////
/*
* IODevice subclass for NeoPixel.
*/
class NeoPixel : public IODevice {
public:
static void create(VPIN vpin, int nPins, uint16_t mode=(NEO_GRB | NEO_KHZ800), I2CAddress i2cAddress=0x60) {
if (checkNoOverlap(vpin, nPins, mode, i2cAddress)) new NeoPixel(vpin, nPins, mode, i2cAddress);
}
private:
static const byte SEESAW_NEOPIXEL_BASE=0x0E;
static const byte SEESAW_NEOPIXEL_STATUS = 0x00;
static const byte SEESAW_NEOPIXEL_PIN = 0x01;
static const byte SEESAW_NEOPIXEL_SPEED = 0x02;
static const byte SEESAW_NEOPIXEL_BUF_LENGTH = 0x03;
static const byte SEESAW_NEOPIXEL_BUF=0x04;
static const byte SEESAW_NEOPIXEL_SHOW=0x05;
// all adafruit examples say this pin. Presumably its hard wired
// in the adapter anyway.
static const byte SEESAW_PIN15 = 15;
// Constructor
NeoPixel(VPIN firstVpin, int nPins, uint16_t mode, I2CAddress i2cAddress) {
_firstVpin = firstVpin;
_nPins=nPins;
_I2CAddress = i2cAddress;
// calculate the offsets into the seesaw buffer for each colour depending
// on the pixel strip type passed in mode.
_redOffset=4+(mode >> 4 & 0x03);
_greenOffset=4+(mode >> 2 & 0x03);
_blueOffset=4+(mode & 0x03);
if (4+(mode >>6 & 0x03) == _redOffset) _bytesPerPixel=3;
else _bytesPerPixel=4; // string has a white byte.
_kHz800=(mode & NEO_KHZ400)==0;
_showPendimg=false;
// Each pixel requires 3 bytes RGB memory.
// Although the driver device can remember this, it cant do off/on without
// forgetting what the on colour was!
pixelBuffer=(RGB *) malloc(_nPins*sizeof(RGB));
stateBuffer=(byte *) calloc((_nPins+7)/8,sizeof(byte)); // all pixels off
if (pixelBuffer==nullptr || stateBuffer==nullptr) {
DIAG(F("NeoPixel I2C:%s not enough RAM"), _I2CAddress.toString());
return;
}
// preset all pins to white so a digital on/off will do something even if no colour set.
memset(pixelBuffer,0xFF,_nPins*sizeof(RGB));
addDevice(this);
}
void _begin() {
// Initialise Neopixel device
I2CManager.begin();
if (!I2CManager.exists(_I2CAddress)) {
DIAG(F("NeoPixel I2C:%s device not found"), _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED;
return;
}
byte speedBuffer[]={SEESAW_NEOPIXEL_BASE, SEESAW_NEOPIXEL_SPEED,_kHz800};
I2CManager.write(_I2CAddress, speedBuffer, sizeof(speedBuffer));
// In the driver there are 3 of 4 byts per pixel
auto numBytes=_bytesPerPixel * _nPins;
byte setbuffer[] = {SEESAW_NEOPIXEL_BASE, SEESAW_NEOPIXEL_BUF_LENGTH,
(byte)(numBytes >> 8), (byte)(numBytes & 0xFF)};
I2CManager.write(_I2CAddress, setbuffer, sizeof(setbuffer));
const byte pinbuffer[] = {SEESAW_NEOPIXEL_BASE, SEESAW_NEOPIXEL_PIN,SEESAW_PIN15};
I2CManager.write(_I2CAddress, pinbuffer, sizeof(pinbuffer));
for (auto pin=0;pin<_nPins;pin++) transmit(pin);
_display();
}
// loop called by HAL supervisor
void _loop(unsigned long currentMicros) override {
if (!_showPendimg) return;
byte showBuffer[]={SEESAW_NEOPIXEL_BASE,SEESAW_NEOPIXEL_SHOW};
I2CManager.write(_I2CAddress,showBuffer,sizeof(showBuffer));
_showPendimg=false;
}
// read back pixel on/off
int _read(VPIN vpin) override {
if (_deviceState == DEVSTATE_FAILED) return 0;
return isPixelOn(vpin-_firstVpin);
}
// Write digital value. Sets pixel on or off
void _write(VPIN vpin, int value) override {
if (_deviceState == DEVSTATE_FAILED) return;
auto pixel=vpin-_firstVpin;
if (value) {
if (isPixelOn(pixel)) return;
setPixelOn(pixel);
}
else { // set off
if (!isPixelOn(pixel)) return;
setPixelOff(pixel);
}
transmit(pixel);
}
VPIN _writeRange(VPIN vpin,int value, int count) {
// using write range cuts out the constant vpin to driver lookup so
// we can update multiple pixels much faster.
VPIN nextVpin=vpin + (count>_nPins ? _nPins : count);
if (_deviceState != DEVSTATE_FAILED) while(vpin<nextVpin) {
_write(vpin,value);
vpin++;
}
return nextVpin; // next pin we cant
}
// Write analogue value.
// The convoluted parameter mashing here is to allow passing the RGB and on/off
// information through the generic HAL _writeAnalog interface which was originally
// designed for servos and short integers
void _writeAnalogue(VPIN vpin, int colour_RG, uint8_t onoff, uint16_t colour_B) override {
if (_deviceState == DEVSTATE_FAILED) return;
RGB newColour={(byte)((colour_RG>>8) & 0xFF), (byte)(colour_RG & 0xFF), (byte)(colour_B & 0xFF)};
auto pixel=vpin-_firstVpin;
if (pixelBuffer[pixel]==newColour && isPixelOn(pixel)==(bool)onoff) return; // no change
if (onoff) setPixelOn(pixel); else setPixelOff(pixel);
pixelBuffer[pixel]=newColour;
transmit(pixel);
}
VPIN _writeAnalogueRange(VPIN vpin, int colour_RG, uint8_t onoff, uint16_t colour_B, int count) override {
// using write range cuts out the constant vpin to driver lookup so
VPIN nextVpin=vpin + (count>_nPins ? _nPins : count);
if (_deviceState != DEVSTATE_FAILED) while(vpin<nextVpin) {
_writeAnalogue(vpin,colour_RG, onoff,colour_B);
vpin++;
}
return nextVpin; // next pin we cant
}
// Display device information and status.
void _display() override {
DIAG(F("NeoPixel I2C:%s Vpins %u-%u %S"),
_I2CAddress.toString(),
(int)_firstVpin, (int)_firstVpin+_nPins-1,
_deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F(""));
}
bool isPixelOn(int16_t pixel) {return stateBuffer[pixel/8] & (0x80>>(pixel%8));}
void setPixelOn(int16_t pixel) {stateBuffer[pixel/8] |= (0x80>>(pixel%8));}
void setPixelOff(int16_t pixel) {stateBuffer[pixel/8] &= ~(0x80>>(pixel%8));}
// Helper function for error handling
void reportError(uint8_t status, bool fail=true) {
DIAG(F("NeoPixel I2C:%s Error:%d (%S)"), _I2CAddress.toString(),
status, I2CManager.getErrorMessage(status));
if (fail)
_deviceState = DEVSTATE_FAILED;
}
void transmit(uint16_t pixel, bool show=true) {
byte buffer[]={SEESAW_NEOPIXEL_BASE,SEESAW_NEOPIXEL_BUF,0x00,0x00,0x00,0x00,0x00};
uint16_t offset= pixel * _bytesPerPixel;
buffer[2]=(byte)(offset>>8);
buffer[3]=(byte)(offset & 0xFF);
if (isPixelOn(pixel)) {
auto colour=pixelBuffer[pixel];
buffer[_redOffset]=colour.red;
buffer[_greenOffset]=colour.green;
buffer[_blueOffset]=colour.blue;
} // else leave buffer black (in buffer preset to zeros above)
// Transmit pixel to driver
I2CManager.write(_I2CAddress,buffer,4 +_bytesPerPixel);
_showPendimg=true;
}
struct RGB {
byte red;
byte green;
byte blue;
bool operator==(const RGB& other) const {
return red == other.red && green == other.green && blue == other.blue;
}
};
RGB* pixelBuffer = nullptr;
byte* stateBuffer = nullptr; // 1 bit per pixel
bool _showPendimg;
// mapping of RGB onto pixel buffer for seesaw.
byte _bytesPerPixel;
byte _redOffset;
byte _greenOffset;
byte _blueOffset;
bool _kHz800;
};
#endif

184
IO_TCA8418.h Normal file
View File

@@ -0,0 +1,184 @@
/*
* © 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 <https://www.gnu.org/licenses/>.
*/
#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<uint64_t> {
public:
static void create(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
if (checkNoOverlap(vpin, nPins, i2cAddress))
// temporarily use the simple 18-pin GPIO mode - we'll switch to 8x8 matrix once this works
new TCA8418(vpin, (nPins = (nPins > 18) ? 18 : nPins), i2cAddress, interruptPin);
}
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
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;
(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,
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

View File

@@ -26,7 +26,7 @@
Thus "MAIN"_hk generates exactly the same run time vakue
as const int16_t HASH_KEYWORD_MAIN=11339
*/
#ifndef KeywordHAsher_h
#ifndef KeywordHasher_h
#define KeywordHasher_h
#include <Arduino.h>

View File

@@ -1,5 +1,6 @@
/*
* © 2022-2024 Paul M Antoine
* © 2024 Herb Morton
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020-2023 Harald Barth
@@ -98,7 +99,7 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
if (HAVE_PORTH(fastSignalPin.inout == &PORTH)) {
DIAG(F("Found PORTH pin %d"),signalPin);
fastSignalPin.shadowinout = fastSignalPin.inout;
fastSignalPin.inout = &shadowPORTF;
fastSignalPin.inout = &shadowPORTH;
}
signalPin2=signal_pin2;
@@ -638,6 +639,10 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
}
throttleInrush(false);
setPower(POWERMODE::ON);
break;
}
if (goodtime > POWER_SAMPLE_ALERT_GOOD/2) {
throttleInrush(false);
}
break;
}

View File

@@ -97,6 +97,18 @@
new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 35/*A2*/, 1.27, 5000, 36 /*A4*/), \
new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 34/*A3*/, 1.27, 5000, 39 /*A5*/)
// EX-CSB1 with integrated motor driver definition
#define EXCSB1 F("EXCSB1"),\
new MotorDriver(25, 0, UNUSED_PIN, -14, 34, 2.23, 5000, 19), \
new MotorDriver(27, 15, UNUSED_PIN, -2, 35, 2.23, 5000, 23)
// EX-CSB1 with EX-8874 stacked on top for 4 outputs
#define EXCSB1_WITH_EX8874 F("EXCSB1_WITH_EX8874"),\
new MotorDriver(25, 0, UNUSED_PIN, -14, 34, 2.23, 5000, 19), \
new MotorDriver(27, 15, UNUSED_PIN, -2, 35, 2.23, 5000, 23), \
new MotorDriver(26, 5, UNUSED_PIN, 13, 36, 1.52, 5000, 18), \
new MotorDriver(16, 4, UNUSED_PIN, 12, 39, 1.52, 5000, 17)
#else
// STANDARD shield on any Arduino Uno or Mega compatible with the original specification.
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \

77
Release_Notes/NeoPixel.md Normal file
View File

@@ -0,0 +1,77 @@
NeoPixel support
The IO_NeoPixel.h driver supports the adafruit neopixel seesaw board. It turns each pixel into an individual VPIN which can be given a colour and turned on or off using the new <o> command or the NEOPIXEL Exrail macro. Exrail SIGNALS can also drive a single pixel signal or multiple separate pixels.
1. Defining the hardware driver:
Add a driver definition in myAutomation.h for each adafruit I2C driver.
HAL(neoPixel, firstVpin, numberOfPixels [, mode [, i2caddress])
Where mode is selected from the various pixel string types which have varying
colour order or refresh frequency. For MOST strings this mode will be NEO_GRB but for others refer to the comments in IO_NeoPixel.h
If omitted the node and i2caddress default to NEO_GRB, 0x60.
HAL(NeoPixel,1000,20)
This is a NeoPixel driver defaulting to I2C aqddress 0x60 for a GRB pixel string. Pixels are given vpin numbers from 1000 to 1019.
HAL(NeoPixel,1020,20,NEO_GRB,0x61)
This is a NeoPixel driver on i2c address 0x61
2. Setting pixels from the < > commands.
By default, each pixel in the string is created as white but switched off.
Each pixel has a vpin starting from the first vpin in the HAL definitions.
<o vpin> switches pixel on (same as <z vpin>) e.g. <o 1005>
<o -vpin> switches pixel off (same as <z -vpin>) e.g. <o -1003>
(the z commands work on pixels the same as other gpio pins.)
<o [-]vpin count> switches on/off count pixels starting at vpin. e.g <o 1000 5>
Note: it IS acceptable to switch across 2 strings of pixels if they are contiguous vpin ranges. It is also interesting that this command doesnt care if the vpins are NeoPixel or any other type, so it can be used to switch a range of other pin types.
<o [-]vpin red green blue [count]> sets the colour and on/off status of a pin or pins. Each colour is 0..255 e.g. <o 1005 255 255 0> sets pin 1005 to bright yellow and ON, <0 -1006 0 0 255 10> sets pins 1006 to 1015 (10 pins) to bright blue but OFF.
Note: If you set a pin to a colour, you can turn it on and off without having to reset the colour every time. This is something the adafruit seesaw library can't do and is just one of several reasons why we dont use it.
3. Setting pixels from EXRAIL
The new NEOPIXEL macro provides the same functionality as the <o [-]vpin red green blue [count]> command above.
NEOPIXEL([-]vpin, red, green, blue [,count])
Setting pixels on or off (without colour change) can be done with SET/RESET [currently there is no set range facility but that may be added as a general exrail thing... watch this space]
Because the pixels obey set/reset, the BLINK command can also be used to control blinking a pixel.
4. EXRAIL pixel signals.
There are two types possible, a mast with separate fixed colour pixels for each aspect, or a mast with one multiple colour pixel for all aspects.
For separate pixels, the colours should be established at startup and a normal SIGNALH macro used.
AUTOSTART
SIGNALH(1010,1011,1012)
NEOPIXEL(1010,255,0,0)
NEOPIXEL(1011,128,128,0)
NEOPIXEL(1012,0,255,0)
RED(1010) // force signal state otherwise all 3 lights will be on
DONE
For signals with 1 pixel, the NEOPIXEL_SIGNAL macro will create a signal
NEOPIXEL_SIGNAL(vpin,redfx,amberfx,greenfx)
** Changed... ****
The fx values above can be created by the NeoRGB macro so a bright red would be NeoRGB(255,0,0) bright green NeoRGB(0,255,0) and amber something like NeoRGB(255,100,0)
NeoRGB creates a single int32_t value so it can be used in several ways as convenient.
// create 1-lamp signal with NeoRGB colours
NEOPIXEL_SIGNAL(1000,NeoRGB(255,0,0),NeoRGB(255,100,0),NeoRGB(0,255,0))
// Create 1-lamp signal with named colours.
// This is better if you have multiple signals.
// (Note: ALIAS is not suitable due to word length defaults)
#define REDLAMP NeoRGB(255,0,0)
#define AMBERLAMP NeoRGB(255,100,0)
#define GREENLAMP NeoRGB(0,255,0)
NEOPIXEL_SIGNAL(1001,REDLAMP,AMBERLAMP,GREENLAMP)
// Create 1-lamp signal with web type RGB colours
// (Using blue for the amber signal , just testing)
NEOPIXEL_SIGNAL(1002,0xFF0000,0x0000FF,0x00FF00)

View File

@@ -68,7 +68,11 @@ void SerialManager::init() {
new SerialManager(&Serial3);
#endif
#ifdef SERIAL2_COMMANDS
#ifdef ARDUINO_ARCH_ESP32
Serial2.begin(115200, SERIAL_8N1, 16, 17); // GPIO 16 RXD2; GPIO 17 TXD2 on ESP32
#else // not ESP32
Serial2.begin(115200);
#endif // ESP32
new SerialManager(&Serial2);
#endif
#ifdef SERIAL1_COMMANDS
@@ -88,8 +92,10 @@ void SerialManager::init() {
}
#endif
#ifdef SABERTOOTH
#ifdef ARDUINO_ARCH_ESP32
Serial2.begin(9600, SERIAL_8N1, 16, 17); // GPIO 16 RXD2; GPIO 17 TXD2 on ESP32
#endif
#endif
}
void SerialManager::broadcast(char * stringBuffer) {

View File

@@ -139,6 +139,7 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
case 'd': printPadded(stream,va_arg(args, int), formatWidth, formatLeft); break;
case 'u': printPadded(stream,va_arg(args, unsigned int), formatWidth, formatLeft); break;
case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break;
case 'L': stream->print(va_arg(args, unsigned long), DEC); break;
case 'b': stream->print(va_arg(args, int), BIN); break;
case 'o': stream->print(va_arg(args, int), OCT); break;
case 'x': stream->print((unsigned int)va_arg(args, unsigned int), HEX); break;

View File

@@ -1,6 +1,8 @@
/*
* © 2022 Chris Harlow
* © 2022-2024 Harald Barth
* © 2023-2024 Paul M. Antoine
* © 2024 Herb Morton
* © 2023 Colin Murdoch
* All rights reserved.
*
@@ -149,6 +151,8 @@ void TrackManager::setDCCSignal( bool on) {
HAVE_PORTD(shadowPORTD=PORTD);
HAVE_PORTE(shadowPORTE=PORTE);
HAVE_PORTF(shadowPORTF=PORTF);
HAVE_PORTG(shadowPORTF=PORTG);
HAVE_PORTH(shadowPORTF=PORTH);
APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on));
HAVE_PORTA(PORTA=shadowPORTA);
HAVE_PORTB(PORTB=shadowPORTB);
@@ -156,6 +160,8 @@ void TrackManager::setDCCSignal( bool on) {
HAVE_PORTD(PORTD=shadowPORTD);
HAVE_PORTE(PORTE=shadowPORTE);
HAVE_PORTF(PORTF=shadowPORTF);
HAVE_PORTG(shadowPORTF=PORTG);
HAVE_PORTH(shadowPORTF=PORTH);
}
// setPROGSignal(), called from interrupt context
@@ -167,6 +173,8 @@ void TrackManager::setPROGSignal( bool on) {
HAVE_PORTD(shadowPORTD=PORTD);
HAVE_PORTE(shadowPORTE=PORTE);
HAVE_PORTF(shadowPORTF=PORTF);
HAVE_PORTG(shadowPORTF=PORTG);
HAVE_PORTH(shadowPORTF=PORTH);
APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on));
HAVE_PORTA(PORTA=shadowPORTA);
HAVE_PORTB(PORTB=shadowPORTB);
@@ -174,6 +182,8 @@ void TrackManager::setPROGSignal( bool on) {
HAVE_PORTD(PORTD=shadowPORTD);
HAVE_PORTE(PORTE=shadowPORTE);
HAVE_PORTF(PORTF=shadowPORTF);
HAVE_PORTG(shadowPORTF=PORTG);
HAVE_PORTH(shadowPORTF=PORTH);
}
// setDCSignal(), called from normal context
@@ -631,23 +641,25 @@ void TrackManager::setJoinRelayPin(byte joinRelayPin) {
void TrackManager::setJoin(bool joined) {
#ifdef ARDUINO_ARCH_ESP32
if (joined) {
if (joined) { // if we go into joined mode (PROG acts as MAIN)
FOR_EACH_TRACK(t) {
if (track[t]->getMode() & TRACK_MODE_PROG) {
tempProgTrack = t;
if (track[t]->getMode() & TRACK_MODE_PROG) { // find PROG track
tempProgTrack = t; // remember PROG track
setTrackMode(t, TRACK_MODE_MAIN);
break;
track[t]->setPower(POWERMODE::ON); // if joined, always on
break; // there is only one prog track, done
}
}
} else {
if (tempProgTrack != MAX_TRACKS+1) {
// as setTrackMode with TRACK_MODE_PROG defaults to
// power off, we will take the current power state
// of our track and then preserve that state.
POWERMODE tPTmode = track[tempProgTrack]->getPower(); //get current power status of this track
setTrackMode(tempProgTrack, TRACK_MODE_PROG);
track[tempProgTrack]->setPower(tPTmode); //set track status as it was before
// setTrackMode defaults to power off, so we
// need to preserve that state.
POWERMODE tPTmode = track[tempProgTrack]->getPower(); // get current power status of this track
setTrackMode(tempProgTrack, TRACK_MODE_PROG); // set track mode back to prog
track[tempProgTrack]->setPower(tPTmode); // set power status as it was before
tempProgTrack = MAX_TRACKS+1;
} else {
DIAG(F("Unjoin but no remembered prog track"));
}
}
#endif

View File

@@ -147,6 +147,12 @@ bool WifiESP::setup(const char *SSid,
// enableCoreWDT(1);
// disableCoreWDT(0);
#ifdef WIFI_LED
// Turn off Wifi LED
pinMode(WIFI_LED, OUTPUT);
digitalWrite(WIFI_LED, 0);
#endif
// clean start
WiFi.mode(WIFI_STA);
WiFi.disconnect(true);
@@ -247,12 +253,19 @@ bool WifiESP::setup(const char *SSid,
// no idea to go on
return false;
}
#ifdef WIFI_LED
else{
// Turn on Wifi connected LED
digitalWrite(WIFI_LED, 1);
}
#endif
// Now Wifi is up, register the mDNS service
if(!MDNS.begin(hostname)) {
DIAG(F("Wifi setup failed to start mDNS"));
}
if(!MDNS.addService("withrottle", "tcp", 2560)) {
if(!MDNS.addService("withrottle", "tcp", port)) {
DIAG(F("Wifi setup failed to add withrottle service to mDNS"));
}

View File

@@ -1,4 +1,5 @@
/*
* © 2022-2024 Paul M. Antoine
* © 2021 Fred Decker
* © 2020-2022 Harald Barth
* © 2020-2022 Chris Harlow
@@ -70,7 +71,7 @@ Stream * WifiInterface::wifiStream;
#define SERIAL3 Serial5
#elif defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) \
|| defined(ARDUINO_NUCLEO_F446ZE) || defined(ARDUINO_NUCLEO_F412ZG) \
|| defined(ARDUINO_NUCLEO_F439ZI)
|| defined(ARDUINO_NUCLEO_F439ZI) || defined(ARDUINO_NUCLEO_F4X9ZI)
#define NUM_SERIAL 2
#define SERIAL1 Serial6
#else

View File

@@ -307,11 +307,21 @@ The configuration file for DCC-EX Command Station
//
//#define SERIAL_BT_COMMANDS
// BOOSTER PIN INPUT ON ESP32
// BOOSTER PIN INPUT ON ESP32 CS
// On ESP32 you have the possibility to define a pin as booster input
// Arduio pin D2 is GPIO 26 on ESPDuino32
//
// Arduino pin D2 is GPIO 26 is Booster Input on ESPDuino32
//#define BOOSTER_INPUT 26
//
// GPIO 32 is Booster Input on EX-CSB1
//#define BOOSTER_INPUT 32
// ESP32 LED Wifi Indicator
// GPIO 2 on ESPduino32
//#define WIFI_LED 2
//
// GPIO 33 on EX-CSB1
//#define WIFI_LED 33
// SABERTOOTH
//

View File

@@ -164,7 +164,11 @@ monitor_echo = yes
build_flags = -mcall-prologues
[env:ESP32]
platform = espressif32
; Lock version to 6.7.0 as that is
; Arduino v2.0.16 (based on IDF v4.4.7)
; which is the latest version based
; on IDF v4. We can not use IDF v5.
platform = espressif32 @ 6.7.0
board = esp32dev
framework = arduino
lib_deps = ${env.lib_deps}
@@ -218,14 +222,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

View File

@@ -3,7 +3,38 @@
#include "StringFormatter.h"
#define VERSION "5.2.57"
#define VERSION "5.2.76i2c"
// 5.2.76i2c - Add various I2C drivers, including TCA8418 and Adafruit NeoPixel Driver initially
// 5.2.76 - Bugfix: EXRAIL: Catch CV read errors in the callback
// 5.2.75 - Bugfix: Serial lines 4 to 6 OK
// 5.2.74 - Bugfix: ESP32 turn on the joined prog (as main) again after a prog operation
// 5.2.73 - Bugfix: STM32 further fixes to shadowPORT entries in TrackManager.cpp for PORTG and PORTH
// 5.2.72 - Bugfix: added shadowPORT entries in TrackManager.cpp for PORTG and PORTH on STM32, fixed typo in MotorDriver.cpp
// 5.2.71 - Broadcasts of loco forgets.
// 5.2.70 - IO_RocoDriver renamed to IO_EncoderThrottle.
// - and included in IODEvice.h (circular dependency removed)
// 5.2.69 - IO_RocoDriver. Direct drive train with rotary encoder hw.
// 5.2.68 - Revert function map to signed (from 5.2.66) to avoid
// incompatibilities with ED etc for F31 frequency flag.
// 5.2.67 - EXRAIL AFTER optional debounce time variable (default 500mS)
// - AFTER(42) == AFTER(42,500) sets time sensor must
// - be continuously off.
// 5.2.66 - <F cab DCFREQ 0..3>
// - EXRAIL SETFREQ drop loco param (breaking since 5.2.28)
// 5.2.65 - Speedup Exrail SETFREQ
// 5.2.64 - Bugfix: <0 PROG> updated to undo JOIN
// 5.2.63 - Implement WIFI_LED for ESP32, ESPduino32 and EX-CSB1, that is turned on when STA mode connects or AP mode is up
// - Add BOOSTER_INPUT definitions for ESPduino32 and EX-CSB1 to config.example.h
// - Add WIFI_LED definitions for ESPduino32 and EX-CSB1 to config.example.h
// 5.2.62 - Allow acks way longer than standard
// 5.2.61 - Merg CBUS ACON/ACOF/ONACON/ONACOF Adapter interface.
// - LCC Adapter interface throttled startup,
// (Breaking change with Adapter base code)
// 5.2.60 - Bugfix: Opcode AFTEROVERLOAD does not have an argument that is a pin and needs to be initialized
// - Remove inrush throttle after half good time so that we go to mode overload if problem persists
// 5.2.59 - STM32 bugfix correct Serial1 definition for Nucleo-F401RE
// - STM32 add support for ARDUINO_NUCLEO_F4X9ZI type to span F429/F439 in upcoming STM32duino release v2.8 as a result of our PR
// 5.2.58 - EXRAIL ALIAS allows named pins
// 5.2.57 - Bugfix autoreverse: Apply mode by binart bit match and not by equality
// 5.2.56 - Bugfix and refactor for EXRAIL getSignalSlot
// 5.2.55 - Move EXRAIL isSignal() to public to allow use in STEALTH call