From 488e6f1b0de0da0c8a568003ab926025cb46ec15 Mon Sep 17 00:00:00 2001 From: Tobin Richard Date: Sun, 25 Jul 2021 22:08:44 +1000 Subject: [PATCH 1/7] Reduce the number of times the config file is opened in SimpleStack (#555) Fixes #398. * Do not permit repeated calls to ConfigUpdateFlow::open_file() as this was ignoring the path argument. * Add ConfigUpdateFlow::get_fd() for callers which need access to the shared config file descriptor. * Reuse the file descriptor for the config file that was opened by the ConfigUpdateFlow when creating FileMemorySpace instances in SimpleStackBase. * Add missing #ifdef GTEST and fix #endif comments. --- src/openlcb/ConfigUpdateFlow.cxx | 14 ++++---------- src/openlcb/ConfigUpdateFlow.hxx | 13 +++++++++++-- src/openlcb/SimpleStack.cxx | 26 +++++++++++++++++++------- 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/src/openlcb/ConfigUpdateFlow.cxx b/src/openlcb/ConfigUpdateFlow.cxx index 13a4ae6a4..f46784f3c 100644 --- a/src/openlcb/ConfigUpdateFlow.cxx +++ b/src/openlcb/ConfigUpdateFlow.cxx @@ -42,16 +42,10 @@ namespace openlcb int ConfigUpdateFlow::open_file(const char *path) { - if (fd_ >= 0) return fd_; - if (!path) - { - fd_ = -1; - } - else - { - fd_ = ::open(path, O_RDWR); - HASSERT(fd_ >= 0); - } + HASSERT(fd_ < 0); + HASSERT(path); + fd_ = ::open(path, O_RDWR); + HASSERT(fd_ >= 0); return fd_; } diff --git a/src/openlcb/ConfigUpdateFlow.hxx b/src/openlcb/ConfigUpdateFlow.hxx index f298c1e60..2d07ea943 100644 --- a/src/openlcb/ConfigUpdateFlow.hxx +++ b/src/openlcb/ConfigUpdateFlow.hxx @@ -71,14 +71,22 @@ public: { } - /// Must be called once before calling anything else. Returns the file - /// descriptor. + /// Must be called once (only) before calling anything else. Returns the + /// file descriptor. int open_file(const char *path); /// Asynchronously invokes all update listeners with the config FD. void init_flow(); /// Synchronously invokes all update listeners to factory reset. void factory_reset(); + /// @return the file descriptor of the configuration file, or -1 if the + /// configuration file has not yet been opened. + int get_fd() + { + return fd_; + } + +#ifdef GTEST void TEST_set_fd(int fd) { fd_ = fd; @@ -86,6 +94,7 @@ public: bool TEST_is_terminated() { return is_terminated(); } +#endif // GTEST void trigger_update() override { diff --git a/src/openlcb/SimpleStack.cxx b/src/openlcb/SimpleStack.cxx index dd0214252..66eece97a 100644 --- a/src/openlcb/SimpleStack.cxx +++ b/src/openlcb/SimpleStack.cxx @@ -103,8 +103,13 @@ void SimpleStackBase::start_stack(bool delay_start) { #if OPENMRN_HAVE_POSIX_FD // Opens the eeprom file and sends configuration update commands to all - // listeners. - configUpdateFlow_.open_file(CONFIG_FILENAME); + // listeners. We must only call ConfigUpdateFlow::open_file() once and it + // may have been done by an earlier call to create_config_file_if_needed() + // or check_version_and_factory_reset(). + if (configUpdateFlow_.get_fd() < 0) + { + configUpdateFlow_.open_file(CONFIG_FILENAME); + } configUpdateFlow_.init_flow(); #endif // have posix fd @@ -139,12 +144,12 @@ void SimpleStackBase::default_start_node() #if OPENMRN_HAVE_POSIX_FD { auto *space = new FileMemorySpace( - SNIP_DYNAMIC_FILENAME, sizeof(SimpleNodeDynamicValues)); + configUpdateFlow_.get_fd(), sizeof(SimpleNodeDynamicValues)); memoryConfigHandler_.registry()->insert( node(), MemoryConfigDefs::SPACE_ACDI_USR, space); additionalComponents_.emplace_back(space); } -#endif // NOT ARDUINO, YES ESP32 +#endif // OPENMRN_HAVE_POSIX_FD size_t cdi_size = strlen(CDI_DATA); if (cdi_size > 0) { @@ -157,12 +162,13 @@ void SimpleStackBase::default_start_node() #if OPENMRN_HAVE_POSIX_FD if (CONFIG_FILENAME != nullptr) { - auto *space = new FileMemorySpace(CONFIG_FILENAME, CONFIG_FILE_SIZE); + auto *space = + new FileMemorySpace(configUpdateFlow_.get_fd(), CONFIG_FILE_SIZE); memory_config_handler()->registry()->insert( node(), openlcb::MemoryConfigDefs::SPACE_CONFIG, space); additionalComponents_.emplace_back(space); } -#endif // NOT ARDUINO, YES ESP32 +#endif // OPENMRN_HAVE_POSIX_FD } SimpleTrainCanStack::SimpleTrainCanStack( @@ -220,6 +226,7 @@ int SimpleStackBase::create_config_file_if_needed(const InternalConfigData &cfg, uint16_t expected_version, unsigned file_size) { HASSERT(CONFIG_FILENAME); + HASSERT(configUpdateFlow_.get_fd() < 0); struct stat statbuf; bool reset = false; bool extend = false; @@ -318,7 +325,12 @@ int SimpleStackBase::check_version_and_factory_reset( const InternalConfigData &cfg, uint16_t expected_version, bool force) { HASSERT(CONFIG_FILENAME); - int fd = configUpdateFlow_.open_file(CONFIG_FILENAME); + int fd = configUpdateFlow_.get_fd(); + if (fd < 0) + { + fd = configUpdateFlow_.open_file(CONFIG_FILENAME); + } + if (cfg.version().read(fd) != expected_version) { /// @todo (balazs.racz): We need to clear the eeprom. Best would be if From 0c9e3a9a3e080e408431fe6267f2106d7717ecd5 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 25 Jul 2021 17:04:15 +0200 Subject: [PATCH 2/7] Adds missing header guard. (#557) --- src/utils/Crc.hxx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/utils/Crc.hxx b/src/utils/Crc.hxx index a3069935d..549e57b72 100644 --- a/src/utils/Crc.hxx +++ b/src/utils/Crc.hxx @@ -32,6 +32,9 @@ * @date 16 Dec 2014 */ +#ifndef _UTILS_CRC_HXX_ +#define _UTILS_CRC_HXX_ + #include #include @@ -186,3 +189,5 @@ private: /// Current value of the state register for the CRC computation. uint8_t state_; }; + +#endif // _UTILS_CRC_HXX_ From 05e5d614fffe9e4cb82d03b7593a41123ca3d55f Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 2 Aug 2021 22:17:00 +0200 Subject: [PATCH 3/7] Adds driver and hooks for sending railcom data out. (#551) This PR enables the dcc_decoder code to interact with a RailCom driver in a way that allows sending railcom data back to the track. - Adds the ability to adjust what timing the railcom driver gets called. This is expectedto change when we generate a cutout versus when we trigger the UART to send data. - adds calls to the RailcomDriver to specify the feedback to be sent - Ensures that railcom data only gets sent for the packet that it belongs to - Adds an STM32 implementation for the railcom send driver, which is derived from the Stm32Uart driver. - Adds a hook in the dcc_decode interrupt that calls an application provided module to compute what railcom feedback we should be sending. ==== * Adds a delta capability for tuning the railcom cutout timing. * Fixes up the compilation of the Tiva DCC decoder target. * Adds API for railcom sender implementation. * Adds railcom sender implementation for STM32. * Adds a call in the DCC interrupt to fill in the railcom feedback. * Adds the send functions to the railcom driver. * Fixes bugs in the stm32 railcom sender. * Removes unneeded forward declaration. --- .../HwInit.cxx | 18 +++ .../freertos.armv7m.ek-tm4c123gxl/HwInit.cxx | 17 +++ src/dcc/PacketProcessor.hxx | 54 ++++++++ src/freertos_drivers/common/DccDecoder.hxx | 33 ++++- src/freertos_drivers/common/RailcomDriver.hxx | 25 ++++ src/freertos_drivers/st/Stm32DCCDecoder.hxx | 18 +++ .../st/Stm32RailcomSender.cxx | 88 ++++++++++++ .../st/Stm32RailcomSender.hxx | 126 ++++++++++++++++++ src/freertos_drivers/st/Stm32Uart.hxx | 3 +- src/freertos_drivers/ti/TivaDCCDecoder.hxx | 18 +++ .../freertos_drivers/stm32cubef091xc/sources | 3 +- 11 files changed, 398 insertions(+), 5 deletions(-) create mode 100644 src/dcc/PacketProcessor.hxx create mode 100644 src/freertos_drivers/st/Stm32RailcomSender.cxx create mode 100644 src/freertos_drivers/st/Stm32RailcomSender.hxx diff --git a/applications/dcc_decoder/targets/freertos.armv6m.st-stm32f091rc-nucleo/HwInit.cxx b/applications/dcc_decoder/targets/freertos.armv6m.st-stm32f091rc-nucleo/HwInit.cxx index 2e7664bbd..d7811a4af 100644 --- a/applications/dcc_decoder/targets/freertos.armv6m.st-stm32f091rc-nucleo/HwInit.cxx +++ b/applications/dcc_decoder/targets/freertos.armv6m.st-stm32f091rc-nucleo/HwInit.cxx @@ -122,6 +122,24 @@ struct DccDecoderHW /// driver to take a feedback sample. static inline void after_feedback_hook() {} + /// How many usec later/earlier should the railcom cutout start happen. + static int time_delta_railcom_pre_usec() + { + return 0; + } + + /// How many usec later/earlier should the railcom cutout middle happen. + static int time_delta_railcom_mid_usec() + { + return 0; + } + + /// How many usec later/earlier should the railcom cutout end happen. + static int time_delta_railcom_end_usec() + { + return 0; + } + /// Second timer resource that will be used to measure microseconds for the /// railcom cutout. May be the same as the Capture Timer, if there are at /// least two channels on that timer resource. diff --git a/applications/dcc_decoder/targets/freertos.armv7m.ek-tm4c123gxl/HwInit.cxx b/applications/dcc_decoder/targets/freertos.armv7m.ek-tm4c123gxl/HwInit.cxx index 6471746e0..47389cb34 100644 --- a/applications/dcc_decoder/targets/freertos.armv7m.ek-tm4c123gxl/HwInit.cxx +++ b/applications/dcc_decoder/targets/freertos.armv7m.ek-tm4c123gxl/HwInit.cxx @@ -167,6 +167,23 @@ struct DCCDecode static inline void dcc_before_cutout_hook() {} static inline void dcc_packet_finished_hook() {} static inline void after_feedback_hook() {} + /// How many usec later/earlier should the railcom cutout start happen. + static int time_delta_railcom_pre_usec() + { + return 0; + } + + /// How many usec later/earlier should the railcom cutout middle happen. + static int time_delta_railcom_mid_usec() + { + return 0; + } + + /// How many usec later/earlier should the railcom cutout end happen. + static int time_delta_railcom_end_usec() + { + return 0; + } }; // Dummy implementation because we are not a railcom detector. diff --git a/src/dcc/PacketProcessor.hxx b/src/dcc/PacketProcessor.hxx new file mode 100644 index 000000000..96448cd2f --- /dev/null +++ b/src/dcc/PacketProcessor.hxx @@ -0,0 +1,54 @@ +/** \copyright + * Copyright (c) 2021, 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. + * + * \file PacketProcessor.hxx + * + * Interface used for processing DCC packets to generate the feedback data. + * + * @author Balazs Racz + * @date 10 July 2021 + */ + +#include "dcc/packet.h" + +class RailcomDriver; + +namespace dcc +{ + +/// Abstract class that is used as a plugin in the DCC decoder. The application +/// logic can implement this class and it will be called from the +/// interrupt. This is necessary to correctly generate the RailCom feedback to +/// be sent back. +class PacketProcessor +{ +public: + /// Called in an OS interrupt with the arrived packet. + virtual void packet_arrived( + const DCCPacket *pkt, RailcomDriver *railcom) = 0; +}; + +} // namespace dcc diff --git a/src/freertos_drivers/common/DccDecoder.hxx b/src/freertos_drivers/common/DccDecoder.hxx index 6bd0d69ff..f66bc266b 100644 --- a/src/freertos_drivers/common/DccDecoder.hxx +++ b/src/freertos_drivers/common/DccDecoder.hxx @@ -35,6 +35,7 @@ #include "RailcomDriver.hxx" // for debug pins #include "dcc/Receiver.hxx" +#include "dcc/PacketProcessor.hxx" #include "dcc/packet.h" /** @@ -95,6 +96,14 @@ public: inputData_->destroy(); } + /// Installs a hook that will be called in the interrupt context for each + /// incoming packet. + /// @param p the hook interface to be called. + void set_packet_processor(dcc::PacketProcessor *p) + { + packetProcessor_ = p; + } + /** Handles a raw interrupt. */ inline void interrupt_handler() __attribute__((always_inline)); @@ -212,12 +221,17 @@ private: bool prepCutout_ = false; /// Which window of the cutout we are in. uint32_t cutoutState_; + /// Counts unique identifiers for DCC packets to be returned. + uint32_t packetId_ = 0; /// How many times did we lose a DCC packet due to no buffer available. //uint32_t overflowCount_ {0}; /// notified for cutout events. RailcomDriver *railcomDriver_; + /// notified for every arrived DCC / MM packet within the interrupt. + dcc::PacketProcessor* packetProcessor_ = nullptr; + /// DCC packet decoder state machine and internal state. dcc::DccDecoder decoder_ {Module::get_ticks_per_usec()}; @@ -302,6 +316,12 @@ __attribute__((optimize("-O3"))) void DccDecoder::interrupt_handler() if (decoder_.before_dcc_cutout()) { prepCutout_ = true; + auto* p = decoder_.pkt(); + if (p) + { + p->feedback_key = ++packetId_; + } + railcomDriver_->set_feedback_key(packetId_); Module::dcc_before_cutout_hook(); } // If we are at the second half of the last 1 bit and the @@ -314,7 +334,8 @@ __attribute__((optimize("-O3"))) void DccDecoder::interrupt_handler() { //Debug::RailcomDriverCutout::set(true); Module::set_cap_timer_time(); - Module::set_cap_timer_delay_usec(RAILCOM_CUTOUT_PRE); + Module::set_cap_timer_delay_usec( + RAILCOM_CUTOUT_PRE + Module::time_delta_railcom_pre_usec()); inCutout_ = true; cutoutState_ = 0; if (decoder_.pkt()) @@ -365,14 +386,16 @@ DccDecoder::rcom_interrupt_handler() { case 0: { - Module::set_cap_timer_delay_usec(RAILCOM_CUTOUT_MID); + Module::set_cap_timer_delay_usec( + RAILCOM_CUTOUT_MID + Module::time_delta_railcom_mid_usec()); railcomDriver_->start_cutout(); cutoutState_ = 1; break; } case 1: { - Module::set_cap_timer_delay_usec(RAILCOM_CUTOUT_END); + Module::set_cap_timer_delay_usec( + RAILCOM_CUTOUT_END + Module::time_delta_railcom_end_usec()); railcomDriver_->middle_cutout(); cutoutState_ = 2; break; @@ -396,6 +419,10 @@ __attribute__((optimize("-O3"))) void DccDecoder::os_interrupt_handler() unsigned woken = 0; if (nextPacketFilled_) { + if (packetProcessor_) + { + packetProcessor_->packet_arrived(decoder_.pkt(), railcomDriver_); + } inputData_->advance(1); nextPacketFilled_ = false; inputData_->signal_condition_from_isr(); diff --git a/src/freertos_drivers/common/RailcomDriver.hxx b/src/freertos_drivers/common/RailcomDriver.hxx index c09bbec3a..44a0f8a34 100644 --- a/src/freertos_drivers/common/RailcomDriver.hxx +++ b/src/freertos_drivers/common/RailcomDriver.hxx @@ -36,6 +36,11 @@ #ifndef _FREERTOS_DRIVERS_COMMON_RAILCOMDRIVER_HXX_ #define _FREERTOS_DRIVERS_COMMON_RAILCOMDRIVER_HXX_ +#include + +#include "utils/macros.h" +#include "dcc/railcom.h" + /// Abstract base class for railcom drivers. This interface is used to /// communicate when the railcom cutout happens. The railcom cutout is produced /// or detected in the DCC generator or DCC parser driver, but the railcom @@ -67,6 +72,26 @@ public: * shall be called before start_cutout. The feedback key set here is used * until this method is called again. @param key is the new feedback key. */ virtual void set_feedback_key(uint32_t key) = 0; + + /** Specifies what packet should be sent for the channel1 cutout. It is + * okay to specify the same packet pointer for ch1 and ch2 cutout. + * @param ch1_pkt the RailCom packet. Only the ch1 data will be read from + * this packet. This pointer must stay alive until the next DCC packet + * comes. The FeedbackKey in this packet must be correct for the current + * DCC packet or else the data will not be sent. */ + virtual void send_ch1(const DCCFeedback *ch1_pkt) + { + } + + /** Specifies what packet should be sent for the channel2 cutout. It is + * okay to specify the same packet pointer for ch1 and ch2 cutout. + * @param ch2_pkt the RailCom packet. Only the ch2 data will be read from + * this packet. This pointer must stay alive until the next DCC packet + * comes. The FeedbackKey in this packet must be correct for the current + * DCC packet or else the data will not be sent. */ + virtual void send_ch2(const DCCFeedback *ch2_pkt) + { + } }; diff --git a/src/freertos_drivers/st/Stm32DCCDecoder.hxx b/src/freertos_drivers/st/Stm32DCCDecoder.hxx index 79d9df173..c981901fe 100644 --- a/src/freertos_drivers/st/Stm32DCCDecoder.hxx +++ b/src/freertos_drivers/st/Stm32DCCDecoder.hxx @@ -175,6 +175,24 @@ public: HW::after_feedback_hook(); } + /// How many usec later should the railcom cutout start happen. + static int time_delta_railcom_pre_usec() + { + return HW::time_delta_railcom_pre_usec(); + } + + /// How many usec later should the railcom cutout middle happen. + static int time_delta_railcom_mid_usec() + { + return HW::time_delta_railcom_mid_usec(); + } + + /// How many usec later should the railcom cutout middle happen. + static int time_delta_railcom_end_usec() + { + return HW::time_delta_railcom_end_usec(); + } + /// Called from the capture interrupt handler. Checks interrupt status, /// clears interrupt. /// @return true if the interrupt was generated by a capture event. diff --git a/src/freertos_drivers/st/Stm32RailcomSender.cxx b/src/freertos_drivers/st/Stm32RailcomSender.cxx new file mode 100644 index 000000000..d90f5a667 --- /dev/null +++ b/src/freertos_drivers/st/Stm32RailcomSender.cxx @@ -0,0 +1,88 @@ +/** \copyright + * Copyright (c) 2021, 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. + * + * \file Stm32RailcomSender.hxx + * + * Implements a RailcomDriver that sends outgoing railcom data through a UART + * TX on an STM32 target. Designed to be invoked from the priority zero + * interrupt of the DCC decoder. + * + * @author Balazs Racz + * @date July 10, 2021 + */ + +#include "freertos_drivers/st/Stm32RailcomSender.hxx" + +#include "stm32f_hal_conf.hxx" + +/// Called at the beginning of the first window. +void Stm32RailcomSender::start_cutout() +{ + const auto* pkt = ch1Pkt_; + if (!pkt || pkt->feedbackKey != expectedFeedbackKey_) + { + // Nothing to send or came too late and should not be sent. + return; + } + if (!__HAL_UART_GET_IT(&uartHandle, UART_IT_TXE)) { + // Transmission is not complete yet. That's weird. + return; + } + if (pkt->ch1Size == 0) { + // Nothing to send. + return; + } + uartHandle.Instance->TDR = pkt->ch1Data[0]; + if (pkt->ch1Size > 1) + { + txBuf->put(pkt->ch1Data + 1, 1); + __HAL_UART_ENABLE_IT(&uartHandle, UART_IT_TXE); + } +} + +void Stm32RailcomSender::middle_cutout() +{ + const auto* pkt = ch2Pkt_; + if (!pkt || pkt->feedbackKey != expectedFeedbackKey_) + { + // Nothing to send or came too late and should not be sent. + return; + } + if (!__HAL_UART_GET_IT(&uartHandle, UART_IT_TXE)) { + // Transmission is not complete yet. That's weird. + return; + } + if (pkt->ch2Size == 0) { + // Nothing to send. + return; + } + uartHandle.Instance->TDR = pkt->ch2Data[0]; + if (pkt->ch2Size > 1) + { + txBuf->put(pkt->ch2Data + 1, pkt->ch2Size - 1); + __HAL_UART_ENABLE_IT(&uartHandle, UART_IT_TXE); + } +} diff --git a/src/freertos_drivers/st/Stm32RailcomSender.hxx b/src/freertos_drivers/st/Stm32RailcomSender.hxx new file mode 100644 index 000000000..eeff2291b --- /dev/null +++ b/src/freertos_drivers/st/Stm32RailcomSender.hxx @@ -0,0 +1,126 @@ +/** \copyright + * Copyright (c) 2021, 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. + * + * \file Stm32RailcomSender.hxx + * + * Implements a RailcomDriver that sends outgoing railcom data through a UART + * TX on an STM32 target. Designed to be invoked from the priority zero + * interrupt of the DCC decoder. + * + * @author Balazs Racz + * @date July 10, 2021 + */ + +#include "freertos_drivers/common/RailcomDriver.hxx" +#include "dcc/railcom.h" +#include "freertos_drivers/st/Stm32Uart.hxx" + +class Stm32RailcomSender : public RailcomDriver, protected Stm32Uart +{ +public: + Stm32RailcomSender( + const char *name, USART_TypeDef *base, IRQn_Type interrupt) + : Stm32Uart(name, base, interrupt) + { + } + +public: + /// Specifies what packet should be sent for the channel1 cutout. It is + /// okay to specify the same packet pointer for ch1 and ch2 cutout. + /// @param ch1_pkt the RailCom packet. Only the ch1 data will be read from + /// this packet. This pointer must stay alive until the next DCC packet + /// comes. The FeedbackKey in this packet must be correct for the current + /// DCC packet or else the data will not be sent. + void send_ch1(const DCCFeedback *ch1_pkt) override + { + ch1Pkt_ = ch1_pkt; + } + + /// Specifies what packet should be sent for the channel2 cutout. It is + /// okay to specify the same packet pointer for ch1 and ch2 cutout. + /// @param ch2_pkt the RailCom packet. Only the ch2 data will be read from + /// this packet. This pointer must stay alive until the next DCC packet + /// comes. The FeedbackKey in this packet must be correct for the current + /// DCC packet or else the data will not be sent. + void send_ch2(const DCCFeedback *ch2_pkt) override + { + ch2Pkt_ = ch2_pkt; + } + + ssize_t write(File *file, const void *buf, size_t count) override + { + // We do not support writing through the regular posix API. + return -EIO; + } + + ssize_t read(File *file, void *buf, size_t count) override + { + // We do not support reading through the regular posix API. + return -EIO; + } + +private: + // ======= DCC driver API ======== + /// No implementation needed. + void feedback_sample() override + { + } + /// Called at the beginning of the first window. + void start_cutout() override; + /// Called at the beginning of the middle window. + void middle_cutout() override; + /// Called after the cutout is over. + void end_cutout() override { + // We throw away the packets that we got given. + //ch1Pkt_ = nullptr; + //ch2Pkt_ = nullptr; + } + /// Called instead of start/mid/end-cutout at the end of the current packet + /// if there was no cutout requested. + void no_cutout() override + { + // We throw away the packets that we got given. + //ch1Pkt_ = nullptr; + //ch2Pkt_ = nullptr; + } + /// Feedback key is set by the DCC decoder driver. The feedback packet must + /// carry the same feedback key or else it will not be transmitted. + void set_feedback_key(uint32_t key) override + { + expectedFeedbackKey_ = key; + } + +private: + /// What should be the feedback key in the packet. This value comes from + /// the DCC driver and is compared to the RailCom packets we should be + /// sending at the beginning of the cutout windows. + uintptr_t expectedFeedbackKey_ = 0; + + /// The packet to send in channel 1. Externally owned. + const DCCFeedback *ch1Pkt_ = nullptr; + /// The packet to send in channel 2. Externally owned. + const DCCFeedback *ch2Pkt_ = nullptr; +}; diff --git a/src/freertos_drivers/st/Stm32Uart.hxx b/src/freertos_drivers/st/Stm32Uart.hxx index 0ff8c87a9..aa403d9c8 100644 --- a/src/freertos_drivers/st/Stm32Uart.hxx +++ b/src/freertos_drivers/st/Stm32Uart.hxx @@ -67,7 +67,7 @@ public: * include/freertos/tc_ioctl.h */ int ioctl(File *file, unsigned long int key, unsigned long data) override; -private: +protected: void enable() override; /**< function to enable device */ void disable() override; /**< function to disable device */ @@ -115,6 +115,7 @@ private: static Stm32Uart *instances[8]; #endif +private: /** Default constructor. */ Stm32Uart(); diff --git a/src/freertos_drivers/ti/TivaDCCDecoder.hxx b/src/freertos_drivers/ti/TivaDCCDecoder.hxx index 4b762d49a..74203105a 100644 --- a/src/freertos_drivers/ti/TivaDCCDecoder.hxx +++ b/src/freertos_drivers/ti/TivaDCCDecoder.hxx @@ -136,6 +136,24 @@ public: HW::after_feedback_hook(); } + /// How many usec later should the railcom cutout start happen. + static int time_delta_railcom_pre_usec() + { + return HW::time_delta_railcom_pre_usec(); + } + + /// How many usec later should the railcom cutout middle happen. + static int time_delta_railcom_mid_usec() + { + return HW::time_delta_railcom_mid_usec(); + } + + /// How many usec later should the railcom cutout middle happen. + static int time_delta_railcom_end_usec() + { + return HW::time_delta_railcom_end_usec(); + } + /// Called from the capture interrupt handler. Checks interrupt status, /// clears interrupt. /// @return true if the interrupt was generated by a capture event. diff --git a/targets/freertos.armv6m/freertos_drivers/stm32cubef091xc/sources b/targets/freertos.armv6m/freertos_drivers/stm32cubef091xc/sources index e7308f35f..6f2189d68 100644 --- a/targets/freertos.armv6m/freertos_drivers/stm32cubef091xc/sources +++ b/targets/freertos.armv6m/freertos_drivers/stm32cubef091xc/sources @@ -49,7 +49,8 @@ CSRCS += stm32f0xx_hal.c \ CXXSRCS += Stm32Can.cxx \ Stm32Uart.cxx \ Stm32SPI.cxx \ - Stm32EEPROMEmulation.cxx + Stm32EEPROMEmulation.cxx \ + Stm32RailcomSender.cxx # for some reason, -0s changes the behavior of HAL_FLASHEx_Erase() and # doesn't perform the erase. From 6c430b6a8e53649def9b5db8e5f41ff4f75adf33 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 2 Aug 2021 22:20:28 +0200 Subject: [PATCH 4/7] Fix comment. --- src/dcc/RailCom.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dcc/RailCom.hxx b/src/dcc/RailCom.hxx index c765a90ee..bafe54b16 100644 --- a/src/dcc/RailCom.hxx +++ b/src/dcc/RailCom.hxx @@ -87,7 +87,7 @@ extern const uint8_t railcom_decode[256]; /// Table for 6-to-8 encoding of railcom data. The table can be indexed by a /// 6-bit value that is the semantic content of a railcom byte, and returns the /// matching 8-bit value to put out on the UART. This table only contains the -/// standard codes, for the special codes like ACK use RailcomDefs above. +/// standard codes, for the special codes like ACK use RailcomDefs::ACK. extern const uint8_t railcom_encode[64]; /// Special constant values returned by the @ref railcom_decode[] array. From 9efe188defc2e8beccf8c342e23daafe9a10c7c9 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 2 Aug 2021 22:22:07 +0200 Subject: [PATCH 5/7] Adds routines to encode railcom data for a sender. (#553) - Adds 6-to-8 encode table - Adds helper functions to perform encoding - Adds unittests that validate that encoding and decoding are correctly inverse of each other. Fixes constants: - Aligns with RCN-217 on the two different ACK values - Adds the newly appointed NACK value from RCN-217 Refactoring in the RailCom header: - Makes RailcomDefs to a struct from a namespace. This is aligned better with the style of the rest of the codebase. === * Adds routines to encode railcom data for a sender. - Adds 6-to-8 encode table - Adds helper functions to perform encoding - Adds unittests that validate that encoding and decoding are correctly inverse of each other. Fixes contants: - Aligns with RCN-217 on the two different ACK values - Adds the newly appointed NACK value from RCN-217 Refactoring in the RailCom header: - Makes RailcomDefs to a struct from a namespace. This is aligned better with the style of the rest of the codebase. * Fix comment. --- src/dcc/RailCom.cxx | 91 ++++++++++++++++++++++++++++---- src/dcc/RailCom.cxxtest | 70 ++++++++++++++++++++++--- src/dcc/RailCom.hxx | 113 ++++++++++++++++++++++++++++++++-------- 3 files changed, 237 insertions(+), 37 deletions(-) diff --git a/src/dcc/RailCom.cxx b/src/dcc/RailCom.cxx index cd6981c2e..e0f89b5f8 100644 --- a/src/dcc/RailCom.cxx +++ b/src/dcc/RailCom.cxx @@ -37,22 +37,21 @@ #include "dcc/RailCom.hxx" namespace dcc { -using RailcomDefs::INV; -using RailcomDefs::ACK; -using RailcomDefs::NACK; -using RailcomDefs::BUSY; -using RailcomDefs::RESVD1; -using RailcomDefs::RESVD2; -using RailcomDefs::RESVD3; +static constexpr uint8_t INV = RailcomDefs::INV; +static constexpr uint8_t ACK = RailcomDefs::ACK; +static constexpr uint8_t NACK = RailcomDefs::NACK; +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, NACK, + 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, RESVD3, INV, INV, 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, @@ -79,6 +78,73 @@ const uint8_t railcom_decode[256] = INV, INV, INV, INV, INV, INV, INV, INV, }; +const uint8_t railcom_encode[64] = { + 0b10101100, + 0b10101010, + 0b10101001, + 0b10100101, + 0b10100011, + 0b10100110, + 0b10011100, + 0b10011010, + 0b10011001, + 0b10010101, + 0b10010011, + 0b10010110, + 0b10001110, + 0b10001101, + 0b10001011, + 0b10110001, + 0b10110010, + 0b10110100, + 0b10111000, + 0b01110100, + 0b01110010, + 0b01101100, + 0b01101010, + 0b01101001, + 0b01100101, + 0b01100011, + 0b01100110, + 0b01011100, + 0b01011010, + 0b01011001, + 0b01010101, + 0b01010011, + 0b01010110, + 0b01001110, + 0b01001101, + 0b01001011, + 0b01000111, + 0b01110001, + 0b11101000, + 0b11100100, + 0b11100010, + 0b11010001, + 0b11001001, + 0b11000101, + 0b11011000, + 0b11010100, + 0b11010010, + 0b11001010, + 0b11000110, + 0b11001100, + 0b01111000, + 0b00010111, + 0b00011011, + 0b00011101, + 0b00011110, + 0b00101110, + 0b00110110, + 0b00111010, + 0b00100111, + 0b00101011, + 0b00101101, + 0b00110101, + 0b00111001, + 0b00110011, +}; + /// Helper function to parse a part of a railcom packet. /// /// @param fb_channel Which hardware channel did the railcom message arrive @@ -159,6 +225,13 @@ void parse_internal(uint8_t fb_channel, uint8_t railcom_channel, len = 2; } break; + case RMOB_XPOM0: + case RMOB_XPOM1: + case RMOB_XPOM2: + case RMOB_XPOM3: + type = RailcomPacket::MOB_XPOM0 + (packet_id - RMOB_XPOM0); + len = 6; + break; case RMOB_DYN: type = RailcomPacket::MOB_DYN; len = 3; diff --git a/src/dcc/RailCom.cxxtest b/src/dcc/RailCom.cxxtest index 7e026e3ca..c20fe9739 100644 --- a/src/dcc/RailCom.cxxtest +++ b/src/dcc/RailCom.cxxtest @@ -82,13 +82,29 @@ TEST_F(RailcomDecodeTest, SimpleAck) { } TEST_F(RailcomDecodeTest, MultipleAckNackBusy) { - fb_.add_ch1_data(0xF0); - fb_.add_ch1_data(0xE1); - fb_.add_ch2_data(0x0F); + fb_.add_ch1_data(0xF0); // one type of ack + fb_.add_ch1_data(0xE1); // NMRA busy + fb_.add_ch2_data(0x3C); // RCN nack + fb_.add_ch2_data(0x0F); // other ack + decode(); + EXPECT_THAT(output_, + ElementsAre(RailcomPacket(3, 1, RailcomPacket::ACK, 0), + RailcomPacket(3, 1, RailcomPacket::BUSY, 0), + RailcomPacket(3, 2, RailcomPacket::NACK, 0), + RailcomPacket(3, 2, RailcomPacket::ACK, 0))); +} + +TEST_F(RailcomDecodeTest, MultipleAckNackBusyCode) { + fb_.add_ch1_data(RailcomDefs::CODE_ACK); // one type of ack + fb_.add_ch1_data(RailcomDefs::CODE_BUSY); // NMRA busy + fb_.add_ch2_data(RailcomDefs::CODE_NACK); // RCN nack + fb_.add_ch2_data(RailcomDefs::CODE_ACK2); // other ack decode(); - EXPECT_THAT(output_, ElementsAre(RailcomPacket(3, 1, RailcomPacket::ACK, 0), - RailcomPacket(3, 1, RailcomPacket::BUSY, 0), - RailcomPacket(3, 2, RailcomPacket::NACK, 0))); + EXPECT_THAT(output_, + ElementsAre(RailcomPacket(3, 1, RailcomPacket::ACK, 0), + RailcomPacket(3, 1, RailcomPacket::BUSY, 0), + RailcomPacket(3, 2, RailcomPacket::NACK, 0), + RailcomPacket(3, 2, RailcomPacket::ACK, 0))); } TEST_F(RailcomDecodeTest, Ch2Ext) { @@ -124,4 +140,46 @@ TEST_F(RailcomDecodeTest, FalseChannelBoundaryProblem) { EXPECT_THAT(output_, ElementsAre(RailcomPacket(3, 1, RailcomPacket::GARBAGE, 0), RailcomPacket(3, 2, RailcomPacket::MOB_EXT, 128))); } +// Verifies that the railcom encode and railcom decode table are inverse of +// each other. +TEST(RailcomEncodeTest, BitsMatch) { + for (unsigned i = 0; i < 63; i++) + { + uint8_t encoded = railcom_encode[i]; + uint8_t decoded = railcom_decode[encoded]; + EXPECT_EQ(i, decoded); + } + EXPECT_EQ(RailcomDefs::ACK, railcom_decode[RailcomDefs::CODE_ACK]); + EXPECT_EQ(RailcomDefs::ACK, railcom_decode[RailcomDefs::CODE_ACK2]); + EXPECT_EQ(RailcomDefs::NACK, railcom_decode[RailcomDefs::CODE_NACK]); +} + +// Verifies that the railcom encode 12 command works correctly. +TEST_F(RailcomDecodeTest, Encode12) { + uint16_t d = RailcomDefs::encode12(RMOB_ADRHIGH, 42); + fb_.add_ch1_data(d>>8); + fb_.add_ch1_data(d & 0xff); + decode(); + EXPECT_THAT(output_, + ElementsAre(RailcomPacket(3, 1, RailcomPacket::MOB_ADRHIGH, 42))); +} + +// Verifies that the railcom encode 12 command works correctly. +TEST_F(RailcomDecodeTest, Append12) { + RailcomDefs::append12(RMOB_ADRHIGH, 42, fb_.ch1Data); + fb_.ch1Size = 2; + decode(); + EXPECT_THAT(output_, + ElementsAre(RailcomPacket(3, 1, RailcomPacket::MOB_ADRHIGH, 42))); +} + +// Verifies that the railcom encode 36 command works correctly. +TEST_F(RailcomDecodeTest, Append36) { + RailcomDefs::append36(RMOB_XPOM2, 0xfedc5432, fb_.ch2Data); + fb_.ch2Size = 6; + decode(); + EXPECT_THAT(output_, + ElementsAre(RailcomPacket(3, 2, RailcomPacket::MOB_XPOM2, 0xfedc5432u))); +} + } // namespace dcc diff --git a/src/dcc/RailCom.hxx b/src/dcc/RailCom.hxx index 9a41f473d..bafe54b16 100644 --- a/src/dcc/RailCom.hxx +++ b/src/dcc/RailCom.hxx @@ -79,33 +79,94 @@ struct Feedback : public DCCFeedback /// Formats a dcc::Feedback message into a debug string. std::string railcom_debug(const Feedback& fb); -/// Special constant values returned by the @ref railcom_decode[] array. -namespace RailcomDefs -{ - /// invalid value (not conforming to the 4bit weighting requirement) - static const uint8_t INV = 0xff; - /// Railcom ACK; the decoder received the message ok. NOTE: some early - /// software versions may have ACK and NACK exchanged. - static const uint8_t ACK = 0xfe; - /// The decoder rejected the packet. - static const uint8_t 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. - static const uint8_t BUSY = 0xfc; - /// Reserved for future expansion. - static const uint8_t RESVD1 = 0xfb; - /// Reserved for future expansion. - static const uint8_t RESVD2 = 0xfa; - /// Reserved for future expansion. - static const uint8_t RESVD3 = 0xf8; -} - /** 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 constants in @ref RailcomDefs. If the * value is invalid, the INV constant is returned. */ extern const uint8_t railcom_decode[256]; +/// Table for 6-to-8 encoding of railcom data. The table can be indexed by a +/// 6-bit value that is the semantic content of a railcom byte, and returns the +/// matching 8-bit value to put out on the UART. This table only contains the +/// standard codes, for the special codes like ACK use RailcomDefs::ACK. +extern const uint8_t railcom_encode[64]; + +/// Special constant values returned by the @ref railcom_decode[] array. +struct RailcomDefs +{ + // These values appear in the railcom_decode table to mean special symbols. + enum + { + /// 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. + BUSY = 0xfc, + + /// Reserved for future expansion. + RESVD1 = 0xfb, + /// Reserved for future expansion. + RESVD2 = 0xfa, + }; + + // These values need to be sent on the UART + enum + { + /// Code point for ACK (according to RCN-217) + CODE_ACK = 0xf0, + /// Another accepted code point for ACK (according to RCN-217) + CODE_ACK2 = 0x0f, + /// Code point for NACK (according to RCN-217) + CODE_NACK = 0x3c, + /// Code point for BUSY (according to NMRA S-9.3.2) + CODE_BUSY = 0xE1, + }; + + /// Encodes 12 bits of useful payload into 16 bits of UART data to transmit. + /// @param nibble top 4 bits of the payload to send + /// @param data bottom 8 bits of payload to send. + /// @return the uart bytes, first byte in the high 8 bits, second byte in + /// the low 8 bits. + static uint16_t encode12(uint8_t nibble, uint8_t data) + { + return (railcom_encode[((nibble << 2) | (data >> 6)) & 0x3F] << 8) | + railcom_encode[data & 0x3f]; + } + + /// Encodes 12 bits of useful payload into 16 bits of UART data to transmit. + /// @param nibble top 4 bits of the payload to send + /// @param data bottom 8 bits of payload to send. + /// @param dst this is where the payload will be stored. + static void append12(uint8_t nibble, uint8_t data, uint8_t* dst) + { + *dst++ = railcom_encode[((nibble << 2) | (data >> 6)) & 0x3F]; + *dst++ = railcom_encode[data & 0x3f]; + } + + /// Encodes a 36-bit railcom datagram into UART bytes. + /// @param nibble the railcom ID (top 4 bits) + /// @param data the 32 bit payload. Will be transmitted MSbyte-first. + /// @param dst this is where the payload will be stored. + static void append36(uint8_t nibble, uint32_t data, uint8_t* dst) + { + *dst++ = railcom_encode[((nibble << 2) | (data >> 30)) & 0x3F]; + *dst++ = railcom_encode[(data >> 24) & 0x3F]; + *dst++ = railcom_encode[(data >> 18) & 0x3F]; + *dst++ = railcom_encode[(data >> 12) & 0x3F]; + *dst++ = railcom_encode[(data >> 6) & 0x3F]; + *dst++ = railcom_encode[data & 0x3F]; + } + +private: + /// This struct cannot be instantiated. + RailcomDefs(); +}; + /// Packet identifiers from Mobile Decoders. enum RailcomMobilePacketId @@ -115,6 +176,10 @@ enum RailcomMobilePacketId RMOB_ADRLOW = 2, RMOB_EXT = 3, RMOB_DYN = 7, + RMOB_XPOM0 = 8, + RMOB_XPOM1 = 9, + RMOB_XPOM2 = 10, + RMOB_XPOM3 = 11, RMOB_SUBID = 12, }; @@ -134,6 +199,10 @@ struct RailcomPacket MOB_ADRLOW, MOB_EXT, MOB_DYN, + MOB_XPOM0, + MOB_XPOM1, + MOB_XPOM2, + MOB_XPOM3, MOB_SUBID }; /// which detector supplied this data From d1c544de30a7094715822ea4d79463f3a7a274a0 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 2 Aug 2021 22:29:26 +0200 Subject: [PATCH 6/7] Adds some example railcom feedback (#552) Updates the dcc decoder app for the stm32f0 to send back some railcom data. === * Adds a railcom UART transmitter to the f091 dcc decoder. * Adds support for F0 command decoding. * Switches uart1 to the stm32 railcom driver. * Implements the railcom sender in the main for the dcc-decoder app. * Sends all zeros to check timing. --- applications/dcc_decoder/main.cxx | 104 +++++++++++++++++- .../HwInit.cxx | 28 ++++- .../hardware.hxx | 12 +- 3 files changed, 136 insertions(+), 8 deletions(-) diff --git a/applications/dcc_decoder/main.cxx b/applications/dcc_decoder/main.cxx index 3d7ca40e0..e732766c3 100644 --- a/applications/dcc_decoder/main.cxx +++ b/applications/dcc_decoder/main.cxx @@ -41,12 +41,102 @@ #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 "freertos/tc_ioctl.h" + +#include "hardware.hxx" Executor<1> executor("executor", 0, 2048); // We reserve a lot of buffer for transmit to cover for small hiccups in the // host reading data. OVERRIDE_CONST(serial_tx_buffer_size, 2048); +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; + +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; + } + unsigned ofs = 1; + if ((dcc_address_wire >> 8) > 127) { + // Two byte address. + ofs++; + if (p.payload[1] != (dcc_address_wire & 0xff)) { + return; + } + } + if ((p.payload[ofs] >> 5) == 0b100) + { + // F0-F4 packet + ofs++; + f0 = p.payload[ofs] & 0b00010000 ? 1 : 0; + } +} + +class IrqProcessor : public dcc::PacketProcessor { +public: + 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 + } + + void packet_arrived( + const DCCPacket *pkt, RailcomDriver *railcom) override { + DEBUG1_Pin::set(true); + 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_); + DEBUG1_Pin::set(false); + } + +private: + dcc::Feedback ch1_; + dcc::Feedback ch2_; +} irqProc; + +extern "C" { +void set_dcc_interrupt_processor(dcc::PacketProcessor *p); +} /** Entry point to application. * @param argc number of command line arguments @@ -61,12 +151,22 @@ int appl_main(int argc, char *argv[]) //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); + + irqProc.init(); + set_dcc_interrupt_processor(&irqProc); + int cnt = 0; while (1) { DCCPacket packet_data; int sret = ::read(fd, &packet_data, sizeof(packet_data)); HASSERT(sret == sizeof(packet_data)); + DEBUG1_Pin::set(true); + process_packet(packet_data); long long t = os_get_time_monotonic(); string txt = StringPrintf("\n%02d.%06d %04d ", (unsigned)((t / 1000000000) % 100), @@ -78,7 +178,9 @@ int appl_main(int argc, char *argv[]) // not enough space in the serial write buffer, we need to throw away // data. ++cnt; - resetblink((cnt >> 3) & 1); + uint8_t blink = ((cnt >> 3) & 15) == 1u ? 1 : 0; + resetblink(f0 ^ blink); + DEBUG1_Pin::set(false); } return 0; } diff --git a/applications/dcc_decoder/targets/freertos.armv6m.st-stm32f091rc-nucleo/HwInit.cxx b/applications/dcc_decoder/targets/freertos.armv6m.st-stm32f091rc-nucleo/HwInit.cxx index d7811a4af..f1365b38d 100644 --- a/applications/dcc_decoder/targets/freertos.armv6m.st-stm32f091rc-nucleo/HwInit.cxx +++ b/applications/dcc_decoder/targets/freertos.armv6m.st-stm32f091rc-nucleo/HwInit.cxx @@ -48,6 +48,7 @@ #include "Stm32Uart.hxx" #include "Stm32Can.hxx" #include "Stm32EEPROMEmulation.hxx" +#include "Stm32RailcomSender.hxx" #include "hardware.hxx" #include "DummyGPIO.hxx" @@ -73,6 +74,9 @@ const char *STDERR_DEVICE = "/dev/ser0"; /** UART 0 serial driver instance */ static Stm32Uart uart0("/dev/ser0", USART2, USART2_IRQn); +/** RailCom sender UART */ +static Stm32RailcomSender railcomUart("/dev/ser1", USART1, USART1_IRQn); + /** CAN 0 CAN driver instance */ static Stm32Can can0("/dev/can0"); @@ -125,13 +129,13 @@ struct DccDecoderHW /// How many usec later/earlier should the railcom cutout start happen. static int time_delta_railcom_pre_usec() { - return 0; + return 80 - 26; } /// How many usec later/earlier should the railcom cutout middle happen. static int time_delta_railcom_mid_usec() { - return 0; + return 193 - 185; } /// How many usec later/earlier should the railcom cutout end happen. @@ -160,14 +164,16 @@ struct DccDecoderHW static constexpr auto OS_IRQn = TSC_IRQn; }; -// Dummy implementation because we are not a railcom detector. -NoRailcomDriver railcom_driver; - Stm32DccDecoder dcc_decoder0( - "/dev/dcc_decoder0", &railcom_driver); + "/dev/dcc_decoder0", &railcomUart); extern "C" { +void set_dcc_interrupt_processor(dcc::PacketProcessor *p) +{ + dcc_decoder0.set_packet_processor(p); +} + /** Blink LED */ uint32_t blinker_pattern = 0; static uint32_t rest_pattern = 0; @@ -274,6 +280,7 @@ void hw_preinit(void) __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); + __HAL_RCC_USART1_CLK_ENABLE(); __HAL_RCC_USART2_CLK_ENABLE(); __HAL_RCC_CAN1_CLK_ENABLE(); __HAL_RCC_TIM14_CLK_ENABLE(); @@ -293,6 +300,14 @@ void hw_preinit(void) gpio_init.Pin = GPIO_PIN_3; HAL_GPIO_Init(GPIOA, &gpio_init); + /* USART1 pinmux on railCom TX pin PB6 */ + gpio_init.Mode = GPIO_MODE_AF_PP; + gpio_init.Pull = GPIO_PULLUP; + gpio_init.Speed = GPIO_SPEED_FREQ_HIGH; + gpio_init.Alternate = GPIO_AF0_USART1; + gpio_init.Pin = GPIO_PIN_6; + HAL_GPIO_Init(GPIOB, &gpio_init); + /* CAN pinmux on PB8 and PB9 */ gpio_init.Mode = GPIO_MODE_AF_PP; gpio_init.Pull = GPIO_PULLUP; @@ -339,6 +354,7 @@ void timer3_interrupt_handler(void) { void touch_interrupt_handler(void) { dcc_decoder0.os_interrupt_handler(); + portYIELD_FROM_ISR(true); } } diff --git a/applications/dcc_decoder/targets/freertos.armv6m.st-stm32f091rc-nucleo/hardware.hxx b/applications/dcc_decoder/targets/freertos.armv6m.st-stm32f091rc-nucleo/hardware.hxx index 3f022fb6c..646e67af0 100644 --- a/applications/dcc_decoder/targets/freertos.armv6m.st-stm32f091rc-nucleo/hardware.hxx +++ b/applications/dcc_decoder/targets/freertos.armv6m.st-stm32f091rc-nucleo/hardware.hxx @@ -8,8 +8,18 @@ GPIO_PIN(LED_GREEN_RAW, LedPin, A, 5); GPIO_PIN(SW_USER, GpioInputPU, C, 13); GPIO_PIN(DCC_IN, GpioInputPU, A, 6); +GPIO_PIN(RAILCOM_TX, GpioOutputSafeHigh, B, 6); -typedef GpioInitializer GpioInit; +GPIO_PIN(DEBUG1, GpioOutputSafeLow, C, 9); +GPIO_PIN(DEBUG2, GpioOutputSafeLow, C, 8); + +typedef GpioInitializer< // + LED_GREEN_RAW_Pin, // + SW_USER_Pin, // + DCC_IN_Pin, // + DEBUG1_Pin, // + DEBUG2_Pin> + GpioInit; typedef LED_GREEN_RAW_Pin BLINKER_RAW_Pin; typedef BLINKER_Pin LED_GREEN_Pin; From 8c4e7bf6a731e52e21c9d682ede5b6ab0e8ab6c5 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 2 Aug 2021 22:30:50 +0200 Subject: [PATCH 7/7] Tiva 129 updates around emulator (#556) - Fixes blink_raw application for Tiva 129 by removing the console from blink_raw. - Adds console the same way to async_blink which is expected to have the higher complexity. - Fixes flash commands to work with newer openocd releases - Adds alternative flash commands that work with XDS110 - Fixes search path for openocd scripts to work with newer openocd releases. === * Adjust the openocd commandline to work with the 432E4 launchpad. * removes console from blink_raw and moves it to async_blink. There seems to be an issue with listen on a tiva 129 which breaks the blink_raw application when the console is enabled. * Update flashing command for 129. * Fixes the search path for newer openocd releases. * Adds some alternative openocd arguments. * Adds comments. --- applications/async_blink/main.cxx | 7 +++---- applications/blink_raw/main.cxx | 12 ------------ .../freertos.armv7m.ti-launchpad-tm4c129/Makefile | 9 ++++++++- etc/path.mk | 2 +- 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/applications/async_blink/main.cxx b/applications/async_blink/main.cxx index 7a3c4e33a..cbf0c8020 100644 --- a/applications/async_blink/main.cxx +++ b/applications/async_blink/main.cxx @@ -63,7 +63,8 @@ #include "utils/ESPWifiClient.hxx" #endif -#if defined (BOARD_LAUNCHPAD_EK) || defined (__linux__) +#if (defined (TARGET_IS_CC3200) || defined (__linux__) || defined(PART_TM4C1294NCPDT)) && (!defined(NO_CONSOLE)) +#define HAVE_CONSOLE #include "console/Console.hxx" #endif @@ -206,9 +207,7 @@ openlcb::BitEventConsumer consumer(&logger); */ int appl_main(int argc, char* argv[]) { -#if defined (BOARD_LAUNCHPAD_EK) - //new Console(stack.executor(), Console::FD_STDIN, Console::FD_STDOUT); -#elif defined (__linux__) +#ifdef HAVE_CONSOLE new Console(stack.executor(), Console::FD_STDIN, Console::FD_STDOUT, 2121); #endif diff --git a/applications/blink_raw/main.cxx b/applications/blink_raw/main.cxx index d6eca82d7..387a2abc2 100644 --- a/applications/blink_raw/main.cxx +++ b/applications/blink_raw/main.cxx @@ -41,15 +41,6 @@ #include "os/os.h" #include "utils/blinker.h" -#include "console/Console.hxx" - -#if (defined (TARGET_IS_CC3200) || defined (__linux__) || defined(PART_TM4C1294NCPDT)) && (!defined(NO_CONSOLE)) -#define HAVE_CONSOLE -#endif - -#ifdef HAVE_CONSOLE -Executor<1> executor("executor", 0, 2048); -#endif /** Entry point to application. * @param argc number of command line arguments @@ -59,9 +50,6 @@ Executor<1> executor("executor", 0, 2048); int appl_main(int argc, char *argv[]) { setblink(0); -#ifdef HAVE_CONSOLE - new Console(&executor, Console::FD_STDIN, Console::FD_STDOUT, 2121); -#endif while (1) { resetblink(1); diff --git a/applications/blink_raw/targets/freertos.armv7m.ti-launchpad-tm4c129/Makefile b/applications/blink_raw/targets/freertos.armv7m.ti-launchpad-tm4c129/Makefile index a2248d081..fd1b66ab4 100644 --- a/applications/blink_raw/targets/freertos.armv7m.ti-launchpad-tm4c129/Makefile +++ b/applications/blink_raw/targets/freertos.armv7m.ti-launchpad-tm4c129/Makefile @@ -34,6 +34,13 @@ export BOARD OPENOCDARGS = -f board/ek-tm4c1294xl.cfg +# this works for XDS110 with SWD (2 wire) +#OPENOCDARGS = -c 'set TRANSPORT swd' -f interface/xds110.cfg -c 'transport select swd' -c 'set WORKAREASIZE 0x8000' -c 'set CHIPNAME tm4c1294ncpdt' -f target/stellaris.cfg + +#OPENOCDARGS = -f interface/xds110.cfg -c 'set WORKAREASIZE 0x8000' -c 'set CHIPNAME tm4c1294ncpdt' -f target/stellaris.cfg + +# this works for XDS110 with JTAG (4 wire) +#OPENOCDARGS = -f interface/xds110.cfg -c 'transport select jtag' -c 'set WORKAREASIZE 0x8000' -c 'set CHIPNAME tm4c1294ncpdt' -f target/stellaris.cfg include $(OPENMRNPATH)/etc/prog.mk @@ -41,7 +48,7 @@ ifeq ($(call find_missing_deps,OPENOCDSCRIPTSPATH OPENOCDPATH),) flash: $(EXECUTABLE)$(EXTENTION) $(EXECUTABLE).lst @if ps ax -o comm | grep -q openocd ; then echo openocd already running. quit existing first. ; exit 1 ; fi cp $< last-flashed-$< - $(GDB) $< -ex "target remote | $(OPENOCDPATH)/openocd -c \"gdb_port pipe\" --search $(OPENOCDSCRIPTSPATH) $(OPENOCDARGS)" -ex "monitor reset halt" -ex "load" -ex "monitor reset init" -ex "monitor reset run" -ex "detach" -ex "quit" + $(GDB) $< -ex "target remote | $(OPENOCDPATH)/openocd -c \"gdb_port pipe\" --search $(OPENOCDSCRIPTSPATH) $(OPENOCDARGS)" -ex "monitor reset init" -ex "monitor reset run" -ex "load" -ex "monitor reset init" -ex "monitor reset run" -ex "detach" -ex "quit" gdb: @if ps ax -o comm | grep -q openocd ; then echo openocd already running. quit existing first. ; exit 1 ; fi diff --git a/etc/path.mk b/etc/path.mk index b0a54986c..a8462bd38 100644 --- a/etc/path.mk +++ b/etc/path.mk @@ -553,7 +553,7 @@ SEARCHPATH := \ /usr/share/openocd/scripts \ -TRYPATH:=$(call findfirst,target/stellaris_icdi.cfg,$(SEARCHPATH)) +TRYPATH:=$(call findfirst,target/stm32f0x.cfg,$(SEARCHPATH)) ifneq ($(TRYPATH),) OPENOCDSCRIPTSPATH:=$(TRYPATH) endif