From 3e7d0222b7b5eb096d926b08deb4bdf63a4a7b3c Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 7 Jan 2023 15:29:24 +0100 Subject: [PATCH 01/31] Makes the PWM interface compatible with constexpr. (#690) Similar to GPIO classes, we often have many PWM objects allocated for multi-channel output use-cases. By making PWM interface compatible with constexpr, we allow for implementations to build these definitions into read-only memory (flash), reducing the RAM footprint of a multi-channel PWM device. There are specific requirementsfor making a class constexpr in C++11, and the base classes have to comply with them as well. This PR makes the PWM base class comply with these requirements. --- src/freertos_drivers/common/PWM.hxx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/freertos_drivers/common/PWM.hxx b/src/freertos_drivers/common/PWM.hxx index 2a50bb60d..9e073da53 100644 --- a/src/freertos_drivers/common/PWM.hxx +++ b/src/freertos_drivers/common/PWM.hxx @@ -83,15 +83,14 @@ public: protected: /// Constructor. - PWM() - { - } - - /// Destructor. - ~PWM() - { - } - + constexpr PWM() = default; + + /// Destructor. This is protected, because only child classes should be + /// allowed to destruct a PWM; but no implementation exists in order to + /// make the the destructor trivial for C++. A trivial destructor is + /// required for a constexpr class. + ~PWM() = default; + private: DISALLOW_COPY_AND_ASSIGN(PWM); From 87cb5fc29484344eef3ffef39f5f0f63fd5cab5c Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 5 Feb 2023 15:29:16 +0100 Subject: [PATCH 02/31] Fix build of Arduino STM32 CAN driver. (#695) It was broken by #644 which added a new feature for which there is no API on the Arduino. --- src/freertos_drivers/st/Stm32Can.cxx | 2 ++ src/freertos_drivers/st/Stm32Can.hxx | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/freertos_drivers/st/Stm32Can.cxx b/src/freertos_drivers/st/Stm32Can.cxx index 185c1a221..eb20fc2dc 100644 --- a/src/freertos_drivers/st/Stm32Can.cxx +++ b/src/freertos_drivers/st/Stm32Can.cxx @@ -132,6 +132,7 @@ Stm32Can::Stm32Can(const char *name) #endif } +#ifndef ARDUINO // // Stm32Can::ioctl() // @@ -144,6 +145,7 @@ int Stm32Can::ioctl(File *file, unsigned long int key, unsigned long data) } return -EINVAL; } +#endif // !ARDUINO /** Enable use of the device. */ diff --git a/src/freertos_drivers/st/Stm32Can.hxx b/src/freertos_drivers/st/Stm32Can.hxx index 499bbfc2d..fb54c334a 100644 --- a/src/freertos_drivers/st/Stm32Can.hxx +++ b/src/freertos_drivers/st/Stm32Can.hxx @@ -72,13 +72,15 @@ public: static Stm32Can *instances[1]; private: +#ifndef ARDUINO /// Request an ioctl transaction. /// @param file file reference for this device /// @param key ioctl key /// @param data key data /// @return >= 0 upon success, -errno upon failure int ioctl(File *file, unsigned long int key, unsigned long data) override; - +#endif + void enable() override; /**< function to enable device */ void disable() override; /**< function to disable device */ void tx_msg() override; /**< function to try and transmit a message */ From 3a70466dc04a26339088341585e77ceb23f0ab61 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 5 Feb 2023 15:53:44 +0100 Subject: [PATCH 03/31] Fixes build for Stm32 CanSerialNode example. (#696) There was a missing symbol for SNIP_DYNAMIC_FILENAME. --- arduino/examples/Stm32CanSerialNode/Stm32CanSerialNode.ino | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arduino/examples/Stm32CanSerialNode/Stm32CanSerialNode.ino b/arduino/examples/Stm32CanSerialNode/Stm32CanSerialNode.ino index d5ab1b92c..b577294b1 100644 --- a/arduino/examples/Stm32CanSerialNode/Stm32CanSerialNode.ino +++ b/arduino/examples/Stm32CanSerialNode/Stm32CanSerialNode.ino @@ -71,6 +71,9 @@ extern const SimpleNodeStaticValues SNIP_STATIC_DATA = { BOARD_NAME, "1.00" }; + +extern const char* const SNIP_DYNAMIC_FILENAME = nullptr; + } // namespace openlcb /// Arduino setup routine. Initializes the OpenLCB software and connects the From 687007d5a043ea8586342382d4f71b2715d72662 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 6 Mar 2023 14:59:55 +0100 Subject: [PATCH 04/31] Adds a nodejs target for memconfig utils (#697) - Adds js.emscripten traget for memconfig_utils commandline tool. This will allow building a windows binary out of it. - Refactors the main.cxx from linux to a shared main.hxx - Updates from synchronous execution to a state flow. - Adds specific main.cxx for linux and for emscripten. === * Moves main.cxx for memconfig utils to the application directory so that it can be shared between different targets. * Ignores and cleans the release binaries for bootloader client. * Adds memconfig utils target for javascript. * Adds new main files for both linux and js.emscripten. * Updates the forked version of main to be includeable. Replaces the synchronous execution of the application code with an async flow such that we can run it in emscripten as well. * Adds newline to gridconnect output. --- .../targets/js.emscripten/.gitignore | 2 + .../targets/js.emscripten/Makefile | 5 +- applications/memconfig_utils/main.hxx | 316 ++++++++++++++++++ applications/memconfig_utils/targets/Makefile | 2 +- .../targets/js.emscripten/.gitignore | 6 + .../targets/js.emscripten/Makefile | 21 ++ .../targets/js.emscripten/lib/Makefile | 1 + .../targets/js.emscripten/main.cxx | 65 ++++ .../targets/js.emscripten/package.json | 13 + .../targets/linux.x86/main.cxx | 202 +---------- 10 files changed, 434 insertions(+), 199 deletions(-) create mode 100644 applications/memconfig_utils/main.hxx create mode 100644 applications/memconfig_utils/targets/js.emscripten/.gitignore create mode 100644 applications/memconfig_utils/targets/js.emscripten/Makefile create mode 100644 applications/memconfig_utils/targets/js.emscripten/lib/Makefile create mode 100644 applications/memconfig_utils/targets/js.emscripten/main.cxx create mode 100644 applications/memconfig_utils/targets/js.emscripten/package.json diff --git a/applications/bootloader_client/targets/js.emscripten/.gitignore b/applications/bootloader_client/targets/js.emscripten/.gitignore index 97dc46a68..e5244b63b 100644 --- a/applications/bootloader_client/targets/js.emscripten/.gitignore +++ b/applications/bootloader_client/targets/js.emscripten/.gitignore @@ -1,2 +1,4 @@ bootloader_client.js node_modules +# released binary +openmrn-bootloader-client-* diff --git a/applications/bootloader_client/targets/js.emscripten/Makefile b/applications/bootloader_client/targets/js.emscripten/Makefile index 1675ca81a..363292221 100644 --- a/applications/bootloader_client/targets/js.emscripten/Makefile +++ b/applications/bootloader_client/targets/js.emscripten/Makefile @@ -11,8 +11,11 @@ release: pkg -C Brotli . -clean: clean-wasm +clean: clean-wasm clean-bin clean-wasm: rm -f $(EXECUTABLE).{wasm,wast} + +clean-bin: + rm -f openmrn-bootloader-client-{linux,macos,win.exe} diff --git a/applications/memconfig_utils/main.hxx b/applications/memconfig_utils/main.hxx new file mode 100644 index 000000000..d37e5f6e2 --- /dev/null +++ b/applications/memconfig_utils/main.hxx @@ -0,0 +1,316 @@ +/** \copyright + * Copyright (c) 2013 - 2023, 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.hxx + * + * An application for downloading an entire memory space from a node. + * + * @author Balazs Racz + * @date 7 Sep 2017 + */ + +#include +#include +#include +#include + +#include + +#include "os/os.h" +#include "utils/constants.hxx" +#include "utils/Hub.hxx" +#include "utils/GridConnectHub.hxx" +#include "utils/GcTcpHub.hxx" +#include "utils/Crc.hxx" +#include "utils/FileUtils.hxx" +#include "utils/format_utils.hxx" +#include "executor/Executor.hxx" +#include "executor/Service.hxx" + +#include "openlcb/IfCan.hxx" +#include "openlcb/DatagramCan.hxx" +#include "openlcb/BootloaderClient.hxx" +#include "openlcb/If.hxx" +#include "openlcb/AliasAllocator.hxx" +#include "openlcb/DefaultNode.hxx" +#include "openlcb/NodeInitializeFlow.hxx" +#include "openlcb/MemoryConfig.hxx" +#include "openlcb/MemoryConfigClient.hxx" +#include "utils/socket_listener.hxx" + +NO_THREAD nt; +Executor<1> g_executor(nt); +Service g_service(&g_executor); +CanHubFlow can_hub0(&g_service); + +OVERRIDE_CONST(gc_generate_newlines, 1); + +static const openlcb::NodeID NODE_ID = 0x05010101181FULL; + +openlcb::IfCan g_if_can(&g_executor, &can_hub0, 3, 3, 2); +openlcb::InitializeFlow g_init_flow{&g_service}; +openlcb::CanDatagramService g_datagram_can(&g_if_can, 10, 2); +static openlcb::AddAliasAllocator g_alias_allocator(NODE_ID, &g_if_can); +openlcb::DefaultNode g_node(&g_if_can, NODE_ID); +openlcb::MemoryConfigHandler g_memcfg(&g_datagram_can, &g_node, 10); +openlcb::MemoryConfigClient g_memcfg_cli(&g_node, &g_memcfg); + +namespace openlcb +{ +Pool *const g_incoming_datagram_allocator = mainBufferPool; +} + +static int port = 12021; +static const char *host = "localhost"; +static const char *device_path = nullptr; +static const char *filename = nullptr; +static uint64_t destination_nodeid = 0; +static uint64_t destination_alias = 0; +static int memory_space_id = openlcb::MemoryConfigDefs::SPACE_CONFIG; +static uint32_t offset = 0; +static constexpr uint32_t NLEN = (uint32_t)-1; +static uint32_t len = NLEN; +static bool partial_read = false; +static bool do_read = false; +static bool do_write = false; + +void usage(const char *e) +{ + fprintf(stderr, + "Usage: %s ([-i destination_host] [-p port] | [-d serial_port]) [-s " + "memory_space_id] [-o offset] [-l len] [-c csum_algo] (-r|-w) " + "(-n nodeid | -a alias) -f filename\n", + e); + fprintf(stderr, + "Connects to an openlcb bus and performs memory configuration protocol " + "operations on openlcb node with id `nodeid` with the contents of a " + "given file or arguments.\n"); + fprintf(stderr, + "The bus connection will be through an OpenLCB HUB on " + "destination_host:port with OpenLCB over TCP " + "(in GridConnect format) protocol, or through the CAN-USB device " + "(also in GridConnect protocol) found at serial_port. Device takes " + "precedence over TCP host:port specification."); + fprintf(stderr, "\tThe default target is localhost:12021.\n"); + fprintf(stderr, "\tnodeid should be a 12-char hex string with 0x prefix and " + "no separators, like '-n 0x05010101141F'\n"); + fprintf(stderr, "\talias should be a 3-char hex string with 0x prefix and no " + "separators, like '-a 0x3F9'\n"); + fprintf(stderr, + "\tmemory_space_id defines which memory space to use " + "data into. Default is '-s 0x%02x'.\n", + openlcb::MemoryConfigDefs::SPACE_CONFIG); + fprintf(stderr, "\t-r or -w defines whether to read or write.\n"); + fprintf(stderr, + "\tIf offset and len are skipped for a read, then the entire memory " + "space will be downloaded.\n"); +#ifdef __EMSCRIPTEN__ + fprintf(stderr, "\t-D lists available serial ports.\n"); +#endif + exit(1); +} + +void parse_args(int argc, char *argv[]) +{ + int opt; + while ((opt = getopt(argc, argv, "hp:i:d:n:a:s:f:rwo:l:D")) >= 0) + { + switch (opt) + { + case 'h': + usage(argv[0]); + break; + case 'p': + port = atoi(optarg); + break; + case 'i': + host = optarg; + break; + case 'd': + device_path = optarg; + break; + case 'f': + filename = optarg; + break; + case 'n': + destination_nodeid = strtoll(optarg, nullptr, 16); + break; + case 'a': + destination_alias = strtoul(optarg, nullptr, 16); + break; + case 's': + memory_space_id = strtol(optarg, nullptr, 16); + break; + case 'o': + offset = strtol(optarg, nullptr, 10); + break; + case 'l': + len = strtol(optarg, nullptr, 10); + break; + case 'r': + do_read = true; + break; + case 'w': + do_write = true; + break; +#ifdef __EMSCRIPTEN__ + case 'D': + JSSerialPort::list_ports(); + break; +#endif + default: + fprintf(stderr, "Unknown option %c\n", opt); + usage(argv[0]); + } + } + partial_read = do_read && ((offset != 0) || (len != NLEN)); + if ((!filename && !partial_read) || + (!destination_nodeid && !destination_alias)) + { + usage(argv[0]); + } + if ((do_read ? 1 : 0) + (do_write ? 1 : 0) != 1) + { + fprintf(stderr, "Must set exactly one of option -r and option -w.\n\n"); + usage(argv[0]); + } +} + + +class HelperFlow : public StateFlowBase { +public: + HelperFlow() : StateFlowBase(&g_service) { + start_flow(STATE(wait_for_boot)); + } + + Action wait_for_boot() + { + return sleep_and_call(&timer_, MSEC_TO_NSEC(400), STATE(send_request)); + } + + /// Application business logic. + Action send_request() + { + openlcb::NodeHandle dst; + dst.alias = destination_alias; + dst.id = destination_nodeid; + HASSERT((!!do_read) + (!!do_write) == 1); + if (do_write) + { + auto payload = read_file_to_string(filename); + printf("Read %" PRIdPTR + " bytes from file %s. Writing to memory space 0x%02x\n", + payload.size(), filename, memory_space_id); + return invoke_subflow_and_wait(&g_memcfg_cli, STATE(write_done), + openlcb::MemoryConfigClientRequest::WRITE, dst, memory_space_id, + 0, std::move(payload)); + } + + if (do_read && partial_read) + { + printf("Loading from space 0x%02x offset %u length %d\n", + (unsigned)memory_space_id, (unsigned)offset, (int)len); + return invoke_subflow_and_wait(&g_memcfg_cli, STATE(part_read_done), + openlcb::MemoryConfigClientRequest::READ_PART, dst, + memory_space_id, offset, len); + } + else if (do_read) + { + auto cb = [](openlcb::MemoryConfigClientRequest *rq) { + static size_t last_len = rq->payload.size(); + if ((last_len & ~1023) != (rq->payload.size() & ~1023)) + { + printf("Loaded %d bytes\n", (int)rq->payload.size()); + last_len = rq->payload.size(); + } + }; + printf("Loading memory space 0x%02x\n", + (unsigned)memory_space_id); + return invoke_subflow_and_wait(&g_memcfg_cli, STATE(read_done), + openlcb::MemoryConfigClientRequest::READ, dst, memory_space_id, + std::move(cb)); + } + + printf("Nothing to do."); + return call_immediately(STATE(flow_done)); + } + + /// Invoked when a write operation is complete. Prints result and + /// terminates. + Action write_done() { + auto b = get_buffer_deleter(full_allocation_result(&g_memcfg_cli)); + printf("Result: %04x\n", b->data()->resultCode); + hasError_ = b->data()->resultCode != 0; + return call_immediately(STATE(flow_done)); + } + + /// Invoked when a partial read operation is complete. Prints result and + /// terminates. + Action part_read_done() { + auto b = get_buffer_deleter(full_allocation_result(&g_memcfg_cli)); + printf("Result: %04x\n", b->data()->resultCode); + printf("Data: %s\n", string_to_hex(b->data()->payload).c_str()); + hasError_ = b->data()->resultCode != 0; + return call_immediately(STATE(flow_done)); + } + + /// Invoked when a read operation is complete. Prints result and + /// terminates. + Action read_done() { + auto b = get_buffer_deleter(full_allocation_result(&g_memcfg_cli)); + printf("Result: %04x\n", b->data()->resultCode); + write_string_to_file(filename, b->data()->payload); + fprintf(stderr, "Written %" PRIdPTR " bytes to file %s.\n", + b->data()->payload.size(), filename); + hasError_ = b->data()->resultCode != 0; + return call_immediately(STATE(flow_done)); + } + + /// Terminates the process. + Action flow_done() + { + fflush(stdout); +#ifdef __EMSCRIPTEN__ + EM_ASM(process.exit()); +#endif + _exit(hasError_ ? 1 : 0); + + return exit(); + } + + StateFlowTimer timer_{this}; + bool hasError_ = false; +} helper_flow; + + +/// Runs the executor. Never returns. +void execute() { + g_if_can.add_addressed_message_support(); + // Bootstraps the alias allocation process. + g_if_can.alias_allocator()->send(g_if_can.alias_allocator()->alloc()); + + g_executor.thread_body(); +} diff --git a/applications/memconfig_utils/targets/Makefile b/applications/memconfig_utils/targets/Makefile index e09efa82d..4e1071528 100644 --- a/applications/memconfig_utils/targets/Makefile +++ b/applications/memconfig_utils/targets/Makefile @@ -1,5 +1,5 @@ SUBDIRS = linux.x86 \ -# js.emscripten + js.emscripten include $(OPENMRNPATH)/etc/recurse.mk diff --git a/applications/memconfig_utils/targets/js.emscripten/.gitignore b/applications/memconfig_utils/targets/js.emscripten/.gitignore new file mode 100644 index 000000000..eba1e38bc --- /dev/null +++ b/applications/memconfig_utils/targets/js.emscripten/.gitignore @@ -0,0 +1,6 @@ +memconfig_utils.js +node_modules +openmrn-memconfig-utils-linux +openmrn-memconfig-utils-macos +openmrn-memconfig-utils-win.exe + diff --git a/applications/memconfig_utils/targets/js.emscripten/Makefile b/applications/memconfig_utils/targets/js.emscripten/Makefile new file mode 100644 index 000000000..7e9f4547d --- /dev/null +++ b/applications/memconfig_utils/targets/js.emscripten/Makefile @@ -0,0 +1,21 @@ +-include ../../config.mk +include $(OPENMRNPATH)/etc/prog.mk +LDFLAGS += --bind -s WASM=0 + + +# How to prepare for releasing this: +# as administrator do +# npm install -g pkg +# then you can call make release +release: + pkg -C Brotli . + + +clean: clean-wasm clean-bin + + +clean-wasm: + rm -f $(EXECUTABLE).{wasm,wast} + +clean-bin: + rm -f openmrn-memconfig-utils-{linux,macos,win.exe} diff --git a/applications/memconfig_utils/targets/js.emscripten/lib/Makefile b/applications/memconfig_utils/targets/js.emscripten/lib/Makefile new file mode 100644 index 000000000..a414ed98e --- /dev/null +++ b/applications/memconfig_utils/targets/js.emscripten/lib/Makefile @@ -0,0 +1 @@ +include $(OPENMRNPATH)/etc/app_target_lib.mk diff --git a/applications/memconfig_utils/targets/js.emscripten/main.cxx b/applications/memconfig_utils/targets/js.emscripten/main.cxx new file mode 100644 index 000000000..36c4fc48c --- /dev/null +++ b/applications/memconfig_utils/targets/js.emscripten/main.cxx @@ -0,0 +1,65 @@ +/** \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 + * + * An application for updating the firmware of a remote node on the bus. + * + * @author Balazs Racz + * @date 3 Aug 2013 + */ + +#include +#include +#include + +#include "utils/JSSerialPort.hxx" +#include "utils/JSTcpClient.hxx" + +#include "main.hxx" + +/** 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[]) +{ + parse_args(argc, argv); + std::unique_ptr dev; + std::unique_ptr client; + if (device_path) + { + dev.reset(new JSSerialPort(&can_hub0, device_path)); + } + else + { + client.reset(new JSTcpClient(&can_hub0, host, port)); + } + + execute(); + return 0; +} diff --git a/applications/memconfig_utils/targets/js.emscripten/package.json b/applications/memconfig_utils/targets/js.emscripten/package.json new file mode 100644 index 000000000..c1fc258ea --- /dev/null +++ b/applications/memconfig_utils/targets/js.emscripten/package.json @@ -0,0 +1,13 @@ +{ + "name": "openmrn-memconfig-utils", + "version": "1.0.0", + "dependencies": { + "serialport": "^9.2.7" + }, + "bin": "memconfig_utils.js", + "pkg": { + "assets": [ + "./node_modules/@serialport/bindings/build/Release/bindings.node" + ] + } +} diff --git a/applications/memconfig_utils/targets/linux.x86/main.cxx b/applications/memconfig_utils/targets/linux.x86/main.cxx index 746a4deb1..511d02f9c 100644 --- a/applications/memconfig_utils/targets/linux.x86/main.cxx +++ b/applications/memconfig_utils/targets/linux.x86/main.cxx @@ -1,5 +1,5 @@ /** \copyright - * Copyright (c) 2013 - 2017, Balazs Racz + * Copyright (c) 2013 - 2023, Balazs Racz * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,159 +29,10 @@ * An application for downloading an entire memory space from a node. * * @author Balazs Racz - * @date 7 Sep 2017 + * @date 1 Mar 2023 */ -#include -#include -#include -#include - -#include - -#include "os/os.h" -#include "utils/constants.hxx" -#include "utils/Hub.hxx" -#include "utils/GridConnectHub.hxx" -#include "utils/GcTcpHub.hxx" -#include "utils/Crc.hxx" -#include "utils/FileUtils.hxx" -#include "utils/format_utils.hxx" -#include "executor/Executor.hxx" -#include "executor/Service.hxx" - -#include "openlcb/IfCan.hxx" -#include "openlcb/DatagramCan.hxx" -#include "openlcb/BootloaderClient.hxx" -#include "openlcb/If.hxx" -#include "openlcb/AliasAllocator.hxx" -#include "openlcb/DefaultNode.hxx" -#include "openlcb/NodeInitializeFlow.hxx" -#include "openlcb/MemoryConfig.hxx" -#include "openlcb/MemoryConfigClient.hxx" -#include "utils/socket_listener.hxx" - -NO_THREAD nt; -Executor<1> g_executor(nt); -Service g_service(&g_executor); -CanHubFlow can_hub0(&g_service); - -static const openlcb::NodeID NODE_ID = 0x05010101181FULL; - -openlcb::IfCan g_if_can(&g_executor, &can_hub0, 3, 3, 2); -openlcb::InitializeFlow g_init_flow{&g_service}; -openlcb::CanDatagramService g_datagram_can(&g_if_can, 10, 2); -static openlcb::AddAliasAllocator g_alias_allocator(NODE_ID, &g_if_can); -openlcb::DefaultNode g_node(&g_if_can, NODE_ID); -openlcb::MemoryConfigHandler g_memcfg(&g_datagram_can, &g_node, 10); -openlcb::MemoryConfigClient g_memcfg_cli(&g_node, &g_memcfg); - -namespace openlcb -{ -Pool *const g_incoming_datagram_allocator = mainBufferPool; -} - -static int port = 12021; -static const char *host = "localhost"; -static const char *device_path = nullptr; -static const char *filename = nullptr; -static uint64_t destination_nodeid = 0; -static uint64_t destination_alias = 0; -static int memory_space_id = openlcb::MemoryConfigDefs::SPACE_CONFIG; -static uint32_t offset = 0; -static constexpr uint32_t NLEN = (uint32_t)-1; -static uint32_t len = NLEN; -static bool partial_read = false; -static bool do_read = false; -static bool do_write = false; - -void usage(const char *e) -{ - fprintf(stderr, - "Usage: %s ([-i destination_host] [-p port] | [-d device_path]) [-s " - "memory_space_id] [-o offset] [-l len] [-c csum_algo] (-r|-w) " - "(-n nodeid | -a alias) -f filename\n", - e); - fprintf(stderr, "Connects to an openlcb bus and performs the " - "bootloader protocol on openlcb node with id nodeid with " - "the contents of a given file.\n"); - fprintf(stderr, - "The bus connection will be through an OpenLCB HUB on " - "destination_host:port with OpenLCB over TCP " - "(in GridConnect format) protocol, or through the CAN-USB device " - "(also in GridConnect protocol) found at device_path. Device takes " - "precedence over TCP host:port specification."); - fprintf(stderr, "The default target is localhost:12021.\n"); - fprintf(stderr, "nodeid should be a 12-char hex string with 0x prefix and " - "no separators, like '-b 0x05010101141F'\n"); - fprintf(stderr, "alias should be a 3-char hex string with 0x prefix and no " - "separators, like '-a 0x3F9'\n"); - fprintf(stderr, "memory_space_id defines which memory space to use " - "data into. Default is '-s 0xF0'.\n"); - fprintf(stderr, "-r or -w defines whether to read or write.\n"); - exit(1); -} - -void parse_args(int argc, char *argv[]) -{ - int opt; - while ((opt = getopt(argc, argv, "hp:i:d:n:a:s:f:rwo:l:")) >= 0) - { - switch (opt) - { - case 'h': - usage(argv[0]); - break; - case 'p': - port = atoi(optarg); - break; - case 'i': - host = optarg; - break; - case 'd': - device_path = optarg; - break; - case 'f': - filename = optarg; - break; - case 'n': - destination_nodeid = strtoll(optarg, nullptr, 16); - break; - case 'a': - destination_alias = strtoul(optarg, nullptr, 16); - break; - case 's': - memory_space_id = strtol(optarg, nullptr, 16); - break; - case 'o': - offset = strtol(optarg, nullptr, 10); - break; - case 'l': - len = strtol(optarg, nullptr, 10); - break; - case 'r': - do_read = true; - break; - case 'w': - do_write = true; - break; - default: - fprintf(stderr, "Unknown option %c\n", opt); - usage(argv[0]); - } - } - partial_read = do_read && ((offset != 0) || (len != NLEN)); - if ((!filename && !partial_read) || - (!destination_nodeid && !destination_alias)) - { - usage(argv[0]); - } - if ((do_read ? 1 : 0) + (do_write ? 1 : 0) != 1) - { - fprintf(stderr, "Must set exactly one of option -r and option -w.\n\n"); - usage(argv[0]); - } -} +#include "main.hxx" /** Entry point to application. * @param argc number of command line arguments @@ -191,6 +42,7 @@ void parse_args(int argc, char *argv[]) int appl_main(int argc, char *argv[]) { parse_args(argc, argv); + int conn_fd = 0; if (device_path) { @@ -203,50 +55,6 @@ int appl_main(int argc, char *argv[]) HASSERT(conn_fd >= 0); create_gc_port_for_can_hub(&can_hub0, conn_fd); - g_if_can.add_addressed_message_support(); - // Bootstraps the alias allocation process. - g_if_can.alias_allocator()->send(g_if_can.alias_allocator()->alloc()); - - g_executor.start_thread("g_executor", 0, 1024); - usleep(400000); - - openlcb::NodeHandle dst; - dst.alias = destination_alias; - dst.id = destination_nodeid; - HASSERT((!!do_read) + (!!do_write) == 1); - if (do_write) - { - auto payload = read_file_to_string(filename); - printf("Read %" PRIdPTR - " bytes from file %s. Writing to memory space 0x%02x\n", - payload.size(), filename, memory_space_id); - auto b = invoke_flow(&g_memcfg_cli, - openlcb::MemoryConfigClientRequest::WRITE, dst, memory_space_id, 0, - std::move(payload)); - printf("Result: %04x\n", b->data()->resultCode); - } - - if (do_read && partial_read) - { - auto b = invoke_flow(&g_memcfg_cli, openlcb::MemoryConfigClientRequest::READ_PART, dst, memory_space_id, offset, len); - printf("Result: %04x\n", b->data()->resultCode); - printf("Data: %s\n", string_to_hex(b->data()->payload).c_str()); - } - else if (do_read) - { - auto cb = [](openlcb::MemoryConfigClientRequest *rq) { - static size_t last_len = rq->payload.size(); - if ((last_len & ~1023) != (rq->payload.size() & ~1023)) { - printf("Loaded %d bytes\n", (int)rq->payload.size()); - last_len = rq->payload.size(); - } - }; - auto b = invoke_flow(&g_memcfg_cli, openlcb::MemoryConfigClientRequest::READ, dst, memory_space_id, std::move(cb)); - printf("Result: %04x\n", b->data()->resultCode); - write_string_to_file(filename, b->data()->payload); - fprintf(stderr, "Written %" PRIdPTR " bytes to file %s.\n", - b->data()->payload.size(), filename); - } - + execute(); return 0; } From 465b36aba81df8a8f38df5b3765a8f801bd49663 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 6 Mar 2023 15:03:40 +0100 Subject: [PATCH 05/31] Virtual Train application: adds ability to spawn many train nodes. (#698) * Virtual Train application: adds ability to spawn many train nodes. * Fixes bug in variable scopes. --- applications/train/targets/linux.x86/main.cxx | 151 +++++++++++++++--- 1 file changed, 128 insertions(+), 23 deletions(-) diff --git a/applications/train/targets/linux.x86/main.cxx b/applications/train/targets/linux.x86/main.cxx index 60e3b0aba..ddd2d40fe 100644 --- a/applications/train/targets/linux.x86/main.cxx +++ b/applications/train/targets/linux.x86/main.cxx @@ -34,8 +34,8 @@ #define LOGLEVEL INFO -#include #include +#include #include @@ -47,6 +47,7 @@ #include "openlcb/TractionTestTrain.hxx" #include "openlcb/TractionTrain.hxx" #include "os/os.h" +#include "os/sleep.h" #include "utils/constants.hxx" static const openlcb::NodeID NODE_ID = 0x0501010100F5ULL; @@ -55,7 +56,8 @@ openlcb::SimpleCanStack stack(NODE_ID); openlcb::TrainService traction_service(stack.iface()); -const char *const openlcb::SNIP_DYNAMIC_FILENAME = openlcb::MockSNIPUserFile::snip_user_file_path; +const char *const openlcb::SNIP_DYNAMIC_FILENAME = + openlcb::MockSNIPUserFile::snip_user_file_path; const char *const openlcb::CONFIG_FILENAME = openlcb::SNIP_DYNAMIC_FILENAME; int port = 12021; @@ -64,6 +66,8 @@ const char *device_path = nullptr; const char *name = "Deadrail Train"; int address = 1732; OVERRIDE_CONST(num_memory_spaces, 4); +OVERRIDE_CONST(local_nodes_count, 250); +OVERRIDE_CONST(local_alias_cache_size, 251); namespace openlcb { @@ -74,17 +78,17 @@ const SimpleNodeStaticValues SNIP_STATIC_DATA = { void usage(const char *e) { fprintf(stderr, - "Usage: %s ([-i destination_host] [-p port] | [-d device_path]) " - "[-a address] [-n name]\n\n", - e); + "Usage: %s ([-i destination_host] [-p port] | [-d device_path]) " + "[-a address] [-n name]\n\n", + e); + fprintf( + stderr, "Connects to an openlcb bus and exports a virtual train.\n"); fprintf(stderr, - "Connects to an openlcb bus and exports a virtual train.\n"); - fprintf(stderr, - "The bus connection will be through an OpenLCB HUB on " - "destination_host:port with OpenLCB over TCP " - "(in GridConnect format) protocol, or through the CAN-USB device " - "(also in GridConnect protocol) found at device_path. Device takes " - "precedence over TCP host:port specification."); + "The bus connection will be through an OpenLCB HUB on " + "destination_host:port with OpenLCB over TCP " + "(in GridConnect format) protocol, or through the CAN-USB device " + "(also in GridConnect protocol) found at device_path. Device takes " + "precedence over TCP host:port specification."); fprintf(stderr, "The default target is localhost:12021.\n"); fprintf(stderr, "Address is the virtual loco address. Default 1726.\n"); exit(1); @@ -122,8 +126,92 @@ void parse_args(int argc, char *argv[]) } } + + +namespace openlcb +{ + +/// This struct encompasses all objects needed for a virtual train node. +struct TrainInstance : private IncomingMessageStateFlow +{ + /// Constructor + /// + /// @param addr Train (DCC) address. Will be used for generating the node + /// ID. + /// @param name Train Name. Will be used for SNIP. + /// @param desc Train User Description. Will be used for SNIP. + TrainInstance(unsigned addr, const string &name, const string &desc) + : IncomingMessageStateFlow(stack.iface()) + , address_(addr) + , nodeName_(name) + , nodeDescription_(desc) + , responseFlow_(stack.info_flow()) + { + stack.iface()->dispatcher()->register_handler( + this, Defs::MTI_IDENT_INFO_REQUEST, Defs::MTI_EXACT); + } + + /// DCC address + unsigned address_; + /// Text name for the train. Will appear on the throttle. + string nodeName_; + /// Text description of the node. Will be used in the SNIP reply. + string nodeDescription_; + /// Common helper flow for responding to SNIP requests. + SimpleInfoFlow *responseFlow_; + /// This implementtion object is supposed to drive the hardware. + LoggingTrain train_impl {address_}; + /// Virtual Train Node object. + TrainNodeWithId node_ {&traction_service, &train_impl, + TractionDefs::NODE_ID_DCC | 0xFF000000u | address_}; + /// Handles PIP requests for this node. + ProtocolIdentificationHandler pip {&node_, + Defs::EVENT_EXCHANGE | Defs::SIMPLE_NODE_INFORMATION | + Defs::TRACTION_CONTROL}; + /// Ensures that this train responds to the IS_TRAIN event requests. + FixedEventProducer isTrainEventHandler_ { + &node_}; + + /// What should be our response to SNIP requests. + const SimpleInfoDescriptor SNIP_CUSTOM_RESPONSE[9] = { + {SimpleInfoDescriptor::LITERAL_BYTE, 4, 0, nullptr}, + {SimpleInfoDescriptor::C_STRING, 0, 0, + SNIP_STATIC_DATA.manufacturer_name}, + {SimpleInfoDescriptor::C_STRING, 0, 0, SNIP_STATIC_DATA.model_name}, + {SimpleInfoDescriptor::C_STRING, 0, 0, + SNIP_STATIC_DATA.hardware_version}, + {SimpleInfoDescriptor::C_STRING, 0, 0, + SNIP_STATIC_DATA.software_version}, + {SimpleInfoDescriptor::LITERAL_BYTE, 2, 0, nullptr}, + {SimpleInfoDescriptor::C_STRING, 0, 0, nodeName_.c_str()}, + {SimpleInfoDescriptor::C_STRING, 0, 0, nodeDescription_.c_str()}, + {SimpleInfoDescriptor::END_OF_DATA, 0, 0, 0}}; + + /// Custom handler for SNIP reply. + Action entry() OVERRIDE + { + if (!nmsg()->dstNode) + return release_and_exit(); + if (nmsg()->dstNode != &node_) + return release_and_exit(); + auto *b = responseFlow_->alloc(); + b->data()->reset( + nmsg(), SNIP_CUSTOM_RESPONSE, Defs::MTI_IDENT_INFO_REPLY); + responseFlow_->send(b); + return release_and_exit(); + } +}; // struct TrainInstance + +} // namespace openlcb + int appl_main(int argc, char *argv[]) { + if (false) + { + // This will make it appear that the device is slow to respond to + // anything. + stack.iface()->set_tx_hook([]() { microsleep(50000); }); + } parse_args(argc, argv); LOG(INFO, "Train name: %s", name); openlcb::MockSNIPUserFile snip_user_file(name, "Deadrail--description"); @@ -137,16 +225,33 @@ int appl_main(int argc, char *argv[]) stack.connect_tcp_gridconnect_hub(host, port); } - openlcb::LoggingTrain train_impl(address); - openlcb::TrainNodeWithId train_node(&traction_service, &train_impl, openlcb::TractionDefs::NODE_ID_DCC | 0xFF000000u | address); - openlcb::FixedEventProducer - is_train_event_handler(&train_node); - openlcb::ProtocolIdentificationHandler pip( - &train_node, - openlcb::Defs::EVENT_EXCHANGE | openlcb::Defs::SIMPLE_NODE_INFORMATION | - openlcb::Defs::TRACTION_CONTROL); - openlcb::SNIPHandler snip_handler{stack.iface(), nullptr, stack.info_flow()}; - - stack.loop_executor(); + if (false) + { + // This is the simple way to create one train + openlcb::LoggingTrain train_impl(address); + openlcb::TrainNodeWithId train_node(&traction_service, &train_impl, + openlcb::TractionDefs::NODE_ID_DCC | 0xFF000000u | address); + openlcb::FixedEventProducer + is_train_event_handler(&train_node); + openlcb::ProtocolIdentificationHandler pip(&train_node, + openlcb::Defs::EVENT_EXCHANGE | + openlcb::Defs::SIMPLE_NODE_INFORMATION | + openlcb::Defs::TRACTION_CONTROL); + openlcb::SNIPHandler snip_handler { + stack.iface(), &train_node, stack.info_flow()}; + + // Have to loop before the objects above go out of scope. + stack.loop_executor(); + } + else + { + // This is how we can create multiple trains. + openlcb::TrainInstance some_train( + address, name, "Deadrail--description"); + + // Have to loop before the objects above go out of scope. + stack.loop_executor(); + } + return 0; } From 95f15f2e0d87bccd9d9ce5e03d87c819891f1914 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 6 Mar 2023 15:08:04 +0100 Subject: [PATCH 06/31] Moves the interrupt operations to the TivaRailcomDriver from RailcomDriverBase. (#699) This is needed to refactor the base class to be non tivaware-specific. --- src/freertos_drivers/ti/TivaRailcom.hxx | 27 ++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/freertos_drivers/ti/TivaRailcom.hxx b/src/freertos_drivers/ti/TivaRailcom.hxx index ecabfde77..ed26424a7 100644 --- a/src/freertos_drivers/ti/TivaRailcom.hxx +++ b/src/freertos_drivers/ti/TivaRailcom.hxx @@ -138,16 +138,17 @@ public: , readableNotifiable_(nullptr) { HW::hw_init(); - MAP_IntPrioritySet(HW::OS_INTERRUPT, configKERNEL_INTERRUPT_PRIORITY); - MAP_IntEnable(HW::OS_INTERRUPT); } ~RailcomDriverBase() { - MAP_IntDisable(HW::OS_INTERRUPT); } private: + /// Sets a given software interrupt pending. + /// @param int_nr interrupt number (will be HW::OS_INTERRUPT) + virtual void int_set_pending(unsigned int_nr) = 0; + ssize_t write(File *, const void *, size_t) OVERRIDE { // This device is read-only. @@ -271,7 +272,7 @@ protected: uint32_t tick_timer = HW::get_timer_tick(); memcpy(feedbackQueue_.back().ch2Data, &tick_timer, 4); feedbackQueue_.increment_back(); - MAP_IntPendSet(HW::OS_INTERRUPT); + int_set_pending(HW::OS_INTERRUPT); } /** Notify this when we have data in our buffers. */ @@ -294,8 +295,16 @@ template class TivaRailcomDriver : public RailcomDriverBase { public: /// Constructor. @param path is the device node path (e.g. "/dev/railcom0"). - TivaRailcomDriver(const char *path) : RailcomDriverBase(path) + TivaRailcomDriver(const char *path) + : RailcomDriverBase(path) { + MAP_IntPrioritySet(HW::OS_INTERRUPT, configKERNEL_INTERRUPT_PRIORITY); + MAP_IntEnable(HW::OS_INTERRUPT); + } + + ~TivaRailcomDriver() + { + MAP_IntDisable(HW::OS_INTERRUPT); } private: @@ -303,6 +312,14 @@ private: bool inCutout_ = false; using RailcomDriverBase::returnedPackets_; + + /// Sets a given software interrupt pending. + /// @param int_nr interrupt number (will be HW::OS_INTERRUPT) + void int_set_pending(unsigned int_nr) override + { + MAP_IntPendSet(int_nr); + } + // File node interface void enable() OVERRIDE { From bd7fb793c6e56fe39a1008999bf26c1e744ca861 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 6 Mar 2023 06:10:06 -0800 Subject: [PATCH 07/31] Fork src/freertos_drivers/ti/TivaRailcom.hxx to src/freertos_drivers/common/RailcomImpl.hxx --- .../{ti/TivaRailcom.hxx => common/RailcomImpl.hxx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/freertos_drivers/{ti/TivaRailcom.hxx => common/RailcomImpl.hxx} (100%) diff --git a/src/freertos_drivers/ti/TivaRailcom.hxx b/src/freertos_drivers/common/RailcomImpl.hxx similarity index 100% rename from src/freertos_drivers/ti/TivaRailcom.hxx rename to src/freertos_drivers/common/RailcomImpl.hxx From 7c559be0b826c58f097d68660c8f124f7053b3b0 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 6 Mar 2023 06:10:06 -0800 Subject: [PATCH 08/31] Fork src/freertos_drivers/ti/TivaRailcom.hxx to src/freertos_drivers/common/RailcomImpl.hxx step 2 --- src/freertos_drivers/ti/{TivaRailcom.hxx => TivaRailcom.hxx.bak} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/freertos_drivers/ti/{TivaRailcom.hxx => TivaRailcom.hxx.bak} (100%) diff --git a/src/freertos_drivers/ti/TivaRailcom.hxx b/src/freertos_drivers/ti/TivaRailcom.hxx.bak similarity index 100% rename from src/freertos_drivers/ti/TivaRailcom.hxx rename to src/freertos_drivers/ti/TivaRailcom.hxx.bak From 5413dafb841b69d722f1e67dbab5701a91d09328 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 6 Mar 2023 06:10:07 -0800 Subject: [PATCH 09/31] Fork src/freertos_drivers/ti/TivaRailcom.hxx to src/freertos_drivers/common/RailcomImpl.hxx step 4 --- src/freertos_drivers/ti/{TivaRailcom.hxx.bak => TivaRailcom.hxx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/freertos_drivers/ti/{TivaRailcom.hxx.bak => TivaRailcom.hxx} (100%) diff --git a/src/freertos_drivers/ti/TivaRailcom.hxx.bak b/src/freertos_drivers/ti/TivaRailcom.hxx similarity index 100% rename from src/freertos_drivers/ti/TivaRailcom.hxx.bak rename to src/freertos_drivers/ti/TivaRailcom.hxx From e217ff440aa8c75afe9a9787ab6bebbf16de2b19 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 6 Mar 2023 06:15:36 -0800 Subject: [PATCH 10/31] Fork src/freertos_drivers/ti/TivaDCC.hxx to src/freertos_drivers/common/FixedQueue.hxx --- src/freertos_drivers/{ti/TivaDCC.hxx => common/FixedQueue.hxx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/freertos_drivers/{ti/TivaDCC.hxx => common/FixedQueue.hxx} (100%) diff --git a/src/freertos_drivers/ti/TivaDCC.hxx b/src/freertos_drivers/common/FixedQueue.hxx similarity index 100% rename from src/freertos_drivers/ti/TivaDCC.hxx rename to src/freertos_drivers/common/FixedQueue.hxx From 79814933b2a05e64f8dd6468750e512195e150a2 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 6 Mar 2023 06:15:36 -0800 Subject: [PATCH 11/31] Fork src/freertos_drivers/ti/TivaDCC.hxx to src/freertos_drivers/common/FixedQueue.hxx step 2 --- src/freertos_drivers/ti/{TivaDCC.hxx => TivaDCC.hxx.bak} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/freertos_drivers/ti/{TivaDCC.hxx => TivaDCC.hxx.bak} (100%) diff --git a/src/freertos_drivers/ti/TivaDCC.hxx b/src/freertos_drivers/ti/TivaDCC.hxx.bak similarity index 100% rename from src/freertos_drivers/ti/TivaDCC.hxx rename to src/freertos_drivers/ti/TivaDCC.hxx.bak From c4a2f48e8385b84ca48f34547ef12b70f3095565 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 6 Mar 2023 06:15:38 -0800 Subject: [PATCH 12/31] Fork src/freertos_drivers/ti/TivaDCC.hxx to src/freertos_drivers/common/FixedQueue.hxx step 4 --- src/freertos_drivers/ti/{TivaDCC.hxx.bak => TivaDCC.hxx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/freertos_drivers/ti/{TivaDCC.hxx.bak => TivaDCC.hxx} (100%) diff --git a/src/freertos_drivers/ti/TivaDCC.hxx.bak b/src/freertos_drivers/ti/TivaDCC.hxx similarity index 100% rename from src/freertos_drivers/ti/TivaDCC.hxx.bak rename to src/freertos_drivers/ti/TivaDCC.hxx From ce647ab8454a65cc51a7dab5659b6c27a86b6b7c Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 6 Mar 2023 15:23:11 +0100 Subject: [PATCH 13/31] Separate RailcomDriverBase to a different file. (#700) Moves RailcomDriverBase to src/freertos_drivers/common/RailcomImpl.hxx Moves FixedQueue to src/freertos_drivers/common/FixedQueue.hxx no code changes are made. === * Separates the code of TivaRailcom and RailcomBase. * Splits fixedqueue to a separate file. * Removes incorrect guard. * fix typo --- src/freertos_drivers/common/FixedQueue.hxx | 1185 +------------------ src/freertos_drivers/common/RailcomImpl.hxx | 223 +--- src/freertos_drivers/ti/TivaDCC.hxx | 94 +- src/freertos_drivers/ti/TivaRailcom.hxx | 165 +-- 4 files changed, 12 insertions(+), 1655 deletions(-) diff --git a/src/freertos_drivers/common/FixedQueue.hxx b/src/freertos_drivers/common/FixedQueue.hxx index 3166819c5..6cdf0a48c 100644 --- a/src/freertos_drivers/common/FixedQueue.hxx +++ b/src/freertos_drivers/common/FixedQueue.hxx @@ -24,57 +24,22 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * - * \file TivaDCC.hxx - * Device class prototype for DCC driver on TivaWare. + * \file FixedQueue.hxx + * Deprecated queue class for device drivers. * * @author Stuart W. Baker * @date 6 May 2014 */ -#ifndef _FREERTOS_DRIVERS_TI_TIVADCC_HXX_ -#define _FREERTOS_DRIVERS_TI_TIVADCC_HXX_ - -#ifndef gcc -#define gcc -#endif - -#if (!defined(TIVADCC_TIVA)) && (!defined(TIVADCC_CC3200)) -#error must define either TIVADCC_TIVA or TIVADCC_CC3200 -#endif +#ifndef _FREERTOS_DRIVERS_COMMON_FIXEDQUEUE_HXX_ +#define _FREERTOS_DRIVERS_COMMON_FIXEDQUEUE_HXX_ #include #include -#include "driverlib/interrupt.h" -#include "driverlib/rom.h" -#include "driverlib/rom_map.h" -#include "driverlib/timer.h" -#include "driverlib/uart.h" -#include "freertos/can_ioctl.h" -#include "inc/hw_memmap.h" -#include "inc/hw_timer.h" -#include "inc/hw_types.h" -#include "inc/hw_uart.h" - -#ifdef TIVADCC_TIVA -#include "driverlib/sysctl.h" -#include "TivaGPIO.hxx" -#else -#include "driverlib/prcm.h" -#include "driverlib/utils.h" -#endif - #include "Devtab.hxx" -#include "RailcomDriver.hxx" -#include "dcc/DccOutput.hxx" -#include "dcc/Packet.hxx" -#include "dcc/RailCom.hxx" #include "executor/Notifiable.hxx" -/// If non-zero, enables the jitter feature to spread the EMC spectrum of DCC -/// signal -extern "C" uint8_t spreadSpectrum; - /// This structure is safe to use from an interrupt context and a regular /// context at the same time, provided that /// @@ -168,1144 +133,4 @@ private: volatile uint8_t count_; }; -/** A device driver for sending DCC packets. If the packet queue is empty, - * then the device driver automatically sends out idle DCC packets. The - * device driver uses two instances of the 16/32-bit timer pairs. The user - * is responsible for providing interrupt entry point for the interval timer - * and calling the inline method @ref interrupt_handler on behalf of this - * device driver. - * - * Write calls work by sending the packet in the format of dcc::Packet. The - * payload should include the X-OR linkage byte. Only one DCC packet may be - * written per call to the write method. If there is no space currently - * available in the write queue, the write method will return -1 with errno - * set to ENOSPC. - * - * Handling of write throttling: - * - * The driver uses a second interrupt, used as a software interrupt at the - * FreeRTOS priority level, to communicate back information on when the device - * is writable. It is okay to use the interrupt for the second timer for this - * purpose. This interrupt will be enabled in the IOCTL after the - * writenotifiable is set, disabled in the interrupt itself when the - * writenotifiable is cleared. It is set pending at startup and when the dcc - * interrupt frees up a packet from the queue, and it is cleared pending when - * a write realizes there is no space for a buffer. - * - * The user must ensure that the 'interrupt' has a very high priority, whereas - * 'os_interrupt' has a priority that's at or lower than the FreeRTOS kernel. - * - * - * Handling of feedback data: - * - * The driver may generate return data for the application layer in the form - * of dcc::Feedback messages. These will be attributed to the incoming - * packets by an opaque key that the application sets. For each packet that - * has a non-zero feedback a dcc::Feedback message will be sent to the - * application layer, which will be readable using the read method on the fd. - * - * The application can request notification of readable and writable status - * using the regular IOCTL method. - * - * - * EMC spectrum spreading - * - * There is an optional feature that helps with passing EMC certification for - * systems that are built on this driver. The observation is that if the - * output signal has may repeats of a certain period, then in the measured - * spectrum there will be a big spike in energy that might exceed the - * thresholds for compliance. However, by slightly varying the timing of the - * output signal, the energy will be spread across a wider spectrum, thus the - * peak of emission will be smaller. - * - * This feature is enabled by `extern uint8_t spreadSpectrum;`. This can come - * from a constant or configuration dependent variable. If enabled, then the - * timing of DCC zero bits are stretched to be a random value between 100.5 - * and 105 usec each half; the timing of DCC one bits will be stretched from - * 56.5 to 60 usec per half. The symmetry within each bit is still perfectly - * matched. Marklin-Motorola packets get up to 2 usec of stretching on each - * phase. - * - * The actual stretching is generated using a uniform random number generator - * within said limits to ensure we spread uniformly across the available - * timings. Up to four bits are output with the same timing, then a new random - * timing is generated. - */ -template -class TivaDCC : public Node -{ -public: - /** Constructor. - * @param name name of this device instance in the file system - * @param railcom is the associated railcom driver, which will get the - * callbacks from the timing derived by the internal signal generator. - */ - TivaDCC(const char *name, RailcomDriver *railcom); - - /** Destructor. - */ - ~TivaDCC() - { - } - - /** Handle an interrupt. - */ - inline void interrupt_handler() __attribute__((always_inline)); - - /** Handles a software interrupt to FreeRTOS. This should be called on the - * interrupt number that is submitted as os_interrupt to the - * constructor. */ - inline void os_interrupt_handler() __attribute__((always_inline)); - - /** Structure for supporting bit timing. */ - struct Timing { - /// In clock cycles: period of the interval timer - uint32_t interval_period; - /// In clock cycles: period of the PWM timer - uint32_t period; - /// When to transition output A; must be within the period - uint32_t transition_a; - /// When to transition output B; must be within the period - uint32_t transition_b; - /// How many ticks (minimum) we can add to the period and transition for - /// spectrum spreading. - uint16_t spread_min = 0; - /// How many ticks (maximum) we can add to the period and transition for - /// spectrum spreading. - uint16_t spread_max = 0; - }; - - /* WARNING: these functions (hw_init, enable_output, disable_output) MUST - * be static, because they will be called from hw_preinit, which happens - * before the C++ constructors have run. This means that at the time of - * calling these functions the state of the object would be undefined / - * uninintialized. The only safe solution is to make them static. */ - /// Initializes the DCC output hardware. - static void hw_preinit() - { -#ifdef TIVADCC_TIVA - MAP_SysCtlPeripheralEnable(HW::CCP_PERIPH); - MAP_SysCtlPeripheralEnable(HW::INTERVAL_PERIPH); - MAP_SysCtlPeripheralEnable(HW::RAILCOM_UART_PERIPH); - HW::PIN_H::hw_init(); - HW::PIN_L::hw_init(); - HW::RAILCOM_TRIGGER_Pin::hw_init(); - HW::RAILCOM_UARTPIN::hw_init(); -#else - MAP_PRCMPeripheralClkEnable(HW::CCP_PERIPH, PRCM_RUN_MODE_CLK); - MAP_PRCMPeripheralClkEnable(HW::INTERVAL_PERIPH, PRCM_RUN_MODE_CLK); - MAP_PRCMPeripheralClkEnable(HW::RAILCOM_UART_PERIPH, PRCM_RUN_MODE_CLK); -#endif - HW::Output1::hw_preinit(); - HW::Output2::hw_preinit(); - HW::Output3::hw_preinit(); - MAP_UARTConfigSetExpClk( - HW::RAILCOM_UART_BASE, configCPU_CLOCK_HZ, 250000, - UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE); - MAP_UARTFIFOEnable(HW::RAILCOM_UART_BASE); - // Disables the uart receive until the railcom cutout is here. - HWREG(HW::RAILCOM_UART_BASE + UART_O_CTL) &= ~UART_CTL_RXE; - } - -private: - /** Read from a file or device. - * @param file file reference for this device - * @param buf location to place read data - * @param count number of bytes to read - * @return number of bytes read upon success, -1 upon failure with errno containing the cause - */ - ssize_t read(File *file, void *buf, size_t count) OVERRIDE; - - /** Write to a file or device. - * @param file file reference for this device - * @param buf location to find write data - * @param count number of bytes to write - * @return number of bytes written upon success, -1 upon failure with errno containing the cause - */ - ssize_t write(File *file, const void *buf, size_t count) OVERRIDE; - - /** Request an ioctl transaction - * @param file file reference for this device - * @param key ioctl key - * @param data key data - */ - int ioctl(File *file, unsigned long int key, unsigned long data) OVERRIDE; - - void enable() OVERRIDE {} /**< function to enable device */ - void disable() OVERRIDE {} /**< function to disable device */ - - /** Discards all pending buffers. Called after disable(). - */ - void flush_buffers() override {}; - - /** maximum packet size we can support */ - static const size_t MAX_PKT_SIZE = 6; - - - /** idle packet */ - static dcc::Packet IDLE_PKT; - - /// Bit timings that we store and precalculate. - typedef enum - { - /// Zero bit for DCC (this is the longer) - DCC_ZERO, - /// One bit for DCC (this is the shorter) - DCC_ONE, - /// One bit for DCC that we generate at the end of packet - /// transition. This has a stretched negative part so that the next - /// packet resync avoids a glitch in the output. - DCC_EOP_ONE, - /// One bit for DCC that we generate during the railcom cutout. Can be - /// used to play with alignment of edges coming out of the railcom - /// cutout. - DCC_RC_ONE, - /// Half zero bit which is sent directly after the railcom cutout is - /// over. Needed to reset certain old decoders packet - /// recognizer. Recommended by - /// https://nmra.org/sites/default/files/standards/sandrp/pdf/tn-2-05draft2005-02-25_for_rp-9.2.3.pdf - DCC_RC_HALF_ZERO, - /// This is not a bit, but specifies when to wake up during the railcom - /// cutout. The time is T_CS, usually 26 usec. - RAILCOM_CUTOUT_PRE, - /// This is not a bit, but specifies when to wake up during the railcom - /// cutout. The time is the time elapsed between T_CS and the middle of - /// the two windows. - RAILCOM_CUTOUT_FIRST, - /// This is not a bit, but specifies when to wake up during the railcom - /// cutout. The time is the time elapsed between the end of the cutout - /// and the the middle of the two windows. - RAILCOM_CUTOUT_SECOND, - /// This is not a bit, but specifies when to wake up during the railcom - /// cutout. This is used for re-synchronizing the - RAILCOM_CUTOUT_POST, - /// Long negative DC pulse to act as a preamble for a Marklin packet. - MM_PREAMBLE, - /// Zero bit for MM packet, which is a short pulse in one direction, - /// then a long pulse in the other. - MM_ZERO, - /// One bit for MM packet, which is a long pulse in one direction, then - /// a short pulse in the other. - MM_ONE, - - NUM_TIMINGS - } BitEnum; - - int hDeadbandDelay_; /**< low->high deadband delay in clock count */ - int lDeadbandDelay_; /**< high->low deadband delay in clock count */ - int usecDelay_; /**< 1 usec of delay in clock count */ - - /// Precalculated bit timings (translated to clock cycles). - Timing timings[NUM_TIMINGS]; - - /// Internal state machine states. - enum State - { - // DCC preamble bits - PREAMBLE, - // Packet start bit - START, - // Data bits for DCC - DATA_0, - DATA_1, - DATA_2, - DATA_3, - DATA_4, - DATA_5, - DATA_6, - DATA_7, - // end-of-byte bit for DCC (either middle-of-packet or end-of-packet) - FRAME, - // Marklin preamble "bit" (constant negative voltage) - ST_MM_PREAMBLE, - // MM data bits. The bit number here is an offset in the current input - // byte, because MM packets contain 18 consecutive bits. - MM_DATA_0, - MM_DATA_1, - MM_DATA_2, - MM_DATA_3, - MM_DATA_4, - MM_DATA_5, - MM_DATA_6, - MM_DATA_7, - // Internal state used for end-of-packet resynchronization of timers. - RESYNC, - // State at the end of packet where we make a decision whether to go - // into a railcom cutout or not. - DCC_MAYBE_RAILCOM, - // If we did not generate a cutout, we generate 5 empty one bits here - // in case a booster wants to insert a cutout. - DCC_NO_CUTOUT, - // Counts 26 usec into the appropriate preamble bit after which to turn - // off output power. - DCC_CUTOUT_PRE, - // Start of railcom. Turns off output power and enables UART. - DCC_START_RAILCOM_RECEIVE, - // Point between railcom window 1 and railcom window 2 where we have to - // read whatever arrived for channel1. - DCC_MIDDLE_RAILCOM_CUTOUT, - // Railcom end-of-channel2 window. Reads out the UART values, and - // enables output power. - DCC_STOP_RAILCOM_RECEIVE, - // Same for marklin. A bit of negative voltage after the packet is over - // but before loading the next packet. This ensures that old marklin - // decoders confirm receiving the packet correctly before we go into a - // potential protocol switch from the next packet loaded. - MM_LEADOUT, - - // Turn on without going through the slow start sequence. - POWER_IMM_TURNON, - }; - /// Current state of internal state machine. - State state_; - - /** Prepares a timing entry. - * - * @param ofs is the bit timing that we are defining. - * @param period_usec is the total length of the bit. - * @param transition_usec is the time of the transition inside the bit, - * counted from the beginning of the bit (i.e. the length of the HIGH part - * of the period). Can be zero for DC output LOW or can be == period_usec - * for DC output HIGH. - * @param interval_period_usec tells when the interval timer should expire - * (next interrupt). Most of the time this should be the same as - * period_usec. - * @param timing_spread_usec if non-zero, allows the high and low of the - * timing to be stretched by at most this many usec. - */ - void fill_timing(BitEnum ofs, uint32_t period_usec, - uint32_t transition_usec, uint32_t interval_period_usec, - uint32_t timing_spread_usec = 0); - - /// Checks each output and enables those that need to be on. - void check_and_enable_outputs() - { - if (HW::Output1::should_be_enabled()) - { - HW::Output1::enable_output(); - } - if (HW::Output2::should_be_enabled()) - { - HW::Output2::enable_output(); - } - if (HW::Output3::should_be_enabled()) - { - HW::Output3::enable_output(); - } - } - -#ifdef TIVADCC_CC3200 - // This function is called differently in tivaware than CC3200. - void MAP_SysCtlDelay(unsigned ticks3) { - ROM_UtilsDelay(ticks3); - } -#endif - - /// Standard timing value of when the railcom cutout should start, measured - /// from the transition of the end-of-packet-one-bit. Minimum 26, max 32 - /// usec. Can be adjusted by HW. - static constexpr unsigned RAILCOM_CUTOUT_START_USEC = 26; - /// Standard timing value of the railcom cutout middle, measured from the - /// transition of the end-of-packet-one-bit. Minimum 177 (end of channel - /// one), maximum 193 (start channel 2) usec. Can be adjusted by HW. - static constexpr unsigned RAILCOM_CUTOUT_MID_USEC = 185; - /// Standard timing value of the railcom cutout end, measured from the - /// transition of the end-of-packet-one-bit. Minimum 454 (end of channel - /// one), maximum 488. Can be adjusted by HW. - static constexpr unsigned RAILCOM_CUTOUT_END_USEC = 486; - - /// Packets still waiting to be sent. - FixedQueue packetQueue_; - Notifiable* writableNotifiable_; /**< Notify this when we have free buffers. */ - RailcomDriver* railcomDriver_; /**< Will be notified for railcom cutout events. */ - /// Seed for a pseudorandom sequence. - unsigned seed_ = 0xb7a11bae; - - /// Parameters for a linear RNG: modulus - static constexpr unsigned PMOD = 65213; - /// Parameters for a linear RNG: multiplier - static constexpr unsigned PMUL = 52253; - /// Parameters for a linear RNG: additive - static constexpr unsigned PADD = 42767; - /** Default constructor. - */ - TivaDCC(); - - DISALLOW_COPY_AND_ASSIGN(TivaDCC); -}; - - -/** Handle an interrupt. - */ -template -__attribute__((optimize("-O3"))) -inline void TivaDCC::interrupt_handler() -{ - static int preamble_count = 0; - static BitEnum last_bit = DCC_ONE; - static int count = 0; - static int packet_repeat_count = 0; - static int bit_repeat_count = 0; - static const dcc::Packet *packet = &IDLE_PKT; - static bool resync = true; - BitEnum current_bit; - bool get_next_packet = false; - - MAP_TimerIntClear(HW::INTERVAL_BASE, TIMER_TIMA_TIMEOUT); - - switch (state_) - { - default: - case RESYNC: - if (packet->packet_header.is_marklin) - { - current_bit = MM_PREAMBLE; - state_ = ST_MM_PREAMBLE; - break; - } - else - { - state_ = PREAMBLE; - } - // fall through - case PREAMBLE: - { - current_bit = DCC_ONE; - // preamble zero is output twice due to the resync, so we deduct - // one from the count. - int preamble_needed = HW::dcc_preamble_count() - 1; - if (HW::generate_railcom_halfzero() && - !packet->packet_header.send_long_preamble) - { - if (preamble_count == 0) - { - current_bit = DCC_RC_HALF_ZERO; - } - preamble_needed++; - } - if (packet->packet_header.send_long_preamble) - { - preamble_needed = 21; - } - if (++preamble_count >= preamble_needed) - { - state_ = START; - preamble_count = 0; - } - break; - } - case START: - current_bit = DCC_ZERO; - count = 0; - state_ = DATA_0; - break; - case DATA_0: - case DATA_1: - case DATA_2: - case DATA_3: - case DATA_4: - case DATA_5: - case DATA_6: - case DATA_7: - { - uint8_t bit = (packet->payload[count] >> (DATA_7 - state_)) & 0x01; - current_bit = static_cast(DCC_ZERO + bit); - state_ = static_cast(static_cast(state_) + 1); - break; - } - case FRAME: - if (++count >= packet->dlc) - { - current_bit = DCC_RC_ONE; // end-of-packet bit - state_ = DCC_MAYBE_RAILCOM; - preamble_count = 0; - } - else - { - current_bit = DCC_ZERO; // end-of-byte bit - state_ = DATA_0; - } - break; - case DCC_MAYBE_RAILCOM: - if ((packet->packet_header.send_long_preamble == 0) && - (HW::Output1::need_railcom_cutout() || - HW::Output2::need_railcom_cutout() || - HW::Output3::need_railcom_cutout())) - { - current_bit = DCC_RC_ONE; - // It takes about 5 usec to get here from the previous - // transition of the output. - // We change the time of the next IRQ. - MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, - timings[RAILCOM_CUTOUT_PRE].interval_period); - state_ = DCC_CUTOUT_PRE; - } - else - { - railcomDriver_->no_cutout(); - current_bit = DCC_ONE; - state_ = DCC_NO_CUTOUT; - } - break; - case DCC_NO_CUTOUT: - current_bit = DCC_ONE; - ++preamble_count; - // maybe railcom already sent one extra ONE bit after the - // end-of-packet one bit. We need four more. - if (preamble_count >= 4) - { - current_bit = DCC_EOP_ONE; - } - if (preamble_count >= 5) - { - // The last bit will be removed by the next packet's beginning - // sync. - get_next_packet = true; - } - break; - case DCC_CUTOUT_PRE: - current_bit = DCC_RC_ONE; - // It takes about 3.6 usec to get here from the transition seen on - // the output. - // We change the time of the next IRQ. - MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, - timings[RAILCOM_CUTOUT_FIRST].interval_period); - state_ = DCC_START_RAILCOM_RECEIVE; - break; - case DCC_START_RAILCOM_RECEIVE: - { - bool rc1 = HW::Output1::need_railcom_cutout(); - bool rc2 = HW::Output2::need_railcom_cutout(); - bool rc3 = HW::Output3::need_railcom_cutout(); - unsigned delay = 0; - // Phase 1 - if (rc1) - { - delay = - std::max(delay, HW::Output1::start_railcom_cutout_phase1()); - HW::Output1::isRailcomCutoutActive_ = 1; - } - if (rc2) - { - delay = - std::max(delay, HW::Output2::start_railcom_cutout_phase1()); - HW::Output2::isRailcomCutoutActive_ = 1; - } - if (rc3) - { - delay = - std::max(delay, HW::Output3::start_railcom_cutout_phase1()); - HW::Output3::isRailcomCutoutActive_ = 1; - } - // Delay - if (delay) - { - MAP_SysCtlDelay(usecDelay_ * delay); - } - delay = 0; - // Phase 2 - if (rc1) - { - delay = - std::max(delay, HW::Output1::start_railcom_cutout_phase2()); - } - if (rc2) - { - delay = - std::max(delay, HW::Output2::start_railcom_cutout_phase2()); - } - if (rc3) - { - delay = - std::max(delay, HW::Output3::start_railcom_cutout_phase2()); - } - // Delay - if (delay) - { - MAP_SysCtlDelay(usecDelay_ * delay); - } - // Enables UART RX. - railcomDriver_->start_cutout(); - // Set up for next wakeup. - current_bit = DCC_RC_ONE; - MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, - timings[RAILCOM_CUTOUT_SECOND].interval_period); - state_ = DCC_MIDDLE_RAILCOM_CUTOUT; - break; - } - case DCC_MIDDLE_RAILCOM_CUTOUT: - railcomDriver_->middle_cutout(); - current_bit = DCC_RC_ONE; - MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, - timings[RAILCOM_CUTOUT_POST].interval_period); - state_ = DCC_STOP_RAILCOM_RECEIVE; - break; - case DCC_STOP_RAILCOM_RECEIVE: - { - current_bit = RAILCOM_CUTOUT_POST; - // This causes the timers to be reinitialized so no fractional bits - // are left in their counters. - resync = true; - get_next_packet = true; - railcomDriver_->end_cutout(); - unsigned delay = 0; - if (HW::Output1::isRailcomCutoutActive_) - { - delay = - std::max(delay, HW::Output1::stop_railcom_cutout_phase1()); - } - if (HW::Output2::isRailcomCutoutActive_) - { - delay = - std::max(delay, HW::Output2::stop_railcom_cutout_phase1()); - } - if (HW::Output3::isRailcomCutoutActive_) - { - delay = - std::max(delay, HW::Output3::stop_railcom_cutout_phase1()); - } - // Delay - if (delay) - { - MAP_SysCtlDelay(usecDelay_ * delay); - } - if (HW::Output1::isRailcomCutoutActive_) - { - HW::Output1::stop_railcom_cutout_phase2(); - } - HW::Output1::isRailcomCutoutActive_ = 0; - if (HW::Output2::isRailcomCutoutActive_) - { - HW::Output2::stop_railcom_cutout_phase2(); - } - HW::Output2::isRailcomCutoutActive_ = 0; - if (HW::Output3::isRailcomCutoutActive_) - { - HW::Output3::stop_railcom_cutout_phase2(); - } - HW::Output3::isRailcomCutoutActive_ = 0; - check_and_enable_outputs(); - break; - } - case ST_MM_PREAMBLE: - current_bit = MM_PREAMBLE; - ++preamble_count; - if (preamble_count == 7 || - preamble_count == 7 + 6 || - preamble_count == 7 + 6 + 10 || - preamble_count == 7 + 6 + 10 + 6) - { - // first byte contains two bits. - state_ = MM_DATA_6; - } - break; - case MM_LEADOUT: - // MM packets never have a cutout. - railcomDriver_->no_cutout(); - current_bit = MM_PREAMBLE; - if (++preamble_count >= 2) { - get_next_packet = true; - } - break; - case MM_DATA_0: - case MM_DATA_1: - case MM_DATA_2: - case MM_DATA_3: - case MM_DATA_4: - case MM_DATA_5: - case MM_DATA_6: - { - uint8_t bit = - (packet->payload[count] >> (MM_DATA_7 - state_)) & 0x01; - current_bit = static_cast(MM_ZERO + bit); - state_ = static_cast(static_cast(state_) + 1); - break; - } - case MM_DATA_7: - { - uint8_t bit = - (packet->payload[count] >> (MM_DATA_7 - state_)) & 0x01; - current_bit = static_cast(MM_ZERO + bit); - ++count; - if (count == 3) { - state_ = ST_MM_PREAMBLE; - } else if (count >= packet->dlc) { - preamble_count = 0; - state_ = MM_LEADOUT; - } else { - state_ = MM_DATA_0; - } - break; - } - case POWER_IMM_TURNON: - current_bit = DCC_ONE; - packet_repeat_count = 0; - get_next_packet = true; - resync = true; - break; - } - - if (resync) { - resync = false; - TDebug::Resync::toggle(); - auto* timing = &timings[current_bit]; - // We are syncing -- cause timers to restart counting when these - // execute. - HWREG(HW::CCP_BASE + TIMER_O_TAMR) &= - ~(TIMER_TAMR_TAMRSU | TIMER_TAMR_TAILD); - HWREG(HW::CCP_BASE + TIMER_O_TBMR) &= - ~(TIMER_TBMR_TBMRSU | TIMER_TBMR_TBILD); - HWREG(HW::INTERVAL_BASE + TIMER_O_TAMR) &= - ~(TIMER_TAMR_TAMRSU | TIMER_TAMR_TAILD); - - // These have to happen very fast because syncing depends on it. We do - // direct register writes here instead of using the plib calls. - HWREG(HW::INTERVAL_BASE + TIMER_O_TAILR) = - timing->interval_period + hDeadbandDelay_ * 2; - - if (!HW::H_DEADBAND_DELAY_NSEC) - { - TDebug::Resync::toggle(); - MAP_TimerDisable(HW::CCP_BASE, TIMER_A|TIMER_B); - // Sets final values for the cycle. - MAP_TimerLoadSet(HW::CCP_BASE, TIMER_A|TIMER_B, timing->period); - MAP_TimerMatchSet(HW::CCP_BASE, TIMER_A, timing->transition_a); - MAP_TimerMatchSet(HW::CCP_BASE, TIMER_B, timing->transition_b); - MAP_TimerEnable(HW::CCP_BASE, TIMER_A | TIMER_B); - - MAP_TimerDisable(HW::INTERVAL_BASE, TIMER_A); - MAP_TimerLoadSet( - HW::INTERVAL_BASE, TIMER_A, timing->interval_period); - MAP_TimerEnable(HW::INTERVAL_BASE, TIMER_A); - TDebug::Resync::toggle(); - - // Switches back to asynch timer update. - HWREG(HW::CCP_BASE + TIMER_O_TAMR) |= - (TIMER_TAMR_TAMRSU | TIMER_TAMR_TAILD); - HWREG(HW::CCP_BASE + TIMER_O_TBMR) |= - (TIMER_TBMR_TBMRSU | TIMER_TBMR_TBILD); - HWREG(HW::INTERVAL_BASE + TIMER_O_TAMR) |= - (TIMER_TAMR_TAMRSU | TIMER_TAMR_TAILD); - } - else - { - - HWREG(HW::CCP_BASE + TIMER_O_TBMATCHR) = - timing->transition_b; // final - // since timer A starts later, the deadband delay cycle we set to be - // constant off (match == period) - HWREG(HW::CCP_BASE + TIMER_O_TAMATCHR) = hDeadbandDelay_; // tmp - HWREG(HW::CCP_BASE + TIMER_O_TBILR) = timing->period; // final - HWREG(HW::CCP_BASE + TIMER_O_TAILR) = hDeadbandDelay_; // tmp - -// timer synchronize (if it works...) -#ifdef TIVADCC_TIVA - HWREG(TIMER0_BASE + TIMER_O_SYNC) = HW::TIMER_SYNC; -#endif - - // Switches back to asynch timer update. - HWREG(HW::CCP_BASE + TIMER_O_TAMR) |= - (TIMER_TAMR_TAMRSU | TIMER_TAMR_TAILD); - HWREG(HW::CCP_BASE + TIMER_O_TBMR) |= - (TIMER_TBMR_TBMRSU | TIMER_TBMR_TBILD); - HWREG(HW::INTERVAL_BASE + TIMER_O_TAMR) |= - (TIMER_TAMR_TAMRSU | TIMER_TAMR_TAILD); - - // Sets final values for the cycle. - MAP_TimerLoadSet(HW::CCP_BASE, TIMER_A, timing->period); - MAP_TimerMatchSet(HW::CCP_BASE, TIMER_A, timing->transition_a); - MAP_TimerLoadSet( - HW::INTERVAL_BASE, TIMER_A, timing->interval_period); - } - - last_bit = current_bit; - bit_repeat_count = 0; - if (current_bit == RAILCOM_CUTOUT_POST) - { - // RAILCOM_CUTOUT_POST purposefully misaligns the two timers. We - // need to resync when the next interval timer ticks to get them - // back. - resync = true; - } - else if (current_bit == DCC_RC_HALF_ZERO) - { - // After resync the same bit is output twice. We don't want that - // with the half-zero, so we preload the DCC preamble bit. - current_bit = DCC_ONE; - } - } - if (bit_repeat_count >= 4) - { - // Forces reinstalling the timing. - last_bit = NUM_TIMINGS; - } - if (last_bit != current_bit) - { - auto* timing = &timings[current_bit]; - // The delta in ticks we add to each side of the signal. - uint32_t spread = 0; - if (spreadSpectrum) - { - spread = timing->spread_max - timing->spread_min; - seed_ *= PMUL; - seed_ += PADD; - seed_ %= PMOD; - spread = (seed_ % spread) + timing->spread_min; - } - MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, - timing->interval_period + (spread << 1)); - MAP_TimerLoadSet(HW::CCP_BASE, TIMER_A, timing->period + (spread << 1)); - MAP_TimerLoadSet(HW::CCP_BASE, TIMER_B, timing->period + (spread << 1)); - MAP_TimerMatchSet(HW::CCP_BASE, TIMER_A, timing->transition_a + spread); - MAP_TimerMatchSet(HW::CCP_BASE, TIMER_B, timing->transition_b + spread); - last_bit = current_bit; - bit_repeat_count = 0; - } - else - { - bit_repeat_count++; - } - - if (get_next_packet) - { - check_and_enable_outputs(); - TDebug::NextPacket::toggle(); - if (packet_repeat_count) { - --packet_repeat_count; - } - if (packet != &IDLE_PKT && packet_repeat_count == 0) - { - packetQueue_.increment_front(); - // Notifies the OS that we can write to the buffer. - MAP_IntPendSet(HW::OS_INTERRUPT); - resync = true; - } else { - } - if (!packetQueue_.empty()) - { - packet = &packetQueue_.front(); - railcomDriver_->set_feedback_key(packet->feedback_key); - } - else - { - packet = &IDLE_PKT; - resync = true; - } - preamble_count = 0; - count = 0; - if (!packet_repeat_count) { - packet_repeat_count = packet->packet_header.rept_count + 1; - } - // If we are in the repeat loop for a marklin packet, we do not do a - // resync to not disturb the marklin preamble (which is DC_negative). - if (resync || !packet->packet_header.is_marklin) { - state_ = RESYNC; - } else { - state_ = ST_MM_PREAMBLE; - } - } -} - -/// Converts a time length given in microseconds to the number of clock cycles. -/// @param usec is time given in microseconds. -/// @return time given in clock cycles. -static const uint32_t usec_to_clocks(uint32_t usec) { - return (configCPU_CLOCK_HZ / 1000000) * usec; -} - -/// Converts a time length given in nanoseconds to the number of clock cycles. -/// @param nsec is time given in nanoseconds. -/// @return time given in clock cycles. -static uint32_t nsec_to_clocks(uint32_t nsec) { - // We have to be careful here not to underflow or overflow. - return ((configCPU_CLOCK_HZ / 1000000) * nsec) / 1000; -} - -template -void TivaDCC::fill_timing(BitEnum ofs, uint32_t period_usec, - uint32_t transition_usec, uint32_t interval_usec, uint32_t spread_max) -{ - auto* timing = &timings[ofs]; - timing->period = usec_to_clocks(period_usec); - timing->interval_period = usec_to_clocks(interval_usec); - if (transition_usec == 0) { - // DC voltage negative. - timing->transition_a = timing->transition_b = timing->period; - } else if (transition_usec >= period_usec) { - // DC voltage positive. - // We use the PLO feature of the timer. - timing->transition_a = timing->transition_b = timing->period + 1; - } else { - int32_t nominal_transition = - timing->period - usec_to_clocks(transition_usec); - timing->transition_a = - nominal_transition + (hDeadbandDelay_ + lDeadbandDelay_) / 2; - timing->transition_b = - nominal_transition - (hDeadbandDelay_ + lDeadbandDelay_) / 2; - } - if (spread_max > 0) - { - timing->spread_min = usec_to_clocks(1) / 2; - timing->spread_max = usec_to_clocks(spread_max); - } -} - -template -dcc::Packet TivaDCC::IDLE_PKT = dcc::Packet::DCC_IDLE(); - -template -TivaDCC::TivaDCC(const char *name, RailcomDriver *railcom_driver) - : Node(name) - , hDeadbandDelay_(nsec_to_clocks(HW::H_DEADBAND_DELAY_NSEC)) - , lDeadbandDelay_(nsec_to_clocks(HW::L_DEADBAND_DELAY_NSEC)) - , usecDelay_(nsec_to_clocks(1000) / 3) - , writableNotifiable_(nullptr) - , railcomDriver_(railcom_driver) -{ - state_ = PREAMBLE; - - fill_timing(DCC_ZERO, 100 << 1, 100, 100 << 1, 5); - fill_timing(DCC_ONE, 56 << 1, 56, 56 << 1, 4); - /// @todo tune this bit to line up with the bit stream starting after the - /// railcom cutout. - fill_timing(DCC_RC_ONE, 57 << 1, 57, 57 << 1); - - // The following #if switch controls whether or not the - // "generate_railcom_halfzero()" will actually generate a half zero bit - // or if it will in actuality generate a full zero bit. It was determined - // that the half zero workaround does not work with some older decoders, - // but the full zero workaround does. It also works with older decoders - // that needed the half zero, so it seems to be a true super-set workaround. - // - // There is an issue filed to reevaluate this after more field data is - // collected. The idea was to make the most minimal change necessary - // until more data can be collected. - // https://github.com/bakerstu/openmrn/issues/652 -#if 0 - // A small pulse in one direction then a half zero bit in the other - // direction. - fill_timing(DCC_RC_HALF_ZERO, 100 + 56, 56, 100 + 56, 5); -#else - // A full zero bit inserted following the RailCom cutout. - fill_timing(DCC_RC_HALF_ZERO, 100 << 1, 100, 100 << 1, 5); -#endif - - // At the end of the packet the resync process will happen, which means that - // we modify the timer registers in synchronous mode instead of double - // buffering to remove any drift that may have happened during the packet. - // This means that we need to kick off the interval timer a bit earlier than - // nominal to compensate for the CPU execution time. At the same time we - // stretch the negative side of the output waveform, because the next packet - // might be marklin. Stretching avoids outputting a short positive glitch - // between the negative half of the last dcc bit and the fully negative - // marklin preamble. - fill_timing( - DCC_EOP_ONE, (56 << 1) + 20, 56, (56 << 1) - HW::RESYNC_DELAY_USEC); - - fill_timing(MM_ZERO, 208, 26, 208, 2); - fill_timing(MM_ONE, 208, 182, 208, 2); - // Motorola preamble is negative DC signal. - fill_timing(MM_PREAMBLE, 208, 0, 208); - - unsigned h_deadband = 2 * (HW::H_DEADBAND_DELAY_NSEC / 1000); - unsigned railcom_part = 0; - unsigned target = - RAILCOM_CUTOUT_START_USEC + HW::RAILCOM_CUTOUT_START_DELTA_USEC; - fill_timing(RAILCOM_CUTOUT_PRE, 56 << 1, 56, target - railcom_part); - railcom_part = target; - - target = RAILCOM_CUTOUT_MID_USEC + HW::RAILCOM_CUTOUT_MID_DELTA_USEC; - fill_timing(RAILCOM_CUTOUT_FIRST, 56 << 1, 56, target - railcom_part); - railcom_part = target; - - target = RAILCOM_CUTOUT_END_USEC + HW::RAILCOM_CUTOUT_END_DELTA_USEC; - fill_timing(RAILCOM_CUTOUT_SECOND, 56 << 1, 56, target - railcom_part); - railcom_part = target; - - static_assert((5 * 56 * 2 - 56 + HW::RAILCOM_CUTOUT_POST_DELTA_USEC) > - RAILCOM_CUTOUT_END_USEC + HW::RAILCOM_CUTOUT_END_DELTA_USEC, - "railcom cutout too long"); - target = 5 * 56 * 2 - 56 + HW::RAILCOM_CUTOUT_POST_DELTA_USEC; - unsigned remaining_high = target - railcom_part; - // remaining time until 5 one bits are complete. For the PWM timer we have - // some fraction of the high part, then a full low side, then we stretch - // the low side to avoid the packet transition glitch. - fill_timing(RAILCOM_CUTOUT_POST, remaining_high + 56 + 20, remaining_high, - remaining_high + 56 + h_deadband + - HW::RAILCOM_CUTOUT_POST_NEGATIVE_DELTA_USEC); - - // We need to disable the timers before making changes to the config. - MAP_TimerDisable(HW::CCP_BASE, TIMER_A); - MAP_TimerDisable(HW::CCP_BASE, TIMER_B); - -#ifdef TIVADCC_TIVA - MAP_TimerClockSourceSet(HW::CCP_BASE, TIMER_CLOCK_SYSTEM); - MAP_TimerClockSourceSet(HW::INTERVAL_BASE, TIMER_CLOCK_SYSTEM); -#endif - MAP_TimerConfigure(HW::CCP_BASE, TIMER_CFG_SPLIT_PAIR | - TIMER_CFG_A_PWM | - TIMER_CFG_B_PWM); - MAP_TimerControlStall(HW::CCP_BASE, TIMER_BOTH, true); - - - // This will cause reloading the timer values only at the next period - // instead of immediately. The PLO bit needs to be set to allow for DC - // voltage output. - HWREG(HW::CCP_BASE + TIMER_O_TAMR) |= - TIMER_TAMR_TAPLO | TIMER_TAMR_TAMRSU | TIMER_TAMR_TAILD; - HWREG(HW::CCP_BASE + TIMER_O_TBMR) |= - TIMER_TBMR_TBPLO | TIMER_TBMR_TBMRSU | TIMER_TBMR_TBILD; - - HWREG(HW::INTERVAL_BASE + TIMER_O_TAMR) |= - TIMER_TAMR_TAMRSU | TIMER_TAMR_TAILD; - - MAP_TimerConfigure(HW::INTERVAL_BASE, TIMER_CFG_SPLIT_PAIR | - TIMER_CFG_A_PERIODIC); - MAP_TimerControlStall(HW::INTERVAL_BASE, TIMER_A, true); - - MAP_TimerControlLevel(HW::CCP_BASE, TIMER_A, HW::PIN_H_INVERT); - MAP_TimerControlLevel(HW::CCP_BASE, TIMER_B, !HW::PIN_L_INVERT); - - MAP_TimerLoadSet(HW::CCP_BASE, TIMER_A, timings[DCC_ONE].period); - MAP_TimerLoadSet(HW::CCP_BASE, TIMER_B, hDeadbandDelay_); - MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, timings[DCC_ONE].period + hDeadbandDelay_ * 2); - MAP_TimerMatchSet(HW::CCP_BASE, TIMER_A, timings[DCC_ONE].transition_a); - MAP_TimerMatchSet(HW::CCP_BASE, TIMER_B, timings[DCC_ONE].transition_b); - - MAP_IntDisable(HW::INTERVAL_INTERRUPT); - MAP_IntPrioritySet(HW::INTERVAL_INTERRUPT, 0x20); - MAP_TimerIntEnable(HW::INTERVAL_BASE, TIMER_TIMA_TIMEOUT); - - MAP_TimerEnable(HW::CCP_BASE, TIMER_A); - MAP_TimerEnable(HW::CCP_BASE, TIMER_B); - MAP_TimerEnable(HW::INTERVAL_BASE, TIMER_A); - -#ifdef TIVADCC_TIVA - MAP_TimerSynchronize(TIMER0_BASE, HW::TIMER_SYNC); -#endif - - MAP_TimerLoadSet(HW::CCP_BASE, TIMER_B, timings[DCC_ONE].period); - MAP_TimerLoadSet( - HW::INTERVAL_BASE, TIMER_A, timings[DCC_ONE].interval_period); - MAP_IntEnable(HW::INTERVAL_INTERRUPT); - - // The OS interrupt does not come from the hardware timer. - MAP_TimerIntDisable(HW::CCP_BASE, 0xFFFFFFFF); - // The OS interrupt comes under the freertos kernel. - MAP_IntPrioritySet(HW::OS_INTERRUPT, configKERNEL_INTERRUPT_PRIORITY); - MAP_IntEnable(HW::OS_INTERRUPT); -} - -/** Read from a file or device. - * @param file file reference for this device - * @param buf location to place read data - * @param count number of bytes to read - * @return number of bytes read upon success, -1 upon failure with errno containing the cause - */ -template -ssize_t TivaDCC::read(File *file, void *buf, size_t count) -{ - return -EINVAL; -} - -/** Write to a file or device. - * @param file file reference for this device - * @param buf location to find write data - * @param count number of bytes to write - * @return number of bytes written upon success, -1 upon failure with errno containing the cause - */ -template -__attribute__((optimize("-O3"))) -ssize_t TivaDCC::write(File *file, const void *buf, size_t count) -{ - if (count != sizeof(dcc::Packet)) - { - return -EINVAL; - } - - OSMutexLock l(&lock_); - - if (packetQueue_.full()) - { - return -ENOSPC; - } - - dcc::Packet* packet = &packetQueue_.back(); - memcpy(packet, buf, count); - - // Duplicates the marklin packet if it came single. - if (packet->packet_header.is_marklin) { - if (packet->dlc == 3) { - packet->dlc = 6; - packet->payload[3] = packet->payload[0]; - packet->payload[4] = packet->payload[1]; - packet->payload[5] = packet->payload[2]; - } else { - HASSERT(packet->dlc == 6); - } - } - - packetQueue_.increment_back(); - static uint8_t flip = 0; - if (++flip >= 4) - { - flip = 0; - HW::flip_led(); - } - - return count; -} - -/** Request an ioctl transaction - * @param file file reference for this device - * @param node node reference for this device - * @param key ioctl key - * @param data key data - */ -template -int TivaDCC::ioctl(File *file, unsigned long int key, unsigned long data) -{ - if (IOC_TYPE(key) == CAN_IOC_MAGIC && - IOC_SIZE(key) == NOTIFIABLE_TYPE && - key == CAN_IOC_WRITE_ACTIVE) { - Notifiable* n = reinterpret_cast(data); - HASSERT(n); - // If there is no space for writing, we put the incomng notification - // into the holder. Otherwise we notify it immediately. - if (packetQueue_.full()) - { - portENTER_CRITICAL(); - if (packetQueue_.full()) - { - // We are in a critical section now. If we got into this - // branch, then the buffer was full at the beginning of the - // critical section. If the hardware interrupt kicks in now, - // and sets the os_interrupt to pending, the os interrupt will - // not happen until we leave the critical section, and thus the - // swap will be in effect by then. - std::swap(n, writableNotifiable_); - } - portEXIT_CRITICAL(); - } - if (n) { - n->notify(); - } - return 0; - } - errno = EINVAL; - return -1; -} - -template -__attribute__((optimize("-O3"))) -inline void TivaDCC::os_interrupt_handler() -{ - if (!packetQueue_.full()) { - Notifiable* n = writableNotifiable_; - writableNotifiable_ = nullptr; - if (n) n->notify_from_isr(); - } -} - - -#endif // _FREERTOS_DRIVERS_TI_TIVADCC_HXX_ +#endif // _FREERTOS_DRIVERS_COMMON_FIXEDQUEUE_HXX_ diff --git a/src/freertos_drivers/common/RailcomImpl.hxx b/src/freertos_drivers/common/RailcomImpl.hxx index ed26424a7..ebf6fb56f 100644 --- a/src/freertos_drivers/common/RailcomImpl.hxx +++ b/src/freertos_drivers/common/RailcomImpl.hxx @@ -54,16 +54,12 @@ * @date 6 Jan 2015 */ -#ifndef _FREERTOS_DRIVERS_TI_TIVARAILCOM_HXX_ -#define _FREERTOS_DRIVERS_TI_TIVARAILCOM_HXX_ - -#if (!defined(TIVADCC_TIVA)) && (!defined(TIVADCC_CC3200)) -#error must define either TIVADCC_TIVA or TIVADCC_CC3200 -#endif +#ifndef _FREERTOS_DRIVERS_COMMON_RAILCOMIMPL_HXX_ +#define _FREERTOS_DRIVERS_COMMON_RAILCOMIMPL_HXX_ #include "TivaDCC.hxx" // for FixedQueue -#include "RailcomDriver.hxx" +#include "freertos_drivers/common/RailcomDriver.hxx" #include "dcc/RailCom.hxx" /* @@ -123,11 +119,7 @@ __attribute__((weak)) = {SYSCTL_PERIPH_UART4, SYSCTL_PERIPH_UART3, SYSCTL_PERIPH */ -/// Base class for railcom drivers. Ideally this base class should be -/// non-specific to the hardwsre or the TivaWare driver library. -/// -/// @todo(balazs.racz) factor this class out into -/// freertos_drivers/common/Railcom.hxx. +/// Base class for railcom driver implementations. template class RailcomDriverBase : public RailcomDriver, private Node { @@ -286,209 +278,4 @@ protected: dcc::Feedback *returnedPackets_[HW::CHANNEL_COUNT]; }; -/// Railcom driver for TI Tiva-class microcontrollers using the TivaWare -/// peripheral library. -/// -/// This railcom driver supports parallel polling of multiple UART channels for -/// the railcom data. -template class TivaRailcomDriver : public RailcomDriverBase -{ -public: - /// Constructor. @param path is the device node path (e.g. "/dev/railcom0"). - TivaRailcomDriver(const char *path) - : RailcomDriverBase(path) - { - MAP_IntPrioritySet(HW::OS_INTERRUPT, configKERNEL_INTERRUPT_PRIORITY); - MAP_IntEnable(HW::OS_INTERRUPT); - } - - ~TivaRailcomDriver() - { - MAP_IntDisable(HW::OS_INTERRUPT); - } - -private: - /// True when we are currently within a cutout. - bool inCutout_ = false; - - using RailcomDriverBase::returnedPackets_; - - /// Sets a given software interrupt pending. - /// @param int_nr interrupt number (will be HW::OS_INTERRUPT) - void int_set_pending(unsigned int_nr) override - { - MAP_IntPendSet(int_nr); - } - - // File node interface - void enable() OVERRIDE - { - for (unsigned i = 0; i < ARRAYSIZE(HW::UART_BASE); ++i) - { -#ifdef TIVADCC_TIVA - MAP_SysCtlPeripheralEnable(HW::UART_PERIPH[i]); -#elif defined(TIVADCC_CC3200) - MAP_PRCMPeripheralClkEnable(HW::UART_PERIPH[i], PRCM_RUN_MODE_CLK); -#endif - MAP_UARTConfigSetExpClk(HW::UART_BASE[i], cm3_cpu_clock_hz, 250000, - UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | - UART_CONFIG_PAR_NONE); - MAP_UARTFIFOEnable(HW::UART_BASE[i]); - // Disables the receiver. - HWREGBITW(HW::UART_BASE[i] + UART_O_CTL, UART_CTL_RXE) = 0; - MAP_UARTEnable(HW::UART_BASE[i]); - } - } - void disable() OVERRIDE - { - for (unsigned i = 0; i < ARRAYSIZE(HW::UART_BASE); ++i) - { - MAP_UARTDisable(HW::UART_BASE[i]); - } - } - - // RailcomDriver interface - void feedback_sample() OVERRIDE { - HW::enable_measurement(true); - this->add_sample(HW::sample()); - HW::disable_measurement(); - } - - void start_cutout() OVERRIDE - { - HW::enable_measurement(false); - const bool need_ch1_cutout = HW::need_ch1_cutout() || (this->feedbackKey_ < 11000); - Debug::RailcomRxActivate::set(true); - for (unsigned i = 0; i < ARRAYSIZE(HW::UART_BASE); ++i) - { - if (need_ch1_cutout) - { - HWREG(HW::UART_BASE[i] + UART_O_CTL) |= UART_CTL_RXE; - } - // HWREGBITW(HW::UART_BASE[i] + UART_O_CTL, UART_CTL_RXE) = - // UART_CTL_RXE; - // flush fifo - while (MAP_UARTCharGetNonBlocking(HW::UART_BASE[i]) >= 0) - ; - returnedPackets_[i] = 0; - } - Debug::RailcomDriverCutout::set(true); - } - - void middle_cutout() OVERRIDE - { - Debug::RailcomDriverCutout::set(false); - for (unsigned i = 0; i < ARRAYSIZE(HW::UART_BASE); ++i) - { - while (MAP_UARTCharsAvail(HW::UART_BASE[i])) - { - Debug::RailcomDataReceived::toggle(); - Debug::RailcomAnyData::set(true); - if (!returnedPackets_[i]) - { - returnedPackets_[i] = this->alloc_new_packet(i); - } - if (!returnedPackets_[i]) - { - break; - } - long data = MAP_UARTCharGetNonBlocking(HW::UART_BASE[i]); - if (data < 0 || data > 0xff) { - // We reset the receiver circuitry because we got an error - // in channel 1. Typical cause of this error is if there - // are multiple locomotives on the block (e.g. if this is - // the global detector) and they talk over each other - // during ch1 broadcast. There is a good likelihood that - // the asynchronous receiver is out of sync with the - // transmitter, but right now we should be in the - // between-byte space. - HWREG(HW::UART_BASE[i] + UART_O_CTL) &= ~UART_CTL_RXE; - Debug::RailcomError::toggle(); - returnedPackets_[i]->add_ch1_data(0xF8 | ((data >> 8) & 0x7)); - continue; - } - returnedPackets_[i]->add_ch1_data(data); - } - HWREG(HW::UART_BASE[i] + UART_O_CTL) |= UART_CTL_RXE; - } - HW::middle_cutout_hook(); - Debug::RailcomDriverCutout::set(true); - } - - void end_cutout() OVERRIDE - { - HW::disable_measurement(); - bool have_packets = false; - for (unsigned i = 0; i < ARRAYSIZE(HW::UART_BASE); ++i) - { - while (MAP_UARTCharsAvail(HW::UART_BASE[i])) - { - Debug::RailcomDataReceived::toggle(); - Debug::RailcomAnyData::set(true); - Debug::RailcomCh2Data::set(true); - if (!returnedPackets_[i]) - { - returnedPackets_[i] = this->alloc_new_packet(i); - } - if (!returnedPackets_[i]) - { - break; - } - long data = MAP_UARTCharGetNonBlocking(HW::UART_BASE[i]); - if (data < 0 || data > 0xff) { - Debug::RailcomError::toggle(); - returnedPackets_[i]->add_ch2_data(0xF8 | ((data >> 8) & 0x7)); - continue; - } - if (data == 0xE0) { - Debug::RailcomE0::toggle(); - } - returnedPackets_[i]->add_ch2_data(data); - } - HWREG(HW::UART_BASE[i] + UART_O_CTL) &= ~UART_CTL_RXE; - Debug::RailcomRxActivate::set(false); - //HWREGBITW(HW::UART_BASE[i] + UART_O_CTL, UART_CTL_RXE) = 0; - if (returnedPackets_[i]) { - have_packets = true; - this->feedbackQueue_.commit_back(); - Debug::RailcomPackets::toggle(); - returnedPackets_[i] = nullptr; - MAP_IntPendSet(HW::OS_INTERRUPT); - } - } - if (!have_packets) - { - // Ensures that at least one feedback packet is sent back even when - // it is with no railcom payload. - auto *p = this->alloc_new_packet(0); - if (p) - { - this->feedbackQueue_.commit_back(); - Debug::RailcomPackets::toggle(); - MAP_IntPendSet(HW::OS_INTERRUPT); - } - } - Debug::RailcomCh2Data::set(false); - Debug::RailcomDriverCutout::set(false); - } - - void no_cutout() OVERRIDE - { - for (unsigned i = 0; i < ARRAYSIZE(HW::UART_BASE); ++i) - { - if (!returnedPackets_[i]) - { - returnedPackets_[i] = this->alloc_new_packet(i); - } - if (returnedPackets_[i]) - { - this->feedbackQueue_.commit_back(); - Debug::RailcomPackets::toggle(); - returnedPackets_[i] = nullptr; - MAP_IntPendSet(HW::OS_INTERRUPT); - } - } - } -}; - -#endif // _FREERTOS_DRIVERS_TI_TIVARAILCOM_HXX_ +#endif // _FREERTOS_DRIVERS_COMMON_RAILCOMIMPL_HXX_ diff --git a/src/freertos_drivers/ti/TivaDCC.hxx b/src/freertos_drivers/ti/TivaDCC.hxx index 3166819c5..29b122afe 100644 --- a/src/freertos_drivers/ti/TivaDCC.hxx +++ b/src/freertos_drivers/ti/TivaDCC.hxx @@ -64,6 +64,7 @@ #include "driverlib/utils.h" #endif +#include "freertos_drivers/common/FixedQueue.hxx" #include "Devtab.hxx" #include "RailcomDriver.hxx" #include "dcc/DccOutput.hxx" @@ -75,99 +76,6 @@ /// signal extern "C" uint8_t spreadSpectrum; -/// This structure is safe to use from an interrupt context and a regular -/// context at the same time, provided that -/// -/// . one context uses only the front() and the other only the back() functions. -/// -/// . ++ and -- are compiled into atomic operations on the processor (on the -/// count_ variable). -/// -/// @deprecated, use @ref DeviceBuffer instead. -/// -/// @todo(balazs.racz) replace uses of this class with DeviceBuffer (to enable -/// select support for example). -template class FixedQueue { -public: - FixedQueue() - : rdIndex_(0) - , wrIndex_(0) - , count_(0) - { - } - - /// @return true if there is no entry in the queue. - bool empty() { return size() == 0; } - /// @return true if the queue cannot accept more elements. - bool full() { return size() >= SIZE; } - /// @return the current number of entries in the queue. - size_t size() { return __atomic_load_n(&count_, __ATOMIC_SEQ_CST); } - - /// Returns the head of the FIFO (next element to read). - T& front() { - HASSERT(!empty()); - return storage_[rdIndex_]; - } - - /// Removes the head of the FIFO from the queue. - void increment_front() { - HASSERT(!empty()); - if (++rdIndex_ >= SIZE) rdIndex_ = 0; - __atomic_fetch_add(&count_, -1, __ATOMIC_SEQ_CST); - } - - /// Returns the space to write the next element to. - T& back() { - HASSERT(!full()); - return storage_[wrIndex_]; - } - - /// Commits the element at back() into the queue. - void increment_back() { - HASSERT(!full()); - if (++wrIndex_ >= SIZE) wrIndex_ = 0; - __atomic_fetch_add(&count_, 1, __ATOMIC_SEQ_CST); - } - - /** Increments the back pointer without committing the entry into the - * queue. - * - * This essentially reserves an entry in the queue for filling in, without - * making that entry available for reading. Must be followed by a - * commit_back call when filling in the entry is finished. An arbitrary - * number of such entries can be reserved (up to the number of free entries - * in the queue). */ - void noncommit_back() { - HASSERT(has_noncommit_space()); - if (++wrIndex_ >= SIZE) wrIndex_ = 0; - } - - /** @returns true if we can do a noncommit back. */ - bool has_noncommit_space() { - if (full()) return false; - auto new_index = wrIndex_; - if (++new_index >= SIZE) new_index = 0; - return new_index != rdIndex_; - } - - /** Commits the oldest entry reserved by noncommit_back. */ - void commit_back() { - HASSERT(count_ <= SIZE); - __atomic_fetch_add(&count_, 1, __ATOMIC_SEQ_CST); - } - -private: - /// Payload of elements stored. - T storage_[SIZE]; - /// The index of the element to return next upon a read. This element is - /// typically full (unless the queue is empty itself). - uint8_t rdIndex_; - /// The index of the element where to write the next input to. - uint8_t wrIndex_; - /// How many elements are there in the queue. - volatile uint8_t count_; -}; - /** A device driver for sending DCC packets. If the packet queue is empty, * then the device driver automatically sends out idle DCC packets. The * device driver uses two instances of the 16/32-bit timer pairs. The user diff --git a/src/freertos_drivers/ti/TivaRailcom.hxx b/src/freertos_drivers/ti/TivaRailcom.hxx index ed26424a7..ba9198820 100644 --- a/src/freertos_drivers/ti/TivaRailcom.hxx +++ b/src/freertos_drivers/ti/TivaRailcom.hxx @@ -63,7 +63,7 @@ #include "TivaDCC.hxx" // for FixedQueue -#include "RailcomDriver.hxx" +#include "freertos_drivers/common/RailcomImpl.hxx" #include "dcc/RailCom.hxx" /* @@ -123,169 +123,6 @@ __attribute__((weak)) = {SYSCTL_PERIPH_UART4, SYSCTL_PERIPH_UART3, SYSCTL_PERIPH */ -/// Base class for railcom drivers. Ideally this base class should be -/// non-specific to the hardwsre or the TivaWare driver library. -/// -/// @todo(balazs.racz) factor this class out into -/// freertos_drivers/common/Railcom.hxx. -template -class RailcomDriverBase : public RailcomDriver, private Node -{ -public: - /// Constructor. @param name is the device node (e.g. "/dev/railcom0"). - RailcomDriverBase(const char *name) - : Node(name) - , readableNotifiable_(nullptr) - { - HW::hw_init(); - } - - ~RailcomDriverBase() - { - } - -private: - /// Sets a given software interrupt pending. - /// @param int_nr interrupt number (will be HW::OS_INTERRUPT) - virtual void int_set_pending(unsigned int_nr) = 0; - - ssize_t write(File *, const void *, size_t) OVERRIDE - { - // This device is read-only. - return -ENOSYS; - } - - ssize_t read(File *file, void *buf, size_t count) OVERRIDE - { - if (count != sizeof(dcc::Feedback)) - { - return -EINVAL; - } - OSMutexLock l(&lock_); - if (feedbackQueue_.empty()) - { - return -EAGAIN; - } - memcpy(buf, &feedbackQueue_.front(), count); - feedbackQueue_.increment_front(); - return count; - } - - void flush_buffers() OVERRIDE - { - while (!feedbackQueue_.empty()) - { - feedbackQueue_.increment_front(); - } - } - - // Asynchronous interface - int ioctl(File *file, unsigned long int key, unsigned long data) override - { - if (IOC_TYPE(key) == CAN_IOC_MAGIC && - IOC_SIZE(key) == NOTIFIABLE_TYPE && key == CAN_IOC_READ_ACTIVE) - { - Notifiable *n = reinterpret_cast(data); - HASSERT(n); - // If there is no data for reading, we put the incoming notification - // into the holder. Otherwise we notify it immediately. - if (feedbackQueue_.empty()) - { - portENTER_CRITICAL(); - if (feedbackQueue_.empty()) - { - // We are in a critical section now. If we got into this - // branch, then the buffer was full at the beginning of the - // critical section. If the hardware interrupt kicks in - // now, and sets the os_interrupt to pending, the os - // interrupt will not happen until we leave the critical - // section, and thus the swap will be in effect by then. - std::swap(n, readableNotifiable_); - } - portEXIT_CRITICAL(); - } - if (n) - { - n->notify(); - } - return 0; - } - errno = EINVAL; - return -1; - } - -public: - /// Implementation of the interrupt handler running at the kernel interrupt - /// priority. Call this function from the hardware interrupt handler. - void os_interrupt_handler() __attribute__((always_inline)) - { - if (!feedbackQueue_.empty()) - { - Notifiable *n = readableNotifiable_; - readableNotifiable_ = nullptr; - if (n) - { - n->notify_from_isr(); - os_isr_exit_yield_test(true); - } - } - } - -private: - void set_feedback_key(uint32_t key) OVERRIDE - { - feedbackKey_ = key; - } - -protected: - /** Takes a new empty packet at the front of the queue, fills in feedback - * key and channel information. - * @param channel is which channel to set the packet for. - * @return a packet pointer to write into. If there is no space, returns - * nullptr.*/ - dcc::Feedback *alloc_new_packet(uint8_t channel) - { - if (!feedbackQueue_.has_noncommit_space()) - { - return nullptr; - } - dcc::Feedback *entry = &feedbackQueue_.back(); - feedbackQueue_.noncommit_back(); - entry->reset(feedbackKey_); - entry->channel = channel; - return entry; - } - - /// Adds a sample for a preamble bit. @param sample is the next sample to - /// return to the application layer (usually a bitmask setting which - /// channels are active). - void add_sample(int sample) { - if (sample < 0) return; // skipped sampling - if (feedbackQueue_.full()) { - extern uint32_t feedback_sample_overflow_count; - ++feedback_sample_overflow_count; - return; - } - feedbackQueue_.back().reset(feedbackKey_); - feedbackQueue_.back().channel = HW::get_feedback_channel(); - feedbackQueue_.back().add_ch1_data(sample); - uint32_t tick_timer = HW::get_timer_tick(); - memcpy(feedbackQueue_.back().ch2Data, &tick_timer, 4); - feedbackQueue_.increment_back(); - int_set_pending(HW::OS_INTERRUPT); - } - - /** Notify this when we have data in our buffers. */ - Notifiable *readableNotifiable_; - /** The packets we have read so far. */ - FixedQueue feedbackQueue_; - /// Stores the key for the next packets to read. - uint32_t feedbackKey_; - /** Stores pointers to packets we are filling right now, one for each - * channel. */ - dcc::Feedback *returnedPackets_[HW::CHANNEL_COUNT]; -}; - /// Railcom driver for TI Tiva-class microcontrollers using the TivaWare /// peripheral library. /// From a5bcbcbf052dc4c524b6ee5299b242ec0e9e9670 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 6 Mar 2023 17:55:08 -0800 Subject: [PATCH 14/31] Removes unneeded include. --- src/freertos_drivers/ti/TivaRailcom.hxx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/freertos_drivers/ti/TivaRailcom.hxx b/src/freertos_drivers/ti/TivaRailcom.hxx index ba9198820..da84c510d 100644 --- a/src/freertos_drivers/ti/TivaRailcom.hxx +++ b/src/freertos_drivers/ti/TivaRailcom.hxx @@ -61,8 +61,6 @@ #error must define either TIVADCC_TIVA or TIVADCC_CC3200 #endif -#include "TivaDCC.hxx" // for FixedQueue - #include "freertos_drivers/common/RailcomImpl.hxx" #include "dcc/RailCom.hxx" From cd5e980808a3d71ff20712b633ef59cf150a5dab Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 6 Mar 2023 17:55:42 -0800 Subject: [PATCH 15/31] Fork src/freertos_drivers/ti/TivaRailcom.hxx to src/freertos_drivers/st/Stm32Railcom.hxx --- src/freertos_drivers/{ti/TivaRailcom.hxx => st/Stm32Railcom.hxx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/freertos_drivers/{ti/TivaRailcom.hxx => st/Stm32Railcom.hxx} (100%) diff --git a/src/freertos_drivers/ti/TivaRailcom.hxx b/src/freertos_drivers/st/Stm32Railcom.hxx similarity index 100% rename from src/freertos_drivers/ti/TivaRailcom.hxx rename to src/freertos_drivers/st/Stm32Railcom.hxx From a1ce1c12f7c3e6798f69b608e97aafb2a8b66c7d Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 6 Mar 2023 17:55:43 -0800 Subject: [PATCH 16/31] Fork src/freertos_drivers/ti/TivaRailcom.hxx to src/freertos_drivers/st/Stm32Railcom.hxx step 2 --- src/freertos_drivers/ti/{TivaRailcom.hxx => TivaRailcom.hxx.bak} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/freertos_drivers/ti/{TivaRailcom.hxx => TivaRailcom.hxx.bak} (100%) diff --git a/src/freertos_drivers/ti/TivaRailcom.hxx b/src/freertos_drivers/ti/TivaRailcom.hxx.bak similarity index 100% rename from src/freertos_drivers/ti/TivaRailcom.hxx rename to src/freertos_drivers/ti/TivaRailcom.hxx.bak From 7dbd8000c9546afd452dfa67861b970e2ce21c03 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 6 Mar 2023 17:55:44 -0800 Subject: [PATCH 17/31] Fork src/freertos_drivers/ti/TivaRailcom.hxx to src/freertos_drivers/st/Stm32Railcom.hxx step 4 --- src/freertos_drivers/ti/{TivaRailcom.hxx.bak => TivaRailcom.hxx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/freertos_drivers/ti/{TivaRailcom.hxx.bak => TivaRailcom.hxx} (100%) diff --git a/src/freertos_drivers/ti/TivaRailcom.hxx.bak b/src/freertos_drivers/ti/TivaRailcom.hxx similarity index 100% rename from src/freertos_drivers/ti/TivaRailcom.hxx.bak rename to src/freertos_drivers/ti/TivaRailcom.hxx From b9e888eb86f28dbfbdbb6f9d4b5047a36d08da6b Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 12 Mar 2023 14:58:37 +0100 Subject: [PATCH 18/31] Eliminates b'' from the revision strings. (#701) The data parsed from the git output was rteated as a byte array by python. Doing str() on a byte array gives b'1234567'. By doing decode() on the byte array we turn it into a string and this liminates the unneeded quotation marks. --- bin/revision.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/revision.py b/bin/revision.py index 51bae5cd9..b6c555562 100755 --- a/bin/revision.py +++ b/bin/revision.py @@ -103,7 +103,7 @@ # get the short hash git_hash = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']) - git_hash = str(git_hash[:7]) + git_hash = str(git_hash[:7].decode()) # get the dirty flag dirty = os.system('git diff --quiet') From c34b444b170e63b9157ba94098edd4ff3d19529f Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 28 Mar 2023 22:14:27 +0200 Subject: [PATCH 19/31] Stores the last offset in the configuration space when generating the XML. (#693) This is helpful in determining how much eeprom space is needed for a given project. The developer just has to check the cdi.xmlout file. (note: this is not the byte size of the XML, but the byte size of the config represented by it.) --- src/openlcb/CompileCdiMain.cxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/openlcb/CompileCdiMain.cxx b/src/openlcb/CompileCdiMain.cxx index f8c391c98..44d7f9542 100644 --- a/src/openlcb/CompileCdiMain.cxx +++ b/src/openlcb/CompileCdiMain.cxx @@ -42,6 +42,8 @@ void render_cdi_helper(const CdiType &t, string ns, string name) printf("extern const size_t %s_SIZE;\n", name.c_str()); printf("const size_t %s_SIZE = sizeof(%s_DATA);\n", name.c_str(), name.c_str()); + printf("const size_t %s_END_OFFSET = %u;\n", name.c_str(), + (unsigned)t.end_offset()); printf("\n} // namespace %s\n\n", ns.c_str()); } } From d96e64c0e9a05f0b00fc9cdc7640ea19466516d8 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Fri, 31 Mar 2023 17:22:58 +0200 Subject: [PATCH 20/31] Removes the driverlib from the path for CC322x.mk Removes a duplicate entry from the clang-format file. --- .clang-format | 2 +- etc/cc322x.mk | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.clang-format b/.clang-format index e7d623a0f..584ed7ada 100644 --- a/.clang-format +++ b/.clang-format @@ -34,7 +34,7 @@ Standard: Cpp11 IndentWidth: 4 TabWidth: 8 UseTab: Never -BreakBeforeBraces: Allman +# BreakBeforeBraces: Allman IndentFunctionDeclarationAfterType: false SpacesInParentheses: false SpacesInAngles: false diff --git a/etc/cc322x.mk b/etc/cc322x.mk index 0133d298f..2f450e82d 100644 --- a/etc/cc322x.mk +++ b/etc/cc322x.mk @@ -6,7 +6,6 @@ ifdef TICC3220SDKPATH INCLUDES += -DSL_PLATFORM_MULTI_THREADED -DSL_FULL -DTARGET_IS_CC3220 \ -I$(OPENMRNPATH)/src/freertos_drivers/ti \ -I$(OPENMRNPATH)/src/freertos_drivers/net_cc32xx \ - -I$(TICC3220SDKPATH)/source/ti/devices/cc32xx/driverlib \ -I$(TICC3220SDKPATH)/source/ti/devices/cc32xx \ -I$(TICC3220SDKPATH)/source/ti/posix/gcc From 32595621f5f02a021c560be883ec3ac502079156 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 1 Apr 2023 12:20:03 +0200 Subject: [PATCH 21/31] Adds support for unaligned reads and writes to the TivaEEPROMFile driver. (#704) * Adds support for unaligned reads and writes to the TivaEEPROMFile driver. Previously this driver would only accept dword-aligned reads and writes. * Changes all masks to be hex. --- src/freertos_drivers/ti/TivaEEPROMFile.hxx | 82 +++++++++++++++++++--- 1 file changed, 74 insertions(+), 8 deletions(-) diff --git a/src/freertos_drivers/ti/TivaEEPROMFile.hxx b/src/freertos_drivers/ti/TivaEEPROMFile.hxx index f9e6a0331..d13de04f6 100644 --- a/src/freertos_drivers/ti/TivaEEPROMFile.hxx +++ b/src/freertos_drivers/ti/TivaEEPROMFile.hxx @@ -75,10 +75,44 @@ public: /// @return number of bytes written upon success, -errno upon failure ssize_t write(unsigned int index, const void *buf, size_t len) override { - HASSERT(index % 4 == 0); - HASSERT(len % 4 == 0); - MAP_EEPROMProgram((uint32_t *)buf, index + byteOffset_, len); - return len; + size_t count = 0; + uint8_t *b = (uint8_t *)buf; + // Partial write at the beginning. + if (index & 0x3) + { + uint32_t rd = 0; + MAP_EEPROMRead(&rd, (index + byteOffset_) & ~0x3, 4); + size_t actual_len = 4 - (index & 0x3); + actual_len = std::min(len, actual_len); + memcpy(((uint8_t*)&rd) + (index & 0x3), b, actual_len); + MAP_EEPROMProgram(&rd, (index + byteOffset_) & ~0x3, 4); + len -= actual_len; + index += actual_len; + b += actual_len; + count += actual_len; + } + // Full writes in the middle. + size_t word_len = len & ~0x3; + if (word_len) + { + HASSERT(index & 0x3 == 0); + MAP_EEPROMProgram((uint32_t *)b, index + byteOffset_, word_len); + index += word_len; + b += word_len; + len -= word_len; + count += word_len; + } + // Partial write at the end. + if (len & 0x3) + { + HASSERT(index & 0x3 == 0); + uint32_t rd = 0; + MAP_EEPROMRead(&rd, index + byteOffset_, 4); + memcpy(&rd, b, len); + MAP_EEPROMProgram(&rd, (index + byteOffset_), 4); + count += len; + } + return count; } /// Read from the eeprom. @@ -88,10 +122,42 @@ public: /// @return number of bytes read upon success, -errno upon failure ssize_t read(unsigned int index, void *buf, size_t len) override { - HASSERT(index % 4 == 0); - HASSERT(len % 4 == 0); - MAP_EEPROMRead((uint32_t *)buf, index + byteOffset_, len); - return len; + size_t count = 0; + uint8_t *b = (uint8_t *)buf; + // Partial read at the beginning. + if (index & 0x3) + { + uint32_t rd = 0; + MAP_EEPROMRead(&rd, (index + byteOffset_) & ~0x3, 4); + size_t actual_len = 4 - (index & 0x3); + actual_len = std::min(len, actual_len); + memcpy(b, ((uint8_t *)&rd) + (index & 0x3), actual_len); + len -= actual_len; + index += actual_len; + b += actual_len; + count += actual_len; + } + // Full reads in the middle. + size_t word_len = len & ~0x3; + if (word_len) + { + HASSERT(index & 0x3 == 0); + MAP_EEPROMRead((uint32_t *)b, index + byteOffset_, word_len); + index += word_len; + b += word_len; + len -= word_len; + count += word_len; + } + // Partial read at the end. + if (len & 0x3) + { + HASSERT(index & 0x3 == 0); + uint32_t rd = 0; + MAP_EEPROMRead(&rd, index + byteOffset_, 4); + memcpy(b, &rd, len); + count += len; + } + return count; } private: From 5c83605a783fc936659ffde93e732718d0353fcd Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 1 Apr 2023 12:58:59 +0200 Subject: [PATCH 22/31] Fixes compilation warnings. --- src/freertos_drivers/ti/TivaEEPROMFile.hxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/freertos_drivers/ti/TivaEEPROMFile.hxx b/src/freertos_drivers/ti/TivaEEPROMFile.hxx index d13de04f6..efcec0af7 100644 --- a/src/freertos_drivers/ti/TivaEEPROMFile.hxx +++ b/src/freertos_drivers/ti/TivaEEPROMFile.hxx @@ -95,7 +95,7 @@ public: size_t word_len = len & ~0x3; if (word_len) { - HASSERT(index & 0x3 == 0); + HASSERT((index & 0x3) == 0); MAP_EEPROMProgram((uint32_t *)b, index + byteOffset_, word_len); index += word_len; b += word_len; @@ -105,7 +105,7 @@ public: // Partial write at the end. if (len & 0x3) { - HASSERT(index & 0x3 == 0); + HASSERT((index & 0x3) == 0); uint32_t rd = 0; MAP_EEPROMRead(&rd, index + byteOffset_, 4); memcpy(&rd, b, len); @@ -141,7 +141,7 @@ public: size_t word_len = len & ~0x3; if (word_len) { - HASSERT(index & 0x3 == 0); + HASSERT((index & 0x3) == 0); MAP_EEPROMRead((uint32_t *)b, index + byteOffset_, word_len); index += word_len; b += word_len; @@ -151,7 +151,7 @@ public: // Partial read at the end. if (len & 0x3) { - HASSERT(index & 0x3 == 0); + HASSERT((index & 0x3) == 0); uint32_t rd = 0; MAP_EEPROMRead(&rd, index + byteOffset_, 4); memcpy(b, &rd, len); From 3e22e2e595b9db9688879d68d9a86a4c8fd40296 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 22 Apr 2023 09:53:38 -0700 Subject: [PATCH 23/31] Fixes bug in flash write. --- src/freertos_drivers/spiffs/stm32f0_f3/Stm32SPIFFS.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/freertos_drivers/spiffs/stm32f0_f3/Stm32SPIFFS.cxx b/src/freertos_drivers/spiffs/stm32f0_f3/Stm32SPIFFS.cxx index 89ce2c0d6..09e7bd15e 100644 --- a/src/freertos_drivers/spiffs/stm32f0_f3/Stm32SPIFFS.cxx +++ b/src/freertos_drivers/spiffs/stm32f0_f3/Stm32SPIFFS.cxx @@ -82,7 +82,7 @@ int32_t Stm32SPIFFS::flash_write(uint32_t addr, uint32_t size, uint8_t *src) src += 1; } - while (size > 8) + while (size >= 8) { WriteWord ww; memcpy(ww.data, src, 8); @@ -93,7 +93,7 @@ int32_t Stm32SPIFFS::flash_write(uint32_t addr, uint32_t size, uint8_t *src) src += 8; } - while (size > 2) + while (size >= 2) { WriteWord ww; memcpy(ww.data_hword, src, 2); From d195728b4a3022fc1a743b92808044a8774f3b0b Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 23 Apr 2023 22:27:20 +0200 Subject: [PATCH 24/31] Fixes some compile errors: (#709) - ByteChunk was incorrectly forward declared - CDI_END_OFFSET is unused and the compiler was complaining about this - adds missing constant from tivadcc params. --- boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx | 5 ++++- src/openlcb/CompileCdiMain.cxx | 4 ++-- src/openlcb/StreamReceiverInterface.hxx | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx b/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx index e337017e5..1f5b0f68c 100644 --- a/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx +++ b/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx @@ -224,7 +224,10 @@ struct DccHwDefs { static const int RAILCOM_CUTOUT_POST_DELTA_USEC = -16; /// Adds this to the negative half after the railcom cutout is done. static const int RAILCOM_CUTOUT_POST_NEGATIVE_DELTA_USEC = -4; - + /// The DCC end of packet bit (after the cutout) has an asymmetry due to the + /// interrupt CPU latency, this constant tunes it to disappear. This time + /// gets deducted from the second half. + static const int RESYNC_DELAY_USEC = 7; /** These timer blocks will be synchronized once per packet, when the * deadband delay is set up. */ diff --git a/src/openlcb/CompileCdiMain.cxx b/src/openlcb/CompileCdiMain.cxx index 44d7f9542..2569b1d87 100644 --- a/src/openlcb/CompileCdiMain.cxx +++ b/src/openlcb/CompileCdiMain.cxx @@ -40,9 +40,9 @@ void render_cdi_helper(const CdiType &t, string ns, string name) printf("const char %s_DATA[] = R\"xmlpayload(%s)xmlpayload\";\n", name.c_str(), payload.c_str()); printf("extern const size_t %s_SIZE;\n", name.c_str()); - printf("const size_t %s_SIZE = sizeof(%s_DATA);\n", name.c_str(), + printf("extern const size_t %s_SIZE = sizeof(%s_DATA);\n", name.c_str(), name.c_str()); - printf("const size_t %s_END_OFFSET = %u;\n", name.c_str(), + printf("extern const size_t %s_END_OFFSET = %u;\n", name.c_str(), (unsigned)t.end_offset()); printf("\n} // namespace %s\n\n", ns.c_str()); } diff --git a/src/openlcb/StreamReceiverInterface.hxx b/src/openlcb/StreamReceiverInterface.hxx index f87eb1da5..fc7a743b3 100644 --- a/src/openlcb/StreamReceiverInterface.hxx +++ b/src/openlcb/StreamReceiverInterface.hxx @@ -41,7 +41,7 @@ template class FlowInterface; template class Buffer; -class ByteChunk; +struct ByteChunk; using ByteBuffer = Buffer; using ByteSink = FlowInterface; From d5062f635c9d29f225069dc60f1720274aa47e8a Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 23 Apr 2023 22:27:42 +0200 Subject: [PATCH 25/31] Deletes stm32f0-f3 SPIFFs driver. (#708) * Deletes stm32f0-f3 SPIFFs driver. The flash hardware in these MCUs does not support setting individual bits from 1 to 0. As such, SPIFFs will never work on this hardware (and really this code has never actually worked). * Deletes subdirectory that was removed. --- src/freertos_drivers/sources | 4 +- .../spiffs/stm32f0_f3/Stm32SPIFFS.cxx | 149 ------------------ .../spiffs/stm32f0_f3/Stm32SPIFFS.hxx | 88 ----------- .../spiffs/stm32f0_f3/spiffs_config.h | 1 - 4 files changed, 2 insertions(+), 240 deletions(-) delete mode 100644 src/freertos_drivers/spiffs/stm32f0_f3/Stm32SPIFFS.cxx delete mode 100644 src/freertos_drivers/spiffs/stm32f0_f3/Stm32SPIFFS.hxx delete mode 120000 src/freertos_drivers/spiffs/stm32f0_f3/spiffs_config.h diff --git a/src/freertos_drivers/sources b/src/freertos_drivers/sources index a570b39e6..0473a59b7 100644 --- a/src/freertos_drivers/sources +++ b/src/freertos_drivers/sources @@ -44,7 +44,7 @@ SUBDIRS += mbed_lpc1768 drivers_lpc1768 tivaware lpc_chip_175x_6x \ cc3220sdk net_cc3220 cc3220 \ net_freertos_tcp freertos_tcp ti_grlib \ spiffs_cc32x0sf spiffs_tm4c129 \ - spiffs_stm32f303xe spiffs_stm32f767xx \ + spiffs_stm32f767xx \ spiffs_spi \ #spiffs_tm4c123 \ @@ -60,7 +60,7 @@ endif ifeq ($(TARGET),freertos.armv7m.exc) SUBDIRS += stm32cubef303x_28x_58x_98x stm32cubef303xe stm32cubef767xx \ - spiffs_stm32f303xe spiffs_stm32f767xx \ + spiffs_stm32f767xx \ # Avoids exception handling from operator new. diff --git a/src/freertos_drivers/spiffs/stm32f0_f3/Stm32SPIFFS.cxx b/src/freertos_drivers/spiffs/stm32f0_f3/Stm32SPIFFS.cxx deleted file mode 100644 index 09e7bd15e..000000000 --- a/src/freertos_drivers/spiffs/stm32f0_f3/Stm32SPIFFS.cxx +++ /dev/null @@ -1,149 +0,0 @@ -/** @copyright - * Copyright (c) 2019, 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 Stm32SPIFFS.cxx - * - * This file implements a SPIFFS FLASH driver specific to STM32 F0..F3 flash - * API. - * - * @author Balazs Racz - * @date 13 July 2019 - */ - -#include "Stm32SPIFFS.hxx" -#include "spiffs.h" -#include "stm32f_hal_conf.hxx" - -// -// Stm32SPIFFS::flash_read() -// -int32_t Stm32SPIFFS::flash_read(uint32_t addr, uint32_t size, uint8_t *dst) -{ - HASSERT(addr >= fs_->cfg.phys_addr && - (addr + size) <= (fs_->cfg.phys_addr + fs_->cfg.phys_size)); - - memcpy(dst, (void *)addr, size); - - return 0; -} - -// -// Stm32SPIFFS::flash_write() -// -int32_t Stm32SPIFFS::flash_write(uint32_t addr, uint32_t size, uint8_t *src) -{ - union WriteWord - { - uint8_t data[8]; - uint16_t data_hword[4]; - uint32_t data_word[2]; - uint64_t data_dw; - }; - - HASSERT(addr >= fs_->cfg.phys_addr && - (addr + size) <= (fs_->cfg.phys_addr + fs_->cfg.phys_size)); - - HAL_FLASH_Unlock(); - - if (addr & 1) - { - // Unaligned write at the beginning. - WriteWord ww; - ww.data_hword[0] = 0xffff; - ww.data[1] = src[0]; - - HASSERT(HAL_OK == HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, - addr - 1, ww.data_dw)); - addr += 1; - size -= 1; - src += 1; - } - - while (size >= 8) - { - WriteWord ww; - memcpy(ww.data, src, 8); - HASSERT(HAL_OK == - HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addr, ww.data_dw)); - addr += 8; - size -= 8; - src += 8; - } - - while (size >= 2) - { - WriteWord ww; - memcpy(ww.data_hword, src, 2); - HASSERT(HAL_OK == - HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, addr, ww.data_dw)); - addr += 2; - size -= 2; - src += 2; - } - - if (size) - { - // Unaligned write at the end of the range. - HASSERT(size == 1); - HASSERT((addr & 1) == 0); - - WriteWord ww; - ww.data_hword[0] = 0xffff; - ww.data[0] = src[0]; - - HASSERT(HAL_OK == - HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, addr, ww.data_dw)); - addr += 1; - size -= 1; - src += 1; - } - - HAL_FLASH_Lock(); - - return 0; -} - -// -// Stm32SPIFFS::flash_erase() -// -int32_t Stm32SPIFFS::flash_erase(uint32_t addr, uint32_t size) -{ - HASSERT(addr >= fs_->cfg.phys_addr && - (addr + size) <= (fs_->cfg.phys_addr + fs_->cfg.phys_size)); - HASSERT((size % ERASE_PAGE_SIZE) == 0); - HASSERT((size % FLASH_PAGE_SIZE) == 0); - - FLASH_EraseInitTypeDef erase_init; - erase_init.TypeErase = FLASH_TYPEERASE_PAGES; - erase_init.PageAddress = (uint32_t)addr; - erase_init.NbPages = size / FLASH_PAGE_SIZE; - uint32_t page_error; - - HASSERT(HAL_OK == HAL_FLASHEx_Erase(&erase_init, &page_error)); - - return 0; -} - diff --git a/src/freertos_drivers/spiffs/stm32f0_f3/Stm32SPIFFS.hxx b/src/freertos_drivers/spiffs/stm32f0_f3/Stm32SPIFFS.hxx deleted file mode 100644 index a952f43bb..000000000 --- a/src/freertos_drivers/spiffs/stm32f0_f3/Stm32SPIFFS.hxx +++ /dev/null @@ -1,88 +0,0 @@ -/** @copyright - * Copyright (c) 2019, 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 Stm32SPIFFS.hxx - * - * This file implements a SPIFFS FLASH driver specific to STM32 F0..F3 flash - * API. - * - * @author Balazs Racz - * @date 13 July 2019 - */ - -#ifndef _FREERTOS_DRIVERS_SPIFFS_STM32F0_F3_STM32SPIFFS_HXX_ -#define _FREERTOS_DRIVERS_SPIFFS_STM32F0_F3_STM32SPIFFS_HXX_ - -#include - -#include "freertos_drivers/spiffs/SPIFFS.hxx" - -/// Specialization of Serial SPIFFS driver for Stm32 F0-F3 devices. -class Stm32SPIFFS : public SPIFFS -{ -public: - /// Constructor. - Stm32SPIFFS(size_t physical_address, size_t size_on_disk, - size_t logical_block_size, size_t logical_page_size, - size_t max_num_open_descriptors = 16, size_t cache_pages = 8, - std::function post_format_hook = nullptr) - : SPIFFS(physical_address, size_on_disk, ERASE_PAGE_SIZE, - logical_block_size, logical_page_size, - max_num_open_descriptors, cache_pages, post_format_hook) - { - } - - /// Destructor. - ~Stm32SPIFFS() - { - unmount(); - } - -private: - /// size of an erase page in FLASH - static constexpr size_t ERASE_PAGE_SIZE = 2 * 1024; - - /// SPIFFS callback to read flash, in context. - /// @param addr adddress location to read - /// @param size size of read in bytes - /// @param dst destination buffer for read - int32_t flash_read(uint32_t addr, uint32_t size, uint8_t *dst) override; - - /// SPIFFS callback to write flash, in context. - /// @param addr adddress location to write - /// @param size size of write in bytes - /// @param src source buffer for write - int32_t flash_write(uint32_t addr, uint32_t size, uint8_t *src) override; - - /// SPIFFS callback to erase flash, in context. - /// @param addr adddress location to erase - /// @param size size of erase region in bytes - int32_t flash_erase(uint32_t addr, uint32_t size) override; - - DISALLOW_COPY_AND_ASSIGN(Stm32SPIFFS); -}; - -#endif // _FREERTOS_DRIVERS_SPIFFS_STM32F0_F3_STM32SPIFFS_HXX_ diff --git a/src/freertos_drivers/spiffs/stm32f0_f3/spiffs_config.h b/src/freertos_drivers/spiffs/stm32f0_f3/spiffs_config.h deleted file mode 120000 index 96f2b5bad..000000000 --- a/src/freertos_drivers/spiffs/stm32f0_f3/spiffs_config.h +++ /dev/null @@ -1 +0,0 @@ -../spiffs_config.h \ No newline at end of file From b9207af85c1259dbf2e9af15f5094476c5da3e38 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 23 Apr 2023 22:39:05 +0200 Subject: [PATCH 26/31] Adds hex_to_string helper function to format_utils. (#707) * Adds hex_to_string helper function to format_utils. * Adds format utils test. * Fix whitespace. * Change API to return nonnegative integer. Fix review comment. --- src/utils/format_utils.cxx | 51 ++++++++++++++++++++++++++++++++++ src/utils/format_utils.cxxtest | 35 +++++++++++++++++++++++ src/utils/format_utils.hxx | 15 ++++++++++ 3 files changed, 101 insertions(+) create mode 100644 src/utils/format_utils.cxxtest diff --git a/src/utils/format_utils.cxx b/src/utils/format_utils.cxx index 9bed54be7..10138abb0 100644 --- a/src/utils/format_utils.cxx +++ b/src/utils/format_utils.cxx @@ -242,6 +242,57 @@ string string_to_hex(const string &arg) return ret; } +static uint8_t get_nibble(char b) +{ + if ('0' <= b && b <= '9') + { + return b - '0'; + } + if ('a' <= b && b <= 'f') + { + return b - 'a' + 10; + } + if ('A' <= b && b <= 'F') + { + return b - 'A' + 10; + } + return 0xff; +} + +size_t hex_to_string( + const char *input, size_t len, string *output, bool ignore_nonhex) +{ + uint8_t b = 0; // current byte + bool next_high = true; // next nibble is high of the byte + for (size_t ofs = 0; ofs < len; ++ofs) + { + uint8_t nib = get_nibble(input[ofs]); + if (nib == 0xff) + { + if (!ignore_nonhex) + { + return ofs; + } + continue; + } + b |= nib & 0xf; + if (next_high) + { + b <<= 4; + next_high = false; + continue; + } + else + { + output->push_back(b); + b = 0; + next_high = true; + continue; + } + } + return len; +} + string mac_to_string(uint8_t mac[6], char colon) { string ret; diff --git a/src/utils/format_utils.cxxtest b/src/utils/format_utils.cxxtest new file mode 100644 index 000000000..622e4a1da --- /dev/null +++ b/src/utils/format_utils.cxxtest @@ -0,0 +1,35 @@ +#include "utils/format_utils.hxx" + +#include "utils/test_main.hxx" + +TEST(HexToString, basic) +{ + string input = "303132"; + string output; + EXPECT_EQ(6u, hex_to_string(input.data(), input.size(), &output)); + EXPECT_EQ("012", output); + + output = "xx"; + EXPECT_EQ(6u, hex_to_string(input.data(), input.size(), &output)); + EXPECT_EQ("xx012", output); + + output.clear(); + input = "0A0BfC"; + EXPECT_EQ(6u, hex_to_string(input.data(), input.size(), &output)); + EXPECT_EQ("\x0a\x0b\xfc", output); + + output.clear(); + input = "30.31.32"; + EXPECT_EQ(2u, hex_to_string(input.data(), input.size(), &output)); + EXPECT_EQ("0", output); + + output.clear(); + EXPECT_EQ(8u, hex_to_string(input.data(), input.size(), &output, true)); + EXPECT_EQ("012", output); + + output.clear(); + input = "303132F"; + EXPECT_EQ(7u, hex_to_string(input.data(), input.size(), &output)); + // lost one nibble, this is ok according to the contract + EXPECT_EQ("012", output); +} diff --git a/src/utils/format_utils.hxx b/src/utils/format_utils.hxx index 80ed7ab78..2f3cbaf73 100644 --- a/src/utils/format_utils.hxx +++ b/src/utils/format_utils.hxx @@ -130,6 +130,21 @@ string int64_to_string_hex(int64_t value, unsigned padding = 0); /// original data. string string_to_hex(const string& arg); +/// Converts hex bytes to binary representation. +/// +/// @param input points to the input hexadecimal string +/// @param len how many bytes are there +/// @param output parsed byte data will be appended here +/// @param ignore_nonhex if true, jumps over all nonhex characters. If false, +/// stops at the first nonhex character. +/// +/// @return number of ascii bytes consumed from the input. For example 0 if the +/// first byte was not hex and ignore_nonhex=false. If the number of hex digits +/// is not even, there will be data loss. +/// +size_t hex_to_string( + const char *input, size_t len, string *output, bool ignore_nonhex = false); + /// Formats a MAC address to string. Works both for Ethernet addresses as well /// as for OpenLCB node IDs. /// From 3f6afa05da1080ef1b162df07cc089ffd0317d8e Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 25 Apr 2023 04:49:26 +0200 Subject: [PATCH 27/31] Adds STM32 RailcomDriver (#702) This driver is based on a fork of the TivaRailcomDriver; the hardware interaction is rewritten to use the STM32 HAL libraries and in part direct access to the USART peripheral. A major difference is that unlike the Tiva, the STM32 devices do not have a FIFO on the UART hardware. This makes it impossible to only perform reads from the UART data registers when the cutout is done. To work around this issue, the STM32 Railcom Driver uses DMA to copy the data from the UART hardware register into the buffer in the driver. This requires a free DMA channel for each railcom channel that is in use. Misc changes: - Implements toggle() in STM32 GPIO driver. - Adds a limit to how many buffers the RailcomToOpenlcbDebugProxy is willing to allocate. When we run out of buffers, we drop the packet to the floor. === * Rename includes and guard. * Fixes incorrect include for railcom implementation header. * Adds another instruction to libatomic. * Implements stm32 railcom driver (forked from the basis of tivarailcom). * Implements toggle() in stm32gpio. * Limits the number of buffers that the railcom debug port is willing to allocate. * Implements the STM32 railcom driver (this is a diff from the forked tiva railcom driver) * Support nullptr parent when using the constructor with more arguments. * Makes detecting a locomotive asymmetric, with fewer positive confirmations needed for reporting the address than how many empty packets are needed to report block empty. * Reserves 1 kbytes of space at the top of RAM for the interrupt stack. Clears this RAM to all zeros at the beginning of the run, so that we can verify that the heap did not grow into the interrupt stack. * Fix review comments including whitespace and hanging braces. switch minmax repeat counts to more intuitive definitions without double counting. --- boards/armv7m/target.ld | 4 +- src/dcc/RailcomBroadcastDecoder.cxx | 75 +++- src/dcc/RailcomBroadcastDecoder.hxx | 8 +- src/dcc/RailcomPortDebug.hxx | 18 +- src/freertos_drivers/common/RailcomImpl.hxx | 3 +- src/freertos_drivers/common/libatomic.c | 12 + src/freertos_drivers/st/Stm32Gpio.hxx | 6 + src/freertos_drivers/st/Stm32Railcom.hxx | 399 ++++++++++++-------- 8 files changed, 347 insertions(+), 178 deletions(-) diff --git a/boards/armv7m/target.ld b/boards/armv7m/target.ld index 3a4b351b4..15a2692e6 100644 --- a/boards/armv7m/target.ld +++ b/boards/armv7m/target.ld @@ -8,7 +8,7 @@ INCLUDE memory_map.ld __top_RAM = ORIGIN(RAM) + LENGTH(RAM); -__cs3_heap_end = __top_RAM; +__cs3_heap_end = __top_RAM - 1024; __start_ram = ORIGIN(RAM); __end_ram = __top_RAM; @@ -41,6 +41,8 @@ SECTIONS __bss_section_table = .; LONG( ADDR(.bss)); LONG( SIZEOF(.bss)); + LONG(__cs3_heap_end); + LONG(__top_RAM - __cs3_heap_end - 64); __bss_section_table_end = .; __section_table_end = . ; /* End of Global Section Table */ diff --git a/src/dcc/RailcomBroadcastDecoder.cxx b/src/dcc/RailcomBroadcastDecoder.cxx index 40aef122d..a9cc9b54e 100644 --- a/src/dcc/RailcomBroadcastDecoder.cxx +++ b/src/dcc/RailcomBroadcastDecoder.cxx @@ -48,15 +48,18 @@ bool RailcomBroadcastDecoder::process_packet(const dcc::Feedback &packet) { if (packet.ch1Size) { - return process_data(packet.ch1Data, packet.ch1Size); - } - else if (packet.ch2Size) - { - return process_data(packet.ch2Data, packet.ch2Size); + return process_data(packet.ch1Data, packet.ch1Size) && + (packet.ch2Size == 0); } else { - return true; // empty packet. + // No channel1 data. + notify_empty(); + if (!packet.ch2Size) + { + return true; // empty packet. + } + return false; } } @@ -65,13 +68,17 @@ bool RailcomBroadcastDecoder::process_data(const uint8_t *data, unsigned size) for (unsigned i = 0; i < size; ++i) { if (railcom_decode[data[i]] == RailcomDefs::INV) + { return true; // garbage. + } } /// TODO(balazs.racz) if we have only one byte in ch1 but we have a second /// byte in ch2, we should still process those because it might be a /// misaligned window. if (size < 2) - return true; // Dunno what this is.a + { + return true; // Dunno what this is. + } uint8_t type = (dcc::railcom_decode[data[0]] >> 2); if (size == 2) { @@ -81,17 +88,29 @@ bool RailcomBroadcastDecoder::process_data(const uint8_t *data, unsigned size) switch (type) { case dcc::RMOB_ADRLOW: - if (currentL_ == payload) { - if (countL_ < REPEAT_COUNT) ++countL_; - } else { + if (currentL_ == payload) + { + if (countL_ < MIN_EMPTY_COUNT) + { + countL_ += 2; + } + } + else + { currentL_ = payload; countL_ = 0; } break; case dcc::RMOB_ADRHIGH: - if (currentH_ == payload) { - if (countH_ < REPEAT_COUNT) ++countH_; - } else { + if (currentH_ == payload) + { + if (countH_ < MIN_EMPTY_COUNT) + { + countH_ += 2; + } + } + else + { currentH_ = payload; countH_ = 0; } @@ -99,7 +118,9 @@ bool RailcomBroadcastDecoder::process_data(const uint8_t *data, unsigned size) default: return false; // This is something we don't know about. } - if (countL_ >= REPEAT_COUNT && countH_ >= REPEAT_COUNT) { + if (countL_ >= (MIN_REPEAT_COUNT * 2) && + countH_ >= (MIN_REPEAT_COUNT * 2)) + { currentAddress_ = (uint16_t(currentH_) << 8) | currentL_; } return true; @@ -110,11 +131,27 @@ bool RailcomBroadcastDecoder::process_data(const uint8_t *data, unsigned size) } } -void RailcomBroadcastDecoder::set_occupancy(bool value) { - if (value) return; - if (countH_) --countH_; - if (countL_) --countL_; - if ((!countH_) || (!countL_)) { +void RailcomBroadcastDecoder::set_occupancy(bool value) +{ + if (value) + { + return; + } + notify_empty(); +} + +void RailcomBroadcastDecoder::notify_empty() +{ + if (countH_) + { + --countH_; + } + if (countL_) + { + --countL_; + } + if ((!countH_) || (!countL_)) + { currentAddress_ = 0; } } diff --git a/src/dcc/RailcomBroadcastDecoder.hxx b/src/dcc/RailcomBroadcastDecoder.hxx index acbfeb9a6..a63fa7943 100644 --- a/src/dcc/RailcomBroadcastDecoder.hxx +++ b/src/dcc/RailcomBroadcastDecoder.hxx @@ -86,9 +86,15 @@ private: /// bytes arethere to decode. @return dunno. bool process_data(const uint8_t *data, unsigned size); + /// Notifies the state machine that there is no occupancy detected. + void notify_empty(); + /// How many times we shall get the same data out of railcom before we /// believe it and report to the bus. - static const uint8_t REPEAT_COUNT = 3; + static const uint8_t MIN_REPEAT_COUNT = 3; + /// This is how many empty packets we need to forget the current address + /// when we're getting empty packets. + static const uint8_t MIN_EMPTY_COUNT = 8; uint8_t currentH_; ///< last received high address bits uint8_t currentL_; ///< last received low address bits diff --git a/src/dcc/RailcomPortDebug.hxx b/src/dcc/RailcomPortDebug.hxx index ef06c2d09..936af2735 100644 --- a/src/dcc/RailcomPortDebug.hxx +++ b/src/dcc/RailcomPortDebug.hxx @@ -36,6 +36,7 @@ #define _DCC_RAILCOMPORTDEBUG_HXX #include "dcc/RailcomHub.hxx" +#include "utils/LimitedPool.hxx" namespace dcc { @@ -213,14 +214,17 @@ public: RailcomToOpenLCBDebugProxy(dcc::RailcomHubFlow *parent, Node *node, dcc::RailcomHubPort *occupancy_port, bool ch1_enabled = true, bool ack_enabled = true) - : dcc::RailcomHubPort(parent->service()) + : dcc::RailcomHubPort(node->iface()) , parent_(parent) , node_(node) , occupancyPort_(occupancy_port) , ch1Enabled_(ch1_enabled) , ackEnabled_(ack_enabled) { - parent_->register_port(this); + if (parent_) + { + parent_->register_port(this); + } } RailcomToOpenLCBDebugProxy(Node *node) @@ -254,11 +258,15 @@ public: { return release_and_exit(); } + if (outputPool_.free_items() == 0) + { + return release_and_exit(); + } if (message()->data()->ch1Size && ch1Enabled_) { return allocate_and_call( node_->iface()->global_message_write_flow(), - STATE(ch1_msg_allocated)); + STATE(ch1_msg_allocated), &outputPool_); } else { @@ -285,13 +293,14 @@ public: Action maybe_send_ch2() { if (message()->data()->ch2Size && + outputPool_.free_items() != 0 && (ackEnabled_ || dcc::railcom_decode[message()->data()->ch2Data[0]] != dcc::RailcomDefs::ACK)) { return allocate_and_call( node_->iface()->global_message_write_flow(), - STATE(ch2_msg_allocated)); + STATE(ch2_msg_allocated), &outputPool_); } else { @@ -318,6 +327,7 @@ public: dcc::RailcomHubFlow *parent_{nullptr}; Node *node_; dcc::RailcomHubPort *occupancyPort_; + LimitedPool outputPool_{sizeof(Buffer), 20}; /// True if we should transmit channel1 data. uint8_t ch1Enabled_ : 1; /// True if we should transmit data that starts with an ACK. diff --git a/src/freertos_drivers/common/RailcomImpl.hxx b/src/freertos_drivers/common/RailcomImpl.hxx index ebf6fb56f..74ecf660f 100644 --- a/src/freertos_drivers/common/RailcomImpl.hxx +++ b/src/freertos_drivers/common/RailcomImpl.hxx @@ -57,9 +57,8 @@ #ifndef _FREERTOS_DRIVERS_COMMON_RAILCOMIMPL_HXX_ #define _FREERTOS_DRIVERS_COMMON_RAILCOMIMPL_HXX_ -#include "TivaDCC.hxx" // for FixedQueue - #include "freertos_drivers/common/RailcomDriver.hxx" +#include "freertos_drivers/common/FixedQueue.hxx" #include "dcc/RailCom.hxx" /* diff --git a/src/freertos_drivers/common/libatomic.c b/src/freertos_drivers/common/libatomic.c index 4e4b5d12c..bd7db1f98 100644 --- a/src/freertos_drivers/common/libatomic.c +++ b/src/freertos_drivers/common/libatomic.c @@ -46,6 +46,18 @@ /// Restores the interrupte enable flag from a register. #define REL_LOCK() __asm volatile(" msr PRIMASK, %0\n " : : "r"(_pastlock)); +/// __atomic_fetch_add_1 +/// +/// This function is needed for GCC-generated code. +uint8_t __atomic_fetch_add_1(uint8_t *ptr, uint8_t val, int memorder) +{ + ACQ_LOCK(); + uint16_t ret = *ptr; + *ptr += val; + REL_LOCK(); + return ret; +} + /// __atomic_fetch_sub_2 /// /// This function is needed for GCC-generated code. diff --git a/src/freertos_drivers/st/Stm32Gpio.hxx b/src/freertos_drivers/st/Stm32Gpio.hxx index 868532010..717d65e23 100644 --- a/src/freertos_drivers/st/Stm32Gpio.hxx +++ b/src/freertos_drivers/st/Stm32Gpio.hxx @@ -88,6 +88,12 @@ template struct Stm32GpioDefs return port()->IDR & pin(); } + /// Changes the output pin level. + static void toggle() + { + set(!get()); + } + /// @return a os-indepentent Gpio abstraction instance for use in /// libraries. static constexpr const Gpio *instance() diff --git a/src/freertos_drivers/st/Stm32Railcom.hxx b/src/freertos_drivers/st/Stm32Railcom.hxx index da84c510d..54719c519 100644 --- a/src/freertos_drivers/st/Stm32Railcom.hxx +++ b/src/freertos_drivers/st/Stm32Railcom.hxx @@ -1,5 +1,5 @@ /** \copyright - * Copyright (c) 2014, Balazs Racz + * Copyright (c) 2023, Balazs Racz * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,57 +24,85 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * - * \file TivaRailcom.hxx + * \file Stm32Railcom.hxx * - * Device driver for TivaWare to read one or more UART inputs for railcom data. - * - * usage: - * - * struct MyRailcomPins { - * // add all definitions from the example RailcomHw here - * }; - * // Some definitions need to go outside of the class. - * // no need for attribute weak if this is in a .cxx file. - * const uint32_t MyRailcomPins::UART_BASE[] __attribute__((weak)) = {...}; - * ... - * - * TivaRailcomDriver railcom_driver("/dev/railcom"); - * - * // assuming you put OS_INTERRRUPT = INT_UART3 into the structure. - * void uart3_interrupt_handler() { - * railcom_driver.os_interrrupt(); - * } - * - * Then open /dev/railcom and read dcc::Feedback structures from it. Each read - * much be exactly sizeof(dcc::Feedback) length. If there are no more packets to - * read, you'll get return=0, errno==EAGAIN. Add an ioctl CAN_READ_ACTIVE to - * get a notification when there is something to read. + * Device driver for STM32 chips to read one or more UART inputs for railcom + * data. * * @author Balazs Racz - * @date 6 Jan 2015 + * @date 6 Mar 2023 */ -#ifndef _FREERTOS_DRIVERS_TI_TIVARAILCOM_HXX_ -#define _FREERTOS_DRIVERS_TI_TIVARAILCOM_HXX_ +#ifndef _FREERTOS_DRIVERS_ST_STM32RAILCOM_HXX_ +#define _FREERTOS_DRIVERS_ST_STM32RAILCOM_HXX_ -#if (!defined(TIVADCC_TIVA)) && (!defined(TIVADCC_CC3200)) -#error must define either TIVADCC_TIVA or TIVADCC_CC3200 -#endif +#include "stm32f_hal_conf.hxx" #include "freertos_drivers/common/RailcomImpl.hxx" -#include "dcc/RailCom.hxx" + +#if defined(STM32F072xB) || defined(STM32F091xC) +#include "stm32f0xx_ll_dma.h" +#include "stm32f0xx_ll_usart.h" +#elif defined(STM32F103xB) +#include "stm32f1xx_ll_dma.h" +#include "stm32f1xx_ll_usart.h" +#elif defined(STM32F303xC) || defined(STM32F303xE) +#include "stm32f3xx_ll_dma.h" +#include "stm32f3xx_ll_usart.h" +#elif defined(STM32L431xx) || defined(STM32L432xx) +#include "stm32l4xx_ll_dma.h" +#include "stm32l4xx_ll_usart.h" +#elif defined(STM32F767xx) +#include "stm32f7xx_ll_dma.h" +#include "stm32f7xx_ll_usart.h" +#else +#error Dont know what STM32 chip you have. +#endif + +/// This struct helps casting the UART base addresses to the appropriate +/// type. It can be allocated in read-only memory (flash) as a static array. It +/// can be passed to the STM32 HAL macros as well. +struct RailcomUartHandle +{ + union + { + /// Initialized by the constants from the processor header like + /// USART1_BASE. + uint32_t baseAddress; + /// Use this to access the registers. + USART_TypeDef *Instance; + }; +}; + +/// This struct helps casting the DMA channel base addresses to the appropriate +/// type. It can be allocated in read-only memory (flash) as a static array. +struct RailcomDmaChannel +{ + union + { + /// Initialized by the constants from the processor header like + /// USART1_BASE. + uint32_t baseAddress; + /// Use this to access the registers. + DMA_Channel_TypeDef *Instance; + }; +}; /* struct RailcomHw { static const uint32_t CHANNEL_COUNT = 4; - static const uint32_t UART_PERIPH[CHANNEL_COUNT]; - static const uint32_t UART_BASE[CHANNEL_COUNT]; + static const RailcomUartHandle UART[CHANNEL_COUNT]; + + // If DMA channel routing is in use, that has to be set up externally + // (e.g. in hw_preinit). + static const RailcomDmaChannel DMA[CHANNEL_COUNT]; + // Make sure there are enough entries here for all the channels times a few // DCC packets. - static const uint32_t Q_SIZE = 16; + static const uint32_t Q_SIZE = 32; - static const auto OS_INTERRUPT = INT_UART2; + static const auto OS_INTERRUPT = USART2_IRQn; GPIO_HWPIN(CH1, GpioHwPin, C, 4, U4RX); GPIO_HWPIN(CH2, GpioHwPin, C, 6, U3RX); @@ -112,34 +140,47 @@ struct RailcomHw if (!CH4_Pin::get()) ret |= 8; return ret; } -}; +}; // The weak attribute is needed if the definition is put into a header file. -const uint32_t RailcomHw::UART_BASE[] __attribute__((weak)) = {UART4_BASE, UART3_BASE, UART2_BASE, UART7_BASE}; -const uint32_t RailcomHw::UART_PERIPH[] -__attribute__((weak)) = {SYSCTL_PERIPH_UART4, SYSCTL_PERIPH_UART3, SYSCTL_PERIPH_UART2, SYSCTL_PERIPH_UART7}; +const uint32_t RailcomHw::UART[] __attribute__((weak)) = { USART4_BASE, +USART1_BASE, USART3_BASE, USART2_BASE }; -*/ +const RailcomDmaChannel RailcomHw::DMA[] __attribute__((weak)) = { +DMA1_Channel1_BASE, DMA1_Channel3_BASE, DMA1_Channel6_BASE, DMA1_Channel5_BASE +}; + +In hw_preinit(), we have this for DMA channel routing: + //DMA channel routing for railcom UARTs + MODIFY_REG(DMA1->CSELR, DMA_CSELR_C1S_Msk, DMA1_CSELR_CH1_USART4_RX); + MODIFY_REG(DMA1->CSELR, DMA_CSELR_C3S_Msk, DMA1_CSELR_CH3_USART1_RX); + MODIFY_REG(DMA1->CSELR, DMA_CSELR_C6S_Msk, DMA1_CSELR_CH3_USART3_RX); + MODIFY_REG(DMA1->CSELR, DMA_CSELR_C5S_Msk, DMA1_CSELR_CH3_USART2_RX); -/// Railcom driver for TI Tiva-class microcontrollers using the TivaWare -/// peripheral library. +*/ +/// Railcom driver for STM32-class microcontrollers using the HAL middleware +/// library. /// /// This railcom driver supports parallel polling of multiple UART channels for -/// the railcom data. -template class TivaRailcomDriver : public RailcomDriverBase +/// the railcom data. +template class Stm32RailcomDriver : public RailcomDriverBase { public: /// Constructor. @param path is the device node path (e.g. "/dev/railcom0"). - TivaRailcomDriver(const char *path) + Stm32RailcomDriver(const char *path) : RailcomDriverBase(path) { - MAP_IntPrioritySet(HW::OS_INTERRUPT, configKERNEL_INTERRUPT_PRIORITY); - MAP_IntEnable(HW::OS_INTERRUPT); +#ifdef GCC_CM3 + SetInterruptPriority(HW::OS_INTERRUPT, configKERNEL_INTERRUPT_PRIORITY); +#else + SetInterruptPriority(HW::OS_INTERRUPT, 0xff); +#endif + HAL_NVIC_EnableIRQ(HW::OS_INTERRUPT); } - ~TivaRailcomDriver() + ~Stm32RailcomDriver() { - MAP_IntDisable(HW::OS_INTERRUPT); + HAL_NVIC_DisableIRQ(HW::OS_INTERRUPT); } private: @@ -148,182 +189,238 @@ private: using RailcomDriverBase::returnedPackets_; + /// @param i channel number (0..HW::NUM_CHANNEL) + /// @return uart hardware instance for channel i. + static constexpr USART_TypeDef *uart(unsigned i) + { + return HW::UART[i].Instance; + } + + /// @param i channel number (0..HW::NUM_CHANNEL) + /// @return dma channel instance for channel i. + static constexpr DMA_Channel_TypeDef *dma_ch(unsigned i) + { + return HW::DMA[i].Instance; + } + /// Sets a given software interrupt pending. /// @param int_nr interrupt number (will be HW::OS_INTERRUPT) void int_set_pending(unsigned int_nr) override { - MAP_IntPendSet(int_nr); + HAL_NVIC_SetPendingIRQ((IRQn_Type)int_nr); } // File node interface void enable() OVERRIDE { - for (unsigned i = 0; i < ARRAYSIZE(HW::UART_BASE); ++i) + for (unsigned i = 0; i < HW::CHANNEL_COUNT; ++i) { -#ifdef TIVADCC_TIVA - MAP_SysCtlPeripheralEnable(HW::UART_PERIPH[i]); -#elif defined(TIVADCC_CC3200) - MAP_PRCMPeripheralClkEnable(HW::UART_PERIPH[i], PRCM_RUN_MODE_CLK); -#endif - MAP_UARTConfigSetExpClk(HW::UART_BASE[i], cm3_cpu_clock_hz, 250000, - UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | - UART_CONFIG_PAR_NONE); - MAP_UARTFIFOEnable(HW::UART_BASE[i]); + UART_HandleTypeDef uart_handle; + memset(&uart_handle, 0, sizeof(uart_handle)); + uart_handle.Init.BaudRate = 250000; + uart_handle.Init.WordLength = UART_WORDLENGTH_8B; + uart_handle.Init.StopBits = UART_STOPBITS_1; + uart_handle.Init.Parity = UART_PARITY_NONE; + uart_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; + uart_handle.Init.Mode = UART_MODE_RX; + uart_handle.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; + uart_handle.Instance = uart(i); + HAL_UART_DeInit(&uart_handle); + volatile auto ret = HAL_UART_Init(&uart_handle); + HASSERT(HAL_OK == ret); + // Disables the receiver. - HWREGBITW(HW::UART_BASE[i] + UART_O_CTL, UART_CTL_RXE) = 0; - MAP_UARTEnable(HW::UART_BASE[i]); + LL_USART_SetTransferDirection(uart(i), LL_USART_DIRECTION_NONE); + + // configure DMA + + // peripheral address + dma_ch(i)->CPAR = (uint32_t)(&uart(i)->RDR); + + // memory address and num transfers not set, these will come from + // each cutout. + // dma_ch->CMAR = (uint32_t)adcSamplesRaw_; + // dma_ch->CNDTR = NUM_SAMPLES * NUM_CHANNELS; + + uint32_t config = LL_DMA_DIRECTION_PERIPH_TO_MEMORY | + LL_DMA_MODE_NORMAL | LL_DMA_PERIPH_NOINCREMENT | + LL_DMA_MEMORY_INCREMENT | LL_DMA_PDATAALIGN_BYTE | + LL_DMA_MDATAALIGN_BYTE | LL_DMA_PRIORITY_HIGH; + MODIFY_REG(dma_ch(i)->CCR, + DMA_CCR_DIR | DMA_CCR_MEM2MEM | DMA_CCR_CIRC | DMA_CCR_PINC | + DMA_CCR_MINC | DMA_CCR_PSIZE | DMA_CCR_MSIZE | DMA_CCR_PL | + DMA_CCR_TCIE | DMA_CCR_HTIE | DMA_CCR_TEIE, + config); + + LL_USART_Enable(uart(i)); } } - void disable() OVERRIDE + void disable() override { - for (unsigned i = 0; i < ARRAYSIZE(HW::UART_BASE); ++i) + for (unsigned i = 0; i < HW::CHANNEL_COUNT; ++i) { - MAP_UARTDisable(HW::UART_BASE[i]); + LL_USART_Disable(uart(i)); } } // RailcomDriver interface - void feedback_sample() OVERRIDE { + void feedback_sample() override + { HW::enable_measurement(true); this->add_sample(HW::sample()); HW::disable_measurement(); } - void start_cutout() OVERRIDE + void start_cutout() override { HW::enable_measurement(false); - const bool need_ch1_cutout = HW::need_ch1_cutout() || (this->feedbackKey_ < 11000); + const bool need_ch1_cutout = + HW::need_ch1_cutout() || (this->feedbackKey_ < 11000); Debug::RailcomRxActivate::set(true); - for (unsigned i = 0; i < ARRAYSIZE(HW::UART_BASE); ++i) + for (unsigned i = 0; i < HW::CHANNEL_COUNT; ++i) { - if (need_ch1_cutout) + while (LL_USART_IsActiveFlag_RXNE(uart(i))) + { + uint8_t data = uart(i)->RDR; + (void)data; + } + returnedPackets_[i] = this->alloc_new_packet(i); + if (need_ch1_cutout && returnedPackets_[i]) { - HWREG(HW::UART_BASE[i] + UART_O_CTL) |= UART_CTL_RXE; + LL_USART_EnableDMAReq_RX(uart(i)); + LL_USART_SetTransferDirection(uart(i), LL_USART_DIRECTION_RX); + LL_USART_ClearFlag_FE(uart(i)); + LL_USART_Enable(uart(i)); + + dma_ch(i)->CNDTR = 2; + dma_ch(i)->CMAR = (uint32_t)returnedPackets_[i]->ch1Data; + dma_ch(i)->CCR |= DMA_CCR_EN; // enable DMA } - // HWREGBITW(HW::UART_BASE[i] + UART_O_CTL, UART_CTL_RXE) = - // UART_CTL_RXE; - // flush fifo - while (MAP_UARTCharGetNonBlocking(HW::UART_BASE[i]) >= 0) - ; - returnedPackets_[i] = 0; } Debug::RailcomDriverCutout::set(true); } - void middle_cutout() OVERRIDE + void middle_cutout() override { Debug::RailcomDriverCutout::set(false); - for (unsigned i = 0; i < ARRAYSIZE(HW::UART_BASE); ++i) + for (unsigned i = 0; i < HW::CHANNEL_COUNT; ++i) { - while (MAP_UARTCharsAvail(HW::UART_BASE[i])) + if (!returnedPackets_[i]) + { + continue; + } + LL_USART_Disable(uart(i)); + CLEAR_BIT(dma_ch(i)->CCR, DMA_CCR_EN); + // How many bytes did we transfer? + returnedPackets_[i]->ch1Size = 2 - dma_ch(i)->CNDTR; + if (returnedPackets_[i]->ch1Size == 1) + { + Debug::RailcomError::toggle(); + } + if (returnedPackets_[i]->ch1Size) { Debug::RailcomDataReceived::toggle(); Debug::RailcomAnyData::set(true); - if (!returnedPackets_[i]) - { - returnedPackets_[i] = this->alloc_new_packet(i); - } - if (!returnedPackets_[i]) - { - break; - } - long data = MAP_UARTCharGetNonBlocking(HW::UART_BASE[i]); - if (data < 0 || data > 0xff) { - // We reset the receiver circuitry because we got an error - // in channel 1. Typical cause of this error is if there - // are multiple locomotives on the block (e.g. if this is - // the global detector) and they talk over each other - // during ch1 broadcast. There is a good likelihood that - // the asynchronous receiver is out of sync with the - // transmitter, but right now we should be in the - // between-byte space. - HWREG(HW::UART_BASE[i] + UART_O_CTL) &= ~UART_CTL_RXE; - Debug::RailcomError::toggle(); - returnedPackets_[i]->add_ch1_data(0xF8 | ((data >> 8) & 0x7)); - continue; - } - returnedPackets_[i]->add_ch1_data(data); } - HWREG(HW::UART_BASE[i] + UART_O_CTL) |= UART_CTL_RXE; + + if (LL_USART_IsActiveFlag_FE(uart(i))) + { + // We reset the receiver circuitry because we got an error + // in channel 1. Typical cause of this error is if there + // are multiple locomotives on the block (e.g. if this is + // the global detector) and they talk over each other + // during ch1 broadcast. There is a good likelihood that + // the asynchronous receiver is out of sync with the + // transmitter, but right now we should be in the + // between-byte space. + LL_USART_SetTransferDirection(uart(i), LL_USART_DIRECTION_NONE); + LL_USART_ClearFlag_FE(uart(i)); + Debug::RailcomError::toggle(); + // Not a valid railcom byte. + returnedPackets_[i]->ch1Data[0] = 0xF8; + returnedPackets_[i]->ch1Size = 1; + } + + // Set up channel 2 reception with DMA. + dma_ch(i)->CNDTR = 6; + dma_ch(i)->CMAR = (uint32_t)returnedPackets_[i]->ch2Data; + dma_ch(i)->CCR |= DMA_CCR_EN; // enable DMA + + LL_USART_SetTransferDirection(uart(i), LL_USART_DIRECTION_RX); + LL_USART_ClearFlag_FE(uart(i)); + LL_USART_Enable(uart(i)); } HW::middle_cutout_hook(); Debug::RailcomDriverCutout::set(true); } - void end_cutout() OVERRIDE + void end_cutout() override { HW::disable_measurement(); bool have_packets = false; - for (unsigned i = 0; i < ARRAYSIZE(HW::UART_BASE); ++i) + for (unsigned i = 0; i < HW::CHANNEL_COUNT; ++i) { - while (MAP_UARTCharsAvail(HW::UART_BASE[i])) + LL_USART_Disable(uart(i)); + if (!returnedPackets_[i]) + { + continue; + } + CLEAR_BIT(dma_ch(i)->CCR, DMA_CCR_EN); + // How many bytes did we transfer? + returnedPackets_[i]->ch2Size = 6 - dma_ch(i)->CNDTR; + if (returnedPackets_[i]->ch2Size) { Debug::RailcomDataReceived::toggle(); Debug::RailcomAnyData::set(true); Debug::RailcomCh2Data::set(true); - if (!returnedPackets_[i]) - { - returnedPackets_[i] = this->alloc_new_packet(i); - } - if (!returnedPackets_[i]) + } + if (LL_USART_IsActiveFlag_FE(uart(i))) + { + Debug::RailcomError::toggle(); + LL_USART_ClearFlag_FE(uart(i)); + if (returnedPackets_[i]->ch2Size < 6) { - break; + returnedPackets_[i]->add_ch2_data(0xF8); } - long data = MAP_UARTCharGetNonBlocking(HW::UART_BASE[i]); - if (data < 0 || data > 0xff) { - Debug::RailcomError::toggle(); - returnedPackets_[i]->add_ch2_data(0xF8 | ((data >> 8) & 0x7)); - continue; - } - if (data == 0xE0) { - Debug::RailcomE0::toggle(); - } - returnedPackets_[i]->add_ch2_data(data); - } - HWREG(HW::UART_BASE[i] + UART_O_CTL) &= ~UART_CTL_RXE; - Debug::RailcomRxActivate::set(false); - //HWREGBITW(HW::UART_BASE[i] + UART_O_CTL, UART_CTL_RXE) = 0; - if (returnedPackets_[i]) { - have_packets = true; - this->feedbackQueue_.commit_back(); - Debug::RailcomPackets::toggle(); - returnedPackets_[i] = nullptr; - MAP_IntPendSet(HW::OS_INTERRUPT); } + + LL_USART_SetTransferDirection(uart(i), LL_USART_DIRECTION_NONE); + + Debug::RailcomPackets::toggle(); + have_packets = true; + this->feedbackQueue_.commit_back(); + returnedPackets_[i] = nullptr; + HAL_NVIC_SetPendingIRQ(HW::OS_INTERRUPT); } + Debug::RailcomRxActivate::set(false); if (!have_packets) { // Ensures that at least one feedback packet is sent back even when // it is with no railcom payload. - auto *p = this->alloc_new_packet(0); + auto *p = this->alloc_new_packet(15); if (p) { this->feedbackQueue_.commit_back(); Debug::RailcomPackets::toggle(); - MAP_IntPendSet(HW::OS_INTERRUPT); + HAL_NVIC_SetPendingIRQ(HW::OS_INTERRUPT); } } Debug::RailcomCh2Data::set(false); Debug::RailcomDriverCutout::set(false); } - void no_cutout() OVERRIDE + void no_cutout() override { - for (unsigned i = 0; i < ARRAYSIZE(HW::UART_BASE); ++i) + // Ensures that at least one feedback packet is sent back even when + // it is with no railcom payload. + auto *p = this->alloc_new_packet(15); + if (p) { - if (!returnedPackets_[i]) - { - returnedPackets_[i] = this->alloc_new_packet(i); - } - if (returnedPackets_[i]) - { - this->feedbackQueue_.commit_back(); - Debug::RailcomPackets::toggle(); - returnedPackets_[i] = nullptr; - MAP_IntPendSet(HW::OS_INTERRUPT); - } + this->feedbackQueue_.commit_back(); + Debug::RailcomPackets::toggle(); + HAL_NVIC_SetPendingIRQ(HW::OS_INTERRUPT); } } }; -#endif // _FREERTOS_DRIVERS_TI_TIVARAILCOM_HXX_ +#endif // _FREERTOS_DRIVERS_ST_STM32RAILCOM_HXX_ From db89362bc34c67df8f452132c5bf85c8fb2d24e9 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 25 Apr 2023 04:52:37 +0200 Subject: [PATCH 28/31] Refactors the flash writing/erasing code for TI flash into a separate class. (#710) This class interface allows using FlashFile for non-filesystem flash files. --- .../spiffs/cc32x0sf/TiSPIFFSImpl.hxx | 190 +------------ src/freertos_drivers/ti/TiFlash.hxx | 267 ++++++++++++++++++ 2 files changed, 271 insertions(+), 186 deletions(-) create mode 100644 src/freertos_drivers/ti/TiFlash.hxx diff --git a/src/freertos_drivers/spiffs/cc32x0sf/TiSPIFFSImpl.hxx b/src/freertos_drivers/spiffs/cc32x0sf/TiSPIFFSImpl.hxx index 19b4d9222..3d96fe26b 100644 --- a/src/freertos_drivers/spiffs/cc32x0sf/TiSPIFFSImpl.hxx +++ b/src/freertos_drivers/spiffs/cc32x0sf/TiSPIFFSImpl.hxx @@ -36,108 +36,10 @@ #include "utils/logging.h" #include "TiSPIFFS.hxx" - +#include "freertos_drivers/ti/TiFlash.hxx" #include "spiffs.h" -#ifdef TI_DUAL_BANK_FLASH - -/// Flash configuration register. -#define FLASH_CONF 0x400FDFC8 -/// This bit in the FLASH_CONF register means that the banks are reversed by -/// their address mapping. -#define FCMME 0x40000000 -/// This value defines up to one bit that needs to be XOR-ed to the flash -/// address before calling the flash APIs to cover for reversed flash banks. -static const unsigned addr_mirror = (HWREG(FLASH_CONF) & FCMME) ? 0x80000 : 0; - -#else - -static constexpr unsigned addr_mirror = 0; - -#endif // Dual bank flash - -// Different options for what to set for flash write locking. These are only -// needed to debug when the operating system is misbehaving during flash -// writes. - -#if defined(TISPIFFS_LOCK_ALL_INTERRUPTS) - -// Global disable interrupts. - -#define DI() asm("cpsid i\n") -#define EI() asm("cpsie i\n") - -#elif defined(TISPIFFS_LOCK_CRITICAL) - -// Critical section (interrupts better than MIN_SYSCALL_PRIORITY are still -// running). - -#define DI() portENTER_CRITICAL() -#define EI() portEXIT_CRITICAL() - -#elif defined(TISPIFFS_LOCK_BASEPRI_FF) - -// Disable interrupts with a priority limit of 0xFF (these are the lowest -// priority interrupts, including the FreeRTOS kernel task switch interrupt). -unsigned ppri; -constexpr unsigned minpri = 0xFF; -#define DI() \ - do \ - { \ - unsigned r; \ - __asm volatile(" mrs %0, basepri\n mov %1, %2\n msr basepri, %1\n" \ - : "=r"(ppri), "=r"(r) \ - : "i"(minpri) \ - : "memory"); \ - } while (0) -#define EI() __asm volatile(" msr basepri, %0\n" : : "r"(ppri) : "memory") - -#elif defined(TISPIFFS_LOCK_NOTICK) - -// Disable the systick timer to prevent preemptive multi-tasking from changing -// to a different task. - -static constexpr unsigned SYSTICKCFG = 0xE000E010; -#define DI() HWREG(SYSTICKCFG) &= ~2; -#define EI() HWREG(SYSTICKCFG) |= 2; - -#elif defined(TISPIFFS_LOCK_SCHEDULER_SUSPEND) - -// Disable freertos scheduler - -#define DI() vTaskSuspendAll() -#define EI() xTaskResumeAll() - -#elif defined(TISPIFFS_LOCK_NONE) -// No write locking. - -#define DI() -#define EI() - -#elif defined(TISPIFFS_LOCK_CRASH) - -// Crashes if two different executions of this locking mechanism are -// concurrent. - -unsigned pend = 0; -#define DI() HASSERT(pend==0); pend=1; -#define EI() pend=0; - -#else -#error Must specify what kind of locking to use for TISPIFFS. -#endif - -// This ifdef decides whether we use the ROM or the flash based implementations -// for Flash write and erase. It also supports correcting for the reversed bank -// addresses. -#if 1 -#define FPG(data, addr, size) ROM_FlashProgram(data, (addr) ^ addr_mirror, size) -#define FER(addr) ROM_FlashErase((addr) ^ addr_mirror) -#else -#define FPG(data, addr, size) FlashProgram(data, (addr) ^ addr_mirror, size) -#define FER(addr) FlashErase((addr) ^ addr_mirror) -#endif // // TiSPIFFS::flash_read() @@ -148,7 +50,7 @@ int32_t TiSPIFFS::flash_read(uint32_t addr, uint32_t size, uint HASSERT(addr >= fs_->cfg.phys_addr && (addr + size) <= (fs_->cfg.phys_addr + fs_->cfg.phys_size)); - memcpy(dst, (void*)addr, size); + TiFlash::read(addr, dst, size); return 0; } @@ -160,85 +62,8 @@ template int32_t TiSPIFFS::flash_write(uint32_t addr, uint32_t size, uint8_t *src) { LOG(INFO, "Write %x sz %d", (unsigned)addr, (unsigned)size); - union WriteWord - { - uint8_t data[4]; - uint32_t data_word; - }; - - HASSERT(addr >= fs_->cfg.phys_addr && - (addr + size) <= (fs_->cfg.phys_addr + fs_->cfg.phys_size)); - - if ((addr % 4) && ((addr % 4) + size) < 4) - { - // single unaligned write in the middle of a word. - WriteWord ww; - ww.data_word = 0xFFFFFFFF; - - memcpy(ww.data + (addr % 4), src, size); - ww.data_word &= *((uint32_t*)(addr & (~0x3))); - DI(); - HASSERT(FPG(&ww.data_word, addr & (~0x3), 4) == 0); - EI(); - LOG(INFO, "Write done1"); - return 0; - } - - int misaligned = (addr + size) % 4; - if (misaligned != 0) - { - // last write unaligned data - WriteWord ww; - ww.data_word = 0xFFFFFFFF; - - memcpy(&ww.data_word, src + size - misaligned, misaligned); - ww.data_word &= *((uint32_t*)((addr + size) & (~0x3))); - DI(); - HASSERT(FPG(&ww.data_word, (addr + size) & (~0x3), 4) == 0); - EI(); - - size -= misaligned; - } - - misaligned = addr % 4; - if (size && misaligned != 0) - { - // first write unaligned data - WriteWord ww; - ww.data_word = 0xFFFFFFFF; - - memcpy(ww.data + misaligned, src, 4 - misaligned); - ww.data_word &= *((uint32_t*)(addr & (~0x3))); - DI(); - HASSERT(FPG(&ww.data_word, addr & (~0x3), 4) == 0); - EI(); - addr += 4 - misaligned; - size -= 4 - misaligned; - src += 4 - misaligned; - } - - HASSERT((addr % 4) == 0); - HASSERT((size % 4) == 0); - - if (size) - { - // the rest of the aligned data - uint8_t *flash = (uint8_t*)addr; - for (uint32_t i = 0; i < size; i += 4) - { - src[i + 0] &= flash[i + 0]; - src[i + 1] &= flash[i + 1]; - src[i + 2] &= flash[i + 2]; - src[i + 3] &= flash[i + 3]; - } - - DI(); - HASSERT(FPG((unsigned long *)src, addr, size) == 0); - EI(); - } - - LOG(INFO, "Write done2"); + TiFlash::write(addr, src, size); return 0; } @@ -253,14 +78,7 @@ int32_t TiSPIFFS::flash_erase(uint32_t addr, uint32_t size) (addr + size) <= (fs_->cfg.phys_addr + fs_->cfg.phys_size)); HASSERT((size % ERASE_PAGE_SIZE) == 0); - while (size) - { - DI(); - HASSERT(FER(addr) == 0); - EI(); - addr += ERASE_PAGE_SIZE; - size -= ERASE_PAGE_SIZE; - } + TiFlash::erase(addr, size); LOG(INFO, "Erasing %x done", (unsigned)addr); return 0; diff --git a/src/freertos_drivers/ti/TiFlash.hxx b/src/freertos_drivers/ti/TiFlash.hxx new file mode 100644 index 000000000..0d3c82dd3 --- /dev/null +++ b/src/freertos_drivers/ti/TiFlash.hxx @@ -0,0 +1,267 @@ +/** \copyright + * Copyright (c) 2023, 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 TiFlash.hxx + * + * Implementation for TI internal flash for Tiva and CC32xxSF devices. This + * class is intended to be used by other device drivers. + * + * @author Balazs Racz + * @date 23 Apr 2023 + */ + +#ifndef _FREERTOS_DRIVERS_TI_TIFLASH_HXX_ +#define _FREERTOS_DRIVERS_TI_TIFLASH_HXX_ + +#ifdef TI_DUAL_BANK_FLASH + +/// Flash configuration register. +#define FLASH_CONF 0x400FDFC8 +/// This bit in the FLASH_CONF register means that the banks are reversed by +/// their address mapping. +#define FCMME 0x40000000 +/// This value defines up to one bit that needs to be XOR-ed to the flash +/// address before calling the flash APIs to cover for reversed flash banks. +static const unsigned addr_mirror = (HWREG(FLASH_CONF) & FCMME) ? 0x80000 : 0; + +#else + +static constexpr unsigned addr_mirror = 0; + +#endif // Dual bank flash + +// Different options for what to set for flash write locking. These are only +// needed to debug when the operating system is misbehaving during flash +// writes. + +#if defined(TISPIFFS_LOCK_ALL_INTERRUPTS) + +// Global disable interrupts. + +#define DI() asm("cpsid i\n") +#define EI() asm("cpsie i\n") + +#elif defined(TISPIFFS_LOCK_CRITICAL) + +// Critical section (interrupts better than MIN_SYSCALL_PRIORITY are still +// running). + +#define DI() portENTER_CRITICAL() +#define EI() portEXIT_CRITICAL() + +#elif defined(TISPIFFS_LOCK_BASEPRI_FF) + +// Disable interrupts with a priority limit of 0xFF (these are the lowest +// priority interrupts, including the FreeRTOS kernel task switch interrupt). +unsigned ppri; +constexpr unsigned minpri = 0xFF; +#define DI() \ + do \ + { \ + unsigned r; \ + __asm volatile(" mrs %0, basepri\n mov %1, %2\n msr basepri, %1\n" \ + : "=r"(ppri), "=r"(r) \ + : "i"(minpri) \ + : "memory"); \ + } while (0) +#define EI() __asm volatile(" msr basepri, %0\n" : : "r"(ppri) : "memory") + +#elif defined(TISPIFFS_LOCK_NOTICK) + +// Disable the systick timer to prevent preemptive multi-tasking from changing +// to a different task. + +static constexpr unsigned SYSTICKCFG = 0xE000E010; +#define DI() HWREG(SYSTICKCFG) &= ~2; +#define EI() HWREG(SYSTICKCFG) |= 2; + +#elif defined(TISPIFFS_LOCK_SCHEDULER_SUSPEND) + +// Disable freertos scheduler + +#define DI() vTaskSuspendAll() +#define EI() xTaskResumeAll() + +#elif defined(TISPIFFS_LOCK_NONE) + +// No write locking. + +#define DI() +#define EI() + +#elif defined(TISPIFFS_LOCK_CRASH) + +// Crashes if two different executions of this locking mechanism are +// concurrent. + +unsigned pend = 0; +#define DI() \ + HASSERT(pend == 0); \ + pend = 1; +#define EI() pend = 0; + +#else +#error Must specify what kind of locking to use for TISPIFFS. +#endif + +// This ifdef decides whether we use the ROM or the flash based implementations +// for Flash write and erase. It also supports correcting for the reversed bank +// addresses. +#if 1 +#define FPG(data, addr, size) ROM_FlashProgram(data, (addr) ^ addr_mirror, size) +#define FER(addr) ROM_FlashErase((addr) ^ addr_mirror) +#else +#define FPG(data, addr, size) FlashProgram(data, (addr) ^ addr_mirror, size) +#define FER(addr) FlashErase((addr) ^ addr_mirror) +#endif + +template class TiFlash +{ +public: + constexpr TiFlash() + { } + + /// Performs write to the device. This call is synchronous; does not return + /// until the write is complete. + /// @param addr where to write (in the address space of the MCU). + /// @param buf data to write + /// @param len how many bytes to write + static void write(uint32_t addr, const void *buf, uint32_t len) + { + // Theory of operation: + // + // The hardware has 32 word long write buffer. This is aligned based on + // the flash address that's being written. We split the writes on the + // boundary of this buffer (which is the low 7 bits of the address). + // + // For each buffer, we identify which words are touched by the + // write. We load these words (32 bits at a time), then we AND the data + // to be written into it (one byte at a time). + // + // Then we program from the buffer using the driverlib API. + // + // This algorithm is agnostic to the alignment of the actual writes, + // and we also guarantee that we never attempt to set a 0 bit to a 1 + // bit in the flash. It also makes optimal use of the parallel write + // capability of the flash hardware. + static constexpr uint32_t BUF_WORD_LEN = 32; + static constexpr uint32_t BUF_BYTE_LEN = 128; + static constexpr uint32_t BUF_BYTE_MASK = BUF_BYTE_LEN - 1; + static constexpr uint32_t BYTE_PER_WORD = 4; + typedef union + { + uint32_t w[BUF_WORD_LEN]; + uint8_t b[BUF_BYTE_LEN]; + } BufType; + static BufType wrbuf; + + const uint8_t *src = (const uint8_t *)buf; + + while (len) + { + // memory address of the first byte in the buffer. + uint32_t *buf_address = (uint32_t *)(addr & ~BUF_BYTE_MASK); + // first word in the buffer that we need to program + uint32_t buf_word_ofs = (addr & BUF_BYTE_MASK) / BYTE_PER_WORD; + // first byte in the buffer that we need to load + uint32_t buf_byte_ofs = (addr & BUF_BYTE_MASK); + // how many bytes to copy to this buffer. + uint32_t buf_byte_count = + std::min((uint32_t)len, BUF_BYTE_LEN - buf_byte_ofs); + // first word that does not need to be touched + uint32_t buf_word_end = + (buf_byte_ofs + buf_byte_count + BYTE_PER_WORD - 1) / + BYTE_PER_WORD; + // how many words do we need to program. + uint32_t buf_word_count = buf_word_end - buf_word_ofs; + + // Pre-fill data with existing flash content. + for (uint32_t i = 0; i < buf_word_count; ++i) + { + wrbuf.w[buf_word_ofs + i] = buf_address[buf_word_ofs + i]; + } + + // Copy the to-be-programmed data with AND operator. + for (uint32_t i = 0; i < buf_byte_count; ++i) + { + wrbuf.b[buf_byte_ofs + i] &= src[i]; + } + + // Program the buffer to flash. + DI(); + HASSERT(FPG(wrbuf.w + buf_word_ofs, + ((uint32_t)buf_address) + buf_word_ofs * BYTE_PER_WORD, + buf_word_count * BYTE_PER_WORD) == 0); + EI(); + + // Go to the next flash buffer page. + len -= buf_byte_count; + addr += buf_byte_count; + src += buf_byte_count; + } + } + + /// Reads data from the device. + /// @param addr where to read from (address space of the MCU) + /// @param buf points to where to put the data read + /// @param len how many bytes to read + static void read(uint32_t addr, void *buf, size_t len) + { + memcpy(buf, (void *)addr, len); + } + + /// Aligns an address to the next possible sector start (i.e., rounds up to + /// sector boundary). + /// @param addr an address in the flash address space. + /// @return If addr is the first byte of a sector, then returns addr + /// unmodified. Otherwise returns the starting address of the next sector. + static uint32_t next_sector_address(uint32_t addr) + { + return (addr + ERASE_PAGE_SIZE - 1) & ~(ERASE_PAGE_SIZE - 1); + } + + /// Erases sector(s) of the device. + /// @param addr beginning of the sector to erase. Must be sector aligned. + /// @param len how many bytes to erase (must be multiple of sector size). + static void erase(uint32_t addr, size_t len) + { + while (len) + { + DI(); + HASSERT(FER(addr) == 0); + EI(); + addr += ERASE_PAGE_SIZE; + len -= ERASE_PAGE_SIZE; + } + } +}; + +#undef DI +#undef EI +#undef FPG +#undef FER + +#endif // _FREERTOS_DRIVERS_TI_TIFLASH_HXX_ From 00171fbd6179a64992e0a6f577ef0c0c1dc0e132 Mon Sep 17 00:00:00 2001 From: Stuart W Baker Date: Fri, 28 Apr 2023 19:50:42 -0500 Subject: [PATCH 29/31] Fix bug that convoluted the file mode for the UART hardware mode (#706) * Fix bug that convoluted the file mode for the UART hardware mode. * Rename uartMode_ in Tiva UART driver for consistency with the updated CC32xx UART driver. --- src/freertos_drivers/ti/CC32xxUart.cxx | 38 ++++++++++++++------------ src/freertos_drivers/ti/CC32xxUart.hxx | 1 + src/freertos_drivers/ti/TivaDev.hxx | 2 +- src/freertos_drivers/ti/TivaUart.cxx | 34 +++++++++++------------ 4 files changed, 39 insertions(+), 36 deletions(-) diff --git a/src/freertos_drivers/ti/CC32xxUart.cxx b/src/freertos_drivers/ti/CC32xxUart.cxx index 1c72cd2ea..910938b89 100644 --- a/src/freertos_drivers/ti/CC32xxUart.cxx +++ b/src/freertos_drivers/ti/CC32xxUart.cxx @@ -72,6 +72,7 @@ CC32xxUart::CC32xxUart(const char *name, unsigned long base, uint32_t interrupt, , base_(base) , interrupt_(interrupt) , baud_(baud) + , uartMode_(mode | UART_CONFIG_PAR_NONE) , txPending_(false) , hwFIFO_(hw_fifo) { @@ -79,7 +80,7 @@ CC32xxUart::CC32xxUart(const char *name, unsigned long base, uint32_t interrupt, UART_CONFIG_PAR_NONE == 0, "driverlib changed against our assumptions"); static_assert( UART_CONFIG_STOP_ONE == 0, "driverlib changed against our assumptions"); - HASSERT(mode <= 0xFFu); + HASSERT(uartMode_ <= 0xFFu); switch (base) { @@ -95,8 +96,7 @@ CC32xxUart::CC32xxUart(const char *name, unsigned long base, uint32_t interrupt, break; } - MAP_UARTConfigSetExpClk(base_, cm3_cpu_clock_hz, baud, - mode | UART_CONFIG_PAR_NONE); + MAP_UARTConfigSetExpClk(base_, cm3_cpu_clock_hz, baud, uartMode_); MAP_IntDisable(interrupt_); /* We set the priority so that it is slightly lower than the highest needed * for FreeRTOS compatibility. This will ensure that CAN interrupts take @@ -150,38 +150,38 @@ int CC32xxUart::ioctl(File *file, unsigned long int key, unsigned long data) MAP_UtilsDelay(12 * 26); break; case TCPARNONE: - mode_ &= ~UART_CONFIG_PAR_MASK; - mode_ |= UART_CONFIG_PAR_NONE; + uartMode_ &= ~UART_CONFIG_PAR_MASK; + uartMode_ |= UART_CONFIG_PAR_NONE; MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_NONE); break; case TCPARODD: - mode_ &= ~UART_CONFIG_PAR_MASK; - mode_ |= UART_CONFIG_PAR_ODD; + uartMode_ &= ~UART_CONFIG_PAR_MASK; + uartMode_ |= UART_CONFIG_PAR_ODD; MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_ODD); break; case TCPAREVEN: - mode_ &= ~UART_CONFIG_PAR_MASK; - mode_ |= UART_CONFIG_PAR_EVEN; + uartMode_ &= ~UART_CONFIG_PAR_MASK; + uartMode_ |= UART_CONFIG_PAR_EVEN; MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_EVEN); break; case TCPARONE: - mode_ &= ~UART_CONFIG_PAR_MASK; - mode_ |= UART_CONFIG_PAR_ONE; + uartMode_ &= ~UART_CONFIG_PAR_MASK; + uartMode_ |= UART_CONFIG_PAR_ONE; MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_ONE); break; case TCPARZERO: - mode_ &= ~UART_CONFIG_PAR_MASK; - mode_ |= UART_CONFIG_PAR_ZERO; + uartMode_ &= ~UART_CONFIG_PAR_MASK; + uartMode_ |= UART_CONFIG_PAR_ZERO; MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_ZERO); break; case TCSTOPONE: - mode_ &= ~UART_CONFIG_STOP_MASK; - mode_ |= UART_CONFIG_STOP_ONE; + uartMode_ &= ~UART_CONFIG_STOP_MASK; + uartMode_ |= UART_CONFIG_STOP_ONE; set_mode(); break; case TCSTOPTWO: - mode_ &= ~UART_CONFIG_STOP_MASK; - mode_ |= UART_CONFIG_STOP_TWO; + uartMode_ &= ~UART_CONFIG_STOP_MASK; + uartMode_ |= UART_CONFIG_STOP_TWO; set_mode(); break; case TCBAUDRATE: @@ -217,7 +217,9 @@ int CC32xxUart::ioctl(File *file, unsigned long int key, unsigned long data) /** Sets the port baud rate and mode from the class variables. */ void CC32xxUart::set_mode() { - MAP_UARTConfigSetExpClk(base_, cm3_cpu_clock_hz, baud_, mode_); + disable(); + MAP_UARTConfigSetExpClk(base_, cm3_cpu_clock_hz, baud_, uartMode_); + enable(); } /** Send data until there is no more space left. diff --git a/src/freertos_drivers/ti/CC32xxUart.hxx b/src/freertos_drivers/ti/CC32xxUart.hxx index 663d36be9..b63ae4c09 100644 --- a/src/freertos_drivers/ti/CC32xxUart.hxx +++ b/src/freertos_drivers/ti/CC32xxUart.hxx @@ -121,6 +121,7 @@ private: unsigned long base_; /**< base address of this device */ uint32_t interrupt_ : 8; /**< interrupt of this device */ uint32_t baud_ : 24; /**< desired baud rate */ + uint32_t uartMode_; /**< mode of the UART, 8 or 9 bit, 1 or 2 stop... */ uint8_t txPending_; /**< transmission currently pending */ uint8_t hwFIFO_; /**< true if hardware fifo is to be enabled, else false */ diff --git a/src/freertos_drivers/ti/TivaDev.hxx b/src/freertos_drivers/ti/TivaDev.hxx index 1bd0a92e0..b36d8023a 100644 --- a/src/freertos_drivers/ti/TivaDev.hxx +++ b/src/freertos_drivers/ti/TivaDev.hxx @@ -227,7 +227,7 @@ private: uint32_t interrupt_ : 8; /**< interrupt of this device */ uint32_t baud_ : 24; /**< desired baud rate */ uint8_t hwFIFO_; /**< enable HW FIFO */ - uint8_t mode_; /**< uart config (mode) flags */ + uint8_t uartMode_; /**< uart config (mode) flags */ uint8_t txPending_; /**< transmission currently pending */ /** Default constructor. diff --git a/src/freertos_drivers/ti/TivaUart.cxx b/src/freertos_drivers/ti/TivaUart.cxx index 1ec1f1c27..384dd17d4 100644 --- a/src/freertos_drivers/ti/TivaUart.cxx +++ b/src/freertos_drivers/ti/TivaUart.cxx @@ -69,7 +69,7 @@ TivaUart::TivaUart(const char *name, unsigned long base, uint32_t interrupt, , interrupt_(interrupt) , baud_(baud) , hwFIFO_(hw_fifo) - , mode_(mode) + , uartMode_(mode) , txPending_(false) { static_assert( @@ -116,7 +116,7 @@ TivaUart::TivaUart(const char *name, unsigned long base, uint32_t interrupt, break; } - MAP_UARTConfigSetExpClk(base_, cm3_cpu_clock_hz, baud_, mode_); + MAP_UARTConfigSetExpClk(base_, cm3_cpu_clock_hz, baud_, uartMode_); MAP_UARTTxIntModeSet(base_, UART_TXINT_MODE_EOT); MAP_IntDisable(interrupt_); /* We set the priority so that it is slightly lower than the highest needed @@ -260,7 +260,7 @@ void TivaUart::interrupt_handler() /** Sets the port baud rate and mode from the class variables. */ void TivaUart::set_mode() { - MAP_UARTConfigSetExpClk(base_, cm3_cpu_clock_hz, baud_, mode_); + MAP_UARTConfigSetExpClk(base_, cm3_cpu_clock_hz, baud_, uartMode_); } /** Request an ioctl transaction @@ -283,38 +283,38 @@ int TivaUart::ioctl(File *file, unsigned long int key, unsigned long data) MAP_SysCtlDelay(12 * 26); break; case TCPARNONE: - mode_ &= ~UART_CONFIG_PAR_MASK; - mode_ |= UART_CONFIG_PAR_NONE; + uartMode_ &= ~UART_CONFIG_PAR_MASK; + uartMode_ |= UART_CONFIG_PAR_NONE; MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_NONE); break; case TCPARODD: - mode_ &= ~UART_CONFIG_PAR_MASK; - mode_ |= UART_CONFIG_PAR_ODD; + uartMode_ &= ~UART_CONFIG_PAR_MASK; + uartMode_ |= UART_CONFIG_PAR_ODD; MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_ODD); break; case TCPAREVEN: - mode_ &= ~UART_CONFIG_PAR_MASK; - mode_ |= UART_CONFIG_PAR_EVEN; + uartMode_ &= ~UART_CONFIG_PAR_MASK; + uartMode_ |= UART_CONFIG_PAR_EVEN; MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_EVEN); break; case TCPARONE: - mode_ &= ~UART_CONFIG_PAR_MASK; - mode_ |= UART_CONFIG_PAR_ONE; + uartMode_ &= ~UART_CONFIG_PAR_MASK; + uartMode_ |= UART_CONFIG_PAR_ONE; MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_ONE); break; case TCPARZERO: - mode_ &= ~UART_CONFIG_PAR_MASK; - mode_ |= UART_CONFIG_PAR_ZERO; + uartMode_ &= ~UART_CONFIG_PAR_MASK; + uartMode_ |= UART_CONFIG_PAR_ZERO; MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_ZERO); break; case TCSTOPONE: - mode_ &= ~UART_CONFIG_STOP_MASK; - mode_ |= UART_CONFIG_STOP_ONE; + uartMode_ &= ~UART_CONFIG_STOP_MASK; + uartMode_ |= UART_CONFIG_STOP_ONE; set_mode(); break; case TCSTOPTWO: - mode_ &= ~UART_CONFIG_STOP_MASK; - mode_ |= UART_CONFIG_STOP_TWO; + uartMode_ &= ~UART_CONFIG_STOP_MASK; + uartMode_ |= UART_CONFIG_STOP_TWO; set_mode(); break; case TCBAUDRATE: From 3092f0ea685de99b1378494d2105e0e0a3f150d7 Mon Sep 17 00:00:00 2001 From: Stuart W Baker Date: Sun, 14 May 2023 12:36:54 -0500 Subject: [PATCH 30/31] Add missing include guards. (#714) --- src/utils/Ewma.hxx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/utils/Ewma.hxx b/src/utils/Ewma.hxx index facb3b4b6..42e796ccd 100644 --- a/src/utils/Ewma.hxx +++ b/src/utils/Ewma.hxx @@ -32,6 +32,9 @@ * @date 2 May 2015 */ +#ifndef _UTILS_EWMA_HXX_ +#define _UTILS_EWMA_HXX_ + #include #include @@ -178,3 +181,5 @@ public: float alpha_; ///< coefficient for EWMA float avg_{0.0}; ///< current state of EWMA }; + +#endif // _UTILS_EWMA_HXX_ From 343b60b976deb6d93dc05f1d675161b89b8b9511 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 8 Jul 2023 13:12:01 +0200 Subject: [PATCH 31/31] Refactors fast time state into a separate base class from BroadcastTime. (#692) --- src/openlcb/BroadcastTime.hxx | 236 +-------------------------- src/utils/TimeBase.hxx | 296 ++++++++++++++++++++++++++++++++++ 2 files changed, 298 insertions(+), 234 deletions(-) create mode 100644 src/utils/TimeBase.hxx diff --git a/src/openlcb/BroadcastTime.hxx b/src/openlcb/BroadcastTime.hxx index d99de21c0..5b6f54a82 100644 --- a/src/openlcb/BroadcastTime.hxx +++ b/src/openlcb/BroadcastTime.hxx @@ -39,6 +39,7 @@ #include "openlcb/BroadcastTimeDefs.hxx" #include "openlcb/EventHandlerTemplates.hxx" +#include "utils/TimeBase.hxx" namespace openlcb { @@ -46,7 +47,7 @@ namespace openlcb /// Implementation of Broadcast Time Protocol. class BroadcastTime : public SimpleEventHandler , public StateFlowBase - , protected Atomic + , public TimeBase { public: typedef std::vector>::size_type UpdateSubscribeHandle; @@ -121,230 +122,6 @@ public: send_event(node_, eventBase_ | BroadcastTimeDefs::QUERY_EVENT_SUFFIX); } - /// Get the time as a value of seconds relative to the system epoch. At the - /// same time get an atomic matching pair of the rate - /// @return pair