Skip to content

Commit

Permalink
Adds some useful railcom implementation to the example decoder (#558)
Browse files Browse the repository at this point in the history
Implements the following railcom messages:
- ADR (ID1 ID2)
- POM reads (ID0)
and the matching DCC packet decoding scheme.

===

* Adds implementation of broadcast address packet sends.

* Adds helper comments to the railcom decode table.

* Adds a test for a railcom broadcast address decode example.

* Fixes helper comments.

* Fixes computation of address broadcast.

* Adds channel2 ACK feedback.

* refactors packet address matching routine.

* Move around and reformat code.

* Adds implementation for POM reads.
  • Loading branch information
balazsracz authored Aug 11, 2021
1 parent fb669a3 commit 831e2d3
Show file tree
Hide file tree
Showing 3 changed files with 247 additions and 93 deletions.
264 changes: 203 additions & 61 deletions applications/dcc_decoder/main.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,19 @@
* @date 4 Sep 2018
*/

#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

#include "os/os.h"
#include "executor/Executor.hxx"
#include "dcc/DccDebug.hxx"
#include "utils/constants.hxx"
#include "utils/StringPrintf.hxx"
#include "dcc/PacketProcessor.hxx"
#include "dcc/RailCom.hxx"
#include "freertos_drivers/common/RailcomDriver.hxx"
#include "executor/Executor.hxx"
#include "freertos/tc_ioctl.h"
#include "freertos_drivers/common/RailcomDriver.hxx"
#include "os/os.h"
#include "utils/StringPrintf.hxx"
#include "utils/constants.hxx"

#include "hardware.hxx"

Expand All @@ -57,85 +57,225 @@ OVERRIDE_CONST(main_thread_priority, 3);

// The DCC address to listen at. This is in wire format. The first address byte
// is the high byte.
static const uint16_t dcc_address_wire = 3 << 8;
uint16_t dcc_address_wire = 3 << 8;

uint8_t f0 = 0;

void process_packet(const DCCPacket& p) {
if (p.packet_header.csum_error) {
return;
}
if (p.dlc < 3) {
return;
}
if (p.payload[0] != (dcc_address_wire >> 8)) {
return;
/// Stores a programming packet which is conditional on a repetition.
DCCPacket progStored;
/// Feedback for the stored programming packet.
dcc::Feedback fbStored;

// File descriptor for the eeprom storage.
int eefd;

/// Constants used for DCC packet decoding.
enum PacketCode {
/// Which bits in the command are coding the CV number high bits.
DCC_PROG_CVMASK = 3,
/// POM read 1 byte command
DCC_PROG_READ1 = 0b11100100,
/// POM write 1 byte command
DCC_PROG_WRITE1 = 0b11101100,
};

/// Reads CVs. Should only be called from the main thread.
void read_cvs(uint32_t ofs, unsigned len, uint8_t *dst)
{
for (unsigned i = 0; i < len; i++)
{
dst[i] = 42;
}
unsigned ofs = 1;
if ((dcc_address_wire >> 8) > 127) {
// Two byte address.
ofs++;
if (p.payload[1] != (dcc_address_wire & 0xff)) {
return;
}
}

void write_cv(uint32_t ofs, uint8_t value) {

}

/// Checks if a packet is addressed to our current DCC address.
/// @param payload the DCC packet payload
/// @return true if addressed to us.
bool match_dcc_address(const uint8_t *payload)
{
return ((payload[0] == (dcc_address_wire >> 8)) &&
((dcc_address_wire < (128 << 8)) ||
(payload[1] == (dcc_address_wire & 0xff))));
}

/// @return true if the two DCC packets are the same on the wire.
bool packet_match(const DCCPacket &a, const DCCPacket &b)
{
if (a.dlc != b.dlc)
{
return false;
}
if ((p.payload[ofs] >> 5) == 0b100)
for (unsigned i = 0; i < a.dlc; i++)
{
// F0-F4 packet
ofs++;
f0 = p.payload[ofs] & 0b00010000 ? 1 : 0;
if (a.payload[i] != b.payload[i])
return false;
}
return true;
}

class IrqProcessor : public dcc::PacketProcessor {
class IrqProcessor : public dcc::PacketProcessor
{
public:
IrqProcessor() {
IrqProcessor()
{
}

/// Called in the main to prepare the railcom feedback packets.
void init()
{
ch1_.reset(0);
ch1_.add_ch1_data(0x00);
ch1_.add_ch1_data(0x00);

ch2_.reset(0);
ch2_.add_ch2_data(0);
ch2_.add_ch2_data(0);
ch2_.add_ch2_data(0);
ch2_.add_ch2_data(0);
ch2_.add_ch2_data(0);
ch2_.add_ch2_data(0);
#if 0
ch2_.add_ch2_data(dcc::RailcomDefs::ACK);
ch2_.add_ch2_data(dcc::RailcomDefs::ACK);
ch2_.add_ch2_data(dcc::RailcomDefs::ACK);
ch2_.add_ch2_data(dcc::RailcomDefs::ACK);
ch2_.add_ch2_data(dcc::RailcomDefs::ACK);
ch2_.add_ch2_data(dcc::RailcomDefs::ACK);
#endif
update_address();
// Programming response packet
((dcc::Packet*)&progStored)->clear();
// 6-byte Ack packet
ack_.reset(0);
for (unsigned i = 0; i < 6; i++)
{
ack_.add_ch2_data(dcc::RailcomDefs::CODE_ACK);
}
}

/// Updates the broadcast datagrams based on the active DCC address.
void update_address()
{
bcastHigh_.reset(0);
bcastLow_.reset(0);
if (dcc_address_wire < (128 << 8))
{
dcc::RailcomDefs::append12(
dcc::RMOB_ADRHIGH, 0, bcastHigh_.ch1Data);
dcc::RailcomDefs::append12(
dcc::RMOB_ADRLOW, dcc_address_wire >> 8, bcastLow_.ch1Data);
}
else
{
uint8_t ah = 0x80 | ((dcc_address_wire >> 8) & 0x3F);
uint8_t al = dcc_address_wire & 0xFF;
dcc::RailcomDefs::append12(
dcc::RMOB_ADRHIGH, ah, bcastHigh_.ch1Data);
dcc::RailcomDefs::append12(dcc::RMOB_ADRLOW, al, bcastLow_.ch1Data);
}
bcastHigh_.ch1Size = 2;
bcastLow_.ch1Size = 2;
}

void packet_arrived(
const DCCPacket *pkt, RailcomDriver *railcom) override {
/// Called from the interrupt routine to process a packet and generate the
/// railcom response.
/// @param pkt the last received packet
/// @param railcom pointer to the railcom driver where the railcom feedback
/// needs to be sent.
void packet_arrived(const DCCPacket *pkt, RailcomDriver *railcom) override
{
DEBUG1_Pin::set(true);
if (pkt->packet_header.csum_error) {
if (pkt->packet_header.csum_error)
{
return;
}
ch1_.feedbackKey = pkt->feedback_key;
ch2_.feedbackKey = pkt->feedback_key;
railcom->send_ch1(&ch1_);
railcom->send_ch2(&ch2_);
uint8_t adrhi = pkt->payload[0];
if (adrhi && (adrhi < 232) && ((adrhi & 0xC0) != 0x80))
{
// Mobile decoder addressed. Send back address.
if (bcastAtHi_)
{
bcastHigh_.feedbackKey = pkt->feedback_key;
railcom->send_ch1(&bcastHigh_);
}
else
{
bcastLow_.feedbackKey = pkt->feedback_key;
railcom->send_ch1(&bcastLow_);
}
bcastAtHi_ ^= 1;
}
// Checks for regular addressing.
if (match_dcc_address(pkt->payload))
{
// Addressed packet to our DCC address.

// Check for a known POM packet.
if (packet_match(*pkt, progStored) &&
(fbStored.feedbackKey == progStored.feedback_key))
{
prog_ = fbStored;
prog_.feedbackKey = pkt->feedback_key;
railcom->send_ch2(&prog_);
}
else
{
// No specific reply prepared -- send just some acks.
ack_.feedbackKey = pkt->feedback_key;
railcom->send_ch2(&ack_);
}
}
DEBUG1_Pin::set(false);
}

private:
dcc::Feedback ch1_;
dcc::Feedback ch2_;
/// RailCom packet to send for address high in the broadcast channel.
dcc::Feedback bcastHigh_;
/// RailCom packet to send for address low in the broadcast channel.
dcc::Feedback bcastLow_;

/// Copy of the stored programming feedback.
dcc::Feedback prog_;

/// RailCom packet with all ACKs in channel2.
dcc::Feedback ack_;

/// 1 if the next broadcast packet should be adrhi, 0 if adrlo.
uint8_t bcastAtHi_ : 1;
} irqProc;

extern "C" {
void set_dcc_interrupt_processor(dcc::PacketProcessor *p);
/// Called from main with the last received packet.
/// @param p the DCC packet from the track.
void process_packet(const DCCPacket &p)
{
if (p.packet_header.csum_error)
{
return;
}
if (p.dlc < 3)
{
return;
}
unsigned ofs = 1;
if (match_dcc_address(p.payload))
{
if (dcc_address_wire >= (128 << 8))
{
// Two byte address.
ofs++;
}
if ((p.payload[ofs] >> 5) == 0b100)
{
// F0-F4 packet
ofs++;
f0 = p.payload[ofs] & 0b00010000 ? 1 : 0;
} else if ((p.payload[ofs] & ~DCC_PROG_CVMASK) == DCC_PROG_READ1) {
if (packet_match(p, progStored))
{
// We already processed this packet, nothing to do.
return;
}
fbStored.reset(0);
progStored = p;
uint32_t cv = ((p.payload[ofs] & DCC_PROG_CVMASK) << 8) |
(p.payload[ofs + 1]);
uint8_t value;
read_cvs(cv, 1, &value);
dcc::RailcomDefs::append12(dcc::RMOB_POM, value, fbStored.ch2Data);
fbStored.ch2Size = 2;
fbStored.feedbackKey = progStored.feedback_key;
}
}
}

extern "C"
{
void set_dcc_interrupt_processor(dcc::PacketProcessor *p);
}

/** Entry point to application.
Expand All @@ -148,17 +288,19 @@ int appl_main(int argc, char *argv[])
setblink(0);
int fd = ::open("/dev/dcc_decoder0", O_RDONLY);
HASSERT(fd >= 0);
//int wfd = ::open("/dev/serUSB0", O_RDWR);
// int wfd = ::open("/dev/serUSB0", O_RDWR);
int wfd = ::open("/dev/ser0", O_RDWR);
HASSERT(wfd >= 0);
int rcfd = ::open("/dev/ser1", O_WRONLY);
HASSERT(rcfd >= 0);
auto ret = ::ioctl(rcfd, TCBAUDRATE, 250000);
HASSERT(ret == 0);
eefd = ::open("/dev/eeprom", O_RDWR);
HASSERT(eefd >= 0);

irqProc.init();
set_dcc_interrupt_processor(&irqProc);

int cnt = 0;
while (1)
{
Expand Down
65 changes: 33 additions & 32 deletions src/dcc/RailCom.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -44,38 +44,39 @@ static constexpr uint8_t BUSY = RailcomDefs::BUSY;
static constexpr uint8_t RESVD1 = RailcomDefs::RESVD1;
static constexpr uint8_t RESVD2 = RailcomDefs::RESVD2;
const uint8_t railcom_decode[256] =
{ INV, INV, INV, INV, INV, INV, INV, INV,
INV, INV, INV, INV, INV, INV, INV, ACK,
INV, INV, INV, INV, INV, INV, INV, 0x33,
INV, INV, INV, 0x34, INV, 0x35, 0x36, INV,
INV, INV, INV, INV, INV, INV, INV, 0x3A,
INV, INV, INV, 0x3B, INV, 0x3C, 0x37, INV,
INV, INV, INV, 0x3F, INV, 0x3D, 0x38, INV,
INV, 0x3E, 0x39, INV, NACK, INV, INV, INV,
INV, INV, INV, INV, INV, INV, INV, 0x24,
INV, INV, INV, 0x23, INV, 0x22, 0x21, INV,
INV, INV, INV, 0x1F, INV, 0x1E, 0x20, INV,
INV, 0x1D, 0x1C, INV, 0x1B, INV, INV, INV,
INV, INV, INV, 0x19, INV, 0x18, 0x1A, INV,
INV, 0x17, 0x16, INV, 0x15, INV, INV, INV,
INV, 0x25, 0x14, INV, 0x13, INV, INV, INV,
0x32, INV, INV, INV, INV, INV, INV, INV,
INV, INV, INV, INV, INV, INV, INV, RESVD2,
INV, INV, INV, 0x0E, INV, 0x0D, 0x0C, INV,
INV, INV, INV, 0x0A, INV, 0x09, 0x0B, INV,
INV, 0x08, 0x07, INV, 0x06, INV, INV, INV,
INV, INV, INV, 0x04, INV, 0x03, 0x05, INV,
INV, 0x02, 0x01, INV, 0x00, INV, INV, INV,
INV, 0x0F, 0x10, INV, 0x11, INV, INV, INV,
0x12, INV, INV, INV, INV, INV, INV, INV,
INV, INV, INV, RESVD1, INV, 0x2B, 0x30, INV,
INV, 0x2A, 0x2F, INV, 0x31, INV, INV, INV,
INV, 0x29, 0x2E, INV, 0x2D, INV, INV, INV,
0x2C, INV, INV, INV, INV, INV, INV, INV,
INV, BUSY, 0x28, INV, 0x27, INV, INV, INV,
0x26, INV, INV, INV, INV, INV, INV, INV,
ACK, INV, INV, INV, INV, INV, INV, INV,
INV, INV, INV, INV, INV, INV, INV, INV,
// 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, BUSY, 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
};

const uint8_t railcom_encode[64] = {
Expand Down
Loading

0 comments on commit 831e2d3

Please sign in to comment.