From cdc1af3a8199bdec2a4ea0a37349a400d49db19c Mon Sep 17 00:00:00 2001 From: Blake Freer <59676067+BlakeFreer@users.noreply.github.com> Date: Fri, 22 Nov 2024 13:05:28 -0500 Subject: [PATCH] Freerb/242 update can drivers (#322) * Adds missing import * moves and removes unused files * Reimplement CAN * Adds message formatter * bk work * for review * simplify get and pop methods * remove unused import * Add comments to Pop and Get * Update headers * Rename veh to demo * Update scripts/cangen/cangen/templates/bus.h.jinja2 Co-authored-by: Samuel Parent <108418256+samparent97@users.noreply.github.com> * Add clang-format on * Explain optional unpacking * Revert to TxMessage concept --------- Co-authored-by: Samuel Parent <108418256+samparent97@users.noreply.github.com> --- firmware/mcal/cli/periph/can.hpp | 29 ++- firmware/mcal/linux/periph/can.cc | 73 +++----- firmware/mcal/linux/periph/can.hpp | 11 +- .../projects/Demo/CAN/Receive/bindings.hpp | 2 +- firmware/projects/Demo/CAN/Receive/main.cc | 29 ++- .../CAN/Receive/platforms/cli/bindings.cc | 6 +- .../CAN/Receive/platforms/linux/bindings.cc | 6 +- firmware/projects/Demo/CAN/Send/bindings.hpp | 2 +- firmware/projects/Demo/CAN/Send/main.cc | 25 +-- .../Demo/CAN/Send/platforms/cli/bindings.cc | 6 +- .../Demo/CAN/Send/platforms/linux/bindings.cc | 6 +- firmware/shared/CMakeLists.txt | 7 +- firmware/shared/comms/CMakeLists.txt | 1 + firmware/shared/comms/can/CMakeLists.txt | 1 + firmware/shared/comms/can/bus.cc | 10 ++ firmware/shared/comms/can/bus.h | 29 +++ firmware/shared/comms/can/can_bus.hpp | 52 ------ firmware/shared/comms/can/can_msg.hpp | 61 ------- firmware/shared/comms/can/msg.cc | 13 ++ firmware/shared/comms/can/msg.h | 41 +++++ firmware/shared/comms/can/msg_registry.hpp | 30 ---- firmware/shared/comms/can/raw_can_msg.hpp | 39 ---- firmware/shared/periph/CMakeLists.txt | 1 + firmware/shared/periph/can.cc | 15 ++ firmware/shared/periph/can.hpp | 14 +- scripts/cangen/cangen/can_generator.py | 14 +- scripts/cangen/cangen/templates/bus.h.jinja2 | 67 +++++++ ..._messages.hpp.jinja2 => messages.h.jinja2} | 166 +++++++++--------- .../cangen/templates/msg_registry.hpp.jinja2 | 85 --------- 29 files changed, 358 insertions(+), 483 deletions(-) create mode 100644 firmware/shared/comms/CMakeLists.txt create mode 100644 firmware/shared/comms/can/CMakeLists.txt create mode 100644 firmware/shared/comms/can/bus.cc create mode 100644 firmware/shared/comms/can/bus.h delete mode 100644 firmware/shared/comms/can/can_bus.hpp delete mode 100644 firmware/shared/comms/can/can_msg.hpp create mode 100644 firmware/shared/comms/can/msg.cc create mode 100644 firmware/shared/comms/can/msg.h delete mode 100644 firmware/shared/comms/can/msg_registry.hpp delete mode 100644 firmware/shared/comms/can/raw_can_msg.hpp create mode 100644 firmware/shared/periph/CMakeLists.txt create mode 100644 firmware/shared/periph/can.cc create mode 100644 scripts/cangen/cangen/templates/bus.h.jinja2 rename scripts/cangen/cangen/templates/{can_messages.hpp.jinja2 => messages.h.jinja2} (51%) delete mode 100644 scripts/cangen/cangen/templates/msg_registry.hpp.jinja2 diff --git a/firmware/mcal/cli/periph/can.hpp b/firmware/mcal/cli/periph/can.hpp index 7a2a0796f..1939dd3d5 100644 --- a/firmware/mcal/cli/periph/can.hpp +++ b/firmware/mcal/cli/periph/can.hpp @@ -8,11 +8,12 @@ #include #include +#include #include #include #include -#include "shared/comms/can/raw_can_msg.hpp" +#include "shared/comms/can/msg.hpp" #include "shared/periph/can.hpp" namespace mcal::cli::periph { @@ -25,27 +26,19 @@ class CanBase : public shared::periph::CanBase { std::cout << "can interface: " << iface_ << std::endl; } - void Send(const shared::can::RawCanMsg& can_tx_msg) { - std::cout << iface_ << " [" << std::hex << std::uppercase - << std::setfill('0') << std::setw(2) << can_tx_msg.header.id - << "] "; - - // Loop through each data byte and print it in uppercase hex with - // leading zeros - for (int i = 0; i < sizeof(can_tx_msg.data); ++i) { - std::cout << std::hex << std::uppercase << std::setfill('0') - << std::setw(2) << static_cast(can_tx_msg.data[i]) - << " "; - } - - std::cout << std::endl; + void Send(const shared::can::RawMessage& msg) { + std::cout << std::format("{} {}", iface_, msg) << std::endl; } - void ReadQueue(shared::can::RawCanMsg can_rx_msgs[], size_t len) {} - private: - using RawCanMsg = shared::can::RawCanMsg; std::string iface_; + + uint32_t GetTimestamp() const override { + using namespace std::chrono; + auto t = system_clock::now().time_since_epoch(); + auto ms = duration_cast(t).count(); + return ms; + } }; } // namespace mcal::cli::periph diff --git a/firmware/mcal/linux/periph/can.cc b/firmware/mcal/linux/periph/can.cc index f47ad96f7..9fe536bf0 100644 --- a/firmware/mcal/linux/periph/can.cc +++ b/firmware/mcal/linux/periph/can.cc @@ -1,44 +1,25 @@ #include #include +#include #include #include #include +#include #include -#include #include #include "can.h" -#include "shared/comms/can/raw_can_msg.hpp" +#include "shared/comms/can/msg.hpp" #include "vcan/vcan.h" -static std::string message_to_string(const shared::can::RawCanMsg& msg) { - std::string str = std::format("[{:02X}]", msg.header.id); - for (int i = 0; i < sizeof(msg.header.data_len); ++i) { - str += std::format(" {:02X}", msg.data[i]); - } - return str; -} - -static can_frame to_can_frame(const shared::can::RawCanMsg& msg) { - struct can_frame frame{.can_id = msg.header.id, - .can_dlc = msg.header.data_len}; - std::memcpy(&frame.data, msg.data, 8); - return frame; -} - -static shared::can::RawCanMsg from_can_frame(const can_frame& frame) { - shared::can::RawCanMsg msg; - msg.header = { - .id = frame.can_id, - .data_len = frame.can_dlc, - .is_extended_frame = static_cast(frame.can_id & CAN_EFF_FLAG), +static can_frame to_can_frame(const shared::can::RawMessage& msg) { + struct can_frame frame{ + .can_id = msg.id, + .can_dlc = msg.data_length, }; - msg.tick_timestamp = 0; - - std::copy(frame.data, frame.data + 8, msg.data); - - return msg; + std::memcpy(&frame.data, msg.data, msg.data_length); + return frame; } namespace mcal::lnx::periph { @@ -52,34 +33,17 @@ void CanBase::Setup() { reader_thread_ = std::thread(&CanBase::StartReading, this); } -void CanBase::Send(const shared::can::RawCanMsg& can_tx_msg) { +void CanBase::Send(const shared::can::RawMessage& can_tx_msg) { std::cout << std::format("CanBase {}: Sending\n| {}", socket_.GetIface(), - message_to_string(can_tx_msg)) + can_tx_msg) << std::endl; auto frame = to_can_frame(can_tx_msg); socket_.Write(&frame); } -void CanBase::ReadQueue(shared::can::RawCanMsg can_rx_msgs[], size_t len) { - uint16_t msg_idx = 0; - std::lock_guard lock(queue_access_); - while (!can_queue_.empty() && msg_idx < len) { - can_rx_msgs[msg_idx] = can_queue_.front(); - - std::cout << std::format("CanBase {}: Received\n| {}", - socket_.GetIface(), - message_to_string(can_rx_msgs[msg_idx])) - << std::endl; - - can_queue_.pop(); - msg_idx++; - } -} - void CanBase::StartReading() { struct can_frame frame; - shared::can::RawCanMsg raw_msg; while (true) { // Block until a frame arrives @@ -89,13 +53,18 @@ void CanBase::StartReading() { exit(1); } - raw_msg = from_can_frame(frame); + shared::can::RawMessage raw_msg(frame.can_id, frame.can_dlc, + frame.data); - { // push RawCanMsg to queue - std::lock_guard lock(queue_access_); - can_queue_.push(raw_msg); - } + AddToBus(raw_msg); } } +uint32_t CanBase::GetTimestamp() const { + using namespace std::chrono; + auto t = system_clock::now().time_since_epoch(); + auto ms = duration_cast(t).count(); + return ms; +} + } // namespace mcal::lnx::periph \ No newline at end of file diff --git a/firmware/mcal/linux/periph/can.hpp b/firmware/mcal/linux/periph/can.hpp index 39c1e15f4..85728d69a 100644 --- a/firmware/mcal/linux/periph/can.hpp +++ b/firmware/mcal/linux/periph/can.hpp @@ -1,11 +1,8 @@ #pragma once -#include -#include #include #include "mcal/linux/periph/vcan/vcan.hpp" -#include "shared/comms/can/raw_can_msg.hpp" #include "shared/periph/can.hpp" #include "vcan/vcan.h" @@ -16,16 +13,14 @@ class CanBase : public shared::periph::CanBase { CanBase(std::string can_iface); void Setup(); - void Send(const shared::can::RawCanMsg&); - void ReadQueue(shared::can::RawCanMsg[], size_t len); + void Send(const shared::can::RawMessage&) override; private: + uint32_t GetTimestamp() const override; + struct vcan::VcanSocket socket_; - std::queue can_queue_; - std::mutex queue_access_; std::thread reader_thread_; - void StartReading(); }; } // namespace mcal::lnx::periph \ No newline at end of file diff --git a/firmware/projects/Demo/CAN/Receive/bindings.hpp b/firmware/projects/Demo/CAN/Receive/bindings.hpp index 12cce3760..1db80676d 100644 --- a/firmware/projects/Demo/CAN/Receive/bindings.hpp +++ b/firmware/projects/Demo/CAN/Receive/bindings.hpp @@ -5,7 +5,7 @@ namespace bindings { -extern shared::periph::CanBase& veh_can_base; +extern shared::periph::CanBase& demo_can_base; extern shared::periph::DigitalOutput& indicator; extern void Initialize(); diff --git a/firmware/projects/Demo/CAN/Receive/main.cc b/firmware/projects/Demo/CAN/Receive/main.cc index 7c0e81c34..faa0c3b4f 100644 --- a/firmware/projects/Demo/CAN/Receive/main.cc +++ b/firmware/projects/Demo/CAN/Receive/main.cc @@ -3,28 +3,27 @@ #include -#include "bindings.hpp" -#include "generated/can/demobus_can_messages.hpp" -#include "generated/can/demobus_msg_registry.hpp" -#include "shared/comms/can/can_bus.hpp" +#include "bindings.h" +#include "generated/can/demobus_bus.pp" +#include "generated/can/demobus_messages.hpp" +#include "shared/comms/can/bus.hpp" -generated::can::DemobusMsgRegistry veh_can_registry{}; +using namespace generated::can; -shared::can::CanBus veh_can_bus{ - bindings::veh_can_base, - veh_can_registry, -}; +DemobusBus demo_can_bus{bindings::demo_can_base}; int main(void) { bindings::Initialize(); - generated::can::ButtonStatus btn_msg{}; - while (true) { - veh_can_bus.Update(); - veh_can_bus.Read(btn_msg); - - bindings::indicator.Set(btn_msg.state); + auto btn_msg = demo_can_bus.PopRxButtonStatus(); + + // We're not guaranteed to have message available, so we need to check. + if (btn_msg.has_value()) { + // Unpack the value from the optional + RxButtonStatus msg = btn_msg.value(); + bindings::indicator.Set(msg.State()); + } } return 0; diff --git a/firmware/projects/Demo/CAN/Receive/platforms/cli/bindings.cc b/firmware/projects/Demo/CAN/Receive/platforms/cli/bindings.cc index 965038a6f..ecb386ac0 100644 --- a/firmware/projects/Demo/CAN/Receive/platforms/cli/bindings.cc +++ b/firmware/projects/Demo/CAN/Receive/platforms/cli/bindings.cc @@ -13,17 +13,17 @@ namespace mcal { using namespace cli::periph; -CanBase veh_can_base{"vcan0"}; +CanBase demo_can_base{"vcan0"}; DigitalOutput indicator{"Indicator"}; } // namespace mcal namespace bindings { -shared::periph::CanBase& veh_can_base = mcal::veh_can_base; +shared::periph::CanBase& demo_can_base = mcal::demo_can_base; shared::periph::DigitalOutput& indicator = mcal::indicator; void Initialize() { std::cout << "Initializing CLI..." << std::endl; - mcal::veh_can_base.Setup(); + mcal::demo_can_base.Setup(); std::cerr << "The CLI platform has no way to receive messages from " "CAN/Send. Consider using the linux platform instead." << std::endl; diff --git a/firmware/projects/Demo/CAN/Receive/platforms/linux/bindings.cc b/firmware/projects/Demo/CAN/Receive/platforms/linux/bindings.cc index fa129bc99..a160d6c30 100644 --- a/firmware/projects/Demo/CAN/Receive/platforms/linux/bindings.cc +++ b/firmware/projects/Demo/CAN/Receive/platforms/linux/bindings.cc @@ -9,19 +9,19 @@ namespace mcal { using namespace lnx::periph; -CanBase veh_can_base{"vcan0"}; +CanBase demo_can_base{"vcan0"}; DigitalOutput indicator{"Indicator"}; } // namespace mcal namespace bindings { -shared::periph::CanBase& veh_can_base = mcal::veh_can_base; +shared::periph::CanBase& demo_can_base = mcal::demo_can_base; shared::periph::DigitalOutput& indicator = mcal::indicator; void Initialize() { std::cout << "Initializing Linux" << std::endl; - mcal::veh_can_base.Setup(); + mcal::demo_can_base.Setup(); } } // namespace bindings \ No newline at end of file diff --git a/firmware/projects/Demo/CAN/Send/bindings.hpp b/firmware/projects/Demo/CAN/Send/bindings.hpp index 0573b3e40..f54c35465 100644 --- a/firmware/projects/Demo/CAN/Send/bindings.hpp +++ b/firmware/projects/Demo/CAN/Send/bindings.hpp @@ -7,7 +7,7 @@ namespace bindings { -extern shared::periph::CanBase& veh_can_base; +extern shared::periph::CanBase& demo_can_base; extern shared::periph::DigitalInput& button; extern void Initialize(); diff --git a/firmware/projects/Demo/CAN/Send/main.cc b/firmware/projects/Demo/CAN/Send/main.cc index bdfd114b6..28ffb84ed 100644 --- a/firmware/projects/Demo/CAN/Send/main.cc +++ b/firmware/projects/Demo/CAN/Send/main.cc @@ -3,30 +3,23 @@ #include -#include "bindings.hpp" -#include "generated/can/demobus_can_messages.hpp" -#include "generated/can/demobus_msg_registry.hpp" -#include "shared/comms/can/can_bus.hpp" +#include "bindings.h" +#include "generated/can/demobus_bus.hpp" +#include "generated/can/demobus_messages.hpp" -generated::can::DemobusMsgRegistry veh_can_registry{}; +using namespace generated::can; -shared::can::CanBus veh_can_bus{ - bindings::veh_can_base, - veh_can_registry, -}; +DemobusBus can_bus{bindings::demo_can_base}; int main(void) { bindings::Initialize(); uint32_t interval_ms = 50; - generated::can::ButtonStatus btn_msg{}; - while (true) { - veh_can_bus.Update(); - - btn_msg.state = bindings::button.Read(); - - veh_can_bus.Send(btn_msg); + TxButtonStatus msg{ + .state = bindings::button.Read(), + }; + can_bus.Send(msg); bindings::TickBlocking(interval_ms); } diff --git a/firmware/projects/Demo/CAN/Send/platforms/cli/bindings.cc b/firmware/projects/Demo/CAN/Send/platforms/cli/bindings.cc index bed9dc529..da6d024ff 100644 --- a/firmware/projects/Demo/CAN/Send/platforms/cli/bindings.cc +++ b/firmware/projects/Demo/CAN/Send/platforms/cli/bindings.cc @@ -13,12 +13,12 @@ namespace mcal { using namespace cli::periph; -CanBase veh_can_base{"vcan0"}; +CanBase demo_can_base{"vcan0"}; DigitalInput button{"Button"}; } // namespace mcal namespace bindings { -shared::periph::CanBase& veh_can_base = mcal::veh_can_base; +shared::periph::CanBase& demo_can_base = mcal::demo_can_base; shared::periph::DigitalInput& button = mcal::button; void TickBlocking(uint32_t ticks) { @@ -28,7 +28,7 @@ void TickBlocking(uint32_t ticks) { } void Initialize() { - mcal::veh_can_base.Setup(); + mcal::demo_can_base.Setup(); std::cout << "Initializing CLI..." << std::endl; } } // namespace bindings \ No newline at end of file diff --git a/firmware/projects/Demo/CAN/Send/platforms/linux/bindings.cc b/firmware/projects/Demo/CAN/Send/platforms/linux/bindings.cc index bcfc0b889..8f7ffcfce 100644 --- a/firmware/projects/Demo/CAN/Send/platforms/linux/bindings.cc +++ b/firmware/projects/Demo/CAN/Send/platforms/linux/bindings.cc @@ -9,14 +9,14 @@ namespace mcal { using namespace lnx::periph; -CanBase veh_can_base{"vcan0"}; +CanBase demo_can_base{"vcan0"}; DigitalInput button{"Button"}; } // namespace mcal namespace bindings { -shared::periph::CanBase& veh_can_base = mcal::veh_can_base; +shared::periph::CanBase& demo_can_base = mcal::demo_can_base; shared::periph::DigitalInput& button = mcal::button; void TickBlocking(uint32_t ms) { @@ -25,7 +25,7 @@ void TickBlocking(uint32_t ms) { void Initialize() { std::cout << "Initializing Linux" << std::endl; - mcal::veh_can_base.Setup(); + mcal::demo_can_base.Setup(); } } // namespace bindings \ No newline at end of file diff --git a/firmware/shared/CMakeLists.txt b/firmware/shared/CMakeLists.txt index 08e49530d..21e9fae67 100644 --- a/firmware/shared/CMakeLists.txt +++ b/firmware/shared/CMakeLists.txt @@ -1,3 +1,6 @@ -add_library(shared INTERFACE) +add_library(shared STATIC) -target_include_directories(shared INTERFACE ${CMAKE_SOURCE_DIR}) +target_include_directories(shared PUBLIC ${CMAKE_SOURCE_DIR}) + +add_subdirectory(comms) +add_subdirectory(periph) \ No newline at end of file diff --git a/firmware/shared/comms/CMakeLists.txt b/firmware/shared/comms/CMakeLists.txt new file mode 100644 index 000000000..ab00f7184 --- /dev/null +++ b/firmware/shared/comms/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(can) \ No newline at end of file diff --git a/firmware/shared/comms/can/CMakeLists.txt b/firmware/shared/comms/can/CMakeLists.txt new file mode 100644 index 000000000..a1b0c955f --- /dev/null +++ b/firmware/shared/comms/can/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(shared PRIVATE msg.cc bus.cc) \ No newline at end of file diff --git a/firmware/shared/comms/can/bus.cc b/firmware/shared/comms/can/bus.cc new file mode 100644 index 000000000..19871d13f --- /dev/null +++ b/firmware/shared/comms/can/bus.cc @@ -0,0 +1,10 @@ +#include "bus.h" +#include "shared/periph/can.h" + +namespace shared::can { + +Bus::Bus(periph::CanBase& can_base) : can_base_(can_base) { + can_base_.RegisterBus(this); +} + +} // namespace shared::can \ No newline at end of file diff --git a/firmware/shared/comms/can/bus.h b/firmware/shared/comms/can/bus.h new file mode 100644 index 000000000..e5004ccdc --- /dev/null +++ b/firmware/shared/comms/can/bus.h @@ -0,0 +1,29 @@ +#pragma once + +#include "shared/comms/can/msg.h" +#include "shared/periph/can.h" + +namespace shared::can { + +class Bus { +public: + Bus(periph::CanBase& can_base); + + template + void Send(const T& msg); + +private: + periph::CanBase& can_base_; + + virtual void AddMessage(const RawMessage& msg, uint32_t timestamp) = 0; + friend class shared::periph::CanBase; +}; + +// Must define this function in the header file since it is templated +template +void Bus::Send(const T& msg) { + RawMessage raw_msg = msg.pack(); + can_base_.Send(raw_msg); +} + +} // namespace shared::can \ No newline at end of file diff --git a/firmware/shared/comms/can/can_bus.hpp b/firmware/shared/comms/can/can_bus.hpp deleted file mode 100644 index 97ee8eb50..000000000 --- a/firmware/shared/comms/can/can_bus.hpp +++ /dev/null @@ -1,52 +0,0 @@ -/// @author Samuel Parent -/// @date 2024-02-09 - -#pragma once - -#include "shared/comms/can/can_msg.hpp" -#include "shared/comms/can/msg_registry.hpp" -#include "shared/comms/can/raw_can_msg.hpp" -#include "shared/periph/can.hpp" - -namespace shared::can { - -class CanBus { -public: - CanBus(shared::periph::CanBase& can_base, MsgRegistry& rx_msg_registry) - : can_base_(can_base), rx_msg_registry_(rx_msg_registry) {}; - - void Send(CanTxMsg& msg) { - RawCanMsg raw_msg; - - msg.Pack(raw_msg); - - can_base_.Send(raw_msg); - } - - void Update() { - RawCanMsg rx_queue[kMaxMsgQueueLen] = {}; - - can_base_.ReadQueue(rx_queue, kMaxMsgQueueLen); - - for (const auto& raw_msg : rx_queue) { - rx_msg_registry_.SetMessage(raw_msg); - } - } - - void Read(CanRxMsg& rx_msg) { - rx_msg_registry_.GetMessage(rx_msg); - } - - void ReadWithUpdate(CanRxMsg& rx_msg) { - Update(); - Read(rx_msg); - } - -private: - constexpr static int kMaxMsgQueueLen = 100; - - shared::periph::CanBase& can_base_; - MsgRegistry& rx_msg_registry_; -}; - -} // namespace shared::can \ No newline at end of file diff --git a/firmware/shared/comms/can/can_msg.hpp b/firmware/shared/comms/can/can_msg.hpp deleted file mode 100644 index 2e1aa0433..000000000 --- a/firmware/shared/comms/can/can_msg.hpp +++ /dev/null @@ -1,61 +0,0 @@ -/// @author Samuel Parent -/// @date 2024-01-06 - -#pragma once - -#include "shared/comms/can/raw_can_msg.hpp" - -namespace shared::can { - -class CanMsg {}; - -// Forward declaration of msg registry -class MsgRegistry; - -class CanRxMsg : public CanMsg { -private: - virtual void Clone(CanRxMsg&) const = 0; - virtual void Unpack(const RawCanMsg&) = 0; - virtual CanId Id() const = 0; - -protected: - template - static inline T unpack_right_shift(uint8_t value, uint8_t shift, - uint8_t mask) { - return static_cast(static_cast(value & mask) >> shift); - } - - template - static inline T unpack_left_shift(uint8_t value, uint8_t shift, - uint8_t mask) { - return static_cast(static_cast(value & mask) << shift); - } - - friend class MsgRegistry; -}; - -class CanBus; - -class CanTxMsg : public CanMsg { -private: - virtual void Pack(RawCanMsg&) const = 0; - -protected: - template - static inline uint8_t pack_left_shift(T value, uint8_t shift, - uint8_t mask) { - return static_cast(static_cast(value << shift) & - mask); - } - - template - static inline uint8_t pack_right_shift(T value, uint8_t shift, - uint8_t mask) { - return static_cast(static_cast(value >> shift) & - mask); - } - - friend class CanBus; -}; - -} // namespace shared::can diff --git a/firmware/shared/comms/can/msg.cc b/firmware/shared/comms/can/msg.cc new file mode 100644 index 000000000..c8d42ccce --- /dev/null +++ b/firmware/shared/comms/can/msg.cc @@ -0,0 +1,13 @@ +#include + +#include "msg.h" + +namespace shared::can { + +RawMessage::RawMessage(uint32_t id_, uint8_t data_length_, + const uint8_t data_[8]) + : id(id_), data_length(data_length_) { + std::memcpy(data, data_, data_length); +} + +} // namespace shared::can \ No newline at end of file diff --git a/firmware/shared/comms/can/msg.h b/firmware/shared/comms/can/msg.h new file mode 100644 index 000000000..dc9ee7be5 --- /dev/null +++ b/firmware/shared/comms/can/msg.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include + +namespace shared::can { + +struct RawMessage { + RawMessage(uint32_t id_, uint8_t data_length_, const uint8_t data_[8]); + + uint32_t id; + uint8_t data_length; + uint8_t data[8]; +}; + +// TxMessage concept is required by Bus.Send() +template +concept TxMessage = requires(const T msg) { + { msg.pack() } -> std::same_as; +}; + +} // namespace shared::can + +// Convert RawMessage to string with `std::format` +template <> +struct std::formatter { + constexpr auto parse(auto& ctx) { + return ctx.begin(); + } + + template + auto format(const shared::can::RawMessage& msg, FormatContext& ctx) const { + std::string str = std::format("[{:02X}]", msg.id); + for (int i = 0; i < msg.data_length; ++i) { + str += std::format(" {:02X}", msg.data[i]); + } + + return std::format_to(ctx.out(), "{}", str); + } +}; \ No newline at end of file diff --git a/firmware/shared/comms/can/msg_registry.hpp b/firmware/shared/comms/can/msg_registry.hpp deleted file mode 100644 index aba5ae6f6..000000000 --- a/firmware/shared/comms/can/msg_registry.hpp +++ /dev/null @@ -1,30 +0,0 @@ -/// @author Samuel Parent -/// @date 2024-03-01 - -#pragma once - -#include "shared/comms/can/raw_can_msg.hpp" - -namespace shared::can { - -class MsgRegistry { -public: - virtual bool SetMessage(const RawCanMsg&) = 0; - virtual bool GetMessage(CanRxMsg&) = 0; - -protected: - static inline void Unpack(CanRxMsg* rx_msg, const RawCanMsg& raw_msg) { - rx_msg->Unpack(raw_msg); - } - - static inline void Clone(const CanRxMsg* const srd_rx_msg, - CanRxMsg& dst_rx_msg) { - srd_rx_msg->Clone(dst_rx_msg); - } - - static inline CanId MsgId(const CanRxMsg* const rx_msg) { - return rx_msg->Id(); - } -}; - -} // namespace shared::can \ No newline at end of file diff --git a/firmware/shared/comms/can/raw_can_msg.hpp b/firmware/shared/comms/can/raw_can_msg.hpp deleted file mode 100644 index 1e66e6590..000000000 --- a/firmware/shared/comms/can/raw_can_msg.hpp +++ /dev/null @@ -1,39 +0,0 @@ -/// @author Samuel Parent -/// @date 2024-01-13 - -#pragma once - -#include - -#include -#include - -namespace shared::can { - -constexpr uint8_t kMaxMsgBytes = 8; - -using CanId = uint32_t; - -struct CanHeader { - CanId id; - uint8_t data_len; - bool is_extended_frame; -}; - -struct RawCanMsg { - CanHeader header; - uint8_t data[kMaxMsgBytes] = {0}; - uint32_t tick_timestamp = 0; - - void Copy(const shared::can::RawCanMsg& other) noexcept { - header.id = other.header.id; - header.data_len = other.header.data_len; - header.is_extended_frame = other.header.is_extended_frame; - tick_timestamp = other.tick_timestamp; - - std::copy(std::begin(other.data), std::end(other.data), - std::begin(data)); - } -}; - -} // namespace shared::can diff --git a/firmware/shared/periph/CMakeLists.txt b/firmware/shared/periph/CMakeLists.txt new file mode 100644 index 000000000..5cb3c9c0b --- /dev/null +++ b/firmware/shared/periph/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(shared PRIVATE can.cc) \ No newline at end of file diff --git a/firmware/shared/periph/can.cc b/firmware/shared/periph/can.cc new file mode 100644 index 000000000..8a50d9b44 --- /dev/null +++ b/firmware/shared/periph/can.cc @@ -0,0 +1,15 @@ +#include "can.h" +#include "shared/comms/can/bus.h" +#include "shared/comms/can/msg.h" + +namespace shared::periph { + +void CanBase::AddToBus(can::RawMessage msg) { + bus_->AddMessage(msg, GetTimestamp()); +} + +void CanBase::RegisterBus(can::Bus* bus) { + bus_ = bus; +} + +} // namespace shared::periph \ No newline at end of file diff --git a/firmware/shared/periph/can.hpp b/firmware/shared/periph/can.hpp index a385ea3c9..0fc888658 100644 --- a/firmware/shared/periph/can.hpp +++ b/firmware/shared/periph/can.hpp @@ -5,15 +5,23 @@ #include -#include "shared/comms/can/raw_can_msg.hpp" +#include "shared/comms/can/msg.hpp" #include "shared/util/peripheral.hpp" namespace shared::periph { class CanBase : public util::Peripheral { public: - virtual void Send(const shared::can::RawCanMsg&) = 0; - virtual void ReadQueue(shared::can::RawCanMsg[], size_t len) = 0; + virtual void Send(const shared::can::RawMessage&) = 0; + void AddToBus(can::RawMessage msg); + +private: + void RegisterBus(can::Bus* bus); + virtual uint32_t GetTimestamp() const = 0; + + can::Bus* bus_; + + friend class can::Bus; }; } // namespace shared::periph \ No newline at end of file diff --git a/scripts/cangen/cangen/can_generator.py b/scripts/cangen/cangen/can_generator.py index 5ce2e9f59..1746d07d2 100644 --- a/scripts/cangen/cangen/can_generator.py +++ b/scripts/cangen/cangen/can_generator.py @@ -23,7 +23,7 @@ EIGHT_BYTES = 8 TOTAL_BITS = EIGHT_BITS * EIGHT_BYTES -TEMPLATE_FILE_NAMES = ["can_messages.hpp.jinja2", "msg_registry.hpp.jinja2"] +TEMPLATE_FILE_NAMES = ["messages.hpp.jinja2", "bus.hpp.jinja2"] def _parse_dbc_files(dbc_file: str) -> Database: @@ -168,11 +168,14 @@ def _camel_to_snake(text): return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower() -def _create_output_file_name(output_dir: str, bus_name: str, template_file_name: str) -> str: +def _create_output_file_name( + output_dir: str, bus_name: str, template_file_name: str +) -> str: return os.path.join( output_dir, bus_name.lower() + "_" + template_file_name.removesuffix(".jinja2") ) + def _generate_code(bus: Bus, output_dir: str): """ Parses DBC files, extracts information, and generates code using Jinja2 @@ -212,7 +215,9 @@ def _generate_code(bus: Bus, output_dir: str): template = env.get_template(template_file_name) rendered_code = template.render(**context) - output_file_name = _create_output_file_name(output_dir, bus.bus_name, template_file_name) + output_file_name = _create_output_file_name( + output_dir, bus.bus_name, template_file_name + ) with open(output_file_name, "w") as output_file: output_file.write(rendered_code) logger.info(f"Rendered code written to '{os.path.abspath(output_file_name)}'") @@ -236,7 +241,8 @@ def _prepare_output_directory(output_dir: str): def generate_can_for_project(project_folder_name: str): """Generates C code for a given project. - Ensure the project folder contains a config.yaml file which specifies the relative path to its corresponding dbc file.""" + Ensure the project folder contains a config.yaml file which specifies the relative path to its corresponding dbc file. + """ os.chdir(project_folder_name) config = Config.from_yaml("config.yaml") diff --git a/scripts/cangen/cangen/templates/bus.h.jinja2 b/scripts/cangen/cangen/templates/bus.h.jinja2 new file mode 100644 index 000000000..9153e34fd --- /dev/null +++ b/scripts/cangen/cangen/templates/bus.h.jinja2 @@ -0,0 +1,67 @@ +/// @author Samuel Parent, Blake Freer +/// @date November 2024 + +// WARNING: DO NOT MODIFY THIS CODE. THIS IS AN AUTOGENERATED FILE. + +// clang-format off + +#include + +#include "shared/comms/can/bus.h" +#include "shared/comms/can/msg.h" +#include "shared/periph/can.h" +#include "{{bus_name | lower}}_messages.h" +#include "etl/mutex.h" + +namespace generated::can { + +{% set bus = bus_name+"Bus" %} +class {{ bus }} : public shared::can::Bus { +public: + {{ bus }}(shared::periph::CanBase& can_base) : shared::can::Bus(can_base) {} + + {% for msg in rx_msgs %} + {% set msg_title = "Rx"+msg.name %} + + // Get the latest message of type {{msg_title}} + auto Get{{msg_title}}() -> std::optional<{{msg_title}}> { + etl::lock_guard lock(msg_guard_); + return {{ msg.name | camel_to_snake }}_msg_; + } + + // Get the latest message of type {{msg_title}} and clear it + auto Pop{{msg_title}}() -> std::optional<{{msg_title}}> { + etl::lock_guard lock(msg_guard_); + auto temp = {{ msg.name | camel_to_snake }}_msg_; + {{ msg.name | camel_to_snake }}_msg_ = std::nullopt; + return temp; + } + {% endfor %} + +private: + etl::mutex msg_guard_; + + void AddMessage(const shared::can::RawMessage& msg, uint32_t timestamp) override { + etl::lock_guard lock(msg_guard_); + switch(msg.id) { + {% for msg in rx_msgs %} + case Rx{{msg.name}}::id: + {{ msg.name | camel_to_snake }}_msg_ = Rx{{msg.name}}(msg, timestamp); + break; + {% endfor %} + default: + break; // ignore messages not intended for this node on this bus + } + } + + // All messages start empty and will be filled when the first instance arrives. + {% for msg in rx_msgs %} + std::optional {{msg.name | camel_to_snake}}_msg_ = std::nullopt; + {% endfor %} + + friend class Base; +}; + +} // namespace generated::can + +// clang-format on \ No newline at end of file diff --git a/scripts/cangen/cangen/templates/can_messages.hpp.jinja2 b/scripts/cangen/cangen/templates/messages.h.jinja2 similarity index 51% rename from scripts/cangen/cangen/templates/can_messages.hpp.jinja2 rename to scripts/cangen/cangen/templates/messages.h.jinja2 index ab9e601f2..3a6ebe24c 100644 --- a/scripts/cangen/cangen/templates/can_messages.hpp.jinja2 +++ b/scripts/cangen/cangen/templates/messages.h.jinja2 @@ -1,22 +1,43 @@ -/// @author Samuel Parent -/// @date {{ date }} +/// @author Samuel Parent, Blake Freer +/// @date November 2024 // WARNING: DO NOT MODIFY THIS CODE. THIS IS AN AUTOGENERATED FILE. /// @todo support enums /// @todo clang format -/// @todo make Id static inline -/// @todo make Clone work in a better way // clang-format off #pragma once -#include "shared/comms/can/can_msg.hpp" -#include "shared/comms/can/raw_can_msg.hpp" +#include +#include "shared/comms/can/msg.hpp" + +template +static inline T unpack_right_shift(uint8_t value, uint8_t shift, uint8_t mask) { + return static_cast(static_cast(value & mask) >> shift); +} + +template +static inline T unpack_left_shift(uint8_t value, uint8_t shift, uint8_t mask) { + return static_cast(static_cast(value & mask) << shift); +} + +template +static inline uint8_t pack_left_shift(T value, uint8_t shift, uint8_t mask) { + return static_cast(static_cast(value << shift) & mask); +} + +template +static inline uint8_t pack_right_shift(T value, uint8_t shift, uint8_t mask) { + return static_cast(static_cast(value >> shift) & mask); +} namespace generated::can { +{% set bus = bus_name+"Bus" %} +class {{ bus }}; + /* -------------------- ENUMS -------------------- */ {% for msg in all_msgs %} {% for sig in msg.signals %} @@ -33,55 +54,41 @@ enum class {{ sig.name }} { /* -------------------- RX MESSAGES -------------------- */ {% for msg in rx_msgs %} -{% set class_name = msg.name %} -class {{ class_name }} : public shared::can::CanRxMsg { +{% set class_name = "Rx"+msg.name %} +class {{ class_name }} { public: {% for sig in msg.signals %} - {{ signal_types[msg.name][sig.name] }} {{ sig.name | camel_to_snake }} = 0; + inline {{ signal_types[msg.name][sig.name] }} {{ sig.name }}() const { return {{ sig.name | camel_to_snake }}_; } {% endfor %} - // Measured in ms since program start - uint32_t tick_timestamp = 0; - private: - // Message properties - static constexpr shared::can::CanId kCanId = {{ msg.frame_id | decimal_to_hex }}; - static constexpr uint8_t kDlc = {{ msg.length }}; - static constexpr bool kIsExtFrame = {{ msg.is_extended_frame | lower }}; - - // Signal properties {% for sig in msg.signals %} - {% set sig_var = sig.name | camel_to_snake %} - {% set sig_var_type = signal_types[msg.name][sig.name] %} - static constexpr double k{{ sig.name }}Scale = {{ sig.scale }}; - static constexpr double k{{ sig.name }}Offset = {{ sig.offset }}; + {{ signal_types[msg.name][sig.name] }} {{ sig.name | camel_to_snake }}_; {% endfor %} - shared::can::CanId Id() const override { return kCanId; } + // Timestamp is platform dependent. See mcal/PLATFORM/periph/can + uint32_t timestamp_; - void Clone(shared::can::CanRxMsg& rx_msg) const override { - {{ class_name }}* p_rx_msg = static_cast<{{ class_name }}*>(&rx_msg); + constexpr static uint32_t id = {{ msg.frame_id }}; + constexpr static uint8_t data_length = {{ msg.length }}; {% for sig in msg.signals %} {% set sig_var = sig.name | camel_to_snake %} - p_rx_msg->{{ sig_var }} = {{ sig_var }}; + {% set sig_var_type = signal_types[msg.name][sig.name] %} + static constexpr double k{{ sig.name }}Scale = {{ sig.scale }}; + static constexpr double k{{ sig.name }}Offset = {{ sig.offset }}; {% endfor %} - p_rx_msg->tick_timestamp = tick_timestamp; - } - void Unpack(const shared::can::RawCanMsg& raw_msg) override { - if (raw_msg.header.id != kCanId) { - return; - } - - tick_timestamp = raw_msg.tick_timestamp; - - // temporary signal variables - {% for sig in msg.signals %} + friend class {{ bus }}; + + // Only {{ bus }} can construct this class which guarantees the data came from the CAN line. + {{ class_name }}(shared::can::RawMessage raw_msg, uint32_t timestamp) { + timestamp_ = timestamp; + {% for sig in msg.signals %} {{ temp_signal_types[msg.name][sig.name] }} temp_{{ sig.name | camel_to_snake }} = 0; - {% endfor %} + {% endfor %} - {% for sig in msg.signals %} + {% for sig in msg.signals %} {% set masks, shifts = unpack_info[msg.name][sig.name] %} {% set temp_sig_var = "temp_" + sig.name | camel_to_snake %} {% set temp_sig_var_type = temp_signal_types[msg.name][sig.name] %} @@ -100,82 +107,73 @@ private: {% endif %} {% endif %} {% endfor %} - {{ sig_var }} = static_cast<{{ sig_var_type }}>(static_cast({{ temp_sig_var }}) * {{ sig_var_scale }} + {{ sig_var_offset }}); - - {% endfor %} + {{ sig_var }}_ = static_cast<{{ sig_var_type }}>(static_cast({{ temp_sig_var }}) * {{ sig_var_scale }} + {{ sig_var_offset }}); + {% endfor %} } + {{ class_name }}() = delete; }; + + {% endfor %} /* -------------------- TX MESSAGES -------------------- */ {% for msg in tx_msgs %} -{% set class_name = msg.name %} -class {{ class_name }} : public shared::can::CanTxMsg { +{% set class_name = "Tx"+msg.name %} +class {{ class_name }} { public: {% for sig in msg.signals %} - {{ signal_types[msg.name][sig.name] }} {{ sig.name | camel_to_snake }} = 0; + {{ signal_types[msg.name][sig.name] }} {{ sig.name | camel_to_snake }}; {% endfor %} -private: - // Message properties - static constexpr shared::can::CanId kCanId = {{ msg.frame_id | decimal_to_hex }}; - static constexpr uint8_t kDlc = {{ msg.length }}; - static constexpr bool kIsExtFrame = {{ msg.is_extended_frame | lower }}; + shared::can::RawMessage pack() const { + uint8_t data[{{msg.length}}] = {}; - // Signal properties - {% for sig in msg.signals %} - {% set sig_var = sig.name | camel_to_snake %} - {% set sig_var_type = signal_types[msg.name][sig.name] %} - static constexpr double k{{ sig.name }}Scale = {{ sig.scale }}; - static constexpr double k{{ sig.name }}Offset = {{ sig.offset }}; - {% endfor %} - - void Pack(shared::can::RawCanMsg& raw_msg) const override { - // temporary raw msg - shared::can::RawCanMsg temp_raw_msg; - temp_raw_msg.header = { - .id = kCanId, - .data_len = kDlc, - .is_extended_frame = kIsExtFrame, - }; + // @todo clamp data // temporary signal variables - {% for sig in msg.signals %} + {% for sig in msg.signals %} {% set sig_var = sig.name | camel_to_snake %} - {% set sig_var_scale = "k" + sig.name + "Scale" %} - {% set sig_var_offset = "k" + sig.name + "Offset" %} - {% set temp_sig_var = "temp_" + (sig.name | camel_to_snake) %} - {% set temp_sig_var_type = temp_signal_types[msg.name][sig.name] %} - {{ temp_sig_var_type }} {{ temp_sig_var }} = static_cast<{{ temp_sig_var_type }}>(static_cast({{ sig_var }} - {{ sig_var_offset }}) / {{ sig_var_scale }}); - {% endfor %} + {% set type = temp_signal_types[msg.name][sig.name] %} + {{ type }} temp_{{ sig.name | camel_to_snake }} = static_cast<{{ type }}>(static_cast({{ sig_var }} - k{{sig.name}}Offset ) / k{{sig.name}}Scale); + {% endfor %} - {% for sig in msg.signals %} + {% for sig in msg.signals %} {% set masks, shifts = pack_info[msg.name][sig.name] %} - {% set temp_sig_var = "temp_" + sig.name | camel_to_snake %} {% set sig_var = sig.name | camel_to_snake %} {% set sig_var_type = signal_types[msg.name][sig.name] %} - {% set sig_var_scale = "k" + sig.name + "Scale" %} - {% set sig_var_offset = "k" + sig.name + "Offset" %} {% for mask in masks %} {% set i = loop.index0 %} {% set shift = shifts[i] %} {% if mask != 0 %} {% if shift >= 0 %} - temp_raw_msg.data[{{ i }}] |= pack_right_shift({{ temp_sig_var }}, {{ shift }}U, {{ mask | decimal_to_hex }}U); + data[{{ i }}] |= pack_right_shift(temp_{{ sig_var }}, {{ shift }}U, {{ mask | decimal_to_hex }}U); {% else %} - temp_raw_msg.data[{{ i }}] |= pack_left_shift({{ temp_sig_var }}, {{ -(shift) }}U, {{ mask | decimal_to_hex }}U); + data[{{ i }}] |= pack_left_shift(temp_{{ sig_var }}, {{ -(shift) }}U, {{ mask | decimal_to_hex }}U); {% endif %} {% endif %} {% endfor %} - {% endfor %} + {% endfor %} - // Copy temp raw msg to raw msg - raw_msg.Copy(temp_raw_msg); + return shared::can::RawMessage(id, data_length, data); } + +private: + static constexpr uint32_t id = {{ msg.frame_id }}; + static constexpr uint8_t data_length = {{ msg.length }}; + + // Signal properties + {% for sig in msg.signals %} + {% set sig_var = sig.name | camel_to_snake %} + {% set sig_var_type = signal_types[msg.name][sig.name] %} + static constexpr double k{{ sig.name }}Scale = {{ sig.scale }}; + static constexpr double k{{ sig.name }}Offset = {{ sig.offset }}; + {% endfor %} }; + + {% endfor %} -// clang-format on +} // namespace generated::can -} // namespace generated::can \ No newline at end of file +// clang-format on \ No newline at end of file diff --git a/scripts/cangen/cangen/templates/msg_registry.hpp.jinja2 b/scripts/cangen/cangen/templates/msg_registry.hpp.jinja2 deleted file mode 100644 index 8c8df8093..000000000 --- a/scripts/cangen/cangen/templates/msg_registry.hpp.jinja2 +++ /dev/null @@ -1,85 +0,0 @@ -/// @author Samuel Parent -/// @date {{ date }} - -// WARNING: DO NOT MODIFY THIS CODE. THIS IS AN AUTOGENERATED FILE. - -#pragma once - -#include "etl/unordered_map.h" -#include "{{ bus_name | lower }}_can_messages.hpp" -#include "shared/comms/can/can_msg.hpp" -#include "shared/comms/can/msg_registry.hpp" -#include "shared/comms/can/raw_can_msg.hpp" - -namespace generated::can { - -{% set reg_class_name = bus_name + "MsgRegistry" %} - -{% if rx_msgs | length == 0 %} -// Dummy class - there are no rx messages for this ecu. -class {{ reg_class_name }} : public shared::can::MsgRegistry { -public: - bool SetMessage(const shared::can::RawCanMsg& raw_msg) override { - return false; - } - - bool GetMessage(shared::can::CanRxMsg& rx_msg) override { - return false; - } -}; -{% else %} -class {{ reg_class_name }} : public shared::can::MsgRegistry { -public: - bool SetMessage(const shared::can::RawCanMsg& raw_msg) override { - bool msg_found = (rx_msg_map_.count(raw_msg.header.id) > 0); - - if (msg_found) { - Unpack(rx_msg_map_[raw_msg.header.id], raw_msg); - } - - return msg_found; - } - - bool GetMessage(shared::can::CanRxMsg& rx_msg) override { - shared::can::CanId mid = MsgId(&rx_msg); - - bool msg_found = (rx_msg_map_.count(mid) > 0); - - if (msg_found) { - Clone(rx_msg_map_[mid], rx_msg); - } - - return msg_found; - } - -private: - static constexpr size_t kNumRxMsgs = {{ rx_msgs | length }}; - - // Message IDs - {% for msg in rx_msgs %} - {% set class_name = msg.name %} - {% set class_const = "k" + class_name + "CanId" %} - {% set hex_id = msg.frame_id | decimal_to_hex %} - static constexpr shared::can::CanId {{ class_const }} = {{ hex_id }}; - {% endfor %} - - // Can Rx Messages - {% for msg in rx_msgs %} - {% set class_name = msg.name %} - {% set class_var = (class_name | camel_to_snake) + "_" %} - {{ class_name }} {{ class_var }}; - {% endfor %} - - etl::unordered_map - rx_msg_map_ = { - {% for msg in rx_msgs %} - {% set class_name = msg.name %} - {% set class_var = (class_name | camel_to_snake) + "_" %} - {% set msg_id = "k" + class_name + "CanId" %} - { {{ msg_id }}, &{{class_var}} }, - {% endfor %} - }; -}; -{% endif %} - -} // namespace generated::can \ No newline at end of file