diff --git a/applications/dcc_cs_login/Makefile b/applications/dcc_cs_login/Makefile new file mode 100644 index 000000000..f5cedde67 --- /dev/null +++ b/applications/dcc_cs_login/Makefile @@ -0,0 +1,3 @@ +SUBDIRS = targets +-include config.mk +include $(OPENMRNPATH)/etc/recurse.mk diff --git a/applications/dcc_cs_login/config.mk b/applications/dcc_cs_login/config.mk new file mode 120000 index 000000000..e270c0389 --- /dev/null +++ b/applications/dcc_cs_login/config.mk @@ -0,0 +1 @@ +../default_config.mk \ No newline at end of file diff --git a/applications/dcc_cs_login/subdirs b/applications/dcc_cs_login/subdirs new file mode 100644 index 000000000..4e0254829 --- /dev/null +++ b/applications/dcc_cs_login/subdirs @@ -0,0 +1,2 @@ +SUBDIRS = \ + diff --git a/applications/dcc_cs_login/targets/.gitignore b/applications/dcc_cs_login/targets/.gitignore new file mode 100644 index 000000000..492730923 --- /dev/null +++ b/applications/dcc_cs_login/targets/.gitignore @@ -0,0 +1,2 @@ +compile_cdi + diff --git a/applications/dcc_cs_login/targets/Makefile b/applications/dcc_cs_login/targets/Makefile new file mode 100644 index 000000000..171d1de9e --- /dev/null +++ b/applications/dcc_cs_login/targets/Makefile @@ -0,0 +1,4 @@ +SUBDIRS = \ + freertos.armv7m.ek-tm4c123gxl \ + +include $(OPENMRNPATH)/etc/recurse.mk diff --git a/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/HwInit.cxx b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/HwInit.cxx new file mode 120000 index 000000000..d3fdcc84a --- /dev/null +++ b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/HwInit.cxx @@ -0,0 +1 @@ +../../../../boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx \ No newline at end of file diff --git a/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/Makefile b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/Makefile new file mode 120000 index 000000000..65ed1ddfb --- /dev/null +++ b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/Makefile @@ -0,0 +1 @@ +../../../../boards/ti-ek-tm4c123gxl-launchpad/Makefile \ No newline at end of file diff --git a/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/config.hxx b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/config.hxx new file mode 100644 index 000000000..97b294a3c --- /dev/null +++ b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/config.hxx @@ -0,0 +1,68 @@ +#ifndef _APPLICATIONS_DCC_CS_LOGIN_TARGET_CONFIG_HXX_ +#define _APPLICATIONS_DCC_CS_LOGIN_TARGET_CONFIG_HXX_ + +#include "openlcb/ConfiguredConsumer.hxx" +#include "openlcb/ConfiguredProducer.hxx" +#include "openlcb/ConfigRepresentation.hxx" +#include "openlcb/MemoryConfig.hxx" + +namespace openlcb +{ + +/// Defines the identification information for the node. The arguments are: +/// +/// - 4 (version info, always 4 by the standard +/// - Manufacturer name +/// - Model name +/// - Hardware version +/// - Software version +/// +/// This data will be used for all purposes of the identification: +/// +/// - the generated cdi.xml will include this data +/// - the Simple Node Ident Info Protocol will return this data +/// - the ACDI memory space will contain this data. +extern const SimpleNodeStaticValues SNIP_STATIC_DATA = { + 4, "OpenMRN", "DCC CS with Logon - Tiva Launchpad 123", + "ek-tm4c123gxl", "1.01"}; + +#define NUM_OUTPUTS 3 +#define NUM_INPUTS 2 + +/// Declares a repeated group of a given base group and number of repeats. The +/// ProducerConfig and ConsumerConfig groups represent the configuration layout +/// needed by the ConfiguredProducer and ConfiguredConsumer classes, and come +/// from their respective hxx file. +using AllConsumers = RepeatedGroup; +using AllProducers = RepeatedGroup; + +/// Modify this value every time the EEPROM needs to be cleared on the node +/// after an update. +static constexpr uint16_t CANONICAL_VERSION = 0x1111; + + +/// Defines the main segment in the configuration CDI. This is laid out at +/// origin 128 to give space for the ACDI user data at the beginning. +CDI_GROUP(IoBoardSegment, Segment(MemoryConfigDefs::SPACE_CONFIG), Offset(128)); +/// Each entry declares the name of the current entry, then the type and then +/// optional arguments list. +CDI_GROUP_ENTRY(internal_config, InternalConfigData); +CDI_GROUP_END(); + +/// The main structure of the CDI. ConfigDef is the symbol we use in main.cxx +/// to refer to the configuration defined here. +CDI_GROUP(ConfigDef, MainCdi()); +/// Adds the tag with the values from SNIP_STATIC_DATA above. +CDI_GROUP_ENTRY(ident, Identification); +/// Adds an tag. +CDI_GROUP_ENTRY(acdi, Acdi); +/// Adds a segment for changing the values in the ACDI user-defined +/// space. UserInfoSegment is defined in the system header. +CDI_GROUP_ENTRY(userinfo, UserInfoSegment); +/// Adds the main configuration segment. +CDI_GROUP_ENTRY(seg, IoBoardSegment); +CDI_GROUP_END(); + +} // namespace openlcb + +#endif // _APPLICATIONS_DCC_CS_LOGIN_TARGET_CONFIG_HXX_ diff --git a/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/hardware.hxx b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/hardware.hxx new file mode 120000 index 000000000..f5521469a --- /dev/null +++ b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/hardware.hxx @@ -0,0 +1 @@ +../../../../boards/ti-ek-tm4c123gxl-launchpad/hardware.hxx \ No newline at end of file diff --git a/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/main.cxx b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/main.cxx new file mode 100644 index 000000000..6df018f66 --- /dev/null +++ b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/main.cxx @@ -0,0 +1,197 @@ +/** \copyright + * Copyright (c) 2013, 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 main.cxx + * + * Main file for the DCC CS with Logon application on the Tiva Launchpad board. + * + * @author Balazs Racz + * @date 11 Aug 2021 + */ + +#include "os/os.h" +#include "nmranet_config.h" + +#include "openlcb/SimpleStack.hxx" +#include "openlcb/TractionTrain.hxx" +#include "openlcb/EventHandlerTemplates.hxx" +#include "dcc/Loco.hxx" +#include "dcc/SimpleUpdateLoop.hxx" +#include "dcc/LocalTrackIf.hxx" +#include "dcc/RailcomHub.hxx" +#include "dcc/RailcomPortDebug.hxx" +#include "executor/PoolToQueueFlow.hxx" +#include "openlcb/TractionCvSpace.hxx" + +#include "freertos_drivers/ti/TivaGPIO.hxx" +#include "freertos_drivers/common/BlinkerGPIO.hxx" +#include "freertos_drivers/common/PersistentGPIO.hxx" +#include "config.hxx" +#include "hardware.hxx" + +// These preprocessor symbols are used to select which physical connections +// will be enabled in the main(). See @ref appl_main below. +#define SNIFF_ON_SERIAL +//#define SNIFF_ON_USB +//#define HAVE_PHYSICAL_CAN_PORT + +// Changes the default behavior by adding a newline after each gridconnect +// packet. Makes it easier for debugging the raw device. +OVERRIDE_CONST(gc_generate_newlines, 1); +// Specifies how much RAM (in bytes) we allocate to the stack of the main +// thread. Useful tuning parameter in case the application runs out of memory. +OVERRIDE_CONST(main_thread_stack_size, 2500); + +// Specifies the 48-bit OpenLCB node identifier. This must be unique for every +// hardware manufactured, so in production this should be replaced by some +// easily incrementable method. +extern const openlcb::NodeID NODE_ID = 0x0501010118DAULL; + +// Sets up a comprehensive OpenLCB stack for a single virtual node. This stack +// contains everything needed for a usual peripheral node -- all +// CAN-bus-specific components, a virtual node, PIP, SNIP, Memory configuration +// protocol, ACDI, CDI, a bunch of memory spaces, etc. +openlcb::SimpleCanStack stack(NODE_ID); + +// ConfigDef comes from config.hxx and is specific to the particular device and +// target. It defines the layout of the configuration memory space and is also +// used to generate the cdi.xml file. Here we instantiate the configuration +// layout. The argument of offset zero is ignored and will be removed later. +openlcb::ConfigDef cfg(0); +// Defines weak constants used by the stack to tell it which device contains +// the volatile configuration information. This device name appears in +// HwInit.cxx that creates the device drivers. +extern const char *const openlcb::CONFIG_FILENAME = "/dev/eeprom"; +// The size of the memory space to export over the above device. +extern const size_t openlcb::CONFIG_FILE_SIZE = + cfg.seg().size() + cfg.seg().offset(); +static_assert(openlcb::CONFIG_FILE_SIZE <= 300, "Need to adjust eeprom size"); +// The SNIP user-changeable information in also stored in the above eeprom +// device. In general this could come from different eeprom segments, but it is +// simpler to keep them together. +extern const char *const openlcb::SNIP_DYNAMIC_FILENAME = + openlcb::CONFIG_FILENAME; + +/// This timer checks the eeprom once a second and if the user has written +/// something, executes a reload of the configuration via the OpenLCB config +/// service. +class AutoUpdateTimer : public ::Timer +{ +public: + AutoUpdateTimer() + : ::Timer(stack.executor()->active_timers()) + { + start(SEC_TO_NSEC(1)); + } + + long long timeout() override + { + extern uint8_t eeprom_updated; + if (eeprom_updated) + { + needUpdate_ = true; + eeprom_updated = 0; + } + else + { + if (needUpdate_) + { + stack.config_service()->trigger_update(); + needUpdate_ = false; + } + } + return RESTART; + } + + bool needUpdate_ {false}; +} update_timer; + +// ====== Command Station components ======= +OVERRIDE_CONST(num_memory_spaces, 10); + +dcc::LocalTrackIf track(stack.service(), 2); +dcc::SimpleUpdateLoop updateLoop(stack.service(), &track); +PoolToQueueFlow> pool_translator( + stack.service(), track.pool(), &updateLoop); + +openlcb::TrainService trainService(stack.iface()); + +dcc::Dcc28Train train3Impl(dcc::DccShortAddress(3)); +openlcb::TrainNodeForProxy train3Node(&trainService, &train3Impl); +openlcb::FixedEventProducer + trainEventProducer(&train3Node); + +// ===== RailCom components ====== +dcc::RailcomHubFlow railcom_hub(stack.service()); +openlcb::RailcomToOpenLCBDebugProxy gRailcomProxy( + &railcom_hub, stack.node(), nullptr, false, true); + +openlcb::TractionCvSpace traction_cv(stack.memory_config_handler(), &track, + &railcom_hub, openlcb::MemoryConfigDefs::SPACE_DCC_CV); + +/** Entry point to application. + * @param argc number of command line arguments + * @param argv array of command line arguments + * @return 0, should never return + */ +int appl_main(int argc, char *argv[]) +{ + stack.check_version_and_factory_reset( + cfg.seg().internal_config(), openlcb::CANONICAL_VERSION, false); + + int fd = ::open("/dev/mainline", O_WRONLY); + HASSERT(fd >= 0); + track.set_fd(fd); + + // The necessary physical ports must be added to the stack. + // + // It is okay to enable multiple physical ports, in which case the stack + // will behave as a bridge between them. For example enabling both the + // physical CAN port and the USB port will make this firmware act as an + // USB-CAN adapter in addition to the producers/consumers created above. + // + // If a port is enabled, it must be functional or else the stack will + // freeze waiting for that port to send the packets out. +#if defined(HAVE_PHYSICAL_CAN_PORT) + stack.add_can_port_select("/dev/can0"); +#endif +#if defined(SNIFF_ON_USB) + stack.add_gridconnect_port("/dev/serUSB0"); +#endif +#if defined(SNIFF_ON_SERIAL) + stack.add_gridconnect_port("/dev/ser0"); +#endif + + HubDeviceNonBlock railcom_port(&railcom_hub, + "/dev/railcom"); + + // This command donates the main thread to the operation of the + // stack. Alternatively the stack could be started in a separate stack and + // then application-specific business logic could be executed ion a busy + // loop in the main thread. + stack.loop_executor(); + return 0; +} diff --git a/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/memory_map.ld b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/memory_map.ld new file mode 120000 index 000000000..c4f4a2559 --- /dev/null +++ b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/memory_map.ld @@ -0,0 +1 @@ +../../../../boards/ti-ek-tm4c123gxl-launchpad/memory_map.ld \ No newline at end of file diff --git a/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/startup.c b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/startup.c new file mode 120000 index 000000000..ab6b1925f --- /dev/null +++ b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/startup.c @@ -0,0 +1 @@ +../../../../boards/ti-ek-tm4c123gxl-launchpad/startup.c \ No newline at end of file diff --git a/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/target.ld b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/target.ld new file mode 120000 index 000000000..accf48b3b --- /dev/null +++ b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/target.ld @@ -0,0 +1 @@ +../../../../boards/ti-ek-tm4c123gxl-launchpad/target.ld \ No newline at end of file diff --git a/src/dcc/RailcomPortDebug.hxx b/src/dcc/RailcomPortDebug.hxx index c41d224bd..ef06c2d09 100644 --- a/src/dcc/RailcomPortDebug.hxx +++ b/src/dcc/RailcomPortDebug.hxx @@ -211,11 +211,14 @@ class RailcomToOpenLCBDebugProxy : public dcc::RailcomHubPort { public: RailcomToOpenLCBDebugProxy(dcc::RailcomHubFlow *parent, Node *node, - dcc::RailcomHubPort *occupancy_port) + dcc::RailcomHubPort *occupancy_port, bool ch1_enabled = true, + bool ack_enabled = true) : dcc::RailcomHubPort(parent->service()) , parent_(parent) , node_(node) , occupancyPort_(occupancy_port) + , ch1Enabled_(ch1_enabled) + , ackEnabled_(ack_enabled) { parent_->register_port(this); } @@ -251,7 +254,7 @@ public: { return release_and_exit(); } - if (message()->data()->ch1Size) + if (message()->data()->ch1Size && ch1Enabled_) { return allocate_and_call( node_->iface()->global_message_write_flow(), @@ -281,7 +284,10 @@ public: Action maybe_send_ch2() { - if (message()->data()->ch2Size) + if (message()->data()->ch2Size && + (ackEnabled_ || + dcc::railcom_decode[message()->data()->ch2Data[0]] != + dcc::RailcomDefs::ACK)) { return allocate_and_call( node_->iface()->global_message_write_flow(), @@ -312,6 +318,10 @@ public: dcc::RailcomHubFlow *parent_{nullptr}; Node *node_; dcc::RailcomHubPort *occupancyPort_; + /// True if we should transmit channel1 data. + uint8_t ch1Enabled_ : 1; + /// True if we should transmit data that starts with an ACK. + uint8_t ackEnabled_ : 1; }; } // namespace openlcb diff --git a/src/openlcb/TractionCvSpace.cxx b/src/openlcb/TractionCvSpace.cxx index 53d5c693f..ab85de087 100644 --- a/src/openlcb/TractionCvSpace.cxx +++ b/src/openlcb/TractionCvSpace.cxx @@ -290,8 +290,10 @@ StateFlowBase::Action TractionCvSpace::fill_read1_packet() } b->data()->add_dcc_pom_read1(cvNumber_); b->data()->feedback_key = reinterpret_cast(this); - railcomHub_->register_port(this); + // We proactively repeat read packets twice. + b->data()->packet_header.rept_count = 1; errorCode_ = ERROR_PENDING; + railcomHub_->register_port(this); track_->send(b); return sleep_and_call(&timer_, MSEC_TO_NSEC(500), STATE(read_returned)); } @@ -399,8 +401,8 @@ StateFlowBase::Action TractionCvSpace::fill_write1_packet() // packets by the standard. We make 4 back to back packets and that // fulfills the requirement. b->data()->packet_header.rept_count = 3; - railcomHub_->register_port(this); errorCode_ = ERROR_PENDING; + railcomHub_->register_port(this); track_->send(b); return sleep_and_call(&timer_, MSEC_TO_NSEC(500), STATE(write_returned)); } @@ -483,12 +485,15 @@ void TractionCvSpace::send(Buffer *b, unsigned priority) break; case dcc::RailcomPacket::ACK: if (new_status == ERROR_PENDING) { - new_status = ERROR_OK; + // Ack should not change the state machine of CV reads or + // writes. Both of those need to return explicitly with a + // MOB_POM. } break; case dcc::RailcomPacket::GARBAGE: if (new_status == ERROR_PENDING) { - new_status = ERROR_GARBAGE; + // If we got garbage, we stay in pending state which will + // re-send the command again. } break; case dcc::RailcomPacket::MOB_POM: @@ -496,12 +501,14 @@ void TractionCvSpace::send(Buffer *b, unsigned priority) new_status = ERROR_OK; break; default: - if (new_status == ERROR_PENDING) { - new_status = ERROR_UNKNOWN_RESPONSE; - } break; } } + if (new_status == ERROR_PENDING) + { + // Do not record status if it is still pending. + return; + } return record_railcom_status(new_status); } diff --git a/src/utils/format_utils.hxx b/src/utils/format_utils.hxx index 0cc8985e1..b35c151ca 100644 --- a/src/utils/format_utils.hxx +++ b/src/utils/format_utils.hxx @@ -38,6 +38,8 @@ #include #include +using std::string; + /** Renders an integer to string, left-justified. @param buffer must be an at * @param buffer must be an at least 10 character long array. * @param value will be rendered into the buffer.