1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-04-15 09:50:11 +02:00

Compare commits

...

8 Commits

Author SHA1 Message Date
Kcsmith0708
2387a32be0
Merge 2afb5f3d6c into 83a5c52a0d 2025-03-31 05:59:21 +02:00
Harald Barth
83a5c52a0d build for relevant platforms: Mega, ESP32, Nucleos 2025-03-23 10:51:07 +01:00
Harald Barth
45af57ebf2 version 5.5.21 2025-03-23 09:51:51 +01:00
Harald Barth
3f8ecf2a52 Revert "Merge branch 'devel-Ash-F439sync' into devel"
This reverts commit 3d794c59d8, reversing
changes made to 84918cbf36.
2025-03-23 09:49:54 +01:00
Asbelos
764639ed79 EXRAIL Assert SET/RESET fix 2025-03-22 23:58:54 +00:00
Asbelos
e56e4826ec Railcom Collector interface 2025-03-19 18:45:20 +00:00
Asbelos
3aa5cbcdfc Squashed commit of the following:
commit ec4c6b9c02
Author: Asbelos <asbelos@btinternet.com>
Date:   Wed Mar 5 00:30:13 2025 +0000

    Cleanup to new structure

commit 620ad6275b
Author: Asbelos <asbelos@btinternet.com>
Date:   Fri Feb 28 01:16:03 2025 +0000

    Railcom3
2025-03-19 18:06:02 +00:00
Kcsmith0708
2afb5f3d6c
Update version.h
Added Updated Versiom 4.1.1 thru 4.1.6
2023-08-24 14:57:57 -04:00
15 changed files with 140 additions and 748 deletions

View File

@ -2,7 +2,7 @@
* © 2022 Paul M Antoine
* © 2021 Neil McKechnie
* © 2021 Mike S
* © 2021-2025 Herb Morton
* © 2021-2024 Herb Morton
* © 2020-2023 Harald Barth
* © 2020-2021 M Steve Todd
* © 2020-2021 Fred Decker
@ -632,19 +632,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
else break; // will reply <X>
}
//TrackManager::streamTrackState(NULL,t);
// reinitialize DC mode timer settings following powerON
#ifdef ARDUINO_ARCH_STM32
for (uint8_t i = 0; i < 8; i++) {
TrackManager::setTrackPowerF439ZI(i);
}
// repeated in case the <F29..31 was set on a later track than power
// Note: this retains power but prevents speed doubling
for (uint8_t i = 0; i < 7; i++) {
TrackManager::setTrackPowerF439ZI(i);
}
#endif
return;
}
@ -801,11 +789,6 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
TrackManager::reportCurrent(stream); // <g limit...limit>
return;
case "L"_hk: // <JL display row> track state and mA value on display
if (params!=3) break;
TrackManager::reportCurrentLCD(p[1], p[2]); // Track power status
return;
case "A"_hk: // <JA> intercepted by EXRAIL// <JA> returns automations/routes
if (params!=1) break; // <JA>
StringFormatter::send(stream, F("<jA>\n"));

View File

@ -1,7 +1,6 @@
/*
* © 2023 Neil McKechnie
* © 2022-2024 Paul M. Antoine
* © 2025 Herb Morton
* © 2021 Mike S
* © 2021, 2023 Harald Barth
* © 2021 Fred Decker
@ -37,21 +36,6 @@
#include "DIAG.h"
#include <wiring_private.h>
// DC mode timers enable the PWM signal on select pins.
// Code added to sync timers which have the same frequency.
// Function prototypes
void refreshDCmodeTimers();
void resetCounterDCmodeTimers();
HardwareTimer *Timer1 = new HardwareTimer(TIM1);
HardwareTimer *Timer2 = new HardwareTimer(TIM2);
HardwareTimer *Timer3 = new HardwareTimer(TIM3);
HardwareTimer *Timer4 = new HardwareTimer(TIM4);
HardwareTimer *Timer9 = new HardwareTimer(TIM9);
#if defined(TIM13)
HardwareTimer *Timer13 = new HardwareTimer(TIM13);
#endif
#if defined(ARDUINO_NUCLEO_F401RE)
// Nucleo-64 boards don't have additional serial ports defined by default
// Serial1 is available on the F401RE, but not hugely convenient.
@ -306,7 +290,7 @@ void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t f) {
else if (f >= 3)
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 16000);
else if (f >= 2)
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 3600);
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 3400);
else if (f == 1)
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 480);
else
@ -344,8 +328,7 @@ void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t frequency
if (pin_timer[pin] != NULL)
{
pin_timer[pin]->setPWM(pin_channel[pin], pin, frequency, 0); // set frequency in Hertz, 0% dutycycle
DIAG(F("DCCEXanalogWriteFrequency::Pin %d on Timer %d Channel %d, frequency %d"), pin, pin_timer[pin], pin_channel[pin], frequency);
resetCounterDCmodeTimers();
DIAG(F("DCCEXanalogWriteFrequency::Pin %d on Timer Channel %d, frequency %d"), pin, pin_channel[pin], frequency);
}
else
DIAG(F("DCCEXanalogWriteFrequency::failed to allocate HardwareTimer instance!"));
@ -353,13 +336,11 @@ void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t frequency
else
{
// Frequency change request
//DIAG(F("DCCEXanalogWriteFrequency_356::pin %d frequency %d"), pin, frequency);
if (frequency != channel_frequency[pin])
{
pinmap_pinout(digitalPinToPinName(pin), PinMap_TIM); // ensure the pin has been configured!
pin_timer[pin]->setOverflow(frequency, HERTZ_FORMAT); // Just change the frequency if it's already running!
DIAG(F("DCCEXanalogWriteFrequency::setting frequency to %d"), frequency);
resetCounterDCmodeTimers();
}
}
channel_frequency[pin] = frequency;
@ -384,9 +365,6 @@ void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value, bool invert) {
pin_timer[pin]->setCaptureCompare(pin_channel[pin], duty_cycle, PERCENT_COMPARE_FORMAT); // DCC_EX_PWM_FREQ Hertz, duty_cycle% dutycycle
DIAG(F("DCCEXanalogWrite::Pin %d, value %d, duty cycle %d"), pin, value, duty_cycle);
// }
refreshDCmodeTimers();
resetCounterDCmodeTimers();
}
else
DIAG(F("DCCEXanalogWrite::Pin %d is not configured for PWM!"), pin);
@ -681,35 +659,4 @@ void ADCee::begin() {
#endif
interrupts();
}
// NOTE: additional testing is needed to check the DCC signal
// where the DCC signal pin is a pwm pin on timers 1, 4, 9, 13
// or the brake pin is defined on a different timer.
// -- example: F411RE/F446RE - pin 10 on stacked EX8874
// lines added to sync timers --
// not exact sync, but timers with the same frequency should be in sync
void refreshDCmodeTimers() {
Timer1->refresh();
Timer2->refresh();
Timer3->refresh();
Timer4->refresh();
Timer9->refresh();
#if defined(TIM13)
Timer13->refresh();
#endif
}
// Function to synchronize timers - called every time there is powerON commmand for any DC track
void resetCounterDCmodeTimers() {
// Reset the counter for all DC mode timers
TIM1->CNT = 0;
TIM2->CNT = 0;
TIM3->CNT = 0;
TIM4->CNT = 0;
TIM9->CNT = 0;
#if defined(TIM13)
TIM13->CNT = 0;
#endif
}
#endif

View File

@ -145,9 +145,9 @@ constexpr bool unsafePin(const int16_t value, const uint16_t pos=0 ) {
// check dangerous pins
#define _PIN_RESERVED_ "\n\nUSER ERROR: Pin is used by Motor Shield or other critical function.\n"
#undef SET
#define SET(vpin) static_assert(!unsafePin(vpin),"SET(" #vpin ")" _PIN_RESERVED_);
#define SET(vpin, ...) static_assert(!unsafePin(vpin),"SET(" #vpin ")" _PIN_RESERVED_);
#undef RESET
#define RESET(vpin) static_assert(!unsafePin(vpin),"RESET(" #vpin ")" _PIN_RESERVED_);
#define RESET(vpin,...) static_assert(!unsafePin(vpin),"RESET(" #vpin ")" _PIN_RESERVED_);
#undef BLINK
#define BLINK(vpin,onDuty,offDuty) static_assert(!unsafePin(vpin),"BLINK(" #vpin ")" _PIN_RESERVED_);
#undef SIGNAL

View File

@ -1 +1 @@
#define GITHUB_SHA "devel-202503022043Z"
#define GITHUB_SHA "devel-202503250850Z"

View File

@ -1,7 +1,5 @@
/*
* © 2024, Chris Harlow.
* © 2025 Herb Morton
* All rights reserved.
* © 2024, Chris Harlow. All rights reserved.
*
* This file is part of CommandStation-EX
*
@ -37,4 +35,4 @@ It has been moved here to be easier to maintain than editing IODevice.h
#include "IO_EXSensorCAM.h"
#include "IO_DS1307.h"
#include "IO_I2CRailcom.h"
#include "IO_HALDisplay.h"

View File

@ -38,8 +38,7 @@
* 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
* vPins : Total number of virtual pins allocated (to prevent overlaps)
* I2C Address : I2C address of the serial controller, in 0x format
*/
@ -47,43 +46,32 @@
#include "IO_I2CRailcom.h"
#include "I2CManager.h"
#include "DIAG.h"
#include "DCC.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){
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);
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"),
DIAG(F("I2CRailcom: %s RailcomCollector %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;
_deviceState=DEVSTATE_NORMAL;
_display();
}
@ -91,213 +79,43 @@ void I2CRailcom::create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
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?
// basically we only poll once per packet when railcom cutout is working
auto cut=DCCWaveform::getRailcomCutoutCounter();
if (cutoutCounter[_UART_CH]==cut) return;
cutoutCounter[_UART_CH]=cut;
if (cutoutCounter==cut) return;
cutoutCounter=cut;
Railcom::loop(); // in case a csv read has timed out
// 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);
// Obtain data length from the collector
byte inbuf[1];
byte queryLength[]={'?'};
auto state=I2CManager.read(_I2CAddress, inbuf, 1,queryLength,sizeof(queryLength));
if (state) {
DIAG(F("RC ? state=%d"),state);
return;
}
auto length=inbuf[0];
if (length==0) return; // nothing to report
// Build a buffer and import the data from the collector
byte inbuf2[length];
byte queryData[]={'>'};
state=I2CManager.read(_I2CAddress, inbuf2, length,queryData,sizeof(queryData));
if (state) {
DIAG(F("RC > %d state=%d"),length,state);
return;
}
// 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);
// process incoming data buffer
Railcom::process(_firstVpin,inbuf2,length);
}
void I2CRailcom::_display() {
DIAG(F("I2CRailcom: Configured on Vpins:%u-%u %S"), _firstVpin, _firstVpin+_nPins-1,
DIAG(F("I2CRailcom: %s blocks %d-%d %S"), _I2CAddress.toString(), _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
};

View File

@ -1,4 +1,4 @@
/*
/*
* © 2024, Henk Kruisbrink & Chris Harlow. All rights reserved.
* © 2023, Neil McKechnie. All rights reserved.
*
@ -19,47 +19,27 @@
*/
/*
*
* 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);
*
* This polls the RailcomCollecter device once per dcc packet
* and obtains an abbreviated list of block occupancy changes which
* are fortunately very rare compared with Railcom raw data.
*
* 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
* vPins : Total number of virtual pins allocated
* I2C Address : I2C address of the Railcom Collector, 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
#include "IODevice.h"
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:
byte cutoutCounter;
public:
// Constructor
I2CRailcom(VPIN firstVpin, int nPins, I2CAddress i2cAddress);
@ -72,74 +52,7 @@ public:
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

View File

@ -1,6 +1,6 @@
/*
* © 2022-2024 Paul M Antoine
* © 2024-2025 Herb Morton
* © 2024 Herb Morton
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020-2023 Harald Barth
@ -371,10 +371,8 @@ void MotorDriver::setDCSignal(byte speedcode, uint8_t frequency /*default =0*/)
}
#endif
//DIAG(F("Brake pin %d value %d freqency %d"), brakePin, brake, f);
//DIAG(F("MotorDriver_cpp_374_DCCEXanalogWriteFequency::Pin %d, frequency %d, tSpeed %d"), brakePin, f, tSpeed);
DCCTimer::DCCEXanalogWrite(brakePin, brake, invertBrake);
DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency
//DIAG(F("MotorDriver_cpp_375_DCCEXanalogWrite::brakePin %d, frequency %d, invertBrake"), brakePin, brake, invertBrake);
DCCTimer::DCCEXanalogWrite(brakePin, brake, invertBrake); // line swapped to set frequency first
#else // all AVR here
DCCTimer::DCCEXanalogWriteFrequency(brakePin, frequency); // frequency steps
analogWrite(brakePin, invertBrake ? 255-brake : brake);

View File

@ -1,6 +1,5 @@
/*
* SEE ADDITIONAL COPYRIGHT ATTRIBUTION BELOW
* © 2024 Chris Harlow
* © 2025 Chris Harlow
* All rights reserved.
*
* This file is part of DCC-EX
@ -19,258 +18,65 @@
* 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;
};
}
// process is called to handle data buffer sent by collector
void Railcom::process(int16_t firstVpin,byte * buffer, byte length) {
// block,locohi,locolow
// block|0x80,data pom read cv
byte i=0;
while (i<length) {
byte block=buffer[i] & 0x3f;
byte type=buffer[i]>>6;
switch (type) {
// a type=0 record has block,locohi,locolow
case 0: {
uint16_t locoid= ((uint16_t)buffer[i+1])<<8 | ((uint16_t)buffer[i+2]);
DIAG(F("RC3 b=%d l=%d"),block,locoid);
if (locoid==0) DCC::clearBlock(firstVpin+block);
else DCC::setLocoInBlock(locoid,firstVpin+block,true);
i+=3;
}
break;
case 2: { // csv value from POM read
byte value=buffer[i+1];
if (expectCV && DCCWaveform::getRailcomLastLocoAddress()==expectLoco) {
DCC::setLocoInBlock(expectLoco,firstVpin+block,false);
if (expectCallback) expectCallback(value);
expectCV=0;
}
i+=2;
}
break;
default:
DIAG(F("Unknown RC Collector code %d"),type);
return;
}
}
}
// loop() is called to detect timeouts waiting for a POM read result
void Railcom::loop() {
if (expectCV && (millis()-expectWait)> POM_READ_TIMEOUT) { // still waiting
expectCallback(-1);
expectCV=0;
}
}

View File

@ -1,5 +1,5 @@
/*
* © 2024 Chris Harlow
* © 202 5Chris Harlow
* All rights reserved.
*
* This file is part of DCC-EX
@ -26,28 +26,15 @@ 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);
static void anticipate(uint16_t loco, uint16_t cv, ACK_CALLBACK callback);
static void process(int16_t firstVpin,byte * buffer, byte length );
static void loop();
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
static const unsigned long POM_READ_TIMEOUT=500; // as per spec
static uint16_t expectCV,expectLoco;
static unsigned long expectWait;
static ACK_CALLBACK expectCallback;
static const byte MAX_WAIT_FOR_GLITCH=20; // number of dead or empty packets before assuming loco=0
};
#endif

View File

@ -17,15 +17,16 @@ Enabling the Railcom Cutout requires a `<C RAILCOM ON>` command. This can be add
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.
Code to actually implement the timing of the cutout is highly cpu dependent and can be found in the 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.
A new HAL handler (`IO_I2CRailcom.h`) has been added to process input from a 32-block railcom collecter which operates over I2C. The collector and its readers sit between the CS and the track and collect railcom data from locos during the cutout.
The Collector device removes 99.9% of the railcom traffic and returns just a summary of what has changed since the last cutout.
After the cutout the HAL driver reads the Collector summary 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. )
Each 32-block reader is described in myAutomation like `HAL(I2CRailcom,10000,32,0x08)` which will assign 32 blocks on i2c address 0x08 with vpin numbers 10000 and 10031. If you only use fewer channel in the collector, you can assign fewer pins here.
(Implementation notes.. you may have multiple collectors, each one will requite a HAL line to define its i2c address and vpins to represent block numbers.)
Making use of Railcom data
@ -62,14 +63,9 @@ Making use of Railcom data
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 PROG Track we use `<R cv>` response is `<r value>`
To read from main track use `<r loco cv>`
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)

View File

@ -2,7 +2,7 @@
* © 2022-2025 Chris Harlow
* © 2022-2024 Harald Barth
* © 2023-2024 Paul M. Antoine
* © 2024-2025 Herb Morton
* © 2024 Herb Morton
* © 2023 Colin Murdoch
* All rights reserved.
*
@ -42,7 +42,6 @@
MotorDriver * TrackManager::track[MAX_TRACKS] = { NULL };
int16_t TrackManager::trackDCAddr[MAX_TRACKS] = { 0 };
int16_t TrackManager::tPwr_mA[8]={0,0,0,0,0,0,0,0};
int8_t TrackManager::lastTrack=-1;
bool TrackManager::progTrackSyncMain=false;
@ -647,33 +646,6 @@ void TrackManager::reportCurrent(Print* stream) {
StringFormatter::send(stream,F(">\n"));
}
void TrackManager::reportCurrentLCD(uint8_t display, byte row) {
FOR_EACH_TRACK(t) {
bool pstate = TrackManager::isPowerOn(t); // checks if power is on or off
TRACK_MODE tMode=(TrackManager::getMode(t)); // gets to current power mode
int16_t DCAddr=(TrackManager::returnDCAddr(t));
if (pstate) { // if power is on do this section
tPwr_mA[t]=(3*tPwr_mA[t]>>2) + ((track[t]->getPower()==POWERMODE::OVERLOAD) ? -1 :
track[t]->raw2mA(track[t]->getCurrentRaw(false)));
if (tMode & TRACK_MODE_DC) { // Test if track is in DC or DCX mode
SCREEN(display, row+t, F("%c: %S %d ON %dmA"), t+'A', (TrackManager::getModeName(tMode)),DCAddr, tPwr_mA[t]>>2);
}
else { // formats without DCAddress
SCREEN(display, row+t, F("%c: %S ON %dmA"), t+'A', (TrackManager::getModeName(tMode)), tPwr_mA[t]>>2);
}
}
else { // if power is off do this section
if (tMode & TRACK_MODE_DC) { // DC / DCX
SCREEN(display, row+t, F("Track %c: %S %d OFF"), t+'A', (TrackManager::getModeName(tMode)),DCAddr);
}
else { // Not DC or DCX
SCREEN(display, row+t, F("Track %c: %S OFF"), t+'A', (TrackManager::getModeName(tMode)));
}
}
}
}
void TrackManager::reportGauges(Print* stream) {
StringFormatter::send(stream,F("<jG"));
FOR_EACH_TRACK(t) {
@ -738,38 +710,3 @@ TRACK_MODE TrackManager::getMode(byte t) {
int16_t TrackManager::returnDCAddr(byte t) {
return (trackDCAddr[t]);
}
// Set track power for EACH track, independent of mode
// This updates the settings so that speed is correct
// following a frequency change - DC mode
void TrackManager::setTrackPowerF439ZI(byte t) {
MotorDriver *driver=track[t];
if (driver == NULL) { // track is not defined at all
// DIAG(F("Error: Track %c does not exist"), t+'A');
return;
}
TRACK_MODE trackmode = driver->getMode();
POWERMODE powermode = driver->getPower(); // line added to enable processing for DC mode tracks
POWERMODE oldpower = driver->getPower();
//if (trackmode & TRACK_MODE_NONE) {
// driver->setBrake(true); // Track is unused. Brake is good to have.
// powermode = POWERMODE::OFF; // Track is unused. Force it to OFF
//} else
if (trackmode & TRACK_MODE_DC) { // includes inverted DC (called DCX)
if (powermode == POWERMODE::ON) {
driver->setBrake(true); // DC starts with brake on
applyDCSpeed(t); // speed match DCC throttles
}
}
//else /* MAIN PROG EXT BOOST */ {
// if (powermode == POWERMODE::ON) {
// // toggle brake before turning power on - resets overcurrent error
// // on the Pololu board if brake is wired to ^D2.
// driver->setBrake(true);
// driver->setBrake(false); // DCC runs with brake off
// }
//}
driver->setPower(powermode);
if (oldpower != driver->getPower())
CommandDistributor::broadcastPower();
}

View File

@ -1,7 +1,6 @@
/*
* © 2022 Chris Harlow
* © 2022-2024 Harald Barth
* © 2025 Herb Morton
* © 2023 Colin Murdoch
*
* All rights reserved.
@ -67,7 +66,6 @@ class TrackManager {
static void setPower(POWERMODE mode) {setMainPower(mode); setProgPower(mode);}
static void setTrackPower(POWERMODE mode, byte t);
static void setTrackPowerF439ZI(byte t);
static void setTrackPower(TRACK_MODE trackmode, POWERMODE powermode);
static void setMainPower(POWERMODE mode) {setTrackPower(TRACK_MODE_MAIN, mode);}
static void setProgPower(POWERMODE mode) {setTrackPower(TRACK_MODE_PROG, mode);}
@ -89,7 +87,6 @@ class TrackManager {
static void sampleCurrent();
static void reportGauges(Print* stream);
static void reportCurrent(Print* stream);
static void reportCurrentLCD(uint8_t display, byte row);
static void reportObsoleteCurrent(Print* stream);
static void streamTrackState(Print* stream, byte t);
static bool isPowerOn(byte t);
@ -108,7 +105,6 @@ class TrackManager {
private:
#endif
static MotorDriver* track[MAX_TRACKS];
static int16_t tPwr_mA[8]; // for <JL ..> command
private:
static void addTrack(byte t, MotorDriver* driver);

View File

@ -13,9 +13,10 @@ default_envs =
mega2560
; uno
; nano
; ESP32
; Nucleo-F411RE
; Nucleo-F446RE
ESP32
Nucleo-F411RE
Nucleo-F446RE
Nucleo-F429ZI
src_dir = .
include_dir = .

View File

@ -3,7 +3,10 @@
#include "StringFormatter.h"
#define VERSION "5.5.18"
#define VERSION "5.5.21"
// 5.5.21 - Backed out the broken merge with frequency change and
// 5.5.20 - EXRAIL SET/RESET assert fix
// 5.5.19 - Railcom change to use RailcomCollector device
// 5.5.18 - New STASH internals
// - EXRAIL IFSTASH/CLEAR_ANY_STASH
// - <JM CLEAR ANY id> to clear any stash with loco id
@ -359,9 +362,18 @@
// TrackManager DCC & DC up to 8 Districts Architecture
// Automatic ALIAS(name)
// Command Parser now accepts Underscore in Alias Names
// 4.1.6 Support for new EX-MotorShield8874 Dual 5Amp Shield
// 4.1.5 Bugfix LCN number parsing
// 4.1.4 Bugfix for issue #299 Turnout Description NULL
// 4.1.3 Bugfix: Ethernet init order
// 4.1.2 Bugfix: Ethernet shield W5100 does not report HW or link level
// 4.1.1 Bugfix: preserve turnout format
// Bugfix: parse multiple commands in one buffer string correct
// Bugfix: </> command signal status in Exrail
// Bugfix: parse multiple commands in one buffer string correctly (ex: <s><Q>)
// Bugfix: </> command signal status of EX-RAIL tasks or threads
// Bugfix: EX-RAIL read long loco address
// Bugfix: Add space character after version string 4.1.1 for JMRI parsing.
// Improved display and loop time for signals make service start to be outside the DONT_TOUCH_WIFI_CONF area
// Improve WiFi startup by making service start to be outside the DONT_TOUCH_WIFI_CONF area
// 4.1.0 ...
//
// 4.0.2 EXRAIL additions: