mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-04-21 20:41:19 +02:00
Compare commits
1 Commits
89a5e31cba
...
6b606ca20a
Author | SHA1 | Date | |
---|---|---|---|
|
6b606ca20a |
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* © 2022 Harald Barth
|
* © 2022 Harald Barth
|
||||||
* © 2020-2025 Chris Harlow
|
* © 2020-2021 Chris Harlow
|
||||||
* © 2020 Gregor Baues
|
* © 2020 Gregor Baues
|
||||||
* © 2022 Colin Murdoch
|
* © 2022 Colin Murdoch
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
@ -185,9 +185,6 @@ void CommandDistributor::setClockTime(int16_t clocktime, int8_t clockrate, byte
|
|||||||
{
|
{
|
||||||
case 1:
|
case 1:
|
||||||
if (clocktime != lastclocktime){
|
if (clocktime != lastclocktime){
|
||||||
auto difference = clocktime - lastclocktime;
|
|
||||||
if (difference<0) difference+=1440;
|
|
||||||
DCC::setTime(clocktime,clockrate,difference>2);
|
|
||||||
// CAH. DIAG removed because LCD does it anyway.
|
// CAH. DIAG removed because LCD does it anyway.
|
||||||
LCD(6,F("Clk Time:%d Sp %d"), clocktime, clockrate);
|
LCD(6,F("Clk Time:%d Sp %d"), clocktime, clockrate);
|
||||||
// look for an event for this time
|
// look for an event for this time
|
||||||
@ -210,13 +207,9 @@ int16_t CommandDistributor::retClockTime() {
|
|||||||
return lastclocktime;
|
return lastclocktime;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandDistributor::broadcastLoco(DCC::LOCO* sp) {
|
void CommandDistributor::broadcastLoco(byte slot) {
|
||||||
if (!sp) {
|
DCC::LOCO * sp=&DCC::speedTable[slot];
|
||||||
broadcastReply(COMMAND_TYPE,F("<l 0 -1 128 0>\n"));
|
broadcastReply(COMMAND_TYPE, F("<l %d %d %d %l>\n"), sp->loco,slot,sp->speedCode,sp->functions);
|
||||||
return;
|
|
||||||
}
|
|
||||||
broadcastReply(COMMAND_TYPE, F("<l %d 0 %d %l>\n"),
|
|
||||||
sp->loco,sp->targetSpeed,sp->functions);
|
|
||||||
#ifdef SABERTOOTH
|
#ifdef SABERTOOTH
|
||||||
if (Serial2 && sp->loco == SABERTOOTH) {
|
if (Serial2 && sp->loco == SABERTOOTH) {
|
||||||
static uint8_t rampingmode = 0;
|
static uint8_t rampingmode = 0;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* © 2022 Harald Barth
|
* © 2022 Harald Barth
|
||||||
* © 2020-2025 Chris Harlow
|
* © 2020-2021 Chris Harlow
|
||||||
* © 2020 Gregor Baues
|
* © 2020 Gregor Baues
|
||||||
* © 2022 Colin Murdoch
|
* © 2022 Colin Murdoch
|
||||||
*
|
*
|
||||||
@ -28,7 +28,6 @@
|
|||||||
#include "StringBuffer.h"
|
#include "StringBuffer.h"
|
||||||
#include "defines.h"
|
#include "defines.h"
|
||||||
#include "EXRAIL2.h"
|
#include "EXRAIL2.h"
|
||||||
#include "DCC.h"
|
|
||||||
|
|
||||||
#if WIFI_ON | ETHERNET_ON
|
#if WIFI_ON | ETHERNET_ON
|
||||||
// Command Distributor must handle a RingStream of clients
|
// Command Distributor must handle a RingStream of clients
|
||||||
@ -47,7 +46,7 @@ private:
|
|||||||
#endif
|
#endif
|
||||||
public :
|
public :
|
||||||
static void parse(byte clientId,byte* buffer, RingStream * ring);
|
static void parse(byte clientId,byte* buffer, RingStream * ring);
|
||||||
static void broadcastLoco(DCC::LOCO * slot);
|
static void broadcastLoco(byte slot);
|
||||||
static void broadcastForgetLoco(int16_t loco);
|
static void broadcastForgetLoco(int16_t loco);
|
||||||
static void broadcastSensor(int16_t id, bool value);
|
static void broadcastSensor(int16_t id, bool value);
|
||||||
static void broadcastTurnout(int16_t id, bool isClosed);
|
static void broadcastTurnout(int16_t id, bool isClosed);
|
||||||
|
417
DCC.cpp
417
DCC.cpp
@ -5,7 +5,7 @@
|
|||||||
* © 2021 Herb Morton
|
* © 2021 Herb Morton
|
||||||
* © 2020-2022 Harald Barth
|
* © 2020-2022 Harald Barth
|
||||||
* © 2020-2021 M Steve Todd
|
* © 2020-2021 M Steve Todd
|
||||||
* © 2020-2025 Chris Harlow
|
* © 2020-2021 Chris Harlow
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of DCC-EX
|
* This file is part of DCC-EX
|
||||||
@ -37,7 +37,6 @@
|
|||||||
#include "CommandDistributor.h"
|
#include "CommandDistributor.h"
|
||||||
#include "TrackManager.h"
|
#include "TrackManager.h"
|
||||||
#include "DCCTimer.h"
|
#include "DCCTimer.h"
|
||||||
#include "Railcom.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.
|
||||||
@ -61,8 +60,6 @@ const byte FN_GROUP_5=0x10;
|
|||||||
FSH* DCC::shieldName=NULL;
|
FSH* DCC::shieldName=NULL;
|
||||||
byte DCC::globalSpeedsteps=128;
|
byte DCC::globalSpeedsteps=128;
|
||||||
|
|
||||||
#define SLOTLOOP for (auto slot=&speedTable[0];slot!=&speedTable[MAX_LOCOS];slot++)
|
|
||||||
|
|
||||||
void DCC::begin() {
|
void DCC::begin() {
|
||||||
StringFormatter::send(&USB_SERIAL,F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
|
StringFormatter::send(&USB_SERIAL,F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
|
||||||
#ifndef DISABLE_EEPROM
|
#ifndef DISABLE_EEPROM
|
||||||
@ -75,49 +72,13 @@ void DCC::begin() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
byte DCC::defaultMomentumA=0;
|
|
||||||
byte DCC::defaultMomentumD=0;
|
|
||||||
bool DCC::linearAcceleration=false;
|
|
||||||
|
|
||||||
byte DCC::getMomentum(LOCO * slot) {
|
|
||||||
auto target=slot->targetSpeed & 0x7f;
|
|
||||||
auto current=slot->speedCode & 0x7f;
|
|
||||||
if (target > current) {
|
|
||||||
// accelerating
|
|
||||||
auto momentum=slot->momentumA==MOMENTUM_USE_DEFAULT ? defaultMomentumA : slot->momentumA;
|
|
||||||
// if nonlinear acceleration, momentum is reduced according to
|
|
||||||
// gap between throttle and speed.
|
|
||||||
// ie. Loco takes accelerates faster if high throttle
|
|
||||||
if (momentum==0 || linearAcceleration) return momentum;
|
|
||||||
auto powerDifference= (target-current)/8;
|
|
||||||
if (momentum-powerDifference <0) return 0;
|
|
||||||
return momentum-powerDifference;
|
|
||||||
}
|
|
||||||
return slot->momentumD==MOMENTUM_USE_DEFAULT ? defaultMomentumD : slot->momentumD;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) {
|
void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) {
|
||||||
if (tSpeed==1) {
|
|
||||||
if (cab==0) {
|
|
||||||
estopAll(); // ESTOP broadcast fix
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
byte speedCode = (tSpeed & 0x7F) + tDirection * 128;
|
byte speedCode = (tSpeed & 0x7F) + tDirection * 128;
|
||||||
LOCO * slot=lookupSpeedTable(cab);
|
setThrottle2(cab, speedCode);
|
||||||
if (slot->targetSpeed==speedCode) return;
|
TrackManager::setDCSignal(cab,speedCode); // in case this is a dcc track on this addr
|
||||||
slot->targetSpeed=speedCode;
|
// retain speed for loco reminders
|
||||||
byte momentum=getMomentum(slot);
|
updateLocoReminder(cab, speedCode );
|
||||||
if (momentum && tSpeed!=1) { // not ESTOP
|
|
||||||
// we dont throttle speed, we just let the reminders take it to target
|
|
||||||
slot->momentum_base=millis();
|
|
||||||
}
|
|
||||||
else { // Momentum not involved, throttle now.
|
|
||||||
slot->speedCode = speedCode;
|
|
||||||
setThrottle2(cab, speedCode);
|
|
||||||
TrackManager::setDCSignal(cab,speedCode); // in case this is a dcc track on this addr
|
|
||||||
}
|
|
||||||
CommandDistributor::broadcastLoco(slot);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::setThrottle2( uint16_t cab, byte speedCode) {
|
void DCC::setThrottle2( uint16_t cab, byte speedCode) {
|
||||||
@ -178,22 +139,18 @@ void DCC::setFunctionInternal(int cab, byte byte1, byte byte2, byte count) {
|
|||||||
// returns speed steps 0 to 127 (1 == emergency stop)
|
// returns speed steps 0 to 127 (1 == emergency stop)
|
||||||
// or -1 on "loco not found"
|
// or -1 on "loco not found"
|
||||||
int8_t DCC::getThrottleSpeed(int cab) {
|
int8_t DCC::getThrottleSpeed(int cab) {
|
||||||
return getThrottleSpeedByte(cab) & 0x7F;
|
int reg=lookupSpeedTable(cab);
|
||||||
|
if (reg<0) return -1;
|
||||||
|
return speedTable[reg].speedCode & 0x7F;
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns speed code byte
|
// returns speed code byte
|
||||||
// or 128 (speed 0, dir forward) on "loco not found".
|
// or 128 (speed 0, dir forward) on "loco not found".
|
||||||
// This is the throttle set speed
|
|
||||||
uint8_t DCC::getThrottleSpeedByte(int cab) {
|
uint8_t DCC::getThrottleSpeedByte(int cab) {
|
||||||
LOCO * slot=lookupSpeedTable(cab,false);
|
int reg=lookupSpeedTable(cab);
|
||||||
return slot?slot->targetSpeed:128;
|
if (reg<0)
|
||||||
}
|
return 128;
|
||||||
// returns speed code byte for loco.
|
return speedTable[reg].speedCode;
|
||||||
// This is the most recently send DCC speed packet byte
|
|
||||||
// or 128 (speed 0, dir forward) on "loco not found".
|
|
||||||
uint8_t DCC::getLocoSpeedByte(int cab) {
|
|
||||||
LOCO* slot=lookupSpeedTable(cab,false);
|
|
||||||
return slot?slot->speedCode:128;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns 0 to 7 for frequency
|
// returns 0 to 7 for frequency
|
||||||
@ -202,11 +159,12 @@ uint8_t DCC::getThrottleFrequency(int cab) {
|
|||||||
(void)cab;
|
(void)cab;
|
||||||
return 0;
|
return 0;
|
||||||
#else
|
#else
|
||||||
LOCO* slot=lookupSpeedTable(cab);
|
int reg=lookupSpeedTable(cab);
|
||||||
if (!slot) return 0; // use default frequency
|
if (reg<0)
|
||||||
|
return 0; // use default frequency
|
||||||
// shift out first 29 bits so we have the 3 "frequency bits" left
|
// shift out first 29 bits so we have the 3 "frequency bits" left
|
||||||
uint8_t res = (uint8_t)(slot->functions >>29);
|
uint8_t res = (uint8_t)(speedTable[reg].functions >>29);
|
||||||
//DIAG(F("Speed table %d functions %l shifted %d"), reg, slot->functions, res);
|
//DIAG(F("Speed table %d functions %l shifted %d"), reg, speedTable[reg].functions, res);
|
||||||
return res;
|
return res;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@ -214,7 +172,9 @@ uint8_t DCC::getThrottleFrequency(int cab) {
|
|||||||
// returns direction on loco
|
// returns direction on loco
|
||||||
// or true/forward on "loco not found"
|
// or true/forward on "loco not found"
|
||||||
bool DCC::getThrottleDirection(int cab) {
|
bool DCC::getThrottleDirection(int cab) {
|
||||||
return getThrottleSpeedByte(cab) & 0x80;
|
int reg=lookupSpeedTable(cab);
|
||||||
|
if (reg<0) return true;
|
||||||
|
return (speedTable[reg].speedCode & 0x80) !=0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set function to value on or off
|
// Set function to value on or off
|
||||||
@ -247,21 +207,22 @@ bool DCC::setFn( int cab, int16_t functionNumber, bool on) {
|
|||||||
if (functionNumber > 31)
|
if (functionNumber > 31)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
LOCO * slot = lookupSpeedTable(cab);
|
int reg = lookupSpeedTable(cab);
|
||||||
|
if (reg<0) return false;
|
||||||
|
|
||||||
// Take care of functions:
|
// Take care of functions:
|
||||||
// Set state of function
|
// Set state of function
|
||||||
uint32_t previous=slot->functions;
|
uint32_t previous=speedTable[reg].functions;
|
||||||
uint32_t funcmask = (1UL<<functionNumber);
|
uint32_t funcmask = (1UL<<functionNumber);
|
||||||
if (on) {
|
if (on) {
|
||||||
slot->functions |= funcmask;
|
speedTable[reg].functions |= funcmask;
|
||||||
} else {
|
} else {
|
||||||
slot->functions &= ~funcmask;
|
speedTable[reg].functions &= ~funcmask;
|
||||||
}
|
}
|
||||||
if (slot->functions != previous) {
|
if (speedTable[reg].functions != previous) {
|
||||||
if (functionNumber <= 28)
|
if (functionNumber <= 28)
|
||||||
updateGroupflags(slot->groupFlags, functionNumber);
|
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
||||||
CommandDistributor::broadcastLoco(slot);
|
CommandDistributor::broadcastLoco(reg);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -269,13 +230,14 @@ bool DCC::setFn( int cab, int16_t functionNumber, bool on) {
|
|||||||
// Flip function state (used from withrottle protocol)
|
// Flip function state (used from withrottle protocol)
|
||||||
void DCC::changeFn( int cab, int16_t functionNumber) {
|
void DCC::changeFn( int cab, int16_t functionNumber) {
|
||||||
if (cab<=0 || functionNumber>31) return;
|
if (cab<=0 || functionNumber>31) return;
|
||||||
auto slot=lookupSpeedTable(cab);
|
int reg = lookupSpeedTable(cab);
|
||||||
|
if (reg<0) return;
|
||||||
unsigned long funcmask = (1UL<<functionNumber);
|
unsigned long funcmask = (1UL<<functionNumber);
|
||||||
slot->functions ^= funcmask;
|
speedTable[reg].functions ^= funcmask;
|
||||||
if (functionNumber <= 28) {
|
if (functionNumber <= 28) {
|
||||||
updateGroupflags(slot->groupFlags, functionNumber);
|
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
||||||
}
|
}
|
||||||
CommandDistributor::broadcastLoco(slot);
|
CommandDistributor::broadcastLoco(reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report function state (used from withrottle protocol)
|
// Report function state (used from withrottle protocol)
|
||||||
@ -283,10 +245,12 @@ void DCC::changeFn( int cab, int16_t functionNumber) {
|
|||||||
int8_t DCC::getFn( int cab, int16_t functionNumber) {
|
int8_t DCC::getFn( int cab, int16_t functionNumber) {
|
||||||
if (cab<=0 || functionNumber>31)
|
if (cab<=0 || functionNumber>31)
|
||||||
return -1; // unknown
|
return -1; // unknown
|
||||||
auto slot = lookupSpeedTable(cab);
|
int reg = lookupSpeedTable(cab);
|
||||||
|
if (reg<0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
unsigned long funcmask = (1UL<<functionNumber);
|
unsigned long funcmask = (1UL<<functionNumber);
|
||||||
return (slot->functions & funcmask)? 1 : 0;
|
return (speedTable[reg].functions & funcmask)? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the group flag to say we have touched the particular group.
|
// Set the group flag to say we have touched the particular group.
|
||||||
@ -303,22 +267,22 @@ void DCC::updateGroupflags(byte & flags, int16_t functionNumber) {
|
|||||||
|
|
||||||
uint32_t DCC::getFunctionMap(int cab) {
|
uint32_t DCC::getFunctionMap(int cab) {
|
||||||
if (cab<=0) return 0; // unknown pretend all functions off
|
if (cab<=0) return 0; // unknown pretend all functions off
|
||||||
auto slot = lookupSpeedTable(cab,false);
|
int reg = lookupSpeedTable(cab);
|
||||||
return slot?slot->functions:0;
|
return (reg<0)?0:speedTable[reg].functions;
|
||||||
}
|
}
|
||||||
|
|
||||||
// saves DC frequency (0..3) in spare functions 29,30,31
|
// saves DC frequency (0..3) in spare functions 29,30,31
|
||||||
void DCC::setDCFreq(int cab,byte freq) {
|
void DCC::setDCFreq(int cab,byte freq) {
|
||||||
if (cab==0 || freq>3) return;
|
if (cab==0 || freq>3) return;
|
||||||
auto slot=lookupSpeedTable(cab,true);
|
auto reg=lookupSpeedTable(cab,true);
|
||||||
// drop and replace F29,30,31 (top 3 bits)
|
// drop and replace F29,30,31 (top 3 bits)
|
||||||
auto newFunctions=slot->functions & 0x1FFFFFFFUL;
|
auto newFunctions=speedTable[reg].functions & 0x1FFFFFFFUL;
|
||||||
if (freq==1) newFunctions |= (1UL<<29); // F29
|
if (freq==1) newFunctions |= (1UL<<29); // F29
|
||||||
else if (freq==2) newFunctions |= (1UL<<30); // F30
|
else if (freq==2) newFunctions |= (1UL<<30); // F30
|
||||||
else if (freq==3) newFunctions |= (1UL<<31); // F31
|
else if (freq==3) newFunctions |= (1UL<<31); // F31
|
||||||
if (newFunctions==slot->functions) return; // no change
|
if (newFunctions==speedTable[reg].functions) return; // no change
|
||||||
slot->functions=newFunctions;
|
speedTable[reg].functions=newFunctions;
|
||||||
CommandDistributor::broadcastLoco(slot);
|
CommandDistributor::broadcastLoco(reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::setAccessory(int address, byte port, bool gate, byte onoff /*= 2*/) {
|
void DCC::setAccessory(int address, byte port, bool gate, byte onoff /*= 2*/) {
|
||||||
@ -425,25 +389,6 @@ void DCC::writeCVByteMain(int cab, int cv, byte bValue) {
|
|||||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
|
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// readCVByteMain: Read a byte with PoM on main.
|
|
||||||
// This requires Railcom active
|
|
||||||
//
|
|
||||||
void DCC::readCVByteMain(int cab, int cv, ACK_CALLBACK callback) {
|
|
||||||
byte b[5];
|
|
||||||
byte nB = 0;
|
|
||||||
if (cab > HIGHEST_SHORT_ADDR)
|
|
||||||
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
|
|
||||||
|
|
||||||
b[nB++] = lowByte(cab);
|
|
||||||
b[nB++] = cv1(READ_BYTE_MAIN, cv); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03
|
|
||||||
b[nB++] = cv2(cv);
|
|
||||||
b[nB++] = 0;
|
|
||||||
|
|
||||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
|
|
||||||
Railcom::anticipate(cab,cv,callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// writeCVBitMain: Write a bit of a byte with PoM on main. This writes
|
// writeCVBitMain: Write a bit of a byte with PoM on main. This writes
|
||||||
// the 5 byte sized packet to implement this DCC function
|
// the 5 byte sized packet to implement this DCC function
|
||||||
@ -465,44 +410,6 @@ void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) {
|
|||||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
|
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DCC::setTime(uint16_t minutes,uint8_t speed, bool suddenChange) {
|
|
||||||
/* see rcn-122
|
|
||||||
5 Global commands
|
|
||||||
These commands are sent and begin exclusively with a broadcast address 0
|
|
||||||
always with {synchronous bits} 0 0000-0000 … and end with the checksum
|
|
||||||
... PPPPPPPP 1. Therefore, only the bytes of the commands and not that of
|
|
||||||
shown below whole package shown. The commands can be used by vehicle and
|
|
||||||
accessory decoders alike.
|
|
||||||
|
|
||||||
5.1 Time command
|
|
||||||
This command is four bytes long and has the format:
|
|
||||||
1100-0001 CCxx-xxxx xxxx-xxxxx xxxx-xxxx
|
|
||||||
CC indicates what data is transmitted in the packet:
|
|
||||||
CC = 00 Model Time
|
|
||||||
1100-0001 00MM-MMMM WWWH-HHHH U0BB-BBBB with:
|
|
||||||
MMMMMM = Minutes, Value range: 0..59
|
|
||||||
WWW = Day of the Week, Value range: 0 = Monday, 1 = Tuesday, 2 = Wednesday,
|
|
||||||
3 = Thursday, 4 = Friday, 5 = Saturday, 6 = Sunday, 7 = Weekday
|
|
||||||
is not supported.
|
|
||||||
HHHHH = Hours, value range: 0..23
|
|
||||||
U =
|
|
||||||
Update, i.e. the time has changed suddenly, e.g. by a new one timetable to start.
|
|
||||||
Up to 4 can occur per sudden change commands can be marked like this.
|
|
||||||
BBBBBB = Acceleration factor, value range 0..63. An acceleration factor of 0 means the
|
|
||||||
model clock has been stopped, a factor of 1 corresponds to real time, at 2 the
|
|
||||||
clock runs twice as fast, at three times as fast as real time, etc.
|
|
||||||
*/
|
|
||||||
if (minutes>=1440 || speed>63 ) return false;
|
|
||||||
byte b[5];
|
|
||||||
b[0]=0; // broadcast address
|
|
||||||
b[1]=0b11000001; // 1100-0001 (model time)
|
|
||||||
b[2]=minutes % 60 ; // MM
|
|
||||||
b[3]= 0b11100000 | (minutes/60); // 111H-HHHH weekday not supported
|
|
||||||
b[4]= (suddenChange ? 0b10000000 : 0) | speed;
|
|
||||||
DCCWaveform::mainTrack.schedulePacket(b, sizeof(b), 2);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
FSH* DCC::getMotorShieldName() {
|
FSH* DCC::getMotorShieldName() {
|
||||||
return shieldName;
|
return shieldName;
|
||||||
}
|
}
|
||||||
@ -831,9 +738,10 @@ void DCC::setConsistId(int id,bool reverse,ACK_CALLBACK callback) {
|
|||||||
|
|
||||||
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
|
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
|
||||||
setThrottle2(cab,1); // ESTOP this loco if still on track
|
setThrottle2(cab,1); // ESTOP this loco if still on track
|
||||||
auto slot=lookupSpeedTable(cab, false);
|
int reg=lookupSpeedTable(cab, false);
|
||||||
if (slot) {
|
if (reg>=0) {
|
||||||
slot->loco=-1; // no longer used but not end of world
|
speedTable[reg].loco=0;
|
||||||
|
setThrottle2(cab,1); // ESTOP if this loco still on track
|
||||||
CommandDistributor::broadcastForgetLoco(cab);
|
CommandDistributor::broadcastForgetLoco(cab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -841,7 +749,7 @@ void DCC::forgetAllLocos() { // removes all speed reminders
|
|||||||
setThrottle2(0,1); // ESTOP all locos still on track
|
setThrottle2(0,1); // ESTOP all locos still on track
|
||||||
for (int i=0;i<MAX_LOCOS;i++) {
|
for (int i=0;i<MAX_LOCOS;i++) {
|
||||||
if (speedTable[i].loco) CommandDistributor::broadcastForgetLoco(speedTable[i].loco);
|
if (speedTable[i].loco) CommandDistributor::broadcastForgetLoco(speedTable[i].loco);
|
||||||
speedTable[i].loco=0; // no longer used and looks like end
|
speedTable[i].loco=0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -856,67 +764,26 @@ void DCC::issueReminders() {
|
|||||||
// if the main track transmitter still has a pending packet, skip this time around.
|
// if the main track transmitter still has a pending packet, skip this time around.
|
||||||
if (!DCCWaveform::mainTrack.isReminderWindowOpen()) return;
|
if (!DCCWaveform::mainTrack.isReminderWindowOpen()) return;
|
||||||
// Move to next loco slot. If occupied, send a reminder.
|
// Move to next loco slot. If occupied, send a reminder.
|
||||||
auto slot = nextLocoReminder;
|
int reg = lastLocoReminder+1;
|
||||||
if (slot >= &speedTable[MAX_LOCOS]) slot=&speedTable[0]; // Go to start of table
|
if (reg > highestUsedReg) reg = 0; // Go to start of table
|
||||||
if (slot->loco > 0)
|
if (speedTable[reg].loco > 0) {
|
||||||
if (!issueReminder(slot))
|
// have found loco to remind
|
||||||
return;
|
if (issueReminder(reg))
|
||||||
// a loco=0 is at the end of the list, a loco <0 is deleted
|
lastLocoReminder = reg;
|
||||||
if (slot->loco==0) nextLocoReminder = &speedTable[0];
|
} else
|
||||||
else nextLocoReminder=slot+1;
|
lastLocoReminder = reg;
|
||||||
}
|
}
|
||||||
|
|
||||||
int16_t normalize(byte speed) {
|
bool DCC::issueReminder(int reg) {
|
||||||
if (speed & 0x80) return speed & 0x7F;
|
unsigned long functions=speedTable[reg].functions;
|
||||||
return 0-1-speed;
|
int loco=speedTable[reg].loco;
|
||||||
}
|
byte flags=speedTable[reg].groupFlags;
|
||||||
byte dccalize(int16_t speed) {
|
|
||||||
if (speed>127) return 0xFF; // 127 forward
|
|
||||||
if (speed<-127) return 0x7F; // 127 reverse
|
|
||||||
if (speed >=0) return speed | 0x80;
|
|
||||||
// negative speeds... -1==dcc 0, -2==dcc 1
|
|
||||||
return (int16_t)-1 - speed;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DCC::issueReminder(LOCO * slot) {
|
|
||||||
unsigned long functions=slot->functions;
|
|
||||||
int loco=slot->loco;
|
|
||||||
byte flags=slot->groupFlags;
|
|
||||||
|
|
||||||
switch (loopStatus) {
|
switch (loopStatus) {
|
||||||
case 0: {
|
case 0:
|
||||||
// calculate any momentum change going on
|
// DIAG(F("Reminder %d speed %d"),loco,speedTable[reg].speedCode);
|
||||||
auto sc=slot->speedCode;
|
setThrottle2(loco, speedTable[reg].speedCode);
|
||||||
if (slot->targetSpeed!=sc) {
|
break;
|
||||||
// calculate new speed code
|
|
||||||
auto now=millis();
|
|
||||||
int16_t delay=now-slot->momentum_base;
|
|
||||||
auto millisPerNotch=MOMENTUM_FACTOR * (int16_t)getMomentum(slot);
|
|
||||||
// allow for momentum change to 0 while accelerating/slowing
|
|
||||||
auto ticks=(millisPerNotch>0)?(delay/millisPerNotch):500;
|
|
||||||
if (ticks>0) {
|
|
||||||
auto current=normalize(sc); // -128..+127
|
|
||||||
auto target=normalize(slot->targetSpeed);
|
|
||||||
// DIAG(F("Momentum l=%d ti=%d sc=%d c=%d t=%d"),loco,ticks,sc,current,target);
|
|
||||||
if (current<target) { // accelerate
|
|
||||||
current+=ticks;
|
|
||||||
if (current>target) current=target;
|
|
||||||
}
|
|
||||||
else { // slow
|
|
||||||
current-=ticks;
|
|
||||||
if (current<target) current=target;
|
|
||||||
}
|
|
||||||
sc=dccalize(current);
|
|
||||||
//DIAG(F("c=%d newsc=%d"),current,sc);
|
|
||||||
slot->speedCode=sc;
|
|
||||||
TrackManager::setDCSignal(loco,sc); // in case this is a dcc track on this addr
|
|
||||||
slot->momentum_base=now;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// DIAG(F("Reminder %d speed %d"),loco,slot->speedCode);
|
|
||||||
setThrottle2(loco, sc);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 1: // remind function group 1 (F0-F4)
|
case 1: // remind function group 1 (F0-F4)
|
||||||
if (flags & FN_GROUP_1)
|
if (flags & FN_GROUP_1)
|
||||||
#ifndef DISABLE_FUNCTION_REMINDERS
|
#ifndef DISABLE_FUNCTION_REMINDERS
|
||||||
@ -977,128 +844,70 @@ byte DCC::cv2(int cv) {
|
|||||||
return lowByte(cv);
|
return lowByte(cv);
|
||||||
}
|
}
|
||||||
|
|
||||||
DCC::LOCO * DCC::lookupSpeedTable(int locoId, bool autoCreate) {
|
int DCC::lookupSpeedTable(int locoId, bool autoCreate) {
|
||||||
// determine speed reg for this loco
|
// determine speed reg for this loco
|
||||||
LOCO * firstEmpty=nullptr;
|
int firstEmpty = MAX_LOCOS;
|
||||||
SLOTLOOP {
|
int reg;
|
||||||
if (firstEmpty==nullptr && slot->loco<=0) firstEmpty=slot;
|
for (reg = 0; reg < MAX_LOCOS; reg++) {
|
||||||
if (slot->loco == locoId) return slot;
|
if (speedTable[reg].loco == locoId) break;
|
||||||
if (slot->loco==0) break;
|
if (speedTable[reg].loco == 0 && firstEmpty == MAX_LOCOS) firstEmpty = reg;
|
||||||
}
|
}
|
||||||
if (!autoCreate) return nullptr;
|
|
||||||
if (firstEmpty==nullptr) {
|
// return -1 if not found and not auto creating
|
||||||
// return last slot if full
|
if (reg== MAX_LOCOS && !autoCreate) return -1;
|
||||||
DIAG(F("Too many locos, reusing last slot"));
|
if (reg == MAX_LOCOS) reg = firstEmpty;
|
||||||
firstEmpty=&speedTable[MAX_LOCOS-1];
|
if (reg >= MAX_LOCOS) {
|
||||||
|
DIAG(F("Too many locos"));
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
// fill first empty slot with new entry
|
if (reg==firstEmpty){
|
||||||
firstEmpty->loco = locoId;
|
speedTable[reg].loco = locoId;
|
||||||
firstEmpty->speedCode=128; // default direction forward
|
speedTable[reg].speedCode=128; // default direction forward
|
||||||
firstEmpty->targetSpeed=128; // default direction forward
|
speedTable[reg].groupFlags=0;
|
||||||
firstEmpty->groupFlags=0;
|
speedTable[reg].functions=0;
|
||||||
firstEmpty->functions=0;
|
}
|
||||||
firstEmpty->momentumA=MOMENTUM_USE_DEFAULT;
|
if (reg > highestUsedReg) highestUsedReg = reg;
|
||||||
firstEmpty->momentumD=MOMENTUM_USE_DEFAULT;
|
return reg;
|
||||||
return firstEmpty;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DCC::setMomentum(int locoId,int16_t accelerating, int16_t decelerating) {
|
void DCC::updateLocoReminder(int loco, byte speedCode) {
|
||||||
if (locoId<0) return false;
|
|
||||||
if (locoId==0) {
|
if (loco==0) {
|
||||||
if (accelerating<0 || decelerating<0) return false;
|
// broadcast stop/estop but dont change direction
|
||||||
defaultMomentumA=accelerating/MOMENTUM_FACTOR;
|
for (int reg = 0; reg <= highestUsedReg; reg++) {
|
||||||
defaultMomentumD=decelerating/MOMENTUM_FACTOR;
|
if (speedTable[reg].loco==0) continue;
|
||||||
return true;
|
byte newspeed=(speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f);
|
||||||
|
if (speedTable[reg].speedCode != newspeed) {
|
||||||
|
speedTable[reg].speedCode = newspeed;
|
||||||
|
CommandDistributor::broadcastLoco(reg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
// -1 is ok and means this loco should use the default.
|
|
||||||
if (accelerating<-1 || decelerating<-1) return false;
|
|
||||||
if (accelerating/MOMENTUM_FACTOR >= MOMENTUM_USE_DEFAULT ||
|
|
||||||
decelerating/MOMENTUM_FACTOR >= MOMENTUM_USE_DEFAULT) return false;
|
|
||||||
|
|
||||||
// Values stored are 255=MOMENTUM_USE_DEFAULT, or millis/MOMENTUM_FACTOR.
|
// determine speed reg for this loco
|
||||||
// This is to keep the values in a byte rather than int16
|
int reg=lookupSpeedTable(loco);
|
||||||
// thus saving 2 bytes RAM per loco slot.
|
if (reg>=0 && speedTable[reg].speedCode!=speedCode) {
|
||||||
LOCO* slot=lookupSpeedTable(locoId,true);
|
speedTable[reg].speedCode = speedCode;
|
||||||
slot->momentumA=(accelerating<0)? MOMENTUM_USE_DEFAULT: (accelerating/MOMENTUM_FACTOR);
|
CommandDistributor::broadcastLoco(reg);
|
||||||
slot->momentumD=(decelerating<0)? MOMENTUM_USE_DEFAULT: (decelerating/MOMENTUM_FACTOR);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void DCC::estopAll() {
|
|
||||||
setThrottle2(0,1); // estop all locos
|
|
||||||
TrackManager::setDCSignal(0,1);
|
|
||||||
|
|
||||||
// remind stop/estop but dont change direction
|
|
||||||
SLOTLOOP {
|
|
||||||
if (slot->loco<=0) continue;
|
|
||||||
byte newspeed=(slot->targetSpeed & 0x80) | 0x01;
|
|
||||||
slot->speedCode = newspeed;
|
|
||||||
slot->targetSpeed = newspeed;
|
|
||||||
CommandDistributor::broadcastLoco(slot);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
DCC::LOCO DCC::speedTable[MAX_LOCOS];
|
DCC::LOCO DCC::speedTable[MAX_LOCOS];
|
||||||
DCC::LOCO * DCC::nextLocoReminder = &DCC::speedTable[0];
|
int DCC::lastLocoReminder = 0;
|
||||||
|
int DCC::highestUsedReg = 0;
|
||||||
|
|
||||||
|
|
||||||
void DCC::displayCabList(Print * stream) {
|
void DCC::displayCabList(Print * stream) {
|
||||||
StringFormatter::send(stream,F("<*\n"));
|
|
||||||
int used=0;
|
int used=0;
|
||||||
SLOTLOOP {
|
for (int reg = 0; reg <= highestUsedReg; reg++) {
|
||||||
if (slot->loco==0) break; // no more locos
|
if (speedTable[reg].loco>0) {
|
||||||
if (slot->loco>0) {
|
|
||||||
used ++;
|
used ++;
|
||||||
StringFormatter::send(stream,F("cab=%d, speed=%d, target=%d, momentum=%d/%d, block=%d\n"),
|
StringFormatter::send(stream,F("cab=%d, speed=%d, dir=%c \n"),
|
||||||
slot->loco, slot->speedCode, slot->targetSpeed,
|
speedTable[reg].loco, speedTable[reg].speedCode & 0x7f,(speedTable[reg].speedCode & 0x80) ? 'F':'R');
|
||||||
slot->momentumA, slot->momentumD, slot->blockOccupied);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StringFormatter::send(stream,F("Used=%d, max=%d, momentum=%d/%d *>\n"),
|
StringFormatter::send(stream,F("Used=%d, max=%d\n"),used,MAX_LOCOS);
|
||||||
used,MAX_LOCOS, DCC::defaultMomentumA,DCC::defaultMomentumD);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DCC::setLocoInBlock(int loco, uint16_t blockid, bool exclusive) {
|
|
||||||
// update block loco is in, tell exrail leaving old block, and entering new.
|
|
||||||
|
|
||||||
// NOTE: The loco table scanning is really inefficient and needs rewriting
|
|
||||||
// This was done once in the momentum poc.
|
|
||||||
#ifdef EXRAIL_ACTIVE
|
|
||||||
auto slot=lookupSpeedTable(loco,true);
|
|
||||||
if (!slot) return;
|
|
||||||
auto oldBlock=slot->blockOccupied;
|
|
||||||
if (oldBlock==blockid) return;
|
|
||||||
if (oldBlock) RMFT2::blockEvent(oldBlock,loco,false);
|
|
||||||
slot->blockOccupied=blockid;
|
|
||||||
if (blockid) RMFT2::blockEvent(blockid,loco,true);
|
|
||||||
|
|
||||||
if (exclusive) {
|
|
||||||
SLOTLOOP {
|
|
||||||
if (slot->loco==0) break; // no more locos
|
|
||||||
if (slot->loco>0) {
|
|
||||||
if (slot->loco!=loco && slot->blockOccupied==blockid) {
|
|
||||||
RMFT2::blockEvent(blockid,slot->loco,false);
|
|
||||||
slot->blockOccupied=0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void DCC::clearBlock(uint16_t blockid) {
|
|
||||||
// Railcom reports block empty... tell Exrail about all leavers
|
|
||||||
#ifdef EXRAIL_ACTIVE
|
|
||||||
SLOTLOOP {
|
|
||||||
if (slot->loco==0) break; // no more locos
|
|
||||||
if (slot->loco>0) {
|
|
||||||
if (slot->blockOccupied==blockid) {
|
|
||||||
RMFT2::blockEvent(blockid,slot->loco,false);
|
|
||||||
slot->blockOccupied=0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
32
DCC.h
32
DCC.h
@ -3,7 +3,7 @@
|
|||||||
* © 2021 Fred Decker
|
* © 2021 Fred Decker
|
||||||
* © 2021 Herb Morton
|
* © 2021 Herb Morton
|
||||||
* © 2020-2021 Harald Barth
|
* © 2020-2021 Harald Barth
|
||||||
* © 2020-2025 Chris Harlow
|
* © 2020-2021 Chris Harlow
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC API
|
* This file is part of Asbelos DCC API
|
||||||
@ -59,15 +59,11 @@ public:
|
|||||||
|
|
||||||
// Public DCC API functions
|
// Public DCC API functions
|
||||||
static void setThrottle(uint16_t cab, uint8_t tSpeed, bool tDirection);
|
static void setThrottle(uint16_t cab, uint8_t tSpeed, bool tDirection);
|
||||||
static void estopAll();
|
|
||||||
static int8_t getThrottleSpeed(int cab);
|
static int8_t getThrottleSpeed(int cab);
|
||||||
static uint8_t getThrottleSpeedByte(int cab);
|
static uint8_t getThrottleSpeedByte(int cab);
|
||||||
static uint8_t getLocoSpeedByte(int cab); // may lag throttle
|
|
||||||
static uint8_t getThrottleFrequency(int cab);
|
static uint8_t getThrottleFrequency(int cab);
|
||||||
static bool getThrottleDirection(int cab);
|
static bool getThrottleDirection(int cab);
|
||||||
static void writeCVByteMain(int cab, int cv, byte bValue);
|
static void writeCVByteMain(int cab, int cv, byte bValue);
|
||||||
static void readCVByteMain(int cab, int cv, ACK_CALLBACK callback);
|
|
||||||
|
|
||||||
static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue);
|
static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue);
|
||||||
static void setFunction(int cab, byte fByte, byte eByte);
|
static void setFunction(int cab, byte fByte, byte eByte);
|
||||||
static bool setFn(int cab, int16_t functionNumber, bool on);
|
static bool setFn(int cab, int16_t functionNumber, bool on);
|
||||||
@ -87,9 +83,7 @@ public:
|
|||||||
static void writeCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback);
|
static void writeCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback);
|
||||||
static void verifyCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback);
|
static void verifyCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback);
|
||||||
static void verifyCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback);
|
static void verifyCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback);
|
||||||
static bool setTime(uint16_t minutes,uint8_t speed, bool suddenChange);
|
|
||||||
static void setLocoInBlock(int loco, uint16_t blockid, bool exclusive);
|
|
||||||
static void clearBlock(uint16_t blockid);
|
|
||||||
static void getLocoId(ACK_CALLBACK callback);
|
static void getLocoId(ACK_CALLBACK callback);
|
||||||
static void setLocoId(int id,ACK_CALLBACK callback);
|
static void setLocoId(int id,ACK_CALLBACK callback);
|
||||||
static void setConsistId(int id,bool reverse,ACK_CALLBACK callback);
|
static void setConsistId(int id,bool reverse,ACK_CALLBACK callback);
|
||||||
@ -108,31 +102,20 @@ public:
|
|||||||
byte speedCode;
|
byte speedCode;
|
||||||
byte groupFlags;
|
byte groupFlags;
|
||||||
uint32_t functions;
|
uint32_t functions;
|
||||||
// Momentum management variables
|
|
||||||
uint32_t momentum_base; // millis() when speed modified under momentum
|
|
||||||
byte momentumA, momentumD;
|
|
||||||
byte targetSpeed; // speed set by throttle
|
|
||||||
uint16_t blockOccupied; // railcom detected block
|
|
||||||
};
|
};
|
||||||
static const int16_t MOMENTUM_FACTOR=7;
|
|
||||||
static const byte MOMENTUM_USE_DEFAULT=255;
|
|
||||||
static bool linearAcceleration;
|
|
||||||
static byte getMomentum(LOCO * slot);
|
|
||||||
|
|
||||||
static LOCO speedTable[MAX_LOCOS];
|
static LOCO speedTable[MAX_LOCOS];
|
||||||
static LOCO * lookupSpeedTable(int locoId, bool autoCreate=true);
|
static int lookupSpeedTable(int locoId, bool autoCreate=true);
|
||||||
static byte cv1(byte opcode, int cv);
|
static byte cv1(byte opcode, int cv);
|
||||||
static byte cv2(int cv);
|
static byte cv2(int cv);
|
||||||
static bool setMomentum(int locoId,int16_t accelerating, int16_t decelerating);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static byte loopStatus;
|
static byte loopStatus;
|
||||||
static byte defaultMomentumA; // Accelerating
|
|
||||||
static byte defaultMomentumD; // Accelerating
|
|
||||||
static void setThrottle2(uint16_t cab, uint8_t speedCode);
|
static void setThrottle2(uint16_t cab, uint8_t speedCode);
|
||||||
|
static void updateLocoReminder(int loco, byte speedCode);
|
||||||
static void setFunctionInternal(int cab, byte fByte, byte eByte, byte count);
|
static void setFunctionInternal(int cab, byte fByte, byte eByte, byte count);
|
||||||
static bool issueReminder(LOCO * slot);
|
static bool issueReminder(int reg);
|
||||||
static LOCO* nextLocoReminder;
|
static int lastLocoReminder;
|
||||||
|
static int highestUsedReg;
|
||||||
static FSH *shieldName;
|
static FSH *shieldName;
|
||||||
static byte globalSpeedsteps;
|
static byte globalSpeedsteps;
|
||||||
|
|
||||||
@ -143,7 +126,6 @@ private:
|
|||||||
// NMRA codes #
|
// NMRA codes #
|
||||||
static const byte SET_SPEED = 0x3f;
|
static const byte SET_SPEED = 0x3f;
|
||||||
static const byte WRITE_BYTE_MAIN = 0xEC;
|
static const byte WRITE_BYTE_MAIN = 0xEC;
|
||||||
static const byte READ_BYTE_MAIN = 0xE4;
|
|
||||||
static const byte WRITE_BIT_MAIN = 0xE8;
|
static const byte WRITE_BIT_MAIN = 0xE8;
|
||||||
static const byte WRITE_BYTE = 0x7C;
|
static const byte WRITE_BYTE = 0x7C;
|
||||||
static const byte VERIFY_BYTE = 0x74;
|
static const byte VERIFY_BYTE = 0x74;
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* © 2020-2023 Harald Barth
|
* © 2020-2023 Harald Barth
|
||||||
* © 2020-2021 M Steve Todd
|
* © 2020-2021 M Steve Todd
|
||||||
* © 2020-2021 Fred Decker
|
* © 2020-2021 Fred Decker
|
||||||
* © 2020-2025 Chris Harlow
|
* © 2020-2021 Chris Harlow
|
||||||
* © 2022 Colin Murdoch
|
* © 2022 Colin Murdoch
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
@ -64,12 +64,11 @@ Once a new OPCODE is decided upon, update this list.
|
|||||||
I, Turntable object command, control, and broadcast
|
I, Turntable object command, control, and broadcast
|
||||||
j, Throttle responses
|
j, Throttle responses
|
||||||
J, Throttle queries
|
J, Throttle queries
|
||||||
k, Block exit (Railcom)
|
k, Reserved for future use - Potentially Railcom
|
||||||
K, Block enter (Railcom)
|
K, Reserved for future use - Potentially Railcom
|
||||||
l, Loco speedbyte/function map broadcast
|
l, Loco speedbyte/function map broadcast
|
||||||
L, Reserved for LCC interface (implemented in EXRAIL)
|
L, Reserved for LCC interface (implemented in EXRAIL)
|
||||||
m, message to throttles (broadcast output)
|
m, message to throttles broadcast
|
||||||
m, set momentum
|
|
||||||
M, Write DCC packet
|
M, Write DCC packet
|
||||||
n, Reserved for SensorCam
|
n, Reserved for SensorCam
|
||||||
N, Reserved for Sensorcam
|
N, Reserved for Sensorcam
|
||||||
@ -317,9 +316,12 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
|||||||
int16_t direction;
|
int16_t direction;
|
||||||
|
|
||||||
if (params==1) { // <t cab> display state
|
if (params==1) { // <t cab> display state
|
||||||
if (p[0]<=0) break;
|
int16_t slot=DCC::lookupSpeedTable(p[0],false);
|
||||||
CommandDistributor::broadcastLoco(DCC::lookupSpeedTable(p[0],false));
|
if (slot>=0)
|
||||||
return;
|
CommandDistributor::broadcastLoco(slot);
|
||||||
|
else // send dummy state speed 0 fwd no functions.
|
||||||
|
StringFormatter::send(stream,F("<l %d -1 128 0>\n"),p[0]);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params == 4)
|
if (params == 4)
|
||||||
@ -481,16 +483,6 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
|||||||
DCC::writeCVByteMain(p[0], p[1], p[2]);
|
DCC::writeCVByteMain(p[0], p[1], p[2]);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
#ifdef HAS_ENOUGH_MEMORY
|
|
||||||
case 'r': // READ CV on MAIN <r CAB CV> Requires Railcom
|
|
||||||
if (params != 2)
|
|
||||||
break;
|
|
||||||
if (!DCCWaveform::isRailcom()) break;
|
|
||||||
if (!stashCallback(stream, p, ringStream)) break;
|
|
||||||
DCC::readCVByteMain(p[0], p[1],callback_r);
|
|
||||||
return;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
case 'b': // WRITE CV BIT ON MAIN <b CAB CV BIT VALUE>
|
case 'b': // WRITE CV BIT ON MAIN <b CAB CV BIT VALUE>
|
||||||
if (params != 4)
|
if (params != 4)
|
||||||
break;
|
break;
|
||||||
@ -498,19 +490,6 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
|||||||
return;
|
return;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
case 'm': // <m cabid momentum [braking]>
|
|
||||||
// <m LINEAR|POWER>
|
|
||||||
if (params==1) {
|
|
||||||
if (p[0]=="LINEAR"_hk) DCC::linearAcceleration=true;
|
|
||||||
else if (p[0]=="POWER"_hk) DCC::linearAcceleration=false;
|
|
||||||
else break;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (params<2 || params>3) break;
|
|
||||||
if (params==2) p[2]=p[1];
|
|
||||||
if (DCC::setMomentum(p[0],p[1],p[2])) return;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'M': // WRITE TRANSPARENT DCC PACKET MAIN <M REG X1 ... X9>
|
case 'M': // WRITE TRANSPARENT DCC PACKET MAIN <M REG X1 ... X9>
|
||||||
#ifndef DISABLE_PROG
|
#ifndef DISABLE_PROG
|
||||||
case 'P': // WRITE TRANSPARENT DCC PACKET PROG <P REG X1 ... X9>
|
case 'P': // WRITE TRANSPARENT DCC PACKET PROG <P REG X1 ... X9>
|
||||||
@ -659,7 +638,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
|||||||
}
|
}
|
||||||
|
|
||||||
case '!': // ESTOP ALL <!>
|
case '!': // ESTOP ALL <!>
|
||||||
DCC::estopAll(); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
|
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
|
||||||
return;
|
return;
|
||||||
|
|
||||||
#ifdef HAS_ENOUGH_MEMORY
|
#ifdef HAS_ENOUGH_MEMORY
|
||||||
@ -1233,10 +1212,6 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
#ifdef HAS_ENOUGH_MEMORY
|
#ifdef HAS_ENOUGH_MEMORY
|
||||||
case "RAILCOM"_hk: // <D RAILCOM ON/OFF>
|
|
||||||
Diag::RAILCOM = onOff;
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case "WIFI"_hk: // <D WIFI ON/OFF>
|
case "WIFI"_hk: // <D WIFI ON/OFF>
|
||||||
Diag::WIFI = onOff;
|
Diag::WIFI = onOff;
|
||||||
return true;
|
return true;
|
||||||
@ -1444,12 +1419,6 @@ void DCCEXParser::callback_R(int16_t result)
|
|||||||
commitAsyncReplyStream();
|
commitAsyncReplyStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCCEXParser::callback_r(int16_t result)
|
|
||||||
{
|
|
||||||
StringFormatter::send(getAsyncReplyStream(), F("<r %d %d %d >\n"), stashP[0], stashP[1], result);
|
|
||||||
commitAsyncReplyStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DCCEXParser::callback_Rloco(int16_t result) {
|
void DCCEXParser::callback_Rloco(int16_t result) {
|
||||||
const FSH * detail;
|
const FSH * detail;
|
||||||
if (result<=0) {
|
if (result<=0) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* © 2021 Mike S
|
* © 2021 Mike S
|
||||||
* © 2021 Fred Decker
|
* © 2021 Fred Decker
|
||||||
* © 2020-2025 Chris Harlow
|
* © 2020-2021 Chris Harlow
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC API
|
* This file is part of Asbelos DCC API
|
||||||
@ -68,8 +68,7 @@ struct DCCEXParser
|
|||||||
static void callback_W(int16_t result);
|
static void callback_W(int16_t result);
|
||||||
static void callback_W4(int16_t result);
|
static void callback_W4(int16_t result);
|
||||||
static void callback_B(int16_t result);
|
static void callback_B(int16_t result);
|
||||||
static void callback_R(int16_t result); // prog
|
static void callback_R(int16_t result);
|
||||||
static void callback_r(int16_t result); // main
|
|
||||||
static void callback_Rloco(int16_t result);
|
static void callback_Rloco(int16_t result);
|
||||||
static void callback_Wloco(int16_t result);
|
static void callback_Wloco(int16_t result);
|
||||||
static void callback_Wconsist(int16_t result);
|
static void callback_Wconsist(int16_t result);
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
* © 2021 Mike S
|
* © 2021 Mike S
|
||||||
* © 2021-2023 Harald Barth
|
* © 2021-2023 Harald Barth
|
||||||
* © 2021 Fred Decker
|
* © 2021 Fred Decker
|
||||||
* © 2021-2025 Chris Harlow
|
* © 2021 Chris Harlow
|
||||||
* © 2021 David Cutting
|
* © 2021 David Cutting
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
@ -57,59 +57,66 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
|||||||
TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1
|
TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1
|
||||||
TIMSK1 = _BV(TOIE1); // Enable Software interrupt
|
TIMSK1 = _BV(TOIE1); // Enable Software interrupt
|
||||||
interrupts();
|
interrupts();
|
||||||
//diagnostic pinMode(4,OUTPUT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DCCTimer::startRailcomTimer(byte brakePin) {
|
void DCCTimer::startRailcomTimer(byte brakePin) {
|
||||||
(void) brakePin; // Ignored... works on pin 9 only
|
|
||||||
// diagnostic digitalWrite(4,HIGH);
|
|
||||||
|
|
||||||
/* The Railcom timer is started in such a way that it
|
/* The Railcom timer is started in such a way that it
|
||||||
- First triggers 58+29 uS after the previous TIMER1 tick.
|
- First triggers 28uS after the last TIMER1 tick.
|
||||||
This provides an accurate offset (in High Accuracy mode)
|
This provides an accurate offset (in High Accuracy mode)
|
||||||
for the start of the Railcom cutout.
|
for the start of the Railcom cutout.
|
||||||
- Sets the Railcom pin high at first tick and subsequent ticks
|
- Sets the Railcom pin high at first tick,
|
||||||
until its reset to setting pin 9 low at next tick.
|
because its been setup with 100% PWM duty cycle.
|
||||||
|
|
||||||
- Cycles at 436uS so the second tick is the
|
- Cycles at 436uS so the second tick is the
|
||||||
correct distance from the cutout.
|
correct distance from the cutout.
|
||||||
|
|
||||||
- Waveform code is responsible for resetting
|
- Waveform code is responsible for altering the PWM
|
||||||
any time between the first and second tick.
|
duty cycle to 0% any time between the first and last tick.
|
||||||
(there will be 7 DCC timer1 ticks in which to do this.)
|
(there will be 7 DCC timer1 ticks in which to do this.)
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
(void) brakePin; // Ignored... works on pin 9 only
|
||||||
const int cutoutDuration = 430; // Desired interval in microseconds
|
const int cutoutDuration = 430; // Desired interval in microseconds
|
||||||
const int cycle=cutoutDuration/2;
|
|
||||||
|
|
||||||
const byte RailcomFudge0=58+58+29;
|
// Set up Timer2 for CTC mode (Clear Timer on Compare Match)
|
||||||
|
TCCR2A = 0; // Clear Timer2 control register A
|
||||||
|
TCCR2B = 0; // Clear Timer2 control register B
|
||||||
|
TCNT2 = 0; // Initialize Timer2 counter value to 0
|
||||||
|
// Configure Phase and Frequency Correct PWM mode
|
||||||
|
TCCR2A = (1 << COM2B1); // enable pwm on pin 9
|
||||||
|
TCCR2A |= (1 << WGM20);
|
||||||
|
|
||||||
|
|
||||||
|
// Set Timer 2 prescaler to 32
|
||||||
|
TCCR2B = (1 << CS21) | (1 << CS20); // 32 prescaler
|
||||||
|
|
||||||
|
// Set the compare match value for desired interval
|
||||||
|
OCR2A = (F_CPU / 1000000) * cutoutDuration / 64 - 1;
|
||||||
|
|
||||||
|
// Calculate the compare match value for desired duty cycle
|
||||||
|
OCR2B = OCR2A+1; // set duty cycle to 100%= OCR2A)
|
||||||
|
|
||||||
// Set Timer2 to CTC mode with set on compare match
|
|
||||||
TCCR2A = (1 << WGM21) | (1 << COM2B0) | (1 << COM2B1);
|
|
||||||
// Prescaler of 32
|
|
||||||
TCCR2B = (1 << CS21) | (1 << CS20);
|
|
||||||
OCR2A = cycle-1; // Compare match value for 430 uS
|
|
||||||
// Enable Timer2 output on pin 9 (OC2B)
|
// Enable Timer2 output on pin 9 (OC2B)
|
||||||
DDRB |= (1 << DDB1);
|
DDRB |= (1 << DDB1);
|
||||||
|
// TODO Fudge TCNT2 to sync with last tcnt1 tick + 28uS
|
||||||
// RailcomFudge2 is the expected time from idealised
|
|
||||||
// setup call (at previous DCC timer interrupt) to the cutout.
|
|
||||||
// This value should be reduced to reflect the Timer1 value
|
|
||||||
// measuring the time since the previous hardware interrupt
|
|
||||||
byte tcfudge=TCNT1/16;
|
|
||||||
TCNT2=cycle-RailcomFudge0/2+tcfudge/2;
|
|
||||||
|
|
||||||
|
|
||||||
// Previous TIMER1 Tick was at rising end-of-packet bit
|
// Previous TIMER1 Tick was at rising end-of-packet bit
|
||||||
// Cutout starts half way through first preamble
|
// Cutout starts half way through first preamble
|
||||||
// that is 2.5 * 58uS later.
|
// that is 2.5 * 58uS later.
|
||||||
}
|
// TCNT1 ticks 8 times / microsecond
|
||||||
|
// auto microsendsToFirstRailcomTick=(58+58+29)-(TCNT1/8);
|
||||||
|
// set the railcom timer counter allowing for phase-correct
|
||||||
|
|
||||||
|
// CHris's NOTE:
|
||||||
|
// I dont kniow quite how this calculation works out but
|
||||||
|
// it does seems to get a good answer.
|
||||||
|
|
||||||
|
TCNT2=193 + (ICR1 - TCNT1)/8;
|
||||||
|
}
|
||||||
|
|
||||||
void DCCTimer::ackRailcomTimer() {
|
void DCCTimer::ackRailcomTimer() {
|
||||||
// Change Timer2 to CTC mode with RESET pin 9 on next compare match
|
OCR2B= 0x00; // brake pin pwm duty cycle 0 at next tick
|
||||||
TCCR2A = (1 << WGM21) | (1 << COM2B1);
|
|
||||||
// diagnostic digitalWrite(4,LOW);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
150
DCCWaveform.cpp
150
DCCWaveform.cpp
@ -24,13 +24,14 @@
|
|||||||
#ifndef ARDUINO_ARCH_ESP32
|
#ifndef ARDUINO_ARCH_ESP32
|
||||||
// This code is replaced entirely on an ESP32
|
// This code is replaced entirely on an ESP32
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
#include "DCCWaveform.h"
|
#include "DCCWaveform.h"
|
||||||
#include "TrackManager.h"
|
#include "TrackManager.h"
|
||||||
#include "DCCTimer.h"
|
#include "DCCTimer.h"
|
||||||
#include "DCCACK.h"
|
#include "DCCACK.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
|
|
||||||
bool DCCWaveform::cutoutNextTime=false;
|
|
||||||
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);
|
||||||
|
|
||||||
@ -70,18 +71,9 @@ void DCCWaveform::loop() {
|
|||||||
|
|
||||||
#pragma GCC push_options
|
#pragma GCC push_options
|
||||||
#pragma GCC optimize ("-O3")
|
#pragma GCC optimize ("-O3")
|
||||||
|
|
||||||
void DCCWaveform::interruptHandler() {
|
void DCCWaveform::interruptHandler() {
|
||||||
// call the timer edge sensitive actions for progtrack and maintrack
|
// call the timer edge sensitive actions for progtrack and maintrack
|
||||||
// member functions would be cleaner but have more overhead
|
// member functions would be cleaner but have more overhead
|
||||||
#if defined(HAS_ENOUGH_MEMORY)
|
|
||||||
if (cutoutNextTime) {
|
|
||||||
cutoutNextTime=false;
|
|
||||||
railcomSampleWindow=false; // about to cutout, stop reading railcom data.
|
|
||||||
railcomCutoutCounter++;
|
|
||||||
DCCTimer::startRailcomTimer(9);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
byte sigMain=signalTransform[mainTrack.state];
|
byte sigMain=signalTransform[mainTrack.state];
|
||||||
byte sigProg=TrackManager::progTrackSyncMain? sigMain : signalTransform[progTrack.state];
|
byte sigProg=TrackManager::progTrackSyncMain? sigMain : signalTransform[progTrack.state];
|
||||||
|
|
||||||
@ -124,23 +116,18 @@ DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
|
|||||||
bits_sent = 0;
|
bits_sent = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DCCWaveform::railcomPossible=false; // High accuracy only
|
|
||||||
volatile bool DCCWaveform::railcomActive=false; // switched on by user
|
volatile bool DCCWaveform::railcomActive=false; // switched on by user
|
||||||
volatile bool DCCWaveform::railcomDebug=false; // switched on by user
|
volatile bool DCCWaveform::railcomDebug=false; // switched on by user
|
||||||
volatile bool DCCWaveform::railcomSampleWindow=false; // true during packet transmit
|
|
||||||
volatile byte DCCWaveform::railcomCutoutCounter=0; // cyclic cutout
|
|
||||||
volatile byte DCCWaveform::railcomLastAddressHigh=0;
|
|
||||||
volatile byte DCCWaveform::railcomLastAddressLow=0;
|
|
||||||
|
|
||||||
bool DCCWaveform::setRailcom(bool on, bool debug) {
|
bool DCCWaveform::setRailcom(bool on, bool debug) {
|
||||||
if (on && railcomPossible) {
|
if (on) {
|
||||||
|
// TODO check possible
|
||||||
railcomActive=true;
|
railcomActive=true;
|
||||||
railcomDebug=debug;
|
railcomDebug=debug;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
railcomActive=false;
|
railcomActive=false;
|
||||||
railcomDebug=false;
|
railcomDebug=false;
|
||||||
railcomSampleWindow=false;
|
|
||||||
}
|
}
|
||||||
return railcomActive;
|
return railcomActive;
|
||||||
}
|
}
|
||||||
@ -153,37 +140,14 @@ void DCCWaveform::interrupt2() {
|
|||||||
// or WAVE_HIGH_0 for a 0 bit.
|
// or WAVE_HIGH_0 for a 0 bit.
|
||||||
if (remainingPreambles > 0 ) {
|
if (remainingPreambles > 0 ) {
|
||||||
state=WAVE_MID_1; // switch state to trigger LOW on next interrupt
|
state=WAVE_MID_1; // switch state to trigger LOW on next interrupt
|
||||||
|
|
||||||
remainingPreambles--;
|
remainingPreambles--;
|
||||||
|
|
||||||
// As we get to the end of the preambles, open the reminder window.
|
// As we get to the end of the preambles, open the reminder window.
|
||||||
// This delays any reminder insertion until the last moment so
|
// This delays any reminder insertion until the last moment so
|
||||||
// that the reminder doesn't block a more urgent packet.
|
// that the reminder doesn't block a more urgent packet.
|
||||||
reminderWindowOpen=transmitRepeats==0 && remainingPreambles<10 && remainingPreambles>1;
|
reminderWindowOpen=transmitRepeats==0 && remainingPreambles<4 && remainingPreambles>1;
|
||||||
if (remainingPreambles==1)
|
if (remainingPreambles==1) promotePendingPacket();
|
||||||
promotePendingPacket();
|
else if (remainingPreambles==10 && isMainTrack && railcomActive) DCCTimer::ackRailcomTimer();
|
||||||
|
|
||||||
#if defined(HAS_ENOUGH_MEMORY)
|
|
||||||
else if (isMainTrack && railcomActive) {
|
|
||||||
if (remainingPreambles==(requiredPreambles-1)) {
|
|
||||||
// First look if we need to start a railcom cutout on next interrupt
|
|
||||||
cutoutNextTime= true;
|
|
||||||
} else if (remainingPreambles==(requiredPreambles-12)) {
|
|
||||||
// cutout has ended so its now possible to poll the railcom detectors
|
|
||||||
// requiredPreambles is one higher that preamble length so
|
|
||||||
// if preamble length is 16 then this evaluates to 5
|
|
||||||
// Remember address bytes of last sent packet so that Railcom can
|
|
||||||
// work out where the channel2 data came from.
|
|
||||||
railcomLastAddressHigh=transmitPacket[0];
|
|
||||||
railcomLastAddressLow =transmitPacket[1];
|
|
||||||
railcomSampleWindow=true;
|
|
||||||
} else if (remainingPreambles==(requiredPreambles-3)) {
|
|
||||||
// cutout can be ended when read
|
|
||||||
// see above for requiredPreambles
|
|
||||||
DCCTimer::ackRailcomTimer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
// Update free memory diagnostic as we don't have anything else to do this time.
|
// Update free memory diagnostic as we don't have anything else to do this time.
|
||||||
// Allow for checkAck and its called functions using 22 bytes more.
|
// Allow for checkAck and its called functions using 22 bytes more.
|
||||||
else DCCTimer::updateMinimumFreeMemoryISR(22);
|
else DCCTimer::updateMinimumFreeMemoryISR(22);
|
||||||
@ -207,7 +171,13 @@ void DCCWaveform::interrupt2() {
|
|||||||
bytes_sent = 0;
|
bytes_sent = 0;
|
||||||
// preamble for next packet will start...
|
// preamble for next packet will start...
|
||||||
remainingPreambles = requiredPreambles;
|
remainingPreambles = requiredPreambles;
|
||||||
}
|
|
||||||
|
// set the railcom coundown to trigger half way
|
||||||
|
// through the first preamble bit.
|
||||||
|
// Note.. we are still sending the last packet bit
|
||||||
|
// and we then have to allow for the packet end bit
|
||||||
|
if (isMainTrack && railcomActive) DCCTimer::startRailcomTimer(9);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#pragma GCC pop_options
|
#pragma GCC pop_options
|
||||||
@ -260,7 +230,7 @@ void DCCWaveform::promotePendingPacket() {
|
|||||||
// Fortunately reset and idle packets are the same length
|
// Fortunately reset and idle packets are the same length
|
||||||
// Note: If railcomDebug is on, then we send resets to the main
|
// Note: If railcomDebug is on, then we send resets to the main
|
||||||
// track instead of idles. This means that all data will be zeros
|
// track instead of idles. This means that all data will be zeros
|
||||||
// and only the presets will be ones, making it much
|
// and only the porersets will be ones, making it much
|
||||||
// easier to read on a logic analyser.
|
// easier to read on a logic analyser.
|
||||||
memcpy( transmitPacket, (isMainTrack && (!railcomDebug)) ? idlePacket : resetPacket, sizeof(idlePacket));
|
memcpy( transmitPacket, (isMainTrack && (!railcomDebug)) ? idlePacket : resetPacket, sizeof(idlePacket));
|
||||||
transmitLength = sizeof(idlePacket);
|
transmitLength = sizeof(idlePacket);
|
||||||
@ -268,3 +238,93 @@ void DCCWaveform::promotePendingPacket() {
|
|||||||
if (getResets() < 250) sentResetsSincePacket++; // only place to increment (private!)
|
if (getResets() < 250) sentResetsSincePacket++; // only place to increment (private!)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
#include "DCCWaveform.h"
|
||||||
|
#include "DCCACK.h"
|
||||||
|
|
||||||
|
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
|
||||||
|
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
||||||
|
RMTChannel *DCCWaveform::rmtMainChannel = NULL;
|
||||||
|
RMTChannel *DCCWaveform::rmtProgChannel = NULL;
|
||||||
|
|
||||||
|
DCCWaveform::DCCWaveform(byte preambleBits, bool isMain) {
|
||||||
|
isMainTrack = isMain;
|
||||||
|
requiredPreambles = preambleBits;
|
||||||
|
}
|
||||||
|
void DCCWaveform::begin() {
|
||||||
|
for(const auto& md: TrackManager::getMainDrivers()) {
|
||||||
|
pinpair p = md->getSignalPin();
|
||||||
|
if(rmtMainChannel) {
|
||||||
|
//DIAG(F("added pins %d %d to MAIN channel"), p.pin, p.invpin);
|
||||||
|
rmtMainChannel->addPin(p); // add pin to existing main channel
|
||||||
|
} else {
|
||||||
|
//DIAG(F("new MAIN channel with pins %d %d"), p.pin, p.invpin);
|
||||||
|
rmtMainChannel = new RMTChannel(p, true); /* create new main channel */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MotorDriver *md = TrackManager::getProgDriver();
|
||||||
|
if (md) {
|
||||||
|
pinpair p = md->getSignalPin();
|
||||||
|
if (rmtProgChannel) {
|
||||||
|
//DIAG(F("added pins %d %d to PROG channel"), p.pin, p.invpin);
|
||||||
|
rmtProgChannel->addPin(p); // add pin to existing prog channel
|
||||||
|
} else {
|
||||||
|
//DIAG(F("new PROGchannel with pins %d %d"), p.pin, p.invpin);
|
||||||
|
rmtProgChannel = new RMTChannel(p, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
|
||||||
|
if (byteCount > MAX_PACKET_SIZE) return; // allow for chksum
|
||||||
|
|
||||||
|
byte checksum = 0;
|
||||||
|
for (byte b = 0; b < byteCount; b++) {
|
||||||
|
checksum ^= buffer[b];
|
||||||
|
pendingPacket[b] = buffer[b];
|
||||||
|
}
|
||||||
|
// buffer is MAX_PACKET_SIZE but pendingPacket is one bigger
|
||||||
|
pendingPacket[byteCount] = checksum;
|
||||||
|
pendingLength = byteCount + 1;
|
||||||
|
pendingRepeats = repeats;
|
||||||
|
// DIAG repeated commands (accesories)
|
||||||
|
// if (pendingRepeats > 0)
|
||||||
|
// DIAG(F("Repeats=%d on %s track"), pendingRepeats, isMainTrack ? "MAIN" : "PROG");
|
||||||
|
// The resets will be zero not only now but as well repeats packets into the future
|
||||||
|
clearResets(repeats+1);
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
do {
|
||||||
|
if(isMainTrack) {
|
||||||
|
if (rmtMainChannel != NULL)
|
||||||
|
ret = rmtMainChannel->RMTfillData(pendingPacket, pendingLength, pendingRepeats);
|
||||||
|
} else {
|
||||||
|
if (rmtProgChannel != NULL)
|
||||||
|
ret = rmtProgChannel->RMTfillData(pendingPacket, pendingLength, pendingRepeats);
|
||||||
|
}
|
||||||
|
} while(ret > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DCCWaveform::isReminderWindowOpen() {
|
||||||
|
if(isMainTrack) {
|
||||||
|
if (rmtMainChannel == NULL)
|
||||||
|
return false;
|
||||||
|
return !rmtMainChannel->busy();
|
||||||
|
} else {
|
||||||
|
if (rmtProgChannel == NULL)
|
||||||
|
return false;
|
||||||
|
return !rmtProgChannel->busy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void IRAM_ATTR DCCWaveform::loop() {
|
||||||
|
DCCACK::checkAck(progTrack.getResets());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DCCWaveform::setRailcom(bool on, bool debug) {
|
||||||
|
// TODO... ESP32 railcom waveform
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
* © 2021 Mike S
|
* © 2021 Mike S
|
||||||
* © 2021 Fred Decker
|
* © 2021 Fred Decker
|
||||||
* © 2020-2024 Harald Barth
|
* © 2020-2024 Harald Barth
|
||||||
* © 2020-2025 Chris Harlow
|
* © 2020-2021 Chris Harlow
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
@ -23,8 +23,11 @@
|
|||||||
*/
|
*/
|
||||||
#ifndef DCCWaveform_h
|
#ifndef DCCWaveform_h
|
||||||
#define DCCWaveform_h
|
#define DCCWaveform_h
|
||||||
|
|
||||||
|
#include "MotorDriver.h"
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
#include "DCCRMT.h"
|
#include "DCCRMT.h"
|
||||||
|
#include "TrackManager.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
@ -83,29 +86,7 @@ class DCCWaveform {
|
|||||||
bool isReminderWindowOpen();
|
bool isReminderWindowOpen();
|
||||||
void promotePendingPacket();
|
void promotePendingPacket();
|
||||||
static bool setRailcom(bool on, bool debug);
|
static bool setRailcom(bool on, bool debug);
|
||||||
inline static bool isRailcom() {
|
static bool isRailcom() {return railcomActive;}
|
||||||
return railcomActive;
|
|
||||||
};
|
|
||||||
inline static byte getRailcomCutoutCounter() {
|
|
||||||
return railcomCutoutCounter;
|
|
||||||
};
|
|
||||||
inline static bool isRailcomSampleWindow() {
|
|
||||||
return railcomSampleWindow;
|
|
||||||
};
|
|
||||||
inline static bool isRailcomPossible() {
|
|
||||||
return railcomPossible;
|
|
||||||
};
|
|
||||||
inline static void setRailcomPossible(bool yes) {
|
|
||||||
railcomPossible=yes;
|
|
||||||
if (!yes) setRailcom(false,false);
|
|
||||||
};
|
|
||||||
inline static uint16_t getRailcomLastLocoAddress() {
|
|
||||||
// first 2 bits 00=short loco, 11=long loco , 01/10 = accessory
|
|
||||||
byte addressType=railcomLastAddressHigh & 0xC0;
|
|
||||||
if (addressType==0xC0) return ((railcomLastAddressHigh & 0x3f)<<8) | railcomLastAddressLow;
|
|
||||||
if (addressType==0x00) return railcomLastAddressHigh & 0x3F;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#ifndef ARDUINO_ARCH_ESP32
|
#ifndef ARDUINO_ARCH_ESP32
|
||||||
@ -131,13 +112,9 @@ class DCCWaveform {
|
|||||||
byte pendingPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
|
byte pendingPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
|
||||||
byte pendingLength;
|
byte pendingLength;
|
||||||
byte pendingRepeats;
|
byte pendingRepeats;
|
||||||
static bool railcomPossible; // High accuracy mode only
|
|
||||||
static volatile bool railcomActive; // switched on by user
|
static volatile bool railcomActive; // switched on by user
|
||||||
static volatile bool railcomDebug; // switched on by user
|
static volatile bool railcomDebug; // switched on by user
|
||||||
static volatile bool railcomSampleWindow; // when safe to sample
|
|
||||||
static volatile byte railcomCutoutCounter; // incremented for each cutout
|
|
||||||
static volatile byte railcomLastAddressHigh,railcomLastAddressLow;
|
|
||||||
static bool cutoutNextTime; // railcom
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
static RMTChannel *rmtMainChannel;
|
static RMTChannel *rmtMainChannel;
|
||||||
static RMTChannel *rmtProgChannel;
|
static RMTChannel *rmtProgChannel;
|
||||||
|
@ -1,122 +0,0 @@
|
|||||||
/*
|
|
||||||
* © 2021 Neil McKechnie
|
|
||||||
* © 2021 Mike S
|
|
||||||
* © 2021 Fred Decker
|
|
||||||
* © 2020-2022 Harald Barth
|
|
||||||
* © 2020-2021 Chris Harlow
|
|
||||||
* 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/>.
|
|
||||||
*/
|
|
||||||
// This code is ESP32 ONLY.
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
|
||||||
#include "DCCWaveform.h"
|
|
||||||
#include "DCCACK.h"
|
|
||||||
#include "TrackManager.h"
|
|
||||||
|
|
||||||
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
|
|
||||||
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
|
||||||
RMTChannel *DCCWaveform::rmtMainChannel = NULL;
|
|
||||||
RMTChannel *DCCWaveform::rmtProgChannel = NULL;
|
|
||||||
|
|
||||||
bool DCCWaveform::railcomPossible=false; // High accuracy only
|
|
||||||
volatile bool DCCWaveform::railcomActive=false; // switched on by user
|
|
||||||
volatile bool DCCWaveform::railcomDebug=false; // switched on by user
|
|
||||||
volatile bool DCCWaveform::railcomSampleWindow=false; // true during packet transmit
|
|
||||||
volatile byte DCCWaveform::railcomCutoutCounter=0; // cyclic cutout
|
|
||||||
volatile byte DCCWaveform::railcomLastAddressHigh=0;
|
|
||||||
volatile byte DCCWaveform::railcomLastAddressLow=0;
|
|
||||||
|
|
||||||
DCCWaveform::DCCWaveform(byte preambleBits, bool isMain) {
|
|
||||||
isMainTrack = isMain;
|
|
||||||
requiredPreambles = preambleBits;
|
|
||||||
}
|
|
||||||
void DCCWaveform::begin() {
|
|
||||||
for(const auto& md: TrackManager::getMainDrivers()) {
|
|
||||||
pinpair p = md->getSignalPin();
|
|
||||||
if(rmtMainChannel) {
|
|
||||||
//DIAG(F("added pins %d %d to MAIN channel"), p.pin, p.invpin);
|
|
||||||
rmtMainChannel->addPin(p); // add pin to existing main channel
|
|
||||||
} else {
|
|
||||||
//DIAG(F("new MAIN channel with pins %d %d"), p.pin, p.invpin);
|
|
||||||
rmtMainChannel = new RMTChannel(p, true); /* create new main channel */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MotorDriver *md = TrackManager::getProgDriver();
|
|
||||||
if (md) {
|
|
||||||
pinpair p = md->getSignalPin();
|
|
||||||
if (rmtProgChannel) {
|
|
||||||
//DIAG(F("added pins %d %d to PROG channel"), p.pin, p.invpin);
|
|
||||||
rmtProgChannel->addPin(p); // add pin to existing prog channel
|
|
||||||
} else {
|
|
||||||
//DIAG(F("new PROGchannel with pins %d %d"), p.pin, p.invpin);
|
|
||||||
rmtProgChannel = new RMTChannel(p, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
|
|
||||||
if (byteCount > MAX_PACKET_SIZE) return; // allow for chksum
|
|
||||||
|
|
||||||
byte checksum = 0;
|
|
||||||
for (byte b = 0; b < byteCount; b++) {
|
|
||||||
checksum ^= buffer[b];
|
|
||||||
pendingPacket[b] = buffer[b];
|
|
||||||
}
|
|
||||||
// buffer is MAX_PACKET_SIZE but pendingPacket is one bigger
|
|
||||||
pendingPacket[byteCount] = checksum;
|
|
||||||
pendingLength = byteCount + 1;
|
|
||||||
pendingRepeats = repeats;
|
|
||||||
// DIAG repeated commands (accesories)
|
|
||||||
// if (pendingRepeats > 0)
|
|
||||||
// DIAG(F("Repeats=%d on %s track"), pendingRepeats, isMainTrack ? "MAIN" : "PROG");
|
|
||||||
// The resets will be zero not only now but as well repeats packets into the future
|
|
||||||
clearResets(repeats+1);
|
|
||||||
{
|
|
||||||
int ret = 0;
|
|
||||||
do {
|
|
||||||
if(isMainTrack) {
|
|
||||||
if (rmtMainChannel != NULL)
|
|
||||||
ret = rmtMainChannel->RMTfillData(pendingPacket, pendingLength, pendingRepeats);
|
|
||||||
} else {
|
|
||||||
if (rmtProgChannel != NULL)
|
|
||||||
ret = rmtProgChannel->RMTfillData(pendingPacket, pendingLength, pendingRepeats);
|
|
||||||
}
|
|
||||||
} while(ret > 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DCCWaveform::isReminderWindowOpen() {
|
|
||||||
if(isMainTrack) {
|
|
||||||
if (rmtMainChannel == NULL)
|
|
||||||
return false;
|
|
||||||
return !rmtMainChannel->busy();
|
|
||||||
} else {
|
|
||||||
if (rmtProgChannel == NULL)
|
|
||||||
return false;
|
|
||||||
return !rmtProgChannel->busy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void IRAM_ATTR DCCWaveform::loop() {
|
|
||||||
DCCACK::checkAck(progTrack.getResets());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DCCWaveform::setRailcom(bool on, bool debug) {
|
|
||||||
// TODO... ESP32 railcom waveform
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
20
EXRAIL.h
20
EXRAIL.h
@ -1,23 +1,3 @@
|
|||||||
/*
|
|
||||||
* © 2021 Fred Decker
|
|
||||||
* 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef EXRAIL_H
|
#ifndef EXRAIL_H
|
||||||
#define EXRAIL_H
|
#define EXRAIL_H
|
||||||
|
|
||||||
|
120
EXRAIL2.cpp
120
EXRAIL2.cpp
@ -2,9 +2,8 @@
|
|||||||
* © 2024 Paul M. Antoine
|
* © 2024 Paul M. Antoine
|
||||||
* © 2021 Neil McKechnie
|
* © 2021 Neil McKechnie
|
||||||
* © 2021-2023 Harald Barth
|
* © 2021-2023 Harald Barth
|
||||||
* © 2020-2025 Chris Harlow
|
* © 2020-2023 Chris Harlow
|
||||||
* © 2022-2023 Colin Murdoch
|
* © 2022-2023 Colin Murdoch
|
||||||
* © 2025 Morten Nielsen
|
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
@ -88,8 +87,6 @@ LookList * RMFT2::onClockLookup=NULL;
|
|||||||
LookList * RMFT2::onRotateLookup=NULL;
|
LookList * RMFT2::onRotateLookup=NULL;
|
||||||
#endif
|
#endif
|
||||||
LookList * RMFT2::onOverloadLookup=NULL;
|
LookList * RMFT2::onOverloadLookup=NULL;
|
||||||
LookList * RMFT2::onBlockEnterLookup=NULL;
|
|
||||||
LookList * RMFT2::onBlockExitLookup=NULL;
|
|
||||||
byte * RMFT2::routeStateArray=nullptr;
|
byte * RMFT2::routeStateArray=nullptr;
|
||||||
const FSH * * RMFT2::routeCaptionArray=nullptr;
|
const FSH * * RMFT2::routeCaptionArray=nullptr;
|
||||||
int16_t * RMFT2::stashArray=nullptr;
|
int16_t * RMFT2::stashArray=nullptr;
|
||||||
@ -134,11 +131,11 @@ int16_t LookList::find(int16_t value) {
|
|||||||
void LookList::chain(LookList * chain) {
|
void LookList::chain(LookList * chain) {
|
||||||
m_chain=chain;
|
m_chain=chain;
|
||||||
}
|
}
|
||||||
void LookList::handleEvent(const FSH* reason,int16_t id, int16_t loco) {
|
void LookList::handleEvent(const FSH* reason,int16_t id) {
|
||||||
// New feature... create multiple ONhandlers
|
// New feature... create multiple ONhandlers
|
||||||
for (int i=0;i<m_size;i++)
|
for (int i=0;i<m_size;i++)
|
||||||
if (m_lookupArray[i]==id)
|
if (m_lookupArray[i]==id)
|
||||||
RMFT2::startNonRecursiveTask(reason,id,m_resultArray[i],loco);
|
RMFT2::startNonRecursiveTask(reason,id,m_resultArray[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -206,12 +203,6 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
|||||||
onRotateLookup=LookListLoader(OPCODE_ONROTATE);
|
onRotateLookup=LookListLoader(OPCODE_ONROTATE);
|
||||||
#endif
|
#endif
|
||||||
onOverloadLookup=LookListLoader(OPCODE_ONOVERLOAD);
|
onOverloadLookup=LookListLoader(OPCODE_ONOVERLOAD);
|
||||||
|
|
||||||
if (compileFeatures & FEATURE_BLOCK) {
|
|
||||||
onBlockEnterLookup=LookListLoader(OPCODE_ONBLOCKENTER);
|
|
||||||
onBlockExitLookup=LookListLoader(OPCODE_ONBLOCKEXIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
// onLCCLookup is not the same so not loaded here.
|
// onLCCLookup is not the same so not loaded here.
|
||||||
|
|
||||||
// Second pass startup, define any turnouts or servos, set signals red
|
// Second pass startup, define any turnouts or servos, set signals red
|
||||||
@ -388,7 +379,7 @@ char RMFT2::getRouteType(int16_t id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
RMFT2::RMFT2(int progCtr, int16_t _loco) {
|
RMFT2::RMFT2(int progCtr) {
|
||||||
progCounter=progCtr;
|
progCounter=progCtr;
|
||||||
|
|
||||||
// get an unused task id from the flags table
|
// get an unused task id from the flags table
|
||||||
@ -401,7 +392,9 @@ RMFT2::RMFT2(int progCtr, int16_t _loco) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
delayTime=0;
|
delayTime=0;
|
||||||
loco=_loco;
|
loco=0;
|
||||||
|
speedo=0;
|
||||||
|
forward=true;
|
||||||
invert=false;
|
invert=false;
|
||||||
blinkState=not_blink_task;
|
blinkState=not_blink_task;
|
||||||
stackDepth=0;
|
stackDepth=0;
|
||||||
@ -419,10 +412,7 @@ RMFT2::RMFT2(int progCtr, int16_t _loco) {
|
|||||||
|
|
||||||
|
|
||||||
RMFT2::~RMFT2() {
|
RMFT2::~RMFT2() {
|
||||||
// estop my loco if this is not an ONevent
|
driveLoco(1); // ESTOP my loco if any
|
||||||
// (prevents DONE stopping loco at the end of an
|
|
||||||
// ONBLOCKENTER or ONBLOCKEXIT )
|
|
||||||
if (loco>0 && this->onEventStartPosition==-1) DCC::setThrottle(loco,1,DCC::getThrottleDirection(loco));
|
|
||||||
setFlag(taskId,0,TASK_FLAG); // we are no longer using this id
|
setFlag(taskId,0,TASK_FLAG); // we are no longer using this id
|
||||||
if (next==this)
|
if (next==this)
|
||||||
loopTask=NULL;
|
loopTask=NULL;
|
||||||
@ -438,9 +428,23 @@ RMFT2::~RMFT2() {
|
|||||||
void RMFT2::createNewTask(int route, uint16_t cab) {
|
void RMFT2::createNewTask(int route, uint16_t cab) {
|
||||||
int pc=routeLookup->find(route);
|
int pc=routeLookup->find(route);
|
||||||
if (pc<0) return;
|
if (pc<0) return;
|
||||||
new RMFT2(pc,cab);
|
RMFT2* task=new RMFT2(pc);
|
||||||
|
task->loco=cab;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RMFT2::driveLoco(byte speed) {
|
||||||
|
if (loco<=0) return; // Prevent broadcast!
|
||||||
|
//if (diag) DIAG(F("EXRAIL drive %d %d %d"),loco,speed,forward^invert);
|
||||||
|
/* TODO.....
|
||||||
|
power on appropriate track if DC or main if dcc
|
||||||
|
if (TrackManager::getMainPowerMode()==POWERMODE::OFF) {
|
||||||
|
TrackManager::setMainPower(POWERMODE::ON);
|
||||||
|
}
|
||||||
|
**********/
|
||||||
|
|
||||||
|
DCC::setThrottle(loco,speed, forward^invert);
|
||||||
|
speedo=speed;
|
||||||
|
}
|
||||||
|
|
||||||
bool RMFT2::readSensor(uint16_t sensorId) {
|
bool RMFT2::readSensor(uint16_t sensorId) {
|
||||||
// Exrail operands are unsigned but we need the signed version as inserted by the macros.
|
// Exrail operands are unsigned but we need the signed version as inserted by the macros.
|
||||||
@ -495,7 +499,7 @@ bool RMFT2::skipIfBlock() {
|
|||||||
if (cv & LONG_ADDR_MARKER) { // maker bit indicates long addr
|
if (cv & LONG_ADDR_MARKER) { // maker bit indicates long addr
|
||||||
progtrackLocoId = cv ^ LONG_ADDR_MARKER; // remove marker bit to get real long addr
|
progtrackLocoId = cv ^ LONG_ADDR_MARKER; // remove marker bit to get real long addr
|
||||||
if (progtrackLocoId <= HIGHEST_SHORT_ADDR ) { // out of range for long addr
|
if (progtrackLocoId <= HIGHEST_SHORT_ADDR ) { // out of range for long addr
|
||||||
DIAG(F("Long addr %d <= %d unsupported\n"), progtrackLocoId, HIGHEST_SHORT_ADDR);
|
DIAG(F("Long addr %d <= %d unsupported"), progtrackLocoId, HIGHEST_SHORT_ADDR);
|
||||||
progtrackLocoId = -1;
|
progtrackLocoId = -1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -503,15 +507,6 @@ bool RMFT2::skipIfBlock() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RMFT2::pause() {
|
|
||||||
if (loco)
|
|
||||||
pauseSpeed=DCC::getThrottleSpeedByte(loco);
|
|
||||||
}
|
|
||||||
void RMFT2::resume() {
|
|
||||||
if (loco)
|
|
||||||
DCC::setThrottle(loco,pauseSpeed & 0x7f, pauseSpeed & 0x80);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RMFT2::loop() {
|
void RMFT2::loop() {
|
||||||
if (compileFeatures & FEATURE_SENSOR)
|
if (compileFeatures & FEATURE_SENSOR)
|
||||||
EXRAILSensor::checkAll();
|
EXRAILSensor::checkAll();
|
||||||
@ -576,23 +571,18 @@ void RMFT2::loop2() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
case OPCODE_REV:
|
case OPCODE_REV:
|
||||||
if (loco) DCC::setThrottle(loco,operand,invert);
|
forward = false;
|
||||||
|
driveLoco(operand);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_FWD:
|
case OPCODE_FWD:
|
||||||
if (loco) DCC::setThrottle(loco,operand,!invert);
|
forward = true;
|
||||||
break;
|
driveLoco(operand);
|
||||||
|
break;
|
||||||
|
|
||||||
case OPCODE_SPEED:
|
case OPCODE_SPEED:
|
||||||
if (loco) DCC::setThrottle(loco,operand,DCC::getThrottleDirection(loco));
|
forward=DCC::getThrottleDirection(loco)^invert;
|
||||||
break;
|
driveLoco(operand);
|
||||||
|
|
||||||
case OPCODE_MOMENTUM:
|
|
||||||
DCC::setMomentum(loco,operand,getOperand(1));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case OPCODE_ESTOPALL:
|
|
||||||
DCC::setThrottle(0,1,1); // all locos speed=1
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_FORGET:
|
case OPCODE_FORGET:
|
||||||
@ -604,11 +594,12 @@ void RMFT2::loop2() {
|
|||||||
|
|
||||||
case OPCODE_INVERT_DIRECTION:
|
case OPCODE_INVERT_DIRECTION:
|
||||||
invert= !invert;
|
invert= !invert;
|
||||||
|
driveLoco(speedo);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_RESERVE:
|
case OPCODE_RESERVE:
|
||||||
if (getFlag(operand,SECTION_FLAG)) {
|
if (getFlag(operand,SECTION_FLAG)) {
|
||||||
if (loco) DCC::setThrottle(loco,1,DCC::getThrottleDirection(loco));
|
driveLoco(0);
|
||||||
delayMe(500);
|
delayMe(500);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -707,7 +698,7 @@ void RMFT2::loop2() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_PAUSE:
|
case OPCODE_PAUSE:
|
||||||
DCC::estopAll(); // pause all locos on the track
|
DCC::setThrottle(0,1,true); // pause all locos on the track
|
||||||
pausingTask=this;
|
pausingTask=this;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -715,10 +706,6 @@ void RMFT2::loop2() {
|
|||||||
if (loco) DCC::writeCVByteMain(loco, operand, getOperand(1));
|
if (loco) DCC::writeCVByteMain(loco, operand, getOperand(1));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_XPOM:
|
|
||||||
DCC::writeCVByteMain(operand, getOperand(1), getOperand(2));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case OPCODE_POWEROFF:
|
case OPCODE_POWEROFF:
|
||||||
TrackManager::setPower(POWERMODE::OFF);
|
TrackManager::setPower(POWERMODE::OFF);
|
||||||
TrackManager::setJoin(false);
|
TrackManager::setJoin(false);
|
||||||
@ -755,8 +742,8 @@ void RMFT2::loop2() {
|
|||||||
|
|
||||||
case OPCODE_RESUME:
|
case OPCODE_RESUME:
|
||||||
pausingTask=NULL;
|
pausingTask=NULL;
|
||||||
resume();
|
driveLoco(speedo);
|
||||||
for (RMFT2 * t=next; t!=this;t=t->next) t->resume();
|
for (RMFT2 * t=next; t!=this;t=t->next) if (t->loco >0) t->driveLoco(t->speedo);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_IF: // do next operand if sensor set
|
case OPCODE_IF: // do next operand if sensor set
|
||||||
@ -867,7 +854,8 @@ void RMFT2::loop2() {
|
|||||||
|
|
||||||
case OPCODE_DRIVE:
|
case OPCODE_DRIVE:
|
||||||
{
|
{
|
||||||
// Non functional but reserved
|
byte analogSpeed=IODevice::readAnalogue(operand) *127 / 1024;
|
||||||
|
if (speedo!=analogSpeed) driveLoco(analogSpeed);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -883,14 +871,6 @@ void RMFT2::loop2() {
|
|||||||
DCC::changeFn(operand,getOperand(1));
|
DCC::changeFn(operand,getOperand(1));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_XFWD:
|
|
||||||
DCC::setThrottle(operand,getOperand(1), true);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case OPCODE_XREV:
|
|
||||||
DCC::setThrottle(operand,getOperand(1), false);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case OPCODE_DCCACTIVATE: {
|
case OPCODE_DCCACTIVATE: {
|
||||||
// operand is address<<3 | subaddr<<1 | active
|
// operand is address<<3 | subaddr<<1 | active
|
||||||
int16_t addr=operand>>3;
|
int16_t addr=operand>>3;
|
||||||
@ -965,6 +945,8 @@ void RMFT2::loop2() {
|
|||||||
// which is intended so it can be checked
|
// which is intended so it can be checked
|
||||||
// from within EXRAIL
|
// from within EXRAIL
|
||||||
loco=progtrackLocoId;
|
loco=progtrackLocoId;
|
||||||
|
speedo=0;
|
||||||
|
forward=true;
|
||||||
invert=false;
|
invert=false;
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
@ -986,13 +968,16 @@ void RMFT2::loop2() {
|
|||||||
{
|
{
|
||||||
int newPc=routeLookup->find(getOperand(1));
|
int newPc=routeLookup->find(getOperand(1));
|
||||||
if (newPc<0) break;
|
if (newPc<0) break;
|
||||||
new RMFT2(newPc,operand); // create new task
|
RMFT2* newtask=new RMFT2(newPc); // create new task
|
||||||
|
newtask->loco=operand;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_SETLOCO:
|
case OPCODE_SETLOCO:
|
||||||
{
|
{
|
||||||
loco=operand;
|
loco=operand;
|
||||||
|
speedo=0;
|
||||||
|
forward=true;
|
||||||
invert=false;
|
invert=false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -1126,8 +1111,7 @@ void RMFT2::loop2() {
|
|||||||
case OPCODE_ONROTATE:
|
case OPCODE_ONROTATE:
|
||||||
#endif
|
#endif
|
||||||
case OPCODE_ONOVERLOAD:
|
case OPCODE_ONOVERLOAD:
|
||||||
case OPCODE_ONBLOCKENTER:
|
|
||||||
case OPCODE_ONBLOCKEXIT:
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -1319,14 +1303,6 @@ void RMFT2::activateEvent(int16_t addr, bool activate) {
|
|||||||
else onDeactivateLookup->handleEvent(F("DEACTIVATE"),addr);
|
else onDeactivateLookup->handleEvent(F("DEACTIVATE"),addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RMFT2::blockEvent(int16_t block, int16_t loco, bool entering) {
|
|
||||||
if (compileFeatures & FEATURE_BLOCK) {
|
|
||||||
// Hunt for an ONBLOCKENTER/ONBLOCKEXIT for this accessory
|
|
||||||
if (entering) onBlockEnterLookup->handleEvent(F("BLOCKENTER"),block,loco);
|
|
||||||
else onBlockExitLookup->handleEvent(F("BLOCKEXIT"),block,loco);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RMFT2::changeEvent(int16_t vpin, bool change) {
|
void RMFT2::changeEvent(int16_t vpin, bool change) {
|
||||||
// Hunt for an ONCHANGE for this sensor
|
// Hunt for an ONCHANGE for this sensor
|
||||||
if (change) onChangeLookup->handleEvent(F("CHANGE"),vpin);
|
if (change) onChangeLookup->handleEvent(F("CHANGE"),vpin);
|
||||||
@ -1382,11 +1358,11 @@ void RMFT2::killBlinkOnVpin(VPIN pin, uint16_t count) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RMFT2::startNonRecursiveTask(const FSH* reason, int16_t id,int pc, uint16_t loco) {
|
void RMFT2::startNonRecursiveTask(const FSH* reason, int16_t id,int pc) {
|
||||||
// Check we dont already have a task running this handler
|
// Check we dont already have a task running this handler
|
||||||
RMFT2 * task=loopTask;
|
RMFT2 * task=loopTask;
|
||||||
while(task) {
|
while(task) {
|
||||||
if (task->onEventStartPosition==pc && task->loco==loco) {
|
if (task->onEventStartPosition==pc) {
|
||||||
DIAG(F("Recursive ON%S(%d)"),reason, id);
|
DIAG(F("Recursive ON%S(%d)"),reason, id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1394,7 +1370,7 @@ void RMFT2::startNonRecursiveTask(const FSH* reason, int16_t id,int pc, uint16_t
|
|||||||
if (task==loopTask) break;
|
if (task==loopTask) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
task=new RMFT2(pc,loco); // new task starts at this instruction
|
task=new RMFT2(pc); // new task starts at this instruction
|
||||||
task->onEventStartPosition=pc; // flag for recursion detector
|
task->onEventStartPosition=pc; // flag for recursion detector
|
||||||
}
|
}
|
||||||
|
|
||||||
|
26
EXRAIL2.h
26
EXRAIL2.h
@ -1,9 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* © 2021 Neil McKechnie
|
* © 2021 Neil McKechnie
|
||||||
* © 2020-2025 Chris Harlow
|
* © 2020-2022 Chris Harlow
|
||||||
* © 2022-2023 Colin Murdoch
|
* © 2022-2023 Colin Murdoch
|
||||||
* © 2023 Harald Barth
|
* © 2023 Harald Barth
|
||||||
* © 2025 Morten Nielsen
|
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
@ -36,7 +35,6 @@
|
|||||||
//
|
//
|
||||||
enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,OPCODE_TOGGLE_TURNOUT,
|
enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,OPCODE_TOGGLE_TURNOUT,
|
||||||
OPCODE_FWD,OPCODE_REV,OPCODE_SPEED,OPCODE_INVERT_DIRECTION,
|
OPCODE_FWD,OPCODE_REV,OPCODE_SPEED,OPCODE_INVERT_DIRECTION,
|
||||||
OPCODE_MOMENTUM,
|
|
||||||
OPCODE_RESERVE,OPCODE_FREE,
|
OPCODE_RESERVE,OPCODE_FREE,
|
||||||
OPCODE_AT,OPCODE_AFTER,
|
OPCODE_AT,OPCODE_AFTER,
|
||||||
OPCODE_AFTEROVERLOAD,OPCODE_AUTOSTART,
|
OPCODE_AFTEROVERLOAD,OPCODE_AUTOSTART,
|
||||||
@ -47,7 +45,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,OPCODE_TOGGLE_TURNOUT,
|
|||||||
OPCODE_ENDIF,OPCODE_ELSE,
|
OPCODE_ENDIF,OPCODE_ELSE,
|
||||||
OPCODE_DELAY,OPCODE_DELAYMINS,OPCODE_DELAYMS,OPCODE_RANDWAIT,
|
OPCODE_DELAY,OPCODE_DELAYMINS,OPCODE_DELAYMS,OPCODE_RANDWAIT,
|
||||||
OPCODE_FON,OPCODE_FOFF,OPCODE_XFON,OPCODE_XFOFF,
|
OPCODE_FON,OPCODE_FOFF,OPCODE_XFON,OPCODE_XFOFF,
|
||||||
OPCODE_FTOGGLE,OPCODE_XFTOGGLE,OPCODE_XFWD,OPCODE_XREV,
|
OPCODE_FTOGGLE,OPCODE_XFTOGGLE,
|
||||||
OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE,
|
OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE,
|
||||||
OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR,
|
OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR,
|
||||||
OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN,
|
OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN,
|
||||||
@ -79,8 +77,6 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,OPCODE_TOGGLE_TURNOUT,
|
|||||||
OPCODE_STASH,OPCODE_CLEAR_STASH,OPCODE_CLEAR_ALL_STASH,OPCODE_PICKUP_STASH,
|
OPCODE_STASH,OPCODE_CLEAR_STASH,OPCODE_CLEAR_ALL_STASH,OPCODE_PICKUP_STASH,
|
||||||
OPCODE_ONBUTTON,OPCODE_ONSENSOR,
|
OPCODE_ONBUTTON,OPCODE_ONSENSOR,
|
||||||
OPCODE_NEOPIXEL,
|
OPCODE_NEOPIXEL,
|
||||||
OPCODE_ONBLOCKENTER,OPCODE_ONBLOCKEXIT,
|
|
||||||
OPCODE_ESTOPALL,OPCODE_XPOM,
|
|
||||||
// OPcodes below this point are skip-nesting IF operations
|
// OPcodes below this point are skip-nesting IF operations
|
||||||
// placed here so that they may be skipped as a group
|
// placed here so that they may be skipped as a group
|
||||||
// see skipIfBlock()
|
// see skipIfBlock()
|
||||||
@ -140,7 +136,6 @@ enum SignalType {
|
|||||||
static const byte FEATURE_STASH = 0x08;
|
static const byte FEATURE_STASH = 0x08;
|
||||||
static const byte FEATURE_BLINK = 0x04;
|
static const byte FEATURE_BLINK = 0x04;
|
||||||
static const byte FEATURE_SENSOR = 0x02;
|
static const byte FEATURE_SENSOR = 0x02;
|
||||||
static const byte FEATURE_BLOCK = 0x01;
|
|
||||||
|
|
||||||
|
|
||||||
// Flag bits for status of hardware and TPL
|
// Flag bits for status of hardware and TPL
|
||||||
@ -167,7 +162,7 @@ class LookList {
|
|||||||
int16_t findPosition(int16_t value); // finds index
|
int16_t findPosition(int16_t value); // finds index
|
||||||
int16_t size();
|
int16_t size();
|
||||||
void stream(Print * _stream);
|
void stream(Print * _stream);
|
||||||
void handleEvent(const FSH* reason,int16_t id, int16_t loco=0);
|
void handleEvent(const FSH* reason,int16_t id);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int16_t m_size;
|
int16_t m_size;
|
||||||
@ -181,7 +176,8 @@ class LookList {
|
|||||||
public:
|
public:
|
||||||
static void begin();
|
static void begin();
|
||||||
static void loop();
|
static void loop();
|
||||||
RMFT2(int progCounter, int16_t cab=0);
|
RMFT2(int progCounter);
|
||||||
|
RMFT2(int route, uint16_t cab);
|
||||||
~RMFT2();
|
~RMFT2();
|
||||||
static void readLocoCallback(int16_t cv);
|
static void readLocoCallback(int16_t cv);
|
||||||
static void createNewTask(int route, uint16_t cab);
|
static void createNewTask(int route, uint16_t cab);
|
||||||
@ -191,7 +187,6 @@ class LookList {
|
|||||||
static void clockEvent(int16_t clocktime, bool change);
|
static void clockEvent(int16_t clocktime, bool change);
|
||||||
static void rotateEvent(int16_t id, bool change);
|
static void rotateEvent(int16_t id, bool change);
|
||||||
static void powerEvent(int16_t track, bool overload);
|
static void powerEvent(int16_t track, bool overload);
|
||||||
static void blockEvent(int16_t block, int16_t loco, bool entering);
|
|
||||||
static bool signalAspectEvent(int16_t address, byte aspect );
|
static bool signalAspectEvent(int16_t address, byte aspect );
|
||||||
// Throttle Info Access functions built by exrail macros
|
// Throttle Info Access functions built by exrail macros
|
||||||
static const byte rosterNameCount;
|
static const byte rosterNameCount;
|
||||||
@ -205,7 +200,7 @@ class LookList {
|
|||||||
static const FSH * getRosterFunctions(int16_t id);
|
static const FSH * getRosterFunctions(int16_t id);
|
||||||
static const FSH * getTurntableDescription(int16_t id);
|
static const FSH * getTurntableDescription(int16_t id);
|
||||||
static const FSH * getTurntablePositionDescription(int16_t turntableId, uint8_t positionId);
|
static const FSH * getTurntablePositionDescription(int16_t turntableId, uint8_t positionId);
|
||||||
static void startNonRecursiveTask(const FSH* reason, int16_t id,int pc, uint16_t loco=0);
|
static void startNonRecursiveTask(const FSH* reason, int16_t id,int pc);
|
||||||
static bool readSensor(uint16_t sensorId);
|
static bool readSensor(uint16_t sensorId);
|
||||||
static bool isSignal(int16_t id,char rag);
|
static bool isSignal(int16_t id,char rag);
|
||||||
static SIGNAL_DEFINITION getSignalSlot(int16_t slotno);
|
static SIGNAL_DEFINITION getSignalSlot(int16_t slotno);
|
||||||
@ -229,6 +224,7 @@ private:
|
|||||||
static RMFT2 * loopTask;
|
static RMFT2 * loopTask;
|
||||||
static RMFT2 * pausingTask;
|
static RMFT2 * pausingTask;
|
||||||
void delayMe(long millisecs);
|
void delayMe(long millisecs);
|
||||||
|
void driveLoco(byte speedo);
|
||||||
bool skipIfBlock();
|
bool skipIfBlock();
|
||||||
bool readLoco();
|
bool readLoco();
|
||||||
void loop2();
|
void loop2();
|
||||||
@ -237,8 +233,6 @@ private:
|
|||||||
void printMessage2(const FSH * msg);
|
void printMessage2(const FSH * msg);
|
||||||
void thrungeString(uint32_t strfar, thrunger mode, byte id=0);
|
void thrungeString(uint32_t strfar, thrunger mode, byte id=0);
|
||||||
uint16_t getOperand(byte n);
|
uint16_t getOperand(byte n);
|
||||||
void pause();
|
|
||||||
void resume();
|
|
||||||
|
|
||||||
static bool diag;
|
static bool diag;
|
||||||
static const HIGHFLASH3 byte RouteCode[];
|
static const HIGHFLASH3 byte RouteCode[];
|
||||||
@ -260,9 +254,6 @@ private:
|
|||||||
static LookList * onRotateLookup;
|
static LookList * onRotateLookup;
|
||||||
#endif
|
#endif
|
||||||
static LookList * onOverloadLookup;
|
static LookList * onOverloadLookup;
|
||||||
static LookList * onBlockEnterLookup;
|
|
||||||
static LookList * onBlockExitLookup;
|
|
||||||
|
|
||||||
|
|
||||||
static const int countLCCLookup;
|
static const int countLCCLookup;
|
||||||
static int onLCCLookup[];
|
static int onLCCLookup[];
|
||||||
@ -287,8 +278,9 @@ private:
|
|||||||
byte taskId;
|
byte taskId;
|
||||||
BlinkState blinkState; // includes AT_TIMEOUT flag.
|
BlinkState blinkState; // includes AT_TIMEOUT flag.
|
||||||
uint16_t loco;
|
uint16_t loco;
|
||||||
|
bool forward;
|
||||||
bool invert;
|
bool invert;
|
||||||
byte pauseSpeed;
|
byte speedo;
|
||||||
int onEventStartPosition;
|
int onEventStartPosition;
|
||||||
byte stackDepth;
|
byte stackDepth;
|
||||||
int callStack[MAX_STACK_DEPTH];
|
int callStack[MAX_STACK_DEPTH];
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020-2025 Chris Harlow. All rights reserved.
|
* © 2020-2022 Chris Harlow. All rights reserved.
|
||||||
* © 2022-2023 Colin Murdoch
|
* © 2022-2023 Colin Murdoch
|
||||||
* © 2023 Harald Barth
|
* © 2023 Harald Barth
|
||||||
* © 2025 Morten Nielsen
|
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
@ -61,7 +60,6 @@
|
|||||||
#undef ENDIF
|
#undef ENDIF
|
||||||
#undef ENDTASK
|
#undef ENDTASK
|
||||||
#undef ESTOP
|
#undef ESTOP
|
||||||
#undef ESTOPALL
|
|
||||||
#undef EXRAIL
|
#undef EXRAIL
|
||||||
#undef EXTT_TURNTABLE
|
#undef EXTT_TURNTABLE
|
||||||
#undef FADE
|
#undef FADE
|
||||||
@ -100,7 +98,6 @@
|
|||||||
#undef LCC
|
#undef LCC
|
||||||
#undef LCCX
|
#undef LCCX
|
||||||
#undef LCN
|
#undef LCN
|
||||||
#undef MOMENTUM
|
|
||||||
#undef MOVETT
|
#undef MOVETT
|
||||||
#undef NEOPIXEL
|
#undef NEOPIXEL
|
||||||
#undef NEOPIXEL_OFF
|
#undef NEOPIXEL_OFF
|
||||||
@ -113,8 +110,6 @@
|
|||||||
#undef ONACTIVATE
|
#undef ONACTIVATE
|
||||||
#undef ONACTIVATEL
|
#undef ONACTIVATEL
|
||||||
#undef ONAMBER
|
#undef ONAMBER
|
||||||
#undef ONBLOCKENTER
|
|
||||||
#undef ONBLOCKEXIT
|
|
||||||
#undef ONDEACTIVATE
|
#undef ONDEACTIVATE
|
||||||
#undef ONDEACTIVATEL
|
#undef ONDEACTIVATEL
|
||||||
#undef ONCLOSE
|
#undef ONCLOSE
|
||||||
@ -199,9 +194,6 @@
|
|||||||
#undef XFOFF
|
#undef XFOFF
|
||||||
#undef XFON
|
#undef XFON
|
||||||
#undef XFTOGGLE
|
#undef XFTOGGLE
|
||||||
#undef XPOM
|
|
||||||
#undef XREV
|
|
||||||
#undef XFWD
|
|
||||||
|
|
||||||
#ifndef RMFT2_UNDEF_ONLY
|
#ifndef RMFT2_UNDEF_ONLY
|
||||||
#define ACTIVATE(addr,subaddr)
|
#define ACTIVATE(addr,subaddr)
|
||||||
@ -240,7 +232,6 @@
|
|||||||
#define ENDIF
|
#define ENDIF
|
||||||
#define ENDTASK
|
#define ENDTASK
|
||||||
#define ESTOP
|
#define ESTOP
|
||||||
#define ESTOPALL
|
|
||||||
#define EXRAIL
|
#define EXRAIL
|
||||||
#define EXTT_TURNTABLE(id,vpin,home,description...)
|
#define EXTT_TURNTABLE(id,vpin,home,description...)
|
||||||
#define FADE(pin,value,ms)
|
#define FADE(pin,value,ms)
|
||||||
@ -277,7 +268,6 @@
|
|||||||
#define LCC(eventid)
|
#define LCC(eventid)
|
||||||
#define LCCX(senderid,eventid)
|
#define LCCX(senderid,eventid)
|
||||||
#define LCD(row,msg)
|
#define LCD(row,msg)
|
||||||
#define MOMENTUM(accel,decel...)
|
|
||||||
#define SCREEN(display,row,msg)
|
#define SCREEN(display,row,msg)
|
||||||
#define LCN(msg)
|
#define LCN(msg)
|
||||||
#define MESSAGE(msg)
|
#define MESSAGE(msg)
|
||||||
@ -291,8 +281,6 @@
|
|||||||
#define ONACTIVATE(addr,subaddr)
|
#define ONACTIVATE(addr,subaddr)
|
||||||
#define ONACTIVATEL(linear)
|
#define ONACTIVATEL(linear)
|
||||||
#define ONAMBER(signal_id)
|
#define ONAMBER(signal_id)
|
||||||
#define ONBLOCKENTER(blockid)
|
|
||||||
#define ONBLOCKEXIT(blockid)
|
|
||||||
#define ONTIME(value)
|
#define ONTIME(value)
|
||||||
#define ONCLOCKTIME(hours,mins)
|
#define ONCLOCKTIME(hours,mins)
|
||||||
#define ONCLOCKMINS(mins)
|
#define ONCLOCKMINS(mins)
|
||||||
@ -377,7 +365,5 @@
|
|||||||
#define XFOFF(cab,func)
|
#define XFOFF(cab,func)
|
||||||
#define XFON(cab,func)
|
#define XFON(cab,func)
|
||||||
#define XFTOGGLE(cab,func)
|
#define XFTOGGLE(cab,func)
|
||||||
#define XFWD(cab,speed)
|
|
||||||
#define XREV(cab,speed)
|
|
||||||
#define XPOM(cab,cv,value)
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* © 2021 Neil McKechnie
|
* © 2021 Neil McKechnie
|
||||||
* © 2021-2023 Harald Barth
|
* © 2021-2023 Harald Barth
|
||||||
* © 2020-2025 Chris Harlow
|
* © 2020-2023 Chris Harlow
|
||||||
* © 2022-2023 Colin Murdoch
|
* © 2022-2023 Colin Murdoch
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
@ -210,15 +210,6 @@ void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
|
||||||
case 'K': // <K blockid loco> Block enter
|
|
||||||
case 'k': // <k blockid loco> Block exit
|
|
||||||
if (paramCount!=2) break;
|
|
||||||
blockEvent(p[0],p[1],opcode=='K');
|
|
||||||
opcode=0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default: // other commands pass through
|
default: // other commands pass through
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -237,9 +228,11 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
StringFormatter::send(stream,F("\nID=%d,PC=%d,LOCO=%d %c"),
|
StringFormatter::send(stream,F("\nID=%d,PC=%d,LOCO=%d%c,SPEED=%d%c"),
|
||||||
(int)(task->taskId),task->progCounter,task->loco,
|
(int)(task->taskId),task->progCounter,task->loco,
|
||||||
task->invert?'I':' '
|
task->invert?'I':' ',
|
||||||
|
task->speedo,
|
||||||
|
task->forward?'F':'R'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
task=task->next;
|
task=task->next;
|
||||||
@ -283,27 +276,19 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
|||||||
switch (p[0]) {
|
switch (p[0]) {
|
||||||
case "PAUSE"_hk: // </ PAUSE>
|
case "PAUSE"_hk: // </ PAUSE>
|
||||||
if (paramCount!=1) return false;
|
if (paramCount!=1) return false;
|
||||||
{ // pause all tasks
|
DCC::setThrottle(0,1,true); // pause all locos on the track
|
||||||
RMFT2 * task=loopTask;
|
|
||||||
while(task) {
|
|
||||||
task->pause();
|
|
||||||
task=task->next;
|
|
||||||
if (task==loopTask) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DCC::estopAll(); // pause all locos on the track
|
|
||||||
pausingTask=(RMFT2 *)1; // Impossible task address
|
pausingTask=(RMFT2 *)1; // Impossible task address
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case "RESUME"_hk: // </ RESUME>
|
case "RESUME"_hk: // </ RESUME>
|
||||||
if (paramCount!=1) return false;
|
if (paramCount!=1) return false;
|
||||||
pausingTask=NULL;
|
pausingTask=NULL;
|
||||||
{ // resume all tasks
|
{
|
||||||
RMFT2 * task=loopTask;
|
RMFT2 * task=loopTask;
|
||||||
while(task) {
|
while(task) {
|
||||||
task->resume();
|
if (task->loco) task->driveLoco(task->speedo);
|
||||||
task=task->next;
|
task=task->next;
|
||||||
if (task==loopTask) break;
|
if (task==loopTask) break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -316,7 +301,8 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
|||||||
uint16_t cab=(paramCount==2)? 0 : p[1];
|
uint16_t cab=(paramCount==2)? 0 : p[1];
|
||||||
int pc=routeLookup->find(route);
|
int pc=routeLookup->find(route);
|
||||||
if (pc<0) return false;
|
if (pc<0) return false;
|
||||||
new RMFT2(pc,cab);
|
RMFT2* task=new RMFT2(pc);
|
||||||
|
task->loco=cab;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* © 2021 Neil McKechnie
|
* © 2021 Neil McKechnie
|
||||||
* © 2020-2025 Chris Harlow
|
* © 2020-2022 Chris Harlow
|
||||||
* © 2022-2023 Colin Murdoch
|
* © 2022-2023 Colin Murdoch
|
||||||
* © 2023 Harald Barth
|
* © 2023 Harald Barth
|
||||||
* © 2025 Morten Nielsen
|
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
@ -24,7 +23,6 @@
|
|||||||
|
|
||||||
#ifndef EXRAILMacros_H
|
#ifndef EXRAILMacros_H
|
||||||
#define EXRAILMacros_H
|
#define EXRAILMacros_H
|
||||||
#include "IODeviceList.h"
|
|
||||||
|
|
||||||
// remove normal code LCD & SERIAL macros (will be restored later)
|
// remove normal code LCD & SERIAL macros (will be restored later)
|
||||||
#undef LCD
|
#undef LCD
|
||||||
@ -232,10 +230,6 @@ bool exrailHalSetup() {
|
|||||||
#define ONBUTTON(vpin) | FEATURE_SENSOR
|
#define ONBUTTON(vpin) | FEATURE_SENSOR
|
||||||
#undef ONSENSOR
|
#undef ONSENSOR
|
||||||
#define ONSENSOR(vpin) | FEATURE_SENSOR
|
#define ONSENSOR(vpin) | FEATURE_SENSOR
|
||||||
#undef ONBLOCKENTER
|
|
||||||
#define ONBLOCKENTER(blockid) | FEATURE_BLOCK
|
|
||||||
#undef ONBLOCKEXIT
|
|
||||||
#define ONBLOCKEXIT(blockid) | FEATURE_BLOCK
|
|
||||||
|
|
||||||
const byte RMFT2::compileFeatures = 0
|
const byte RMFT2::compileFeatures = 0
|
||||||
#include "myAutomation.h"
|
#include "myAutomation.h"
|
||||||
@ -518,7 +512,6 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
|||||||
#define ENDIF OPCODE_ENDIF,0,0,
|
#define ENDIF OPCODE_ENDIF,0,0,
|
||||||
#define ENDTASK OPCODE_ENDTASK,0,0,
|
#define ENDTASK OPCODE_ENDTASK,0,0,
|
||||||
#define ESTOP OPCODE_SPEED,V(1),
|
#define ESTOP OPCODE_SPEED,V(1),
|
||||||
#define ESTOPALL OPCODE_ESTOPALL,0,0,
|
|
||||||
#define EXRAIL
|
#define EXRAIL
|
||||||
#ifndef IO_NO_HAL
|
#ifndef IO_NO_HAL
|
||||||
#define EXTT_TURNTABLE(id,vpin,home,description...) OPCODE_EXTTTURNTABLE,V(id),OPCODE_PAD,V(vpin),OPCODE_PAD,V(home),
|
#define EXTT_TURNTABLE(id,vpin,home,description...) OPCODE_EXTTTURNTABLE,V(id),OPCODE_PAD,V(vpin),OPCODE_PAD,V(home),
|
||||||
@ -571,7 +564,6 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
|||||||
#define STEALTH_GLOBAL(code...)
|
#define STEALTH_GLOBAL(code...)
|
||||||
#define LCN(msg) PRINT(msg)
|
#define LCN(msg) PRINT(msg)
|
||||||
#define MESSAGE(msg) PRINT(msg)
|
#define MESSAGE(msg) PRINT(msg)
|
||||||
#define MOMENTUM(accel,decel...) OPCODE_MOMENTUM,V(accel),OPCODE_PAD,V(#decel[0]?decel+0:accel),
|
|
||||||
#define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0),
|
#define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0),
|
||||||
#define NEOPIXEL(id,r,g,b,count...) OPCODE_NEOPIXEL,V(id),\
|
#define NEOPIXEL(id,r,g,b,count...) OPCODE_NEOPIXEL,V(id),\
|
||||||
OPCODE_PAD,V(((r & 0xff)<<8) | (g & 0xff)),\
|
OPCODE_PAD,V(((r & 0xff)<<8) | (g & 0xff)),\
|
||||||
@ -582,8 +574,6 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
|||||||
#define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr),
|
#define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr),
|
||||||
#define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3),
|
#define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3),
|
||||||
#define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id),
|
#define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id),
|
||||||
#define ONBLOCKENTER(block_id) OPCODE_ONBLOCKENTER,V(block_id),
|
|
||||||
#define ONBLOCKEXIT(block_id) OPCODE_ONBLOCKEXIT,V(block_id),
|
|
||||||
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
|
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
|
||||||
#define ONLCC(sender,event) OPCODE_ONLCC,V(event),\
|
#define ONLCC(sender,event) OPCODE_ONLCC,V(event),\
|
||||||
OPCODE_PAD,V((((uint64_t)sender)>>32)&0xFFFF),\
|
OPCODE_PAD,V((((uint64_t)sender)>>32)&0xFFFF),\
|
||||||
@ -675,9 +665,6 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
|||||||
#define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func),
|
#define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func),
|
||||||
#define XFON(cab,func) OPCODE_XFON,V(cab),OPCODE_PAD,V(func),
|
#define XFON(cab,func) OPCODE_XFON,V(cab),OPCODE_PAD,V(func),
|
||||||
#define XFTOGGLE(cab,func) OPCODE_XFTOGGLE,V(cab),OPCODE_PAD,V(func),
|
#define XFTOGGLE(cab,func) OPCODE_XFTOGGLE,V(cab),OPCODE_PAD,V(func),
|
||||||
#define XFWD(cab,speed) OPCODE_XFWD,V(cab),OPCODE_PAD,V(speed),
|
|
||||||
#define XREV(cab,speed) OPCODE_XREV,V(cab),OPCODE_PAD,V(speed),
|
|
||||||
#define XPOM(cab,cv,value) OPCODE_XPOM,V(cab),OPCODE_PAD,V(cv),OPCODE_PAD,V(value),
|
|
||||||
|
|
||||||
// Build RouteCode
|
// Build RouteCode
|
||||||
const int StringMacroTracker2=__COUNTER__;
|
const int StringMacroTracker2=__COUNTER__;
|
||||||
|
@ -1 +1 @@
|
|||||||
#define GITHUB_SHA "devel-202501092043Z"
|
#define GITHUB_SHA "devel-202501021826Z"
|
||||||
|
14
IODevice.h
14
IODevice.h
@ -560,6 +560,18 @@ protected:
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//#include "IODeviceList.h"
|
#include "IO_MCP23008.h"
|
||||||
|
#include "IO_MCP23017.h"
|
||||||
|
#include "IO_PCF8574.h"
|
||||||
|
#include "IO_PCF8575.h"
|
||||||
|
#include "IO_PCA9555.h"
|
||||||
|
#include "IO_duinoNodes.h"
|
||||||
|
#include "IO_EXIOExpander.h"
|
||||||
|
#include "IO_trainbrains.h"
|
||||||
|
#include "IO_EncoderThrottle.h"
|
||||||
|
#include "IO_TCA8418.h"
|
||||||
|
#include "IO_NeoPixel.h"
|
||||||
|
#include "IO_TM1638.h"
|
||||||
|
#include "IO_EXSensorCAM.h"
|
||||||
|
|
||||||
#endif // iodevice_h
|
#endif // iodevice_h
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
/*
|
|
||||||
* © 2024, Chris Harlow. 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/>.
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
This is the list of HAL drivers automatically included by IODevice.h
|
|
||||||
It has been moved here to be easier to maintain than editing IODevice.h
|
|
||||||
*/
|
|
||||||
#include "IO_MCP23008.h"
|
|
||||||
#include "IO_MCP23017.h"
|
|
||||||
#include "IO_PCF8574.h"
|
|
||||||
#include "IO_PCF8575.h"
|
|
||||||
#include "IO_PCA9555.h"
|
|
||||||
#include "IO_duinoNodes.h"
|
|
||||||
#include "IO_EXIOExpander.h"
|
|
||||||
#include "IO_trainbrains.h"
|
|
||||||
#include "IO_EncoderThrottle.h"
|
|
||||||
#include "IO_TCA8418.h"
|
|
||||||
#include "IO_NeoPixel.h"
|
|
||||||
#include "IO_TM1638.h"
|
|
||||||
#include "IO_EXSensorCAM.h"
|
|
||||||
#include "IO_DS1307.h"
|
|
||||||
#include "IO_I2CRailcom.h"
|
|
||||||
|
|
143
IO_DS1307.cpp
143
IO_DS1307.cpp
@ -1,143 +0,0 @@
|
|||||||
/*
|
|
||||||
* © 2024, Chris Harlow. 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_DS1307 device driver is used to interface a standalone realtime clock.
|
|
||||||
* The clock will announce every minute (which will trigger EXRAIL ONTIME events).
|
|
||||||
* Seconds, and Day/date info is ignored, except that the announced hhmm time
|
|
||||||
* will attempt to synchronize with the 0 seconds of the clock.
|
|
||||||
* An analog read in EXRAIL (IFGTE(vpin, value) etc will check against the hh*60+mm time.
|
|
||||||
* The clock can be easily set by an analog write to the vpin using 24 hr clock time
|
|
||||||
* with the command <z vpin hh mm ss>
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IO_DS1307.h"
|
|
||||||
#include "I2CManager.h"
|
|
||||||
#include "DIAG.h"
|
|
||||||
#include "CommandDistributor.h"
|
|
||||||
|
|
||||||
uint8_t d2b(uint8_t d) {
|
|
||||||
return (d >> 4)*10 + (d & 0x0F);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DS1307::create(VPIN vpin, I2CAddress i2cAddress) {
|
|
||||||
if (checkNoOverlap(vpin, 1, i2cAddress)) new DS1307(vpin, i2cAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Constructor
|
|
||||||
DS1307::DS1307(VPIN vpin,I2CAddress i2cAddress){
|
|
||||||
_firstVpin = vpin;
|
|
||||||
_nPins = 1;
|
|
||||||
_I2CAddress = i2cAddress;
|
|
||||||
addDevice(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t DS1307::getTime() {
|
|
||||||
// Obtain ss,mm,hh buffers from device
|
|
||||||
uint8_t readBuffer[3];
|
|
||||||
const uint8_t writeBuffer[1]={0};
|
|
||||||
|
|
||||||
// address register 0 for read.
|
|
||||||
I2CManager.write(_I2CAddress, writeBuffer, 1);
|
|
||||||
if (I2CManager.read(_I2CAddress, readBuffer, 3) != I2C_STATUS_OK) {
|
|
||||||
_deviceState=DEVSTATE_FAILED;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
_deviceState=DEVSTATE_NORMAL;
|
|
||||||
|
|
||||||
if (debug) {
|
|
||||||
static const char hexchars[]="0123456789ABCDEF";
|
|
||||||
USB_SERIAL.print(F("<*RTC"));
|
|
||||||
for (int i=2;i>=0;i--) {
|
|
||||||
USB_SERIAL.write(' ');
|
|
||||||
USB_SERIAL.write(hexchars[readBuffer[i]>>4]);
|
|
||||||
USB_SERIAL.write(hexchars[readBuffer[i]& 0x0F ]);
|
|
||||||
}
|
|
||||||
StringFormatter::send(&USB_SERIAL,F(" %d *>\n"),_deviceState);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (readBuffer[0] & 0x80) {
|
|
||||||
_deviceState=DEVSTATE_INITIALISING;
|
|
||||||
DIAG(F("DS1307 clock in standby"));
|
|
||||||
return 0; // clock is not running
|
|
||||||
}
|
|
||||||
// convert device format to seconds since midnight
|
|
||||||
uint8_t ss=d2b(readBuffer[0] & 0x7F);
|
|
||||||
uint8_t mm=d2b(readBuffer[1]);
|
|
||||||
uint8_t hh=d2b(readBuffer[2] & 0x3F);
|
|
||||||
return (hh*60ul +mm)*60ul +ss;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DS1307::_begin() {
|
|
||||||
// Initialise device and sync loop() to zero seconds
|
|
||||||
I2CManager.begin();
|
|
||||||
auto tstamp=getTime();
|
|
||||||
if (_deviceState==DEVSTATE_NORMAL) {
|
|
||||||
byte seconds=tstamp%60;
|
|
||||||
delayUntil(micros() + ((60-seconds) * 1000000));
|
|
||||||
}
|
|
||||||
_display();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Processing loop to obtain clock time.
|
|
||||||
// This self-synchronizes to the next minute tickover
|
|
||||||
void DS1307::_loop(unsigned long currentMicros) {
|
|
||||||
auto time=getTime();
|
|
||||||
if (_deviceState==DEVSTATE_NORMAL) {
|
|
||||||
byte ss=time%60;
|
|
||||||
CommandDistributor::setClockTime(time/60, 1, 1);
|
|
||||||
delayUntil(currentMicros + ((60-ss) * 1000000));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Display device driver info.
|
|
||||||
void DS1307::_display() {
|
|
||||||
auto tstamp=getTime();
|
|
||||||
byte ss=tstamp%60;
|
|
||||||
tstamp/=60;
|
|
||||||
byte mm=tstamp%60;
|
|
||||||
byte hh=tstamp/60;
|
|
||||||
DIAG(F("DS1307 on I2C:%s vpin %d %d:%d:%d %S"),
|
|
||||||
_I2CAddress.toString(), _firstVpin,
|
|
||||||
hh,mm,ss,
|
|
||||||
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
|
||||||
}
|
|
||||||
|
|
||||||
// allow user to set the clock
|
|
||||||
void DS1307::_writeAnalogue(VPIN vpin, int hh, uint8_t mm, uint16_t ss) {
|
|
||||||
(void) vpin;
|
|
||||||
uint8_t writeBuffer[3];
|
|
||||||
writeBuffer[0]=1; // write mm,hh first
|
|
||||||
writeBuffer[1]=((mm/10)<<4) + (mm % 10);
|
|
||||||
writeBuffer[2]=((hh/10)<<4) + (hh % 10);
|
|
||||||
I2CManager.write(_I2CAddress, writeBuffer, 3);
|
|
||||||
writeBuffer[0]=0; // write ss
|
|
||||||
writeBuffer[1]=((ss/10)<<4) + (ss % 10);
|
|
||||||
I2CManager.write(_I2CAddress, writeBuffer, 2);
|
|
||||||
_loop(micros()); // resync with seconds rollover
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method to read analogue hh*60+mm time
|
|
||||||
int DS1307::_readAnalogue(VPIN vpin) {
|
|
||||||
(void)vpin;
|
|
||||||
return getTime()/60;
|
|
||||||
};
|
|
||||||
|
|
54
IO_DS1307.h
54
IO_DS1307.h
@ -1,54 +0,0 @@
|
|||||||
/*
|
|
||||||
* © 2024, Chris Harlow. 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_DS1307 device driver is used to interface a standalone realtime clock.
|
|
||||||
* The clock will announce every minute (which will trigger EXRAIL ONTIME events).
|
|
||||||
* Seconds, and Day/date info is ignored, except that the announced hhmm time
|
|
||||||
* will attempt to synchronize with the 0 seconds of the clock.
|
|
||||||
* An analog read in EXRAIL (IFGTE(vpin, value) etc will check against the hh*60+mm time.
|
|
||||||
* The clock can be easily set by an analog write to the vpin using 24 hr clock time
|
|
||||||
* with the command <z vpin hh mm ss>
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef IO_DS1307_h
|
|
||||||
#define IO_DS1307_h
|
|
||||||
|
|
||||||
|
|
||||||
#include "IODevice.h"
|
|
||||||
|
|
||||||
class DS1307 : public IODevice {
|
|
||||||
public:
|
|
||||||
static const bool debug=false;
|
|
||||||
static void create(VPIN vpin, I2CAddress i2cAddress);
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
// Constructor
|
|
||||||
DS1307(VPIN vpin,I2CAddress i2cAddress);
|
|
||||||
uint32_t getTime();
|
|
||||||
void _begin() override;
|
|
||||||
void _display() override;
|
|
||||||
void _loop(unsigned long currentMicros) override;
|
|
||||||
int _readAnalogue(VPIN vpin) override;
|
|
||||||
void _writeAnalogue(VPIN vpin, int hh, uint8_t mm, uint16_t ss) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
@ -24,7 +24,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "IODevice.h"
|
#include "IODevice.h"
|
||||||
#include "IO_EncoderThrottle.h"
|
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
#include "DCC.h"
|
#include "DCC.h"
|
||||||
|
|
||||||
|
@ -1,303 +0,0 @@
|
|||||||
/*
|
|
||||||
* © 2024, Henk Kruisbrink & Chris Harlow. All rights reserved.
|
|
||||||
* © 2023, Neil McKechnie. All rights reserved.
|
|
||||||
*
|
|
||||||
* This file is part of DCC++EX API
|
|
||||||
*
|
|
||||||
* This is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* It is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Dec 2023, Added NXP SC16IS752 I2C Dual UART
|
|
||||||
* The SC16IS752 has 64 bytes TX & RX FIFO buffer
|
|
||||||
* First version without interrupts from I2C UART and only RX/TX are used, interrupts may not be
|
|
||||||
* needed as the RX Fifo holds the reply
|
|
||||||
*
|
|
||||||
* Jan 2024, Issue with using both UARTs simultaniously, the secod uart seems to work but the first transmit
|
|
||||||
* corrupt data. This need more analysis and experimenatation.
|
|
||||||
* Will push this driver to the dev branch with the uart fixed to 0
|
|
||||||
* Both SC16IS750 (single uart) and SC16IS752 (dual uart, but only uart 0 is enable)
|
|
||||||
*
|
|
||||||
* myHall.cpp configuration syntax:
|
|
||||||
*
|
|
||||||
* I2CRailcom::create(1st vPin, vPins, I2C address);
|
|
||||||
*
|
|
||||||
* myAutomation configuration
|
|
||||||
* HAL(I2CRailcom, 1st vPin, vPins, I2C address)
|
|
||||||
* Parameters:
|
|
||||||
* 1st vPin : First virtual pin that EX-Rail can control to play a sound, use PLAYSOUND command (alias of ANOUT)
|
|
||||||
* vPins : Total number of virtual pins allocated (2 vPins are supported, one for each UART)
|
|
||||||
* 1st vPin for UART 0, 2nd for UART 1
|
|
||||||
* I2C Address : I2C address of the serial controller, in 0x format
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IODevice.h"
|
|
||||||
#include "IO_I2CRailcom.h"
|
|
||||||
#include "I2CManager.h"
|
|
||||||
#include "DIAG.h"
|
|
||||||
#include "DCCWaveform.h"
|
|
||||||
#include "Railcom.h"
|
|
||||||
|
|
||||||
// Debug and diagnostic defines, enable too many will result in slowing the driver
|
|
||||||
#define DIAG_I2CRailcom
|
|
||||||
|
|
||||||
I2CRailcom::I2CRailcom(VPIN firstVpin, int nPins, I2CAddress i2cAddress){
|
|
||||||
_firstVpin = firstVpin;
|
|
||||||
_nPins = nPins;
|
|
||||||
_I2CAddress = i2cAddress;
|
|
||||||
_channelMonitors[0]=new Railcom(firstVpin);
|
|
||||||
if (nPins>1) _channelMonitors[1]=new Railcom(firstVpin+1);
|
|
||||||
addDevice(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void I2CRailcom::create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
|
|
||||||
if (nPins>2) nPins=2;
|
|
||||||
if (checkNoOverlap(firstVpin, nPins, i2cAddress))
|
|
||||||
new I2CRailcom(firstVpin, nPins, i2cAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
void I2CRailcom::_begin() {
|
|
||||||
I2CManager.setClock(1000000); // TODO do we need this?
|
|
||||||
I2CManager.begin();
|
|
||||||
auto exists=I2CManager.exists(_I2CAddress);
|
|
||||||
DIAG(F("I2CRailcom: %s UART%S detected"),
|
|
||||||
_I2CAddress.toString(), exists?F(""):F(" NOT"));
|
|
||||||
if (!exists) return;
|
|
||||||
|
|
||||||
_UART_CH=0;
|
|
||||||
Init_SC16IS752(); // Initialize UART0
|
|
||||||
if (_nPins>1) {
|
|
||||||
_UART_CH=1;
|
|
||||||
Init_SC16IS752(); // Initialize UART1
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_deviceState==DEVSTATE_INITIALISING) _deviceState=DEVSTATE_NORMAL;
|
|
||||||
_display();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void I2CRailcom::_loop(unsigned long currentMicros) {
|
|
||||||
// Read responses from device
|
|
||||||
if (_deviceState!=DEVSTATE_NORMAL) return;
|
|
||||||
|
|
||||||
// return if in cutout or cutout very soon.
|
|
||||||
if (!DCCWaveform::isRailcomSampleWindow()) return;
|
|
||||||
|
|
||||||
// IF we have 2 channels, flip channels each loop
|
|
||||||
if (_nPins>1) _UART_CH=_UART_CH?0:1;
|
|
||||||
|
|
||||||
// have we read this cutout already?
|
|
||||||
auto cut=DCCWaveform::getRailcomCutoutCounter();
|
|
||||||
if (cutoutCounter[_UART_CH]==cut) return;
|
|
||||||
cutoutCounter[_UART_CH]=cut;
|
|
||||||
|
|
||||||
// Read incoming raw Railcom data, and process accordingly
|
|
||||||
|
|
||||||
auto inlength = UART_ReadRegister(REG_RXLV);
|
|
||||||
|
|
||||||
if (inlength> sizeof(_inbuf)) inlength=sizeof(_inbuf);
|
|
||||||
_inbuf[0]=0;
|
|
||||||
if (inlength>0) {
|
|
||||||
// Read data buffer from UART
|
|
||||||
_outbuf[0]=(byte)(REG_RHR << 3 | _UART_CH << 1);
|
|
||||||
I2CManager.read(_I2CAddress, _inbuf, inlength, _outbuf, 1);
|
|
||||||
}
|
|
||||||
// HK: Reset FIFO at end of read cycle
|
|
||||||
UART_WriteRegister(REG_FCR, 0x07,false);
|
|
||||||
|
|
||||||
// Ask Railcom to interpret the raw data
|
|
||||||
_channelMonitors[_UART_CH]->process(_inbuf,inlength);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void I2CRailcom::_display() {
|
|
||||||
DIAG(F("I2CRailcom: Configured on Vpins:%u-%u %S"), _firstVpin, _firstVpin+_nPins-1,
|
|
||||||
(_deviceState!=DEVSTATE_NORMAL) ? F("OFFLINE") : F(""));
|
|
||||||
}
|
|
||||||
|
|
||||||
// SC16IS752 functions
|
|
||||||
// Initialise SC16IS752 only for this channel
|
|
||||||
// First a software reset
|
|
||||||
// Enable FIFO and clear TX & RX FIFO
|
|
||||||
// Need to set the following registers
|
|
||||||
// IOCONTROL set bit 1 and 2 to 0 indicating that they are GPIO
|
|
||||||
// IODIR set all bit to 1 indicating al are output
|
|
||||||
// IOSTATE set only bit 0 to 1 for UART 0, or only bit 1 for UART 1 //
|
|
||||||
// LCR bit 7=0 divisor latch (clock division registers DLH & DLL, they store 16 bit divisor),
|
|
||||||
// WORD_LEN, STOP_BIT, PARITY_ENA and PARITY_TYPE
|
|
||||||
// MCR bit 7=0 clock divisor devide-by-1 clock input
|
|
||||||
// DLH most significant part of divisor
|
|
||||||
// DLL least significant part of divisor
|
|
||||||
//
|
|
||||||
// BAUD_RATE, WORD_LEN, STOP_BIT, PARITY_ENA and PARITY_TYPE have been defined and initialized
|
|
||||||
//
|
|
||||||
// Communication parameters 8 bit, No parity, 1 stopbit
|
|
||||||
static const uint8_t WORD_LEN = 0x03; // Value LCR bit 0,1
|
|
||||||
static const uint8_t STOP_BIT = 0x00; // Value LCR bit 2
|
|
||||||
static const uint8_t PARITY_ENA = 0x00; // Value LCR bit 3
|
|
||||||
static const uint8_t PARITY_TYPE = 0x00; // Value LCR bit 4
|
|
||||||
static const uint32_t BAUD_RATE = 250000;
|
|
||||||
static const uint8_t PRESCALER = 0x01; // Value MCR bit 7
|
|
||||||
static const unsigned long SC16IS752_XTAL_FREQ_RAILCOM = 16000000; // Baud rate for Railcom signal
|
|
||||||
static const uint16_t _divisor = (SC16IS752_XTAL_FREQ_RAILCOM / PRESCALER) / (BAUD_RATE * 16);
|
|
||||||
|
|
||||||
void I2CRailcom::Init_SC16IS752(){
|
|
||||||
if (_UART_CH==0) { // HK: Currently fixed on ch 0
|
|
||||||
// only reset on channel 0}
|
|
||||||
UART_WriteRegister(REG_IOCONTROL, 0x08,false); // UART Software reset
|
|
||||||
//_deviceState=DEVSTATE_INITIALISING; // ignores error during reset which seems normal. // HK: this line is moved to below
|
|
||||||
auto iocontrol_readback = UART_ReadRegister(REG_IOCONTROL);
|
|
||||||
if (iocontrol_readback == 0x00){
|
|
||||||
_deviceState=DEVSTATE_INITIALISING;
|
|
||||||
DIAG(F("I2CRailcom: %s SRESET readback: 0x%x"),_I2CAddress.toString(), iocontrol_readback);
|
|
||||||
} else {
|
|
||||||
DIAG(F("I2CRailcom: %s SRESET: 0x%x"),_I2CAddress.toString(), iocontrol_readback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// HK:
|
|
||||||
// You write 0x08 to the IOCONTROL register, setting bit 3 (SRESET), as per datasheet 8.18:
|
|
||||||
// "Software Reset. A write to this bit will reset the device. Once the
|
|
||||||
// device is reset this bit is automatically set to logic 0"
|
|
||||||
// So you can not readback the val you have written as this has changed.
|
|
||||||
// I've added an extra UART_ReadRegister(REG_IOCONTROL) and check if the return value is 0x00
|
|
||||||
// then set _deviceState=DEVSTATE_INITIALISING;
|
|
||||||
|
|
||||||
|
|
||||||
// HK: only do clear FIFO at end of Init_SC16IS752
|
|
||||||
//UART_WriteRegister(REG_FCR, 0x07,false); // Reset FIFO, clear RX & TX FIFO (write only)
|
|
||||||
|
|
||||||
UART_WriteRegister(REG_MCR, 0x00); // Set MCR to all 0, includes Clock divisor
|
|
||||||
|
|
||||||
//UART_WriteRegister(REG_LCR, 0x80); // Divisor latch enabled
|
|
||||||
|
|
||||||
UART_WriteRegister(REG_LCR, 0x80 | WORD_LEN | STOP_BIT | PARITY_ENA | PARITY_TYPE); // Divisor latch enabled and comm parameters set
|
|
||||||
UART_WriteRegister(REG_DLL, (uint8_t)_divisor); // Write DLL
|
|
||||||
UART_WriteRegister(REG_DLH, (uint8_t)(_divisor >> 8)); // Write DLH
|
|
||||||
auto lcr_readback = UART_ReadRegister(REG_LCR);
|
|
||||||
lcr_readback = lcr_readback & 0x7F;
|
|
||||||
UART_WriteRegister(REG_LCR, lcr_readback); // Divisor latch disabled
|
|
||||||
|
|
||||||
//UART_WriteRegister(REG_LCR, WORD_LEN | STOP_BIT | PARITY_ENA | PARITY_TYPE); // Divisor latch disabled
|
|
||||||
|
|
||||||
UART_WriteRegister(REG_FCR, 0x07,false); // Reset FIFO, clear RX & TX FIFO (write only)
|
|
||||||
|
|
||||||
#ifdef DIAG_I2CRailcom
|
|
||||||
// HK: Test to see if internal loopback works and if REG_RXLV increment to at least 0x01
|
|
||||||
// Set REG_MCR bit 4 to 1, Enable Loopback
|
|
||||||
UART_WriteRegister(REG_MCR, 0x10);
|
|
||||||
UART_WriteRegister(REG_THR, 0x88, false); // Send 0x88
|
|
||||||
auto inlen = UART_ReadRegister(REG_RXLV);
|
|
||||||
if (inlen == 0){
|
|
||||||
DIAG(F("I2CRailcom: Loopback test: %s/%d failed"),_I2CAddress.toString(), _UART_CH);
|
|
||||||
} else {
|
|
||||||
DIAG(F("Railcom: Loopback test: %s/%d RX Fifo lvl: 0x%x"),_I2CAddress.toString(), _UART_CH, inlen);
|
|
||||||
_outbuf[0]=(byte)(REG_RHR << 3 | _UART_CH << 1);
|
|
||||||
I2CManager.read(_I2CAddress, _inbuf, inlen, _outbuf, 1);
|
|
||||||
#ifdef DIAG_I2CRailcom_data
|
|
||||||
DIAG(F("Railcom: Loopback test: %s/%d RX FIFO Data"), _I2CAddress.toString(), _UART_CH);
|
|
||||||
for (int i = 0; i < inlen; i++){
|
|
||||||
DIAG(F("Railcom: Loopback data [0x%x]: 0x%x"), i, _inbuf[i]);
|
|
||||||
//DIAG(F("[0x%x]: 0x%x"), i, _inbuf[i]);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
UART_WriteRegister(REG_MCR, 0x00); // Set REG_MCR back to 0x00
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef DIAG_I2CRailcom
|
|
||||||
// Sent some data to check if UART baudrate is set correctly, check with logic analyzer on TX pin
|
|
||||||
UART_WriteRegister(REG_THR, 9, false);
|
|
||||||
DIAG(F("I2CRailcom: UART %s/%d Test TX = 0x09"),_I2CAddress.toString(), _UART_CH);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (_deviceState==DEVSTATE_INITIALISING) {
|
|
||||||
DIAG(F("I2CRailcom: UART %d init complete"),_UART_CH);
|
|
||||||
}
|
|
||||||
// HK: final FIFO reset
|
|
||||||
UART_WriteRegister(REG_FCR, 0x07,false); // Reset FIFO, clear RX & TX FIFO (write only)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void I2CRailcom::UART_WriteRegister(uint8_t reg, uint8_t val, bool readback){
|
|
||||||
_outbuf[0] = (byte)( reg << 3 | _UART_CH << 1);
|
|
||||||
_outbuf[1]=val;
|
|
||||||
auto status=I2CManager.write(_I2CAddress, _outbuf, (uint8_t)2);
|
|
||||||
if(status!=I2C_STATUS_OK) {
|
|
||||||
DIAG(F("I2CRailcom: %s/%d write reg=0x%x,data=0x%x,I2Cstate=%d"),
|
|
||||||
_I2CAddress.toString(), _UART_CH, reg, val, status);
|
|
||||||
_deviceState=DEVSTATE_FAILED;
|
|
||||||
}
|
|
||||||
if (readback) { // Read it back to cross check
|
|
||||||
auto readback=UART_ReadRegister(reg);
|
|
||||||
if (readback!=val) {
|
|
||||||
DIAG(F("I2CRailcom readback: %s/%d reg:0x%x write=0x%x read=0x%x"),_I2CAddress.toString(),_UART_CH,reg,val,readback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
uint8_t I2CRailcom::UART_ReadRegister(uint8_t reg){
|
|
||||||
_outbuf[0] = (byte)(reg << 3 | _UART_CH << 1); // _outbuffer[0] has now UART_REG and UART_CH
|
|
||||||
_inbuf[0]=0;
|
|
||||||
auto status=I2CManager.read(_I2CAddress, _inbuf, 1, _outbuf, 1);
|
|
||||||
if (status!=I2C_STATUS_OK) {
|
|
||||||
DIAG(F("I2CRailcom read: %s/%d read reg=0x%x,I2Cstate=%d"),
|
|
||||||
_I2CAddress.toString(), _UART_CH, reg, status);
|
|
||||||
_deviceState=DEVSTATE_FAILED;
|
|
||||||
}
|
|
||||||
return _inbuf[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// SC16IS752 General register set (from the datasheet)
|
|
||||||
enum : uint8_t {
|
|
||||||
REG_RHR = 0x00, // FIFO Read
|
|
||||||
REG_THR = 0x00, // FIFO Write
|
|
||||||
REG_IER = 0x01, // Interrupt Enable Register R/W
|
|
||||||
REG_FCR = 0x02, // FIFO Control Register Write
|
|
||||||
REG_IIR = 0x02, // Interrupt Identification Register Read
|
|
||||||
REG_LCR = 0x03, // Line Control Register R/W
|
|
||||||
REG_MCR = 0x04, // Modem Control Register R/W
|
|
||||||
REG_LSR = 0x05, // Line Status Register Read
|
|
||||||
REG_MSR = 0x06, // Modem Status Register Read
|
|
||||||
REG_SPR = 0x07, // Scratchpad Register R/W
|
|
||||||
REG_TCR = 0x06, // Transmission Control Register R/W
|
|
||||||
REG_TLR = 0x07, // Trigger Level Register R/W
|
|
||||||
REG_TXLV = 0x08, // Transmitter FIFO Level register Read
|
|
||||||
REG_RXLV = 0x09, // Receiver FIFO Level register Read
|
|
||||||
REG_IODIR = 0x0A, // Programmable I/O pins Direction register R/W
|
|
||||||
REG_IOSTATE = 0x0B, // Programmable I/O pins State register R/W
|
|
||||||
REG_IOINTENA = 0x0C, // I/O Interrupt Enable register R/W
|
|
||||||
REG_IOCONTROL = 0x0E, // I/O Control register R/W
|
|
||||||
REG_EFCR = 0x0F, // Extra Features Control Register R/W
|
|
||||||
};
|
|
||||||
|
|
||||||
// SC16IS752 Special register set
|
|
||||||
enum : uint8_t{
|
|
||||||
REG_DLL = 0x00, // Division registers R/W
|
|
||||||
REG_DLH = 0x01, // Division registers R/W
|
|
||||||
};
|
|
||||||
|
|
||||||
// SC16IS752 Enhanced regiter set
|
|
||||||
enum : uint8_t{
|
|
||||||
REG_EFR = 0X02, // Enhanced Features Register R/W
|
|
||||||
REG_XON1 = 0x04, // R/W
|
|
||||||
REG_XON2 = 0x05, // R/W
|
|
||||||
REG_XOFF1 = 0x06, // R/W
|
|
||||||
REG_XOFF2 = 0x07, // R/W
|
|
||||||
};
|
|
||||||
|
|
145
IO_I2CRailcom.h
145
IO_I2CRailcom.h
@ -1,145 +0,0 @@
|
|||||||
/*
|
|
||||||
* © 2024, Henk Kruisbrink & Chris Harlow. All rights reserved.
|
|
||||||
* © 2023, Neil McKechnie. All rights reserved.
|
|
||||||
*
|
|
||||||
* This file is part of DCC++EX API
|
|
||||||
*
|
|
||||||
* This is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* It is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Dec 2023, Added NXP SC16IS752 I2C Dual UART
|
|
||||||
* The SC16IS752 has 64 bytes TX & RX FIFO buffer
|
|
||||||
* First version without interrupts from I2C UART and only RX/TX are used, interrupts may not be
|
|
||||||
* needed as the RX Fifo holds the reply
|
|
||||||
*
|
|
||||||
* Jan 2024, Issue with using both UARTs simultaniously, the secod uart seems to work but the first transmit
|
|
||||||
* corrupt data. This need more analysis and experimenatation.
|
|
||||||
* Will push this driver to the dev branch with the uart fixed to 0
|
|
||||||
* Both SC16IS750 (single uart) and SC16IS752 (dual uart, but only uart 0 is enable)
|
|
||||||
*
|
|
||||||
* myHall.cpp configuration syntax:
|
|
||||||
*
|
|
||||||
* I2CRailcom::create(1st vPin, vPins, I2C address);
|
|
||||||
*
|
|
||||||
* myAutomation configuration
|
|
||||||
* HAL(I2CRailcom, 1st vPin, vPins, I2C address)
|
|
||||||
* Parameters:
|
|
||||||
* 1st vPin : First virtual pin that EX-Rail can control to play a sound, use PLAYSOUND command (alias of ANOUT)
|
|
||||||
* vPins : Total number of virtual pins allocated (2 vPins are supported, one for each UART)
|
|
||||||
* 1st vPin for UART 0, 2nd for UART 1
|
|
||||||
* I2C Address : I2C address of the serial controller, in 0x format
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef IO_I2CRailcom_h
|
|
||||||
#define IO_I2CRailcom_h
|
|
||||||
#include "Arduino.h"
|
|
||||||
#include "Railcom.h"
|
|
||||||
|
|
||||||
// Debug and diagnostic defines, enable too many will result in slowing the driver
|
|
||||||
#define DIAG_I2CRailcom
|
|
||||||
|
|
||||||
class I2CRailcom : public IODevice {
|
|
||||||
private:
|
|
||||||
// SC16IS752 defines
|
|
||||||
uint8_t _UART_CH=0x00; // channel 0 or 1 flips each loop if npins>1
|
|
||||||
byte _inbuf[12];
|
|
||||||
byte _outbuf[2];
|
|
||||||
byte cutoutCounter[2];
|
|
||||||
Railcom * _channelMonitors[2];
|
|
||||||
public:
|
|
||||||
// Constructor
|
|
||||||
I2CRailcom(VPIN firstVpin, int nPins, I2CAddress i2cAddress);
|
|
||||||
|
|
||||||
static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) ;
|
|
||||||
|
|
||||||
void _begin() ;
|
|
||||||
void _loop(unsigned long currentMicros) override ;
|
|
||||||
void _display() override ;
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
|
|
||||||
// SC16IS752 functions
|
|
||||||
// Initialise SC16IS752 only for this channel
|
|
||||||
// First a software reset
|
|
||||||
// Enable FIFO and clear TX & RX FIFO
|
|
||||||
// Need to set the following registers
|
|
||||||
// IOCONTROL set bit 1 and 2 to 0 indicating that they are GPIO
|
|
||||||
// IODIR set all bit to 1 indicating al are output
|
|
||||||
// IOSTATE set only bit 0 to 1 for UART 0, or only bit 1 for UART 1 //
|
|
||||||
// LCR bit 7=0 divisor latch (clock division registers DLH & DLL, they store 16 bit divisor),
|
|
||||||
// WORD_LEN, STOP_BIT, PARITY_ENA and PARITY_TYPE
|
|
||||||
// MCR bit 7=0 clock divisor devide-by-1 clock input
|
|
||||||
// DLH most significant part of divisor
|
|
||||||
// DLL least significant part of divisor
|
|
||||||
//
|
|
||||||
// BAUD_RATE, WORD_LEN, STOP_BIT, PARITY_ENA and PARITY_TYPE have been defined and initialized
|
|
||||||
//
|
|
||||||
// Communication parameters 8 bit, No parity, 1 stopbit
|
|
||||||
static const uint8_t WORD_LEN = 0x03; // Value LCR bit 0,1
|
|
||||||
static const uint8_t STOP_BIT = 0x00; // Value LCR bit 2
|
|
||||||
static const uint8_t PARITY_ENA = 0x00; // Value LCR bit 3
|
|
||||||
static const uint8_t PARITY_TYPE = 0x00; // Value LCR bit 4
|
|
||||||
static const uint32_t BAUD_RATE = 250000;
|
|
||||||
static const uint8_t PRESCALER = 0x01; // Value MCR bit 7
|
|
||||||
static const unsigned long SC16IS752_XTAL_FREQ_RAILCOM = 16000000; // Baud rate for Railcom signal
|
|
||||||
static const uint16_t _divisor = (SC16IS752_XTAL_FREQ_RAILCOM / PRESCALER) / (BAUD_RATE * 16);
|
|
||||||
|
|
||||||
void Init_SC16IS752();
|
|
||||||
void UART_WriteRegister(uint8_t reg, uint8_t val, bool readback=true);
|
|
||||||
uint8_t UART_ReadRegister(uint8_t reg);
|
|
||||||
|
|
||||||
// SC16IS752 General register set (from the datasheet)
|
|
||||||
enum : uint8_t {
|
|
||||||
REG_RHR = 0x00, // FIFO Read
|
|
||||||
REG_THR = 0x00, // FIFO Write
|
|
||||||
REG_IER = 0x01, // Interrupt Enable Register R/W
|
|
||||||
REG_FCR = 0x02, // FIFO Control Register Write
|
|
||||||
REG_IIR = 0x02, // Interrupt Identification Register Read
|
|
||||||
REG_LCR = 0x03, // Line Control Register R/W
|
|
||||||
REG_MCR = 0x04, // Modem Control Register R/W
|
|
||||||
REG_LSR = 0x05, // Line Status Register Read
|
|
||||||
REG_MSR = 0x06, // Modem Status Register Read
|
|
||||||
REG_SPR = 0x07, // Scratchpad Register R/W
|
|
||||||
REG_TCR = 0x06, // Transmission Control Register R/W
|
|
||||||
REG_TLR = 0x07, // Trigger Level Register R/W
|
|
||||||
REG_TXLV = 0x08, // Transmitter FIFO Level register Read
|
|
||||||
REG_RXLV = 0x09, // Receiver FIFO Level register Read
|
|
||||||
REG_IODIR = 0x0A, // Programmable I/O pins Direction register R/W
|
|
||||||
REG_IOSTATE = 0x0B, // Programmable I/O pins State register R/W
|
|
||||||
REG_IOINTENA = 0x0C, // I/O Interrupt Enable register R/W
|
|
||||||
REG_IOCONTROL = 0x0E, // I/O Control register R/W
|
|
||||||
REG_EFCR = 0x0F, // Extra Features Control Register R/W
|
|
||||||
};
|
|
||||||
|
|
||||||
// SC16IS752 Special register set
|
|
||||||
enum : uint8_t{
|
|
||||||
REG_DLL = 0x00, // Division registers R/W
|
|
||||||
REG_DLH = 0x01, // Division registers R/W
|
|
||||||
};
|
|
||||||
|
|
||||||
// SC16IS752 Enhanced regiter set
|
|
||||||
enum : uint8_t{
|
|
||||||
REG_EFR = 0X02, // Enhanced Features Register R/W
|
|
||||||
REG_XON1 = 0x04, // R/W
|
|
||||||
REG_XON2 = 0x05, // R/W
|
|
||||||
REG_XOFF1 = 0x06, // R/W
|
|
||||||
REG_XOFF2 = 0x07, // R/W
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // IO_I2CRailcom_h
|
|
@ -22,7 +22,6 @@
|
|||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "IODevice.h"
|
#include "IODevice.h"
|
||||||
#include "IO_TM1638.h"
|
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
|
|
||||||
|
|
||||||
|
276
Railcom.cpp
276
Railcom.cpp
@ -1,276 +0,0 @@
|
|||||||
/*
|
|
||||||
* SEE ADDITIONAL COPYRIGHT ATTRIBUTION BELOW
|
|
||||||
* © 2024 Chris Harlow
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* This file is part of DCC-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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** Sections of this code (the decode table constants)
|
|
||||||
* are taken from openmrn
|
|
||||||
* https://github.com/bakerstu/openmrn/blob/master/src/dcc/RailCom.cxx
|
|
||||||
* under the following copyright.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2014, Balazs Racz
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* - Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* - Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
||||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
||||||
* POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
**/
|
|
||||||
|
|
||||||
#include "Railcom.h"
|
|
||||||
#include "defines.h"
|
|
||||||
#include "FSH.h"
|
|
||||||
#include "DCC.h"
|
|
||||||
#include "DIAG.h"
|
|
||||||
#include "DCCWaveform.h"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** Table for 8-to-6 decoding of railcom data. This table can be indexed by the
|
|
||||||
* 8-bit value read from the railcom channel, and the return value will be
|
|
||||||
* either a 6-bit number, or one of the defined Railcom constrantrs. If the
|
|
||||||
* value is invalid, the INV constant is returned. */
|
|
||||||
|
|
||||||
// These values appear in the railcom_decode table to mean special symbols.
|
|
||||||
static constexpr uint8_t
|
|
||||||
// highest valid 6-bit value
|
|
||||||
MAX_VALID = 0x3F,
|
|
||||||
/// invalid value (not conforming to the 4bit weighting requirement)
|
|
||||||
INV = 0xff,
|
|
||||||
/// Railcom ACK; the decoder received the message ok. NOTE: There are
|
|
||||||
/// two codepoints that map to this.
|
|
||||||
ACK = 0xfe,
|
|
||||||
/// The decoder rejected the packet.
|
|
||||||
NACK = 0xfd,
|
|
||||||
/// The decoder is busy; send the packet again. This is typically
|
|
||||||
/// returned when a POM CV write is still pending; the caller must
|
|
||||||
/// re-try sending the packet later.
|
|
||||||
RCBUSY = 0xfc,
|
|
||||||
|
|
||||||
/// Reserved for future expansion.
|
|
||||||
RESVD1 = 0xfb,
|
|
||||||
/// Reserved for future expansion.
|
|
||||||
RESVD2 = 0xfa;
|
|
||||||
|
|
||||||
const uint8_t HIGHFLASH decode[256] =
|
|
||||||
// 0|8 1|9 2|a 3|b 4|c 5|d 6|e 7|f
|
|
||||||
{ INV, INV, INV, INV, INV, INV, INV, INV, // 0
|
|
||||||
INV, INV, INV, INV, INV, INV, INV, ACK, // 0
|
|
||||||
INV, INV, INV, INV, INV, INV, INV, 0x33, // 1
|
|
||||||
INV, INV, INV, 0x34, INV, 0x35, 0x36, INV, // 1
|
|
||||||
INV, INV, INV, INV, INV, INV, INV, 0x3A, // 2
|
|
||||||
INV, INV, INV, 0x3B, INV, 0x3C, 0x37, INV, // 2
|
|
||||||
INV, INV, INV, 0x3F, INV, 0x3D, 0x38, INV, // 3
|
|
||||||
INV, 0x3E, 0x39, INV, NACK, INV, INV, INV, // 3
|
|
||||||
INV, INV, INV, INV, INV, INV, INV, 0x24, // 4
|
|
||||||
INV, INV, INV, 0x23, INV, 0x22, 0x21, INV, // 4
|
|
||||||
INV, INV, INV, 0x1F, INV, 0x1E, 0x20, INV, // 5
|
|
||||||
INV, 0x1D, 0x1C, INV, 0x1B, INV, INV, INV, // 5
|
|
||||||
INV, INV, INV, 0x19, INV, 0x18, 0x1A, INV, // 6
|
|
||||||
INV, 0x17, 0x16, INV, 0x15, INV, INV, INV, // 6
|
|
||||||
INV, 0x25, 0x14, INV, 0x13, INV, INV, INV, // 7
|
|
||||||
0x32, INV, INV, INV, INV, INV, INV, INV, // 7
|
|
||||||
INV, INV, INV, INV, INV, INV, INV, RESVD2, // 8
|
|
||||||
INV, INV, INV, 0x0E, INV, 0x0D, 0x0C, INV, // 8
|
|
||||||
INV, INV, INV, 0x0A, INV, 0x09, 0x0B, INV, // 9
|
|
||||||
INV, 0x08, 0x07, INV, 0x06, INV, INV, INV, // 9
|
|
||||||
INV, INV, INV, 0x04, INV, 0x03, 0x05, INV, // a
|
|
||||||
INV, 0x02, 0x01, INV, 0x00, INV, INV, INV, // a
|
|
||||||
INV, 0x0F, 0x10, INV, 0x11, INV, INV, INV, // b
|
|
||||||
0x12, INV, INV, INV, INV, INV, INV, INV, // b
|
|
||||||
INV, INV, INV, RESVD1, INV, 0x2B, 0x30, INV, // c
|
|
||||||
INV, 0x2A, 0x2F, INV, 0x31, INV, INV, INV, // c
|
|
||||||
INV, 0x29, 0x2E, INV, 0x2D, INV, INV, INV, // d
|
|
||||||
0x2C, INV, INV, INV, INV, INV, INV, INV, // d
|
|
||||||
INV, RCBUSY, 0x28, INV, 0x27, INV, INV, INV, // e
|
|
||||||
0x26, INV, INV, INV, INV, INV, INV, INV, // e
|
|
||||||
ACK, INV, INV, INV, INV, INV, INV, INV, // f
|
|
||||||
INV, INV, INV, INV, INV, INV, INV, INV, // f
|
|
||||||
};
|
|
||||||
/// Packet identifiers from Mobile Decoders.
|
|
||||||
enum RailcomMobilePacketId
|
|
||||||
{
|
|
||||||
RMOB_POM = 0,
|
|
||||||
RMOB_ADRHIGH = 1,
|
|
||||||
RMOB_ADRLOW = 2,
|
|
||||||
RMOB_EXT = 3,
|
|
||||||
RMOB_DYN = 7,
|
|
||||||
RMOB_XPOM0 = 8,
|
|
||||||
RMOB_XPOM1 = 9,
|
|
||||||
RMOB_XPOM2 = 10,
|
|
||||||
RMOB_XPOM3 = 11,
|
|
||||||
RMOB_SUBID = 12,
|
|
||||||
RMOB_LOGON_ASSIGN_FEEDBACK = 13,
|
|
||||||
RMOB_LOGON_ENABLE_FEEDBACK = 15,
|
|
||||||
};
|
|
||||||
|
|
||||||
// each railcom block is represented by an instance of this class.
|
|
||||||
// The blockvpin is the vpin associated with this block for the purposes of
|
|
||||||
// a HAL driver for the railcom detection and the EXRAIL ONBLOCKENTER/ONBLOCKEXIT
|
|
||||||
|
|
||||||
// need to know if there is any detector Railcom detector
|
|
||||||
// otherwise <r cab cv> would block the async reply feature.
|
|
||||||
bool Railcom::hasActiveDetectors=false;
|
|
||||||
|
|
||||||
Railcom::Railcom(uint16_t blockvpin) {
|
|
||||||
DIAG(F("Create Railcom block %d"),blockvpin);
|
|
||||||
haveHigh=false;
|
|
||||||
haveLow=false;
|
|
||||||
packetsWithNoData=0;
|
|
||||||
lastChannel1Loco=0;
|
|
||||||
vpin=blockvpin;
|
|
||||||
}
|
|
||||||
uint16_t Railcom::expectLoco=0;
|
|
||||||
uint16_t Railcom::expectCV=0;
|
|
||||||
unsigned long Railcom::expectWait=0;
|
|
||||||
ACK_CALLBACK Railcom::expectCallback=0;
|
|
||||||
|
|
||||||
|
|
||||||
// Process is called by a raw data collector.
|
|
||||||
void Railcom::process(uint8_t * inbound, uint8_t length) {
|
|
||||||
hasActiveDetectors=true;
|
|
||||||
|
|
||||||
|
|
||||||
if (length<2 || (inbound[0]==0 && inbound[1]==0)) {
|
|
||||||
noData();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (Diag::RAILCOM) {
|
|
||||||
static const char hexchars[]="0123456789ABCDEF";
|
|
||||||
if (length>2) {
|
|
||||||
USB_SERIAL.print(F("<*R "));
|
|
||||||
for (byte i=0;i<length;i++) {
|
|
||||||
if (i==2) Serial.write(' ');
|
|
||||||
USB_SERIAL.write(hexchars[inbound[i]>>4]);
|
|
||||||
USB_SERIAL.write(hexchars[inbound[i]& 0x0F ]);
|
|
||||||
}
|
|
||||||
USB_SERIAL.print(F(" *>\n"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expectCV && DCCWaveform::getRailcomLastLocoAddress()==expectLoco) {
|
|
||||||
if (length>=4) {
|
|
||||||
auto v2=GETHIGHFLASH(decode,inbound[2]);
|
|
||||||
auto v3=GETHIGHFLASH(decode,inbound[3]);
|
|
||||||
uint16_t packet=(v2<<6) | (v3 & 0x3f);
|
|
||||||
// packet is 12 bits TTTTDDDDDDDD
|
|
||||||
byte type=(packet>>8) & 0x0F;
|
|
||||||
byte data= packet & 0xFF;
|
|
||||||
if (type==RMOB_POM) {
|
|
||||||
// DIAG(F("POM READ loco=%d cv(%d)=%d/0x%x"), expectLoco, expectCV,data,data);
|
|
||||||
expectCallback(data);
|
|
||||||
expectCV=0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expectCV && (millis()-expectWait)> POM_READ_TIMEOUT) { // still waiting
|
|
||||||
expectCallback(-1);
|
|
||||||
expectCV=0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
auto v1=GETHIGHFLASH(decode,inbound[0]);
|
|
||||||
auto v2=(length>1) ? GETHIGHFLASH(decode,inbound[1]):INV;
|
|
||||||
uint16_t packet=(v1<<6) | (v2 & 0x3f);
|
|
||||||
// packet is 12 bits TTTTDDDDDDDD
|
|
||||||
byte type=(packet>>8) & 0x0F;
|
|
||||||
byte data= packet & 0xFF;
|
|
||||||
if (type==RMOB_ADRHIGH) {
|
|
||||||
holdoverHigh=data;
|
|
||||||
haveHigh=true;
|
|
||||||
packetsWithNoData=0;
|
|
||||||
}
|
|
||||||
else if (type==RMOB_ADRLOW) {
|
|
||||||
holdoverLow=data;
|
|
||||||
haveLow=true;
|
|
||||||
packetsWithNoData=0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// channel1 is unreadable or not loco address so maybe multiple locos in block
|
|
||||||
if (length>2 && GETHIGHFLASH(decode,inbound[0])!=INV) {
|
|
||||||
// it looks like we have channel2 data
|
|
||||||
auto thisLoco=DCCWaveform::getRailcomLastLocoAddress();
|
|
||||||
if (Diag::RAILCOM) DIAG(F("c2=%d"),thisLoco);
|
|
||||||
if (thisLoco==lastChannel1Loco) return;
|
|
||||||
if (thisLoco) DCC::setLocoInBlock(thisLoco,vpin,false); // this loco is in block, but not exclusive
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// channel1 no good and no channel2
|
|
||||||
noData();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (haveHigh && haveLow) {
|
|
||||||
uint16_t thisLoco=((holdoverHigh<<8)| holdoverLow) & 0x7FFF; // drop top bit
|
|
||||||
if (thisLoco!=lastChannel1Loco) {
|
|
||||||
// the exclusive DCC call is quite expensive, we dont want to call it every packet
|
|
||||||
if (Diag::RAILCOM) DIAG(F("h=%x l=%xc1=%d"),holdoverHigh, holdoverLow,thisLoco);
|
|
||||||
DCC::setLocoInBlock(thisLoco,vpin,true); // only this loco is in block
|
|
||||||
lastChannel1Loco=thisLoco;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Railcom::noData() {
|
|
||||||
if (packetsWithNoData>MAX_WAIT_FOR_GLITCH) return;
|
|
||||||
if (packetsWithNoData==MAX_WAIT_FOR_GLITCH) {
|
|
||||||
// treat as no loco
|
|
||||||
haveHigh=false;
|
|
||||||
haveLow=false;
|
|
||||||
lastChannel1Loco=0;
|
|
||||||
// Previous locos (if any) is exiting block
|
|
||||||
DCC::clearBlock(vpin);
|
|
||||||
}
|
|
||||||
packetsWithNoData++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// anticipate is used when waiting for a CV read from a railcom loco
|
|
||||||
void Railcom::anticipate(uint16_t loco, uint16_t cv, ACK_CALLBACK callback) {
|
|
||||||
if (!hasActiveDetectors) {
|
|
||||||
// if there are no active railcom detectors, this will
|
|
||||||
// not be timed out in process()... so deny it now.
|
|
||||||
callback(-2);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
expectLoco=loco;
|
|
||||||
expectCV=cv;
|
|
||||||
expectWait=millis(); // start of timeout
|
|
||||||
expectCallback=callback;
|
|
||||||
};
|
|
53
Railcom.h
53
Railcom.h
@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* © 2024 Chris Harlow
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* This file is part of DCC-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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef Railcom_h
|
|
||||||
#define Railcom_h
|
|
||||||
#include "Arduino.h"
|
|
||||||
|
|
||||||
typedef void (*ACK_CALLBACK)(int16_t result);
|
|
||||||
|
|
||||||
class Railcom {
|
|
||||||
public:
|
|
||||||
Railcom(uint16_t vpin);
|
|
||||||
|
|
||||||
/* Process returns -1: Call again next packet
|
|
||||||
0: No loco on track
|
|
||||||
>0: loco id
|
|
||||||
*/
|
|
||||||
void process(uint8_t * inbound,uint8_t length);
|
|
||||||
static void anticipate(uint16_t loco, uint16_t cv, ACK_CALLBACK callback);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static bool hasActiveDetectors;
|
|
||||||
static const unsigned long POM_READ_TIMEOUT=500; // as per spec
|
|
||||||
static uint16_t expectCV,expectLoco;
|
|
||||||
static unsigned long expectWait;
|
|
||||||
static ACK_CALLBACK expectCallback;
|
|
||||||
void noData();
|
|
||||||
uint16_t vpin;
|
|
||||||
uint8_t holdoverHigh,holdoverLow;
|
|
||||||
bool haveHigh,haveLow;
|
|
||||||
uint8_t packetsWithNoData;
|
|
||||||
uint16_t lastChannel1Loco;
|
|
||||||
static const byte MAX_WAIT_FOR_GLITCH=20; // number of dead or empty packets before assuming loco=0
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,75 +0,0 @@
|
|||||||
Railcom implementation notes, Chris Harlow Oct 2024
|
|
||||||
|
|
||||||
Railcom support is in 3 parts
|
|
||||||
1. Generation of the DCC waveform with a Railcom cutout.
|
|
||||||
2. Accessing the railcom feedback from a loco using hardware detectors
|
|
||||||
3. Utilising the feedback to do something useful.
|
|
||||||
|
|
||||||
DCC Waveform Railcom cutout depends on using suitable motor shields (EX8874 primarily) as the standard Arduino shield is not suitable. (Too high resistance during cutout)
|
|
||||||
The choice of track management also depends on wiring all the MAIN tracks to use the same signal and brake pins. This allows separate track power management but prevents switching a single track from MAIN to PROG or DC...
|
|
||||||
Some CPUs require very specific choice of brake pins etc to match their internal timer register architecture.
|
|
||||||
|
|
||||||
- MEGA.. The default shield setting for an EX8874 is suitable for Railcom on Channel A (MAIN)
|
|
||||||
- ESP32 .. not yet supported.
|
|
||||||
- Nucleo ... TBA
|
|
||||||
|
|
||||||
Enabling the Railcom Cutout requires a `<C RAILCOM ON>` command. This can be added to myAutomation using `PARSE("<C RAILCOM ON>")`
|
|
||||||
Code to calculate the cutout position and provide synchronization for the sampling is in `DCCWaveform.cpp` (not ESP32)
|
|
||||||
and in general a global search for "railcom" will show all code changes that have been made to support this.
|
|
||||||
|
|
||||||
Code to actually implement the timing of the cutout is hihjly cpu dependent and can be found in gthe various implementations of `DCCTimer.h`. At this time only `DCCTimerAVR.cpp`has implemented this.
|
|
||||||
|
|
||||||
|
|
||||||
Reading Railcom data:
|
|
||||||
A new HAL handler (`IO_I2CRailcom.h`)has been added to process input from a 2-block railcom reader (Refer Henk) which operates as a 2 channel UART accessible over I2C. The reader(s) sit between the CS and the track and collect railcom data from locos during the cutout.
|
|
||||||
After the cutout the HAL driver reads the UARTs over I2C and passes the raw data to the CS logic (`Railcom.cpp`)for analysis.
|
|
||||||
|
|
||||||
Each 2-block reader is described in myAutomation like `HAL(I2CRailcom,10000,2,0x48)` which will assign 2 channels on i2c address 0x48 with vpin numbers 10000 and 10001. If you only use the first channel in the reader, just asign one pin instead of two.
|
|
||||||
(Implementation notes.. potentially other readers are possible with suitable HAL drivers. There are however several touch-points with the code DCC Waveform code which helps the HAL driver to understand when the data is safe to sample, and how to interpret responses when the sender is unknown. )
|
|
||||||
|
|
||||||
Making use of Railcom data
|
|
||||||
|
|
||||||
Exrail has two additional event handlers which can capture locos entering and exiting blocks. These handlers are started with the loco information already set, so for example:
|
|
||||||
```
|
|
||||||
ONBLOCKENTER(10000)
|
|
||||||
// a loco has entered block 10000
|
|
||||||
FON(0) // turn the light on
|
|
||||||
FON(1) // make a lot of noise
|
|
||||||
SPEED(20) // slow down
|
|
||||||
DONE
|
|
||||||
|
|
||||||
ONBLOCKEXIT(10000)
|
|
||||||
// a loco has left block 10000
|
|
||||||
FOFF(0) // turn the light off
|
|
||||||
FOFF(1) // stop the noise
|
|
||||||
SPEED(50) // speed up again
|
|
||||||
DONE
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that the Railcom interpretation code is capable of detecting multiple locos in the same block at the same time and will create separate exrail tasks for each one.
|
|
||||||
There is however one minor loophole in the block exit logic...
|
|
||||||
If THREE or more locos are in the same block and ONE of them leaves, then ONBLOCKEXIT will not fire until
|
|
||||||
EITHER - The leaving loco enters another railcom block
|
|
||||||
OR - only ONE loco remains in the block just left.
|
|
||||||
|
|
||||||
To further support block management in railcom, two additional serial commands are available
|
|
||||||
|
|
||||||
`<K block loco >` to simulate a loco entering a block, and trigger any ONBLOCKENTER
|
|
||||||
`<k block loco >` to simulate a loco leaving a block, and trigger and ONBLOCKEXIT
|
|
||||||
|
|
||||||
|
|
||||||
Reading CV values on MAIN.
|
|
||||||
|
|
||||||
Railcom allows for the facility to read loco cv values while on the main track. This is considerably faster than PROG track access but depends on the loco being in a Railcom monitored block.
|
|
||||||
|
|
||||||
To read from prog Track we use `<R cv>` response is `<r value>`
|
|
||||||
|
|
||||||
To read from main track use `<r loco cv>`
|
|
||||||
response is `<r loco cv value>`
|
|
||||||
|
|
||||||
|
|
||||||
Additional EXRAIL features in Railcom Branch:
|
|
||||||
- ESTAOPALL stops all locos immediately
|
|
||||||
- XPOM(cab,cv,value) POM write cv to sepcific loco
|
|
||||||
(POM(cv,value) already writes cv to current loco)
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
|||||||
New Momentum feature notes:
|
|
||||||
|
|
||||||
The command station can apply momentum to throttle movements in the same way that a standards compliant DCC decoder can be set to do. This momentum can be defaulted system wide and overridden on individual locos. It does not use or alter the loco CV values and so it also works when driving DC locos.
|
|
||||||
The momentum is applied regardless of the throttle type used (or even EXRAIL).
|
|
||||||
|
|
||||||
Momentum is specified in mS / throttle_step.
|
|
||||||
|
|
||||||
There is a new command `<m cabid accelerating [brake]>`
|
|
||||||
where the brake value defaults to the accelerating value.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
`<m 3 0>` sets loco 3 to no momentum.
|
|
||||||
`<m 3 21>` sets loco 3 to 21 mS/step.
|
|
||||||
`<m 3 21 42>` sets loco 3 to 21 mS/step accelerating and 42 mS/step when decelerating.
|
|
||||||
|
|
||||||
`<m 0 21>` sets the default momentum to 21mS/Step for all current and future locos that have not been specifically set.
|
|
||||||
`<m 3 -1>` sets loco 3 to track the default momentum value.
|
|
||||||
|
|
||||||
EXRAIL
|
|
||||||
A new macro `MOMENTUM(accel [, decel])` sets the momentum value of the current tasks loco ot the global default if loco=0.
|
|
||||||
|
|
||||||
Note: Setting Momentum 7,14,21 etc is similar in effect to setting a decoder CV03/CV04 to 1,2,3.
|
|
||||||
|
|
||||||
As an additional option, the momentum calculation is based on the
|
|
||||||
difference in throttle setting and actual speed. For example, the time taken to reach speed 50 from a standing start would be less if the throttle were set to speed 100, thus increasing the acceleration.
|
|
||||||
|
|
||||||
`<m LINEAR>` - acceleration is uniform up to selected throttle speed.
|
|
||||||
`<m POWER>` - acceleration depends on difference between loco speed and selected throttle speed.
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020=2025, Chris Harlow. All rights reserved.
|
* © 2020, Chris Harlow. All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC API
|
* This file is part of Asbelos DCC API
|
||||||
*
|
*
|
||||||
@ -27,8 +27,6 @@ bool Diag::WIFI=false;
|
|||||||
bool Diag::WITHROTTLE=false;
|
bool Diag::WITHROTTLE=false;
|
||||||
bool Diag::ETHERNET=false;
|
bool Diag::ETHERNET=false;
|
||||||
bool Diag::LCN=false;
|
bool Diag::LCN=false;
|
||||||
bool Diag::RAILCOM=false;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void StringFormatter::diag( const FSH* input...) {
|
void StringFormatter::diag( const FSH* input...) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020-2025, Chris Harlow. All rights reserved.
|
* © 2020, Chris Harlow. All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC API
|
* This file is part of Asbelos DCC API
|
||||||
*
|
*
|
||||||
@ -30,7 +30,6 @@ class Diag {
|
|||||||
static bool WITHROTTLE;
|
static bool WITHROTTLE;
|
||||||
static bool ETHERNET;
|
static bool ETHERNET;
|
||||||
static bool LCN;
|
static bool LCN;
|
||||||
static bool RAILCOM;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* © 2022-2025 Chris Harlow
|
* © 2022 Chris Harlow
|
||||||
* © 2022-2024 Harald Barth
|
* © 2022-2024 Harald Barth
|
||||||
* © 2023-2024 Paul M. Antoine
|
* © 2023-2024 Paul M. Antoine
|
||||||
* © 2024 Herb Morton
|
* © 2024 Herb Morton
|
||||||
@ -332,8 +332,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
|||||||
canDo &= track[t]->trackPWM;
|
canDo &= track[t]->trackPWM;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (canDo) DIAG(F("HA mode"));
|
if (!canDo) {
|
||||||
else {
|
|
||||||
// if we discover that HA mode was globally impossible
|
// if we discover that HA mode was globally impossible
|
||||||
// we must adjust the trackPWM capabilities
|
// we must adjust the trackPWM capabilities
|
||||||
FOR_EACH_TRACK(t) {
|
FOR_EACH_TRACK(t) {
|
||||||
@ -342,7 +341,6 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
|||||||
}
|
}
|
||||||
DCCTimer::clearPWM(); // has to be AFTER trackPWM changes because if trackPWM==true this is undone for that track
|
DCCTimer::clearPWM(); // has to be AFTER trackPWM changes because if trackPWM==true this is undone for that track
|
||||||
}
|
}
|
||||||
DCCWaveform::setRailcomPossible(canDo);
|
|
||||||
#else
|
#else
|
||||||
// For ESP32 we just reinitialize the DCC Waveform
|
// For ESP32 we just reinitialize the DCC Waveform
|
||||||
DCCWaveform::begin();
|
DCCWaveform::begin();
|
||||||
@ -381,7 +379,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TrackManager::applyDCSpeed(byte t) {
|
void TrackManager::applyDCSpeed(byte t) {
|
||||||
track[t]->setDCSignal(DCC::getLocoSpeedByte(trackDCAddr[t]),
|
track[t]->setDCSignal(DCC::getThrottleSpeedByte(trackDCAddr[t]),
|
||||||
DCC::getThrottleFrequency(trackDCAddr[t]));
|
DCC::getThrottleFrequency(trackDCAddr[t]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,10 +12,18 @@
|
|||||||
default_envs =
|
default_envs =
|
||||||
mega2560
|
mega2560
|
||||||
uno
|
uno
|
||||||
|
unowifiR2
|
||||||
nano
|
nano
|
||||||
|
samd21-dev-usb
|
||||||
|
samd21-zero-usb
|
||||||
ESP32
|
ESP32
|
||||||
Nucleo-F411RE
|
Nucleo-F411RE
|
||||||
Nucleo-F446RE
|
Nucleo-F446RE
|
||||||
|
Teensy3_2
|
||||||
|
Teensy3_5
|
||||||
|
Teensy3_6
|
||||||
|
Teensy4_0
|
||||||
|
Teensy4_1
|
||||||
src_dir = .
|
src_dir = .
|
||||||
include_dir = .
|
include_dir = .
|
||||||
|
|
||||||
|
18
version.h
18
version.h
@ -3,23 +3,7 @@
|
|||||||
|
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
|
|
||||||
#define VERSION "5.5.6"
|
#define VERSION "5.2.95"
|
||||||
// 5.5.6 - Fix ESP32 build bug caused by include reference loop
|
|
||||||
// 5.5.5 - Railcom implementation with IO_I2CRailcom driver
|
|
||||||
// - response analysis and block management.
|
|
||||||
// - <r locoid cv> POM read using Railcom.
|
|
||||||
// - See Release_notes/Railcom.md
|
|
||||||
// 5.5.4 - Split ESP32 from DCCWaveform to DCCWaveformRMT
|
|
||||||
// - Railcom Cutout control (DCCTimerAVR Mega only so far)
|
|
||||||
// 5.5.3 - EXRAIL ESTOPALL,XPOM,
|
|
||||||
// - Bugfix RESERVE to cause ESTOP.(was STOP)
|
|
||||||
// - Correct direction sync after manual throttle change.
|
|
||||||
// - plus ONBLOCKENTER/EXIT in preparation for Railcom
|
|
||||||
// 5.5.2 - DS1307 Real Time clock
|
|
||||||
// 5.5.1 - Momentum
|
|
||||||
// 5.5.0 - New version on devel
|
|
||||||
// 5.4.0 - New version on master
|
|
||||||
// 5.2.96 - EXRAIL additions XFWD() and XREV()
|
|
||||||
// 5.2.95 - Release candidate for 5.4
|
// 5.2.95 - Release candidate for 5.4
|
||||||
// 5.2.94 - Bugfix: Less confusion and simpler code around the RCN213 defines
|
// 5.2.94 - Bugfix: Less confusion and simpler code around the RCN213 defines
|
||||||
// 5.2.93 - Bugfix ESP32: clear progTrackSyncMain (join flag) when prog track is removed
|
// 5.2.93 - Bugfix ESP32: clear progTrackSyncMain (join flag) when prog track is removed
|
||||||
|
Loading…
x
Reference in New Issue
Block a user