1
0
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:
Asbelos 2021-01-25 21:12:06 +00:00
commit 13e516f8b2
15 changed files with 315 additions and 77 deletions

View File

@ -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

View File

@ -49,10 +49,19 @@ void setup()
// 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);
#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"));
}
@ -75,6 +84,10 @@ void loop()
EthernetInterface::loop();
#endif
#if defined(RMFT_ACTIVE)
RMFT::loop();
#endif
LCDDisplay::loop(); // ignored if LCD not in use
// Optionally report any decrease in memory (will automatically trigger on first call)

105
DCC.cpp
View File

@ -23,6 +23,7 @@
#include "EEStore.h"
#include "GITHUB_SHA.h"
#include "version.h"
#include "FSH.h"
// This module is responsible for converting API calls into
// messages to be sent to the waveform generator.
@ -45,15 +46,15 @@ const byte FN_GROUP_5=0x10;
FSH* DCC::shieldName=NULL;
void DCC::begin(const FSH* motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver, byte timerNumber) {
shieldName=(FSH*)motorShieldName;
void DCC::begin( FSH * motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver) {
shieldName=motorShieldName;
DIAG(F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
// Load stuff from EEprom
(void)EEPROM; // tell compiler not to warn this is unused
EEStore::init();
DCCWaveform::begin(mainDriver,progDriver, timerNumber);
DCCWaveform::begin(mainDriver,progDriver);
}
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[] = {
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,
SETBIT,(ackOp)5,
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
SETCV, (ackOp)17, // CV 17 is part of locoid
STARTMERGE,
@ -366,7 +388,7 @@ const ackOp FLASH LOCO_ID_PROG[] = {
SKIPTARGET,
SETCV, (ackOp)1,
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,
@ -378,6 +400,44 @@ const ackOp FLASH LOCO_ID_PROG[] = {
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.
// 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);
}
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
int reg=lookupSpeedTable(cab);
if (reg>=0) speedTable[reg].loco=0;
@ -552,6 +618,7 @@ int DCC::nextLoco = 0;
ackOp const * DCC::ackManagerProg;
byte DCC::ackManagerByte;
byte DCC::ackManagerStash;
int DCC::ackManagerWord;
int DCC::ackManagerCv;
byte DCC::ackManagerBitNum;
bool DCC::ackReceived;
@ -567,6 +634,13 @@ void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[]
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
// checkRessets return true if the caller should yield back to loop and try later.
@ -673,6 +747,14 @@ void DCC::ackManagerLoop(bool blocking) {
}
break;
case ITCB7: // If True callback(byte & 0xF)
if (ackReceived) {
ackManagerProg = NULL; // all done now
callback(ackManagerByte & 0x7F);
return;
}
break;
case NAKFAIL: // If nack callback(-1)
if (!ackReceived) {
ackManagerProg = NULL; // all done now
@ -708,6 +790,19 @@ void DCC::ackManagerLoop(bool blocking) {
ackManagerCv=GETFLASH(ackManagerProg);
break;
case SETBYTE:
ackManagerProg++;
ackManagerByte=GETFLASH(ackManagerProg);
break;
case SETBYTEH:
ackManagerByte=highByte(ackManagerWord);
break;
case SETBYTEL:
ackManagerByte=lowByte(ackManagerWord);
break;
case STASHLOCOID:
ackManagerStash=ackManagerByte; // stash value from CV17
break;

9
DCC.h
View File

@ -37,12 +37,16 @@ enum ackOp
ITC1, // If True Callback(1) (if prevous WACK got an ACK)
ITC0, // If True callback(0);
ITCB, // If True callback(byte)
ITCB7, // If True callback(byte &0x7F)
NAKFAIL, // if false callback(-1)
FAIL, // callback(-1)
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)
SETBIT, // sets bit 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
COMBINELOCOID, // combines current value with stashed value and returns it
ITSKIP, // skip to SKIPTARGET if ack true
@ -60,7 +64,7 @@ const byte MAX_LOCOS = 50;
class DCC
{
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();
// 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 getLocoId(ACK_CALLBACK callback, bool blocking = false);
static void setLocoId(int id,ACK_CALLBACK callback, bool blocking = false);
// Enhanced API functions
static void forgetLoco(int cab); // removes any speed reminders for this loco
@ -124,10 +129,12 @@ private:
static byte ackManagerByte;
static byte ackManagerBitNum;
static int ackManagerCv;
static int ackManagerWord;
static byte ackManagerStash;
static bool ackReceived;
static ACK_CALLBACK ackManagerCallback;
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 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)

View File

@ -15,6 +15,10 @@
#endif
#include "LCD_Implementation.h"
#include "freeMemory.h"
#include <Arduino.h>
#if __has_include ( "myAutomation.h")
#include "RMFT.h"
#define RMFT_ACTIVE
#endif
#endif

View File

@ -221,16 +221,29 @@ int DCCEXParser::splitHexValues(int result[MAX_PARAMS], const byte *cmd)
}
FILTER_CALLBACK DCCEXParser::filterCallback = 0;
FILTER_CALLBACK DCCEXParser::filterRMFTCallback = 0;
AT_COMMAND_CALLBACK DCCEXParser::atCommandCallback = 0;
void DCCEXParser::setFilter(FILTER_CALLBACK filter)
{
filterCallback = filter;
}
void DCCEXParser::setRMFTFilter(FILTER_CALLBACK filter)
{
filterRMFTCallback = filter;
}
void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK 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
void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
{
@ -245,6 +258,8 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
if (filterCallback)
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
switch (opcode)
@ -346,6 +361,9 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
case 'W': // WRITE CV ON PROG <W CV VALUE CALLBACKNUM CALLBACKSUB>
if (!stashCallback(stream, p))
break;
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;
@ -434,8 +452,11 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
}
return;
case 'c': // READ CURRENT <c>
StringFormatter::send(stream, F("<a %d>"), DCCWaveform::mainTrack.get1024Current());
case 'c': // SEND METER RESPONSES <c>
// <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;
case 'Q': // SENSORS <Q>
@ -445,7 +466,7 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
case 's': // <s>
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));
parseT(stream, 0, p); //send all Turnout states
Turnout::printAll(stream); //send all Turnout states
Output::printAll(stream); //send all Output states
Sensor::printAll(stream); //send all Sensor states
// 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>"));
return true;
case 0: // <Z>
case 0: // <Z> list Output definitions
{
bool gotone = false;
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)
{
case 0: // <T> list all turnout states
case 0: // <T> list turnout definitions
{
bool gotOne = false;
for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout)
{
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
}
@ -646,7 +668,7 @@ bool DCCEXParser::parseS(Print *stream, int params, int p[])
StringFormatter::send(stream, F("<O>"));
return true;
case 0: // <S> list sensor states
case 0: // <S> list sensor definitions
if (Sensor::firstSensor == NULL)
return false;
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)
{
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;
}

View File

@ -28,8 +28,10 @@ struct DCCEXParser
DCCEXParser();
void loop(Stream & stream);
void parse(Print * stream, byte * command, bool blocking);
void parse(const __FlashStringHelper * cmd);
void flush();
static void setFilter(FILTER_CALLBACK filter);
static void setRMFTFilter(FILTER_CALLBACK filter);
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
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_R(int result);
static void callback_Rloco(int result);
static void callback_Wloco(int result);
static void callback_Vbit(int result);
static void callback_Vbyte(int result);
static FILTER_CALLBACK filterCallback;
static FILTER_CALLBACK filterRMFTCallback;
static AT_COMMAND_CALLBACK atCommandCallback;
static void funcmap(int cab, byte value, byte fstart, byte fstop);

73
DCCTimer.cpp Normal file
View 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
View 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

View File

@ -20,17 +20,9 @@
#include <Arduino.h>
#include "DCCWaveform.h"
#include "DCCTimer.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::progTrack(PREAMBLE_BITS_PROG, false);
@ -39,21 +31,17 @@ DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
bool DCCWaveform::progTrackSyncMain=false;
bool DCCWaveform::progTrackBoosted=false;
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber) {
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) {
mainTrack.motorDriver=mainDriver;
progTrack.motorDriver=progDriver;
mainTrack.setPowerMode(POWERMODE::OFF);
progTrack.setPowerMode(POWERMODE::OFF);
Timer1.initialize();
Timer1.setPeriod(NORMAL_SIGNAL_TIME); // this is the 58uS DCC 1-bit waveform half-cycle
Timer1.attachInterrupt(interruptHandler);
Timer1.start();
DCCTimer::begin(DCCWaveform::interruptHandler);
}
void DCCWaveform::setDiagnosticSlowWave(bool slow) {
Timer1.setPeriod(slow? SLOW_SIGNAL_TIME : NORMAL_SIGNAL_TIME);
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"));
}
@ -62,8 +50,6 @@ void DCCWaveform::loop() {
progTrack.checkPowerOverload();
}
// static //
void DCCWaveform::interruptHandler() {
// call the timer edge sensitive actions for progtrack and maintrack
bool mainCall2 = mainTrack.interrupt1();
@ -109,7 +95,6 @@ POWERMODE DCCWaveform::getPowerMode() {
}
void DCCWaveform::setPowerMode(POWERMODE mode) {
powerMode = mode;
bool ison = (mode == POWERMODE::ON);
motorDriver->setPower( ison);
@ -162,10 +147,6 @@ void DCCWaveform::checkPowerOverload() {
}
}
// process time-edge sensitive part of interrupt
// return true if second level required
bool DCCWaveform::interrupt1() {

View File

@ -21,7 +21,6 @@
#define DCCWaveform_h
#include "MotorDriver.h"
// Wait times for power management. Unit: milliseconds
const int POWER_SAMPLE_ON_WAIT = 100;
const int POWER_SAMPLE_OFF_WAIT = 1000;
@ -46,7 +45,7 @@ const byte resetPacket[] = {0x00, 0x00, 0x00};
class DCCWaveform {
public:
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 loop();
static DCCWaveform mainTrack;
@ -62,6 +61,23 @@ class DCCWaveform {
return (int)(lastCurrent*(long int)1024/motorDriver->getRawCurrentTripValue());
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);
volatile bool packetPending;
volatile byte sentResetsSincePacket;
@ -88,6 +104,7 @@ class DCCWaveform {
}
private:
static void interruptHandler();
bool interrupt1();
void interrupt2();
@ -111,7 +128,8 @@ class DCCWaveform {
byte pendingLength;
byte pendingRepeats;
int lastCurrent;
int maxmA;
int tripmA;
// current sampling
POWERMODE powerMode;

View File

@ -1,10 +1,19 @@
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:**
- **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.
- **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:**
- **Add back fix for jitter**
- **Add Turnouts, Outputs and Sensors to ```<s>``` command output**

View File

@ -21,10 +21,17 @@
#include "Turnouts.h"
#include "EEStore.h"
#include "PWMServoDriver.h"
#include "StringFormatter.h"
#ifdef EESTOREDEBUG
#include "DIAG.h"
#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){
#ifdef EESTOREDEBUG
DIAG(F("\nTurnout::activate(%d,%d)\n"),n,state);

View File

@ -49,6 +49,7 @@ class Turnout {
static Turnout *create(int id , byte pin , int activeAngle, int inactiveAngle);
static Turnout *create(int id);
void activate(bool state);
static void printAll(Print *);
#ifdef EESTOREDEBUG
void print(Turnout *tt);
#endif

View File

@ -1,6 +1,10 @@
#ifndef 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