mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-12-25 13:41:23 +01:00
Merge branch 'portableTimer' into nanoEvery2
This commit is contained in:
commit
13e516f8b2
@ -1,20 +0,0 @@
|
|||||||
// This file is copied from https://github.com/davidcutting42/ArduinoTimers
|
|
||||||
// All Credit and copyright David Cutting
|
|
||||||
// The files included below come from the same source.
|
|
||||||
// This library had been included with the DCC code to avoid issues with
|
|
||||||
// library management for inexperienced users. "It just works (TM)"
|
|
||||||
|
|
||||||
#ifndef ArduinoTimers_h
|
|
||||||
#define ArduinoTimers_h
|
|
||||||
|
|
||||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
|
||||||
#include "ATMEGA2560/Timer.h"
|
|
||||||
#elif defined(ARDUINO_AVR_UNO)
|
|
||||||
#include "ATMEGA328/Timer.h"
|
|
||||||
#elif defined(ARDUINO_ARCH_MEGAAVR)
|
|
||||||
#include "ATMEGA4809/EveryTimerB.h"
|
|
||||||
#else
|
|
||||||
#error "Cannot compile - ArduinoTimers library does not support your board, or you are missing compatible build flags."
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
@ -49,10 +49,19 @@ void setup()
|
|||||||
|
|
||||||
// STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h
|
// STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h
|
||||||
|
|
||||||
// Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the
|
|
||||||
// waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2
|
|
||||||
|
|
||||||
DCC::begin(MOTOR_SHIELD_TYPE);
|
DCC::begin(MOTOR_SHIELD_TYPE);
|
||||||
|
|
||||||
|
#if defined(RMFT_ACTIVE)
|
||||||
|
RMFT::begin();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __has_include ( "mySetup.h")
|
||||||
|
#define SETUP(cmd) serialParser.parse(F(cmd))
|
||||||
|
#include "mySetup.h"
|
||||||
|
#undef SETUP
|
||||||
|
#endif
|
||||||
|
|
||||||
LCD(1,F("Ready"));
|
LCD(1,F("Ready"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,6 +84,10 @@ void loop()
|
|||||||
EthernetInterface::loop();
|
EthernetInterface::loop();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(RMFT_ACTIVE)
|
||||||
|
RMFT::loop();
|
||||||
|
#endif
|
||||||
|
|
||||||
LCDDisplay::loop(); // ignored if LCD not in use
|
LCDDisplay::loop(); // ignored if LCD not in use
|
||||||
|
|
||||||
// Optionally report any decrease in memory (will automatically trigger on first call)
|
// Optionally report any decrease in memory (will automatically trigger on first call)
|
||||||
|
109
DCC.cpp
109
DCC.cpp
@ -23,6 +23,7 @@
|
|||||||
#include "EEStore.h"
|
#include "EEStore.h"
|
||||||
#include "GITHUB_SHA.h"
|
#include "GITHUB_SHA.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
#include "FSH.h"
|
||||||
|
|
||||||
// This module is responsible for converting API calls into
|
// This module is responsible for converting API calls into
|
||||||
// messages to be sent to the waveform generator.
|
// messages to be sent to the waveform generator.
|
||||||
@ -45,15 +46,15 @@ const byte FN_GROUP_5=0x10;
|
|||||||
|
|
||||||
FSH* DCC::shieldName=NULL;
|
FSH* DCC::shieldName=NULL;
|
||||||
|
|
||||||
void DCC::begin(const FSH* motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver, byte timerNumber) {
|
void DCC::begin( FSH * motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver) {
|
||||||
shieldName=(FSH*)motorShieldName;
|
shieldName=motorShieldName;
|
||||||
DIAG(F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
|
DIAG(F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
|
||||||
|
|
||||||
// Load stuff from EEprom
|
// Load stuff from EEprom
|
||||||
(void)EEPROM; // tell compiler not to warn this is unused
|
(void)EEPROM; // tell compiler not to warn this is unused
|
||||||
EEStore::init();
|
EEStore::init();
|
||||||
|
|
||||||
DCCWaveform::begin(mainDriver,progDriver, timerNumber);
|
DCCWaveform::begin(mainDriver,progDriver);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) {
|
void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) {
|
||||||
@ -331,10 +332,31 @@ const ackOp FLASH READ_CV_PROG[] = {
|
|||||||
|
|
||||||
const ackOp FLASH LOCO_ID_PROG[] = {
|
const ackOp FLASH LOCO_ID_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
|
SETCV, (ackOp)1,
|
||||||
|
SETBIT, (ackOp)7,
|
||||||
|
V0,WACK,NAKFAIL, // test CV 1 bit 7 is a zero... NAK means no loco found
|
||||||
|
|
||||||
|
SETCV, (ackOp)19, // CV 19 is consist setting
|
||||||
|
SETBYTE, (ackOp)0,
|
||||||
|
VB, WACK, ITSKIP, // ignore consist if cv19 is zero (no consist)
|
||||||
|
SETBYTE, (ackOp)128,
|
||||||
|
VB, WACK, ITSKIP, // ignore consist if cv19 is 128 (no consist, direction bit set)
|
||||||
|
STARTMERGE, // Setup to read cv 19
|
||||||
|
V0, WACK, MERGE,
|
||||||
|
V0, WACK, MERGE,
|
||||||
|
V0, WACK, MERGE,
|
||||||
|
V0, WACK, MERGE,
|
||||||
|
V0, WACK, MERGE,
|
||||||
|
V0, WACK, MERGE,
|
||||||
|
V0, WACK, MERGE,
|
||||||
|
V0, WACK, MERGE,
|
||||||
|
VB, WACK, ITCB7, // return 7 bits only, No_ACK means CV19 not supported so ignore it
|
||||||
|
|
||||||
|
SKIPTARGET, // continue here if CV 19 is zero or fails all validation
|
||||||
SETCV,(ackOp)29,
|
SETCV,(ackOp)29,
|
||||||
SETBIT,(ackOp)5,
|
SETBIT,(ackOp)5,
|
||||||
V0, WACK, ITSKIP, // Skip to SKIPTARGET if bit 5 of CV29 is zero
|
V0, WACK, ITSKIP, // Skip to SKIPTARGET if bit 5 of CV29 is zero
|
||||||
V1, WACK, NAKFAIL, // fast fail if no loco on track
|
|
||||||
// Long locoid
|
// Long locoid
|
||||||
SETCV, (ackOp)17, // CV 17 is part of locoid
|
SETCV, (ackOp)17, // CV 17 is part of locoid
|
||||||
STARTMERGE,
|
STARTMERGE,
|
||||||
@ -366,7 +388,7 @@ const ackOp FLASH LOCO_ID_PROG[] = {
|
|||||||
SKIPTARGET,
|
SKIPTARGET,
|
||||||
SETCV, (ackOp)1,
|
SETCV, (ackOp)1,
|
||||||
STARTMERGE,
|
STARTMERGE,
|
||||||
V0, WACK, MERGE, // read and merge bit 1 etc
|
SETBIT, (ackOp)6, // skip over first bit as we know its a zero
|
||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
@ -378,6 +400,44 @@ const ackOp FLASH LOCO_ID_PROG[] = {
|
|||||||
FAIL
|
FAIL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ackOp PROGMEM SHORT_LOCO_ID_PROG[] = {
|
||||||
|
BASELINE,
|
||||||
|
SETCV,(ackOp)19,
|
||||||
|
SETBYTE, (ackOp)0,
|
||||||
|
WB,WACK, // ignore router without cv19 support
|
||||||
|
// Turn off long address flag
|
||||||
|
SETCV,(ackOp)29,
|
||||||
|
SETBIT,(ackOp)5,
|
||||||
|
W0,WACK,NAKFAIL,
|
||||||
|
SETCV, (ackOp)1,
|
||||||
|
SETBYTEL, // low byte of word
|
||||||
|
WB,WACK,NAKFAIL,
|
||||||
|
VB,WACK,ITCB,
|
||||||
|
FAIL
|
||||||
|
};
|
||||||
|
|
||||||
|
const ackOp PROGMEM LONG_LOCO_ID_PROG[] = {
|
||||||
|
BASELINE,
|
||||||
|
// Clear consist CV 19
|
||||||
|
SETCV,(ackOp)19,
|
||||||
|
SETBYTE, (ackOp)0,
|
||||||
|
WB,WACK, // ignore router without cv19 support
|
||||||
|
// Turn on long address flag cv29 bit 5
|
||||||
|
SETCV,(ackOp)29,
|
||||||
|
SETBIT,(ackOp)5,
|
||||||
|
W1,WACK,NAKFAIL,
|
||||||
|
// Store high byte of address in cv 17
|
||||||
|
SETCV, (ackOp)17,
|
||||||
|
SETBYTEH, // high byte of word
|
||||||
|
WB,WACK,NAKFAIL,
|
||||||
|
VB,WACK,NAKFAIL,
|
||||||
|
// store
|
||||||
|
SETCV, (ackOp)18,
|
||||||
|
SETBYTEL, // low byte of word
|
||||||
|
WB,WACK,NAKFAIL,
|
||||||
|
VB,WACK,ITC1, // callback(1) means Ok
|
||||||
|
FAIL
|
||||||
|
};
|
||||||
|
|
||||||
// On the following prog-track functions blocking defaults to false.
|
// On the following prog-track functions blocking defaults to false.
|
||||||
// blocking=true forces the API to block, waiting for the response and invoke the callback BEFORE returning.
|
// blocking=true forces the API to block, waiting for the response and invoke the callback BEFORE returning.
|
||||||
@ -420,6 +480,12 @@ void DCC::getLocoId(ACK_CALLBACK callback, bool blocking) {
|
|||||||
ackManagerSetup(0,0, LOCO_ID_PROG, callback, blocking);
|
ackManagerSetup(0,0, LOCO_ID_PROG, callback, blocking);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DCC::setLocoId(int id,ACK_CALLBACK callback, bool blocking) {
|
||||||
|
if (id<=0 || id>9999) callback(-1);
|
||||||
|
if (id<=127) ackManagerSetup(id,SHORT_LOCO_ID_PROG, callback, blocking);
|
||||||
|
else ackManagerSetup(id | 0xc000,LONG_LOCO_ID_PROG, callback, blocking);
|
||||||
|
}
|
||||||
|
|
||||||
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
|
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
|
||||||
int reg=lookupSpeedTable(cab);
|
int reg=lookupSpeedTable(cab);
|
||||||
if (reg>=0) speedTable[reg].loco=0;
|
if (reg>=0) speedTable[reg].loco=0;
|
||||||
@ -552,7 +618,8 @@ int DCC::nextLoco = 0;
|
|||||||
ackOp const * DCC::ackManagerProg;
|
ackOp const * DCC::ackManagerProg;
|
||||||
byte DCC::ackManagerByte;
|
byte DCC::ackManagerByte;
|
||||||
byte DCC::ackManagerStash;
|
byte DCC::ackManagerStash;
|
||||||
int DCC::ackManagerCv;
|
int DCC::ackManagerWord;
|
||||||
|
int DCC::ackManagerCv;
|
||||||
byte DCC::ackManagerBitNum;
|
byte DCC::ackManagerBitNum;
|
||||||
bool DCC::ackReceived;
|
bool DCC::ackReceived;
|
||||||
|
|
||||||
@ -567,6 +634,13 @@ void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[]
|
|||||||
if (blocking) ackManagerLoop(blocking);
|
if (blocking) ackManagerLoop(blocking);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DCC::ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback, bool blocking) {
|
||||||
|
ackManagerWord=wordval;
|
||||||
|
ackManagerProg = program;
|
||||||
|
ackManagerCallback = callback;
|
||||||
|
if (blocking) ackManagerLoop(blocking);
|
||||||
|
}
|
||||||
|
|
||||||
const byte RESET_MIN=8; // tuning of reset counter before sending message
|
const byte RESET_MIN=8; // tuning of reset counter before sending message
|
||||||
|
|
||||||
// checkRessets return true if the caller should yield back to loop and try later.
|
// checkRessets return true if the caller should yield back to loop and try later.
|
||||||
@ -668,7 +742,15 @@ void DCC::ackManagerLoop(bool blocking) {
|
|||||||
case ITCB: // If True callback(byte)
|
case ITCB: // If True callback(byte)
|
||||||
if (ackReceived) {
|
if (ackReceived) {
|
||||||
ackManagerProg = NULL; // all done now
|
ackManagerProg = NULL; // all done now
|
||||||
callback(ackManagerByte);
|
callback(ackManagerByte);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ITCB7: // If True callback(byte & 0xF)
|
||||||
|
if (ackReceived) {
|
||||||
|
ackManagerProg = NULL; // all done now
|
||||||
|
callback(ackManagerByte & 0x7F);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -708,6 +790,19 @@ void DCC::ackManagerLoop(bool blocking) {
|
|||||||
ackManagerCv=GETFLASH(ackManagerProg);
|
ackManagerCv=GETFLASH(ackManagerProg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case SETBYTE:
|
||||||
|
ackManagerProg++;
|
||||||
|
ackManagerByte=GETFLASH(ackManagerProg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SETBYTEH:
|
||||||
|
ackManagerByte=highByte(ackManagerWord);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SETBYTEL:
|
||||||
|
ackManagerByte=lowByte(ackManagerWord);
|
||||||
|
break;
|
||||||
|
|
||||||
case STASHLOCOID:
|
case STASHLOCOID:
|
||||||
ackManagerStash=ackManagerByte; // stash value from CV17
|
ackManagerStash=ackManagerByte; // stash value from CV17
|
||||||
break;
|
break;
|
||||||
|
9
DCC.h
9
DCC.h
@ -37,12 +37,16 @@ enum ackOp
|
|||||||
ITC1, // If True Callback(1) (if prevous WACK got an ACK)
|
ITC1, // If True Callback(1) (if prevous WACK got an ACK)
|
||||||
ITC0, // If True callback(0);
|
ITC0, // If True callback(0);
|
||||||
ITCB, // If True callback(byte)
|
ITCB, // If True callback(byte)
|
||||||
|
ITCB7, // If True callback(byte &0x7F)
|
||||||
NAKFAIL, // if false callback(-1)
|
NAKFAIL, // if false callback(-1)
|
||||||
FAIL, // callback(-1)
|
FAIL, // callback(-1)
|
||||||
STARTMERGE, // Clear bit and byte settings ready for merge pass
|
STARTMERGE, // Clear bit and byte settings ready for merge pass
|
||||||
MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes)
|
MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes)
|
||||||
SETBIT, // sets bit number to next prog byte
|
SETBIT, // sets bit number to next prog byte
|
||||||
SETCV, // sets cv number to next prog byte
|
SETCV, // sets cv number to next prog byte
|
||||||
|
SETBYTE, // sets current byte to next prog byte
|
||||||
|
SETBYTEH, // sets current byte to word high byte
|
||||||
|
SETBYTEL, // sets current byte to word low byte
|
||||||
STASHLOCOID, // keeps current byte value for later
|
STASHLOCOID, // keeps current byte value for later
|
||||||
COMBINELOCOID, // combines current value with stashed value and returns it
|
COMBINELOCOID, // combines current value with stashed value and returns it
|
||||||
ITSKIP, // skip to SKIPTARGET if ack true
|
ITSKIP, // skip to SKIPTARGET if ack true
|
||||||
@ -60,7 +64,7 @@ const byte MAX_LOCOS = 50;
|
|||||||
class DCC
|
class DCC
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static void begin(const FSH *motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver, byte timerNumber = 1);
|
static void begin(FSH * motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver);
|
||||||
static void loop();
|
static void loop();
|
||||||
|
|
||||||
// Public DCC API functions
|
// Public DCC API functions
|
||||||
@ -88,6 +92,7 @@ public:
|
|||||||
static void verifyCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking = false);
|
static void verifyCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking = false);
|
||||||
|
|
||||||
static void getLocoId(ACK_CALLBACK callback, bool blocking = false);
|
static void getLocoId(ACK_CALLBACK callback, bool blocking = false);
|
||||||
|
static void setLocoId(int id,ACK_CALLBACK callback, bool blocking = false);
|
||||||
|
|
||||||
// Enhanced API functions
|
// Enhanced API functions
|
||||||
static void forgetLoco(int cab); // removes any speed reminders for this loco
|
static void forgetLoco(int cab); // removes any speed reminders for this loco
|
||||||
@ -124,10 +129,12 @@ private:
|
|||||||
static byte ackManagerByte;
|
static byte ackManagerByte;
|
||||||
static byte ackManagerBitNum;
|
static byte ackManagerBitNum;
|
||||||
static int ackManagerCv;
|
static int ackManagerCv;
|
||||||
|
static int ackManagerWord;
|
||||||
static byte ackManagerStash;
|
static byte ackManagerStash;
|
||||||
static bool ackReceived;
|
static bool ackReceived;
|
||||||
static ACK_CALLBACK ackManagerCallback;
|
static ACK_CALLBACK ackManagerCallback;
|
||||||
static void ackManagerSetup(int cv, byte bitNumOrbyteValue, ackOp const program[], ACK_CALLBACK callback, bool blocking);
|
static void ackManagerSetup(int cv, byte bitNumOrbyteValue, ackOp const program[], ACK_CALLBACK callback, bool blocking);
|
||||||
|
static void ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback, bool blocking);
|
||||||
static void ackManagerLoop(bool blocking);
|
static void ackManagerLoop(bool blocking);
|
||||||
static bool checkResets(bool blocking, uint8_t numResets);
|
static bool checkResets(bool blocking, uint8_t numResets);
|
||||||
static const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable)
|
static const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable)
|
||||||
|
6
DCCEX.h
6
DCCEX.h
@ -15,6 +15,10 @@
|
|||||||
#endif
|
#endif
|
||||||
#include "LCD_Implementation.h"
|
#include "LCD_Implementation.h"
|
||||||
#include "freeMemory.h"
|
#include "freeMemory.h"
|
||||||
#include <Arduino.h>
|
|
||||||
|
|
||||||
|
#if __has_include ( "myAutomation.h")
|
||||||
|
#include "RMFT.h"
|
||||||
|
#define RMFT_ACTIVE
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -221,16 +221,29 @@ int DCCEXParser::splitHexValues(int result[MAX_PARAMS], const byte *cmd)
|
|||||||
}
|
}
|
||||||
|
|
||||||
FILTER_CALLBACK DCCEXParser::filterCallback = 0;
|
FILTER_CALLBACK DCCEXParser::filterCallback = 0;
|
||||||
|
FILTER_CALLBACK DCCEXParser::filterRMFTCallback = 0;
|
||||||
AT_COMMAND_CALLBACK DCCEXParser::atCommandCallback = 0;
|
AT_COMMAND_CALLBACK DCCEXParser::atCommandCallback = 0;
|
||||||
void DCCEXParser::setFilter(FILTER_CALLBACK filter)
|
void DCCEXParser::setFilter(FILTER_CALLBACK filter)
|
||||||
{
|
{
|
||||||
filterCallback = filter;
|
filterCallback = filter;
|
||||||
}
|
}
|
||||||
|
void DCCEXParser::setRMFTFilter(FILTER_CALLBACK filter)
|
||||||
|
{
|
||||||
|
filterRMFTCallback = filter;
|
||||||
|
}
|
||||||
void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback)
|
void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback)
|
||||||
{
|
{
|
||||||
atCommandCallback = callback;
|
atCommandCallback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse an F() string
|
||||||
|
void DCCEXParser::parse(const __FlashStringHelper * cmd) {
|
||||||
|
int size=strlen_P((char *)cmd)+1;
|
||||||
|
char buffer[size];
|
||||||
|
strcpy_P(buffer,(char *)cmd);
|
||||||
|
parse(&Serial,(byte *)buffer,true);
|
||||||
|
}
|
||||||
|
|
||||||
// See documentation on DCC class for info on this section
|
// See documentation on DCC class for info on this section
|
||||||
void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
|
void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
|
||||||
{
|
{
|
||||||
@ -245,6 +258,8 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
|
|||||||
|
|
||||||
if (filterCallback)
|
if (filterCallback)
|
||||||
filterCallback(stream, opcode, params, p);
|
filterCallback(stream, opcode, params, p);
|
||||||
|
if (filterRMFTCallback && opcode!='\0')
|
||||||
|
filterRMFTCallback(stream, opcode, params, p);
|
||||||
|
|
||||||
// Functions return from this switch if complete, break from switch implies error <X> to send
|
// Functions return from this switch if complete, break from switch implies error <X> to send
|
||||||
switch (opcode)
|
switch (opcode)
|
||||||
@ -344,9 +359,12 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
case 'W': // WRITE CV ON PROG <W CV VALUE CALLBACKNUM CALLBACKSUB>
|
case 'W': // WRITE CV ON PROG <W CV VALUE CALLBACKNUM CALLBACKSUB>
|
||||||
if (!stashCallback(stream, p))
|
if (!stashCallback(stream, p))
|
||||||
break;
|
break;
|
||||||
DCC::writeCVByte(p[0], p[1], callback_W, blocking);
|
if (params == 1) // <W id> Write new loco id (clearing consist and managing short/long)
|
||||||
|
DCC::setLocoId(p[0],callback_Wloco, blocking);
|
||||||
|
else // WRITE CV ON PROG <W CV VALUE [CALLBACKNUM] [CALLBACKSUB]>
|
||||||
|
DCC::writeCVByte(p[0], p[1], callback_W, blocking);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'V': // VERIFY CV ON PROG <V CV VALUE> <V CV BIT 0|1>
|
case 'V': // VERIFY CV ON PROG <V CV VALUE> <V CV BIT 0|1>
|
||||||
@ -434,8 +452,11 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'c': // READ CURRENT <c>
|
case 'c': // SEND METER RESPONSES <c>
|
||||||
StringFormatter::send(stream, F("<a %d>"), DCCWaveform::mainTrack.get1024Current());
|
// <c MeterName value C/V unit min max res warn>
|
||||||
|
StringFormatter::send(stream, F("<c CurrentMAIN %d C Milli 0 %d 1 %d>"), DCCWaveform::mainTrack.getCurrentmA(),
|
||||||
|
DCCWaveform::mainTrack.getMaxmA(), DCCWaveform::mainTrack.getTripmA());
|
||||||
|
StringFormatter::send(stream, F("<a %d>"), DCCWaveform::mainTrack.get1024Current()); //'a' message deprecated, remove once JMRI 4.22 is available
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'Q': // SENSORS <Q>
|
case 'Q': // SENSORS <Q>
|
||||||
@ -445,7 +466,7 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
|
|||||||
case 's': // <s>
|
case 's': // <s>
|
||||||
StringFormatter::send(stream, F("<p%d>"), DCCWaveform::mainTrack.getPowerMode() == POWERMODE::ON);
|
StringFormatter::send(stream, F("<p%d>"), DCCWaveform::mainTrack.getPowerMode() == POWERMODE::ON);
|
||||||
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||||
parseT(stream, 0, p); //send all Turnout states
|
Turnout::printAll(stream); //send all Turnout states
|
||||||
Output::printAll(stream); //send all Output states
|
Output::printAll(stream); //send all Output states
|
||||||
Sensor::printAll(stream); //send all Sensor states
|
Sensor::printAll(stream); //send all Sensor states
|
||||||
// TODO Send stats of speed reminders table
|
// TODO Send stats of speed reminders table
|
||||||
@ -529,7 +550,7 @@ bool DCCEXParser::parseZ(Print *stream, int params, int p[])
|
|||||||
StringFormatter::send(stream, F("<O>"));
|
StringFormatter::send(stream, F("<O>"));
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case 0: // <Z>
|
case 0: // <Z> list Output definitions
|
||||||
{
|
{
|
||||||
bool gotone = false;
|
bool gotone = false;
|
||||||
for (Output *tt = Output::firstOutput; tt != NULL; tt = tt->nextOutput)
|
for (Output *tt = Output::firstOutput; tt != NULL; tt = tt->nextOutput)
|
||||||
@ -591,13 +612,14 @@ bool DCCEXParser::parseT(Print *stream, int params, int p[])
|
|||||||
{
|
{
|
||||||
switch (params)
|
switch (params)
|
||||||
{
|
{
|
||||||
case 0: // <T> list all turnout states
|
case 0: // <T> list turnout definitions
|
||||||
{
|
{
|
||||||
bool gotOne = false;
|
bool gotOne = false;
|
||||||
for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout)
|
for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout)
|
||||||
{
|
{
|
||||||
gotOne = true;
|
gotOne = true;
|
||||||
StringFormatter::send(stream, F("<H %d %d>"), tt->data.id, (tt->data.tStatus & STATUS_ACTIVE)!=0);
|
StringFormatter::send(stream, F("<H %d %d %d %d>"), tt->data.id, tt->data.address,
|
||||||
|
tt->data.subAddress, (tt->data.tStatus & STATUS_ACTIVE)!=0);
|
||||||
}
|
}
|
||||||
return gotOne; // will <X> if none found
|
return gotOne; // will <X> if none found
|
||||||
}
|
}
|
||||||
@ -646,7 +668,7 @@ bool DCCEXParser::parseS(Print *stream, int params, int p[])
|
|||||||
StringFormatter::send(stream, F("<O>"));
|
StringFormatter::send(stream, F("<O>"));
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case 0: // <S> list sensor states
|
case 0: // <S> list sensor definitions
|
||||||
if (Sensor::firstSensor == NULL)
|
if (Sensor::firstSensor == NULL)
|
||||||
return false;
|
return false;
|
||||||
for (Sensor *tt = Sensor::firstSensor; tt != NULL; tt = tt->nextSensor)
|
for (Sensor *tt = Sensor::firstSensor; tt != NULL; tt = tt->nextSensor)
|
||||||
@ -769,6 +791,13 @@ void DCCEXParser::callback_R(int result)
|
|||||||
|
|
||||||
void DCCEXParser::callback_Rloco(int result)
|
void DCCEXParser::callback_Rloco(int result)
|
||||||
{
|
{
|
||||||
StringFormatter::send(stashStream, F("<r %d>"), result);
|
StringFormatter::send(stashStream, F("<r %d>"), result & 0x3FFF);
|
||||||
|
stashBusy = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DCCEXParser::callback_Wloco(int result)
|
||||||
|
{
|
||||||
|
if (result==1) result=stashP[0]; // pick up original requested id from command
|
||||||
|
StringFormatter::send(stashStream, F("<w %d>"), result);
|
||||||
stashBusy = false;
|
stashBusy = false;
|
||||||
}
|
}
|
||||||
|
@ -28,8 +28,10 @@ struct DCCEXParser
|
|||||||
DCCEXParser();
|
DCCEXParser();
|
||||||
void loop(Stream & stream);
|
void loop(Stream & stream);
|
||||||
void parse(Print * stream, byte * command, bool blocking);
|
void parse(Print * stream, byte * command, bool blocking);
|
||||||
|
void parse(const __FlashStringHelper * cmd);
|
||||||
void flush();
|
void flush();
|
||||||
static void setFilter(FILTER_CALLBACK filter);
|
static void setFilter(FILTER_CALLBACK filter);
|
||||||
|
static void setRMFTFilter(FILTER_CALLBACK filter);
|
||||||
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
|
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
|
||||||
static const int MAX_PARAMS=10; // Must not exceed this
|
static const int MAX_PARAMS=10; // Must not exceed this
|
||||||
|
|
||||||
@ -58,9 +60,11 @@ struct DCCEXParser
|
|||||||
static void callback_B(int result);
|
static void callback_B(int result);
|
||||||
static void callback_R(int result);
|
static void callback_R(int result);
|
||||||
static void callback_Rloco(int result);
|
static void callback_Rloco(int result);
|
||||||
|
static void callback_Wloco(int result);
|
||||||
static void callback_Vbit(int result);
|
static void callback_Vbit(int result);
|
||||||
static void callback_Vbyte(int result);
|
static void callback_Vbyte(int result);
|
||||||
static FILTER_CALLBACK filterCallback;
|
static FILTER_CALLBACK filterCallback;
|
||||||
|
static FILTER_CALLBACK filterRMFTCallback;
|
||||||
static AT_COMMAND_CALLBACK atCommandCallback;
|
static AT_COMMAND_CALLBACK atCommandCallback;
|
||||||
static void funcmap(int cab, byte value, byte fstart, byte fstop);
|
static void funcmap(int cab, byte value, byte fstart, byte fstop);
|
||||||
|
|
||||||
|
73
DCCTimer.cpp
Normal file
73
DCCTimer.cpp
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* © 2021, Chris Harlow & David Cutting. All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is part of Asbelos DCC 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/* This timer class is used to manage the single timer required to handle the DCC waveform.
|
||||||
|
* All timer access comes through this class so that it can be compiled for
|
||||||
|
* various hardware CPU types.
|
||||||
|
*
|
||||||
|
* DCCEX works on a single timer interrupt at a regular 58uS interval.
|
||||||
|
* The DCCWaveform class generates the signals to the motor shield
|
||||||
|
* based on this timer.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "DCCTimer.h"
|
||||||
|
|
||||||
|
const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle
|
||||||
|
const int DCC_SLOW_TIME=58*512; // for <D DCC SLOW> command diagnostics
|
||||||
|
|
||||||
|
INTERRUPT_CALLBACK interruptHandler=0;
|
||||||
|
|
||||||
|
|
||||||
|
void DCCTimer::begin(INTERRUPT_CALLBACK callback, bool slow) {
|
||||||
|
interruptHandler=callback;
|
||||||
|
// Initialise timer1 to trigger every 58us (DCC_SIGNAL_TIME)
|
||||||
|
long clockCycles=((F_CPU / 1000000) * (slow? DCC_SLOW_TIME : DCC_SIGNAL_TIME)) >>1;
|
||||||
|
noInterrupts();
|
||||||
|
|
||||||
|
#ifdef ARDUINO_ARCH_MEGAAVR
|
||||||
|
// Arduino unoWifi Rev2 and nanoEvery architectire
|
||||||
|
TCB0.CCMP = clockCycles;
|
||||||
|
TCB0.INTFLAGS = TCB_CAPT_bm; // clear interrupt request flag
|
||||||
|
TCB0.INTCTRL = TCB_CAPT_bm; // Enable the interrupt
|
||||||
|
TCB0.CNT = 0;
|
||||||
|
TCB0.CTRLA |= TCB_ENABLE_bm; // start
|
||||||
|
#define ISR_NAME TCB2_INT_vect
|
||||||
|
|
||||||
|
#else
|
||||||
|
// Arduino nano, uno, mega
|
||||||
|
TCCR1A = 0;
|
||||||
|
ICR1 = clockCycles;
|
||||||
|
TCNT1 = 0;
|
||||||
|
TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1
|
||||||
|
TIMSK1 = _BV(TOIE1); // Enable Software interrupt
|
||||||
|
#define ISR_NAME TIMER1_OVF_vect
|
||||||
|
#endif
|
||||||
|
|
||||||
|
interrupts();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ISR called by timer interrupt every 58uS
|
||||||
|
ISR(ISR_NAME)
|
||||||
|
{
|
||||||
|
#ifdef ARDUINO_ARCH_MEGAAVR
|
||||||
|
TCB0.INTFLAGS = TCB_CAPT_bm;
|
||||||
|
#endif
|
||||||
|
if (interruptHandler) interruptHandler();
|
||||||
|
}
|
13
DCCTimer.h
Normal file
13
DCCTimer.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#ifndef DCCTimer_h
|
||||||
|
#define DCCTimer_h
|
||||||
|
#include "Arduino.h"
|
||||||
|
|
||||||
|
typedef void (*INTERRUPT_CALLBACK)();
|
||||||
|
|
||||||
|
class DCCTimer {
|
||||||
|
public:
|
||||||
|
static void begin(INTERRUPT_CALLBACK interrupt, bool slow=false);
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -20,17 +20,9 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
#include "DCCWaveform.h"
|
#include "DCCWaveform.h"
|
||||||
|
#include "DCCTimer.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
|
|
||||||
#ifdef ARDUINO_ARCH_MEGAAVR
|
|
||||||
#include "EveryTimerB.h"
|
|
||||||
#define Timer1 TimerB2 // use TimerB2 as a drop in replacement for Timer1
|
|
||||||
#else // assume architecture supported by TimerOne ....
|
|
||||||
#include "TimerOne.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const int NORMAL_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle
|
|
||||||
const int SLOW_SIGNAL_TIME=NORMAL_SIGNAL_TIME*512;
|
|
||||||
|
|
||||||
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
|
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
|
||||||
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
||||||
@ -39,21 +31,17 @@ DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
|||||||
bool DCCWaveform::progTrackSyncMain=false;
|
bool DCCWaveform::progTrackSyncMain=false;
|
||||||
bool DCCWaveform::progTrackBoosted=false;
|
bool DCCWaveform::progTrackBoosted=false;
|
||||||
|
|
||||||
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber) {
|
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) {
|
||||||
mainTrack.motorDriver=mainDriver;
|
mainTrack.motorDriver=mainDriver;
|
||||||
progTrack.motorDriver=progDriver;
|
progTrack.motorDriver=progDriver;
|
||||||
|
|
||||||
mainTrack.setPowerMode(POWERMODE::OFF);
|
mainTrack.setPowerMode(POWERMODE::OFF);
|
||||||
progTrack.setPowerMode(POWERMODE::OFF);
|
progTrack.setPowerMode(POWERMODE::OFF);
|
||||||
|
DCCTimer::begin(DCCWaveform::interruptHandler);
|
||||||
Timer1.initialize();
|
|
||||||
Timer1.setPeriod(NORMAL_SIGNAL_TIME); // this is the 58uS DCC 1-bit waveform half-cycle
|
|
||||||
Timer1.attachInterrupt(interruptHandler);
|
|
||||||
Timer1.start();
|
|
||||||
}
|
}
|
||||||
void DCCWaveform::setDiagnosticSlowWave(bool slow) {
|
|
||||||
Timer1.setPeriod(slow? SLOW_SIGNAL_TIME : NORMAL_SIGNAL_TIME);
|
void DCCWaveform::setDiagnosticSlowWave(bool slow) {
|
||||||
Timer1.start();
|
DCCTimer::begin(DCCWaveform::interruptHandler, slow);
|
||||||
DIAG(F("\nDCC SLOW WAVE %S\n"),slow?F("SET. DO NOT ADD LOCOS TO TRACK"):F("RESET"));
|
DIAG(F("\nDCC SLOW WAVE %S\n"),slow?F("SET. DO NOT ADD LOCOS TO TRACK"):F("RESET"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,8 +50,6 @@ void DCCWaveform::loop() {
|
|||||||
progTrack.checkPowerOverload();
|
progTrack.checkPowerOverload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// static //
|
|
||||||
void DCCWaveform::interruptHandler() {
|
void DCCWaveform::interruptHandler() {
|
||||||
// call the timer edge sensitive actions for progtrack and maintrack
|
// call the timer edge sensitive actions for progtrack and maintrack
|
||||||
bool mainCall2 = mainTrack.interrupt1();
|
bool mainCall2 = mainTrack.interrupt1();
|
||||||
@ -109,7 +95,6 @@ POWERMODE DCCWaveform::getPowerMode() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DCCWaveform::setPowerMode(POWERMODE mode) {
|
void DCCWaveform::setPowerMode(POWERMODE mode) {
|
||||||
|
|
||||||
powerMode = mode;
|
powerMode = mode;
|
||||||
bool ison = (mode == POWERMODE::ON);
|
bool ison = (mode == POWERMODE::ON);
|
||||||
motorDriver->setPower( ison);
|
motorDriver->setPower( ison);
|
||||||
@ -162,10 +147,6 @@ void DCCWaveform::checkPowerOverload() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// process time-edge sensitive part of interrupt
|
// process time-edge sensitive part of interrupt
|
||||||
// return true if second level required
|
// return true if second level required
|
||||||
bool DCCWaveform::interrupt1() {
|
bool DCCWaveform::interrupt1() {
|
||||||
|
@ -21,7 +21,6 @@
|
|||||||
#define DCCWaveform_h
|
#define DCCWaveform_h
|
||||||
#include "MotorDriver.h"
|
#include "MotorDriver.h"
|
||||||
|
|
||||||
|
|
||||||
// Wait times for power management. Unit: milliseconds
|
// Wait times for power management. Unit: milliseconds
|
||||||
const int POWER_SAMPLE_ON_WAIT = 100;
|
const int POWER_SAMPLE_ON_WAIT = 100;
|
||||||
const int POWER_SAMPLE_OFF_WAIT = 1000;
|
const int POWER_SAMPLE_OFF_WAIT = 1000;
|
||||||
@ -46,7 +45,7 @@ const byte resetPacket[] = {0x00, 0x00, 0x00};
|
|||||||
class DCCWaveform {
|
class DCCWaveform {
|
||||||
public:
|
public:
|
||||||
DCCWaveform( byte preambleBits, bool isMain);
|
DCCWaveform( byte preambleBits, bool isMain);
|
||||||
static void begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber);
|
static void begin(MotorDriver * mainDriver, MotorDriver * progDriver);
|
||||||
static void setDiagnosticSlowWave(bool slow);
|
static void setDiagnosticSlowWave(bool slow);
|
||||||
static void loop();
|
static void loop();
|
||||||
static DCCWaveform mainTrack;
|
static DCCWaveform mainTrack;
|
||||||
@ -58,9 +57,26 @@ class DCCWaveform {
|
|||||||
void checkPowerOverload();
|
void checkPowerOverload();
|
||||||
int getLastCurrent();
|
int getLastCurrent();
|
||||||
inline int get1024Current() {
|
inline int get1024Current() {
|
||||||
if (powerMode == POWERMODE::ON)
|
if (powerMode == POWERMODE::ON)
|
||||||
return (int)(lastCurrent*(long int)1024/motorDriver->getRawCurrentTripValue());
|
return (int)(lastCurrent*(long int)1024/motorDriver->getRawCurrentTripValue());
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
inline int getCurrentmA() {
|
||||||
|
if (powerMode == POWERMODE::ON)
|
||||||
|
return motorDriver->raw2mA(lastCurrent);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
inline int getMaxmA() {
|
||||||
|
if (maxmA == 0) { //only calculate this for first request, it doesn't change
|
||||||
|
maxmA = motorDriver->raw2mA(motorDriver->getRawCurrentTripValue()); //TODO: replace with actual max value or calc
|
||||||
|
}
|
||||||
|
return maxmA;
|
||||||
|
}
|
||||||
|
inline int getTripmA() {
|
||||||
|
if (tripmA == 0) { //only calculate this for first request, it doesn't change
|
||||||
|
tripmA = motorDriver->raw2mA(motorDriver->getRawCurrentTripValue());
|
||||||
|
}
|
||||||
|
return tripmA;
|
||||||
}
|
}
|
||||||
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
|
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
|
||||||
volatile bool packetPending;
|
volatile bool packetPending;
|
||||||
@ -88,6 +104,7 @@ class DCCWaveform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
static void interruptHandler();
|
static void interruptHandler();
|
||||||
bool interrupt1();
|
bool interrupt1();
|
||||||
void interrupt2();
|
void interrupt2();
|
||||||
@ -111,7 +128,8 @@ class DCCWaveform {
|
|||||||
byte pendingLength;
|
byte pendingLength;
|
||||||
byte pendingRepeats;
|
byte pendingRepeats;
|
||||||
int lastCurrent;
|
int lastCurrent;
|
||||||
|
int maxmA;
|
||||||
|
int tripmA;
|
||||||
|
|
||||||
// current sampling
|
// current sampling
|
||||||
POWERMODE powerMode;
|
POWERMODE powerMode;
|
||||||
|
@ -1,9 +1,18 @@
|
|||||||
The DCC-EX Team is pleased to release CommandStation-EX-v3.0.0 as a Production Release. This release is a major re-write of earlier versions. We've re-architected the code-base so that it can better handle new features going forward.
|
The DCC-EX Team is pleased to release CommandStation-EX-v3.0.0 as a Production Release. This release is a major re-write of earlier versions. We've re-architected the code-base so that it can better handle new features going forward.
|
||||||
|
|
||||||
**Known Bugs:**
|
**Known Bugs:**
|
||||||
- **Consisting through JMRI** - currently does not work in this release. A number of testers were able to develop a work around. If interested enter a Support Ticket.
|
- **Consisting through JMRI** - currently does not work in this release. You may use the <M> command to do this manually.
|
||||||
- **Wi-Fi** - works, but can be challenging to use if you want to switch between AP mode and STA station mode.
|
- **Wi-Fi** - works, but can be challenging to use if you want to switch between AP mode and STA station mode.
|
||||||
- **Pololu Motor Shield** - is supported with this release, but the user may have to play around with some timings to enable programming mode due to limitation in its current sensing circuitry
|
- **Pololu Motor Shield** - is supported with this release, but the user may have to play around with some timings to enable programming mode due to limitation in its current sensing circuitry
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.3**
|
||||||
|
- **<W addr> command to write loco address and clear consist**
|
||||||
|
- **<R> command will allow for consist address**
|
||||||
|
- **Startup commands implemented**
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.2:**
|
||||||
|
- **Create new output for current in mA for ``<c>`` command** - New current response outputs current in mA, overlimit current, and maximum board capable current
|
||||||
|
- **Simultaneously update JMRI to handle new current meter**
|
||||||
|
|
||||||
**Summary of the key new features added to CommandStation-EX V3.0.1:**
|
**Summary of the key new features added to CommandStation-EX V3.0.1:**
|
||||||
- **Add back fix for jitter**
|
- **Add back fix for jitter**
|
||||||
|
@ -21,10 +21,17 @@
|
|||||||
#include "Turnouts.h"
|
#include "Turnouts.h"
|
||||||
#include "EEStore.h"
|
#include "EEStore.h"
|
||||||
#include "PWMServoDriver.h"
|
#include "PWMServoDriver.h"
|
||||||
|
#include "StringFormatter.h"
|
||||||
#ifdef EESTOREDEBUG
|
#ifdef EESTOREDEBUG
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// print all turnout states to stream
|
||||||
|
void Turnout::printAll(Print *stream){
|
||||||
|
for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout)
|
||||||
|
StringFormatter::send(stream, F("<H %d %d>"), tt->data.id, (tt->data.tStatus & STATUS_ACTIVE)!=0);
|
||||||
|
} // Turnout::printAll
|
||||||
|
|
||||||
bool Turnout::activate(int n,bool state){
|
bool Turnout::activate(int n,bool state){
|
||||||
#ifdef EESTOREDEBUG
|
#ifdef EESTOREDEBUG
|
||||||
DIAG(F("\nTurnout::activate(%d,%d)\n"),n,state);
|
DIAG(F("\nTurnout::activate(%d,%d)\n"),n,state);
|
||||||
|
@ -49,6 +49,7 @@ class Turnout {
|
|||||||
static Turnout *create(int id , byte pin , int activeAngle, int inactiveAngle);
|
static Turnout *create(int id , byte pin , int activeAngle, int inactiveAngle);
|
||||||
static Turnout *create(int id);
|
static Turnout *create(int id);
|
||||||
void activate(bool state);
|
void activate(bool state);
|
||||||
|
static void printAll(Print *);
|
||||||
#ifdef EESTOREDEBUG
|
#ifdef EESTOREDEBUG
|
||||||
void print(Turnout *tt);
|
void print(Turnout *tt);
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
#ifndef version_h
|
#ifndef version_h
|
||||||
#define version_h
|
#define version_h
|
||||||
|
|
||||||
#define VERSION "3.0.1"
|
#define VERSION "3.0.3"
|
||||||
|
// 3.0.3 Includes:
|
||||||
|
// <W addr> command to write loco address and clear consist
|
||||||
|
// <R> command will allow for consist address
|
||||||
|
// Startup commands implemented
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user