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 5cc7ed1..6bad7de 100644 --- a/include/can_interface.h +++ b/include/can_interface.h @@ -42,6 +42,10 @@ class CANMessage std::array data_; }; +/** + * @brief PGNCANMessage is used to implement J1939 and NMEA2000 + * + */ class PGNCANMessage : public CANMessage { public: @@ -119,7 +123,8 @@ constexpr uint8_t CANSignal_generate_position(uint8_t position, uint8_t length, : ((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; @@ -500,6 +505,74 @@ class MultiplexedSignalGroup : public std::array, pub size_t size() const override { return std::array::size(); } }; +/** + * @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 */ @@ -609,7 +682,9 @@ class CANTXMessage : public ICANTXMessage 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(); } @@ -637,13 +712,15 @@ class MultiplexedCANTXMessage : public ICANTXMessage public: template /** - * @brief Construct a new CANTXMessage object + * @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 */ @@ -679,12 +756,15 @@ class MultiplexedCANTXMessage : public ICANTXMessage template /** - * @brief Construct a new CANTXMessage object, default to standard id + * @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 */ @@ -702,7 +782,8 @@ class MultiplexedCANTXMessage : public ICANTXMessage template /** - * @brief Construct a new CANTXMessage object and automatically adds it to a VirtualTimerGroup + * @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 @@ -710,6 +791,8 @@ class MultiplexedCANTXMessage : public ICANTXMessage * @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 */ @@ -738,14 +821,16 @@ class MultiplexedCANTXMessage : public ICANTXMessage template /** - * @brief Construct a new CANTXMessage object and automatically adds it to a VirtualTimerGroup, default to standard - * id + * @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 */ @@ -797,19 +882,13 @@ class MultiplexedCANTXMessage : public ICANTXMessage VirtualTimer &GetTransmitTimer() override { return transmit_timer_; } #endif - void Enable() - { /* transmit_timer_.Enable(); */ - } - void Disable() - { /* transmit_timer_.Disable(); */ - } + void Enable() { transmit_timer_.Enable(); } + void Disable() { transmit_timer_.Disable(); } private: ICAN &can_interface_; CANMessage message_; -#if !defined(NATIVE) // workaround for unit tests VirtualTimer transmit_timer_; -#endif std::array multiplexor_values_to_transmit_; ITypedCANSignal *multiplexor_; std::array signal_groups_; @@ -866,14 +945,12 @@ class PGNCANTXMessage : public ICANTXMessage public: template /** - * @brief Construct a new CANTXMessage object + * @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 - * @param extended_id Whether the ID is extended (true) or standard (false) + * @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 start_time The time in ms to start transmitting the message * @param signals The ICANSignals contained in the message */ PGNCANTXMessage(ICAN &can_interface, @@ -892,14 +969,14 @@ class PGNCANTXMessage : public ICANTXMessage template /** - * @brief Construct a new CANTXMessage object and automatically adds it to a VirtualTimerGroup + * @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 + * @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 start_time The time in ms to start transmitting the message * @param timer_group A timer group to add the transmit timer to * @param signals The ICANSignals contained in the message */ @@ -923,7 +1000,9 @@ class PGNCANTXMessage : public ICANTXMessage 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(); } @@ -1278,3 +1357,260 @@ class PGNCANRXMessage : public ICANRXMessage uint32_t last_receive_time_ = 0; }; + +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/library.json b/library.json index 8d4d50c..d43ae34 100644 --- a/library.json +++ b/library.json @@ -6,5 +6,23 @@ }, "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/src/esp_can.cpp b/src/esp_can.cpp index 8bb8316..2afd596 100644 --- a/src/esp_can.cpp +++ b/src/esp_can.cpp @@ -101,6 +101,10 @@ void ESPCAN::Tick() while (status.msgs_to_rx > 0) { + if (status.rx_missed_count > 0) + { + 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; diff --git a/test/test_main.cpp b/test/test_main.cpp index dbd9132..e6ba806 100644 --- a/test/test_main.cpp +++ b/test/test_main.cpp @@ -188,7 +188,8 @@ void MultiplexedCANMessageTest(void) MockCAN can{}; - MultiplexedCANTXMessage<2, uint8_t> tx_msg{can, 100, 8, 100, tx_multiplexor, tx_signals_0, tx_signals_1}; + 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};