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

Compare commits

..

94 Commits

Author SHA1 Message Date
Harald Barth
762742b4af Add the macro def 2023-01-27 13:05:36 +01:00
Harald Barth
88b572a148 Add EXRAIL IFLOCO function 2023-01-26 16:55:58 +01:00
peteGSX
fcf16c1367 Update version 2023-01-26 18:53:25 +10:00
Colin Murdoch
c69b8d85c8 Merge branch 'devel-plus-fastclock' into devel 2023-01-24 12:30:48 +00:00
Colin Murdoch
006c85e6ae Delete platformio.ini.original
Delete file not required
2023-01-24 12:21:28 +00:00
peteGSX
4f233de726 Merge pull request #297 from DCC-EX:31-exio-to-do-optimise-read-speed
31-exio-to-do-optimise-read-speed
2023-01-24 08:25:13 +10:00
peteGSX
4acf46db54 EX-IO reads optimised for speed 2023-01-24 08:17:43 +10:00
peteGSX
20b3e9064c Analogue inputs functioning 2023-01-23 21:35:22 +10:00
peteGSX
459904e5dd More analogue inputs 2023-01-23 20:12:28 +10:00
peteGSX
878549d538 Working on analogue inputs 2023-01-23 16:26:07 +10:00
peteGSX
7f4e3d9cea Digital inputs optimised 2023-01-23 11:49:23 +10:00
peteGSX
f2aeb4069f Merge pull request #296 from DCC-EX:294-bug-report-ex-rail-signalsignalh-inverted
294-bug-report-ex-rail-signalsignalh-inverted
2023-01-23 04:59:54 +10:00
peteGSX
aaf25d5426 Remove excess comments 2023-01-23 04:53:39 +10:00
peteGSX
fb9170ab8b SIGNAL/SIGNALH operating correctly 2023-01-22 19:25:00 +10:00
Colin Murdoch
286bdc3c4d Create platformio.ini.original 2023-01-21 10:20:49 +00:00
Colin Murdoch
cd46d3c9e0 Remove #ifdef and merge calcs
Remove #idfef statements and merge duplicate routines into CommandDistributor
2023-01-21 10:18:54 +00:00
pmantoine
fb36bd1380 Fix F446 Serial Pin Comment (Rx/Tx) 2023-01-17 20:15:30 +08:00
Colin Murdoch
b62c4da04d Update CommandDistributor.h
Fixed #endif typo.
2023-01-17 10:56:12 +00:00
Colin Murdoch
8fac20a451 Add #ifdef selections
Add #ifdef selections linked to #define in config.exampe.h
2023-01-16 18:16:25 +00:00
pmantoine
1be382a6ed Fixed comment re Serial1 for STM32F446RE 2023-01-14 12:45:21 +08:00
pmantoine
1f433d0c17 Serial1 for STM32F446RE corrected. 2023-01-14 12:43:05 +08:00
pmantoine
046e62a8b3 Minor fix to DCCTimerSTM32.cpp for F412ZG. 2023-01-13 17:24:26 +08:00
peteGSX
a2c7c7d12a Merge pull request #292 from DCC-EX:exio-prevent-digital-analogue-conflict
Exio-prevent-digital-analogue-conflict
2023-01-12 08:21:13 +10:00
peteGSX
9b36bdcf46 Logic and diag message done 2023-01-12 08:10:41 +10:00
peteGSX
a8646a2f32 Fix EX-Turntable diag message 2023-01-12 07:33:50 +10:00
peteGSX
22e20f9092 Logic added and working 2023-01-12 07:27:42 +10:00
Colin Murdoch
873d470f86 Supply missing function
Supply missing function
2023-01-11 19:50:39 +00:00
Colin Murdoch
ff7260b9bc Added code for FastClock
Added code for both I2C fastclock and serial clocks
2023-01-11 17:36:11 +00:00
peteGSX
de4954ca3e Merge pull request #288 from DCC-EX:debug-ex-io-expander-on-mega
Debug-ex-io-expander-on-mega
2023-01-10 20:11:45 +10:00
peteGSX
c26f53e1fa Device driver fixed 2023-01-10 20:05:09 +10:00
peteGSX
e48a40fafb Change to blocking I2CManager calls 2023-01-10 13:07:54 +10:00
peteGSX
5c120efa16 Add being 2023-01-10 08:16:42 +10:00
peteGSX
9abcfb9e4f Add begin delay to test 2023-01-09 20:08:36 +10:00
peteGSX
e01893bcf1 Comment out unused variables 2023-01-09 20:03:18 +10:00
pmantoine
6eff836473 Add -Wunused-variable build flag to Nucleo builds 2023-01-09 17:18:50 +08:00
pmantoine
402e16727c Fix platformio for Nucleo-F446RE 2023-01-09 16:47:29 +08:00
pmantoine
658fca2601 Nucleo-F446RE Build target support 2023-01-09 16:24:29 +08:00
peteGSX
3fccf6a484 Fix EX-IOExpander myHal.cpp example 2023-01-03 08:57:21 +10:00
peteGSX
ace9c1642a Merge pull request #285 from DCC-EX:add-rotary-encoder
New working rotary encoder branch
2022-12-30 10:38:10 +10:00
peteGSX
ec4dfb8c1e New working rotary encoder branch 2022-12-30 09:46:42 +10:00
peteGSX
6482a421b4 Merge pull request #282 from DCC-EX/add-ex-ioexpander
Add-ex-ioexpander
2022-12-30 08:10:23 +10:00
peteGSX
d02c6b1f61 Merge branch 'devel' into add-ex-ioexpander 2022-12-30 08:04:49 +10:00
Asbelos
94c8dafeb2 renamed macros 2022-12-29 10:38:04 +00:00
peteGSX
322cb3db54 Include driver in IODevice.h 2022-12-29 08:44:08 +10:00
peteGSX
ffdf023de6 Clean up 2022-12-29 05:10:37 +10:00
peteGSX
8f32ae712f Fix myHal example 2022-12-27 10:13:08 +10:00
peteGSX
eea1396997 Remove EX-IO pin macros 2022-12-27 10:10:44 +10:00
Asbelos
b1bd28273d duinoNodes support 2022-12-26 11:06:42 +00:00
Asbelos
0be25f6e7f Squashed commit of the following:
commit e06668f042
Author: Asbelos <asbelos@btinternet.com>
Date:   Mon Dec 26 10:09:34 2022 +0000

    speedup

commit 3e5d3b1caa
Author: Asbelos <asbelos@btinternet.com>
Date:   Sun Dec 25 22:11:56 2022 +0000

    Rename

commit 81099af42b
Author: Asbelos <asbelos@btinternet.com>
Date:   Sun Dec 25 21:35:38 2022 +0000

    spelling and polling

commit 9240e7c6ba
Author: Asbelos <asbelos@btinternet.com>
Date:   Sun Dec 25 20:52:07 2022 +0000

    input working

commit 6c1c681a26
Author: Asbelos <asbelos@btinternet.com>
Date:   Wed Dec 21 11:18:39 2022 +0000

    input working

    1 board, no kit map, output untested

commit 5ce67fac97
Author: Asbelos <asbelos@btinternet.com>
Date:   Sun Dec 18 15:32:37 2022 +0000

    Include IO_DNU08 automatically

commit ac8d453d2c
Author: Asbelos <asbelos@btinternet.com>
Date:   Sun Dec 18 12:28:13 2022 +0000

    BNOU8 HAL driver
2022-12-26 10:41:15 +00:00
peteGSX
71ce913712 Version bugfix 2022-12-26 07:36:12 +10:00
peteGSX
70845b4932 Receive/display EXIO version 2022-12-26 06:44:15 +10:00
peteGSX
c44fb0ac44 Disable device driver version, add myHal example 2022-12-22 07:22:04 +10:00
peteGSX
1c7103c21e Analogue read bugfix 2022-12-21 08:37:23 +10:00
peteGSX
5170147e3e Error checking pin config, code tidy 2022-12-20 19:41:32 +10:00
peteGSX
2ad08029a4 Remove excess DIAG output 2022-12-20 08:05:05 +10:00
peteGSX
25b3250345 Digital read working 2022-12-20 07:08:42 +10:00
peteGSX
3973996344 Digital pin config done, digital read in progress 2022-12-19 14:24:49 +10:00
peteGSX
943494385f Add digital write 2022-12-18 18:59:16 +10:00
peteGSX
c8fea3a4a7 Add version, analogue reads working 2022-12-18 09:43:11 +10:00
Asbelos
1d61a8f3f9 HIGHMEM + WITHROTTLE
EXRAIL HIGHMEM feature affects parser and withrottle.

Ringstream and wifi fixes

Withrottle connecting / reconnecting
2022-12-16 13:14:48 +00:00
peteGSX
a480a5a3d2 Add comments, remove unnecessary functions 2022-12-15 15:10:53 +10:00
peteGSX
070daa37dc Move buffers to constructor 2022-12-15 07:58:21 +10:00
peteGSX
75f1a8f43a More bugs to fix 2022-12-14 07:49:09 +10:00
Harald Barth
ad294ea17e typo 2022-12-13 15:29:20 +01:00
peteGSX
8ecb408da7 Update I2C address, fix bug setting analogue pins 2022-12-13 19:51:41 +10:00
peteGSX
3862f7250d Fix bugs, learn I2CManager 2022-12-12 19:54:20 +10:00
peteGSX
785b515f9e Bug fixes, update registers 2022-12-11 19:44:42 +10:00
peteGSX
9699a44081 Rename pin file 2022-12-11 10:25:29 +10:00
peteGSX
cb9a8bb7a6 Getting somewhere 2022-12-11 10:22:48 +10:00
peteGSX
1d5897d2d2 A bit lost 2022-12-10 19:14:32 +10:00
peteGSX
7bc0433197 Add myHal.cpp example to driver 2022-12-10 08:32:15 +10:00
peteGSX
9104956009 Fix default pin maps 2022-12-10 08:28:20 +10:00
peteGSX
af4d8d4075 Merge branch 'add-ex-ioexpander' of https://github.com/DCC-EX/CommandStation-EX into add-ex-ioexpander 2022-12-10 08:25:05 +10:00
peteGSX
06945bb114 Try to add pin map classes 2022-12-10 08:23:46 +10:00
peteGSX
2d27cb052d Add registers 2022-12-09 14:41:48 +10:00
peteGSX
8cbcf5df32 Basic shell of device driver started 2022-12-08 14:21:01 +10:00
Harald Barth
13368c319a reuse WiThrottle list entries 2022-12-05 15:52:23 +01:00
Harald Barth
cb1fc75077 version 2022-12-04 20:12:04 +01:00
Harald Barth
b671d70dfe fix static IP addr 2022-12-04 20:09:14 +01:00
Harald Barth
45b36c48cb be more strict about int vs char for the wifi diag 2022-11-30 13:16:04 +01:00
pmantoine
d95096ded8 Fixes STM32 compiler warning, and WIT/WIFI diags 2022-11-30 10:11:27 +08:00
pmantoine
a6ae1a48a2 Fixed STM32F4xx MAC address simulation 2022-11-28 10:44:41 +08:00
pmantoine
913f0a0c86 STM32F412ZG/F446ZE serial support update 2022-11-27 21:04:49 +08:00
Harald Barth
984fd2fa08 select better bytes for the faked mac addr 2022-11-25 20:44:16 +01:00
Harald Barth
d062de2eb8 better pseudo random 2022-11-24 20:24:57 +01:00
Harald Barth
e1ec63464c Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2022-11-23 22:45:59 +01:00
Harald Barth
aa02cd11e3 firebox disable, gcc flag for smaller binary 2022-11-23 22:44:29 +01:00
Neil McKechnie
ce5d546b9b Update DCC.cpp to remove diagnostic code. 2022-11-22 17:26:11 +00:00
Neil McKechnie
644cb29a3a Performance improvements in function DCC::issueReminders
Function issueReminders was taking around 1700us to complete.  It has been refactored to optimise calculations and reduce the amount of the loco table that needs to be scanned each time.  It now takes typically under 50us to execute.
2022-11-22 17:24:11 +00:00
Neil McKechnie
70203c3733 Fix to IO_DFPlayer.h - device was ignoring commands
The DFPlayer device does not like successive commands arriving to quickly after one another and may ignore a command.  The driver has been modified to enforce a delay between commands by sending pad characters where necessary.
2022-11-22 17:15:13 +00:00
Harald Barth
f4aa572df2 Remove RAM thief 2022-11-18 20:19:53 +01:00
Harald Barth
01e5d49332 version 2022-11-16 00:14:54 +01:00
Harald Barth
6a3a891682 break to va_end() 2022-11-16 00:14:11 +01:00
Harald Barth
f5b48619bf AVR Mega2560: Set timer reg ADCSRB correct 2022-11-16 00:13:31 +01:00
37 changed files with 1487 additions and 428 deletions

View File

@@ -29,6 +29,11 @@
#include "DCCWaveform.h"
#include "DCC.h"
#include "TrackManager.h"
#include "StringFormatter.h"
// variables to hold clock time
int16_t lastclocktime;
int8_t lastclockrate;
#if WIFI_ON || ETHERNET_ON || defined(SERIAL1_COMMANDS) || defined(SERIAL2_COMMANDS) || defined(SERIAL3_COMMANDS)
@@ -97,7 +102,7 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream
}
void CommandDistributor::forget(byte clientId) {
// keep for later if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId);
if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId);
clients[clientId]=NONE_TYPE;
}
#endif
@@ -155,6 +160,50 @@ void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) {
#endif
}
void CommandDistributor::broadcastClockTime(int16_t time, int8_t rate) {
// The JMRI clock command is of the form : PFT65871<;>4
// The CS broadcast is of the form "<jC mmmm nn" where mmmm is time minutes and dd speed
// The string below contains serial and Withrottle protocols which should
// be safe for both types.
broadcastReply(COMMAND_TYPE, F("<jC %d %d>\n"),time, rate);
#ifdef CD_HANDLE_RING
broadcastReply(WITHROTTLE_TYPE, F("PFT%d<;>%d\n"), time*60, rate);
#endif
}
void CommandDistributor::setClockTime(int16_t clocktime, int8_t clockrate, byte opt) {
// opt - case 1 save the latest time if changed
// case 2 broadcast the time when requested
// case 3 display latest time
switch (opt)
{
case 1:
if (clocktime != lastclocktime){
if (Diag::CMD) {
DIAG(F("Clock Command Received"));
DIAG(F("Received Clock Time is: %d at rate: %d"), clocktime, clockrate);
}
LCD(6,F("Clk Time:%d Sp %d"), clocktime, clockrate);
// look for an event for this time
RMFT2::clockEvent(clocktime,1);
// Now tell everyone else what the time is.
CommandDistributor::broadcastClockTime(clocktime, clockrate);
lastclocktime = clocktime;
lastclockrate = clockrate;
}
return;
case 2:
CommandDistributor::broadcastClockTime(lastclocktime, lastclockrate);
return;
}
}
int16_t CommandDistributor::retClockTime() {
return lastclocktime;
}
void CommandDistributor::broadcastLoco(byte slot) {
DCC::LOCO * sp=&DCC::speedTable[slot];
broadcastReply(COMMAND_TYPE, F("<l %d %d %d %l>\n"), sp->loco,slot,sp->speedCode,sp->functions);

View File

@@ -25,6 +25,7 @@
#include "RingStream.h"
#include "StringBuffer.h"
#include "defines.h"
#include "EXRAIL2.h"
#if WIFI_ON | ETHERNET_ON
// Command Distributor must handle a RingStream of clients
@@ -45,10 +46,14 @@ public :
static void broadcastLoco(byte slot);
static void broadcastSensor(int16_t id, bool value);
static void broadcastTurnout(int16_t id, bool isClosed);
static void broadcastClockTime(int16_t time, int8_t rate);
static void setClockTime(int16_t time, int8_t rate, byte opt);
static int16_t retClockTime();
static void broadcastPower();
static void broadcastText(const FSH * msg);
template<typename... Targs> static void broadcastReply(clientType type, Targs... msg);
static void forget(byte clientId);
};
#endif

41
DCC.cpp
View File

@@ -595,30 +595,15 @@ void DCC::loop() {
void DCC::issueReminders() {
// if the main track transmitter still has a pending packet, skip this time around.
if ( DCCWaveform::mainTrack.getPacketPending()) return;
// This loop searches for a loco in the speed table starting at nextLoco and cycling back around
/*
for (int reg=0;reg<MAX_LOCOS;reg++) {
int slot=reg+nextLoco;
if (slot>=MAX_LOCOS) slot-=MAX_LOCOS;
if (speedTable[slot].loco > 0) {
// have found the next loco to remind
// issueReminder will return true if this loco is completed (ie speed and functions)
if (issueReminder(slot)) nextLoco=slot+1;
return;
}
}
*/
for (int reg=nextLoco;reg<MAX_LOCOS+nextLoco;reg++) {
int slot=reg%MAX_LOCOS;
if (speedTable[slot].loco > 0) {
// have found the next loco to remind
// issueReminder will return true if this loco is completed (ie speed and functions)
if (issueReminder(slot))
nextLoco=(slot+1)%MAX_LOCOS;
return;
}
}
// Move to next loco slot. If occupied, send a reminder.
int reg = lastLocoReminder+1;
if (reg > highestUsedReg) reg = 0; // Go to start of table
if (speedTable[reg].loco > 0) {
// have found loco to remind
if (issueReminder(reg))
lastLocoReminder = reg;
} else
lastLocoReminder = reg;
}
bool DCC::issueReminder(int reg) {
@@ -698,6 +683,7 @@ int DCC::lookupSpeedTable(int locoId, bool autoCreate) {
speedTable[reg].groupFlags=0;
speedTable[reg].functions=0;
}
if (reg > highestUsedReg) highestUsedReg = reg;
return reg;
}
@@ -705,7 +691,7 @@ void DCC::updateLocoReminder(int loco, byte speedCode) {
if (loco==0) {
// broadcast stop/estop but dont change direction
for (int reg = 0; reg < MAX_LOCOS; reg++) {
for (int reg = 0; reg < highestUsedReg; reg++) {
if (speedTable[reg].loco==0) continue;
byte newspeed=(speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f);
if (speedTable[reg].speedCode != newspeed) {
@@ -725,13 +711,14 @@ void DCC::updateLocoReminder(int loco, byte speedCode) {
}
DCC::LOCO DCC::speedTable[MAX_LOCOS];
int DCC::nextLoco = 0;
int DCC::lastLocoReminder = 0;
int DCC::highestUsedReg = 0;
void DCC::displayCabList(Print * stream) {
int used=0;
for (int reg = 0; reg < MAX_LOCOS; reg++) {
for (int reg = 0; reg <= highestUsedReg; reg++) {
if (speedTable[reg].loco>0) {
used ++;
StringFormatter::send(stream,F("cab=%d, speed=%d, dir=%c \n"),

3
DCC.h
View File

@@ -108,7 +108,8 @@ private:
static void updateLocoReminder(int loco, byte speedCode);
static void setFunctionInternal(int cab, byte fByte, byte eByte);
static bool issueReminder(int reg);
static int nextLoco;
static int lastLocoReminder;
static int highestUsedReg;
static FSH *shieldName;
static byte globalSpeedsteps;

View File

@@ -41,6 +41,14 @@
#include "DCCTimer.h"
#include "EXRAIL2.h"
// This macro can't be created easily as a portable function because the
// flashlist requires a far pointer for high flash access.
#define SENDFLASHLIST(stream,flashList) \
for (int16_t i=0;;i+=sizeof(flashList[0])) { \
int16_t value=GETHIGHFLASHW(flashList,i); \
if (value==0) break; \
StringFormatter::send(stream,F(" %d"),value); \
}
// These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter.
@@ -562,15 +570,25 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
case 'J' : // throttle info access
{
if ((params<1) | (params>2)) break; // <J>
if ((params<1) | (params>3)) break; // <J>
//if ((params<1) | (params>2)) break; // <J>
int16_t id=(params==2)?p[1]:0;
switch(p[0]) {
case HASH_KEYWORD_C: // <JC mmmm nn> sets time and speed
if (params==1) { // <JC> returns latest time
int16_t x = CommandDistributor::retClockTime();
StringFormatter::send(stream, F("<jC %d>\n"), x);
return;
}
CommandDistributor::setClockTime(p[1], p[2], 1);
return;
case HASH_KEYWORD_A: // <JA> returns automations/routes
StringFormatter::send(stream, F("<jA"));
if (params==1) {// <JA>
#ifdef EXRAIL_ACTIVE
sendFlashList(stream,RMFT2::routeIdList);
sendFlashList(stream,RMFT2::automationIdList);
SENDFLASHLIST(stream,RMFT2::routeIdList)
SENDFLASHLIST(stream,RMFT2::automationIdList)
#endif
}
else { // <JA id>
@@ -589,7 +607,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
case HASH_KEYWORD_R: // <JR> returns rosters
StringFormatter::send(stream, F("<jR"));
#ifdef EXRAIL_ACTIVE
if (params==1) sendFlashList(stream,RMFT2::rosterIdList);
if (params==1) {
SENDFLASHLIST(stream,RMFT2::rosterIdList)
}
else StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
id, RMFT2::getRosterName(id), RMFT2::getRosterFunctions(id));
#endif
@@ -634,14 +654,6 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
StringFormatter::send(stream, F("<X>\n"));
}
void DCCEXParser::sendFlashList(Print * stream,const int16_t flashList[]) {
for (int16_t i=0;;i++) {
int16_t value=GETFLASHW(flashList+i);
if (value==0) return;
StringFormatter::send(stream,F(" %d"),value);
}
}
bool DCCEXParser::parseZ(Print *stream, int16_t params, int16_t p[])
{

View File

@@ -88,7 +88,10 @@ void DCCTimer::clearPWM() {
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
for (byte i=0; i<6; i++) {
mac[i]=boot_signature_byte_get(0x0E + i);
// take the fist 3 and last 3 of the serial.
// the first 5 of 8 are at 0x0E to 0x013
// the last 3 of 8 are at 0x15 to 0x017
mac[i]=boot_signature_byte_get(0x0E + i + (i>2? 4 : 0));
}
mac[0] &= 0xFE;
mac[0] |= 0x02;
@@ -126,6 +129,7 @@ void DCCTimer::reset() {
#endif
uint16_t ADCee::usedpins = 0;
int * ADCee::analogvals = NULL;
bool ADCusesHighPort = false;
/*
* Register a new pin to be scanned
@@ -136,6 +140,8 @@ int ADCee::init(uint8_t pin) {
uint8_t id = pin - A0;
if (id > NUM_ADC_INPUTS)
return -1023;
if (id > 7)
ADCusesHighPort = true;
pinMode(pin, INPUT);
int value = analogRead(pin);
if (analogvals == NULL)
@@ -196,7 +202,15 @@ void ADCee::scan() {
while (true) {
if (mask & usedpins) {
// start new ADC aquire on id
ADMUX=(1<<REFS0)|id; //select AVCC as reference and set MUX
#if defined(ADCSRB) && defined(MUX5)
if (ADCusesHighPort) { // if we ever have started to use high pins)
if (id > 7) // if we use a high ADC pin
bitSet(ADCSRB, MUX5); // set MUX5 bit
else
bitClear(ADCSRB, MUX5);
}
#endif
ADMUX=(1<<REFS0)|(id & 0x07); //select AVCC as reference and set MUX
bitSet(ADCSRA,ADSC); // start conversion
// for scope debug TrackManager::track[1]->setBrake(1);
waiting = true;

View File

@@ -28,23 +28,23 @@
// This is to avoid repetition and duplication.
#ifdef ARDUINO_ARCH_STM32
#include "FSH.h" //PMA temp debug
#include "DIAG.h" //PMA temp debug
#include "DCCTimer.h"
#define STM32F411RE // PMA - ideally this ought to be derived from within the STM32 support somehow
#if defined(STM32F411RE)
// STM32F411RE doesn't have Serial1 defined by default
#if defined(ARDUINO_NUCLEO_F411RE)
// Nucleo-64 boards don't have Serial1 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
// via the debugger on the Nucleo-64 STM32F411RE. 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 F411RE)
// 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 Nucleo-64s)
HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE
#elif defined(STM32F446ZE)
// STM32F446ZE doesn't have Serial1 defined by default
HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F446ZE
#elif defined(ARDUINO_NUCLEO_F446RE)
// Nucleo-64 boards don't have Serial1 defined by default
HardwareSerial Serial1(PA10, PB6); // Rx=PA10, Tx=PB6 -- CN10 pins 33 and 17 - F446RE
// 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.
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
// Nucleo-144 boards don't have Serial1 defined by default
HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE
#else
#warning Serial1 not defined
#endif
@@ -94,10 +94,9 @@ void DCCTimer::clearPWM() {
}
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
volatile uint32_t *serno1 = (volatile uint32_t *)0x0080A00C;
volatile uint32_t *serno2 = (volatile uint32_t *)0x0080A040;
// volatile uint32_t *serno3 = (volatile uint32_t *)0x0080A044;
// volatile uint32_t *serno4 = (volatile uint32_t *)0x0080A048;
volatile uint32_t *serno1 = (volatile uint32_t *)0x1FFF7A10;
volatile uint32_t *serno2 = (volatile uint32_t *)0x1FFF7A14;
volatile uint32_t *serno3 = (volatile uint32_t *)0x1FFF7A18;
volatile uint32_t m1 = *serno1;
volatile uint32_t m2 = *serno2;

View File

@@ -41,6 +41,7 @@
*/
#include <Arduino.h>
#include "defines.h"
#include "EXRAIL2.h"
#include "DCC.h"
#include "DCCWaveform.h"
@@ -90,15 +91,27 @@ LookList * RMFT2::onDeactivateLookup=NULL;
LookList * RMFT2::onRedLookup=NULL;
LookList * RMFT2::onAmberLookup=NULL;
LookList * RMFT2::onGreenLookup=NULL;
#define GET_OPCODE GETFLASH(RMFT2::RouteCode+progCounter)
#ifdef ARDUINO_ARCH_AVR
#define GET_OPERAND(n) GETFLASHW(RMFT2::RouteCode+progCounter+1+(n*3))
#else
#define GET_OPERAND(n) GETOPW(RMFT2::RouteCode+progCounter+1+(n*3))
#define GETOPW(A) (((uint32_t)A)%2 ? GETFLASH((const byte *)A) | (GETFLASH(1+(const byte *)A)<<8) : GETFLASHW(A))
#endif
LookList * RMFT2::onChangeLookup=NULL;
LookList * RMFT2::onClockLookup=NULL;
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
#define SKIPOP progCounter+=3
// getOperand instance version, uses progCounter from instance.
uint16_t RMFT2::getOperand(byte n) {
return getOperand(progCounter,n);
}
// getOperand static version, must be provided prog counter from loop etc.
uint16_t RMFT2::getOperand(int progCounter,byte n) {
int offset=progCounter+1+(n*3);
if (offset&1) {
byte lsb=GETHIGHFLASH(RouteCode,offset);
byte msb=GETHIGHFLASH(RouteCode,offset+1);
return msb<<8|lsb;
}
return GETHIGHFLASHW(RouteCode,offset);
}
LookList::LookList(int16_t size) {
m_size=size;
@@ -139,12 +152,17 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
for (progCounter=0;; SKIPOP) {
byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) break;
if (opcode==op1 || opcode==op2 || opcode==op3) list->add(GET_OPERAND(0),progCounter);
if (opcode==op1 || opcode==op2 || opcode==op3) list->add(getOperand(progCounter,0),progCounter);
}
return list;
}
/* static */ void RMFT2::begin() {
DIAG(F("EXRAIL RoutCode at =%P"),RouteCode);
bool saved_diag=diag;
diag=true;
DCCEXParser::setRMFTFilter(RMFT2::ComandFilter);
for (int f=0;f<MAX_FLAGS;f++) flags[f]=0;
@@ -157,11 +175,14 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
onRedLookup=LookListLoader(OPCODE_ONRED);
onAmberLookup=LookListLoader(OPCODE_ONAMBER);
onGreenLookup=LookListLoader(OPCODE_ONGREEN);
onChangeLookup=LookListLoader(OPCODE_ONCHANGE);
onClockLookup=LookListLoader(OPCODE_ONTIME);
// Second pass startup, define any turnouts or servos, set signals red
// add sequences onRoutines to the lookups
for (int sigpos=0;;sigpos+=4) {
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
for (int sigslot=0;;sigslot++) {
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
if (sigid==0) break; // end of signal list
doSignal(sigid & SIGNAL_ID_MASK, SIGNAL_RED);
}
@@ -170,7 +191,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
for (progCounter=0;; SKIPOP){
byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) break;
VPIN operand=GET_OPERAND(0);
VPIN operand=getOperand(progCounter,0);
switch (opcode) {
case OPCODE_AT:
@@ -180,6 +201,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
case OPCODE_IFNOT: {
int16_t pin = (int16_t)operand;
if (pin<0) pin = -pin;
DIAG(F("EXRAIL input vpin %d"),pin);
IODevice::configureInput((VPIN)pin,true);
break;
}
@@ -189,31 +211,32 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
case OPCODE_IFGTE:
case OPCODE_IFLT:
case OPCODE_DRIVE: {
DIAG(F("EXRAIL analog input vpin %d"),(VPIN)operand);
IODevice::configureAnalogIn((VPIN)operand);
break;
}
case OPCODE_TURNOUT: {
VPIN id=operand;
int addr=GET_OPERAND(1);
byte subAddr=GET_OPERAND(2);
int addr=getOperand(progCounter,1);
byte subAddr=getOperand(progCounter,2);
setTurnoutHiddenState(DCCTurnout::create(id,addr,subAddr));
break;
}
case OPCODE_SERVOTURNOUT: {
VPIN id=operand;
VPIN pin=GET_OPERAND(1);
int activeAngle=GET_OPERAND(2);
int inactiveAngle=GET_OPERAND(3);
int profile=GET_OPERAND(4);
VPIN pin=getOperand(progCounter,1);
int activeAngle=getOperand(progCounter,2);
int inactiveAngle=getOperand(progCounter,3);
int profile=getOperand(progCounter,4);
setTurnoutHiddenState(ServoTurnout::create(id,pin,activeAngle,inactiveAngle,profile));
break;
}
case OPCODE_PINTURNOUT: {
VPIN id=operand;
VPIN pin=GET_OPERAND(1);
VPIN pin=getOperand(progCounter,1);
setTurnoutHiddenState(VpinTurnout::create(id,pin));
break;
}
@@ -233,20 +256,22 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
DIAG(F("EXRAIL %db, fl=%d"),progCounter,MAX_FLAGS);
new RMFT2(0); // add the startup route
diag=saved_diag;
}
void RMFT2::setTurnoutHiddenState(Turnout * t) {
// turnout descriptions are in low flash F strings
t->setHidden(GETFLASH(getTurnoutDescription(t->getId()))==0x01);
}
char RMFT2::getRouteType(int16_t id) {
for (int16_t i=0;;i++) {
int16_t rid= GETFLASHW(routeIdList+i);
for (int16_t i=0;;i+=2) {
int16_t rid= GETHIGHFLASHW(routeIdList,i);
if (rid==id) return 'R';
if (rid==0) break;
}
for (int16_t i=0;;i++) {
int16_t rid= GETFLASHW(automationIdList+i);
for (int16_t i=0;;i+=2) {
int16_t rid= GETHIGHFLASHW(automationIdList,i);
if (rid==id) return 'A';
if (rid==0) break;
}
@@ -308,7 +333,7 @@ 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++) {
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigslot*4);
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
if (sigid==0) break; // end of signal list
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id
StringFormatter::send(stream,F("\n%S[%d]"),
@@ -555,7 +580,7 @@ void RMFT2::loop2() {
if (delayTime!=0 && millis()-delayStart < delayTime) return;
byte opcode = GET_OPCODE;
int16_t operand = GET_OPERAND(0);
int16_t operand = getOperand(0);
// skipIf will get set to indicate a failing IF condition
bool skipIf=false;
@@ -621,13 +646,13 @@ void RMFT2::loop2() {
case OPCODE_ATGTE: // wait for analog sensor>= value
timeoutFlag=false;
if (IODevice::readAnalogue(operand) >= (int)(GET_OPERAND(1))) break;
if (IODevice::readAnalogue(operand) >= (int)(getOperand(1))) break;
delayMe(50);
return;
case OPCODE_ATLT: // wait for analog sensor < value
timeoutFlag=false;
if (IODevice::readAnalogue(operand) < (int)(GET_OPERAND(1))) break;
if (IODevice::readAnalogue(operand) < (int)(getOperand(1))) break;
delayMe(50);
return;
@@ -638,7 +663,7 @@ void RMFT2::loop2() {
case OPCODE_ATTIMEOUT2:
if (readSensor(operand)) break; // success without timeout
if (millis()-timeoutStart > 100*GET_OPERAND(1)) {
if (millis()-timeoutStart > 100*getOperand(1)) {
timeoutFlag=true;
break; // and drop through
}
@@ -681,7 +706,7 @@ void RMFT2::loop2() {
break;
case OPCODE_POM:
if (loco) DCC::writeCVByteMain(loco, operand, GET_OPERAND(1));
if (loco) DCC::writeCVByteMain(loco, operand, getOperand(1));
break;
case OPCODE_POWEROFF:
@@ -715,19 +740,27 @@ void RMFT2::loop2() {
break;
case OPCODE_IFGTE: // do next operand if sensor>= value
skipIf=IODevice::readAnalogue(operand)<(int)(GET_OPERAND(1));
skipIf=IODevice::readAnalogue(operand)<(int)(getOperand(1));
break;
case OPCODE_IFLT: // do next operand if sensor< value
skipIf=IODevice::readAnalogue(operand)>=(int)(GET_OPERAND(1));
skipIf=IODevice::readAnalogue(operand)>=(int)(getOperand(1));
break;
case OPCODE_IFLOCO: // do if the loco is the active one
skipIf=loco!=operand;
break;
case OPCODE_IFNOT: // do next operand if sensor not set
skipIf=readSensor(operand);
break;
case OPCODE_IFRE: // do next operand if rotary encoder != position
skipIf=IODevice::readAnalogue(operand)!=(int)(getOperand(1));
break;
case OPCODE_IFRANDOM: // do block on random percentage
skipIf=(int16_t)(micros()%100) >= operand;
skipIf=(uint8_t)micros() >= operand * 255/100;
break;
case OPCODE_IFRESERVE: // do block if we successfully RERSERVE
@@ -802,11 +835,11 @@ void RMFT2::loop2() {
}
case OPCODE_XFON:
DCC::setFn(operand,GET_OPERAND(1),true);
DCC::setFn(operand,getOperand(1),true);
break;
case OPCODE_XFOFF:
DCC::setFn(operand,GET_OPERAND(1),false);
DCC::setFn(operand,getOperand(1),false);
break;
case OPCODE_DCCACTIVATE: {
@@ -898,7 +931,7 @@ void RMFT2::loop2() {
case OPCODE_SENDLOCO: // cab, route
{
int newPc=sequenceLookup->find(GET_OPERAND(1));
int newPc=sequenceLookup->find(getOperand(1));
if (newPc<0) break;
RMFT2* newtask=new RMFT2(newPc); // create new task
newtask->loco=operand;
@@ -916,7 +949,7 @@ void RMFT2::loop2() {
case OPCODE_SERVO: // OPCODE_SERVO,V(vpin),OPCODE_PAD,V(position),OPCODE_PAD,V(profile),OPCODE_PAD,V(duration)
IODevice::writeAnalogue(operand,GET_OPERAND(1),GET_OPERAND(2),GET_OPERAND(3));
IODevice::writeAnalogue(operand,getOperand(1),getOperand(2),getOperand(3));
break;
case OPCODE_WAITFOR: // OPCODE_SERVO,V(pin)
@@ -948,6 +981,8 @@ void RMFT2::loop2() {
case OPCODE_ONRED:
case OPCODE_ONAMBER:
case OPCODE_ONGREEN:
case OPCODE_ONCHANGE:
case OPCODE_ONTIME:
break;
@@ -965,7 +1000,7 @@ void RMFT2::delayMe(long delay) {
delayStart=millis();
}
boolean RMFT2::setFlag(VPIN id,byte onMask, byte offMask) {
bool RMFT2::setFlag(VPIN id,byte onMask, byte offMask) {
if (FLAGOVERFLOW(id)) return false; // Outside range limit
byte f=flags[id];
f &= ~offMask;
@@ -986,8 +1021,8 @@ void RMFT2::kill(const FSH * reason, int operand) {
}
int16_t RMFT2::getSignalSlot(int16_t id) {
for (int sigpos=0;;sigpos+=4) {
int16_t sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
for (int sigslot=0;;sigslot++) {
int16_t sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
if (sigid==0) { // end of signal list
DIAG(F("EXRAIL Signal %d not defined"), id);
return -1;
@@ -997,9 +1032,10 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
// but for a servo signal it will also have SERVO_SIGNAL_FLAG set.
if ((sigid & SIGNAL_ID_MASK)!= id) continue; // keep looking
return sigpos/4; // relative slot in signals table
return sigslot; // relative slot in signals table
}
}
/* static */ void RMFT2::doSignal(int16_t id,char rag) {
if (diag) DIAG(F(" doSignal %d %x"),id,rag);
@@ -1016,11 +1052,11 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
setFlag(sigslot,rag,SIGNAL_MASK);
// Correct signal definition found, get the rag values
int16_t sigpos=sigslot*4;
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
VPIN redpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+1);
VPIN amberpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+2);
VPIN greenpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+3);
int16_t sigpos=sigslot*8;
VPIN sigid=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=sigid & ~SIGNAL_ID_MASK;
@@ -1048,11 +1084,23 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
// Manage invert (HIGH on) pins
bool aHigh=sigid & ACTIVE_HIGH_SIGNAL_FLAG;
// set the three pins
if (redpin) IODevice::write(redpin,(rag==SIGNAL_RED || rag==SIMAMBER)^aHigh);
if (amberpin) IODevice::write(amberpin,(rag==SIGNAL_AMBER)^aHigh);
if (greenpin) IODevice::write(greenpin,(rag==SIGNAL_GREEN || rag==SIMAMBER)^aHigh);
if (redpin) {
bool redval=(rag==SIGNAL_RED || rag==SIMAMBER);
if (!aHigh) redval=!redval;
IODevice::write(redpin,redval);
}
if (amberpin) {
bool amberval=(rag==SIGNAL_AMBER);
if (!aHigh) amberval=!amberval;
IODevice::write(amberpin,amberval);
}
if (greenpin) {
bool greenval=(rag==SIGNAL_GREEN || rag==SIMAMBER);
if (!aHigh) greenval=!greenval;
IODevice::write(greenpin,greenval);
}
}
/* static */ bool RMFT2::isSignal(int16_t id,char rag) {
@@ -1073,7 +1121,19 @@ void RMFT2::activateEvent(int16_t addr, bool activate) {
if (activate) handleEvent(F("ACTIVATE"),onActivateLookup,addr);
else handleEvent(F("DEACTIVATE"),onDeactivateLookup,addr);
}
void RMFT2::changeEvent(int16_t vpin, bool change) {
// Hunt for an ONCHANGE for this sensor
if (change) handleEvent(F("CHANGE"),onChangeLookup,vpin);
}
void RMFT2::clockEvent(int16_t clocktime, bool change) {
// Hunt for an ONTIME for this time
if (Diag::CMD)
DIAG(F("Looking for clock event at : %d"), clocktime);
if (change) handleEvent(F("CLOCK"),onClockLookup,clocktime);
}
void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
int pc= handlers->find(id);
if (pc<0) return;
@@ -1096,3 +1156,95 @@ void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
void RMFT2::printMessage2(const FSH * msg) {
DIAG(F("EXRAIL(%d) %S"),loco,msg);
}
static StringBuffer * buffer=NULL;
/* thrungeString is used to stream a HIGHFLASH string to a suitable Serial
and handle the oddities like LCD, BROADCAST and PARSE */
void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
//DIAG(F("thrunge addr=%l mode=%d id=%d"), strfar,mode,id);
Print * stream=NULL;
// Find out where the string is going
switch (mode) {
case thrunge_print:
StringFormatter::send(&Serial,F("<* EXRAIL(%d) "),loco);
stream=&Serial;
break;
case thrunge_serial: stream=&Serial; break;
case thrunge_serial1:
#ifdef SERIAL1_COMMANDS
stream=&Serial1;
#endif
break;
case thrunge_serial2:
#ifdef SERIAL2_COMMANDS
stream=&Serial2;
#endif
break;
case thrunge_serial3:
#ifdef SERIAL3_COMMANDS
stream=&Serial3;
#endif
break;
case thrunge_serial4:
#ifdef SERIAL4_COMMANDS
stream=&Serial4;
#endif
break;
case thrunge_serial5:
#ifdef SERIAL5_COMMANDS
stream=&Serial5;
#endif
break;
case thrunge_serial6:
#ifdef SERIAL6_COMMANDS
stream=&Serial6;
#endif
break;
// TODO more serials for SAMx case thrunge_serial4: stream=&Serial4; break;
case thrunge_lcn:
#if defined(LCN_SERIAL)
stream=&LCN_SERIAL;
#endif
break;
case thrunge_parse:
case thrunge_broadcast:
case thrunge_lcd:
if (!buffer) buffer=new StringBuffer();
buffer->flush();
stream=buffer;
break;
}
if (!stream) return;
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
// if mega stream it out
for (;;strfar++) {
char c=pgm_read_byte_far(strfar);
if (c=='\0') break;
stream->write(c);
}
#else
// UNO/NANO CPUs dont have high memory
// 32 bit cpus dont care anyway
stream->print((FSH *)strfar);
#endif
// and decide what to do next
switch (mode) {
case thrunge_print:
StringFormatter::send(&Serial,F(" *>\n"));
break;
// TODO more serials for SAMx case thrunge_serial4: stream=&Serial4; break;
case thrunge_parse:
DCCEXParser::parseOne(&Serial,(byte*)buffer->getString(),NULL);
break;
case thrunge_broadcast:
// TODO CommandDistributor::broadcastText(buffer->getString());
break;
case thrunge_lcd:
LCD(id,F("%s"),buffer->getString());
break;
default: break;
}
}

View File

@@ -54,6 +54,9 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_ENDTASK,OPCODE_ENDEXRAIL,
OPCODE_SET_TRACK,
OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN,
OPCODE_ONCHANGE,
OPCODE_ONCLOCKTIME,
OPCODE_ONTIME,
// OPcodes below this point are skip-nesting IF operations
// placed here so that they may be skipped as a group
@@ -64,9 +67,17 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_IFTIMEOUT,
OPCODE_IF,OPCODE_IFNOT,
OPCODE_IFRANDOM,OPCODE_IFRESERVE,
OPCODE_IFCLOSED,OPCODE_IFTHROWN
OPCODE_IFCLOSED,OPCODE_IFTHROWN,
OPCODE_IFRE,
OPCODE_IFLOCO
};
enum thrunger: byte {
thrunge_print, thrunge_broadcast, thrunge_serial,thrunge_parse,
thrunge_serial1, thrunge_serial2, thrunge_serial3,
thrunge_serial4, thrunge_serial5, thrunge_serial6,
thrunge_lcd, thrunge_lcn};
// Flag bits for status of hardware and TPL
@@ -107,16 +118,17 @@ class LookList {
static void createNewTask(int route, uint16_t cab);
static void turnoutEvent(int16_t id, bool closed);
static void activateEvent(int16_t addr, bool active);
static void changeEvent(int16_t id, bool change);
static void clockEvent(int16_t clocktime, bool change);
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 SIGNAL_ID_MASK=0x0FFF;
// Throttle Info Access functions built by exrail macros
static const byte rosterNameCount;
static const int16_t FLASH routeIdList[];
static const int16_t FLASH automationIdList[];
static const int16_t FLASH rosterIdList[];
static const int16_t HIGHFLASH routeIdList[];
static const int16_t HIGHFLASH automationIdList[];
static const int16_t HIGHFLASH rosterIdList[];
static const FSH * getRouteDescription(int16_t id);
static char getRouteType(int16_t id);
static const FSH * getTurnoutDescription(int16_t id);
@@ -137,6 +149,7 @@ private:
static LookList* LookListLoader(OPCODE op1,
OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL);
static void handleEvent(const FSH* reason,LookList* handlers, int16_t id);
static uint16_t getOperand(int progCounter,byte n);
static RMFT2 * loopTask;
static RMFT2 * pausingTask;
void delayMe(long millisecs);
@@ -148,10 +161,12 @@ private:
void kill(const FSH * reason=NULL,int operand=0);
void printMessage(uint16_t id); // Built by RMFTMacros.h
void printMessage2(const FSH * msg);
void thrungeString(uint32_t strfar, thrunger mode, byte id=0);
uint16_t getOperand(byte n);
static bool diag;
static const FLASH byte RouteCode[];
static const FLASH int16_t SignalDefinitions[];
static const HIGHFLASH byte RouteCode[];
static const HIGHFLASH int16_t SignalDefinitions[];
static byte flags[MAX_FLAGS];
static LookList * sequenceLookup;
static LookList * onThrowLookup;
@@ -161,7 +176,8 @@ private:
static LookList * onRedLookup;
static LookList * onAmberLookup;
static LookList * onGreenLookup;
static LookList * onChangeLookup;
static LookList * onClockLookup;
// Local variables - exist for each instance/task
RMFT2 *next; // loop chain

View File

@@ -65,6 +65,7 @@
#undef IFCLOSED
#undef IFGREEN
#undef IFGTE
#undef IFLOCO
#undef IFLT
#undef IFNOT
#undef IFRANDOM
@@ -72,6 +73,7 @@
#undef IFRESERVE
#undef IFTHROWN
#undef IFTIMEOUT
#undef IFRE
#undef INVERT_DIRECTION
#undef JOIN
#undef KILLALL
@@ -85,9 +87,12 @@
#undef ONDEACTIVATE
#undef ONDEACTIVATEL
#undef ONCLOSE
#undef ONTIME
#undef ONCLOCKTIME
#undef ONGREEN
#undef ONRED
#undef ONTHROW
#undef ONCHANGE
#undef PARSE
#undef PAUSE
#undef PIN_TURNOUT
@@ -110,6 +115,9 @@
#undef SERIAL1
#undef SERIAL2
#undef SERIAL3
#undef SERIAL4
#undef SERIAL5
#undef SERIAL6
#undef SERVO
#undef SERVO2
#undef SERVO_TURNOUT
@@ -175,6 +183,7 @@
#define IFCLOSED(turnout_id)
#define IFGREEN(signal_id)
#define IFGTE(sensor_id,value)
#define IFLOCO(loco_id)
#define IFLT(sensor_id,value)
#define IFNOT(sensor_id)
#define IFRANDOM(percent)
@@ -182,6 +191,7 @@
#define IFTHROWN(turnout_id)
#define IFRESERVE(block)
#define IFTIMEOUT
#define IFRE(sensor_id,value)
#define INVERT_DIRECTION
#define JOIN
#define KILLALL
@@ -192,12 +202,15 @@
#define ONACTIVATE(addr,subaddr)
#define ONACTIVATEL(linear)
#define ONAMBER(signal_id)
#define ONTIME(value)
#define ONCLOCKTIME(hours,mins)
#define ONDEACTIVATE(addr,subaddr)
#define ONDEACTIVATEL(linear)
#define ONCLOSE(turnout_id)
#define ONGREEN(signal_id)
#define ONRED(signal_id)
#define ONTHROW(turnout_id)
#define ONCHANGE(sensor_id)
#define PAUSE
#define PIN_TURNOUT(id,pin,description...)
#define PRINT(msg)
@@ -220,6 +233,9 @@
#define SERIAL1(msg)
#define SERIAL2(msg)
#define SERIAL3(msg)
#define SERIAL4(msg)
#define SERIAL5(msg)
#define SERIAL6(msg)
#define SERVO(id,position,profile)
#define SERVO2(id,position,duration)
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)

View File

@@ -55,6 +55,10 @@
// helper macro for turnout description as HIDDEN
#define HIDDEN "\x01"
// helper macro to strip leading zeros off time inputs
// (10#mins)%100)
#define STRIP_ZERO(value) 10##value%100
// Pass 1 Implements aliases
#include "EXRAIL2MacroReset.h"
#undef ALIAS
@@ -73,14 +77,14 @@ void exrailHalSetup() {
#include "EXRAIL2MacroReset.h"
#undef ROUTE
#define ROUTE(id, description) id,
const int16_t FLASH RMFT2::routeIdList[]= {
const int16_t HIGHFLASH RMFT2::routeIdList[]= {
#include "myAutomation.h"
0};
// Pass 2a create throttle automation list
#include "EXRAIL2MacroReset.h"
#undef AUTOMATION
#define AUTOMATION(id, description) id,
const int16_t FLASH RMFT2::automationIdList[]= {
const int16_t HIGHFLASH RMFT2::automationIdList[]= {
#include "myAutomation.h"
0};
@@ -100,30 +104,54 @@ const FSH * RMFT2::getRouteDescription(int16_t id) {
// Pass 4... Create Text sending functions
#include "EXRAIL2MacroReset.h"
const int StringMacroTracker1=__COUNTER__;
#define THRUNGE(msg,mode) \
case (__COUNTER__ - StringMacroTracker1) : {\
static const char HIGHFLASH thrunge[]=msg;\
strfar=(uint32_t)GETFARPTR(thrunge);\
tmode=mode;\
break;\
}
#undef BROADCAST
#define BROADCAST(msg) case (__COUNTER__ - StringMacroTracker1) : CommandDistributor::broadcastText(F(msg));break;
#define BROADCAST(msg) THRUNGE(msg,thrunge_broadcast)
#undef PARSE
#define PARSE(msg) case (__COUNTER__ - StringMacroTracker1) : DCCEXParser::parse(F(msg));break;
#define PARSE(msg) THRUNGE(msg,thrunge_parse)
#undef PRINT
#define PRINT(msg) case (__COUNTER__ - StringMacroTracker1) : printMessage2(F(msg));break;
#define PRINT(msg) THRUNGE(msg,thrunge_print)
#undef LCN
#define LCN(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&LCN_SERIAL,F(msg));break;
#define LCN(msg) THRUNGE(msg,thrunge_lcn)
#undef SERIAL
#define SERIAL(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial,F(msg));break;
#define SERIAL(msg) THRUNGE(msg,thrunge_serial)
#undef SERIAL1
#define SERIAL1(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial1,F(msg));break;
#define SERIAL1(msg) THRUNGE(msg,thrunge_serial1)
#undef SERIAL2
#define SERIAL2(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial2,F(msg));break;
#define SERIAL2(msg) THRUNGE(msg,thrunge_serial2)
#undef SERIAL3
#define SERIAL3(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial3,F(msg));break;
#define SERIAL3(msg) THRUNGE(msg,thrunge_serial3)
#undef SERIAL4
#define SERIAL4(msg) THRUNGE(msg,thrunge_serial4)
#undef SERIAL5
#define SERIAL5(msg) THRUNGE(msg,thrunge_serial5)
#undef SERIAL6
#define SERIAL6(msg) THRUNGE(msg,thrunge_serial6)
#undef LCD
#define LCD(id,msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::lcd(id,F(msg));break;
#define LCD(id,msg) \
case (__COUNTER__ - StringMacroTracker1) : {\
static const char HIGHFLASH thrunge[]=msg;\
strfar=(uint32_t)GETFARPTR(thrunge);\
tmode=thrunge_lcd; \
lcdid=id;\
break;\
}
void RMFT2::printMessage(uint16_t id) {
thrunger tmode;
uint32_t strfar=0;
byte lcdid=0;
switch(id) {
#include "myAutomation.h"
default: break ;
}
if (strfar) thrungeString(strfar,tmode,lcdid);
}
@@ -158,7 +186,7 @@ const byte RMFT2::rosterNameCount=0
#include "EXRAIL2MacroReset.h"
#undef ROSTER
#define ROSTER(cabid,name,funcmap...) cabid,
const int16_t FLASH RMFT2::rosterIdList[]={
const int16_t HIGHFLASH RMFT2::rosterIdList[]={
#include "myAutomation.h"
0};
@@ -198,7 +226,7 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) {
#undef VIRTUAL_SIGNAL
#define VIRTUAL_SIGNAL(id) id,0,0,0,
const FLASH int16_t RMFT2::SignalDefinitions[] = {
const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#include "myAutomation.h"
0,0,0,0 };
@@ -254,6 +282,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
#define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_id),
#define IFGREEN(signal_id) OPCODE_IFGREEN,V(signal_id),
#define IFGTE(sensor_id,value) OPCODE_IFGTE,V(sensor_id),OPCODE_PAD,V(value),
#define IFLOCO(loco_id) OPCODE_IFLOCO,V(loco_id),
#define IFLT(sensor_id,value) OPCODE_IFLT,V(sensor_id),OPCODE_PAD,V(value),
#define IFNOT(sensor_id) OPCODE_IFNOT,V(sensor_id),
#define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent),
@@ -261,6 +290,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
#define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id),
#define IFTIMEOUT OPCODE_IFTIMEOUT,0,0,
#define IFRE(sensor_id,value) OPCODE_IFRE,V(sensor_id),OPCODE_PAD,V(value),
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0,
#define JOIN OPCODE_JOIN,0,0,
#define KILLALL OPCODE_KILLALL,0,0,
@@ -272,11 +302,14 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
#define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3),
#define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id),
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
#define ONTIME(value) OPCODE_ONTIME,V(value),
#define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(mins)),
#define ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr),
#define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3),
#define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id),
#define ONRED(signal_id) OPCODE_ONRED,V(signal_id),
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id),
#define ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id),
#define PAUSE OPCODE_PAUSE,0,0,
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
@@ -299,6 +332,9 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
#define SERIAL1(msg) PRINT(msg)
#define SERIAL2(msg) PRINT(msg)
#define SERIAL3(msg) PRINT(msg)
#define SERIAL4(msg) PRINT(msg)
#define SERIAL5(msg) PRINT(msg)
#define SERIAL6(msg) PRINT(msg)
#define SERVO(id,position,profile) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::profile),OPCODE_PAD,V(0),
#define SERVO2(id,position,ms) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::Instant),OPCODE_PAD,V(ms/100L),
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)
@@ -323,7 +359,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
// Build RouteCode
const int StringMacroTracker2=__COUNTER__;
const FLASH byte RMFT2::RouteCode[] = {
const HIGHFLASH byte RMFT2::RouteCode[] = {
#include "myAutomation.h"
OPCODE_ENDTASK,0,0,OPCODE_ENDEXRAIL,0,0 };

View File

@@ -60,14 +60,14 @@ EthernetInterface::EthernetInterface()
connected=false;
#ifdef IP_ADDRESS
if (Ethernet.begin(mac, IP_ADDRESS) == 0)
Ethernet.begin(mac, IP_ADDRESS);
#else
if (Ethernet.begin(mac) == 0)
#endif
{
DIAG(F("Ethernet.begin FAILED"));
return;
}
#endif
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
DIAG(F("Ethernet shield not found or W5100"));
}
@@ -135,7 +135,10 @@ bool EthernetInterface::checkLink() {
if(!connected) {
DIAG(F("Ethernet cable connected"));
connected=true;
IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address
#ifdef IP_ADDRESS
setLocalIP(IP_ADDRESS); // for static IP, set it again
#endif
IPAddress ip = Ethernet.localIP(); // look what IP was obtained (dynamic or static)
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT
server->begin();
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);

65
FSH.h
View File

@@ -34,42 +34,51 @@
* PROGMEM use FLASH instead
* pgm_read_byte_near use GETFLASH instead.
* pgm_read_word_near use GETFLASHW instead.
*
* Also:
* HIGHFLASH - PROGMEM forced to end of link so needs far pointers.
* GETHIGHFLASH,GETHIGHFLASHW to access them
*
*/
#include <Arduino.h>
#ifdef ARDUINO_ARCH_AVR
// AVR devices have flash memory mapped differently
// progmem can be accessed by _near functions or _far
typedef __FlashStringHelper FSH;
#define FLASH PROGMEM
#define GETFLASH(addr) pgm_read_byte_near(addr)
#if defined(ARDUINO_ARCH_MEGAAVR)
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
// AVR_MEGA memory deliberately placed at end of link may need _far functions
#define HIGHFLASH __attribute__((section(".fini2")))
#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)
#else
// AVR_UNO/NANO runtime does not support _far functions so just use _near equivalent
// as there is no progmem above 32kb anyway.
#define HIGHFLASH PROGMEM
#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))
#endif
#else
// Non-AVR Flat-memory devices have no need of this support so can be remapped to normal memory access
#ifdef F
#undef F
#endif
#define F(str) (str)
typedef char FSH;
#define GETFLASH(addr) (*(const unsigned char *)(addr))
#define GETFLASHW(addr) (*(const unsigned short *)(addr))
#define FLASH
#define strlen_P strlen
#define strcpy_P strcpy
#elif defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32)
typedef __FlashStringHelper FSH;
#define GETFLASH(addr) pgm_read_byte(addr)
// pgm_read_word is buggy if addr is odd but here
// we do only read well aligned addrs, the others are
// taken care about in the GET_OPERAND(n) macro in EXRAIL2.cpp.
#define GETFLASHW(addr) pgm_read_word(addr)
#ifdef FLASH
#undef FLASH
#endif
#define FLASH PROGMEM
#else // AVR and AVR compat here
typedef __FlashStringHelper FSH;
#define GETFLASH(addr) pgm_read_byte_near(addr)
#define GETFLASHW(addr) pgm_read_word_near(addr)
#define FLASH PROGMEM
#endif // flash stuff
#endif // FSH
#define F(str) (str)
typedef char FSH;
#define FLASH
#define HIGHFLASH
#define GETFARPTR(data) ((uint32_t)(data))
#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))
#endif
#endif

View File

@@ -1 +1 @@
#define GITHUB_SHA "devel-202211082310Z"
#define GITHUB_SHA "devel-202212051450Z"

View File

@@ -161,6 +161,8 @@ public:
// once the GPIO port concerned has been read.
void setGPIOInterruptPin(int16_t pinNumber);
// Method to check if pins will overlap before creating new device.
static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0);
protected:
@@ -234,9 +236,6 @@ protected:
// pin low if an input changes state.
int16_t _gpioInterruptPin = -1;
// Method to check if pins will overlap before creating new device.
static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0);
// Static support function for subclass creation
static void addDevice(IODevice *newDevice);
@@ -408,5 +407,8 @@ private:
#include "IO_MCP23008.h"
#include "IO_MCP23017.h"
#include "IO_PCF8574.h"
#include "IO_duinoNodes.h"
#include "IO_EXIOExpander.h"
#endif // iodevice_h

View File

@@ -1,5 +1,5 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
* © 2022, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
@@ -36,24 +36,31 @@
* In mySetup function within mySetup.cpp:
* DFPlayer::create(3500, 5, Serial1);
*
* Writing an analogue value 0-2999 to the first pin will select a numbered file from the SD card;
* Writing an analogue value 0-30 to the second pin will set the volume of the output;
* Writing a digital value to the first pin will play or stop the file;
* Writing an analogue value 1-2999 to the first pin (3500) will play the numbered file from the SD card;
* Writing an analogue value 0 to the first pin (3500) will stop the file playing;
* Writing an analogue value 0-30 to the second pin (3501) will set the volume;
* Writing a digital value of 1 to a pin will play the file corresponding to that pin, e.g.
the first file will be played by setting pin 3500, the second by setting pin 3501 etc.;
* Writing a digital value of 0 to any pin will stop the player;
* Reading a digital value from any pin will return true(1) if the player is playing, false(0) otherwise.
*
* From EX-RAIL, the following commands may be used:
* SET(3500) -- starts playing the first file on the SD card
* SET(3501) -- starts playing the second file on the SD card
* SET(3500) -- starts playing the first file (file 1) on the SD card
* SET(3501) -- starts playing the second file (file 2) on the SD card
* etc.
* RESET(3500) -- stops all playing on the player
* WAITFOR(3500) -- wait for the file currently being played by the player to complete
* SERVO(3500,23,0) -- plays file 23 at current volume
* SERVO(3500,23,30) -- plays file 23 at volume 30 (maximum)
* SERVO(3501,20,0) -- Sets the volume to 20
* SERVO(3500,2,Instant) -- plays file 2 at current volume
* SERVO(3501,20,Instant) -- Sets the volume to 20
*
* NB The DFPlayer's serial lines are not 5V safe, so connecting the Arduino TX directly
* to the DFPlayer's RX terminal will cause lots of noise over the speaker, or worse.
* A 1k resistor in series with the module's RX terminal will alleviate this.
*
* Files on the SD card are numbered according to their order in the directory on the
* card (as listed by the DIR command in Windows). This may not match the order of the files
* as displayed by Windows File Manager, which sorts the file names. It is suggested that
* files be copied into an empty SDcard in the desired order, one at a time.
*/
#ifndef IO_DFPlayer_h
@@ -68,6 +75,19 @@ private:
uint8_t _inputIndex = 0;
unsigned long _commandSendTime; // Allows timeout processing
// When two commands are sent in quick succession, the device sometimes
// fails to execute one. A delay is required between successive commands.
// This could be implemented by buffering commands and outputting them
// from the loop() function, but it would somewhat complicate the
// driver. A simpler solution is to output a number of NUL pad characters
// between successive command strings if there isn't sufficient elapsed time
// between them. At 9600 baud, each pad character takes approximately
// 1ms to complete. Experiments indicate that the minimum number of pads
// for reliable operation is 17. This gives 17.7ms between the end of one
// command and the beginning of the next, or 28ms between successive commands
// being completed. I've allowed 20 characters, which is almost 21ms.
const int numPadCharacters = 20; // Number of pad characters between commands
public:
static void create(VPIN firstVpin, int nPins, HardwareSerial &serial) {
@@ -83,8 +103,10 @@ protected:
addDevice(this);
}
void _begin() override {
_serial->begin(9600);
void _begin() override {
_serial->begin(9600, SERIAL_8N1); // 9600baud, no parity, 1 stop bit
// Flush any data in input queue
while (_serial->available()) _serial->read();
_deviceState = DEVSTATE_INITIALISING;
// Send a query to the device to see if it responds
@@ -94,10 +116,10 @@ protected:
void _loop(unsigned long currentMicros) override {
// Check for incoming data on _serial, and update busy flag accordingly.
// Expected message is in the form "7F FF 06 3D xx xx xx xx xx EF"
// Expected message is in the form "7E FF 06 3D xx xx xx xx xx EF"
while (_serial->available()) {
int c = _serial->read();
if (c == 0x7E)
if (c == 0x7E && _inputIndex == 0)
_inputIndex = 1;
else if ((c==0xFF && _inputIndex==1)
|| (c==0x3D && _inputIndex==3)
@@ -124,8 +146,8 @@ protected:
} else
_inputIndex = 0; // Unrecognised character sequence, start again!
}
// Check if the initial prompt to device has timed out. Allow 1 second
if (_deviceState == DEVSTATE_INITIALISING && currentMicros - _commandSendTime > 1000000UL) {
// Check if the initial prompt to device has timed out. Allow 5 seconds
if (_deviceState == DEVSTATE_INITIALISING && currentMicros - _commandSendTime > 5000000UL) {
DIAG(F("DFPlayer device not responding on serial port"));
_deviceState = DEVSTATE_FAILED;
}
@@ -218,6 +240,7 @@ private:
void sendPacket(uint8_t command, uint16_t arg = 0)
{
unsigned long currentMillis = millis();
uint8_t out[] = { 0x7E,
0xFF,
06,
@@ -231,7 +254,19 @@ private:
setChecksum(out);
// Check how long since the last command was sent.
// Each character takes approx 1ms at 9600 baud
unsigned long minimumGap = numPadCharacters + sizeof(out);
if (currentMillis - _commandSendTime < minimumGap) {
// Output some pad characters to add an
// artificial delay between commands
for (int i=0; i<numPadCharacters; i++)
_serial->write(0);
}
// Now output the command
_serial->write(out, sizeof(out));
_commandSendTime = currentMillis;
}
uint16_t calcChecksum(uint8_t* packet)

128
IO_EXFastclock.h Normal file
View File

@@ -0,0 +1,128 @@
/*
* © 2022, Colin Murdoch. All rights reserved.
*
* This file is part of CommandStation-EX
*
* 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_EXFastclock device driver is used to interface the standalone fast clock and receive time data.
*
* The EX-fastClock code lives in a separate repo (https://github.com/DCC-EX/EX-Fastclock) and contains the clock logic.
*
*
*/
#ifndef IO_EXFastclock_h
#define IO_EXFastclock_h
#include "IODevice.h"
#include "I2CManager.h"
#include "DIAG.h"
#include "EXRAIL2.h"
#include "CommandDistributor.h"
bool FAST_CLOCK_EXISTS = true;
class EXFastClock : public IODevice {
public:
// Constructor
EXFastClock(uint8_t I2CAddress){
_I2CAddress = I2CAddress;
addDevice(this);
}
static void create(uint8_t _I2CAddress) {
DIAG(F("Checking for Clock"));
// Start by assuming we will find the clock
// Check if specified I2C address is responding (blocking operation)
// Returns I2C_STATUS_OK (0) if OK, or error code.
uint8_t _checkforclock = I2CManager.checkAddress(_I2CAddress);
DIAG(F("Clock check result - %d"), _checkforclock);
// XXXX change thistosave2 bytes
if (_checkforclock == 0) {
FAST_CLOCK_EXISTS = true;
//DIAG(F("I2C Fast Clock found at x%x"), _I2CAddress);
new EXFastClock(_I2CAddress);
}
else {
FAST_CLOCK_EXISTS = false;
//DIAG(F("No Fast Clock found"));
LCD(6,F("CLOCK NOT FOUND"));
}
}
private:
uint8_t _I2CAddress;
// Initialisation of Fastclock
void _begin() override {
if (FAST_CLOCK_EXISTS == true) {
I2CManager.begin();
if (I2CManager.exists(_I2CAddress)) {
_deviceState = DEVSTATE_NORMAL;
#ifdef DIAG_IO
_display();
#endif
} else {
_deviceState = DEVSTATE_FAILED;
//LCD(6,F("CLOCK NOT FOUND"));
DIAG(F("Fast Clock Not Found at address %d"), _I2CAddress);
}
}
}
// Processing loop to obtain clock time
void _loop(unsigned long currentMicros) override{
if (FAST_CLOCK_EXISTS==true) {
uint8_t readBuffer[3];
byte a,b;
#ifdef EXRAIL_ACTIVE
I2CManager.read(_I2CAddress, readBuffer, 3);
// XXXX change this to save a few bytes
a = readBuffer[0];
b = readBuffer[1];
//_clocktime = (a << 8) + b;
//_clockrate = readBuffer[2];
CommandDistributor::setClockTime(((a << 8) + b), readBuffer[2], 1);
//setClockTime(int16_t clocktime, int8_t clockrate, byte opt);
// As the minimum clock increment is 2 seconds delay a bit - say 1 sec.
// Clock interval is 60/ clockspeed i.e 60/b seconds
delayUntil(currentMicros + ((60/b) * 1000000));
}
#endif
}
// Display EX-FastClock device driver info.
void _display() {
DIAG(F("FastCLock on I2C:x%x - %S"), _I2CAddress, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}
};
#endif

212
IO_EXIOExpander.h Normal file
View File

@@ -0,0 +1,212 @@
/*
* © 2021, Peter Cole. 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_EXIOExpander.h device driver integrates with one or more EX-IOExpander devices.
* This device driver will configure the device on startup, along with
* interacting with the device for all input/output duties.
*
* To create EX-IOExpander devices, these are defined in myHal.cpp:
* (Note the device driver is included by default)
*
* void halSetup() {
* // EXIOExpander::create(vpin, num_vpins, i2c_address, digitalPinCount, analoguePinCount);
* EXIOExpander::create(800, 18, 0x65, 12, 8);
* }
*
* Note when defining the number of digital and analogue pins, there is no way to sanity check
* this from the device driver, and it is up to the user to define the correct values here.
*
* All pins available on the EX-IOExpander device must be accounted for.
*
* Vpins are allocated to digital pins first, and then analogue pins, so digital pins will
* populate the first part of the specified vpin range, with the analogue pins populating the
* last part of the vpin range.
* Eg. for a default Nano, 800 - 811 are digital (D2 - D13), 812 to 817 are analogue (A0 - A3, A6/A7).
*/
#ifndef IO_EX_IOEXPANDER_H
#define IO_EX_IOEXPANDER_H
#include "I2CManager.h"
#include "DIAG.h"
#include "FSH.h"
/////////////////////////////////////////////////////////////////////////////////////////////////////
/*
* IODevice subclass for EX-IOExpander.
*/
class EXIOExpander : public IODevice {
public:
static void create(VPIN vpin, int nPins, uint8_t i2cAddress, int numDigitalPins, int numAnaloguePins) {
if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress, numDigitalPins, numAnaloguePins);
}
private:
// Constructor
EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress, int numDigitalPins, int numAnaloguePins) {
_firstVpin = firstVpin;
_nPins = nPins;
_i2cAddress = i2cAddress;
_numDigitalPins = numDigitalPins;
_numAnaloguePins = numAnaloguePins;
_digitalPinBytes = (numDigitalPins+7)/8;
_analoguePinBytes = numAnaloguePins * 2;
_digitalInputStates=(byte*) calloc(_digitalPinBytes,1);
_analogueInputStates=(byte*) calloc(_analoguePinBytes,1);
addDevice(this);
}
void _begin() {
// Initialise EX-IOExander device
I2CManager.begin();
if (I2CManager.exists(_i2cAddress)) {
_digitalOutBuffer[0] = EXIOINIT;
_digitalOutBuffer[1] = _numDigitalPins;
_digitalOutBuffer[2] = _numAnaloguePins;
// Send config, if EXIORDY returned, we're good, otherwise go offline
I2CManager.read(_i2cAddress, _commandBuffer, 1, _digitalOutBuffer, 3);
if (_commandBuffer[0] != EXIORDY) {
DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress);
_deviceState = DEVSTATE_FAILED;
return;
}
// Attempt to get version, if we don't get it, we don't care, don't go offline
// Using digital in buffer in reverse to save RAM
_commandBuffer[0] = EXIOVER;
I2CManager.read(_i2cAddress, _versionBuffer, 3, _commandBuffer, 1);
_majorVer = _versionBuffer[0];
_minorVer = _versionBuffer[1];
_patchVer = _versionBuffer[2];
DIAG(F("EX-IOExpander device found, I2C:x%x, Version v%d.%d.%d"),
_i2cAddress, _versionBuffer[0], _versionBuffer[1], _versionBuffer[2]);
#ifdef DIAG_IO
_display();
#endif
} else {
DIAG(F("EX-IOExpander device not found, I2C:x%x"), _i2cAddress);
_deviceState = DEVSTATE_FAILED;
}
}
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override {
if (configType != CONFIGURE_INPUT) return false;
if (paramCount != 1) return false;
if (vpin >= _firstVpin + _numDigitalPins) {
DIAG(F("EX-IOExpander ERROR: Vpin %d is an analogue pin, cannot use as a digital pin"), vpin);
return false;
}
bool pullup = params[0];
int pin = vpin - _firstVpin;
_digitalOutBuffer[0] = EXIODPUP;
_digitalOutBuffer[1] = pin;
_digitalOutBuffer[2] = pullup;
I2CManager.write(_i2cAddress, _digitalOutBuffer, 3);
return true;
}
// We only use this to detect incorrect use of analogue pins
int _configureAnalogIn(VPIN vpin) override {
if (vpin < _firstVpin + _numDigitalPins) {
DIAG(F("EX-IOExpander ERROR: Vpin %d is a digital pin, cannot use as an analogue pin"), vpin);
return false;
}
int pin = vpin - _firstVpin;
_analogueOutBuffer[0] = EXIOENAN;
_analogueOutBuffer[1] = pin;
I2CManager.write(_i2cAddress, _analogueOutBuffer, 2);
return true;
}
void _loop(unsigned long currentMicros) override {
_commandBuffer[0] = EXIORDD;
I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _commandBuffer, 1);
_commandBuffer[0] = EXIORDAN;
I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _commandBuffer, 1);
}
int _readAnalogue(VPIN vpin) override {
if (vpin < _firstVpin + _numDigitalPins) return false;
int pin = vpin - _firstVpin - _numDigitalPins;
uint8_t _pinLSBByte = pin * 2;
uint8_t _pinMSBByte = _pinLSBByte + 1;
return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte];
}
int _read(VPIN vpin) override {
if (vpin >= _firstVpin + _numDigitalPins) return false;
int pin = vpin - _firstVpin;
uint8_t pinByte = pin / 8;
bool value = _digitalInputStates[pinByte] >> (pin - pinByte * 8);
return value;
}
void _write(VPIN vpin, int value) override {
if (vpin >= _firstVpin + _numDigitalPins) return;
int pin = vpin - _firstVpin;
_digitalOutBuffer[0] = EXIOWRD;
_digitalOutBuffer[1] = pin;
_digitalOutBuffer[2] = value;
I2CManager.write(_i2cAddress, _digitalOutBuffer, 3);
}
void _display() override {
int _firstAnalogue, _lastAnalogue;
if (_numAnaloguePins == 0) {
_firstAnalogue = 0;
_lastAnalogue = 0;
} else {
_firstAnalogue = _firstVpin + _numDigitalPins;
_lastAnalogue = _firstVpin + _nPins - 1;
}
DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d: %d Digital Vpins %d-%d, %d Analogue Vpins %d-%d %S"),
_i2cAddress, _majorVer, _minorVer, _patchVer,
_numDigitalPins, _firstVpin, _firstVpin + _numDigitalPins - 1,
_numAnaloguePins, _firstAnalogue, _lastAnalogue,
_deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F(""));
}
uint8_t _i2cAddress;
uint8_t _numDigitalPins;
uint8_t _numAnaloguePins;
byte _analogueOutBuffer[2];
byte _digitalOutBuffer[3];
uint8_t _versionBuffer[3];
uint8_t _majorVer = 0;
uint8_t _minorVer = 0;
uint8_t _patchVer = 0;
byte* _digitalInputStates;
byte* _analogueInputStates;
uint8_t _digitalPinBytes = 0;
uint8_t _analoguePinBytes = 0;
byte _commandBuffer[1];
enum {
EXIOINIT = 0xE0, // Flag to initialise setup procedure
EXIORDY = 0xE1, // Flag we have completed setup procedure, also for EX-IO to ACK setup
EXIODPUP = 0xE2, // Flag we're sending digital pin pullup configuration
EXIOVER = 0xE3, // Flag to get version
EXIORDAN = 0xE4, // Flag to read an analogue input
EXIOWRD = 0xE5, // Flag for digital write
EXIORDD = 0xE6, // Flag to read digital input
EXIOENAN = 0xE7, // Flag eo enable an analogue pin
};
};
#endif

View File

@@ -47,7 +47,7 @@ EXTurntable::EXTurntable(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
addDevice(this);
}
// Initialisation of TurntableEX
// Initialisation of EXTurntable
void EXTurntable::_begin() {
I2CManager.begin();
I2CManager.setClock(1000000);
@@ -103,7 +103,7 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_
uint8_t stepsMSB = value >> 8;
uint8_t stepsLSB = value & 0xFF;
#ifdef DIAG_IO
DIAG(F("TurntableEX WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"),
DIAG(F("EX-Turntable WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"),
vpin, value, activity, duration);
DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"),
_I2CAddress, stepsMSB, stepsLSB, activity);
@@ -114,7 +114,7 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_
// Display Turnetable-EX device driver info.
void EXTurntable::_display() {
DIAG(F("TurntableEX I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin,
DIAG(F("EX-Turntable I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin,
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}

127
IO_RotaryEncoder.h Normal file
View File

@@ -0,0 +1,127 @@
/*
* © 2022, Peter Cole. 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_RotaryEncoder device driver is used to receive positions from a rotary encoder connected to an Arduino via I2C.
*
* There is separate code required for the Arduino the rotary encoder is connected to, which is located here:
* https://github.com/peteGSX-Projects/dcc-ex-rotary-encoder
*
* This device driver receives the rotary encoder position when the rotary encoder button is pushed, and these positions
* can be tested in EX-RAIL with:
* ONCHANGE(vpin) - flag when the rotary encoder position has changed from the previous position
* IFRE(vpin, position) - test to see if specified rotary encoder position has been received
*
* Further to this, feedback can be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin.
* A SET(vpin) will flag that a turntable (or anything else) is in motion, and a RESET(vpin) that the motion has finished.
*
* Refer to the documentation for further information including the valid activities and examples.
*/
#ifndef IO_ROTARYENCODER_H
#define IO_ROTARYENCODER_H
#include "EXRAIL2.h"
#include "IODevice.h"
#include "I2CManager.h"
#include "DIAG.h"
class RotaryEncoder : public IODevice {
public:
// Constructor
RotaryEncoder(VPIN firstVpin, int nPins, uint8_t I2CAddress){
_firstVpin = firstVpin;
_nPins = nPins;
_I2CAddress = I2CAddress;
addDevice(this);
}
static void create(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
if (checkNoOverlap(firstVpin, nPins, I2CAddress)) new RotaryEncoder(firstVpin, nPins, I2CAddress);
}
private:
// Initiate the device
void _begin() {
I2CManager.begin();
if (I2CManager.exists(_I2CAddress)) {
byte _getVersion[1] = {RE_VER};
I2CManager.read(_I2CAddress, _versionBuffer, 3, _getVersion, 1);
_majorVer = _versionBuffer[0];
_minorVer = _versionBuffer[1];
_patchVer = _versionBuffer[2];
_buffer[0] = RE_OP;
I2CManager.write(_I2CAddress, _buffer, 1);
#ifdef DIAG_IO
_display();
#endif
} else {
_deviceState = DEVSTATE_FAILED;
}
}
void _loop(unsigned long currentMicros) override {
I2CManager.read(_I2CAddress, _buffer, 1);
_position = _buffer[0];
// This here needs to have a change check, ie. position is a different value.
#if defined(EXRAIL_ACTIVE)
if (_position != _previousPosition) {
_previousPosition = _position;
RMFT2::changeEvent(_firstVpin,1);
} else {
RMFT2::changeEvent(_firstVpin,0);
}
#endif
delayUntil(currentMicros + 100000);
}
// Device specific read function
int _readAnalogue(VPIN vpin) override {
if (_deviceState == DEVSTATE_FAILED) return 0;
return _position;
}
void _write(VPIN vpin, int value) override {
if (vpin == _firstVpin + 1) {
byte _feedbackBuffer[2] = {RE_OP, value};
I2CManager.write(_I2CAddress, _feedbackBuffer, 2);
}
}
void _display() override {
DIAG(F("Rotary Encoder I2C:x%x v%d.%d.%d Configured on Vpin:%d-%d %S"), _I2CAddress, _majorVer, _minorVer, _patchVer,
(int)_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}
uint8_t _I2CAddress;
int8_t _position;
int8_t _previousPosition = 0;
uint8_t _versionBuffer[3];
uint8_t _buffer[1];
uint8_t _majorVer = 0;
uint8_t _minorVer = 0;
uint8_t _patchVer = 0;
enum {
RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device
RE_OP = 0xA1, // Flag for normal operation
};
};
#endif

172
IO_duinoNodes.h Normal file
View File

@@ -0,0 +1,172 @@
/*
* © 2022, Chris Harlow. All rights reserved.
* Based on original by: Robin Simonds, Beagle Bay Inc
*
* 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_duinoNodes_h
#define IO_duinoNodes_h
#include <Arduino.h>
#include "defines.h"
#include "IODevice.h"
#define DN_PIN_MASK(bit) (0x80>>(bit%8))
#define DN_GET_BIT(x) (_pinValues[(x)/8] & DN_PIN_MASK((x)) )
#define DN_SET_BIT(x) _pinValues[(x)/8] |= DN_PIN_MASK((x))
#define DN_CLR_BIT(x) _pinValues[(x)/8] &= ~DN_PIN_MASK((x))
class IO_duinoNodes : public IODevice {
public:
IO_duinoNodes(VPIN firstVpin, int nPins,
byte clockPin, byte latchPin, byte dataPin,
const byte* pinmap) :
IODevice(firstVpin, nPins) {
_latchPin=latchPin;
_clockPin=clockPin;
_dataPin=dataPin;
_pinMap=pinmap;
_nShiftBytes=(nPins+7)/8; // rounded up to multiples of 8 bits
_pinValues=(byte*) calloc(_nShiftBytes,1);
// Connect to HAL so my _write, _read and _loop will be called as required.
IODevice::addDevice(this);
}
// Called by HAL to start handling this device
void _begin() override {
_deviceState = DEVSTATE_NORMAL;
pinMode(_latchPin,OUTPUT);
pinMode(_clockPin,OUTPUT);
pinMode(_dataPin,_pinMap?INPUT_PULLUP:OUTPUT);
_display();
}
// loop called by HAL supervisor
void _loop(unsigned long currentMicros) override {
if (_pinMap) _loopInput(currentMicros);
else if (_xmitPending) _loopOutput();
}
void _loopInput(unsigned long currentMicros) {
if (currentMicros-_prevMicros < POLL_MICROS) return; // Nothing to do
_prevMicros=currentMicros;
//set latch to HIGH to freeze & store parallel data
ArduinoPins::fastWriteDigital(_latchPin, HIGH);
delayMicroseconds(1);
//set latch to LOW to enable the data to be transmitted serially
ArduinoPins::fastWriteDigital(_latchPin, LOW);
// stream in the bitmap using mapping order provided at constructor
for (int xmitByte=0;xmitByte<_nShiftBytes; xmitByte++) {
byte newByte=0;
for (int xmitBit=0;xmitBit<8; xmitBit++) {
ArduinoPins::fastWriteDigital(_clockPin, LOW);
delayMicroseconds(1);
bool data = ArduinoPins::fastReadDigital(_dataPin);
byte map=_pinMap[xmitBit];
if (data) newByte |= map;
else newByte &= ~map;
ArduinoPins::fastWriteDigital(_clockPin, HIGH);
delayMicroseconds(1);
}
_pinValues[xmitByte]=newByte;
// DIAG(F("DIN %x=%x"),xmitByte, newByte);
}
}
void _loopOutput() {
// stream out the bitmap (highest pin first)
_xmitPending=false;
ArduinoPins::fastWriteDigital(_latchPin, LOW);
for (int xmitBit=_nShiftBytes*8 -1; xmitBit>=0; xmitBit--) {
ArduinoPins::fastWriteDigital(_dataPin,DN_GET_BIT(xmitBit));
ArduinoPins::fastWriteDigital(_clockPin,HIGH);
ArduinoPins::fastWriteDigital(_clockPin,LOW);
}
ArduinoPins::fastWriteDigital(_latchPin, HIGH);
}
int _read(VPIN vpin) override {
int pin=vpin - _firstVpin;
bool b=DN_GET_BIT(pin);
return b?1:0;
}
void _write(VPIN vpin, int value) override {
int pin = vpin - _firstVpin;
bool oldval=DN_GET_BIT(pin);
bool newval=value!=0;
if (newval==oldval) return; // no change
if (newval) DN_SET_BIT(pin);
else DN_CLR_BIT(pin);
_xmitPending=true; // shift register will be sent on next _loop()
}
void _display() override {
DIAG(F("IO_duinoNodes %SPUT Configured on VPins:%d-%d shift=%d"),
_pinMap?F("IN"):F("OUT"),
(int)_firstVpin,
(int)_firstVpin+_nPins-1, _nShiftBytes*8);
}
private:
static const unsigned long POLL_MICROS=100000; // 10 / S
unsigned long _prevMicros;
int _nShiftBytes=0;
VPIN _latchPin,_clockPin,_dataPin;
byte* _pinValues;
bool _xmitPending; // Only relevant in output mode
const byte* _pinMap; // NULL in output mode
};
class IO_DNIN8 {
public:
static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin )
{
// input arrives as board pin 0,7,6,5,1,2,3,4
static const byte pinmap[8]={0x80,0x01,0x02,0x04,0x40,0x20,0x10,0x08};
if (IODevice::checkNoOverlap(firstVpin,nPins))
new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,pinmap);
}
};
class IO_DNIN8K {
public:
static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin )
{
// input arrives as board pin 0, 1, 2, 3, 4, 5, 6, 7
static const byte pinmap[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
if (IODevice::checkNoOverlap(firstVpin,nPins))
new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,pinmap);
}
};
class IO_DNOU8 {
public:
static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin )
{
if (IODevice::checkNoOverlap(firstVpin,nPins))
new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,NULL);
}
};
#endif

View File

@@ -182,7 +182,7 @@
#define STACKED_MOTOR_SHIELD F("STACKED_MOTOR_SHIELD"),\
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 2.99, 1500, UNUSED_PIN), \
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 1500, UNUSED_PIN), \
new MotorDriver( 2, 10, UNUSED_PIN, 7, A3, 2.99, 1500, UNUSED_PIN), \
new MotorDriver( 5, 4, UNUSED_PIN, 6, A4, 2.99, 1500, UNUSED_PIN)
new MotorDriver( 2, 10, UNUSED_PIN, 7, A4, 2.99, 1500, UNUSED_PIN), \
new MotorDriver( 5, 4, UNUSED_PIN, 6, A5, 2.99, 1500, UNUSED_PIN)
//
#endif

View File

@@ -0,0 +1,39 @@
Using Lew's Duino Gear boards:
1. DNIN8 Input
This is a shift-register implementation of a digital input collector.
Multiple DNIN8 may be connected in sequence but it is IMPORTANT that the software
configuratuion correctly represents the number of boards connected otherwise the results will be meaningless.
Use in myAnimation.h
HAL(IO_DNIN8, firstVpin, numPins, clockPin, latchPin, dataPin)
e.g.
HAL(IO_DNIN8, 400, 16, 40, 42, 44)
OR Use in myHal.cpp
IO_DNIN8::create( firstVpin, numPins, clockPin, latchPin, dataPin)
This will create virtaul pins 400-415 using two DNIN8 boards connected in sequence.
Vpins 400-407 will be on the first board (closest to the CS) and 408-415 on the second.
Note: 16 pins uses two boards. You may specify a non-multiple-of-8 pins but this will be rounded up to a multiple of 8 and you must connect ONLY the number of boards that this takes.
This example uses Arduino GPIO pins 40,42,44 as these are conveniently side-by-side on a Mega which is easier when you are using a 3 strand cable.
The DNIN8K module works the same but you must use DNIN8K in the HAL setup instead of DNIN8. NO you cant mix 8 and 8k versions in the same string of boards but you can create another string of boards.
DNOU8 works the same way,
Use in myAnimation.h
HAL(IO_DNOU8, firstVpin, numPins, clockPin, latchPin, dataPin)
e.g.
HAL(IO_DNIN8, 450, 16, 45, 47, 49)
OR Use in myHal.cpp
IO_DNIN8::create( firstVpin, numPins, clockPin, latchPin, dataPin)
This creates a string of input pins 450-465. Note the clock/latch/data pins must be different to any DNIN8/k pins.

View File

@@ -65,6 +65,13 @@ int RingStream::availableForWrite() {
}
size_t RingStream::printFlash(const FSH * flashBuffer) {
// This function does not work on a 32 bit processor where the runtime
// sometimes misrepresents the pointer size in uintptr_t.
// In any case its not really necessary in a 32 bit processor because
// we have adequate ram.
if (sizeof(void*)>2) return print(flashBuffer);
// We are about to add a PROGMEM string to the buffer.
// To save RAM we can insert a marker and the
// progmem address into the buffer instead.
@@ -107,8 +114,11 @@ int RingStream::read() {
if ((_pos_read==_pos_write) && !_overflow) return -1; // empty
byte b=readRawByte();
if (b!=FLASH_INSERT_MARKER) return b;
#ifndef ARDUINO_ARCH_ESP32
// Detected a flash insert
if (sizeof(void*)>2) {
DIAG(F("Detected invalid flash insert marker at pos %d"),_pos_read);
return '?';
}
// read address bytes LSB first (size depends on CPU)
uintptr_t iFlash=0;
for (byte f=0; f<sizeof(iFlash); f++) {
@@ -120,10 +130,6 @@ int RingStream::read() {
_flashInsert=reinterpret_cast<char * >( iFlash);
// and try again... so will read the first byte of the insert.
return read();
#else
DIAG(F("Detected flash insert marker at pos %d but there should not be one"),_pos_read);
return '\0';
#endif
}
byte RingStream::readRawByte() {
@@ -189,11 +195,6 @@ bool RingStream::commit() {
_mark++;
if (_mark==_len) _mark=0;
_buffer[_mark]=lowByte(_count);
{ char s[_count+2];
strncpy(s, (const char*)&(_buffer[_mark+1]), _count);
s[_count]=0;
//DIAG(F("RS commit count=%d core %d \"%s\""), _count, xPortGetCoreID(), s);
}
_ringClient = NO_CLIENT;
return true; // commit worked
}

View File

@@ -27,7 +27,7 @@ class RingStream : public Print {
public:
RingStream( const uint16_t len);
static const int THIS_IS_A_RINGSTREAM=77;
static const int THIS_IS_A_RINGSTREAM=777;
virtual size_t write(uint8_t b);
// This availableForWrite function is subverted from its original intention so that a caller

View File

@@ -32,7 +32,7 @@ class StringBuffer : public Print {
private:
static const int buffer_max=64; // enough for long text msgs to throttles
int16_t _pos_write;
char _buffer[buffer_max+1];
char _buffer[buffer_max+2];
};
#endif

View File

@@ -70,7 +70,7 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
char* flash=(char*)format;
for(int i=0; ; ++i) {
char c=GETFLASH(flash+i);
if (c=='\0') return;
if (c=='\0') break; // to va_end()
if(c!='%') { stream->print(c); continue; }
bool formatContinues=false;
@@ -91,9 +91,6 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
{
const FSH* flash= (const FSH*)va_arg(args, char*);
#ifndef ARDUINO_ARCH_ESP32
// On ESP32 the reading flashstring from rinstream code
// crashes, so don't use the flashstream hack on ESP32
#if WIFI_ON | ETHERNET_ON
// RingStream has special logic to handle flash strings
// but is not implemented unless wifi or ethernet are enabled.
@@ -101,11 +98,11 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
if (stream->availableForWrite()==RingStream::THIS_IS_A_RINGSTREAM)
((RingStream *)stream)->printFlash(flash);
else
#endif
#endif
stream->print(flash);
break;
}
case 'P': stream->print((uint32_t)va_arg(args, void*), HEX); break;
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;
@@ -168,8 +165,8 @@ void StringFormatter::printEscape(Print * stream, char c) {
case '\r': stream->print(F("\\r")); break;
case '\0': stream->print(F("\\0")); return;
case '\t': stream->print(F("\\t")); break;
case '\\': stream->print(F("\\")); break;
default: stream->print(c);
case '\\': stream->print(F("\\\\")); break;
default: stream->write(c);
}
}

View File

@@ -63,69 +63,6 @@
WiThrottle * WiThrottle::firstThrottle=NULL;
static uint8_t xstrncmp(const char *s1, const char *s2, uint8_t n) {
if (n == 0)
return 0;
do {
if (*s1 != *s2++)
return 1;
if (*s1++ == 0)
break;
} while (--n != 0);
return 0;
}
void WiThrottle::findUniqThrottle(int id, char *u) {
WiThrottle *wtmyid = NULL;
WiThrottle *wtmyuniq = NULL;
// search 1, look for clientid match
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){
if (wt->clientid == id) {
if (xstrncmp(u, wt->uniq, 16) == 0) // should be most common case
return;
wtmyid = wt;
break;
}
}
// search 2, look for string match
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){
if (xstrncmp(u, wt->uniq, 16) == 0) {
wtmyuniq = wt;
break;
}
}
// analyse result of the two for loops:
if (wtmyid == NULL) { // should not happen
DIAG(F("Did not find my own wiThrottle handle"));
return;
}
// wtmyuniq == wtmyid has already returned in for loop 1
if (wtmyuniq == NULL) { // register uniq in the found id
strncpy(wtmyid->uniq, u, 16);
wtmyid->uniq[16] = '\0';
if (Diag::WITHROTTLE) DIAG(F("Client %d registered as %s"),wtmyid->clientid, wtmyid->uniq);
return;
}
// if we get here wtmyid and wtmyuniq point on objects but differnet ones
// so we need to do the copy (all other options covered above)
for(int n=0; n < MAX_MY_LOCO; n++)
wtmyid->myLocos[n] = wtmyuniq->myLocos[n];
wtmyid->heartBeatEnable = wtmyuniq->heartBeatEnable;
wtmyid->heartBeat = wtmyuniq->heartBeat;
wtmyid->initSent = wtmyuniq->initSent;
wtmyid->exRailSent = wtmyuniq->exRailSent;
wtmyid->mostRecentCab = wtmyuniq->mostRecentCab;
wtmyid->turnoutListHash = wtmyuniq->turnoutListHash;
wtmyid->lastPowerState = wtmyuniq->lastPowerState;
strncpy(wtmyid->uniq, u, 16);
wtmyid->uniq[16] = '\0';
if (Diag::WITHROTTLE)
DIAG(F("New client %d replaces old client %d as %s"), wtmyid->clientid, wtmyuniq->clientid, wtmyid->uniq);
forget(wtmyuniq->clientid); // do not use wtmyid after this
}
WiThrottle* WiThrottle::getThrottle( int wifiClient) {
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
if (wt->clientid==wifiClient) return wt;
@@ -135,6 +72,7 @@ WiThrottle* WiThrottle::getThrottle( int wifiClient) {
void WiThrottle::forget( byte clientId) {
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
if (wt->clientid==clientId) {
DIAG(F("Withrottle client %d dropped"),clientId);
delete wt;
break;
}
@@ -159,10 +97,7 @@ WiThrottle::WiThrottle( int wificlientid) {
nextThrottle=firstThrottle;
firstThrottle= this;
clientid=wificlientid;
initSent=false; // prevent sending heartbeats before connection completed
heartBeatEnable=false; // until client turns it on
turnoutListHash = -1; // make sure turnout list is sent once
exRailSent=false;
mostRecentCab=0;
for (int loco=0;loco<MAX_MY_LOCO; loco++) myLocos[loco].throttle='\0';
}
@@ -187,47 +122,17 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
heartBeat=millis();
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d)<-[%e]"),millis(),clientid,cmd);
// On first few commands, send turnout, roster and routes
if (introSent) {
if (!turnoutsSent) sendTurnouts(stream);
else if(!rosterSent) sendRoster(stream);
else if (!routesSent) sendRoutes(stream);
else if (!heartrateSent) {
heartrateSent=true;
// allow heartbeat to slow down once all metadata sent
StringFormatter::send(stream,F("*%d\nHMConnected\n"),HEARTBEAT_SECONDS);
if (initSent) {
// Send turnout list if changed since last sent (will replace list on client)
if (turnoutListHash != Turnout::turnoutlistHash) {
StringFormatter::send(stream,F("PTL"));
for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){
if (tt->isHidden()) continue;
int id=tt->getId();
const FSH * tdesc=NULL;
#ifdef EXRAIL_ACTIVE
tdesc=RMFT2::getTurnoutDescription(id);
#endif
char tchar=Turnout::isClosed(id)?'2':'4';
if (tdesc==NULL) // turnout with no description
StringFormatter::send(stream,F("]\\[%d}|{T%d}|{T%c"), id,id,tchar);
else
StringFormatter::send(stream,F("]\\[%d}|{%S}|{%c"), id,tdesc,tchar);
}
StringFormatter::send(stream,F("\n"));
turnoutListHash = Turnout::turnoutlistHash; // keep a copy of hash for later comparison
}
else if (!exRailSent) {
// Send EX-RAIL routes list if not already sent (but not at same time as turnouts above)
exRailSent=true;
#ifdef EXRAIL_ACTIVE
StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL"));
for (byte pass=0;pass<2;pass++) {
// first pass automations, second pass routes.
for (int ix=0;;ix++) {
int16_t id=GETFLASHW((pass?RMFT2::automationIdList:RMFT2::routeIdList)+ix);
if (id==0) break;
const FSH * desc=RMFT2::getRouteDescription(id);
StringFormatter::send(stream,F("]\\[%c%d}|{%S}|{%c"),
pass?'A':'R',id,desc, pass?'4':'2');
}
}
StringFormatter::send(stream,F("\n"));
#endif
// allow heartbeat to slow down once all metadata sent
StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_SECONDS);
}
}
@@ -283,32 +188,14 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
}
break;
case 'N': // Heartbeat (2), only send if connection completed by 'HU' message
StringFormatter::send(stream, F("*%d\n"), initSent ? HEARTBEAT_SECONDS : HEARTBEAT_SECONDS/2); // return timeout value
StringFormatter::send(stream, F("*%d\n"), heartrateSent ? HEARTBEAT_SECONDS : HEARTBEAT_PRELOAD); // return timeout value
break;
case 'M': // multithrottle
multithrottle(stream, cmd);
break;
case 'H': // send initial connection info after receiving "HU" message
if (cmd[1] == 'U') {
WiThrottle::findUniqThrottle(clientid, (char *)cmd+2);
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
StringFormatter::send(stream,F("PPA%x\n"),TrackManager::getMainPower()==POWERMODE::ON);
#ifdef EXRAIL_ACTIVE
StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount);
for (int16_t r=0;r<RMFT2::rosterNameCount;r++) {
int16_t cabid=GETFLASHW(RMFT2::rosterIdList+r);
StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"),
RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L');
}
stream->write('\n'); // end roster
#endif
// set heartbeat to 5 seconds because we need to sync the metadata (1 second is too short!)
StringFormatter::send(stream,F("*%d\n"), HEARTBEAT_SECONDS/2);
initSent = true;
if (cmd[1] == 'U') {
sendIntro(stream);
}
break;
case 'Q': //
@@ -317,7 +204,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
StringFormatter::send(stream, F("M%c-%c%d<;>\n"), myLocos[loco].throttle, LorS(myLocos[loco].cab), myLocos[loco].cab);
}
}
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) Quit"),millis(),clientid);
if (Diag::WITHROTTLE) DIAG(F("WiThrottle(%d) Quit"),clientid);
delete this;
break;
}
@@ -378,65 +265,17 @@ void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
}
//use first empty "slot" on this client's list, will be added to DCC registration list
for (int loco=0;loco<MAX_MY_LOCO;loco++) {
if (myLocos[loco].throttle=='\0') {
myLocos[loco].throttle=throttleChar;
myLocos[loco].cab=locoid;
myLocos[loco].functionMap=DCC::getFunctionMap(locoid);
myLocos[loco].broadcastPending=true; // means speed/dir will be sent later
mostRecentCab=locoid;
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco
int fkeys=29;
myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle
#ifdef EXRAIL_ACTIVE
const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid);
if (!functionNames) {
// no roster, use presets as above
}
else if (GETFLASH(functionNames)=='\0') {
// "" = Roster but no functions given
fkeys=0;
}
else {
// we have function names...
// scan names list emitting names, counting functions and
// flagging non-toggling things like horn.
myLocos[loco].functionToggles =0;
StringFormatter::send(stream, F("M%cL%c%d<;>]\\["), throttleChar,cmd[3],locoid);
fkeys=0;
bool firstchar=true;
for (int fx=0;;fx++) {
char c=GETFLASH(functionNames+fx);
if (c=='\0') {
fkeys++;
break;
}
if (c=='/') {
fkeys++;
StringFormatter::send(stream,F("]\\["));
firstchar=true;
}
else if (firstchar && c=='*') {
myLocos[loco].functionToggles |= 1UL<<fkeys;
firstchar=false;
}
else {
firstchar=false;
stream->write(c);
}
}
StringFormatter::send(stream,F("\n"));
}
#endif
for(int fKey=0; fKey<fkeys; fKey++) {
int fstate=DCC::getFn(locoid,fKey);
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),throttleChar,cmd[3],locoid,fstate,fKey);
}
//speed and direction will be published at next broadcast cycle
StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128
return;
if (myLocos[loco].throttle=='\0') {
myLocos[loco].throttle=throttleChar;
myLocos[loco].cab=locoid;
myLocos[loco].functionMap=DCC::getFunctionMap(locoid);
myLocos[loco].broadcastPending=true; // means speed/dir will be sent later
mostRecentCab=locoid;
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco
sendFunctions(stream,loco);
//speed and direction will be published at next broadcast cycle
StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128
return;
}
}
StringFormatter::send(stream, F("HMMax locos (%d) exceeded, %d not added!\n"), MAX_MY_LOCO ,locoid);
@@ -540,8 +379,6 @@ void WiThrottle::loop(RingStream * stream) {
// for each WiThrottle, check the heartbeat and broadcast needed
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
wt->checkHeartbeat(stream);
}
void WiThrottle::checkHeartbeat(RingStream * stream) {
@@ -555,8 +392,8 @@ void WiThrottle::checkHeartbeat(RingStream * stream) {
heartBeat=millis(); // We have just stopped everyting, we don't need to do that again at next loop.
}
}
//haba no, not necessary the only throttle and it may come back
//delete this;
// if it does come back, the throttle should re-acquire
delete this;
return;
}
@@ -656,5 +493,120 @@ void WiThrottle::getLocoCallback(int16_t locoid) {
DIAG(F("LocoCallback commit success"));
stashStream->commit();
CommandDistributor::broadcastPower();
}
void WiThrottle::sendIntro(Print* stream) {
introSent=true;
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
StringFormatter::send(stream,F("PPA%x\n"),TrackManager::getMainPower()==POWERMODE::ON);
// set heartbeat to 2 seconds because we need to sync the metadata (1 second is too short!)
StringFormatter::send(stream,F("*%d\nHMConnecting..\n"), HEARTBEAT_PRELOAD);
}
void WiThrottle::sendTurnouts(Print* stream) {
turnoutsSent=true;
StringFormatter::send(stream,F("PTL"));
for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){
if (tt->isHidden()) continue;
int id=tt->getId();
const FSH * tdesc=NULL;
#ifdef EXRAIL_ACTIVE
tdesc=RMFT2::getTurnoutDescription(id);
#endif
char tchar=Turnout::isClosed(id)?'2':'4';
if (tdesc==NULL) // turnout with no description
StringFormatter::send(stream,F("]\\[%d}|{T%d}|{T%c"), id,id,tchar);
else
StringFormatter::send(stream,F("]\\[%d}|{%S}|{%c"), id,tdesc,tchar);
}
StringFormatter::send(stream,F("\n"));
}
void WiThrottle::sendRoster(Print* stream) {
rosterSent=true;
#ifdef EXRAIL_ACTIVE
StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount);
for (int16_t r=0;r<RMFT2::rosterNameCount;r++) {
int16_t cabid=GETHIGHFLASHW(RMFT2::rosterIdList,r*2);
StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"),
RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L');
}
StringFormatter::send(stream,F("\n"));
#endif
}
void WiThrottle::sendRoutes(Print* stream) {
routesSent=true;
#ifdef EXRAIL_ACTIVE
StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL"));
// first pass automations
for (int ix=0;;ix+=2) {
int16_t id =GETHIGHFLASHW(RMFT2::automationIdList,ix);
if (id==0) break;
const FSH * desc=RMFT2::getRouteDescription(id);
StringFormatter::send(stream,F("]\\[A%d}|{%S}|{4"),id,desc);
}
// second pass routes.
for (int ix=0;;ix+=2) {
int16_t id=GETHIGHFLASHW(RMFT2::routeIdList,ix);
if (id==0) break;
const FSH * desc=RMFT2::getRouteDescription(id);
StringFormatter::send(stream,F("]\\[R%d}|{%S}|{2"),id,desc);
}
StringFormatter::send(stream,F("\n"));
#endif
}
void WiThrottle::sendFunctions(Print* stream, byte loco) {
int16_t locoid=myLocos[loco].cab;
int fkeys=29;
myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle
#ifdef EXRAIL_ACTIVE
const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid);
if (!functionNames) {
// no roster, use non-exrail presets as above
}
else if (GETFLASH(functionNames)=='\0') {
// "" = Roster but no functions given
fkeys=0;
}
else {
// we have function names...
// scan names list emitting names, counting functions and
// flagging non-toggling things like horn.
myLocos[loco].functionToggles =0;
StringFormatter::send(stream, F("M%cL%c%d<;>]\\["), myLocos[loco].throttle,LorS(locoid),locoid);
fkeys=0;
bool firstchar=true;
for (int fx=0;;fx++) {
char c=GETFLASH(functionNames+fx);
if (c=='\0') {
fkeys++;
break;
}
if (c=='/') {
fkeys++;
StringFormatter::send(stream,F("]\\["));
firstchar=true;
}
else if (firstchar && c=='*') {
myLocos[loco].functionToggles |= 1UL<<fkeys;
firstchar=false;
}
else {
firstchar=false;
stream->write(c);
}
}
StringFormatter::send(stream,F("\n"));
}
#endif
for(int fKey=0; fKey<fkeys; fKey++) {
int fstate=DCC::getFn(locoid,fKey);
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),myLocos[loco].throttle,LorS(locoid),locoid,fstate,fKey);
}
}

View File

@@ -45,7 +45,8 @@ class WiThrottle {
~WiThrottle();
static const int MAX_MY_LOCO=10; // maximum number of locos assigned to a single client
static const int HEARTBEAT_SECONDS=10; // heartbeat at 4secs to provide messaging transport
static const int HEARTBEAT_SECONDS=10; // heartbeat at 10 secs to provide messaging transport
static const int HEARTBEAT_PRELOAD=2; // request fast callback when connecting multiple messages
static const int ESTOP_SECONDS=20; // eStop if no incoming messages for more than 8secs
static WiThrottle* firstThrottle;
static int getInt(byte * cmd);
@@ -61,10 +62,12 @@ class WiThrottle {
MYLOCO myLocos[MAX_MY_LOCO];
bool heartBeatEnable;
unsigned long heartBeat;
bool initSent; // valid connection established
bool exRailSent; // valid connection established
bool introSent=false;
bool turnoutsSent=false;
bool rosterSent=false;
bool routesSent=false;
bool heartrateSent=false;
uint16_t mostRecentCab;
int turnoutListHash; // used to check for changes to turnout list
bool lastPowerState; // last power state sent to this client
int DCCToWiTSpeed(int DCCSpeed);
@@ -74,6 +77,11 @@ class WiThrottle {
void accessory(RingStream *, byte* cmd);
void checkHeartbeat(RingStream * stream);
void markForBroadcast2(int cab);
void sendIntro(Print * stream);
void sendTurnouts(Print * stream);
void sendRoster(Print * stream);
void sendRoutes(Print * stream);
void sendFunctions(Print* stream, byte loco);
// callback stuff to support prog track acquire
static RingStream * stashStream;
static WiThrottle * stashInstance;

View File

@@ -66,7 +66,7 @@ void WifiInboundHandler::loop1() {
}
if (pendingCipsend) {
if (pendingCipsend && millis()-lastCIPSEND > CIPSENDgap) {
if (Diag::WIFI) DIAG( F("WiFi: [[CIPSEND=%d,%d]]"), clientPendingCIPSEND, currentReplySize);
StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientPendingCIPSEND, currentReplySize);
pendingCipsend=false;
@@ -131,11 +131,13 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
if (ch=='S') { // SEND OK probably
loopState=SKIPTOEND;
lastCIPSEND=0; // no need to wait next time
break;
}
if (ch=='b') { // This is a busy indicator... probabaly must restart a CIPSEND
pendingCipsend=(clientPendingCIPSEND>=0);
if (pendingCipsend) lastCIPSEND=millis(); // forces a gap to next CIPSEND
loopState=SKIPTOEND;
break;
}

View File

@@ -68,7 +68,9 @@ class WifiInboundHandler {
Stream * wifiStream;
static const int INBOUND_RING = 512;
static const int OUTBOUND_RING = 2048;
static const int OUTBOUND_RING = sizeof(void*)==2?2048:8192;
static const int CIPSENDgap=100; // millis() between retries of cipsend.
RingStream * inboundRing;
RingStream * outboundRing;
@@ -79,5 +81,7 @@ class WifiInboundHandler {
int clientPendingCIPSEND=-1;
int currentReplySize;
bool pendingCipsend;
uint32_t lastCIPSEND=0; // millis() of previous cipsend
};
#endif

View File

@@ -344,11 +344,10 @@ void WifiInterface::ATCommand(HardwareSerial * stream,const byte * command) {
while (wifiStream->available()) stream->write(wifiStream->read());
if (stream->available()) {
int cx=stream->read();
// A newline followed by !!! is an exit
// A newline followed by ! is an exit
if (cx=='\n' || cx=='\r') startOfLine=true;
else if (startOfLine && cx=='!') break;
else startOfLine=false;
stream->write(cx);
wifiStream->write(cx);
}
}
@@ -377,11 +376,12 @@ bool WifiInterface::checkForOK( const unsigned int timeout, const FSH * waitfor,
char *locator = (char *)waitfor;
DIAG(F("Wifi Check: [%E]"), waitfor);
while ( millis() - startTime < timeout) {
while (wifiStream->available()) {
int ch = wifiStream->read();
int nextchar;
while (wifiStream->available() && (nextchar = wifiStream->read()) > -1) {
char ch = (char)nextchar;
if (echo) {
if (escapeEcho) StringFormatter::printEscape( ch); /// THIS IS A DIAG IN DISGUISE
else USB_SERIAL.print((char)ch);
else USB_SERIAL.print(ch);
}
if (ch != GETFLASH(locator)) locator = (char *)waitfor;
if (ch == GETFLASH(locator)) {

View File

@@ -224,4 +224,8 @@ The configuration file for DCC-EX Command Station
//
//#define SERIAL_BT_COMMANDS
// FastClock Enabler
// To build the FastClock code into the CS please uncomment the line below
//#define USEFASTCLOCK
/////////////////////////////////////////////////////////////////////////////////////

View File

@@ -149,6 +149,12 @@
#define CPU_TYPE_ERROR
#endif
// replace board type if provided by compiler
#ifdef BOARD_NAME
#undef ARDUINO_TYPE
#define ARDUINO_TYPE BOARD_NAME
#endif
////////////////////////////////////////////////////////////////////////////////
//
// WIFI_ON: All prereqs for running with WIFI are met

View File

@@ -20,7 +20,8 @@
#include "IO_HCSR04.h" // Ultrasonic range sensor
#include "IO_VL53L0X.h" // Laser time-of-flight sensor
#include "IO_DFPlayer.h" // MP3 sound player
//#include "IO_EXTurntable.h" // Turntable-EX turntable controller
//#include "IO_EXFastClock.h" // FastClock driver
//==========================================================================
// The function halSetup() is invoked from CS if it exists within the build.
@@ -160,6 +161,65 @@ void halSetup() {
// DFPlayer::create(10000, 10, Serial1);
//=======================================================================
// The following directive defines an EX-Turntable turntable instance.
//=======================================================================
// EXTurntable::create(VPIN, Number of VPINs, I2C Address)
//
// The parameters are:
// VPIN=600
// Number of VPINs=1 (Note there is no reason to change this)
// I2C address=0x60
//
// Note that the I2C address is defined in the EX-Turntable code, and 0x60 is the default.
//EXTurntable::create(600, 1, 0x60);
//=======================================================================
// The following directive defines an EX-IOExpander instance.
//=======================================================================
// EXIOExpander::create(VPIN, Number of VPINs, I2C Address, Digital pin count, Analogue pin count)
//
// The parameters are:
// VPIN=an available Vpin
// Number of VPINs=Digital pin count + Analogue pin count (must match device in use as per documentation)
// I2C address=an available I2C address (default 0x65)
//
// Note that the I2C address is defined in the EX-IOExpander code, and 0x65 is the default.
// The first example is for an Arduino Nano with the default pin allocations.
// The second example is for an Arduino Uno using all pins as digital only.
//EXIOExpander::create(800, 18, 0x65, 12, 6);
//EXIOExpander::create(820, 16, 0x66, 16, 0);
//=======================================================================
// The following directive defines a rotary encoder instance.
//=======================================================================
// The parameters are:
// firstVpin = First available Vpin to allocate
// numPins= Number of Vpins to allocate, can be either 1 or 2
// i2cAddress = Available I2C address (default 0x70)
//RotaryEncoder::create(firstVpin, numPins, i2cAddress);
//RotaryEncoder::create(700, 1, 0x70);
//RotaryEncoder::create(701, 2, 0x71);
//=======================================================================
// The following directive defines an EX-FastClock instance.
//=======================================================================
// EXFastCLock::create(I2C Address)
//
// The parameters are:
//
// I2C address=0x55 (decimal 85)
//
// Note that the I2C address is defined in the EX-FastClock code, and 0x55 is the default.
// EXFastClock::create(0x55);
}
#endif

View File

@@ -19,6 +19,7 @@ default_envs =
samd21-zero-usb
ESP32
Nucleo-F411RE
Nucleo-F446RE
Teensy3.2
Teensy3.5
Teensy3.6
@@ -50,18 +51,6 @@ monitor_speed = 115200
monitor_echo = yes
build_flags = -std=c++17
[env:samc21-firebox]
platform = atmelsam
board = firebox
framework = arduino
upload_protocol = atmel-ice
lib_deps =
${env.lib_deps}
SparkFun External EEPROM Arduino Library
monitor_speed = 115200
monitor_echo = yes
build_flags = -std=c++17
[env:mega2560-debug]
platform = atmelavr
board = megaatmega2560
@@ -108,6 +97,7 @@ lib_deps =
SPI
monitor_speed = 115200
monitor_echo = yes
build_flags = -mcall-prologues
[env:mega328]
platform = atmelavr
@@ -155,6 +145,7 @@ lib_deps =
SPI
monitor_speed = 115200
monitor_echo = yes
build_flags = -mcall-prologues
[env:nano]
platform = atmelavr
@@ -177,7 +168,16 @@ platform = ststm32
board = nucleo_f411re
framework = arduino
lib_deps = ${env.lib_deps}
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
build_flags = -std=c++17 -Os -g2 -Wunused-variable
monitor_speed = 115200
monitor_echo = yes
[env:Nucleo-F446RE]
platform = ststm32
board = nucleo_f446re
framework = arduino
lib_deps = ${env.lib_deps}
build_flags = -std=c++17 -Os -g2 -Wunused-variable
monitor_speed = 115200
monitor_echo = yes
@@ -185,7 +185,7 @@ monitor_echo = yes
platform = teensy
board = teensy31
framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet
@@ -193,7 +193,7 @@ lib_ignore = NativeEthernet
platform = teensy
board = teensy35
framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet
@@ -201,7 +201,7 @@ lib_ignore = NativeEthernet
platform = teensy
board = teensy36
framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet
@@ -209,7 +209,7 @@ lib_ignore = NativeEthernet
platform = teensy
board = teensy40
framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet
@@ -217,6 +217,7 @@ lib_ignore = NativeEthernet
platform = teensy
board = teensy41
framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore =
lib_ignore =

View File

@@ -4,7 +4,17 @@
#include "StringFormatter.h"
#define VERSION "4.2.5"
#define VERSION "4.2.10pre1"
// 4.2.10 SIGNAL/SIGNALH bug fix as they were inverted
// IO_EXIOExpander.h input speed optimisation
// 4.2.9 duinoNodes support
// 4.2.8 HIGHMEM (EXRAIL support beyond 64kb)
// Withrottle connect/disconnect improvements
// Report BOARD_TYPE if provided by compiler
// 4.2.7 FIX: Static IP addr
// FIX: Reuse WiThrottle list entries
// 4.2.6 FIX: Remove RAM thief
// FIX: ADC port 8-15 fix
// 4.2.5 Make GETFLASHW code more universal
// FIX: Withrottle roster index
// Ethernet start improvement and link detection