From 831e2d3ead3fcc329beda1510d749f662863967d Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Wed, 11 Aug 2021 11:27:35 +0200 Subject: [PATCH] Adds some useful railcom implementation to the example decoder (#558) 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. --- applications/dcc_decoder/main.cxx | 264 +++++++++++++++++++++++------- src/dcc/RailCom.cxx | 65 ++++---- src/dcc/RailCom.cxxtest | 11 ++ 3 files changed, 247 insertions(+), 93 deletions(-) diff --git a/applications/dcc_decoder/main.cxx b/applications/dcc_decoder/main.cxx index e732766c3..9134ef9cb 100644 --- a/applications/dcc_decoder/main.cxx +++ b/applications/dcc_decoder/main.cxx @@ -32,19 +32,19 @@ * @date 4 Sep 2018 */ +#include #include #include -#include -#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" @@ -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. @@ -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) { diff --git a/src/dcc/RailCom.cxx b/src/dcc/RailCom.cxx index e0f89b5f8..ef111539e 100644 --- a/src/dcc/RailCom.cxx +++ b/src/dcc/RailCom.cxx @@ -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] = { diff --git a/src/dcc/RailCom.cxxtest b/src/dcc/RailCom.cxxtest index c20fe9739..0536bfb8c 100644 --- a/src/dcc/RailCom.cxxtest +++ b/src/dcc/RailCom.cxxtest @@ -125,6 +125,17 @@ TEST_F(RailcomDecodeTest, Ch2ExtAndFeedback) { RailcomPacket(3, 2, RailcomPacket::MOB_POM, 0xA5))); } +TEST_F(RailcomDecodeTest, Ch1Bcast) { + fb_.add_ch1_data(0xA3); + fb_.add_ch1_data(0xAC); + fb_.add_ch2_data(0x99); + fb_.add_ch2_data(0xa5); + decode(); + EXPECT_THAT( + output_, ElementsAre(RailcomPacket(3, 1, RailcomPacket::MOB_ADRHIGH, 0), + RailcomPacket(3, 2, RailcomPacket::MOB_ADRLOW, 3))); +} + TEST_F(RailcomDecodeTest, ChannelBoundaryProblem) { fb_.add_ch1_data(0x8b); // note wrong channel assignment fb_.add_ch2_data(0xac);