diff --git a/core/include/librmcs/data/datas.hpp b/core/include/librmcs/data/datas.hpp index afa015c..3e7bd86 100644 --- a/core/include/librmcs/data/datas.hpp +++ b/core/include/librmcs/data/datas.hpp @@ -4,6 +4,8 @@ #include #include +#include + namespace librmcs::data { enum class DataId : uint8_t { @@ -43,12 +45,10 @@ struct UartDataView { }; struct GpioDigitalDataView { - uint8_t channel; bool high; }; struct GpioAnalogDataView { - uint8_t channel; uint16_t value; }; @@ -59,12 +59,20 @@ enum class GpioPull : uint8_t { }; struct GpioReadConfigView { - uint8_t channel; uint16_t period_ms = 0; bool asap = false; bool rising_edge = false; bool falling_edge = false; GpioPull pull = GpioPull::kNone; + + [[nodiscard]] constexpr bool supported(const spec::GpioDescriptor& gpio) const noexcept { + return (!asap || gpio.supports(spec::GpioCapability::kDigitalReadOnce)) + && (!period_ms || gpio.supports(spec::GpioCapability::kDigitalReadPeriodic)) + && ((!rising_edge && !falling_edge) + || gpio.supports(spec::GpioCapability::kDigitalReadInterrupt)) + && (pull != GpioPull::kUp || gpio.supports(spec::GpioCapability::kPullUp)) + && (pull != GpioPull::kDown || gpio.supports(spec::GpioCapability::kPullDown)); + } }; struct AccelerometerDataView { @@ -93,9 +101,11 @@ class DataCallback { virtual bool uart_receive_callback(DataId id, const UartDataView& data) = 0; - virtual void gpio_digital_read_result_callback(const GpioDigitalDataView& data) = 0; + virtual void gpio_digital_read_result_callback( + uint8_t channel_index, const GpioDigitalDataView& data) = 0; - virtual void gpio_analog_read_result_callback(const GpioAnalogDataView& data) = 0; + virtual void + gpio_analog_read_result_callback(uint8_t channel_index, const GpioAnalogDataView& data) = 0; virtual void accelerometer_receive_callback(const AccelerometerDataView& data) = 0; diff --git a/core/include/librmcs/spec/c_board/gpio.hpp b/core/include/librmcs/spec/c_board/gpio.hpp new file mode 100644 index 0000000..2ebca46 --- /dev/null +++ b/core/include/librmcs/spec/c_board/gpio.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include + +#include + +namespace librmcs::spec::c_board { + +namespace internal { +class GpioDescriptors; +} + +// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions) +class GpioDescriptor : public spec::GpioDescriptor { + friend internal::GpioDescriptors; + constexpr GpioDescriptor(uint8_t channel_index, GpioCapability capability_mask) + : spec::GpioDescriptor(channel_index, capability_mask) {} + +public: + GpioDescriptor(const GpioDescriptor&) = delete; + GpioDescriptor& operator=(const GpioDescriptor&) = delete; + GpioDescriptor(GpioDescriptor&&) = delete; + GpioDescriptor& operator=(GpioDescriptor&&) = delete; + + [[nodiscard]] constexpr bool operator==(const GpioDescriptor& other) const noexcept { + return channel_index == other.channel_index; + } +}; + +namespace internal { +class GpioDescriptors { + static constexpr GpioDescriptor kArray[]{ + {0, kPwmCapabilities}, + {1, kPwmCapabilities}, + {2, kPwmCapabilities}, + {3, kPwmCapabilities}, + {4, kPwmCapabilities}, + {5, kPwmCapabilities & ~GpioCapability::kDigitalReadInterrupt}, + {6, kPwmCapabilities} + }; + static_assert(channel_indices_match_indices(kArray)); + +public: + constexpr GpioDescriptors() = default; + + static constexpr std::size_t size() noexcept { return std::size(kArray); } + + static constexpr const GpioDescriptor& operator[](std::size_t channel_index) noexcept { + return kArray[channel_index]; + } + + static constexpr const GpioDescriptor* begin() noexcept { return std::begin(kArray); } + + static constexpr const GpioDescriptor* end() noexcept { return std::end(kArray); } + + static constexpr const GpioDescriptor& kPwm1 = kArray[0]; + static constexpr const GpioDescriptor& kPwm2 = kArray[1]; + static constexpr const GpioDescriptor& kPwm3 = kArray[2]; + static constexpr const GpioDescriptor& kPwm4 = kArray[3]; + static constexpr const GpioDescriptor& kPwm5 = kArray[4]; + static constexpr const GpioDescriptor& kPwm6 = kArray[5]; + static constexpr const GpioDescriptor& kPwm7 = kArray[6]; +}; +} // namespace internal + +inline constexpr internal::GpioDescriptors kGpioDescriptors{}; + +} // namespace librmcs::spec::c_board diff --git a/core/include/librmcs/spec/gpio.hpp b/core/include/librmcs/spec/gpio.hpp new file mode 100644 index 0000000..b279f44 --- /dev/null +++ b/core/include/librmcs/spec/gpio.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include +#include + +namespace librmcs::spec { + +enum class GpioCapability : std::uint8_t { + kNone = 0, + kDigitalWrite = 1U << 0, + kAnalogWrite = 1U << 1, + kDigitalReadOnce = 1U << 2, + kDigitalReadPeriodic = 1U << 3, + kDigitalReadInterrupt = 1U << 4, + kPullUp = 1U << 5, + kPullDown = 1U << 6, +}; + +constexpr GpioCapability operator|(GpioCapability a, GpioCapability b) noexcept { + return static_cast(static_cast(a) | static_cast(b)); +} + +constexpr GpioCapability operator&(GpioCapability a, GpioCapability b) noexcept { + return static_cast(static_cast(a) & static_cast(b)); +} + +constexpr GpioCapability operator~(GpioCapability a) noexcept { + return static_cast(~static_cast(a)); +} + +inline constexpr GpioCapability kDigitalCapabilities = + GpioCapability::kDigitalWrite | GpioCapability::kDigitalReadOnce + | GpioCapability::kDigitalReadPeriodic | GpioCapability::kDigitalReadInterrupt + | GpioCapability::kPullUp | GpioCapability::kPullDown; + +inline constexpr GpioCapability kPwmCapabilities = + kDigitalCapabilities | GpioCapability::kAnalogWrite; + +struct GpioDescriptor { + std::uint8_t channel_index; + GpioCapability capability_mask = GpioCapability::kNone; + + [[nodiscard]] constexpr bool supports(GpioCapability capability) const noexcept { + return (capability_mask & capability) == capability; + } +}; + +template +[[nodiscard]] consteval bool + channel_indices_match_indices(const Descriptor (&descriptors)[n]) noexcept { + for (std::size_t index = 0; index < n; ++index) { + if (descriptors[index].channel_index != index) + return false; + } + return true; +} + +} // namespace librmcs::spec diff --git a/core/include/librmcs/spec/rmcs_board_lite/gpio.hpp b/core/include/librmcs/spec/rmcs_board_lite/gpio.hpp new file mode 100644 index 0000000..5b6c649 --- /dev/null +++ b/core/include/librmcs/spec/rmcs_board_lite/gpio.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include + +#include + +namespace librmcs::spec::rmcs_board_lite { + +namespace internal { +class GpioDescriptors; +} + +// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions) +class GpioDescriptor : public spec::GpioDescriptor { + friend internal::GpioDescriptors; + constexpr GpioDescriptor(uint8_t channel_index, GpioCapability capability_mask) + : spec::GpioDescriptor(channel_index, capability_mask) {} + +public: + GpioDescriptor(const GpioDescriptor&) = delete; + GpioDescriptor& operator=(const GpioDescriptor&) = delete; + GpioDescriptor(GpioDescriptor&&) = delete; + GpioDescriptor& operator=(GpioDescriptor&&) = delete; + + [[nodiscard]] constexpr bool operator==(const GpioDescriptor& other) const noexcept { + return channel_index == other.channel_index; + } +}; + +namespace internal { + +class GpioDescriptors { + static constexpr GpioDescriptor kArray[]{ + {0, kDigitalCapabilities}, + {1, kDigitalCapabilities}, + {2, kDigitalCapabilities}, + {3, kDigitalCapabilities}, + }; + static_assert(channel_indices_match_indices(kArray)); + +public: + constexpr GpioDescriptors() = default; + + static constexpr std::size_t size() noexcept { return std::size(kArray); } + + static constexpr const GpioDescriptor& operator[](std::size_t channel_index) noexcept { + return kArray[channel_index]; + } + + static constexpr const GpioDescriptor* begin() noexcept { return std::begin(kArray); } + + static constexpr const GpioDescriptor* end() noexcept { return std::end(kArray); } + + static constexpr const GpioDescriptor& kUart0Rx = kArray[0]; + static constexpr const GpioDescriptor& kUart0Tx = kArray[1]; + static constexpr const GpioDescriptor& kUart1Rx = kArray[2]; + static constexpr const GpioDescriptor& kUart1Tx = kArray[3]; +}; + +} // namespace internal + +inline constexpr internal::GpioDescriptors kGpioDescriptors{}; + +} // namespace librmcs::spec::rmcs_board_lite diff --git a/core/include/librmcs/spec/rmcs_board_pro/gpio.hpp b/core/include/librmcs/spec/rmcs_board_pro/gpio.hpp new file mode 100644 index 0000000..3e1b7a3 --- /dev/null +++ b/core/include/librmcs/spec/rmcs_board_pro/gpio.hpp @@ -0,0 +1,94 @@ +#pragma once + +#include +#include +#include + +#include + +namespace librmcs::spec::rmcs_board_pro { + +namespace internal { +class GpioDescriptors; +} + +// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions) +class GpioDescriptor : public spec::GpioDescriptor { + friend internal::GpioDescriptors; + constexpr GpioDescriptor(uint8_t channel_index, GpioCapability capability_mask) + : spec::GpioDescriptor(channel_index, capability_mask) {} + +public: + GpioDescriptor(const GpioDescriptor&) = delete; + GpioDescriptor& operator=(const GpioDescriptor&) = delete; + GpioDescriptor(GpioDescriptor&&) = delete; + GpioDescriptor& operator=(GpioDescriptor&&) = delete; + + [[nodiscard]] constexpr bool operator==(const GpioDescriptor& other) const noexcept { + return channel_index == other.channel_index; + } +}; + +namespace internal { + +class GpioDescriptors { + static constexpr GpioDescriptor kArray[]{ + { 0, kPwmCapabilities}, + { 1, kPwmCapabilities}, + { 2, kPwmCapabilities}, + { 3, kPwmCapabilities}, + { 4, kDigitalCapabilities}, + { 5, kDigitalCapabilities}, + { 6, kDigitalCapabilities}, + { 7, kDigitalCapabilities}, + { 8, kDigitalCapabilities}, + { 9, kDigitalCapabilities}, + {10, kDigitalCapabilities}, + {11, kDigitalCapabilities}, + {12, kDigitalCapabilities}, + {13, kDigitalCapabilities}, + {14, kDigitalCapabilities}, + {15, kDigitalCapabilities}, + {16, kDigitalCapabilities}, + }; + static_assert(channel_indices_match_indices(kArray)); + +public: + constexpr GpioDescriptors() = default; + + static constexpr std::size_t size() noexcept { return std::size(kArray); } + + static constexpr const GpioDescriptor& operator[](std::size_t channel_index) noexcept { + return kArray[channel_index]; + } + + static constexpr const GpioDescriptor* begin() noexcept { return std::begin(kArray); } + + static constexpr const GpioDescriptor* end() noexcept { return std::end(kArray); } + + static constexpr const GpioDescriptor& kPwm0 = kArray[0]; + static constexpr const GpioDescriptor& kPwm1 = kArray[1]; + static constexpr const GpioDescriptor& kPwm2 = kArray[2]; + static constexpr const GpioDescriptor& kPwm3 = kArray[3]; + + static constexpr const GpioDescriptor& kSpiI2cSocket0 = kArray[4]; + static constexpr const GpioDescriptor& kSpiI2cSocket1 = kArray[5]; + static constexpr const GpioDescriptor& kSpiI2cSocket2 = kArray[6]; + static constexpr const GpioDescriptor& kSpiI2cSocket3 = kArray[7]; + static constexpr const GpioDescriptor& kSpiI2cSocket4 = kArray[8]; + static constexpr const GpioDescriptor& kSpiI2cSocket5 = kArray[9]; + static constexpr const GpioDescriptor& kSpiI2cSocket6 = kArray[10]; + + static constexpr const GpioDescriptor& kUart0Rx = kArray[11]; + static constexpr const GpioDescriptor& kUart0Tx = kArray[12]; + static constexpr const GpioDescriptor& kUart1Rx = kArray[13]; + static constexpr const GpioDescriptor& kUart1Tx = kArray[14]; + static constexpr const GpioDescriptor& kUart2Rx = kArray[15]; + static constexpr const GpioDescriptor& kUart2Tx = kArray[16]; +}; + +} // namespace internal + +inline constexpr internal::GpioDescriptors kGpioDescriptors{}; + +} // namespace librmcs::spec::rmcs_board_pro diff --git a/core/src/protocol/deserializer.cpp b/core/src/protocol/deserializer.cpp index 657377c..c3baf9b 100644 --- a/core/src/protocol/deserializer.cpp +++ b/core/src/protocol/deserializer.cpp @@ -153,7 +153,7 @@ coroutine::LifoTask Deserializer::process_uart_field(FieldId field_id) { coroutine::LifoTask Deserializer::process_gpio_field(FieldId) { GpioHeader::PayloadEnum payload_type; - std::uint8_t channel = 0; + std::uint8_t channel_index = 0; data::GpioPull pull = data::GpioPull::kNone; { const auto* header_bytes = co_await peek_bytes(sizeof(GpioHeader)); @@ -162,7 +162,7 @@ coroutine::LifoTask Deserializer::process_gpio_field(FieldId) { auto header = GpioHeader::CRef{header_bytes}; payload_type = header.get(); - channel = header.get(); + channel_index = header.get(); pull = header.get(); consume_peeked(); } @@ -171,9 +171,8 @@ coroutine::LifoTask Deserializer::process_gpio_field(FieldId) { case GpioHeader::PayloadEnum::kDigitalWriteLow: case GpioHeader::PayloadEnum::kDigitalWriteHigh: { data::GpioDigitalDataView data_view{}; - data_view.channel = channel; data_view.high = payload_type == GpioHeader::PayloadEnum::kDigitalWriteHigh; - callback_.gpio_digital_data_deserialized_callback(data_view); + callback_.gpio_digital_data_deserialized_callback(channel_index, data_view); break; } case GpioHeader::PayloadEnum::kAnalogWrite: { @@ -183,11 +182,10 @@ coroutine::LifoTask Deserializer::process_gpio_field(FieldId) { auto payload = GpioAnalogPayload::CRef{payload_bytes}; data::GpioAnalogDataView data_view{}; - data_view.channel = channel; data_view.value = payload.get(); consume_peeked(); - callback_.gpio_analog_data_deserialized_callback(data_view); + callback_.gpio_analog_data_deserialized_callback(channel_index, data_view); break; } case GpioHeader::PayloadEnum::kDigitalRead: @@ -198,7 +196,6 @@ coroutine::LifoTask Deserializer::process_gpio_field(FieldId) { auto payload = GpioReadConfigPayload::CRef{payload_bytes}; data::GpioReadConfigView data_view{}; - data_view.channel = channel; data_view.asap = payload.get(); data_view.rising_edge = payload.get(); data_view.falling_edge = payload.get(); @@ -211,18 +208,17 @@ coroutine::LifoTask Deserializer::process_gpio_field(FieldId) { co_return false; if (payload_type == GpioHeader::PayloadEnum::kDigitalRead) { - callback_.gpio_digital_read_config_deserialized_callback(data_view); + callback_.gpio_digital_read_config_deserialized_callback(channel_index, data_view); } else { - callback_.gpio_analog_read_config_deserialized_callback(data_view); + callback_.gpio_analog_read_config_deserialized_callback(channel_index, data_view); } break; } case GpioHeader::PayloadEnum::kDigitalReadResultLow: case GpioHeader::PayloadEnum::kDigitalReadResultHigh: { data::GpioDigitalDataView data_view{}; - data_view.channel = channel; data_view.high = payload_type == GpioHeader::PayloadEnum::kDigitalReadResultHigh; - callback_.gpio_digital_data_deserialized_callback(data_view); + callback_.gpio_digital_data_deserialized_callback(channel_index, data_view); break; } case GpioHeader::PayloadEnum::kAnalogReadResult: { @@ -232,11 +228,10 @@ coroutine::LifoTask Deserializer::process_gpio_field(FieldId) { auto payload = GpioAnalogPayload::CRef{payload_bytes}; data::GpioAnalogDataView data_view{}; - data_view.channel = channel; data_view.value = payload.get(); consume_peeked(); - callback_.gpio_analog_data_deserialized_callback(data_view); + callback_.gpio_analog_data_deserialized_callback(channel_index, data_view); break; } default: co_return false; diff --git a/core/src/protocol/deserializer.hpp b/core/src/protocol/deserializer.hpp index a8a74f0..7ac65d7 100644 --- a/core/src/protocol/deserializer.hpp +++ b/core/src/protocol/deserializer.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -27,15 +28,17 @@ class DeserializeCallback { virtual void uart_deserialized_callback(FieldId id, const data::UartDataView& data) = 0; - virtual void gpio_digital_data_deserialized_callback(const data::GpioDigitalDataView& data) = 0; + virtual void gpio_digital_data_deserialized_callback( + uint8_t channel_index, const data::GpioDigitalDataView& data) = 0; - virtual void gpio_analog_data_deserialized_callback(const data::GpioAnalogDataView& data) = 0; + virtual void gpio_analog_data_deserialized_callback( + uint8_t channel_index, const data::GpioAnalogDataView& data) = 0; - virtual void - gpio_digital_read_config_deserialized_callback(const data::GpioReadConfigView& data) = 0; + virtual void gpio_digital_read_config_deserialized_callback( + uint8_t channel_index, const data::GpioReadConfigView& data) = 0; - virtual void - gpio_analog_read_config_deserialized_callback(const data::GpioReadConfigView& data) = 0; + virtual void gpio_analog_read_config_deserialized_callback( + uint8_t channel_index, const data::GpioReadConfigView& data) = 0; virtual void accelerometer_deserialized_callback(const data::AccelerometerDataView& data) = 0; diff --git a/core/src/protocol/protocol.hpp b/core/src/protocol/protocol.hpp index ba64224..12cfd92 100644 --- a/core/src/protocol/protocol.hpp +++ b/core/src/protocol/protocol.hpp @@ -95,7 +95,7 @@ struct GpioHeader : utility::Bitfield<2> { }; using PayloadType = utility::BitfieldMember<4, 4, PayloadEnum>; - using Channel = utility::BitfieldMember<8, 6>; + using ChannelIndex = utility::BitfieldMember<8, 6>; using Pull = utility::BitfieldMember<14, 2, data::GpioPull>; }; diff --git a/core/src/protocol/serializer.hpp b/core/src/protocol/serializer.hpp index 911349a..16821ef 100644 --- a/core/src/protocol/serializer.hpp +++ b/core/src/protocol/serializer.hpp @@ -120,7 +120,9 @@ class Serializer { return SerializeResult::kSuccess; } - SerializeResult write_gpio_digital_data(const data::GpioDigitalDataView& view) noexcept { + SerializeResult write_gpio_digital_data( + uint8_t channel_index, const data::GpioDigitalDataView& view) noexcept { + utility::assert_debug(channel_index < (1U << GpioHeader::ChannelIndex::kBitWidth)); const auto payload_type = view.high ? GpioHeader::PayloadEnum::kDigitalWriteHigh : GpioHeader::PayloadEnum::kDigitalWriteLow; const std::size_t required = required_gpio_size(FieldId::kGpio, payload_type); @@ -136,14 +138,16 @@ class Serializer { auto header = GpioHeader::Ref(cursor); cursor += sizeof(GpioHeader); header.set(payload_type); - header.set(view.channel); + header.set(channel_index); header.set(data::GpioPull::kNone); utility::assert_debug(cursor == dst.data() + dst.size()); return SerializeResult::kSuccess; } - SerializeResult write_gpio_digital_read_config(const data::GpioReadConfigView& view) noexcept { + SerializeResult write_gpio_digital_read_config( + uint8_t channel_index, const data::GpioReadConfigView& view) noexcept { + utility::assert_debug(channel_index < (1U << GpioHeader::ChannelIndex::kBitWidth)); LIBRMCS_VERIFY_LIKELY( view.period_ms <= ((1U << 13) - 1U), SerializeResult::kInvalidArgument); @@ -161,7 +165,7 @@ class Serializer { auto header = GpioHeader::Ref(cursor); cursor += sizeof(GpioHeader); header.set(GpioHeader::PayloadEnum::kDigitalRead); - header.set(view.channel); + header.set(channel_index); header.set(view.pull); auto payload = GpioReadConfigPayload::Ref(cursor); @@ -175,7 +179,9 @@ class Serializer { return SerializeResult::kSuccess; } - SerializeResult write_gpio_analog_data(const data::GpioAnalogDataView& view) noexcept { + SerializeResult write_gpio_analog_data( + uint8_t channel_index, const data::GpioAnalogDataView& view) noexcept { + utility::assert_debug(channel_index < (1U << GpioHeader::ChannelIndex::kBitWidth)); const std::size_t required = required_gpio_size(FieldId::kGpio, GpioHeader::PayloadEnum::kAnalogWrite); LIBRMCS_VERIFY_LIKELY(required, SerializeResult::kInvalidArgument); @@ -190,7 +196,7 @@ class Serializer { auto header = GpioHeader::Ref(cursor); cursor += sizeof(GpioHeader); header.set(GpioHeader::PayloadEnum::kAnalogWrite); - header.set(view.channel); + header.set(channel_index); header.set(data::GpioPull::kNone); auto payload = GpioAnalogPayload::Ref(cursor); @@ -201,7 +207,9 @@ class Serializer { return SerializeResult::kSuccess; } - SerializeResult write_gpio_analog_read_config(const data::GpioReadConfigView& view) noexcept { + SerializeResult write_gpio_analog_read_config( + uint8_t channel_index, const data::GpioReadConfigView& view) noexcept { + utility::assert_debug(channel_index < (1U << GpioHeader::ChannelIndex::kBitWidth)); LIBRMCS_VERIFY_LIKELY( view.period_ms <= ((1U << 13) - 1U), SerializeResult::kInvalidArgument); LIBRMCS_VERIFY_LIKELY( @@ -221,7 +229,7 @@ class Serializer { auto header = GpioHeader::Ref(cursor); cursor += sizeof(GpioHeader); header.set(GpioHeader::PayloadEnum::kAnalogRead); - header.set(view.channel); + header.set(channel_index); header.set(view.pull); auto payload = GpioReadConfigPayload::Ref(cursor); @@ -235,7 +243,9 @@ class Serializer { return SerializeResult::kSuccess; } - SerializeResult write_gpio_digital_read_result(const data::GpioDigitalDataView& view) noexcept { + SerializeResult write_gpio_digital_read_result( + uint8_t channel_index, const data::GpioDigitalDataView& view) noexcept { + utility::assert_debug(channel_index < (1U << GpioHeader::ChannelIndex::kBitWidth)); const auto payload_type = view.high ? GpioHeader::PayloadEnum::kDigitalReadResultHigh : GpioHeader::PayloadEnum::kDigitalReadResultLow; const std::size_t required = required_gpio_size(FieldId::kGpio, payload_type); @@ -251,14 +261,16 @@ class Serializer { auto header = GpioHeader::Ref(cursor); cursor += sizeof(GpioHeader); header.set(payload_type); - header.set(view.channel); + header.set(channel_index); header.set(data::GpioPull::kNone); utility::assert_debug(cursor == dst.data() + dst.size()); return SerializeResult::kSuccess; } - SerializeResult write_gpio_analog_read_result(const data::GpioAnalogDataView& view) noexcept { + SerializeResult write_gpio_analog_read_result( + uint8_t channel_index, const data::GpioAnalogDataView& view) noexcept { + utility::assert_debug(channel_index < (1U << GpioHeader::ChannelIndex::kBitWidth)); const std::size_t required = required_gpio_size(FieldId::kGpio, GpioHeader::PayloadEnum::kAnalogReadResult); LIBRMCS_VERIFY_LIKELY(required, SerializeResult::kInvalidArgument); @@ -273,7 +285,7 @@ class Serializer { auto header = GpioHeader::Ref(cursor); cursor += sizeof(GpioHeader); header.set(GpioHeader::PayloadEnum::kAnalogReadResult); - header.set(view.channel); + header.set(channel_index); header.set(data::GpioPull::kNone); auto payload = GpioAnalogPayload::Ref(cursor); diff --git a/firmware/c_board/CMakeLists.txt b/firmware/c_board/CMakeLists.txt index 98e6c19..2f33267 100644 --- a/firmware/c_board/CMakeLists.txt +++ b/firmware/c_board/CMakeLists.txt @@ -35,6 +35,7 @@ add_subdirectory("${LIBRMCS_STM32_CUBEMX_ROOT}/cmake/stm32cubemx" SYSTEM) function(c_board_apply_target_options target linker_file) target_include_directories(${target} PRIVATE + ${LIBRMCS_PROJECT_ROOT}/core/include ${LIBRMCS_PROJECT_ROOT} ) diff --git a/firmware/c_board/app/src/gpio/gpio.hpp b/firmware/c_board/app/src/gpio/gpio.hpp index 5cd235e..da4f425 100644 --- a/firmware/c_board/app/src/gpio/gpio.hpp +++ b/firmware/c_board/app/src/gpio/gpio.hpp @@ -1,13 +1,16 @@ #pragma once #include +#include #include +#include #include #include #include #include "core/include/librmcs/data/datas.hpp" +#include "core/include/librmcs/spec/c_board/gpio.hpp" #include "core/src/protocol/serializer.hpp" #include "core/src/utility/assert.hpp" #include "core/src/utility/immovable.hpp" @@ -35,66 +38,55 @@ class Gpio : private core::utility::Immovable { HAL_NVIC_SetPriority(EXTI15_10_IRQn, 4, 0); HAL_NVIC_EnableIRQ(EXTI15_10_IRQn); - for (uint8_t i = 1; i <= 7; i++) { - set_pwm_compare(i, 0); - configure_digital_input_mode({ - .channel = i, - .period_ms = 0, - .asap = false, - .rising_edge = false, - .falling_edge = false, - }); + constexpr data::GpioReadConfigView k_default_input_config{}; + for (const auto& gpio : spec::c_board::kGpioDescriptors) { + set_pwm_compare(gpio.channel_index, 0); + configure_digital_input_mode(gpio.channel_index, k_default_input_config); } } - void handle_digital_write(const data::GpioDigitalDataView& data) { - if (!is_supported_channel(data.channel)) - return; - - configure_output_mode(data.channel); - set_pwm_compare(data.channel, data.high ? kPwmCounterPeriod : 0); + void handle_digital_write(uint8_t channel_index, const data::GpioDigitalDataView& data) { + configure_output_mode(channel_index); + set_pwm_compare(channel_index, data.high ? kPwmCounterPeriod : 0); } - void handle_analog_write(const data::GpioAnalogDataView& data) { - if (!is_supported_channel(data.channel)) - return; - - configure_output_mode(data.channel); - set_pwm_compare(data.channel, duty16_to_pwm_compare(data.value)); + void handle_analog_write(uint8_t channel_index, const data::GpioAnalogDataView& data) { + configure_output_mode(channel_index); + set_pwm_compare(channel_index, duty16_to_pwm_compare(data.value)); } - void handle_digital_read(const data::GpioReadConfigView& data) { - if (!is_supported_channel(data.channel)) - return; + void handle_digital_read(uint8_t channel_index, const data::GpioReadConfigView& data) { + configure_digital_input_mode(channel_index, data); - configure_digital_input_mode(data); + if (data.asap) + publish_digital_input_sample(channel_index); } void poll_periodic_input_samples() { const auto now = timer::timer->timepoint(); - for (uint8_t channel = 1; channel <= 7; ++channel) { - auto& state = channel_states_[channel - 1]; - if (state.mode != GpioMode::kDigitalInput || state.period == kNoPeriod) + for (std::size_t channel_index = 0; channel_index < kChannelCount; ++channel_index) { + auto& state = channel_states_[channel_index]; + if (state.mode != GpioMode::kDigitalInput || state.sample_period == kNoPeriod) continue; - if (!timer::timer->check_expired(state.next_sample_time, state.period)) + if (!timer::timer->check_reached(state.next_sample_deadline)) continue; - publish_digital_input_sample(channel); - state.next_sample_time = now; + publish_digital_input_sample(static_cast(channel_index)); + state.next_sample_deadline = now + state.sample_period; } } void handle_input_edge_interrupt(uint16_t gpio_pin) { - const uint8_t channel = channel_from_exti_line(exti_line_from_pin(gpio_pin)); - if (!channel) + const uint8_t channel_index = channel_index_from_exti_line(exti_line_from_pin(gpio_pin)); + if (channel_index == kInvalidChannelIndex) return; - auto& state = channel_states_[channel - 1]; + auto& state = channel_state(channel_index); if (state.mode != GpioMode::kDigitalInput || (!state.rising_edge && !state.falling_edge)) return; - publish_digital_input_sample(channel); + publish_digital_input_sample(channel_index); } private: @@ -105,8 +97,8 @@ class Gpio : private core::utility::Immovable { bool rising_edge = false; bool falling_edge = false; data::GpioPull pull = data::GpioPull::kNone; - timer::Timer::Duration period = timer::Timer::Duration::zero(); - timer::Timer::TimePoint next_sample_time; + timer::Timer::Duration sample_period = timer::Timer::Duration::zero(); + timer::Timer::TimePoint next_sample_deadline; }; struct ChannelHardware { @@ -118,60 +110,51 @@ class Gpio : private core::utility::Immovable { static constexpr uint32_t kPwmCounterPeriod = 60000; static constexpr auto kNoPeriod = timer::Timer::Duration::zero(); + static constexpr std::size_t kChannelCount = std::size(spec::c_board::kGpioDescriptors); + static constexpr uint8_t kInvalidChannelIndex = 0xFFU; - void configure_output_mode(uint8_t channel) { - auto& state = channel_states_[channel - 1]; + void configure_output_mode(uint8_t channel_index) { + auto& state = channel_state(channel_index); if (state.mode == GpioMode::kOutput) return; state.mode = GpioMode::kOutput; - configure_hal_gpio_output(channel); + configure_hal_gpio_output(channel_index); } - void configure_digital_input_mode(const data::GpioReadConfigView& data) { - const auto& channel = data.channel; - auto& state = channel_states_[channel - 1]; + void configure_digital_input_mode(uint8_t channel_index, const data::GpioReadConfigView& data) { + auto& state = channel_state(channel_index); - const auto& asap = data.asap; auto rising_edge = data.rising_edge; auto falling_edge = data.falling_edge; const auto pull = data.pull; - const auto period = + const auto sample_period = (data.period_ms == 0) ? kNoPeriod : timer::Timer::to_duration_checked(std::chrono::milliseconds{data.period_ms}); - if (channel == 6) { - // Channel 6 (PI6) shares EXTI line 6 with Channel 5 (PC6), - // so Channel 6 does not support EXTI. - rising_edge = falling_edge = false; - } - if (state.mode != GpioMode::kDigitalInput // || state.rising_edge != rising_edge || state.falling_edge != falling_edge - || state.pull != pull || state.period != period) { + || state.pull != pull || state.sample_period != sample_period) { state.mode = GpioMode::kDigitalInput; state.rising_edge = rising_edge; state.falling_edge = falling_edge; state.pull = pull; - state.period = period; - state.next_sample_time = timer::timer->timepoint(); + state.sample_period = sample_period; + state.next_sample_deadline = timer::timer->timepoint(); - configure_hal_gpio_input(channel, rising_edge, falling_edge, pull); + configure_hal_gpio_input(channel_index, rising_edge, falling_edge, pull); } - - if (asap) - publish_digital_input_sample(channel); } - void set_pwm_compare(uint8_t channel, uint32_t compare) { - const auto& hardware = channel_hardware(channel); + void set_pwm_compare(uint8_t channel_index, uint32_t compare) { + const auto& hardware = channel_hardware(channel_index); *hardware.compare_register = compare; } - void configure_hal_gpio_output(uint8_t channel) { - const auto& hardware = channel_hardware(channel); + void configure_hal_gpio_output(uint8_t channel_index) { + const auto& hardware = channel_hardware(channel_index); GPIO_InitTypeDef gpio_init = {}; gpio_init.Pin = hardware.gpio_pin; @@ -183,8 +166,8 @@ class Gpio : private core::utility::Immovable { } void configure_hal_gpio_input( - uint8_t channel, bool rising_edge, bool falling_edge, data::GpioPull pull) { - const auto& hardware = channel_hardware(channel); + uint8_t channel_index, bool rising_edge, bool falling_edge, data::GpioPull pull) { + const auto& hardware = channel_hardware(channel_index); GPIO_InitTypeDef gpio_init = {}; gpio_init.Pin = hardware.gpio_pin; @@ -202,13 +185,13 @@ class Gpio : private core::utility::Immovable { HAL_GPIO_Init(hardware.gpio_port, &gpio_init); } - void publish_digital_input_sample(uint8_t channel) { - const auto& hardware = channel_hardware(channel); + void publish_digital_input_sample(uint8_t channel_index) { + const auto& hardware = channel_hardware(channel_index); const bool high = HAL_GPIO_ReadPin(hardware.gpio_port, hardware.gpio_pin) == GPIO_PIN_SET; auto& serializer = usb::get_serializer(); core::utility::assert_debug( - serializer.write_gpio_digital_read_result({.channel = channel, .high = high}) + serializer.write_gpio_digital_read_result(channel_index, {.high = high}) != core::protocol::Serializer::SerializeResult::kInvalidArgument); } @@ -224,24 +207,29 @@ class Gpio : private core::utility::Immovable { return line; } - static uint8_t channel_from_exti_line(uint8_t exti_line) { + static uint8_t channel_index_from_exti_line(uint8_t exti_line) { switch (exti_line) { - case 9: return 1; - case 11: return 2; - case 13: return 3; - case 14: return 4; - case 6: return 5; - case 7: return 7; - default: return 0; + case 9: return spec::c_board::kGpioDescriptors.kPwm1.channel_index; + case 11: return spec::c_board::kGpioDescriptors.kPwm2.channel_index; + case 13: return spec::c_board::kGpioDescriptors.kPwm3.channel_index; + case 14: return spec::c_board::kGpioDescriptors.kPwm4.channel_index; + case 6: return spec::c_board::kGpioDescriptors.kPwm5.channel_index; + case 7: return spec::c_board::kGpioDescriptors.kPwm7.channel_index; + default: return kInvalidChannelIndex; } } - const ChannelHardware& channel_hardware(uint8_t channel) const { - core::utility::assert_debug_lazy([&]() noexcept { return is_supported_channel(channel); }); - return channel_hardware_[channel - 1]; + ChannelState& channel_state(uint8_t channel_index) { + const auto index = static_cast(channel_index); + core::utility::assert_debug(index < kChannelCount); + return channel_states_[index]; } - static bool is_supported_channel(uint8_t channel) { return channel >= 1 && channel <= 7; } + const ChannelHardware& channel_hardware(uint8_t channel_index) const { + const auto index = static_cast(channel_index); + core::utility::assert_debug(index < kChannelCount); + return channel_hardware_[index]; + } static uint32_t duty16_to_pwm_compare(uint16_t duty) { return ((static_cast(duty) * kPwmCounterPeriod) + 32767U) / 65535U; @@ -256,7 +244,7 @@ class Gpio : private core::utility::Immovable { } } - const ChannelHardware channel_hardware_[7]{ + const ChannelHardware channel_hardware_[kChannelCount]{ { .gpio_port = CHANNEL1_GPIO_Port, .gpio_pin = CHANNEL1_Pin, @@ -301,7 +289,7 @@ class Gpio : private core::utility::Immovable { }, }; - ChannelState channel_states_[7]; + ChannelState channel_states_[kChannelCount]; }; inline constinit Gpio::Lazy gpio; diff --git a/firmware/c_board/app/src/timer/timer.hpp b/firmware/c_board/app/src/timer/timer.hpp index cb43447..891bd28 100644 --- a/firmware/c_board/app/src/timer/timer.hpp +++ b/firmware/c_board/app/src/timer/timer.hpp @@ -30,7 +30,7 @@ class Timer { using TimePoint48 = std::chrono::time_point; // Keep the true-window at least half-cycle for stateless expiration checks. - static constexpr uint64_t kMaxDurationTicks = uint64_t{1} << 31; + static constexpr uint32_t kMaxDurationTicks = uint32_t{1} << 31; static constexpr uint64_t kMaxDuration48Ticks = uint64_t{1} << 47; static constexpr uint64_t kCounter48Mask = 0xFFFFFFFFFFFFULL; @@ -67,6 +67,13 @@ class Timer { return elapsed_duration >= delay; } + [[nodiscard]] bool check_reached(TimePoint deadline) const { + const uint32_t deadline_ticks = deadline.time_since_epoch().count(); + const uint32_t now_ticks = timepoint().time_since_epoch().count(); + const uint32_t elapsed_ticks = now_ticks - deadline_ticks; + return elapsed_ticks < kMaxDurationTicks; + } + [[nodiscard]] bool check_expired(TimePoint48 start_point, Duration48 delay) const { core::utility::assert_debug(delay.count() <= kMaxDuration48Ticks); @@ -93,7 +100,7 @@ class Timer { using InputDuration = std::chrono::duration; const InputDuration duration_u64{count}; - constexpr Duration max_duration{static_cast(kMaxDurationTicks)}; + constexpr Duration max_duration{kMaxDurationTicks}; const InputDuration max_input_duration = std::chrono::duration_cast(max_duration); diff --git a/firmware/c_board/app/src/usb/vendor.hpp b/firmware/c_board/app/src/usb/vendor.hpp index 7432364..fb4c0cc 100644 --- a/firmware/c_board/app/src/usb/vendor.hpp +++ b/firmware/c_board/app/src/usb/vendor.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -10,6 +11,8 @@ #include #include "core/include/librmcs/data/datas.hpp" +#include "core/include/librmcs/spec/c_board/gpio.hpp" +#include "core/include/librmcs/spec/gpio.hpp" #include "core/src/protocol/deserializer.hpp" #include "core/src/protocol/protocol.hpp" #include "core/src/protocol/serializer.hpp" @@ -31,6 +34,7 @@ class Vendor using Lazy = utility::Lazy; static constexpr size_t kMaxPacketSize = 64; + static constexpr std::size_t kGpioChannelCount = std::size(spec::c_board::kGpioDescriptors); Vendor() { usb::usb_descriptors.init(); @@ -101,21 +105,45 @@ class Vendor } } - void gpio_digital_data_deserialized_callback(const data::GpioDigitalDataView& data) override { - gpio::gpio->handle_digital_write(data); + void gpio_digital_data_deserialized_callback( + uint8_t channel_index, const data::GpioDigitalDataView& data) override { + if (channel_index >= kGpioChannelCount) + return; + + const auto& gpio = spec::c_board::kGpioDescriptors[channel_index]; + if (!gpio.supports(spec::GpioCapability::kDigitalWrite)) + return; + + gpio::gpio->handle_digital_write(channel_index, data); } - void gpio_analog_data_deserialized_callback(const data::GpioAnalogDataView& data) override { - gpio::gpio->handle_analog_write(data); + void gpio_analog_data_deserialized_callback( + uint8_t channel_index, const data::GpioAnalogDataView& data) override { + if (channel_index >= kGpioChannelCount) + return; + + const auto& gpio = spec::c_board::kGpioDescriptors[channel_index]; + if (!gpio.supports(spec::GpioCapability::kAnalogWrite)) + return; + + gpio::gpio->handle_analog_write(channel_index, data); } void gpio_digital_read_config_deserialized_callback( - const data::GpioReadConfigView& data) override { - gpio::gpio->handle_digital_read(data); + uint8_t channel_index, const data::GpioReadConfigView& data) override { + if (channel_index >= kGpioChannelCount) + return; + + const auto& gpio = spec::c_board::kGpioDescriptors[channel_index]; + if (!data.supported(gpio)) + return; + + gpio::gpio->handle_digital_read(channel_index, data); } void gpio_analog_read_config_deserialized_callback( - const data::GpioReadConfigView& data) override { + uint8_t channel_index, const data::GpioReadConfigView& data) override { + (void)channel_index; (void)data; } diff --git a/firmware/rmcs_board/app/CMakeLists.txt b/firmware/rmcs_board/app/CMakeLists.txt index dd047a5..2f9d6bf 100644 --- a/firmware/rmcs_board/app/CMakeLists.txt +++ b/firmware/rmcs_board/app/CMakeLists.txt @@ -61,6 +61,7 @@ sdk_compile_definitions(-DCFG_TUSB_MCU=OPT_MCU_HPM) sdk_compile_definitions(-DUSB_HOST_MCU_CORE=HPM_CORE0) sdk_inc("${CMAKE_CURRENT_SOURCE_DIR}/include") +sdk_app_inc("${LIBRMCS_PROJECT_ROOT}/core/include") sdk_app_inc("${LIBRMCS_PROJECT_ROOT}") file(GLOB_RECURSE PROJECT_SOURCE CONFIGURE_DEPENDS diff --git a/firmware/rmcs_board/app/src/app.cpp b/firmware/rmcs_board/app/src/app.cpp index 0214b9d..469bb04 100644 --- a/firmware/rmcs_board/app/src/app.cpp +++ b/firmware/rmcs_board/app/src/app.cpp @@ -5,6 +5,7 @@ #include #include "firmware/rmcs_board/app/src/can/can.hpp" +#include "firmware/rmcs_board/app/src/gpio/gpio.hpp" #include "firmware/rmcs_board/app/src/spi/bmi088/accel.hpp" #include "firmware/rmcs_board/app/src/spi/bmi088/gyro.hpp" #include "firmware/rmcs_board/app/src/uart/uart.hpp" @@ -33,6 +34,8 @@ App::App() { for (auto& board_uart : uart::uart_array) board_uart.init(); + gpio::gpio.init(); + spi::bmi088::accelerometer.init(); spi::bmi088::gyroscope.init(); } @@ -46,6 +49,7 @@ App::App() { for (auto& board_uart : uart::uart_array) board_uart->try_transmit(); + gpio::gpio->poll_periodic_input_samples(); } } diff --git a/firmware/rmcs_board/app/src/gpio/analog_gpio_pin.hpp b/firmware/rmcs_board/app/src/gpio/analog_gpio_pin.hpp new file mode 100644 index 0000000..08f0935 --- /dev/null +++ b/firmware/rmcs_board/app/src/gpio/analog_gpio_pin.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include + +#include // IWYU pragma: keep +#include +#include +#include +#include + +#include "core/src/utility/assert.hpp" +#include "firmware/rmcs_board/app/src/gpio/gpio_pin.hpp" + +namespace librmcs::firmware { + +class AnalogGpioPin : public GpioPin { +public: + constexpr AnalogGpioPin(const GpioPin& pin) // NOLINT(google-explicit-constructor) + : GpioPin(pin) + , pwm_base_(0) + , pwm_ioc_function_(0) + , pwm_output_index_(0) {} + + constexpr AnalogGpioPin( + const GpioPin& pin, uintptr_t pwm_base, uint32_t pwm_ioc_function, uint8_t pwm_output_index) + : GpioPin(pin) + , pwm_base_(pwm_base) + , pwm_ioc_function_(static_cast(pwm_ioc_function)) + , pwm_output_index_(pwm_output_index) { + core::utility::assert_debug(pwm_ioc_function < 32 && pwm_output_index < 8); + } + + [[nodiscard]] constexpr bool supports_pwm() const noexcept { return pwm_base_ != 0; } + + void configure_as_gpio() const { + configure_controller(); + configure_ioc_function(); + } + + void configure_as_pwm() const { + core::utility::assert_debug(supports_pwm()); + configure_controller(); + configure_ioc_function(pwm_ioc_function_); + } + + void disable_interrupt() const { + gpio_disable_pin_interrupt(gpio_instance(), port(), pin()); + clear_interrupt_flag(); + } + + void update_pwm_compare_edge_aligned(uint32_t compare_value) const { + core::utility::assert_debug(supports_pwm()); + pwm_update_raw_cmp_edge_aligned(pwm_instance(), pwm_output_index_, compare_value); + } + + hpm_stat_t setup_pwm_waveform_edge_aligned(uint32_t reload, pwm_config_t& pwm_config) const { + core::utility::assert_debug(supports_pwm()); + + pwm_cmp_config_t cmp_config{}; + pwm_get_default_cmp_config(pwm_instance(), &cmp_config); + cmp_config.cmp = reload + 1; + + return pwm_setup_waveform( + pwm_instance(), pwm_output_index_, &pwm_config, pwm_output_index_, &cmp_config, 1); + } + +private: + PWM_Type* pwm_instance() const { return reinterpret_cast(pwm_base_); } + + uintptr_t pwm_base_; + + uint8_t pwm_ioc_function_; + uint8_t pwm_output_index_; + + static_assert(IOC_PAD_FUNC_CTL_ALT_SELECT_SET(31) == 31); +}; + +} // namespace librmcs::firmware diff --git a/firmware/rmcs_board/app/src/gpio/gpio.cpp b/firmware/rmcs_board/app/src/gpio/gpio.cpp new file mode 100644 index 0000000..9c98528 --- /dev/null +++ b/firmware/rmcs_board/app/src/gpio/gpio.cpp @@ -0,0 +1,11 @@ +#include "firmware/rmcs_board/app/src/gpio/gpio.hpp" + +#include + +#include "board_app.hpp" + +namespace librmcs::firmware::board { + +void gpio_irq_handler(uint32_t port_index) { gpio::gpio->handle_port_interrupt(port_index); } + +} // namespace librmcs::firmware::board diff --git a/firmware/rmcs_board/app/src/gpio/gpio.hpp b/firmware/rmcs_board/app/src/gpio/gpio.hpp new file mode 100644 index 0000000..ea7cb0a --- /dev/null +++ b/firmware/rmcs_board/app/src/gpio/gpio.hpp @@ -0,0 +1,305 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "board_app.hpp" +#include "core/include/librmcs/data/datas.hpp" +#include "core/src/protocol/serializer.hpp" +#include "core/src/utility/assert.hpp" +#include "core/src/utility/immovable.hpp" +#include "firmware/rmcs_board/app/src/gpio/analog_gpio_pin.hpp" +#include "firmware/rmcs_board/app/src/usb/helper.hpp" +#include "firmware/rmcs_board/app/src/utility/lazy.hpp" + +namespace librmcs::firmware::gpio { + +class Gpio : private core::utility::Immovable { +public: + using Lazy = utility::Lazy; + + Gpio() + : mchtmr_frequency_hz_(clock_get_frequency(clock_mchtmr0)) { + board::init_gpio_pins(); + } + + void handle_digital_write(uint8_t channel_index, const data::GpioDigitalDataView& data) { + configure_digital_output_mode(channel_index, data.high); + } + + void handle_analog_write(uint8_t channel_index, const data::GpioAnalogDataView& data) { + configure_pwm_output_mode(channel_index); + channel_hardware(channel_index) + .update_pwm_compare_edge_aligned(duty16_to_pwm_compare(data.value)); + } + + void handle_digital_read(uint8_t channel_index, const data::GpioReadConfigView& data) { + configure_digital_input_mode(channel_index, data); + + if (data.asap) + publish_digital_input_sample(channel_index); + } + + void poll_periodic_input_samples() { + const uint64_t now = now_ticks(); + + for (std::size_t channel_index = 0; channel_index < board::spec::kGpioDescriptors.size(); + ++channel_index) { + auto& state = channel_states_[channel_index]; + if (state.mode != ChannelMode::kDigitalInput || state.period_ticks == 0) + continue; + if (now < state.next_sample_tick) + continue; + + publish_digital_input_sample(static_cast(channel_index)); + state.next_sample_tick = now + state.period_ticks; + } + } + + void handle_port_interrupt(uint32_t port_index) { + for (std::size_t channel_index = 0; channel_index < board::spec::kGpioDescriptors.size(); + ++channel_index) { + const auto& hardware = channel_hardware(static_cast(channel_index)); + if (hardware.port() != port_index) + continue; + if (!hardware.check_clear_interrupt_flag()) + continue; + + auto& state = channel_states_[channel_index]; + if (state.mode != ChannelMode::kDigitalInput + || (!state.rising_edge && !state.falling_edge)) + continue; + + publish_digital_input_sample(static_cast(channel_index)); + } + } + +private: + enum class ChannelMode : uint8_t { + kUnconfigured = 0, + kDigitalOutput = 1, + kDigitalInput = 2, + kPwmOutput = 3, + }; + + struct ChannelState { + ChannelMode mode = ChannelMode::kUnconfigured; + bool rising_edge = false; + bool falling_edge = false; + data::GpioPull pull = data::GpioPull::kNone; + uint64_t period_ticks = 0; + uint64_t next_sample_tick = 0; + }; + static constexpr uint32_t kPwmFrequencyHz = 50; + + void configure_digital_output_mode(uint8_t channel_index, bool initial_high) { + auto& state = channel_state(channel_index); + const auto& hardware = channel_hardware(channel_index); + + if (state.mode != ChannelMode::kDigitalOutput) { + prepare_pin_for_digital_output(hardware, initial_high); + set_output_mode_state(state, ChannelMode::kDigitalOutput); + return; + } + + hardware.write_pin(initial_high); + } + + ChannelState& channel_state(uint8_t channel_index) { + core::utility::assert_debug(channel_index < board::spec::kGpioDescriptors.size()); + return channel_states_[channel_index]; + } + + static const AnalogGpioPin& channel_hardware(uint8_t channel_index) { + core::utility::assert_debug(channel_index < board::spec::kGpioDescriptors.size()); + return board::kGpioHardwareDescriptors[channel_index]; + } + + static void prepare_pin_for_digital_output(const AnalogGpioPin& hardware, bool initial_high) { + hardware.disable_interrupt(); + hardware.configure_as_gpio(); + hardware.configure_pad_control(0); + hardware.write_pin(initial_high); + hardware.configure_as_output(); + } + + void prepare_pin_for_pwm_output(const AnalogGpioPin& hardware) { + hardware.disable_interrupt(); + ensure_pwm_initialized(); + hardware.configure_pad_control(0); + hardware.configure_as_pwm(); + } + + static void set_output_mode_state(ChannelState& state, ChannelMode mode) { + state.mode = mode; + state.rising_edge = false; + state.falling_edge = false; + state.pull = data::GpioPull::kNone; + state.period_ticks = 0; + state.next_sample_tick = 0; + } + + void configure_pwm_output_mode(uint8_t channel_index) { + auto& state = channel_state(channel_index); + const auto& hardware = channel_hardware(channel_index); + + core::utility::assert_debug(hardware.supports_pwm()); + if (state.mode == ChannelMode::kPwmOutput) + return; + + prepare_pin_for_pwm_output(hardware); + set_output_mode_state(state, ChannelMode::kPwmOutput); + } + + static void set_digital_input_mode_state( + ChannelState& state, const data::GpioReadConfigView& data, uint64_t period_ticks) { + state.mode = ChannelMode::kDigitalInput; + state.rising_edge = data.rising_edge; + state.falling_edge = data.falling_edge; + state.pull = data.pull; + state.period_ticks = period_ticks; + state.next_sample_tick = now_ticks(); + } + + void ensure_pwm_initialized() { + if (pwm_initialized_) + return; + + clock_add_to_group(clock_mot0, 0); + const uint32_t pwm_clock_hz = clock_get_frequency(clock_mot0); + core::utility::assert_always(pwm_clock_hz >= kPwmFrequencyHz); + + pwm_reload_ = pwm_clock_hz / kPwmFrequencyHz; + core::utility::assert_always(pwm_reload_ > 1); + + pwm_deinit(HPM_PWM0); + pwm_set_reload(HPM_PWM0, 0, pwm_reload_); + pwm_set_start_count(HPM_PWM0, 0, 0); + + pwm_config_t pwm_config{}; + pwm_get_default_pwm_config(HPM_PWM0, &pwm_config); + pwm_config.enable_output = true; + pwm_config.invert_output = false; + + for (const auto& hardware : board::kGpioHardwareDescriptors) { + if (!hardware.supports_pwm()) + continue; + + core::utility::assert_always( + hardware.setup_pwm_waveform_edge_aligned(pwm_reload_, pwm_config) + == status_success); + } + + pwm_start_counter(HPM_PWM0); + pwm_issue_shadow_register_lock_event(HPM_PWM0); + pwm_initialized_ = true; + } + + [[nodiscard]] uint32_t duty16_to_pwm_compare(uint16_t duty) const { + if (duty == 0) + return pwm_reload_ + 1; + return (static_cast(pwm_reload_) * (65535U - duty)) / 65535U; + } + + void configure_digital_input_mode(uint8_t channel_index, const data::GpioReadConfigView& data) { + auto& state = channel_state(channel_index); + const uint64_t period_ticks = period_ms_to_ticks(data.period_ms); + if (!needs_digital_input_reconfigure(state, data, period_ticks)) + return; + + const auto& hardware = channel_hardware(channel_index); + prepare_pin_for_digital_input(hardware, data.pull); + set_digital_input_mode_state(state, data, period_ticks); + + if (data.rising_edge || data.falling_edge) + configure_interrupt(hardware, data.rising_edge, data.falling_edge); + } + + [[nodiscard]] uint64_t period_ms_to_ticks(uint16_t period_ms) const { + if (period_ms == 0) + return 0; + return (static_cast(mchtmr_frequency_hz_) * period_ms) / 1000U; + } + + static bool needs_digital_input_reconfigure( + const ChannelState& state, const data::GpioReadConfigView& data, uint64_t period_ticks) { + return state.mode != ChannelMode::kDigitalInput || state.rising_edge != data.rising_edge + || state.falling_edge != data.falling_edge || state.pull != data.pull + || state.period_ticks != period_ticks; + } + + static uint32_t pad_control_from_pull(data::GpioPull pull) { + switch (pull) { + case data::GpioPull::kNone: return IOC_PAD_PAD_CTL_HYS_SET(1); + case data::GpioPull::kUp: + return IOC_PAD_PAD_CTL_PE_SET(1) | IOC_PAD_PAD_CTL_PS_SET(1) + | IOC_PAD_PAD_CTL_HYS_SET(1); + case data::GpioPull::kDown: + return IOC_PAD_PAD_CTL_PE_SET(1) | IOC_PAD_PAD_CTL_PS_SET(0) + | IOC_PAD_PAD_CTL_HYS_SET(1); + default: core::utility::assert_failed_debug(); return IOC_PAD_PAD_CTL_HYS_SET(1); + } + } + + static void prepare_pin_for_digital_input(const AnalogGpioPin& hardware, data::GpioPull pull) { + hardware.disable_interrupt(); + hardware.configure_as_gpio(); + hardware.configure_pad_control(pad_control_from_pull(pull)); + hardware.configure_as_input(); + } + + [[nodiscard]] static uint64_t now_ticks() { return mchtmr_get_count(HPM_MCHTMR); } + + static void + configure_interrupt(const AnalogGpioPin& hardware, bool rising_edge, bool falling_edge) { + gpio_interrupt_trigger_t trigger = gpio_interrupt_trigger_edge_rising; + if (rising_edge && falling_edge) { + trigger = gpio_interrupt_trigger_edge_both; + } else if (falling_edge) { + trigger = gpio_interrupt_trigger_edge_falling; + } + + hardware.configure_interrupt(trigger); + hardware.clear_interrupt_flag(); + hardware.enable_interrupt(); + enable_port_irq(hardware.port()); + } + + static void enable_port_irq(uint32_t port_index) { + switch (port_index) { + case GPIO_DI_GPIOA: intc_m_enable_irq_with_priority(IRQn_GPIO0_A, 1); break; + case GPIO_DI_GPIOB: intc_m_enable_irq_with_priority(IRQn_GPIO0_B, 1); break; + case GPIO_DI_GPIOY: intc_m_enable_irq_with_priority(IRQn_GPIO0_Y, 1); break; + default: core::utility::assert_failed_debug(); + } + } + + static void publish_digital_input_sample(uint8_t channel_index) { + const auto& hardware = channel_hardware(channel_index); + const bool high = hardware.read_pin(); + + auto& serializer = usb::get_serializer(); + core::utility::assert_debug( + serializer.write_gpio_digital_read_result(channel_index, {.high = high}) + != core::protocol::Serializer::SerializeResult::kInvalidArgument); + } + + const uint32_t mchtmr_frequency_hz_; + uint32_t pwm_reload_ = 0; + bool pwm_initialized_ = false; + ChannelState channel_states_[board::spec::kGpioDescriptors.size()]{}; +}; + +inline constinit Gpio::Lazy gpio; + +} // namespace librmcs::firmware::gpio diff --git a/firmware/rmcs_board/app/src/gpio/gpio_pin.hpp b/firmware/rmcs_board/app/src/gpio/gpio_pin.hpp index 2c82924..19ebcee 100644 --- a/firmware/rmcs_board/app/src/gpio/gpio_pin.hpp +++ b/firmware/rmcs_board/app/src/gpio/gpio_pin.hpp @@ -13,6 +13,8 @@ #include #include +#include "core/src/utility/assert.hpp" + namespace librmcs::firmware { class GpioPin { @@ -30,8 +32,7 @@ class GpioPin { , controller_(static_cast(controller)) , pad_(pad) , pin_(pin) { - if (pad >= (1 << 16) || port >= (1 << 4) || pin >= (1 << 5)) [[unlikely]] - __builtin_trap(); + core::utility::assert_debug(pad < (1 << 16) && port < (1 << 4) && pin < (1 << 5)); } constexpr gpiom_gpio_t controller() const { return static_cast(controller_); } diff --git a/firmware/rmcs_board/app/src/usb/vendor.hpp b/firmware/rmcs_board/app/src/usb/vendor.hpp index 9736423..41ec672 100644 --- a/firmware/rmcs_board/app/src/usb/vendor.hpp +++ b/firmware/rmcs_board/app/src/usb/vendor.hpp @@ -12,12 +12,14 @@ #include "board_app.hpp" #include "core/include/librmcs/data/datas.hpp" +#include "core/include/librmcs/spec/gpio.hpp" #include "core/src/protocol/deserializer.hpp" #include "core/src/protocol/protocol.hpp" #include "core/src/protocol/serializer.hpp" #include "core/src/utility/assert.hpp" #include "core/src/utility/immovable.hpp" #include "firmware/rmcs_board/app/src/can/can.hpp" +#include "firmware/rmcs_board/app/src/gpio/gpio.hpp" #include "firmware/rmcs_board/app/src/uart/uart.hpp" #include "firmware/rmcs_board/app/src/usb/interrupt_safe_buffer.hpp" #include "firmware/rmcs_board/app/src/usb/usb_descriptors.hpp" @@ -115,21 +117,45 @@ class Vendor } } - void gpio_digital_data_deserialized_callback(const data::GpioDigitalDataView& data) override { - (void)data; + void gpio_digital_data_deserialized_callback( + uint8_t channel_index, const data::GpioDigitalDataView& data) override { + if (channel_index >= board::spec::kGpioDescriptors.size()) + return; + + const auto& gpio_descriptor = board::spec::kGpioDescriptors[channel_index]; + if (!gpio_descriptor.supports(spec::GpioCapability::kDigitalWrite)) + return; + + gpio::gpio->handle_digital_write(channel_index, data); } - void gpio_analog_data_deserialized_callback(const data::GpioAnalogDataView& data) override { - (void)data; + void gpio_analog_data_deserialized_callback( + uint8_t channel_index, const data::GpioAnalogDataView& data) override { + if (channel_index >= board::spec::kGpioDescriptors.size()) + return; + + const auto& gpio_descriptor = board::spec::kGpioDescriptors[channel_index]; + if (!gpio_descriptor.supports(spec::GpioCapability::kAnalogWrite)) + return; + + gpio::gpio->handle_analog_write(channel_index, data); } void gpio_digital_read_config_deserialized_callback( - const data::GpioReadConfigView& data) override { - (void)data; + uint8_t channel_index, const data::GpioReadConfigView& data) override { + if (channel_index >= board::spec::kGpioDescriptors.size()) + return; + + const auto& gpio_descriptor = board::spec::kGpioDescriptors[channel_index]; + if (!data.supported(gpio_descriptor)) + return; + + gpio::gpio->handle_digital_read(channel_index, data); } void gpio_analog_read_config_deserialized_callback( - const data::GpioReadConfigView& data) override { + uint8_t channel_index, const data::GpioReadConfigView& data) override { + (void)channel_index; (void)data; } diff --git a/firmware/rmcs_board/boards/lite/app/board_app.cpp b/firmware/rmcs_board/boards/lite/app/board_app.cpp index 086b1c2..1b8098c 100644 --- a/firmware/rmcs_board/boards/lite/app/board_app.cpp +++ b/firmware/rmcs_board/boards/lite/app/board_app.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -136,6 +137,11 @@ uint32_t init_spi(SPI_Type* ptr) { return init_spi_clock(ptr); } +void init_gpio_pins() { + kGpioHardwareDescriptors[0].configure_pioc_function(); + kGpioHardwareDescriptors[1].configure_pioc_function(); +} + void init_user_button_and_switch_pins() { kUserHsFsSwitchPin.configure_controller(); kUserHsFsSwitchPin.configure_ioc_function(); @@ -155,8 +161,12 @@ void gpio_bmi088_int_isr() { if (kBmi088AccelIntPin.check_clear_interrupt_flag()) { bmi088_accel_dataready_irq_handler(); } + gpio_irq_handler(GPIO_DI_GPIOB); } +SDK_DECLARE_EXT_ISR_M(IRQn_GPIO0_Y, gpio_y_isr) +void gpio_y_isr() { gpio_irq_handler(GPIO_DI_GPIOY); } + SDK_DECLARE_EXT_ISR_M(BOARD_CAN0(IRQn_MCAN, ), can0_isr) void can0_isr() { can_irq_handler(0); } diff --git a/firmware/rmcs_board/boards/lite/app/board_app.hpp b/firmware/rmcs_board/boards/lite/app/board_app.hpp index 13b77d9..b4c5c56 100644 --- a/firmware/rmcs_board/boards/lite/app/board_app.hpp +++ b/firmware/rmcs_board/boards/lite/app/board_app.hpp @@ -2,19 +2,25 @@ #include #include +#include #include #include +#include #include #include #include #include #include +#include "core/include/librmcs/spec/rmcs_board_lite/gpio.hpp" // IWYU pragma: export +#include "firmware/rmcs_board/app/src/gpio/analog_gpio_pin.hpp" #include "firmware/rmcs_board/app/src/gpio/gpio_pin.hpp" namespace librmcs::firmware::board { +namespace spec = librmcs::spec::rmcs_board_lite; // NOLINT(misc-unused-alias-decls) + #define BOARD_CAN0(prefix, suffix) prefix##1##suffix #define BOARD_CAN1(prefix, suffix) prefix##0##suffix #define BOARD_CAN2(prefix, suffix) prefix##3##suffix @@ -48,4 +54,15 @@ constexpr GpioPin kUserHsFsSwitchPin = make_gpio_pin(), + make_gpio_pin(), + make_gpio_pin(), + make_gpio_pin(), +}; +static_assert(board::spec::kGpioDescriptors.size() == std::size(board::kGpioHardwareDescriptors)); + +void init_gpio_pins(); +void gpio_irq_handler(uint32_t port_index); + } // namespace librmcs::firmware::board diff --git a/firmware/rmcs_board/boards/pro/app/board_app.cpp b/firmware/rmcs_board/boards/pro/app/board_app.cpp index 4a552b4..101eee8 100644 --- a/firmware/rmcs_board/boards/pro/app/board_app.cpp +++ b/firmware/rmcs_board/boards/pro/app/board_app.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -201,11 +202,19 @@ void init_user_button_and_switch_pins() { kUserHsFsSwitchPin.configure_as_input(); } +void init_gpio_pins() { + // Intentionally empty: No PIOC pins need to be configured. +} + +SDK_DECLARE_EXT_ISR_M(IRQn_GPIO0_A, gpio_a_isr) +void gpio_a_isr() { gpio_irq_handler(GPIO_DI_GPIOA); } + SDK_DECLARE_EXT_ISR_M(IRQn_GPIO0_B, gpio_bmi088_int_gyro_isr) void gpio_bmi088_int_gyro_isr() { if (kBmi088GyroIntPin.check_clear_interrupt_flag()) { bmi088_gyro_dataready_irq_handler(); } + gpio_irq_handler(GPIO_DI_GPIOB); } SDK_DECLARE_EXT_ISR_M(IRQn_GPIO0_Y, gpio_bmi088_int_accel_isr) @@ -213,6 +222,7 @@ void gpio_bmi088_int_accel_isr() { if (kBmi088AccelIntPin.check_clear_interrupt_flag()) { bmi088_accel_dataready_irq_handler(); } + gpio_irq_handler(GPIO_DI_GPIOY); } SDK_DECLARE_EXT_ISR_M(BOARD_CAN0(IRQn_MCAN, ), can0_isr) diff --git a/firmware/rmcs_board/boards/pro/app/board_app.hpp b/firmware/rmcs_board/boards/pro/app/board_app.hpp index ed35041..bff430c 100644 --- a/firmware/rmcs_board/boards/pro/app/board_app.hpp +++ b/firmware/rmcs_board/boards/pro/app/board_app.hpp @@ -2,19 +2,25 @@ #include #include +#include #include #include +#include #include #include #include #include #include +#include "core/include/librmcs/spec/rmcs_board_pro/gpio.hpp" // IWYU pragma: export +#include "firmware/rmcs_board/app/src/gpio/analog_gpio_pin.hpp" #include "firmware/rmcs_board/app/src/gpio/gpio_pin.hpp" namespace librmcs::firmware::board { +namespace spec = librmcs::spec::rmcs_board_pro; + #define BOARD_CAN0(prefix, suffix) prefix##3##suffix #define BOARD_CAN1(prefix, suffix) prefix##2##suffix #define BOARD_CAN2(prefix, suffix) prefix##1##suffix @@ -51,4 +57,28 @@ constexpr GpioPin kUserHsFsSwitchPin = make_gpio_pin(), HPM_PWM0_BASE, IOC_PB00_FUNC_CTL_PWM0_P_0, 0}, + {make_gpio_pin(), HPM_PWM0_BASE, IOC_PB01_FUNC_CTL_PWM0_P_1, 1}, + {make_gpio_pin(), HPM_PWM0_BASE, IOC_PB02_FUNC_CTL_PWM0_P_2, 2}, + {make_gpio_pin(), HPM_PWM0_BASE, IOC_PB03_FUNC_CTL_PWM0_P_3, 3}, + {make_gpio_pin()}, + {make_gpio_pin()}, + {make_gpio_pin()}, + {make_gpio_pin()}, + {make_gpio_pin()}, + {make_gpio_pin()}, + {make_gpio_pin()}, + {make_gpio_pin()}, + {make_gpio_pin()}, + {make_gpio_pin()}, + {make_gpio_pin()}, + {make_gpio_pin()}, + {make_gpio_pin()}, +}; +static_assert(board::spec::kGpioDescriptors.size() == std::size(board::kGpioHardwareDescriptors)); + +void init_gpio_pins(); +void gpio_irq_handler(uint32_t port_index); + } // namespace librmcs::firmware::board diff --git a/firmware/rmcs_board/bootloader/CMakeLists.txt b/firmware/rmcs_board/bootloader/CMakeLists.txt index bf946dc..b9b8446 100644 --- a/firmware/rmcs_board/bootloader/CMakeLists.txt +++ b/firmware/rmcs_board/bootloader/CMakeLists.txt @@ -67,6 +67,7 @@ sdk_compile_definitions(-DUSB_HOST_MCU_CORE=HPM_CORE0) sdk_compile_definitions(-DLIBRMCS_BOOTLOADER_MODE_AUTO=${bootloader_mode_auto}) sdk_inc("${CMAKE_CURRENT_SOURCE_DIR}/include") +sdk_app_inc("${LIBRMCS_PROJECT_ROOT}/core/include") sdk_app_inc("${LIBRMCS_PROJECT_ROOT}") file(GLOB_RECURSE PROJECT_SOURCE CONFIGURE_DEPENDS diff --git a/host/include/librmcs/agent/c_board.hpp b/host/include/librmcs/agent/c_board.hpp index 58cceb1..a3b00cd 100644 --- a/host/include/librmcs/agent/c_board.hpp +++ b/host/include/librmcs/agent/c_board.hpp @@ -1,11 +1,14 @@ #pragma once +#include #include #include #include #include #include +#include +#include namespace librmcs::agent { @@ -46,23 +49,28 @@ class CBoard : private data::DataCallback { return *this; } - PacketBuilder& gpio_digital_write(const librmcs::data::GpioDigitalDataView& data) { - if (data.channel < 1 || data.channel > 7 || !builder_.write_gpio_digital_data(data)) - [[unlikely]] + PacketBuilder& gpio_digital_write( + const librmcs::spec::c_board::GpioDescriptor& gpio, + const librmcs::data::GpioDigitalDataView& data) { + if (!gpio.supports(spec::GpioCapability::kDigitalWrite) + || !builder_.write_gpio_digital_data(gpio.channel_index, data)) [[unlikely]] throw std::invalid_argument{"GPIO digital transmission failed: Invalid GPIO data"}; return *this; } - PacketBuilder& gpio_digital_read(const librmcs::data::GpioReadConfigView& data) { - if (data.channel < 1 || data.channel > 7 - || (data.channel == 6 && (data.rising_edge || data.falling_edge)) - || !builder_.write_gpio_digital_read_config(data)) [[unlikely]] + PacketBuilder& gpio_digital_read( + const librmcs::spec::c_board::GpioDescriptor& gpio, + const librmcs::data::GpioReadConfigView& data) { + if (!data.supported(gpio) + || !builder_.write_gpio_digital_read_config(gpio.channel_index, data)) [[unlikely]] throw std::invalid_argument{ "GPIO digital read configuration transmission failed: Invalid GPIO data"}; return *this; } - PacketBuilder& gpio_analog_write(const librmcs::data::GpioAnalogDataView& data) { - if (data.channel < 1 || data.channel > 7 || !builder_.write_gpio_analog_data(data)) - [[unlikely]] + PacketBuilder& gpio_analog_write( + const librmcs::spec::c_board::GpioDescriptor& gpio, + const librmcs::data::GpioAnalogDataView& data) { + if (!gpio.supports(spec::GpioCapability::kAnalogWrite) + || !builder_.write_gpio_analog_data(gpio.channel_index, data)) [[unlikely]] throw std::invalid_argument{"GPIO analog transmission failed: Invalid GPIO data"}; return *this; } @@ -100,14 +108,32 @@ class CBoard : private data::DataCallback { virtual void uart1_receive_callback(const librmcs::data::UartDataView& data) { (void)data; } virtual void uart2_receive_callback(const librmcs::data::UartDataView& data) { (void)data; } - void - gpio_digital_read_result_callback(const librmcs::data::GpioDigitalDataView& data) override { + virtual void gpio_digital_read_result_callback( + const librmcs::spec::c_board::GpioDescriptor& gpio, + const librmcs::data::GpioDigitalDataView& data) { + (void)gpio; (void)data; } - void gpio_analog_read_result_callback(const librmcs::data::GpioAnalogDataView& data) override { + virtual void gpio_analog_read_result_callback( + const librmcs::spec::c_board::GpioDescriptor& gpio, + const librmcs::data::GpioAnalogDataView& data) { + (void)gpio; (void)data; } + void gpio_digital_read_result_callback( + uint8_t channel_index, const librmcs::data::GpioDigitalDataView& data) override { + if (channel_index >= spec::c_board::kGpioDescriptors.size()) [[unlikely]] + return; + gpio_digital_read_result_callback(spec::c_board::kGpioDescriptors[channel_index], data); + } + void gpio_analog_read_result_callback( + uint8_t channel_index, const librmcs::data::GpioAnalogDataView& data) override { + if (channel_index >= spec::c_board::kGpioDescriptors.size()) [[unlikely]] + return; + gpio_analog_read_result_callback(spec::c_board::kGpioDescriptors[channel_index], data); + } + void accelerometer_receive_callback(const librmcs::data::AccelerometerDataView& data) override { (void)data; } diff --git a/host/include/librmcs/agent/rmcs_board_lite.hpp b/host/include/librmcs/agent/rmcs_board_lite.hpp index cc0f9f0..a6dcccb 100644 --- a/host/include/librmcs/agent/rmcs_board_lite.hpp +++ b/host/include/librmcs/agent/rmcs_board_lite.hpp @@ -1,11 +1,14 @@ #pragma once +#include #include #include #include #include #include +#include +#include namespace librmcs::agent { @@ -56,6 +59,32 @@ class RmcsBoardLite : private data::DataCallback { return *this; } + PacketBuilder& gpio_digital_write( + const librmcs::spec::rmcs_board_lite::GpioDescriptor& gpio, + const librmcs::data::GpioDigitalDataView& data) { + if (!gpio.supports(spec::GpioCapability::kDigitalWrite) + || !builder_.write_gpio_digital_data(gpio.channel_index, data)) [[unlikely]] + throw std::invalid_argument{"GPIO digital transmission failed: Invalid GPIO data"}; + return *this; + } + PacketBuilder& gpio_digital_read( + const librmcs::spec::rmcs_board_lite::GpioDescriptor& gpio, + const librmcs::data::GpioReadConfigView& data) { + if (!data.supported(gpio) + || !builder_.write_gpio_digital_read_config(gpio.channel_index, data)) [[unlikely]] + throw std::invalid_argument{ + "GPIO digital read configuration transmission failed: Invalid GPIO data"}; + return *this; + } + PacketBuilder& gpio_analog_write( + const librmcs::spec::rmcs_board_lite::GpioDescriptor& gpio, + const librmcs::data::GpioAnalogDataView& data) { + if (!gpio.supports(spec::GpioCapability::kAnalogWrite) + || !builder_.write_gpio_analog_data(gpio.channel_index, data)) [[unlikely]] + throw std::invalid_argument{"GPIO analog transmission failed: Invalid GPIO data"}; + return *this; + } + private: explicit PacketBuilder(host::protocol::Handler& handler) noexcept : builder_(handler.start_transmit()) {} @@ -93,14 +122,34 @@ class RmcsBoardLite : private data::DataCallback { virtual void uart0_receive_callback(const librmcs::data::UartDataView& data) { (void)data; } virtual void uart1_receive_callback(const librmcs::data::UartDataView& data) { (void)data; } - void - gpio_digital_read_result_callback(const librmcs::data::GpioDigitalDataView& data) override { + virtual void gpio_digital_read_result_callback( + const librmcs::spec::rmcs_board_lite::GpioDescriptor& gpio, + const librmcs::data::GpioDigitalDataView& data) { + (void)gpio; (void)data; } - void gpio_analog_read_result_callback(const librmcs::data::GpioAnalogDataView& data) override { + virtual void gpio_analog_read_result_callback( + const librmcs::spec::rmcs_board_lite::GpioDescriptor& gpio, + const librmcs::data::GpioAnalogDataView& data) { + (void)gpio; (void)data; } + void gpio_digital_read_result_callback( + uint8_t channel_index, const librmcs::data::GpioDigitalDataView& data) final { + if (channel_index >= spec::rmcs_board_lite::kGpioDescriptors.size()) [[unlikely]] + return; + gpio_digital_read_result_callback( + spec::rmcs_board_lite::kGpioDescriptors[channel_index], data); + } + void gpio_analog_read_result_callback( + uint8_t channel_index, const librmcs::data::GpioAnalogDataView& data) final { + if (channel_index >= spec::rmcs_board_lite::kGpioDescriptors.size()) [[unlikely]] + return; + gpio_analog_read_result_callback( + spec::rmcs_board_lite::kGpioDescriptors[channel_index], data); + } + void accelerometer_receive_callback(const librmcs::data::AccelerometerDataView& data) override { (void)data; } diff --git a/host/include/librmcs/agent/rmcs_board_pro.hpp b/host/include/librmcs/agent/rmcs_board_pro.hpp index c0ba562..2059fee 100644 --- a/host/include/librmcs/agent/rmcs_board_pro.hpp +++ b/host/include/librmcs/agent/rmcs_board_pro.hpp @@ -1,11 +1,14 @@ #pragma once +#include #include #include #include #include #include +#include +#include namespace librmcs::agent { @@ -66,6 +69,32 @@ class RmcsBoardPro : private data::DataCallback { return *this; } + PacketBuilder& gpio_digital_write( + const librmcs::spec::rmcs_board_pro::GpioDescriptor& gpio, + const librmcs::data::GpioDigitalDataView& data) { + if (!gpio.supports(spec::GpioCapability::kDigitalWrite) + || !builder_.write_gpio_digital_data(gpio.channel_index, data)) [[unlikely]] + throw std::invalid_argument{"GPIO digital transmission failed: Invalid GPIO data"}; + return *this; + } + PacketBuilder& gpio_digital_read( + const librmcs::spec::rmcs_board_pro::GpioDescriptor& gpio, + const librmcs::data::GpioReadConfigView& data) { + if (!data.supported(gpio) + || !builder_.write_gpio_digital_read_config(gpio.channel_index, data)) [[unlikely]] + throw std::invalid_argument{ + "GPIO digital read configuration transmission failed: Invalid GPIO data"}; + return *this; + } + PacketBuilder& gpio_analog_write( + const librmcs::spec::rmcs_board_pro::GpioDescriptor& gpio, + const librmcs::data::GpioAnalogDataView& data) { + if (!gpio.supports(spec::GpioCapability::kAnalogWrite) + || !builder_.write_gpio_analog_data(gpio.channel_index, data)) [[unlikely]] + throw std::invalid_argument{"GPIO analog transmission failed: Invalid GPIO data"}; + return *this; + } + private: explicit PacketBuilder(host::protocol::Handler& handler) noexcept : builder_(handler.start_transmit()) {} @@ -107,11 +136,31 @@ class RmcsBoardPro : private data::DataCallback { virtual void uart2_receive_callback(const librmcs::data::UartDataView& data) { (void)data; } virtual void uart3_receive_callback(const librmcs::data::UartDataView& data) { (void)data; } - void - gpio_digital_read_result_callback(const librmcs::data::GpioDigitalDataView& data) override { + void gpio_digital_read_result_callback( + uint8_t channel_index, const librmcs::data::GpioDigitalDataView& data) final { + if (channel_index >= spec::rmcs_board_pro::kGpioDescriptors.size()) [[unlikely]] + return; + gpio_digital_read_result_callback( + spec::rmcs_board_pro::kGpioDescriptors[channel_index], data); + } + void gpio_analog_read_result_callback( + uint8_t channel_index, const librmcs::data::GpioAnalogDataView& data) final { + if (channel_index >= spec::rmcs_board_pro::kGpioDescriptors.size()) [[unlikely]] + return; + gpio_analog_read_result_callback( + spec::rmcs_board_pro::kGpioDescriptors[channel_index], data); + } + + virtual void gpio_digital_read_result_callback( + const librmcs::spec::rmcs_board_pro::GpioDescriptor& gpio, + const librmcs::data::GpioDigitalDataView& data) { + (void)gpio; (void)data; } - void gpio_analog_read_result_callback(const librmcs::data::GpioAnalogDataView& data) override { + virtual void gpio_analog_read_result_callback( + const librmcs::spec::rmcs_board_pro::GpioDescriptor& gpio, + const librmcs::data::GpioAnalogDataView& data) { + (void)gpio; (void)data; } diff --git a/host/include/librmcs/protocol/handler.hpp b/host/include/librmcs/protocol/handler.hpp index 4a9fcca..12aafe3 100644 --- a/host/include/librmcs/protocol/handler.hpp +++ b/host/include/librmcs/protocol/handler.hpp @@ -24,11 +24,14 @@ class LIBRMCS_API Handler { bool write_uart(data::DataId field_id, const data::UartDataView& view) noexcept; - bool write_gpio_digital_data(const data::GpioDigitalDataView& view) noexcept; + bool write_gpio_digital_data( + uint8_t channel_index, const data::GpioDigitalDataView& view) noexcept; - bool write_gpio_digital_read_config(const data::GpioReadConfigView& view) noexcept; + bool write_gpio_digital_read_config( + uint8_t channel_index, const data::GpioReadConfigView& view) noexcept; - bool write_gpio_analog_data(const data::GpioAnalogDataView& view) noexcept; + bool write_gpio_analog_data( + uint8_t channel_index, const data::GpioAnalogDataView& view) noexcept; bool write_imu_accelerometer(const data::AccelerometerDataView& view) noexcept; diff --git a/host/src/protocol/handler.cpp b/host/src/protocol/handler.cpp index a6ca13c..5e739b9 100644 --- a/host/src/protocol/handler.cpp +++ b/host/src/protocol/handler.cpp @@ -48,22 +48,26 @@ class Handler::Impl : public core::protocol::DeserializeCallback { logging::get_logger().error("Unexpected uart field id: ", static_cast(id)); } - void gpio_digital_data_deserialized_callback(const data::GpioDigitalDataView& data) override { - callback_.gpio_digital_read_result_callback(data); + void gpio_digital_data_deserialized_callback( + uint8_t channel_index, const data::GpioDigitalDataView& data) override { + callback_.gpio_digital_read_result_callback(channel_index, data); } - void gpio_analog_data_deserialized_callback(const data::GpioAnalogDataView& data) override { - callback_.gpio_analog_read_result_callback(data); + void gpio_analog_data_deserialized_callback( + uint8_t channel_index, const data::GpioAnalogDataView& data) override { + callback_.gpio_analog_read_result_callback(channel_index, data); } void gpio_digital_read_config_deserialized_callback( - const data::GpioReadConfigView& data) override { + uint8_t channel_index, const data::GpioReadConfigView& data) override { + (void)channel_index; (void)data; logging::get_logger().error("Unexpected gpio digital read config field in uplink"); } void gpio_analog_read_config_deserialized_callback( - const data::GpioReadConfigView& data) override { + uint8_t channel_index, const data::GpioReadConfigView& data) override { + (void)channel_index; (void)data; logging::get_logger().error("Unexpected gpio analog read config field in uplink"); } @@ -113,17 +117,19 @@ struct PacketBuilderImpl { return process_result(serializer_.write_uart(field_id, view)); } - [[nodiscard]] bool write_gpio_digital_data(const data::GpioDigitalDataView& view) noexcept { - return process_result(serializer_.write_gpio_digital_data(view)); + [[nodiscard]] bool write_gpio_digital_data( + uint8_t channel_index, const data::GpioDigitalDataView& view) noexcept { + return process_result(serializer_.write_gpio_digital_data(channel_index, view)); } - [[nodiscard]] bool - write_gpio_digital_read_config(const data::GpioReadConfigView& view) noexcept { - return process_result(serializer_.write_gpio_digital_read_config(view)); + [[nodiscard]] bool write_gpio_digital_read_config( + uint8_t channel_index, const data::GpioReadConfigView& view) noexcept { + return process_result(serializer_.write_gpio_digital_read_config(channel_index, view)); } - [[nodiscard]] bool write_gpio_analog_data(const data::GpioAnalogDataView& view) noexcept { - return process_result(serializer_.write_gpio_analog_data(view)); + [[nodiscard]] bool write_gpio_analog_data( + uint8_t channel_index, const data::GpioAnalogDataView& view) noexcept { + return process_result(serializer_.write_gpio_analog_data(channel_index, view)); } [[nodiscard]] bool write_imu_accelerometer(const data::AccelerometerDataView& view) noexcept { @@ -178,20 +184,21 @@ bool Handler::PacketBuilder::write_uart( } bool Handler::PacketBuilder::write_gpio_digital_data( - const data::GpioDigitalDataView& view) noexcept { + uint8_t channel_index, const data::GpioDigitalDataView& view) noexcept { return std::launder(reinterpret_cast(storage_)) - ->write_gpio_digital_data(view); + ->write_gpio_digital_data(channel_index, view); } bool Handler::PacketBuilder::write_gpio_digital_read_config( - const data::GpioReadConfigView& view) noexcept { + uint8_t channel_index, const data::GpioReadConfigView& view) noexcept { return std::launder(reinterpret_cast(storage_)) - ->write_gpio_digital_read_config(view); + ->write_gpio_digital_read_config(channel_index, view); } -bool Handler::PacketBuilder::write_gpio_analog_data(const data::GpioAnalogDataView& view) noexcept { +bool Handler::PacketBuilder::write_gpio_analog_data( + uint8_t channel_index, const data::GpioAnalogDataView& view) noexcept { return std::launder(reinterpret_cast(storage_)) - ->write_gpio_analog_data(view); + ->write_gpio_analog_data(channel_index, view); } bool Handler::PacketBuilder::write_imu_accelerometer(