mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-01-28 21:23:06 +01:00
Railcom Implementation
HAL driver, Railcom interpreter, block management, <r command. See release notes
This commit is contained in:
parent
dc84f560e6
commit
748ddcde8c
67
DCC.cpp
67
DCC.cpp
@ -37,6 +37,7 @@
|
||||
#include "CommandDistributor.h"
|
||||
#include "TrackManager.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "Railcom.h"
|
||||
|
||||
// This module is responsible for converting API calls into
|
||||
// messages to be sent to the waveform generator.
|
||||
@ -424,6 +425,25 @@ void DCC::writeCVByteMain(int cab, int cv, byte bValue) {
|
||||
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
|
||||
// the 5 byte sized packet to implement this DCC function
|
||||
@ -1031,11 +1051,54 @@ void DCC::displayCabList(Print * stream) {
|
||||
if (slot->loco==0) break; // no more locos
|
||||
if (slot->loco>0) {
|
||||
used ++;
|
||||
StringFormatter::send(stream,F("cab=%d, speed=%d, target=%d momentum=%d/%d\n"),
|
||||
StringFormatter::send(stream,F("cab=%d, speed=%d, target=%d, momentum=%d/%d, block=%d\n"),
|
||||
slot->loco, slot->speedCode, slot->targetSpeed,
|
||||
slot->momentumA, slot->momentumD);
|
||||
slot->momentumA, slot->momentumD, slot->blockOccupied);
|
||||
}
|
||||
}
|
||||
StringFormatter::send(stream,F("Used=%d, max=%d, momentum=%d/%d *>\n"),
|
||||
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
|
||||
}
|
||||
|
7
DCC.h
7
DCC.h
@ -66,6 +66,8 @@ public:
|
||||
static uint8_t getThrottleFrequency(int cab);
|
||||
static bool getThrottleDirection(int cab);
|
||||
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 setFunction(int cab, byte fByte, byte eByte);
|
||||
static bool setFn(int cab, int16_t functionNumber, bool on);
|
||||
@ -86,7 +88,8 @@ public:
|
||||
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 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 setLocoId(int id,ACK_CALLBACK callback);
|
||||
static void setConsistId(int id,bool reverse,ACK_CALLBACK callback);
|
||||
@ -109,6 +112,7 @@ public:
|
||||
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;
|
||||
@ -139,6 +143,7 @@ private:
|
||||
// NMRA codes #
|
||||
static const byte SET_SPEED = 0x3f;
|
||||
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_BYTE = 0x7C;
|
||||
static const byte VERIFY_BYTE = 0x74;
|
||||
|
@ -481,6 +481,16 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
DCC::writeCVByteMain(p[0], p[1], p[2]);
|
||||
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>
|
||||
if (params != 4)
|
||||
break;
|
||||
@ -1434,6 +1444,12 @@ void DCCEXParser::callback_R(int16_t result)
|
||||
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) {
|
||||
const FSH * detail;
|
||||
if (result<=0) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2020-2025 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of Asbelos DCC API
|
||||
@ -68,7 +68,8 @@ struct DCCEXParser
|
||||
static void callback_W(int16_t result);
|
||||
static void callback_W4(int16_t result);
|
||||
static void callback_B(int16_t result);
|
||||
static void callback_R(int16_t result);
|
||||
static void callback_R(int16_t result); // prog
|
||||
static void callback_r(int16_t result); // main
|
||||
static void callback_Rloco(int16_t result);
|
||||
static void callback_Wloco(int16_t result);
|
||||
static void callback_Wconsist(int16_t result);
|
||||
|
14
IODevice.h
14
IODevice.h
@ -560,18 +560,6 @@ protected:
|
||||
|
||||
};
|
||||
|
||||
#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 "IODeviceList.h"
|
||||
|
||||
#endif // iodevice_h
|
||||
|
@ -34,3 +34,5 @@ It has been moved here to be easier to maintain than editing IODevice.h
|
||||
#include "IO_TM1638.h"
|
||||
#include "IO_EXSensorCAM.h"
|
||||
#include "IO_DS1307.h"
|
||||
#include "IO_I2CRailcom.h"
|
||||
|
||||
|
321
IO_I2CRailcom.h
Normal file
321
IO_I2CRailcom.h
Normal file
@ -0,0 +1,321 @@
|
||||
/*
|
||||
* © 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 "IODevice.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
|
||||
|
||||
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){
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_I2CAddress = i2cAddress;
|
||||
_channelMonitors[0]=new Railcom(firstVpin);
|
||||
if (nPins>1) _channelMonitors[1]=new Railcom(firstVpin+1);
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
|
||||
if (nPins>2) nPins=2;
|
||||
if (checkNoOverlap(firstVpin, nPins, i2cAddress))
|
||||
new I2CRailcom(firstVpin, nPins, i2cAddress);
|
||||
}
|
||||
|
||||
void _begin() override {
|
||||
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 _loop(unsigned long currentMicros) override {
|
||||
// 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 _display() override {
|
||||
DIAG(F("I2CRailcom: Configured on Vpins:%u-%u %S"), _firstVpin, _firstVpin+_nPins-1,
|
||||
(_deviceState!=DEVSTATE_NORMAL) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
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(){
|
||||
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 UART_WriteRegister(uint8_t reg, uint8_t val, bool readback=true){
|
||||
_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 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
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif // IO_I2CRailcom_h
|
276
Railcom.cpp
Normal file
276
Railcom.cpp
Normal file
@ -0,0 +1,276 @@
|
||||
/*
|
||||
* 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
Normal file
53
Railcom.h
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* © 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
|
75
Release_Notes/Railcom.md
Normal file
75
Release_Notes/Railcom.md
Normal file
@ -0,0 +1,75 @@
|
||||
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)
|
||||
|
@ -3,7 +3,11 @@
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
#define VERSION "5.5.4"
|
||||
#define VERSION "5.5.5"
|
||||
// 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,
|
||||
|
Loading…
Reference in New Issue
Block a user