mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-11-22 15:46:14 +01:00
Compare commits
3 Commits
d25173e0b4
...
44869a549d
Author | SHA1 | Date | |
---|---|---|---|
|
44869a549d | ||
|
a0a503ad7d | ||
|
68809fa8d5 |
19
DCC.cpp
19
DCC.cpp
|
@ -389,6 +389,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
|
||||
|
|
3
DCC.h
3
DCC.h
|
@ -64,6 +64,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);
|
||||
|
@ -128,6 +130,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;
|
||||
|
|
|
@ -480,6 +480,14 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
|||
break;
|
||||
DCC::writeCVByteMain(p[0], p[1], p[2]);
|
||||
return;
|
||||
|
||||
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;
|
||||
|
||||
case 'b': // WRITE CV BIT ON MAIN <b CAB CV BIT VALUE>
|
||||
if (params != 4)
|
||||
|
@ -1417,6 +1425,12 @@ void DCCEXParser::callback_R(int16_t result)
|
|||
commitAsyncReplyStream();
|
||||
}
|
||||
|
||||
void DCCEXParser::callback_r(int16_t result)
|
||||
{
|
||||
StringFormatter::send(getAsyncReplyStream(), F("<r MAIN %d %d %d >\n"), stashP[0], stashP[1], result);
|
||||
commitAsyncReplyStream();
|
||||
}
|
||||
|
||||
void DCCEXParser::callback_Rloco(int16_t result) {
|
||||
const FSH * detail;
|
||||
if (result<=0) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1364,11 +1364,11 @@ void RMFT2::killBlinkOnVpin(VPIN pin, uint16_t count) {
|
|||
}
|
||||
}
|
||||
|
||||
void RMFT2::startNonRecursiveTask(const FSH* reason, int16_t id,int pc, int16_t loco) {
|
||||
void RMFT2::startNonRecursiveTask(const FSH* reason, int16_t id,int pc, uint16_t loco) {
|
||||
// Check we dont already have a task running this handler
|
||||
RMFT2 * task=loopTask;
|
||||
while(task) {
|
||||
if (task->onEventStartPosition==pc) {
|
||||
if (task->onEventStartPosition==pc && task->loco==loco) {
|
||||
DIAG(F("Recursive ON%S(%d)"),reason, id);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -202,7 +202,7 @@ class LookList {
|
|||
static const FSH * getRosterFunctions(int16_t id);
|
||||
static const FSH * getTurntableDescription(int16_t id);
|
||||
static const FSH * getTurntablePositionDescription(int16_t turntableId, uint8_t positionId);
|
||||
static void startNonRecursiveTask(const FSH* reason, int16_t id,int pc, int16_t loco=0);
|
||||
static void startNonRecursiveTask(const FSH* reason, int16_t id,int pc, uint16_t loco=0);
|
||||
static bool readSensor(uint16_t sensorId);
|
||||
static bool isSignal(int16_t id,char rag);
|
||||
static SIGNAL_DEFINITION getSignalSlot(int16_t slotno);
|
||||
|
|
26
Railcom.cpp
26
Railcom.cpp
|
@ -145,6 +145,10 @@ Railcom::Railcom(uint16_t blockvpin) {
|
|||
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.
|
||||
|
@ -170,7 +174,29 @@ void Railcom::process(uint8_t * inbound, uint8_t length) {
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
12
Railcom.h
12
Railcom.h
|
@ -22,6 +22,8 @@
|
|||
#define Railcom_h
|
||||
#include "Arduino.h"
|
||||
|
||||
typedef void (*ACK_CALLBACK)(int16_t result);
|
||||
|
||||
class Railcom {
|
||||
public:
|
||||
Railcom(uint16_t vpin);
|
||||
|
@ -31,8 +33,18 @@ class Railcom {
|
|||
>0: loco id
|
||||
*/
|
||||
void process(uint8_t * inbound,uint8_t length);
|
||||
static void anticipate(uint16_t loco, uint16_t cv, ACK_CALLBACK callback) {
|
||||
expectLoco=loco;
|
||||
expectCV=cv;
|
||||
expectWait=millis(); // start of timeout
|
||||
expectCallback=callback;
|
||||
};
|
||||
|
||||
private:
|
||||
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;
|
||||
|
|
51
Release_Notes/Railcom.md
Normal file
51
Release_Notes/Railcom.md
Normal file
|
@ -0,0 +1,51 @@
|
|||
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>")`
|
||||
|
||||
|
||||
Reading Railcom data:
|
||||
A new HAL handler 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 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.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user