From dc53b5828a7be35f1db9ee6ba72ad0bd9fc9fcd6 Mon Sep 17 00:00:00 2001 From: Joshua Feist <40469822+feistjo@users.noreply.github.com> Date: Thu, 6 Jul 2023 14:50:13 -0700 Subject: [PATCH 1/2] Added MIT license (#1) * Added license * Added 'MIT License' text --- license.txt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 license.txt diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..be844fb --- /dev/null +++ b/license.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright 2023 Northwestern Formula Racing + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file From c9b0fd35a7e48eb2b7bb0bc3b67f4e9c30dacfdb Mon Sep 17 00:00:00 2001 From: Joshua Feist <40469822+feistjo@users.noreply.github.com> Date: Fri, 13 Oct 2023 15:14:09 -0700 Subject: [PATCH 2/2] Upstream updates (#2) * Added license * Added 'MIT License' text * Made CANSignal SignalType atomic, fixed resulting errors * Handle enums in dbc_to_h * Added MockCAN, implemented multiplexed CAN messages * Improved compatibility of multiplexed messages * Updated dbc to h with multiplexed messages * Update to dbc_to_h, updates to remove warnings (#52) Co-authored-by: Joshua Feist * Fix missing float cast (#53) * Update to dbc_to_h, updates to remove warnings * Fixed missing float cast --------- Co-authored-by: Joshua Feist * Updates to dbc_to_h and multiplexed CAN (#54) * Fixed some missing overrides * Made multiplexor index a size_t * Added data type estimation to dbc_to_h, updated can_interface.h to work with non-multiplexed signals in multiplexed messages * updated dbc_to_h to automatically fill transmit periods * Added missing static_cast to get rid of warnings --------- Co-authored-by: Joshua Feist * Made time getters const (#55) Co-authored-by: Joshua Feist * Significant updates, including TWAI ESP driver and ESP updates over CAN (#57) * Update dbc_to_h for better compatibility * Work on improving multiplexed message transmitting * Fixed some issues with multiplexed messages * Initial switch to twai driver * Fixed a couple bugs, tested and working twai * Added check to not send message when twai bus state isn't running * Made atomic work for uint64_t with freertos, updated multiplexed message handling * Preliminary support for PGNs * Added operator definitions for assigning and comparing CAN signals of the same type without having to cast to SignalType * PGN support needs testing * Marked functions as override * Static cast to fix a couple warnings * Support esp32 updates over CAN (#3) * Added support for uploading firmware to ESP32 over CAN * Updated for better compatibility with other projects * Cleanup update code, fix timeout/abort, properly close SocketCan on close * Made else if --------- Co-authored-by: Joshua Feist --------- Co-authored-by: Joshua Feist * Fixed test for new MultiplexedCANTXMessage template * Put examples in library * Added check to print if missed RX messages due to full queue for ESP, added comments to clarify differences between types of CAN Messages * Added comments * Fixed failing unit tests, added comment explaining long ternary and commented code block --------- Co-authored-by: Joshua Feist --- .vscode/settings.json | 5 +- README.md | 11 + docs/dbc_to_h.py | 82 +- examples/CAN_Interface_Demo/platformio.ini | 25 + examples/CAN_Interface_Demo/src/main.cpp | 162 ++++ examples/CAN_Update_Demo/platformio.ini | 26 + examples/CAN_Update_Demo/src/main.cpp | 32 + include/can_interface.h | 840 +++++++++++++++++++-- include/esp_can.h | 36 +- include/esp_can_update.h | 152 ++++ include/freertos_atomic.h | 30 + library.json | 23 +- scripts/esp_can_update.dbc | 56 ++ scripts/esp_can_update.py | 193 +++++ src/ESP32CAN/CAN.c | 308 -------- src/ESP32CAN/CAN.h | 139 ---- src/ESP32CAN/CAN_config.h | 71 -- src/ESP32CAN/ESP32CAN.cpp | 13 - src/ESP32CAN/ESP32CAN.h | 17 - src/ESP32CAN/can_regdef.h | 279 ------- src/esp_can.cpp | 107 ++- src/teensy_can.cpp | 8 +- test/test_main.cpp | 58 ++ 23 files changed, 1719 insertions(+), 954 deletions(-) create mode 100644 examples/CAN_Interface_Demo/platformio.ini create mode 100644 examples/CAN_Interface_Demo/src/main.cpp create mode 100644 examples/CAN_Update_Demo/platformio.ini create mode 100644 examples/CAN_Update_Demo/src/main.cpp create mode 100644 include/esp_can_update.h create mode 100644 include/freertos_atomic.h create mode 100644 scripts/esp_can_update.dbc create mode 100644 scripts/esp_can_update.py delete mode 100644 src/ESP32CAN/CAN.c delete mode 100644 src/ESP32CAN/CAN.h delete mode 100644 src/ESP32CAN/CAN_config.h delete mode 100644 src/ESP32CAN/ESP32CAN.cpp delete mode 100644 src/ESP32CAN/ESP32CAN.h delete mode 100644 src/ESP32CAN/can_regdef.h diff --git a/.vscode/settings.json b/.vscode/settings.json index 934ca93..c8c7059 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -49,7 +49,10 @@ "memory_resource": "cpp", "random": "cpp", "string_view": "cpp", - "numbers": "cpp" + "numbers": "cpp", + "cstring": "cpp", + "iomanip": "cpp", + "iostream": "cpp" }, "nrf-connect.topdir": "${nrf-connect.sdk:2.2.0}", "nrf-connect.toolchain.path": "${nrf-connect.toolchain:2.2.0}" diff --git a/README.md b/README.md index ae7efa3..15cb0eb 100644 --- a/README.md +++ b/README.md @@ -26,3 +26,14 @@ This code base currently includes the following hardware platforms: More support to come in the future. We would also like to thank our sponsor [Innomaker](https://www.inno-maker.com/product/usb2can-cable/) for supporting us with their products for the 22-23 season! + +### Updates + +This code has support for uploading new code to the ESP32 over CAN. To use this feature, add + upload_protocol = custom + upload_can = y +to the env in platformio.ini, and also add + [can_update] + update_baud = 500000 + update_message_id = 0x530 +to platformio.ini diff --git a/docs/dbc_to_h.py b/docs/dbc_to_h.py index d48acc8..70ea8cd 100644 --- a/docs/dbc_to_h.py +++ b/docs/dbc_to_h.py @@ -1,7 +1,25 @@ import cantools import sys -def dbc_to_h(dbc_file, h_file): +# This will generate a functional .h file from a dbc compatible with the NFR CAN library +# Note: signal data types should work, but may be incorrect or suboptimal + +def get_data_type(signed, length, factor, offset): + if not isinstance(factor, int): + if length <= 32: + return "float" + else: + return "double" + if not signed: + if factor < 0: + return "int" + str(64 if length >= 32 else 32 if length >= 16 else 16 if length >= 8 else 8) + "_t" + else: + return "uint" + str(64 if length > 32 else 32 if length > 16 else 16 if length > 8 else 8) + "_t" + else: + return "int" + str(64 if length > 32 else 32 if length > 16 else 16 if length > 8 else 8) + "_t" + + +def dbc_to_h(dbc_file, h_file, get_millis): # Load the DBC file db = cantools.database.load_file(dbc_file) @@ -11,9 +29,18 @@ def dbc_to_h(dbc_file, h_file): with open(h_file, 'w') as file: file.write("// Signals\n") for message in db.messages: + multiplexor_signal = None + multiplexor_signal_str = "" signals = [] + for signal in message.signals: signalString = "" + data_type = get_data_type(signal.is_signed, signal.length, signal.scale, signal.offset) + if (signal.choices != None): + signalString += "enum class " + signal.name + "_Enum : " + data_type + " {\n" + for value, name in signal.choices.items(): + signalString += '_'.join(str(name).replace('-', ' ').replace('(', ' ').replace(')', ' ').split()) + " = " + str(value) + ",\n" + signalString += "};\n" if signal.byte_order == "big_endian": byteOrder = "Endian" endian = ", ICANSignal::ByteOrder::kBigEndian" @@ -21,13 +48,49 @@ def dbc_to_h(dbc_file, h_file): byteOrder = "" endian = "" signalType = "Signed" if signal.is_signed else "Unsigned" - signalString = "Make" + byteOrder + signalType + "CANSignal(data_type_placeholder," + str(signal.start) + "," + str(signal.length) + "," + str(signal.scale) + "," + str(signal.offset) + endian + ") " + signal.name + "_Signal{};\n" + signalString += "Make" + byteOrder + signalType + "CANSignal(" + (data_type if signal.choices == None else signal.name + "_Enum") + "," + str(signal.start) + "," + str(signal.length) + "," + str(signal.scale) + ("" if isinstance(signal.scale, int) else "f") + "," + str(signal.offset) + endian + ") " + signal.name + "_Signal_{};\n" with open(h_file, 'a') as file: file.write(signalString) - signals.append(signal.name + "_Signal") - rx_message = "CANRXMessage<" + str(len(signals)) + "> " + message.name + "_RX_Message{can_bus, 0x" + format(message.frame_id, 'x') + ", " + ', '.join(signals) + "};\n" + if message.is_multiplexed(): + if (signal.is_multiplexer): + multiplexor_signal = signal + multiplexor_signal_str = signal.name + "_Signal_" + continue + + found = False + if signal.multiplexer_ids == None: + for i in range(len(signals)): + if signals[i][0] == "true": + signals[i].append(signal.name + "_Signal_") + found = True + continue + if not found: + signals.append(["true", "0xFFFFFFFFFFFFFFFFul", signal.name + "_Signal_"]) + else: + for i in range(len(signals)): + if signals[i][1] == signal.multiplexer_ids[0]: + signals[i].append(signal.name + "_Signal_") + found = True + continue + if not found: + signals.append(["false", signal.multiplexer_ids[0], signal.name + "_Signal_"]) + else: + signals.append(signal.name + "_Signal_") + print(signals) + if message.is_multiplexed(): + signal_groups_str = "" + signal_groups = [] + for i in range(len(signals)): + signal_groups_str += "MultiplexedSignalGroup<" + str(len(signals[i]) - 2) + "> " + message.name + "_SignalGroup_" + str(i) + "_{" + ', '.join(str(s) for s in signals[i]) + "};\n" + signal_groups.append(message.name + "_SignalGroup_" + str(i) + "_") + #MultiplexedCANTXMessage<2, uint8_t> tx_msg{can, 100, 8, 100, tx_multiplexor, tx_signals_0, tx_signals_1}; + rx_message = signal_groups_str + rx_message += "MultiplexedCANRXMessage<" + str(len(signal_groups)) + ", " + get_data_type(multiplexor_signal.is_signed, multiplexor_signal.length, multiplexor_signal.scale, multiplexor_signal.offset) + "> " + message.name + "_RX_Message_{can_bus_, 0x" + format(message.frame_id, 'x') + ", " + ((get_millis + ", ") if get_millis != None else "") + multiplexor_signal_str + ", " + ', '.join(signal_groups) + "};\n" + tx_message = "MultiplexedCANTXMessage<" + str(len(signal_groups)) + ", " + get_data_type(multiplexor_signal.is_signed, multiplexor_signal.length, multiplexor_signal.scale, multiplexor_signal.offset) + "> " + message.name + "_TX_Message_{can_bus_, 0x" + format(message.frame_id, 'x') + ", " + ("true, " if message.is_extended_frame else "") + str(message.length) + ", " + ("0" if message.cycle_time == None else str(message.cycle_time)) + ", timer_group_, " + multiplexor_signal_str + ", " + ', '.join(signal_groups) + "};\n" + else: + rx_message = "CANRXMessage<" + str(len(signals)) + "> " + message.name + "_RX_Message_{can_bus_, 0x" + format(message.frame_id, 'x') + ", " + ((get_millis + ", ") if get_millis != None else "") + ', '.join(signals) + "};\n" + tx_message = "CANTXMessage<" + str(len(signals)) + "> " + message.name + "_TX_Message_{can_bus_, 0x" + format(message.frame_id, 'x') + ", " + ("true, " if message.is_extended_frame else "") + str(message.length) + ", " + ("0" if message.cycle_time == None else str(message.cycle_time)) + ", timer_group_, " + ', '.join(signals) + "};\n" rx_messages += rx_message - tx_message = "CANTXMessage<" + str(len(signals)) + "> " + message.name + "_TX_Message{can_bus, 0x" + format(message.frame_id, 'x') + ", " + str(message.length) + ", freq_placeholder, timer_group, " + ', '.join(signals) + "};\n" tx_messages += tx_message with open(h_file, 'a') as file: file.write("\n// RX Messages\n") @@ -36,12 +99,13 @@ def dbc_to_h(dbc_file, h_file): file.write(tx_messages) if __name__ == "__main__": - if len(sys.argv) != 3: - print("Usage: python dbc_to_h.py ") + if len(sys.argv) != 3 and len(sys.argv) != 4: + print("Usage: python dbc_to_h.py ") sys.exit(1) dbc_file = sys.argv[1] h_file = sys.argv[2] + get_millis = sys.argv[3] if len(sys.argv) == 4 else None - dbc_to_h(dbc_file, h_file) - print(f"Converted {dbc_file} to {h_file}") \ No newline at end of file + dbc_to_h(dbc_file, h_file, get_millis) + print(f"Converted {dbc_file} to {h_file}") diff --git a/examples/CAN_Interface_Demo/platformio.ini b/examples/CAN_Interface_Demo/platformio.ini new file mode 100644 index 0000000..924729e --- /dev/null +++ b/examples/CAN_Interface_Demo/platformio.ini @@ -0,0 +1,25 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[platformio] +default_envs = teensy40 + +[env:esp32dev] +platform = espressif32 +board = esp32dev +framework = arduino +lib_deps = https://github.com/NU-Formula-Racing/CAN.git + + +[env:teensy40] +platform = teensy +board = teensy40 +framework = arduino +lib_deps = https://github.com/NU-Formula-Racing/CAN.git \ No newline at end of file diff --git a/examples/CAN_Interface_Demo/src/main.cpp b/examples/CAN_Interface_Demo/src/main.cpp new file mode 100644 index 0000000..d7c26f5 --- /dev/null +++ b/examples/CAN_Interface_Demo/src/main.cpp @@ -0,0 +1,162 @@ +#include + +/** + * This file is meant to show how to use the CAN library. + * First, you need to include the correct library (ESP or Teensy) andinstantiate the CAN bus you will be using. + * + */ + +#if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41) +#include "teensy_can.h" +// The bus number is a template argument for Teensy: TeensyCAN +TeensyCAN<1> can_bus{}; +#endif + +#ifdef ARDUINO_ARCH_ESP32 +#include "esp_can.h" +// The tx and rx pins are constructor arguments to ESPCan, which default to TX = 5, RX = 4 +ESPCAN can_bus{}; +#endif + +/** + * @brief You also need to include the VirtualTimers library in order to use VirtualTimers and VirtualTimerGroups + * + */ +#include "virtualTimer.h" + +// Make a VirtualTimerGroup to add your timers to +VirtualTimerGroup timer_group{}; + +/** + * Every CAN message, TX or RX, has signals, which need to be instantiated before the message. You should never put the + * same signal in multiple messages. The CANSignal class is used to create these signals. The signal type, starting + * position, length, factor, offset, and signedness of the signal are all templated arguments. You can construct a + * CANSignal using the MakeSignedCANSignal(SignalType, position, length, factor, offset) or + * MakeUnsignedCANSignal(SignalType, position, length, factor, offset) macro. There are no constructor arguments + * + */ +MakeSignedCANSignal(float, 0, 16, 0.01, 0) float_tx_signal{}; +MakeUnsignedCANSignal(uint8_t, 16, 8, 1, 0) uint8_t_tx_signal{}; +MakeUnsignedCANSignal(bool, 24, 1, 1, 0) bool_tx_signal{}; +MakeUnsignedCANSignal(uint32_t, 32, 32, 1, 0) millis_tx_signal{}; + +MakeSignedCANSignal(float, 0, 16, 0.01, 0) float_rx_signal{}; +MakeUnsignedCANSignal(uint8_t, 16, 8, 1, 0) uint8_t_rx_signal{}; +MakeUnsignedCANSignal(bool, 24, 1, 1, 0) bool_rx_signal{}; +MakeUnsignedCANSignal(uint32_t, 32, 32, 1, 0) millis_rx_signal{}; + +// big endian +MakeEndianSignedCANSignal(float, 0, 16, 0.01, 0, ICANSignal::ByteOrder::kBigEndian) big_endian_float_tx_signal{}; +MakeEndianUnsignedCANSignal(uint8_t, 16, 8, 1, 0, ICANSignal::ByteOrder::kBigEndian) big_endian_uint8_t_tx_signal{}; +MakeEndianUnsignedCANSignal(bool, 24, 1, 1, 0, ICANSignal::ByteOrder::kBigEndian) big_endian_bool_tx_signal{}; +MakeEndianUnsignedCANSignal(uint32_t, 32, 32, 1, 0, ICANSignal::ByteOrder::kBigEndian) big_endian_millis_tx_signal{}; + +MakeEndianSignedCANSignal(float, 0, 16, 0.01, 0, ICANSignal::ByteOrder::kBigEndian) big_endian_float_rx_signal{}; +MakeEndianUnsignedCANSignal(uint8_t, 16, 8, 1, 0, ICANSignal::ByteOrder::kBigEndian) big_endian_uint8_t_rx_signal{}; +MakeEndianUnsignedCANSignal(bool, 24, 1, 1, 0, ICANSignal::ByteOrder::kBigEndian) big_endian_bool_rx_signal{}; +MakeEndianUnsignedCANSignal(uint32_t, 32, 32, 1, 0, ICANSignal::ByteOrder::kBigEndian) big_endian_millis_rx_signal{}; + +/** + * The CANTXMessage and CANRXMessage classes are used to create messages with signals in them. + * Both of these classes take the number of signals as a templated argument (CAN*xMessage) + * + */ + +/** + * CANTXMessage takes the CAN bus to transmit on, the message ID, the message size in bytes (based on the end position + * of the highest signal), the transmit period, and the signals as arguments + * CANTXMessage can also take a VirtualTimerGroup to add its transmit timer to + * + */ +CANTXMessage<4> tx_message{ + can_bus, 0x100, 8, 100, timer_group, float_tx_signal, uint8_t_tx_signal, bool_tx_signal, millis_tx_signal}; + +/** + * CANRXMessage takes the CAN bus to receive on, the message ID, and the signals to be received as constructor arguments + * CANRXMessages automatically register themselves with the can_bus on construction + * + */ +CANRXMessage<4> rx_message{can_bus, + 0x200, + []() { Serial.println(rx_message.GetLastReceiveTime()); }, + float_rx_signal, + uint8_t_rx_signal, + bool_rx_signal, + millis_rx_signal}; + +// big endian +CANTXMessage<4> big_endian_tx_message{can_bus, + 0x300, + 8, + 100, + timer_group, + big_endian_float_tx_signal, + big_endian_uint8_t_tx_signal, + big_endian_bool_tx_signal, + big_endian_millis_tx_signal}; + +CANRXMessage<4> big_endian_rx_message{can_bus, + 0x400, + []() { Serial.println(rx_message.GetLastReceiveTime()); }, + big_endian_float_rx_signal, + big_endian_uint8_t_rx_signal, + big_endian_bool_rx_signal, + big_endian_millis_rx_signal}; + +// You should make a function to do anything that needs to be periodic and run it with a VirtualTimer in a TimerGroup +void ten_ms_task() +{ + /** + * You can use and set the CANSignals as if they were SignalType + * + */ + float_tx_signal = 5.195f; + uint8_t_tx_signal = 132; + bool_tx_signal = true; + millis_tx_signal = millis(); + + float test_float = float_rx_signal; + uint8_t test_uint8_t = uint8_t_rx_signal; + bool test_bool = bool_rx_signal; + uint32_t test_millis = millis_rx_signal; + + // The CANRXMessage automatically gets updated on message reception from the interrupt. Not: in order for this to + // work, you must periodically call Tick() on the can_bus + can_bus.Tick(); +#ifndef ARDUINO_ARCH_ESP32 // ESP gets sad and doesn't work properly if you serial print too much + Serial.print("Sent float: "); + Serial.print(float_tx_signal); + Serial.print(" Sent uint8_t: "); + Serial.print(uint8_t_tx_signal); + Serial.print(" Sent bool: "); + Serial.print(bool_tx_signal); + Serial.print(" Sent millis: "); + Serial.println(millis_tx_signal); + + Serial.print("Received float: "); + Serial.print(float(test_float)); + Serial.print(" Received uint8_t: "); + Serial.print(uint8_t(test_uint8_t)); + Serial.print(" Received bool: "); + Serial.print(bool(test_bool)); + Serial.print(" Received millis: "); + Serial.println(uint32_t(test_millis)); +#endif +} + +void setup() +{ + /** + * The CAN bus(es) need to be initialized with their baud rate + * + */ + can_bus.Initialize(ICAN::BaudRate::kBaud1M); + + // You can create a new timer in a VirtualTimerGroup using the AddTimer(function, time) function + timer_group.AddTimer(10, ten_ms_task); + + Serial.begin(9600); + Serial.println("Started"); +} + +void loop() { timer_group.Tick(millis()); } \ No newline at end of file diff --git a/examples/CAN_Update_Demo/platformio.ini b/examples/CAN_Update_Demo/platformio.ini new file mode 100644 index 0000000..012c87f --- /dev/null +++ b/examples/CAN_Update_Demo/platformio.ini @@ -0,0 +1,26 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:seeed_xiao_esp32c3] +platform = espressif32 +board = seeed_xiao_esp32c3 +framework = arduino +lib_deps = https://github.com/NU-Formula-Racing/CAN.git + +;Set upload protocol to use updates over CAN +upload_protocol = custom +upload_can = y +;define update ID based on can_update in platformio.ini +build_flags = -DCAN_UPDATE_ID=${can_update.update_message_id} + +;This section defnes information about the CAN configuration for uploading new firmware +[can_update] +update_baud = 500000 +update_message_id = 0x530 diff --git a/examples/CAN_Update_Demo/src/main.cpp b/examples/CAN_Update_Demo/src/main.cpp new file mode 100644 index 0000000..498f96b --- /dev/null +++ b/examples/CAN_Update_Demo/src/main.cpp @@ -0,0 +1,32 @@ +#include +#include + +#include "esp_can_update.h" + +VirtualTimerGroup timer_group_{}; + +const uint8_t kCrx = 43; +const uint8_t kCtx = 44; + +ESPCAN can_interface_{10, static_cast(kCtx), static_cast(kCrx)}; + +// Initialize the update class, adding its RXMessage to can_interface and its periodic functions to timer_group +// CAN_UPDATE_ID comes from platformio.ini +CANUpdate updater{CAN_UPDATE_ID, can_interface_, timer_group_}; + +void setup() +{ + // put your setup code here, to run once: + Serial.begin(); + // delay(5000); + can_interface_.Initialize(ICAN::BaudRate::kBaud500K); + Serial.println("Updated version of code! 5"); +} + +void loop() +{ + // put your main code here, to run repeatedly: + timer_group_.Tick(millis()); + can_interface_.Tick(); + delay(1); +} \ No newline at end of file diff --git a/include/can_interface.h b/include/can_interface.h index 1f3bbaa..4cbd6e2 100644 --- a/include/can_interface.h +++ b/include/can_interface.h @@ -4,11 +4,22 @@ #include #include +#include #include #include #include +#ifdef TIMER_IMPLEMENTATION +#include TIMER_IMPLEMENTATION +#else #include "virtualTimer.h" +#endif + +#ifdef FREERTOS_ATOMIC_IMPL +#include "freertos_atomic.h" +#else +#define Atomic std::atomic +#endif #ifdef ARDUINO #include @@ -31,6 +42,56 @@ class CANMessage std::array data_; }; +/** + * @brief PGNCANMessage is used to implement J1939 and NMEA2000 + * + */ +class PGNCANMessage : public CANMessage +{ +public: + union PGN + { + uint32_t raw; + struct + { + uint8_t pdu_specific : 8; + uint8_t pdu_format : 8; + bool data_page : 1; + bool reserved : 1; + } pgn; + PGN() = default; + operator uint32_t() const { return raw; } + PGN(uint32_t r) : raw{r} {} + PGN(bool data_page, uint8_t pdu_format, uint8_t pdu_specific) + : pgn{.pdu_specific = pdu_specific, .pdu_format = pdu_format, .data_page = data_page, .reserved = 0} + { + } + }; + + union ExtendedId + { + uint32_t raw; + struct + { + uint8_t source_address : 8; + uint32_t pgn : 18; + uint8_t priority : 3; + } extended_id; + ExtendedId() = default; + operator uint32_t() const { return raw; } + ExtendedId(uint32_t r) : raw{r} {} + ExtendedId(uint8_t source_address, PGN pgn, uint8_t priority) + : extended_id{.source_address = source_address, + .pgn = static_cast(pgn & 0x3FFFF), + .priority = static_cast(priority & 0b11) + + } + { + } + }; + PGNCANMessage(ExtendedId id, uint8_t len, std::array data) : CANMessage{id, true, len, data} {} +}; + class ICANSignal { public: @@ -51,17 +112,18 @@ constexpr typename std::enable_if::value, T>::type bswap(T i constexpr uint8_t CANSignal_generate_position(uint8_t position, uint8_t length, ICANSignal::ByteOrder byte_order) { - return (byte_order == ICANSignal::ByteOrder::kLittleEndian - || (length - (8 - (position % 8)) /* bits_in_last_byte */ < 0)) - ? position - : position - - ((8 - * (((length - (8 - (position % 8)) /* bits_in_last_byte */) % 8) /* remaining_bits */ == 0 - ? ((length - (8 - (position % 8)) /* bits_in_last_byte */) / 8) /* full_bytes */ - : ((length - (8 - (position % 8)) /* bits_in_last_byte */) / 8) /* full_bytes */ + 1)) - + (8 - ((length - (8 - (position % 8)) /* bits_in_last_byte */) % 8) /* remaining_bits */) - - (8 - (position % 8)) /* bits_in_last_byte */); - /* + return static_cast( + (byte_order == ICANSignal::ByteOrder::kLittleEndian + || (length - (8 - (position % 8)) /* bits_in_last_byte */ < 0)) + ? position + : position + - ((8 + * (((length - (8 - (position % 8)) /* bits_in_last_byte */) % 8) /* remaining_bits */ == 0 + ? ((length - (8 - (position % 8)) /* bits_in_last_byte */) / 8) /* full_bytes */ + : ((length - (8 - (position % 8)) /* bits_in_last_byte */) / 8) /* full_bytes */ + 1)) + + (8 - ((length - (8 - (position % 8)) /* bits_in_last_byte */) % 8) /* remaining_bits */) + - (8 - (position % 8)) /* bits_in_last_byte */)); + /* //Expanded version because the nested ternary operators are incomprehensible, but they are required for C++11 if (byte_order == ICANSignal::ByteOrder::kLittleEndian) { return position; @@ -98,22 +160,48 @@ template class ITypedCANSignal : public ICANSignal { public: - SignalType &value_ref() { return signal_; } + Atomic &value_ref() { return signal_; } + + operator SignalType() const { return signal_; } void operator=(const SignalType &signal) { signal_ = signal; } - SignalType operator+=(const SignalType &signal) { return signal_ += signal; } + SignalType operator+=(const SignalType &signal) { return signal_ = signal_ + signal; } - SignalType operator-=(const SignalType &signal) { return signal_ -= signal; } + SignalType operator-=(const SignalType &signal) { return signal_ = signal_ - signal; } - SignalType operator*=(const SignalType &signal) { return signal_ *= signal; } + SignalType operator*=(const SignalType &signal) { return signal_ = signal_ * signal; } - SignalType operator/=(const SignalType &signal) { return signal_ /= signal; } + SignalType operator/=(const SignalType &signal) { return signal_ = signal_ / signal; } - operator SignalType() const { return signal_; } + bool operator>(const SignalType &signal) { return signal_ > signal; } + + bool operator<(const SignalType &signal) { return signal_ < signal; } + + bool operator>=(const SignalType &signal) { return signal_ >= signal; } + + bool operator<=(const SignalType &signal) { return signal_ <= signal; } + + void operator=(const ITypedCANSignal &signal) { signal_ = signal; } + + SignalType operator+=(const ITypedCANSignal &signal) { return signal_ = signal_ + signal; } + + SignalType operator-=(const ITypedCANSignal &signal) { return signal_ = signal_ - signal; } + + SignalType operator*=(const ITypedCANSignal &signal) { return signal_ = signal_ * signal; } + + SignalType operator/=(const ITypedCANSignal &signal) { return signal_ = signal_ / signal; } + + bool operator>(const ITypedCANSignal &signal) { return signal_ > signal; } + + bool operator<(const ITypedCANSignal &signal) { return signal_ < signal; } + + bool operator>=(const ITypedCANSignal &signal) { return signal_ >= signal; } + + bool operator<=(const ITypedCANSignal &signal) { return signal_ <= signal; } protected: - SignalType signal_; + Atomic signal_; }; // Needed so compiler knows these template classes exist @@ -126,7 +214,7 @@ template class ITypedCANSignal; template class ITypedCANSignal; static constexpr int kCANTemplateFloatDenominator{1 << 16}; // 2^16 -constexpr int CANTemplateConvertFloat(float value) { return value * kCANTemplateFloatDenominator; } +constexpr int CANTemplateConvertFloat(float value) { return static_cast(value * kCANTemplateFloatDenominator); } constexpr float CANTemplateGetFloat(int value) { return static_cast(value) / kCANTemplateFloatDenominator; } template @@ -179,6 +267,7 @@ class CANSignal : public ITypedCANSignal CANSignal() { static_assert(factor != 0, "The integer representation of the factor for a CAN signal must not be 0"); + this->signal_ = static_cast(0); } void EncodeSignal(uint64_t *buffer) override { InternalEncodeSignal(buffer); } @@ -188,15 +277,17 @@ class CANSignal : public ITypedCANSignal { if (byte_order == ICANSignal::ByteOrder::kLittleEndian) { - *buffer |= (static_cast(this->signal_) << position) & mask; + SignalType signal = this->signal_; + *buffer |= (static_cast(signal) << position) & mask; } else { + SignalType signal = this->signal_; uint8_t temp_reversed_buffer[8]{0}; void *temp_reversed_buffer_ptr{ temp_reversed_buffer}; // intermediate as void* to get rid of strict aliasing compiler warnings *reinterpret_cast(temp_reversed_buffer_ptr) |= - (static_cast(this->signal_) << (64 - (position + length))); + (static_cast(signal) << (64 - (position + length))); std::reverse(std::begin(temp_reversed_buffer), std::end(temp_reversed_buffer)); *buffer |= *reinterpret_cast(temp_reversed_buffer_ptr) & mask; } @@ -207,18 +298,19 @@ class CANSignal : public ITypedCANSignal { if (byte_order == ICANSignal::ByteOrder::kLittleEndian) { - *buffer |= (static_cast( - ((this->signal_ - CANTemplateGetFloat(offset)) / CANTemplateGetFloat(factor))) - << position) - & mask; + SignalType signal = this->signal_; + *buffer |= + (static_cast(((signal - CANTemplateGetFloat(offset)) / CANTemplateGetFloat(factor))) + << position) + & mask; } else { + SignalType signal = this->signal_; uint8_t temp_reversed_buffer[8]{0}; void *temp_reversed_buffer_ptr{temp_reversed_buffer}; *reinterpret_cast(temp_reversed_buffer_ptr) |= - (static_cast( - ((this->signal_ - CANTemplateGetFloat(offset)) / CANTemplateGetFloat(factor))) + (static_cast(((signal - CANTemplateGetFloat(offset)) / CANTemplateGetFloat(factor))) << (64 - (position + length))); std::reverse(std::begin(temp_reversed_buffer), std::end(temp_reversed_buffer)); *buffer |= *reinterpret_cast(temp_reversed_buffer_ptr) & mask; @@ -257,11 +349,12 @@ class CANSignal : public ITypedCANSignal uint8_t temp_buffer[8]{0}; void *temp_buffer_ptr{temp_buffer}; *reinterpret_cast(temp_buffer_ptr) = *buffer & mask; - this->signal_ = static_cast( - (((*reinterpret_cast(temp_buffer_ptr)) << (64 - (position + length)) - >> (64 - length)) - * CANTemplateGetFloat(factor)) - + CANTemplateGetFloat(offset)); + this->signal_ = + static_cast((static_cast((*reinterpret_cast(temp_buffer_ptr)) + << (64 - (position + length)) + >> (64 - length)) + * CANTemplateGetFloat(factor)) + + CANTemplateGetFloat(offset)); } else { @@ -270,7 +363,8 @@ class CANSignal : public ITypedCANSignal *reinterpret_cast(temp_buffer_ptr) = *buffer & mask; std::reverse(std::begin(temp_buffer), std::end(temp_buffer)); this->signal_ = static_cast( - (((*reinterpret_cast(temp_buffer_ptr)) << position >> (64 - length)) + (static_cast((*reinterpret_cast(temp_buffer_ptr)) << position + >> (64 - length)) * CANTemplateGetFloat(factor)) + CANTemplateGetFloat(offset)); } @@ -313,8 +407,9 @@ class ICANTXMessage { public: virtual uint32_t GetID() = 0; +#if !defined(NATIVE) // workaround for unit tests virtual VirtualTimer &GetTransmitTimer() = 0; - virtual void EncodeSignals() = 0; +#endif virtual void EncodeAndSend() = 0; }; @@ -322,7 +417,7 @@ class ICANRXMessage { public: virtual uint32_t GetID() = 0; - virtual void DecodeSignals(CANMessage message) = 0; + virtual void DecodeSignals(CANMessage message) = 0; // Decodes signals if ID matches }; class ICAN @@ -345,6 +440,74 @@ class ICAN virtual void Tick() = 0; }; +/** + * @brief This class is used to test CAN interface implementations without accessing hardware + * + */ +class MockCAN : public ICAN +{ +public: + void Initialize(BaudRate baud __attribute__((unused))) {} + bool SendMessage(CANMessage &msg) + { + /* for (size_t i = 0; i < rx_messages_.size(); i++) + { + if (rx_messages_[i]->GetID() == msg.id_) + { + rx_messages_[i]->DecodeSignals(msg); + } + } */ + last_message = msg; + return true; + } + void RegisterRXMessage(ICANRXMessage &msg __attribute__((unused))) + { /* rx_messages_.push_back(&msg); */ + } + void Tick() {} + + CANMessage last_message{0, 8, std::array{0}}; + +private: + // std::vector rx_messages_; //not working in native unit tests +}; + +class IMultiplexedSignalGroup +{ +public: + virtual ICANSignal *at(size_t index) = 0; + virtual size_t size() const = 0; + + uint64_t multiplexor_value_{0}; + bool always_active_{false}; +}; + +template +class MultiplexedSignalGroup : public std::array, public IMultiplexedSignalGroup +{ +public: + template + MultiplexedSignalGroup(uint64_t multiplexor_value, Ts &... signals) + : std::array{&signals...} + { + static_assert(sizeof...(signals) == num_signals, "Wrong number of signals passed into SignalGroup."); + multiplexor_value_ = multiplexor_value; + always_active_ = false; + } + + template + MultiplexedSignalGroup(bool always_active, uint64_t multiplexor_value, Ts &... signals) + : std::array{&signals...} + { + static_assert(sizeof...(signals) == num_signals, "Wrong number of signals passed into SignalGroup."); + multiplexor_value_ = multiplexor_value; + always_active_ = always_active; + } + + ICANSignal *at(size_t index) override { return std::array::at(index); } + + size_t size() const override { return std::array::size(); } +}; + /** * @brief A class for storing signals in a message that sends every period */ @@ -370,7 +533,7 @@ class CANTXMessage : public ICANTXMessage uint8_t length, uint32_t period, ICANSignal &signal_1, - Ts &...signals) + Ts &... signals) : can_interface_{can_interface}, message_{id, extended_id, length, std::array()}, transmit_timer_{period, [this]() { this->EncodeAndSend(); }, VirtualTimer::Type::kRepeating}, @@ -391,7 +554,7 @@ class CANTXMessage : public ICANTXMessage * @param signals The ICANSignals contained in the message */ CANTXMessage( - ICAN &can_interface, uint32_t id, uint8_t length, uint32_t period, ICANSignal &signal_1, Ts &...signals) + ICAN &can_interface, uint32_t id, uint8_t length, uint32_t period, ICANSignal &signal_1, Ts &... signals) : CANTXMessage(can_interface, id, false, length, period, signal_1, signals...) { } @@ -416,7 +579,7 @@ class CANTXMessage : public ICANTXMessage uint32_t period, VirtualTimerGroup &timer_group, ICANSignal &signal_1, - Ts &...signals) + Ts &... signals) : CANTXMessage(can_interface, id, extended_id, length, period, signal_1, signals...) { timer_group.AddTimer(transmit_timer_); @@ -441,7 +604,7 @@ class CANTXMessage : public ICANTXMessage uint32_t period, VirtualTimerGroup &timer_group, ICANSignal &signal_1, - Ts &...signals) + Ts &... signals) : CANTXMessage(can_interface, id, false, length, period, timer_group, signal_1, signals...) { } @@ -452,9 +615,11 @@ class CANTXMessage : public ICANTXMessage can_interface_.SendMessage(message_); } - uint32_t GetID() { return message_.id_; } + uint32_t GetID() override { return message_.id_; } - VirtualTimer &GetTransmitTimer() { return transmit_timer_; } +#if !defined(NATIVE) // workaround for unit tests + VirtualTimer &GetTransmitTimer() override { return transmit_timer_; } +#endif void Enable() { transmit_timer_.Enable(); } void Disable() { transmit_timer_.Disable(); } @@ -476,6 +641,324 @@ class CANTXMessage : public ICANTXMessage } }; +template +class MultiplexedCANTXMessage : public ICANTXMessage +{ +public: + template + /** + * @brief Construct a new MultiplexedCANTXMessage object, for use if your message uses a multiplexor + * + * @param can_interface The ICAN object the message will be transmitted on + * @param id The ID of the CAN message + * @param extended_id Whether the ID is extended (true) or standard (false) + * @param length The length in bytes of the message + * @param period The transmit period in ms of the message + * @param multiplexor_values_to_transmit An array of multiplexor values that gets cycled through to automatically + * transmit the messages + * @param multiplexor The CANSignal that will be used to multiplex the message + * @param signal_groups The SignalGroups contained in the message + */ + MultiplexedCANTXMessage(ICAN &can_interface, + uint32_t id, + bool extended_id, + uint8_t length, + uint32_t period, + std::array multiplexor_values_to_transmit, + ITypedCANSignal &multiplexor, + Ts &... signal_groups) + : can_interface_{can_interface}, + message_{id, extended_id, length, std::array()}, +#if !defined(NATIVE) // workaround for unit tests + transmit_timer_{period, [this]() { this->EncodeAndSend(); }, VirtualTimer::Type::kRepeating}, +#endif + multiplexor_values_to_transmit_{multiplexor_values_to_transmit}, + multiplexor_{&multiplexor}, + signal_groups_{&signal_groups...} + { + static_assert(sizeof...(signal_groups) == num_groups, + "Wrong number of signal groups passed into MultiplexedCANTXMessage."); + for (size_t i = 0; i < signal_groups_.size(); i++) + { + if (signal_groups_.at(i)->always_active_) + { + has_always_active_signal_group_ = true; + always_active_signal_group_index_ = i; + break; + } + } + } + + template + /** + * @brief Construct a new MultiplexedCANTXMessage object, for use if your message uses a multiplexor, default to + * standard id + * + * @param can_interface The ICAN object the message will be transmitted on + * @param id The ID of the CAN message + * @param length The length in bytes of the message + * @param period The transmit period in ms of the message + * @param multiplexor_values_to_transmit An array of multiplexor values that gets cycled through to automatically + * transmit the messages + * @param multiplexor The CANSignal that will be used to multiplex the message + * @param signal_groups The SignalGroups contained in the message + */ + MultiplexedCANTXMessage(ICAN &can_interface, + uint32_t id, + uint8_t length, + uint32_t period, + std::array multiplexor_values_to_transmit, + ITypedCANSignal &multiplexor, + Ts &... signal_groups) + : MultiplexedCANTXMessage( + can_interface, id, false, length, period, multiplexor_values_to_transmit, multiplexor, signal_groups...) + { + } + + template + /** + * @brief Construct a new MultiplexedCANTXMessage object, for use if your message uses a multiplexor, and + * automatically adds it to a VirtualTimerGroup + * + * @param can_interface The ICAN object the message will be transmitted on + * @param id The ID of the CAN message + * @param extended_id Whether the ID is extended (true) or standard (false) + * @param length The length in bytes of the message + * @param period The transmit period in ms of the message + * @param timer_group A timer group to add the transmit timer to + * @param multiplexor_values_to_transmit An array of multiplexor values that gets cycled through to automatically + * transmit the messages + * @param multiplexor The CANSignal that will be used to multiplex the message + * @param signal_groups The SignalGroups contained in the message + */ + MultiplexedCANTXMessage(ICAN &can_interface, + uint32_t id, + bool extended_id, + uint8_t length, + uint32_t period, + VirtualTimerGroup &timer_group, + std::array multiplexor_values_to_transmit, + ITypedCANSignal &multiplexor, + Ts &... signal_groups) + : MultiplexedCANTXMessage(can_interface, + id, + extended_id, + length, + period, + multiplexor_values_to_transmit, + multiplexor, + signal_groups...) + { +#if !defined(NATIVE) // workaround for unit tests + timer_group.AddTimer(transmit_timer_); +#endif + } + + template + /** + * @brief Construct a new MultiplexedCANTXMessage object, for use if your message uses a multiplexor, and + * automatically adds it to a VirtualTimerGroup, default to standard id + * + * @param can_interface The ICAN object the message will be transmitted on + * @param id The ID of the CAN message + * @param length The length in bytes of the message + * @param period The transmit period in ms of the message + * @param timer_group A timer group to add the transmit timer to + * @param multiplexor_values_to_transmit An array of multiplexor values that gets cycled through to automatically + * transmit the messages + * @param multiplexor The CANSignal that will be used to multiplex the message + * @param signal_groups The SignalGroups contained in the message + */ + MultiplexedCANTXMessage(ICAN &can_interface, + uint32_t id, + uint8_t length, + uint32_t period, + VirtualTimerGroup &timer_group, + std::array multiplexor_values_to_transmit, + ITypedCANSignal &multiplexor, + Ts &... signal_groups) + : MultiplexedCANTXMessage(can_interface, + id, + false, + length, + period, + timer_group, + multiplexor_values_to_transmit, + multiplexor, + signal_groups...) + { + } + + void EncodeAndSend() override // increments multiplexor automatically + { + *multiplexor_ = multiplexor_values_to_transmit_.at(static_cast(multiplexor_index_)); + EncodeSignals(GetSignalGroupIndex(*multiplexor_)); + can_interface_.SendMessage(message_); + if (multiplexor_index_ < num_multiplexors_to_transmit - 1) + { + multiplexor_index_ += 1; + } + else + { + multiplexor_index_ = 0; + } + } + + void EncodeAndSend(MultiplexorType multiplexor_value) + { + *multiplexor_ = multiplexor_value; + EncodeSignals(GetSignalGroupIndex(*multiplexor_)); + can_interface_.SendMessage(message_); + } + + uint32_t GetID() override { return message_.id_; } + +#if !defined(NATIVE) // workaround for unit tests + VirtualTimer &GetTransmitTimer() override { return transmit_timer_; } +#endif + + void Enable() { transmit_timer_.Enable(); } + void Disable() { transmit_timer_.Disable(); } + +private: + ICAN &can_interface_; + CANMessage message_; + VirtualTimer transmit_timer_; + std::array multiplexor_values_to_transmit_; + ITypedCANSignal *multiplexor_; + std::array signal_groups_; + bool has_always_active_signal_group_{false}; + uint64_t always_active_signal_group_index_{0}; + + uint64_t multiplexor_index_ = 0; + + uint64_t GetSignalGroupIndex(MultiplexorType multiplexor_value) + { + size_t index = 0xFFFFFFFFul; // init to invalid value + for (size_t i = 0; i < num_groups; i++) + { + if (static_cast(multiplexor_value) == signal_groups_.at(i)->multiplexor_value_) + { + index = i; + break; + } + } + return index; + } + + void EncodeSignals(uint64_t signal_group_index) + { + uint8_t temp_raw[8]{0}; + multiplexor_->EncodeSignal(reinterpret_cast(temp_raw)); + if (has_always_active_signal_group_) + { + for (uint8_t i = 0; i < signal_groups_.at(static_cast(always_active_signal_group_index_))->size(); + i++) + { + signal_groups_.at(static_cast(always_active_signal_group_index_)) + ->at(i) + ->EncodeSignal(reinterpret_cast(temp_raw)); + } + } + if (signal_group_index != 0xFFFFFFFFul) // not invalid value + { + for (uint8_t i = 0; i < signal_groups_.at(static_cast(signal_group_index))->size(); i++) + { + signal_groups_.at(static_cast(signal_group_index)) + ->at(i) + ->EncodeSignal(reinterpret_cast(temp_raw)); + } + } + + std::copy(std::begin(temp_raw), std::end(temp_raw), message_.data_.begin()); + } +}; + +template +class PGNCANTXMessage : public ICANTXMessage +{ +public: + template + /** + * @brief Construct a new PGNCANTXMessage object for use with J1939 or NMEA2000 + * + * @param can_interface The ICAN object the message will be transmitted on + * @param id The ID of the CAN message as a PGN object + * @param length The length in bytes of the message + * @param period The transmit period in ms of the message + * @param signals The ICANSignals contained in the message + */ + PGNCANTXMessage(ICAN &can_interface, + PGNCANMessage::ExtendedId id, + uint8_t length, + uint32_t period, + ICANSignal &signal_1, + Ts &... signals) + : can_interface_{can_interface}, + message_{id, length, std::array()}, + transmit_timer_{period, [this]() { this->EncodeAndSend(); }, VirtualTimer::Type::kRepeating}, + signals_{&signal_1, &signals...} + { + static_assert(sizeof...(signals) == num_signals - 1, "Wrong number of signals passed into PGNCANTXMessage."); + } + + template + /** + * @brief Construct a new PGNCANTXMessage object for use with J1939 or NMEA2000 and automatically adds it to a + * VirtualTimerGroup + * + * @param can_interface The ICAN object the message will be transmitted on + * @param id The ID of the CAN message as a PGN object + * @param extended_id Whether the ID is extended (true) or standard (false) + * @param length The length in bytes of the message + * @param period The transmit period in ms of the message + * @param timer_group A timer group to add the transmit timer to + * @param signals The ICANSignals contained in the message + */ + PGNCANTXMessage(ICAN &can_interface, + PGNCANMessage::ExtendedId id, + uint8_t length, + uint32_t period, + VirtualTimerGroup &timer_group, + ICANSignal &signal_1, + Ts &... signals) + : PGNCANTXMessage(can_interface, id, length, period, signal_1, signals...) + { + timer_group.AddTimer(transmit_timer_); + } + + void EncodeAndSend() override + { + EncodeSignals(); + can_interface_.SendMessage(message_); + } + + uint32_t GetID() override { return message_.id_; } + +#if !defined(NATIVE) // workaround for unit tests + VirtualTimer &GetTransmitTimer() override { return transmit_timer_; } +#endif + + void Enable() { transmit_timer_.Enable(); } + void Disable() { transmit_timer_.Disable(); } + +private: + ICAN &can_interface_; + PGNCANMessage message_; + VirtualTimer transmit_timer_; + std::array signals_; + + void EncodeSignals() + { + uint8_t temp_raw[8]{0}; + for (uint8_t i = 0; i < num_signals; i++) + { + signals_.at(i)->EncodeSignal(reinterpret_cast(temp_raw)); + } + std::copy(std::begin(temp_raw), std::end(temp_raw), message_.data_.begin()); + } +}; + /** * @brief A class for storing signals that get updated every time a matching message is received */ @@ -489,7 +972,7 @@ class CANRXMessage : public ICANRXMessage std::function get_millis, std::function callback_function, ICANSignal &signal_1, - Ts &...signals) + Ts &... signals) : can_interface_{can_interface}, id_{id}, get_millis_{get_millis}, @@ -505,7 +988,7 @@ class CANRXMessage : public ICANRXMessage uint32_t id, std::function get_millis, ICANSignal &signal_1, - Ts &...signals) + Ts &... signals) : CANRXMessage{can_interface, id, get_millis, nullptr, signal_1, signals...} { } @@ -518,13 +1001,13 @@ class CANRXMessage : public ICANRXMessage uint32_t id, std::function callback_function, ICANSignal &signal_1, - Ts &...signals) + Ts &... signals) : CANRXMessage{can_interface, id, []() { return millis(); }, callback_function, signal_1, signals...} { } template - CANRXMessage(ICAN &can_interface, uint32_t id, ICANSignal &signal_1, Ts &...signals) + CANRXMessage(ICAN &can_interface, uint32_t id, ICANSignal &signal_1, Ts &... signals) : CANRXMessage{can_interface, id, []() { return millis(); }, nullptr, signal_1, signals...} { } @@ -534,6 +1017,10 @@ class CANRXMessage : public ICANRXMessage void DecodeSignals(CANMessage message) { + if (message.id_ != id_) + { + return; + } uint64_t temp_raw = *reinterpret_cast(message.data_.data()); for (uint8_t i = 0; i < num_signals; i++) { @@ -549,8 +1036,8 @@ class CANRXMessage : public ICANRXMessage last_receive_time_ = get_millis_(); } - uint32_t GetLastReceiveTime() { return last_receive_time_; } - uint32_t GetTimeSinceLastReceive() { return get_millis_() - last_receive_time_; } + uint32_t GetLastReceiveTime() const { return last_receive_time_; } + uint32_t GetTimeSinceLastReceive() const { return get_millis_() - last_receive_time_; } private: ICAN &can_interface_; @@ -566,4 +1053,261 @@ class CANRXMessage : public ICANRXMessage uint64_t raw_message; uint32_t last_receive_time_ = 0; -}; \ No newline at end of file +}; + +template +class MultiplexedCANRXMessage : public ICANRXMessage +{ +public: + /** + * @brief Construct a new MultiplexedCANRXMessage object, for use if your message uses a multiplexor + * + * @param can_interface The ICAN object the message will be received from + * @param id The ID of the message + * @param get_millis A function to get the current time in milliseconds + * @param callback_function A function to be called on message reception + * @param multiplexor The signal to be used as a multiplexor + * @param signal_groups The signalgroups to be decoded, selected based on the value of the multiplexor + */ + template + MultiplexedCANRXMessage(ICAN &can_interface, + uint32_t id, + std::function get_millis, + std::function callback_function, + ITypedCANSignal &multiplexor, + Ts &... signal_groups) + : can_interface_{can_interface}, + id_{id}, + get_millis_{get_millis}, + callback_function_{callback_function}, + multiplexor_{&multiplexor}, + signal_groups_{&signal_groups...} + { + static_assert(sizeof...(signal_groups) == num_groups, + "Wrong number of SignalGroups passed into MultiplexedCANRXMessage."); + can_interface_.RegisterRXMessage(*this); + for (size_t i = 0; i < signal_groups_.size(); i++) + { + if (signal_groups_.at(i)->always_active_) + { + has_always_active_signal_group_ = true; + always_active_signal_group_index_ = i; + break; + } + } + } + + template + MultiplexedCANRXMessage(ICAN &can_interface, + uint32_t id, + std::function get_millis, + ITypedCANSignal &multiplexor, + Ts &... signal_groups) + : MultiplexedCANRXMessage{can_interface, id, get_millis, nullptr, multiplexor, signal_groups...} + { + } + +// If compiling for Arduino, automatically uses millis() instead of requiring a std::function to get the +// current time +#ifdef ARDUINO + template + MultiplexedCANRXMessage(ICAN &can_interface, + uint32_t id, + std::function callback_function, + ITypedCANSignal &multiplexor, + Ts &... signal_groups) + : MultiplexedCANRXMessage{ + can_interface, id, []() { return millis(); }, callback_function, multiplexor, signal_groups...} + { + } + + template + MultiplexedCANRXMessage(ICAN &can_interface, + uint32_t id, + ITypedCANSignal &multiplexor, + Ts &... signal_groups) + : MultiplexedCANRXMessage{can_interface, id, []() { return millis(); }, nullptr, multiplexor, signal_groups...} + { + } +#endif + + uint32_t GetID() { return id_; } + + void DecodeSignals(CANMessage message) + { + if (message.id_ != id_) + { + return; + } + + uint64_t temp_raw = *reinterpret_cast(message.data_.data()); + + if (has_always_active_signal_group_) + { + for (uint8_t i = 0; i < signal_groups_.at(static_cast(always_active_signal_group_index_))->size(); + i++) + { + signal_groups_.at(static_cast(always_active_signal_group_index_)) + ->at(i) + ->DecodeSignal(&temp_raw); + } + } + + multiplexor_->DecodeSignal(&temp_raw); + size_t multiplexor_index = 0xFFFFFFFFul; // init to invalid value + for (size_t i = 0; i < num_groups; i++) + { + MultiplexorType multiplexor_value = *multiplexor_; + if (static_cast(multiplexor_value) == signal_groups_.at(i)->multiplexor_value_) + { + multiplexor_index = i; + break; + } + } + + if (multiplexor_index == 0xFFFFFFFFull) // If the multiplexor is invalid, don't decode any signals + { + return; + } + for (uint8_t i = 0; i < signal_groups_.at(multiplexor_index)->size(); i++) + { + signal_groups_.at(multiplexor_index)->at(i)->DecodeSignal(&temp_raw); + } + + // DecodeSignals is called only on message received + if (callback_function_) + { + callback_function_(); + } + + last_receive_time_ = get_millis_(); + } + + uint32_t GetLastReceiveTime() const { return last_receive_time_; } + uint32_t GetTimeSinceLastReceive() const { return get_millis_() - last_receive_time_; } + +private: + ICAN &can_interface_; + uint32_t id_; + // A function to get the current time in millis on the current platform + std::function get_millis_; + + // The callback function should be a very short function that will get called every time a new message is received. + std::function callback_function_; + + ITypedCANSignal *multiplexor_; + std::array signal_groups_; + bool has_always_active_signal_group_{false}; + uint64_t always_active_signal_group_index_{0}; + + uint64_t multiplexor_index{0}; + + uint64_t raw_message; + + uint32_t last_receive_time_ = 0; +}; + +template +class PGNCANRXMessage : public ICANRXMessage +{ +public: + /** + * @brief Construct a new PGNCANRXMessage object, for use receiving J1939 or NMEA2000 messages + * + * @param can_interface The ICAN object the message will be received on + * @param id The ID of the message, as a PGN object + * @param get_millis A function to get the current time in milliseconds + * @param callback_function An optional callback function to get called on message reception + * @param signals The signals contained in the message + */ + template + PGNCANRXMessage(ICAN &can_interface, + PGNCANMessage::ExtendedId id, + std::function get_millis, + std::function callback_function, + ICANSignal &signal_1, + Ts &... signals) + : can_interface_{can_interface}, + id_{id}, + get_millis_{get_millis}, + callback_function_{callback_function}, + signals_{&signal_1, &signals...} + { + static_assert(sizeof...(signals) == num_signals - 1, "Wrong number of signals passed into CANRXMessage."); + can_interface_.RegisterRXMessage(*this); + } + + template + PGNCANRXMessage(ICAN &can_interface, + PGNCANMessage::ExtendedId id, + std::function get_millis, + ICANSignal &signal_1, + Ts &... signals) + : PGNCANRXMessage{can_interface, id, get_millis, nullptr, signal_1, signals...} + { + } + +// If compiling for Arduino, automatically uses millis() instead of requiring a std::function to get the +// current time +#ifdef ARDUINO + template + PGNCANRXMessage(ICAN &can_interface, + PGNCANMessage::ExtendedId id, + std::function callback_function, + ICANSignal &signal_1, + Ts &... signals) + : PGNCANRXMessage{can_interface, id, []() { return millis(); }, callback_function, signal_1, signals...} + { + } + + template + PGNCANRXMessage(ICAN &can_interface, PGNCANMessage::ExtendedId id, ICANSignal &signal_1, Ts &... signals) + : PGNCANRXMessage{can_interface, id, []() { return millis(); }, nullptr, signal_1, signals...} + { + } +#endif + + uint32_t GetID() { return id_; } + + void DecodeSignals(CANMessage message) + { + PGNCANMessage::PGN incoming_pgn = + static_cast(static_cast(message.id_).extended_id.pgn); + PGNCANMessage::PGN pgn = static_cast(id_.extended_id.pgn); + if (incoming_pgn.raw != pgn.raw) + { + return; + } + uint64_t temp_raw = *reinterpret_cast(message.data_.data()); + for (uint8_t i = 0; i < num_signals; i++) + { + signals_[i]->DecodeSignal(&temp_raw); + } + + // DecodeSignals is called only on message received + if (callback_function_) + { + callback_function_(); + } + + last_receive_time_ = get_millis_(); + } + + uint32_t GetLastReceiveTime() const { return last_receive_time_; } + uint32_t GetTimeSinceLastReceive() const { return get_millis_() - last_receive_time_; } + +private: + ICAN &can_interface_; + PGNCANMessage::ExtendedId id_; + // A function to get the current time in millis on the current platform + std::function get_millis_; + + // The callback function should be a very short function that will get called every time a new message is received. + std::function callback_function_; + + std::array signals_; + + uint64_t raw_message; + + uint32_t last_receive_time_ = 0; +}; diff --git a/include/esp_can.h b/include/esp_can.h index d51a7ea..b8273b9 100644 --- a/include/esp_can.h +++ b/include/esp_can.h @@ -1,8 +1,8 @@ #pragma once -#include - #include "can_interface.h" +#include "driver/gpio.h" +#include "driver/twai.h" class ESPCAN : public ICAN { @@ -23,35 +23,9 @@ class ESPCAN : public ICAN void Tick() override; - static void ProcessReceive(void *pvParameter) - { - std::array msg_data{}; - CAN_frame_t rx_frame; - uint32_t msg_id; - uint8_t msg_len; - - if ((xQueueReceive(CAN_cfg.rx_queue, &rx_frame, 3 * portTICK_PERIOD_MS) == pdTRUE)) - { - msg_id = rx_frame.MsgID; - msg_len = rx_frame.FIR.B.DLC; - - for (int i = 0; i < msg_len; i++) - { - msg_data[i] = rx_frame.data.u8[i]; - } - CANMessage received_message{static_cast(msg_id), msg_len, msg_data}; - for (size_t i = 0; i < rx_messages_.size(); i++) - { - if (rx_messages_[i]->GetID() == received_message.id_) - { - rx_messages_[i]->DecodeSignals(received_message); - } - } - } - portYIELD_FROM_ISR(); - } - private: static std::vector rx_messages_; - const uint8_t kRxQueueSize; + twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(GPIO_NUM_5, GPIO_NUM_4, TWAI_MODE_NORMAL); + twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS(); + twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL(); }; \ No newline at end of file diff --git a/include/esp_can_update.h b/include/esp_can_update.h new file mode 100644 index 0000000..c5d99fe --- /dev/null +++ b/include/esp_can_update.h @@ -0,0 +1,152 @@ +#pragma once + +#include +#include + +#include + +#include "CAN.h" + +class CANUpdate +{ +public: + CANUpdate(uint32_t update_id, ICAN &can_bus, VirtualTimerGroup &timer_group) + : kUpdateId{update_id}, can_interface_{can_bus}, timer_group_{timer_group} + { + update_progress_message_.Disable(); + timer_group_.AddTimer(100, [this]() { + if (update_data_message_.GetTimeSinceLastReceive() >= kUpdateTimeout) + { + update_progress_message_.Disable(); + update_started_ = false; + received_md5_ = false; + received_len_ = false; + written_ = false; + received_md5_arr_.at(0) = false; + received_md5_arr_.at(1) = false; + received_md5_arr_.at(2) = false; + received_md5_arr_.at(3) = false; + update_block_idx_ = 0; + Update.abort(); + } + }); + } + +private: + const uint32_t kUpdateId; + ICAN &can_interface_; + VirtualTimerGroup &timer_group_; + + const uint32_t kUpdateTimeout{500}; + + enum class MessageType : uint8_t + { + kUpdateStart = 0, + kUpdateData = 1, + kMd5 = 2 + }; + MakeUnsignedCANSignal(MessageType, 0, 8, 1, 0) message_type_{}; + MakeUnsignedCANSignal(uint32_t, 8, 32, 1, 0) update_length_{}; + MakeUnsignedCANSignal(uint16_t, 8, 8, 1, 0) update_md5_idx_{}; + MakeUnsignedCANSignal(uint32_t, 16, 32, 1, 0) update_md5_{}; + MakeUnsignedCANSignal(uint32_t, 8, 24, 1, 0) data_block_index_{}; + MakeUnsignedCANSignal(uint32_t, 32, 32, 1, 0) update_data_{}; + + MultiplexedSignalGroup<1> length_signal_group_{static_cast(MessageType::kUpdateStart), update_length_}; + MultiplexedSignalGroup<2> data_signal_group_{ + static_cast(MessageType::kUpdateData), data_block_index_, update_data_}; + MultiplexedSignalGroup<2> md5_signal_group_{static_cast(MessageType::kMd5), update_md5_idx_, update_md5_}; + + MakeUnsignedCANSignal(uint32_t, 0, 24, 1, 0) update_block_idx_{}; + MakeUnsignedCANSignal(bool, 24, 1, 1, 0) received_len_{}; + MakeUnsignedCANSignal(bool, 25, 1, 1, 0) received_md5_{}; + MakeUnsignedCANSignal(bool, 26, 1, 1, 0) written_{}; + + CANTXMessage<4> update_progress_message_{ + can_interface_, kUpdateId + 1, 4, 10, timer_group_, update_block_idx_, received_len_, received_md5_, written_}; + + bool update_started_ = false; + std::array received_md5_arr_ = {false, false, false, false}; + std::array md5_arr_{}; + char md5_cstr_[33]; + + MultiplexedCANRXMessage<3, MessageType> update_data_message_{ + can_interface_, + kUpdateId, + [this]() { + if (message_type_ == MessageType::kMd5) + { + update_progress_message_.Enable(); + received_md5_arr_.at(static_cast(update_md5_idx_)) = true; + md5_arr_[static_cast(update_md5_idx_)] = update_md5_; + if (received_md5_arr_.at(0) && received_md5_arr_.at(1) && received_md5_arr_.at(2) + && received_md5_arr_.at(3)) + { + received_md5_ = true; + } + } + else if (!update_started_ && message_type_ == MessageType::kUpdateStart) + { + if (!Update.begin(update_length_)) + { + Update.printError(Serial); + } + else + { + sprintf(md5_cstr_, + "%08x%08x%08x%08x", + __bswap32(md5_arr_.at(0)), + __bswap32(md5_arr_.at(1)), + __bswap32(md5_arr_.at(2)), + __bswap32(md5_arr_.at(3))); + Serial.printf("%s\n", md5_cstr_); + Update.setMD5(md5_cstr_); + update_block_idx_ = 0; + received_len_ = true; + update_started_ = true; + written_ = false; + update_progress_message_.EncodeAndSend(); + update_progress_message_.Enable(); + } + } + else if (update_started_ && message_type_ == MessageType::kUpdateData) + { + uint32_t data = update_data_; + if (static_cast(update_block_idx_) == data_block_index_) + { + if (data_block_index_ * 4 >= update_length_ - 4) + { + Update.write(reinterpret_cast(&data), update_length_ - (data_block_index_ * 4)); + written_ = true; + update_progress_message_.EncodeAndSend(); + + if (Update.end()) + { + Serial.println("Update success!"); + ESP.restart(); + } + else + { + Update.printError(Serial); + Serial.printf("Expected MD5: %s\n", md5_cstr_); + update_started_ = false; + update_progress_message_.Disable(); + } + } + else + { + Update.write(reinterpret_cast(&data), 4); + written_ = true; + update_progress_message_.EncodeAndSend(); + update_block_idx_ += 1; + written_ = false; + update_progress_message_.EncodeAndSend(); + } + } + } + }, + message_type_, + length_signal_group_, + data_signal_group_, + md5_signal_group_}; +}; diff --git a/include/freertos_atomic.h b/include/freertos_atomic.h new file mode 100644 index 0000000..9e4ac6e --- /dev/null +++ b/include/freertos_atomic.h @@ -0,0 +1,30 @@ +#pragma once + +#include "FreeRTOS.h" +#include "task.h" // must define taskENTER_CRITICAL() and taskEXIT_CRITICAL() + +template +class Atomic +{ +public: + Atomic() {} + + operator t() const + { + taskENTER_CRITICAL(); + t val = val_; + taskEXIT_CRITICAL(); + return val; + } + + Atomic &operator=(const t &val) + { + taskENTER_CRITICAL(); + val_ = val; + taskEXIT_CRITICAL(); + return *this; + } + +private: + t val_; +}; diff --git a/library.json b/library.json index 67b9cc2..d43ae34 100644 --- a/library.json +++ b/library.json @@ -3,5 +3,26 @@ "version": "0.0.0+20220926210002", "dependencies": { "timers": "https://github.com/NU-Formula-Racing/timers.git" - } + }, + "build": { + "extraScript": "scripts/esp_can_update.py" + }, + "examples": [ + { + "name": "CAN_Interface_Demo", + "base": "examples/CAN_Interface_Demo", + "files": [ + "platformio.ini", + "src/main.cpp" + ] + }, + { + "name": "CAN_Update_Demo", + "base": "examples/CAN_Update_Demo", + "files": [ + "platformio.ini", + "src/main.cpp" + ] + } + ] } diff --git a/scripts/esp_can_update.dbc b/scripts/esp_can_update.dbc new file mode 100644 index 0000000..9c1b3c9 --- /dev/null +++ b/scripts/esp_can_update.dbc @@ -0,0 +1,56 @@ +VERSION "" + + +NS_ : + NS_DESC_ + CM_ + BA_DEF_ + BA_ + VAL_ + CAT_DEF_ + CAT_ + FILTER + BA_DEF_DEF_ + EV_DATA_ + ENVVAR_DATA_ + SGTYPE_ + SGTYPE_VAL_ + BA_DEF_SGTYPE_ + BA_SGTYPE_ + SIG_TYPE_REF_ + VAL_TABLE_ + SIG_GROUP_ + SIG_VALTYPE_ + SIGTYPE_VALTYPE_ + BO_TX_BU_ + BA_DEF_REL_ + BA_REL_ + BA_DEF_DEF_REL_ + BU_SG_REL_ + BU_EV_REL_ + BU_BO_REL_ + SG_MUL_VAL_ + +BS_: + +BU_: + + +BO_ 1330 update_data_message: 8 Vector__XXX + SG_ message_type M : 0|8@1+ (1,0) [0|0] "" Vector__XXX + SG_ update_length m0 : 8|32@1+ (1,0) [0|0] "" Vector__XXX + SG_ update_md5 m2 : 16|32@1+ (1,0) [0|0] "" Vector__XXX + SG_ data_block_index m1 : 8|24@1+ (1,0) [0|0] "" Vector__XXX + SG_ update_data m1 : 32|32@1+ (1,0) [0|0] "" Vector__XXX + SG_ update_md5_idx m2 : 8|8@1+ (1,0) [0|0] "" Vector__XXX + +BO_ 1331 update_progress_message: 4 Vector__XXX + SG_ update_block_idx : 0|24@1+ (1,0) [0|0] "" Vector__XXX + SG_ received_len : 24|1@1+ (1,0) [0|0] "" Vector__XXX + SG_ received_md5 : 25|1@1+ (1,0) [0|0] "" Vector__XXX + SG_ written : 26|1@1+ (1,0) [0|0] "" Vector__XXX + + + +VAL_ 1330 message_type 0 "UpdateStart" 1 "UpdateData" 2 "Md5" ; + diff --git a/scripts/esp_can_update.py b/scripts/esp_can_update.py new file mode 100644 index 0000000..86bb3cf --- /dev/null +++ b/scripts/esp_can_update.py @@ -0,0 +1,193 @@ +import requests +import sys +import hashlib +import time +import math +from os.path import basename + +Import("env") + +try: + import can + import cantools + from tqdm import tqdm +except ImportError: + env.Execute("$PYTHONEXE -m pip install can") + env.Execute("$PYTHONEXE -m pip install cantools") + env.Execute("$PYTHONEXE -m pip install tqdm") + import can + import cantools + from tqdm import tqdm + +try: + import configparser +except ImportError: + import ConfigParser as configparser +# project_config = configparser.ConfigParser() +# project_config.read("platformio.ini") +# can_update_config = {k: v for k, v in project_config.items("can_update")} +can_update_config = env.GetProjectConfig().items("can_update", as_dict=True) +db = cantools.database.load_file("esp_can_update.dbc") + + +def on_upload(source, target, env): + firmware_path = str(source[0]) + + with open(firmware_path, "rb") as firmware: + firmware_bytes = firmware.read() + md5 = hashlib.md5(firmware_bytes).digest() + print(hashlib.md5(firmware_bytes).hexdigest()) + firmware.seek(0) + + data_message = db.get_message_by_name("update_data_message") + data_message.frame_id = int(can_update_config.get("update_message_id"), 0) + progress_message = db.get_message_by_name("update_progress_message") + progress_message.frame_id = data_message.frame_id + 1 + + can_bus = can.interface.Bus( + "can0", + bustype="socketcan", + bitrate=int(can_update_config.get("update_baud")), + ) + if can_bus is None: + env.Execute( + "sudo ip link set up can0 type can bitrate " + + can_update_config.get("update_baud") + ) + can_bus = can.interface.Bus( + "can0", + bustype="socketcan", + bitrate=int(can_update_config.get("update_baud")), + ) + + try: + data_message_data = data_message.encode( + { + "message_type": 2, + "update_md5": md5[i * 4] + + (md5[(i * 4) + 1] << 8) + + (md5[(i * 4) + 2] << 16) + + (md5[(i * 4) + 3] << 24), + "update_md5_idx": i, + } + ) + can_bus.send( + can.Message( + arbitration_id=data_message.frame_id, data=data_message_data + ) + ) + except: + env.Execute( + "sudo ip link set up can0 type can bitrate " + + can_update_config.get("update_baud") + ) + can_bus = can.interface.Bus( + "can0", + bustype="socketcan", + bitrate=int(can_update_config.get("update_baud")), + ) + + received_md5 = False + while not received_md5: + for i in range(4): + data_message_data = data_message.encode( + { + "message_type": 2, + "update_md5": md5[i * 4] + + (md5[(i * 4) + 1] << 8) + + (md5[(i * 4) + 2] << 16) + + (md5[(i * 4) + 3] << 24), + "update_md5_idx": i, + } + ) + can_bus.send( + can.Message( + arbitration_id=data_message.frame_id, data=data_message_data + ) + ) + time.sleep(0.02) + msg = can_bus.recv(0.1) + if (msg != None) and msg.arbitration_id == progress_message.frame_id: + received_progress_msg = db.decode_message( + "update_progress_message", msg.data + ) + if received_progress_msg["received_md5"]: + received_md5 = True + while msg is not None: + msg = can_bus.recv(0.00001) + + data_message_data = data_message.encode( + {"message_type": 0, "update_length": len(firmware_bytes)} + ) + received_len = False + + while not received_len: + can_bus.send( + can.Message( + arbitration_id=data_message.frame_id, data=data_message_data + ) + ) + time.sleep(0.01) + msg = can_bus.recv(0.05) + if (msg != None) and msg.arbitration_id == progress_message.frame_id: + received_progress_msg = db.decode_message( + "update_progress_message", msg.data + ) + if received_progress_msg["received_len"]: + received_len = True + while msg is not None: + msg = can_bus.recv(0.00001) + + bar = tqdm( + desc="Upload Progress", + total=len(firmware_bytes), + dynamic_ncols=True, + unit="B", + unit_scale=True, + ) + + for i in range(math.floor((len(firmware_bytes) + 3) / 4)): + current_bytes_written = False + while not current_bytes_written: + data_message_data = data_message.encode( + { + "message_type": 1, + "data_block_index": i, + "update_data": firmware_bytes[i * 4] + + (firmware_bytes[(i * 4) + 1] << 8) + + (firmware_bytes[(i * 4) + 2] << 16) + + (firmware_bytes[(i * 4) + 3] << 24), + } + ) + can_bus.send( + can.Message( + arbitration_id=data_message.frame_id, data=data_message_data + ) + ) + + msg = can_bus.recv(0.1) + while msg is not None: + if msg.arbitration_id == progress_message.frame_id: + received_progress_msg = db.decode_message( + "update_progress_message", msg.data + ) + # print(db.decode_message("update_progress_message", data_message_data)) + # print(i) + if ( + received_progress_msg["written"] == True + and received_progress_msg["update_block_idx"] == i + ) or (received_progress_msg["update_block_idx"] == i + 1): + current_bytes_written = True + msg = can_bus.recv(0.00001) + + if i % 1024 == 0: + bar.update(4096) + # time.sleep(0.1) + + can_bus.shutdown() + +try: + if env.GetProjectOption("upload_can") == "y": + DefaultEnvironment().Replace(UPLOADCMD=on_upload) +except: + pass diff --git a/src/ESP32CAN/CAN.c b/src/ESP32CAN/CAN.c deleted file mode 100644 index 960434a..0000000 --- a/src/ESP32CAN/CAN.c +++ /dev/null @@ -1,308 +0,0 @@ -/** - * @section License - * - * The MIT License (MIT) - * - * Copyright (c) 2017, Thomas Barth, barth-dev.de - * 2017, Jaime Breva, jbreva@nayarsystems.com - * 2018, Michael Wagner, mw@iot-make.de - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -#ifdef ARDUINO_ARCH_ESP32 - -#include "CAN.h" - -#include - -#include "CAN_config.h" -#include "can_regdef.h" -#include "driver/gpio.h" -#include "esp_intr_alloc.h" -#include "freertos/FreeRTOS.h" -#include "freertos/queue.h" -#include "soc/dport_reg.h" - -// CAN Filter - no acceptance filter -static CAN_filter_t __filter = {Dual_Mode, 0, 0, 0, 0, 0Xff, 0Xff, 0Xff, 0Xff}; - -static void CAN_read_frame_phy(); -static void CAN_isr(void *arg_p); -static int CAN_write_frame_phy(const CAN_frame_t *p_frame); -static SemaphoreHandle_t sem_tx_complete; - -static void CAN_isr(void *arg_p) -{ - // Interrupt flag buffer - __CAN_IRQ_t interrupt; - BaseType_t higherPriorityTaskWoken = pdFALSE; - - // Read interrupt status and clear flags - interrupt = MODULE_CAN->IR.U; - - // Handle RX frame available interrupt - if ((interrupt & __CAN_IRQ_RX) != 0) CAN_read_frame_phy(&higherPriorityTaskWoken); - - // Handle TX complete interrupt - // Handle error interrupts. - if ((interrupt - & (__CAN_IRQ_TX | __CAN_IRQ_ERR // 0x4 - | __CAN_IRQ_DATA_OVERRUN // 0x8 - | __CAN_IRQ_WAKEUP // 0x10 - | __CAN_IRQ_ERR_PASSIVE // 0x20 - | __CAN_IRQ_ARB_LOST // 0x40 - | __CAN_IRQ_BUS_ERR // 0x80 - )) - != 0) - { - xSemaphoreGiveFromISR(sem_tx_complete, &higherPriorityTaskWoken); - } - - // check if any higher priority task has been woken by any handler - if (higherPriorityTaskWoken) portYIELD_FROM_ISR(); -} - -static void CAN_read_frame_phy(BaseType_t *higherPriorityTaskWoken) -{ - // byte iterator - uint8_t __byte_i; - - // frame read buffer - CAN_frame_t __frame; - - // check if we have a queue. If not, operation is aborted. - if (CAN_cfg.rx_queue == NULL) - { - // Let the hardware know the frame has been read. - MODULE_CAN->CMR.B.RRB = 1; - return; - } - - // get FIR - __frame.FIR.U = MODULE_CAN->MBX_CTRL.FCTRL.FIR.U; - - // check if this is a standard or extended CAN frame - // standard frame - if (__frame.FIR.B.FF == CAN_frame_std) - { - // Get Message ID - __frame.MsgID = _CAN_GET_STD_ID; - - // deep copy data bytes - for (__byte_i = 0; __byte_i < __frame.FIR.B.DLC; __byte_i++) - __frame.data.u8[__byte_i] = MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.STD.data[__byte_i]; - } - // extended frame - else - { - // Get Message ID - __frame.MsgID = _CAN_GET_EXT_ID; - - // deep copy data bytes - for (__byte_i = 0; __byte_i < __frame.FIR.B.DLC; __byte_i++) - __frame.data.u8[__byte_i] = MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.data[__byte_i]; - } - - // send frame to input queue - xQueueSendToBackFromISR(CAN_cfg.rx_queue, &__frame, higherPriorityTaskWoken); - - // Let the hardware know the frame has been read. - MODULE_CAN->CMR.B.RRB = 1; -} - -static int CAN_write_frame_phy(const CAN_frame_t *p_frame) -{ - // byte iterator - uint8_t __byte_i; - - // copy frame information record - MODULE_CAN->MBX_CTRL.FCTRL.FIR.U = p_frame->FIR.U; - - // standard frame - if (p_frame->FIR.B.FF == CAN_frame_std) - { - // Write message ID - _CAN_SET_STD_ID(p_frame->MsgID); - - // Copy the frame data to the hardware - for (__byte_i = 0; __byte_i < p_frame->FIR.B.DLC; __byte_i++) - MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.STD.data[__byte_i] = p_frame->data.u8[__byte_i]; - } - // extended frame - else - { - // Write message ID - _CAN_SET_EXT_ID(p_frame->MsgID); - - // Copy the frame data to the hardware - for (__byte_i = 0; __byte_i < p_frame->FIR.B.DLC; __byte_i++) - MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.data[__byte_i] = p_frame->data.u8[__byte_i]; - } - - // Transmit frame - MODULE_CAN->CMR.B.TR = 1; - - return 0; -} - -int CAN_init() -{ - // Time quantum - double __tq; - - // enable module - DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_CAN_CLK_EN); - DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_CAN_RST); - - // configure TX pin - gpio_set_level(CAN_cfg.tx_pin_id, 1); - gpio_set_direction(CAN_cfg.tx_pin_id, GPIO_MODE_OUTPUT); - gpio_matrix_out(CAN_cfg.tx_pin_id, CAN_TX_IDX, 0, 0); - gpio_pad_select_gpio(CAN_cfg.tx_pin_id); - - // configure RX pin - gpio_set_direction(CAN_cfg.rx_pin_id, GPIO_MODE_INPUT); - gpio_matrix_in(CAN_cfg.rx_pin_id, CAN_RX_IDX, 0); - gpio_pad_select_gpio(CAN_cfg.rx_pin_id); - - // set to PELICAN mode - MODULE_CAN->CDR.B.CAN_M = 0x1; - - // synchronization jump width is the same for all baud rates - MODULE_CAN->BTR0.B.SJW = 0x1; - - // TSEG2 is the same for all baud rates - MODULE_CAN->BTR1.B.TSEG2 = 0x1; - - // select time quantum and set TSEG1 - switch (CAN_cfg.speed) - { - case CAN_SPEED_1000KBPS: - MODULE_CAN->BTR1.B.TSEG1 = 0x4; - __tq = 0.125; - break; - - case CAN_SPEED_800KBPS: - MODULE_CAN->BTR1.B.TSEG1 = 0x6; - __tq = 0.125; - break; - - case CAN_SPEED_200KBPS: - MODULE_CAN->BTR1.B.TSEG1 = 0xc; - MODULE_CAN->BTR1.B.TSEG2 = 0x5; - __tq = 0.25; - break; - - default: - MODULE_CAN->BTR1.B.TSEG1 = 0xc; - __tq = ((float)1000 / CAN_cfg.speed) / 16; - } - - // set baud rate prescaler - MODULE_CAN->BTR0.B.BRP = (uint8_t)round((((APB_CLK_FREQ * __tq) / 2) - 1) / 1000000) - 1; - - /* Set sampling - * 1 -> triple; the bus is sampled three times; recommended for low/medium speed buses (class A and B) where - * filtering spikes on the bus line is beneficial 0 -> single; the bus is sampled once; recommended for high speed - * buses (SAE class C)*/ - MODULE_CAN->BTR1.B.SAM = 0x1; - - // enable all interrupts - MODULE_CAN->IER.U = 0xff; - - //disable bit that can be wake-up interrupt enable or divide baud rate by 2 - MODULE_CAN->IER.B.WUIE = 0; - - // Set acceptance filter - MODULE_CAN->MOD.B.AFM = __filter.FM; - MODULE_CAN->MBX_CTRL.ACC.CODE[0] = __filter.ACR0; - MODULE_CAN->MBX_CTRL.ACC.CODE[1] = __filter.ACR1; - MODULE_CAN->MBX_CTRL.ACC.CODE[2] = __filter.ACR2; - MODULE_CAN->MBX_CTRL.ACC.CODE[3] = __filter.ACR3; - MODULE_CAN->MBX_CTRL.ACC.MASK[0] = __filter.AMR0; - MODULE_CAN->MBX_CTRL.ACC.MASK[1] = __filter.AMR1; - MODULE_CAN->MBX_CTRL.ACC.MASK[2] = __filter.AMR2; - MODULE_CAN->MBX_CTRL.ACC.MASK[3] = __filter.AMR3; - - // set to normal mode - MODULE_CAN->OCR.B.OCMODE = __CAN_OC_NOM; - - // clear error counters - MODULE_CAN->TXERR.U = 0; - MODULE_CAN->RXERR.U = 0; - (void)MODULE_CAN->ECC; - - // clear interrupt flags - (void)MODULE_CAN->IR.U; - - // install CAN ISR - esp_intr_alloc(ETS_CAN_INTR_SOURCE, 0, CAN_isr, NULL, NULL); - - // allocate the tx complete semaphore - sem_tx_complete = xSemaphoreCreateBinary(); - - // Showtime. Release Reset Mode. - MODULE_CAN->MOD.B.RM = 0; - - return 0; -} - -int CAN_write_frame(const CAN_frame_t *p_frame, TickType_t timeout) -{ - if (sem_tx_complete == NULL) - { - return -1; - } - - // Write the frame to the controller - CAN_write_frame_phy(p_frame); - - // wait for the frame tx to complete - xSemaphoreTake(sem_tx_complete, timeout); - - return 0; -} - -int CAN_stop() -{ - // enter reset mode - MODULE_CAN->MOD.B.RM = 1; - - return 0; -} - -int CAN_config_filter(const CAN_filter_t *p_filter) -{ - __filter.FM = p_filter->FM; - __filter.ACR0 = p_filter->ACR0; - __filter.ACR1 = p_filter->ACR1; - __filter.ACR2 = p_filter->ACR2; - __filter.ACR3 = p_filter->ACR3; - __filter.AMR0 = p_filter->AMR0; - __filter.AMR1 = p_filter->AMR1; - __filter.AMR2 = p_filter->AMR2; - __filter.AMR3 = p_filter->AMR3; - - return 0; -} -#endif diff --git a/src/ESP32CAN/CAN.h b/src/ESP32CAN/CAN.h deleted file mode 100644 index 4ba352b..0000000 --- a/src/ESP32CAN/CAN.h +++ /dev/null @@ -1,139 +0,0 @@ -/** - * @section License - * - * The MIT License (MIT) - * - * Copyright (c) 2017, Thomas Barth, barth-dev.de - * 2018, Michael Wagner, mw@iot-make.de - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef __DRIVERS_CAN_H__ -#define __DRIVERS_CAN_H__ - -#include -#include "CAN_config.h" - -#ifdef __cplusplus -extern "C" -{ -#endif - - /** - * \brief CAN frame type (standard/extended) - */ - typedef enum - { - CAN_frame_std = 0, /**< Standard frame, using 11 bit identifer. */ - CAN_frame_ext = 1 /**< Extended frame, using 29 bit identifer. */ - } CAN_frame_format_t; - - /** - * \brief CAN RTR - */ - typedef enum - { - CAN_no_RTR = 0, /**< No RTR frame. */ - CAN_RTR = 1 /**< RTR frame. */ - } CAN_RTR_t; - - /** \brief Frame information record type */ - typedef union - { - uint32_t U; /**< \brief Unsigned access */ - struct - { - uint8_t DLC : 4; /**< \brief [3:0] DLC, Data length container */ - unsigned int unknown_2 : 2; /**< \brief \internal unknown */ - CAN_RTR_t RTR : 1; /**< \brief [6:6] RTR, Remote Transmission Request */ - CAN_frame_format_t FF : 1; /**< \brief [7:7] Frame Format, see# CAN_frame_format_t*/ - unsigned int reserved_24 : 24; /**< \brief \internal Reserved */ - } B; - } CAN_FIR_t; - - /** \brief CAN Frame structure */ - typedef struct - { - CAN_FIR_t FIR; /**< \brief Frame information record*/ - uint32_t MsgID; /**< \brief Message ID */ - union - { - uint8_t u8[8]; /**< \brief Payload byte access*/ - uint32_t u32[2]; /**< \brief Payload u32 access*/ - uint64_t u64; /**< \brief Payload u64 access*/ - } data; - } CAN_frame_t; - - typedef enum - { - Dual_Mode = 0, /**< \brief The dual acceptance filter option is enabled (two filters, each with the length of 16 bit are active) */ - Single_Mode = 1 /**< \brief The single acceptance filter option is enabled (one filter with the length of 32 bit is active) */ - } CAN_filter_mode_t; - - /** \brief CAN Filter structure */ - typedef struct - { - CAN_filter_mode_t FM : 1; /**< \brief [0:0] Filter Mode */ - uint8_t ACR0; /**< \brief Acceptance Code Register ACR0 */ - uint8_t ACR1; /**< \brief Acceptance Code Register ACR1 */ - uint8_t ACR2; /**< \brief Acceptance Code Register ACR2 */ - uint8_t ACR3; /**< \brief Acceptance Code Register ACR3 */ - uint8_t AMR0; /**< \brief Acceptance Mask Register AMR0 */ - uint8_t AMR1; /**< \brief Acceptance Mask Register AMR1 */ - uint8_t AMR2; /**< \brief Acceptance Mask Register AMR2 */ - uint8_t AMR3; /**< \brief Acceptance Mask Register AMR3 */ - } CAN_filter_t; - - /** - * \brief Initialize the CAN Module - * - * \return 0 CAN Module had been initialized - */ - int CAN_init(void); - - /** - * \brief Send a can frame - * - * \param p_frame Pointer to the frame to be send, see #CAN_frame_t - * \return 0 Frame has been written to the module - */ - int CAN_write_frame(const CAN_frame_t *p_frame, TickType_t timeout); - - /** - * \brief Stops the CAN Module - * - * \return 0 CAN Module was stopped - */ - int CAN_stop(void); - - /** - * \brief Config CAN Filter, must call before CANInit() - * - * \param p_filter Pointer to the filter, see #CAN_filter_t - * \return 0 CAN Filter had been initialized - */ - int CAN_config_filter(const CAN_filter_t *p_filter); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/ESP32CAN/CAN_config.h b/src/ESP32CAN/CAN_config.h deleted file mode 100644 index 2840a3c..0000000 --- a/src/ESP32CAN/CAN_config.h +++ /dev/null @@ -1,71 +0,0 @@ -/** - * @section License - * - * The MIT License (MIT) - * - * Copyright (c) 2017, Thomas Barth, barth-dev.de - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef __DRIVERS_CAN_CFG_H__ -#define __DRIVERS_CAN_CFG_H__ - -#include "freertos/FreeRTOS.h" -#include "freertos/queue.h" -#include "freertos/task.h" -#include "driver/gpio.h" -#include "freertos/semphr.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** \brief CAN Node Bus speed */ -typedef enum { - CAN_SPEED_100KBPS = 100, /**< \brief CAN Node runs at 100kBit/s. */ - CAN_SPEED_125KBPS = 125, /**< \brief CAN Node runs at 125kBit/s. */ - CAN_SPEED_200KBPS = 200, /**< \brief CAN Node runs at 250kBit/s. */ - CAN_SPEED_250KBPS = 250, /**< \brief CAN Node runs at 250kBit/s. */ - CAN_SPEED_500KBPS = 500, /**< \brief CAN Node runs at 500kBit/s. */ - CAN_SPEED_800KBPS = 800, /**< \brief CAN Node runs at 800kBit/s. */ - CAN_SPEED_1000KBPS = 1000 /**< \brief CAN Node runs at 1000kBit/s. */ -} CAN_speed_t; - -/** \brief CAN configuration structure */ -typedef struct { - CAN_speed_t speed; /**< \brief CAN speed. */ - gpio_num_t tx_pin_id; /**< \brief TX pin. */ - gpio_num_t rx_pin_id; /**< \brief RX pin. */ - QueueHandle_t rx_queue; /**< \brief Handler to FreeRTOS RX queue. */ - QueueHandle_t tx_queue; /**< \brief Handler to FreeRTOS TX queue. */ - TaskHandle_t tx_handle; /**< \brief Handler to FreeRTOS TX task. */ - TaskHandle_t rx_handle; /**< \brief Handler to FreeRTOS RX task. */ -} CAN_device_t; - -/** \brief CAN configuration reference */ -extern CAN_device_t CAN_cfg; - -#ifdef __cplusplus -} -#endif - -#endif /* __DRIVERS_CAN_CFG_H__ */ diff --git a/src/ESP32CAN/ESP32CAN.cpp b/src/ESP32CAN/ESP32CAN.cpp deleted file mode 100644 index e8db2fd..0000000 --- a/src/ESP32CAN/ESP32CAN.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#ifdef ARDUINO_ARCH_ESP32 -#include "ESP32CAN.h" - -int ESP32CAN::CANInit() { return CAN_init(); } -int ESP32CAN::CANWriteFrame(const CAN_frame_t *p_frame, TickType_t timeout) -{ - return CAN_write_frame(p_frame, timeout); -} -int ESP32CAN::CANStop() { return CAN_stop(); } -int ESP32CAN::CANConfigFilter(const CAN_filter_t *p_filter) { return CAN_config_filter(p_filter); } - -ESP32CAN ESP32Can; -#endif diff --git a/src/ESP32CAN/ESP32CAN.h b/src/ESP32CAN/ESP32CAN.h deleted file mode 100644 index 2b2c3e4..0000000 --- a/src/ESP32CAN/ESP32CAN.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef ESP32CAN_H -#define ESP32CAN_H - -#include "CAN_config.h" -#include "CAN.h" - -class ESP32CAN -{ -public: - int CANInit(); - int CANConfigFilter(const CAN_filter_t *p_filter); - int CANWriteFrame(const CAN_frame_t *p_frame, TickType_t timeout = portMAX_DELAY); - int CANStop(); -}; - -extern ESP32CAN ESP32Can; -#endif diff --git a/src/ESP32CAN/can_regdef.h b/src/ESP32CAN/can_regdef.h deleted file mode 100644 index 967ac81..0000000 --- a/src/ESP32CAN/can_regdef.h +++ /dev/null @@ -1,279 +0,0 @@ -/** - * @section License - * - * The MIT License (MIT) - * - * Copyright (c) 2017, Thomas Barth, barth-dev.de - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef __DRIVERS_CAN_REGDEF_H_ -#define __DRIVERS_CAN_REGDEF_H_ - -#include "CAN.h" //CAN_FIR_t - -#ifdef __cplusplus -extern "C" { -#endif - -/** \brief Start address of CAN registers */ -#define MODULE_CAN ((volatile CAN_Module_t *) 0x3ff6b000) - -/** \brief Get standard message ID */ -#define _CAN_GET_STD_ID \ - (((uint32_t) MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.STD.ID[0] << 3) | (MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.STD.ID[1] >> 5)) - -/** \brief Get extended message ID */ -#define _CAN_GET_EXT_ID \ - (((uint32_t) MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[0] << 21) | \ - (MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[1] << 13) | (MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[2] << 5) | \ - (MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[3] >> 3)) - -/** \brief Set standard message ID */ -#define _CAN_SET_STD_ID(x) \ - MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.STD.ID[0] = ((x) >> 3); \ - MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.STD.ID[1] = ((x) << 5); - -/** \brief Set extended message ID */ -#define _CAN_SET_EXT_ID(x) \ - MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[0] = ((x) >> 21); \ - MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[1] = ((x) >> 13); \ - MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[2] = ((x) >> 5); \ - MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[3] = ((x) << 3); - -/** \brief Interrupt status register */ -typedef enum { - __CAN_IRQ_RX = BIT(0), /**< \brief RX Interrupt */ - __CAN_IRQ_TX = BIT(1), /**< \brief TX Interrupt */ - __CAN_IRQ_ERR = BIT(2), /**< \brief Error Interrupt */ - __CAN_IRQ_DATA_OVERRUN = BIT(3), /**< \brief Data Overrun Interrupt */ - __CAN_IRQ_WAKEUP = BIT(4), /**< \brief Wakeup Interrupt */ - __CAN_IRQ_ERR_PASSIVE = BIT(5), /**< \brief Passive Error Interrupt */ - __CAN_IRQ_ARB_LOST = BIT(6), /**< \brief Arbitration lost interrupt */ - __CAN_IRQ_BUS_ERR = BIT(7), /**< \brief Bus error Interrupt */ -} __CAN_IRQ_t; - -/** \brief OCMODE options. */ -typedef enum { - __CAN_OC_BOM = 0b00, /**< \brief bi-phase output mode */ - __CAN_OC_TOM = 0b01, /**< \brief test output mode */ - __CAN_OC_NOM = 0b10, /**< \brief normal output mode */ - __CAN_OC_COM = 0b11, /**< \brief clock output mode */ -} __CAN_OCMODE_t; - -/** - * CAN controller (SJA1000). - */ -typedef struct { - union { - uint32_t U; /**< \brief Unsigned access */ - struct { - unsigned int RM : 1; /**< \brief MOD.0 Reset Mode */ - unsigned int LOM : 1; /**< \brief MOD.1 Listen Only Mode */ - unsigned int STM : 1; /**< \brief MOD.2 Self Test Mode */ - unsigned int AFM : 1; /**< \brief MOD.3 Acceptance Filter Mode */ - unsigned int SM : 1; /**< \brief MOD.4 Sleep Mode */ - unsigned int reserved_27 : 27; /**< \brief \internal Reserved */ - } B; - } MOD; - union { - uint32_t U; /**< \brief Unsigned access */ - struct { - unsigned int TR : 1; /**< \brief CMR.0 Transmission Request */ - unsigned int AT : 1; /**< \brief CMR.1 Abort Transmission */ - unsigned int RRB : 1; /**< \brief CMR.2 Release Receive Buffer */ - unsigned int CDO : 1; /**< \brief CMR.3 Clear Data Overrun */ - unsigned int GTS : 1; /**< \brief CMR.4 Go To Sleep */ - unsigned int reserved_27 : 27; /**< \brief \internal Reserved */ - } B; - } CMR; - union { - uint32_t U; /**< \brief Unsigned access */ - struct { - unsigned int RBS : 1; /**< \brief SR.0 Receive Buffer Status */ - unsigned int DOS : 1; /**< \brief SR.1 Data Overrun Status */ - unsigned int TBS : 1; /**< \brief SR.2 Transmit Buffer Status */ - unsigned int TCS : 1; /**< \brief SR.3 Transmission Complete Status */ - unsigned int RS : 1; /**< \brief SR.4 Receive Status */ - unsigned int TS : 1; /**< \brief SR.5 Transmit Status */ - unsigned int ES : 1; /**< \brief SR.6 Error Status */ - unsigned int BS : 1; /**< \brief SR.7 Bus Status */ - unsigned int reserved_24 : 24; /**< \brief \internal Reserved */ - } B; - } SR; - union { - uint32_t U; /**< \brief Unsigned access */ - struct { - unsigned int RI : 1; /**< \brief IR.0 Receive Interrupt */ - unsigned int TI : 1; /**< \brief IR.1 Transmit Interrupt */ - unsigned int EI : 1; /**< \brief IR.2 Error Interrupt */ - unsigned int DOI : 1; /**< \brief IR.3 Data Overrun Interrupt */ - unsigned int WUI : 1; /**< \brief IR.4 Wake-Up Interrupt */ - unsigned int EPI : 1; /**< \brief IR.5 Error Passive Interrupt */ - unsigned int ALI : 1; /**< \brief IR.6 Arbitration Lost Interrupt */ - unsigned int BEI : 1; /**< \brief IR.7 Bus Error Interrupt */ - unsigned int reserved_24 : 24; /**< \brief \internal Reserved */ - } B; - } IR; - union { - uint32_t U; /**< \brief Unsigned access */ - struct { - unsigned int RIE : 1; /**< \brief IER.0 Receive Interrupt Enable */ - unsigned int TIE : 1; /**< \brief IER.1 Transmit Interrupt Enable */ - unsigned int EIE : 1; /**< \brief IER.2 Error Interrupt Enable */ - unsigned int DOIE : 1; /**< \brief IER.3 Data Overrun Interrupt Enable */ - unsigned int WUIE : 1; /**< \brief IER.4 Wake-Up Interrupt Enable */ - unsigned int EPIE : 1; /**< \brief IER.5 Error Passive Interrupt Enable */ - unsigned int ALIE : 1; /**< \brief IER.6 Arbitration Lost Interrupt Enable */ - unsigned int BEIE : 1; /**< \brief IER.7 Bus Error Interrupt Enable */ - unsigned int reserved_24 : 24; /**< \brief \internal Reserved */ - } B; - } IER; - uint32_t RESERVED0; - union { - uint32_t U; /**< \brief Unsigned access */ - struct { - unsigned int BRP : 6; /**< \brief BTR0[5:0] Baud Rate Prescaler */ - unsigned int SJW : 2; /**< \brief BTR0[7:6] Synchronization Jump Width*/ - unsigned int reserved_24 : 24; /**< \brief \internal Reserved */ - } B; - } BTR0; - union { - uint32_t U; /**< \brief Unsigned access */ - struct { - unsigned int TSEG1 : 4; /**< \brief BTR1[3:0] Timing Segment 1 */ - unsigned int TSEG2 : 3; /**< \brief BTR1[6:4] Timing Segment 2*/ - unsigned int SAM : 1; /**< \brief BTR1.7 Sampling*/ - unsigned int reserved_24 : 24; /**< \brief \internal Reserved */ - } B; - } BTR1; - union { - uint32_t U; /**< \brief Unsigned access */ - struct { - unsigned int OCMODE : 2; /**< \brief OCR[1:0] Output Control Mode, see # */ - unsigned int OCPOL0 : 1; /**< \brief OCR.2 Output Control Polarity 0 */ - unsigned int OCTN0 : 1; /**< \brief OCR.3 Output Control Transistor N0 */ - unsigned int OCTP0 : 1; /**< \brief OCR.4 Output Control Transistor P0 */ - unsigned int OCPOL1 : 1; /**< \brief OCR.5 Output Control Polarity 1 */ - unsigned int OCTN1 : 1; /**< \brief OCR.6 Output Control Transistor N1 */ - unsigned int OCTP1 : 1; /**< \brief OCR.7 Output Control Transistor P1 */ - unsigned int reserved_24 : 24; /**< \brief \internal Reserved */ - } B; - } OCR; - uint32_t RESERVED1[2]; - union { - uint32_t U; /**< \brief Unsigned access */ - struct { - unsigned int ALC : 8; /**< \brief ALC[7:0] Arbitration Lost Capture */ - unsigned int reserved_24 : 24; /**< \brief \internal Reserved */ - } B; - } ALC; - union { - uint32_t U; /**< \brief Unsigned access */ - struct { - unsigned int ECC : 8; /**< \brief ECC[7:0] Error Code Capture */ - unsigned int reserved_24 : 24; /**< \brief \internal Reserved */ - } B; - } ECC; - union { - uint32_t U; /**< \brief Unsigned access */ - struct { - unsigned int EWLR : 8; /**< \brief EWLR[7:0] Error Warning Limit */ - unsigned int reserved_24 : 24; /**< \brief \internal Reserved */ - } B; - } EWLR; - union { - uint32_t U; /**< \brief Unsigned access */ - struct { - unsigned int RXERR : 8; /**< \brief RXERR[7:0] Receive Error Counter */ - unsigned int reserved_24 : 24; /**< \brief \internal Reserved */ - } B; - } RXERR; - union { - uint32_t U; /**< \brief Unsigned access */ - struct { - unsigned int TXERR : 8; /**< \brief TXERR[7:0] Transmit Error Counter */ - unsigned int reserved_24 : 24; /**< \brief \internal Reserved */ - } B; - } TXERR; - - union { - struct { - uint32_t CODE[4]; /**< \brief Acceptance Message ID */ - uint32_t MASK[4]; /**< \brief Acceptance Mask */ - uint32_t RESERVED2[5]; - } ACC; /**< \brief Acceptance filtering */ - struct { - CAN_FIR_t FIR; /**< \brief Frame information record */ - union { - struct { - uint32_t ID[2]; /**< \brief Standard frame message-ID*/ - uint32_t data[8]; /**< \brief Standard frame payload */ - uint32_t reserved[2]; - } STD; /**< \brief Standard frame format */ - struct { - uint32_t ID[4]; /**< \brief Extended frame message-ID*/ - uint32_t data[8]; /**< \brief Extended frame payload */ - } EXT; /**< \brief Extended frame format */ - } TX_RX; /**< \brief RX/TX interface */ - } FCTRL; /**< \brief Function control regs */ - } MBX_CTRL; /**< \brief Mailbox control */ - union { - uint32_t U; /**< \brief Unsigned access */ - struct { - unsigned int RMC : 8; /**< \brief RMC[7:0] RX Message Counter */ - unsigned int reserved_24 : 24; /**< \brief \internal Reserved Enable */ - } B; - } RMC; - union { - uint32_t U; /**< \brief Unsigned access */ - struct { - unsigned int RBSA : 8; /**< \brief RBSA[7:0] RX Buffer Start Address */ - unsigned int reserved_24 : 24; /**< \brief \internal Reserved Enable */ - } B; - } RBSA; - union { - uint32_t U; /**< \brief Unsigned access */ - struct { - unsigned int COD : 3; /**< \brief CDR[2:0] CLKOUT frequency selector based of fOSC*/ - unsigned int COFF : 1; /**< \brief CDR.3 CLKOUT off*/ - unsigned int reserved_1 : 1; /**< \brief \internal Reserved */ - unsigned int - RXINTEN : 1; /**< \brief CDR.5 This bit allows the TX1 output to be used as a dedicated receive interrupt - output*/ - unsigned int - CBP : 1; /**< \brief CDR.6 allows to bypass the CAN input comparator and is only possible in reset mode.*/ - unsigned int - CAN_M : 1; /**< \brief CDR.7 If CDR.7 is at logic 0 the CAN controller operates in BasicCAN mode. If set to - logic 1 the CAN controller operates in PeliCAN mode. Write access is only possible in reset - mode*/ - unsigned int reserved_24 : 24; /**< \brief \internal Reserved */ - } B; - } CDR; - uint32_t IRAM[2]; -} CAN_Module_t; - -#ifdef __cplusplus -} -#endif - -#endif /* __DRIVERS_CAN_REGDEF_H_ */ diff --git a/src/esp_can.cpp b/src/esp_can.cpp index 0aa2294..2afd596 100644 --- a/src/esp_can.cpp +++ b/src/esp_can.cpp @@ -3,14 +3,15 @@ #include -CAN_device_t CAN_cfg; // CAN Config +#include "driver/gpio.h" +#include "driver/twai.h" std::vector ESPCAN::rx_messages_{}; -ESPCAN::ESPCAN(uint8_t rx_queue_size, gpio_num_t tx, gpio_num_t rx) : kRxQueueSize{rx_queue_size} +ESPCAN::ESPCAN(uint8_t rx_queue_size, gpio_num_t tx, gpio_num_t rx) { - CAN_cfg.tx_pin_id = tx; - CAN_cfg.rx_pin_id = rx; + g_config = TWAI_GENERAL_CONFIG_DEFAULT(tx, rx, TWAI_MODE_NORMAL); + g_config.rx_queue_len = rx_queue_size; } void ESPCAN::Initialize(BaudRate baud) @@ -18,65 +19,109 @@ void ESPCAN::Initialize(BaudRate baud) switch (baud) { case BaudRate::kBaud125k: - CAN_cfg.speed = CAN_speed_t::CAN_SPEED_125KBPS; + t_config = TWAI_TIMING_CONFIG_125KBITS(); break; case BaudRate::kBaud250K: - CAN_cfg.speed = CAN_speed_t::CAN_SPEED_250KBPS; + t_config = TWAI_TIMING_CONFIG_250KBITS(); break; case BaudRate::kBaud500K: - CAN_cfg.speed = CAN_speed_t::CAN_SPEED_500KBPS; + t_config = TWAI_TIMING_CONFIG_500KBITS(); break; case BaudRate::kBaud1M: - CAN_cfg.speed = CAN_speed_t::CAN_SPEED_1000KBPS; + t_config = TWAI_TIMING_CONFIG_1MBITS(); break; } - CAN_cfg.rx_queue = xQueueCreate(kRxQueueSize, sizeof(CAN_frame_t)); - // CAN_cfg.rx_handle = xTaskCreate(&ProcessReceive, "Process CAN Receive", 200, NULL, 5, NULL); - // Init CAN Module - ESP32Can.CANInit(); +// Install TWAI driver +#if defined(CONFIG_TWAI_ISR_IN_IRAM) and CONFIG_TWAI_ISR_IN_IRAM + g_config.intr_flags |= ESP_INTR_FLAG_IRAM; +#endif + if (twai_driver_install(&g_config, &t_config, &f_config) == ESP_OK) + { + printf("TWAI driver installed\n"); + } + else + { + printf("Failed to install TWAI driver\n"); + return; + } + + // Start TWAI driver + if (twai_start() == ESP_OK) + { + printf("TWAI driver started\n"); + } + else + { + printf("Failed to start TWAI driver\n"); + return; + } } bool ESPCAN::SendMessage(CANMessage &msg) { - CAN_frame_t tx_frame; - tx_frame.FIR.B.FF = msg.extended_id_ ? CAN_frame_ext : CAN_frame_std; - tx_frame.FIR.B.RTR = CAN_no_RTR; - bool ret = false; + static twai_status_info_t status; + twai_get_status_info(&status); + if (status.state != TWAI_STATE_RUNNING) + { + return false; + } - tx_frame.MsgID = msg.id_; - tx_frame.FIR.B.DLC = msg.len_; + static twai_message_t t_message; + t_message.identifier = msg.id_; + t_message.extd = msg.extended_id_; + t_message.data_length_code = msg.len_; + t_message.rtr = 0; for (int i = 0; i < msg.len_; i++) { - tx_frame.data.u8[i] = msg.data_[i]; + t_message.data[i] = msg.data_[i]; } - ret = (ESP32Can.CANWriteFrame(&tx_frame, TickType_t(10)) != -1); - - return ret; + return twai_transmit(&t_message, TickType_t(10)) == ESP_OK; } void ESPCAN::Tick() { - std::array msg_data{}; - CANMessage received_message{0, 8, msg_data}; - CAN_frame_t rx_frame; + static std::array msg_data{}; + static CANMessage received_message{0, 8, msg_data}; + static twai_message_t r_message; + static twai_status_info_t status; + twai_get_status_info(&status); - while ((xQueueReceive(CAN_cfg.rx_queue, &rx_frame, 3 * portTICK_PERIOD_MS) == pdTRUE)) + if (status.state == TWAI_STATE_BUS_OFF) { - received_message.id_ = rx_frame.MsgID; - received_message.len_ = rx_frame.FIR.B.DLC; + twai_initiate_recovery(); + } - memcpy(received_message.data_.data(), rx_frame.data.u8, 8); + if (status.state == TWAI_STATE_STOPPED) + { + twai_start(); + } - for (size_t i = 0; i < rx_messages_.size(); i++) + while (status.msgs_to_rx > 0) + { + if (status.rx_missed_count > 0) { - if (rx_messages_[i]->GetID() == received_message.id_) + printf("Missed %d CAN messages due to full queue\n", status.rx_missed_count); + } + if (twai_receive(&r_message, TickType_t(100)) == ESP_OK) + { + received_message.id_ = r_message.identifier; + received_message.len_ = r_message.data_length_code; + + memcpy(received_message.data_.data(), r_message.data, 8); + + for (size_t i = 0; i < rx_messages_.size(); i++) { rx_messages_[i]->DecodeSignals(received_message); } } + else + { + printf("Failed to read message from queue\n"); + } + twai_get_status_info(&status); } } diff --git a/src/teensy_can.cpp b/src/teensy_can.cpp index 5ab429f..c5433b2 100644 --- a/src/teensy_can.cpp +++ b/src/teensy_can.cpp @@ -92,17 +92,13 @@ bool TeensyCAN::SendMessage(CANMessage &msg) } template -_MB_ptr TeensyCAN::ProcessMessage = [](const CAN_message_t &msg) -{ +_MB_ptr TeensyCAN::ProcessMessage = [](const CAN_message_t &msg) { std::array msg_data{}; memcpy(msg_data.data(), msg.buf, 8); CANMessage received_message{static_cast(msg.id), msg.len, msg_data}; for (size_t i = 0; i < rx_messages_.size(); i++) { - if (rx_messages_[i]->GetID() == received_message.id_) - { - rx_messages_[i]->DecodeSignals(received_message); - } + rx_messages_[i]->DecodeSignals(received_message); } }; #endif \ No newline at end of file diff --git a/test/test_main.cpp b/test/test_main.cpp index 05a2f47..e6ba806 100644 --- a/test/test_main.cpp +++ b/test/test_main.cpp @@ -168,6 +168,63 @@ void OperatorsTest(void) TEST_ASSERT_EQUAL_FLOAT(5, test_signal); } +void MultiplexedCANMessageTest(void) +{ + MakeUnsignedCANSignal(uint8_t, 0, 8, 1, 0) tx_multiplexor; + MakeSignedCANSignal(int32_t, 8, 32, 1, 0) tx_signal_0_0; + MakeSignedCANSignal(int16_t, 40, 16, 1, 0) tx_signal_0_1; + MultiplexedSignalGroup<2> tx_signals_0{0, tx_signal_0_0, tx_signal_0_1}; + MakeSignedCANSignal(int32_t, 8, 32, 1, 0) tx_signal_1_0; + MakeSignedCANSignal(int16_t, 40, 16, 1, 0) tx_signal_1_1; + MultiplexedSignalGroup<2> tx_signals_1{1, tx_signal_1_0, tx_signal_1_1}; + + MakeUnsignedCANSignal(uint8_t, 0, 8, 1, 0) rx_multiplexor; + MakeSignedCANSignal(int32_t, 8, 32, 1, 0) rx_signal_0_0; + MakeSignedCANSignal(int16_t, 40, 16, 1, 0) rx_signal_0_1; + MultiplexedSignalGroup<2> rx_signals_0{0, rx_signal_0_0, rx_signal_0_1}; + MakeSignedCANSignal(int32_t, 8, 32, 1, 0) rx_signal_1_0; + MakeSignedCANSignal(int16_t, 40, 16, 1, 0) rx_signal_1_1; + MultiplexedSignalGroup<2> rx_signals_1{1, rx_signal_1_0, rx_signal_1_1}; + + MockCAN can{}; + + MultiplexedCANTXMessage<2, 2, uint8_t> tx_msg{ + can, 100, 8, 100, std::array{0, 1}, tx_multiplexor, tx_signals_0, tx_signals_1}; + MultiplexedCANRXMessage<2, uint8_t> rx_msg{ + can, 100, []() { return 0; }, rx_multiplexor, rx_signals_0, rx_signals_1}; + + tx_signal_0_0 = 1; + tx_signal_0_1 = 2; + tx_signal_1_0 = 3; + tx_signal_1_1 = 4; + + tx_msg.EncodeAndSend(); + TEST_ASSERT(tx_multiplexor == 0); + + rx_msg.DecodeSignals(can.last_message); + TEST_ASSERT(rx_multiplexor == 0); + TEST_ASSERT(rx_signal_0_0 == 1); + TEST_ASSERT(rx_signal_0_1 == 2); + TEST_ASSERT(rx_signal_1_0 == 0); + TEST_ASSERT(rx_signal_1_1 == 0); + + tx_signal_0_0 = 0; + tx_signal_0_1 = 0; + + tx_msg.EncodeAndSend(); + TEST_ASSERT(tx_multiplexor == 1); + + rx_msg.DecodeSignals(can.last_message); + TEST_ASSERT(rx_multiplexor == 1); + TEST_ASSERT(rx_signal_0_0 == 1); + TEST_ASSERT(rx_signal_0_1 == 2); + TEST_ASSERT(rx_signal_1_0 == 3); + TEST_ASSERT(rx_signal_1_1 == 4); + + tx_msg.EncodeAndSend(); + TEST_ASSERT(tx_multiplexor == 0); +} + int runUnityTests(void) { UNITY_BEGIN(); @@ -179,6 +236,7 @@ int runUnityTests(void) RUN_TEST(EnumClassSignalTest); RUN_TEST(MITMotorBigEndianCANSignalTest); RUN_TEST(OperatorsTest); + RUN_TEST(MultiplexedCANMessageTest); return UNITY_END(); }