diff --git a/CMakeLists.txt b/CMakeLists.txt index 50d4afe..df1b0b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ libhal_test_and_make_library( LIBRARY_NAME libhal-sensor SOURCES + src/as5600.cpp src/imu/icm20948.cpp src/imu/mpu6050.cpp src/multi/bmp180.cpp @@ -33,5 +34,6 @@ libhal_test_and_make_library( tests/imu/mpu6050.test.cpp tests/multi/bmp180.test.cpp tests/temperature/tmp102.test.cpp + tests/as5600.cpp tests/main.test.cpp ) diff --git a/datasheets/AS5600.pdf b/datasheets/AS5600.pdf new file mode 100644 index 0000000..520c541 Binary files /dev/null and b/datasheets/AS5600.pdf differ diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index b3abb51..0c6bec5 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -19,10 +19,12 @@ project(demos LANGUAGES CXX) libhal_build_demos( DEMOS #bmp180 - icm20948 - mpl3115a2 - mpu6050 - tmp102 + as5600 + #icm20948 + #mpl3115a2 + #mpu6050 + #tmp102 + #TODO(#37): update drivers and demos to use strong ptr INCLUDES . diff --git a/demos/applications/as5600.cpp b/demos/applications/as5600.cpp new file mode 100644 index 0000000..5833f28 --- /dev/null +++ b/demos/applications/as5600.cpp @@ -0,0 +1,67 @@ +// Copyright 2026 Malia Labor and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include + +void application() +{ + using namespace std::chrono_literals; + using namespace hal::literals; + + auto const clock = resources::clock(); + auto const console = resources::console(); + auto const i2c = resources::i2c(); + + hal::print(*console, "AS5600 Application Starting...\n"); + hal::sensor::as5600 hall_sensor(i2c); + + auto magnet_status = hall_sensor.magnet_status(); + if (magnet_status.detected) { + hal::print(*console, "Magnet detected\n"); + } + auto const start_angle = hall_sensor.start_angle(); + auto const stop_angle = hall_sensor.stop_angle(); + auto const angular_range = hall_sensor.angular_range(); + auto const power_mode = hall_sensor.power_mode(); + auto const hysteresis = hall_sensor.hysteresis(); + bool const watchdog_enabled = hall_sensor.watchdog_enabled(); + + auto const agc = hall_sensor.auto_gain_control(); + auto const mag = hall_sensor.magnitude(); + + hal::print<32>(*console, "Start angle: %.2f \n", start_angle); + hal::print<32>(*console, "Stop angle: %.2f \n", stop_angle); + hal::print<32>(*console, "Max Angle: %.2f \n", angular_range); + hal::print<32>(*console, "Power Mode: %d \n", power_mode); + hal::print<32>(*console, "Hysteresis: %d \n", hysteresis); + hal::print<32>(*console, "WD: %d \n", watchdog_enabled); + hal::print<32>(*console, "AGC: %d \n", agc); + hal::print<32>(*console, "Magnitude: %d \n", mag); + + while (true) { + magnet_status = hall_sensor.magnet_status(); + if (magnet_status.detected) { + auto raw_angle = hall_sensor.raw_angle(); + auto angle = hall_sensor.angle(); + + hal::print<64>(*console, "Raw Angle: %.2f ", raw_angle); + hal::print<64>(*console, "Angle: %.2f \n", angle); + } + hal::delay(*clock, 500ms); + } +} diff --git a/demos/main.cpp b/demos/main.cpp index e603925..4662321 100644 --- a/demos/main.cpp +++ b/demos/main.cpp @@ -12,75 +12,62 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include + +#include + #include #include -#include #include -resource_list resources{}; +int main() +{ + initialize_platform(); + application(); + std::terminate(); +} -[[noreturn]] void terminate_handler() noexcept +// Override global new operator +void* operator new(std::size_t) { - if (resources.console) { - hal::print(*resources.console.value(), "☠️ APPLICATION TERMINATED ☠️\n\n"); - } + std::terminate(); +} - if (resources.status_led && resources.clock) { - auto& led = *resources.status_led.value(); - auto& clock = *resources.clock.value(); +// Override global new[] operator +void* operator new[](std::size_t) +{ + std::terminate(); +} - while (true) { - using namespace std::chrono_literals; - led.level(false); - hal::delay(clock, 100ms); - led.level(true); - hal::delay(clock, 100ms); - led.level(false); - hal::delay(clock, 100ms); - led.level(true); - hal::delay(clock, 1000ms); - } - } +void* operator new(unsigned int, std::align_val_t) +{ + std::terminate(); +} - // spin here forever - while (true) { - continue; - } +// Override global delete operator +void operator delete(void*) noexcept +{ } -int main() +// Override global delete[] operator +void operator delete[](void*) noexcept { - try { - initialize_platform(resources); - } catch (...) { - while (true) { - // halt here and wait for a debugger to connect - continue; - } - } +} - hal::set_terminate(terminate_handler); +// Optional: Override sized delete operators (C++14 and later) +void operator delete(void*, std::size_t) noexcept +{ +} - try { - application(resources); - } catch (std::bad_optional_access const& e) { - if (resources.console) { - hal::print(*resources.console.value(), - "A resource required by the application was not available!\n" - "Calling terminate!\n"); - } - } // Allow any other exceptions to terminate the application +void operator delete[](void*, std::size_t) noexcept +{ +} - // Terminate if the code reaches this point. - std::terminate(); +void operator delete[](void*, std::align_val_t) noexcept +{ } -extern "C" +void operator delete(void*, std::align_val_t) noexcept { - // This gets rid of an issue with libhal-exceptions in Debug mode. - void __assert_func() - { - } } diff --git a/demos/platforms/stm32f103c8.cpp b/demos/platforms/stm32f103c8.cpp index 9a42c5f..8f42123 100644 --- a/demos/platforms/stm32f103c8.cpp +++ b/demos/platforms/stm32f103c8.cpp @@ -14,45 +14,184 @@ #include #include +#include #include #include #include +#include #include #include +#include #include +#include +#include +#include #include #include -void initialize_platform(resource_list& p_resources) +// Global optionals for exception handling +hal::v5::optional_ptr clock_ptr; +hal::v5::optional_ptr status_led_ptr; + +[[noreturn]] void terminate_handler() noexcept { - using namespace hal::literals; - p_resources.reset = +[]() { hal::cortex_m::reset(); }; + if (not status_led_ptr && not clock_ptr) { + // spin here until debugger is connected + while (true) { + continue; + } + } + + // Otherwise, blink the led in a pattern + while (true) { + using namespace std::chrono_literals; + status_led_ptr->level(false); + hal::delay(*clock_ptr, 100ms); + status_led_ptr->level(true); + hal::delay(*clock_ptr, 100ms); + status_led_ptr->level(false); + hal::delay(*clock_ptr, 100ms); + status_led_ptr->level(true); + hal::delay(*clock_ptr, 1000ms); + } +} +void initialize_platform() +{ + using namespace hal::literals; + hal::set_terminate(terminate_handler); // Set the MCU to the maximum clock speed hal::stm32f1::maximum_speed_using_internal_oscillator(); +} - static hal::cortex_m::dwt_counter steady_clock( - hal::stm32f1::frequency(hal::stm32f1::peripheral::cpu)); - p_resources.clock = &steady_clock; +namespace resources { +using namespace hal::literals; - static hal::stm32f1::uart uart1(hal::port<1>, - hal::buffer<128>, - hal::serial::settings{ - .baud_rate = 115200, - }); - p_resources.console = &uart1; +std::pmr::polymorphic_allocator<> driver_allocator() +{ + static std::array driver_memory{}; + static std::pmr::monotonic_buffer_resource resource( + driver_memory.data(), + driver_memory.size(), + std::pmr::null_memory_resource()); + return &resource; +} - static hal::stm32f1::output_pin led('C', 13); - p_resources.status_led = &led; +void reset() +{ + hal::cortex_m::reset(); +} + +void sleep(hal::time_duration p_duration) +{ + auto delay_clock = resources::clock(); + hal::delay(*delay_clock, p_duration); +} +hal::v5::strong_ptr clock() +{ + if (clock_ptr) { + return clock_ptr; + } + + clock_ptr = hal::v5::make_strong_ptr( + driver_allocator(), hal::stm32f1::frequency(hal::stm32f1::peripheral::cpu)); + return clock_ptr; +} + +hal::v5::strong_ptr console() +{ + return hal::v5::make_strong_ptr(driver_allocator(), + hal::port<1>, + hal::buffer<128>, + hal::serial::settings{ + .baud_rate = 115200, + }); +} + +hal::v5::strong_ptr uart2() +{ + return hal::v5::make_strong_ptr( + driver_allocator(), hal::port<2>, hal::buffer<128>); +} + +hal::v5::strong_ptr status_led() +{ + if (status_led_ptr) { + return status_led_ptr; + } + + status_led_ptr = hal::v5::make_strong_ptr( + driver_allocator(), 'C', 13); + return status_led_ptr; +} + +hal::v5::strong_ptr can_transceiver() +{ + throw hal::operation_not_supported(nullptr); +} + +hal::v5::strong_ptr can_bus_manager() +{ + throw hal::operation_not_supported(nullptr); +} + +hal::v5::strong_ptr can_identifier_filter() +{ + throw hal::operation_not_supported(nullptr); +} + +hal::v5::strong_ptr pwm() +{ + throw hal::operation_not_supported(nullptr); +} + +auto& timer1() +{ + static hal::stm32f1::advanced_timer + timer1{}; + return timer1; +} + +hal::v5::optional_ptr pwm16_channel_ptr; +hal::v5::strong_ptr pwm_channel() +{ + if (pwm16_channel_ptr) { + return pwm16_channel_ptr; + } + + auto pwm = timer1().acquire_pwm16_channel(hal::stm32f1::timer1_pin::pa8); + using PwmType = decltype(pwm); + pwm16_channel_ptr = + hal::v5::make_strong_ptr(driver_allocator(), std::move(pwm)); + return pwm16_channel_ptr; +} + +hal::v5::optional_ptr pwm_group_manager_ptr; +hal::v5::strong_ptr pwm_frequency() +{ + if (pwm_group_manager_ptr) { + return pwm_group_manager_ptr; + } + + auto timer_pwm_frequency = timer1().acquire_pwm_group_frequency(); + pwm_group_manager_ptr = + hal::v5::make_strong_ptr( + driver_allocator(), std::move(timer_pwm_frequency)); + return pwm_group_manager_ptr; +} + +hal::v5::strong_ptr i2c() +{ static hal::stm32f1::output_pin sda_output_pin('B', 7); static hal::stm32f1::output_pin scl_output_pin('B', 6); - static hal::bit_bang_i2c bit_bang_i2c( - hal::bit_bang_i2c::pins{ - .sda = &sda_output_pin, - .scl = &scl_output_pin, - }, - steady_clock); - p_resources.i2c = &bit_bang_i2c; + + return hal::v5::make_strong_ptr(driver_allocator(), + hal::bit_bang_i2c::pins{ + .sda = &sda_output_pin, + .scl = &scl_output_pin, + }, + *clock_ptr); } +} // namespace resources diff --git a/demos/resource_list.hpp b/demos/resource_list.hpp index 4a6a483..64e8102 100644 --- a/demos/resource_list.hpp +++ b/demos/resource_list.hpp @@ -14,24 +14,33 @@ #pragma once -#include - +#include #include #include #include +#include +#include #include #include +#include -struct resource_list -{ - hal::callback reset; - std::optional console; - std::optional clock; - std::optional status_led; - std::optional i2c; - // Add more driver interfaces here ... -}; +namespace resources { +std::pmr::polymorphic_allocator<> driver_allocator(); +void reset(); +void sleep(hal::time_duration p_duration); +hal::v5::strong_ptr console(); +hal::v5::strong_ptr uart2(); +hal::v5::strong_ptr clock(); +hal::v5::strong_ptr status_led(); +hal::v5::strong_ptr can_transceiver(); +hal::v5::strong_ptr can_bus_manager(); +hal::v5::strong_ptr can_identifier_filter(); +hal::v5::strong_ptr pwm(); +hal::v5::strong_ptr pwm_channel(); +hal::v5::strong_ptr pwm_frequency(); +hal::v5::strong_ptr i2c(); +} // namespace resources -void initialize_platform(resource_list& p_resources); // Application function is implemented by one of the .cpp files. -void application(resource_list& p_resources); +void initialize_platform(); +void application(); diff --git a/include/libhal-sensor/as5600.hpp b/include/libhal-sensor/as5600.hpp new file mode 100644 index 0000000..452b888 --- /dev/null +++ b/include/libhal-sensor/as5600.hpp @@ -0,0 +1,268 @@ +// Copyright 2026 Malia Labor and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +#include +#include +#include +#include +#include + +namespace hal::sensor { +/** + * @brief The AS5600 is a magnetic rotary position sensor with a high-resolution + * 12-bit i2c output. + * + * This contactless system measures the absolute angle of a diametric magnetized + * on-axis magnet. By default the output represents a range from 0 to 360 + * degrees. It is also possible to define a smaller range to the output by + * programming a zero angle (start position) and a maximum angle (stop + * position). The AS5600 is also equipped with a smart low power mode feature to + * automatically reduce the power consumption. + * + */ +class as5600 +{ +public: + /** + * @brief Power mode settings available to use when configuring. + * + */ + enum class power_mode_config : u8 + { + /// Normal mode with max sampling rate results in a current consumption + /// of: 6.5 mA + poll_max_6500uA = 0, + /// Polling period = 5ms, current consumption of = 3.4 mA + poll_5ms_3400uA = 0b01, + /// Polling period = 20ms, current consumption of = 1.8 mA + poll_20ms_1800uA = 0b10, + /// Polling period = 100ms, current consumption of = 1.5 mA + poll_100ms_1500uA = 0b11, + }; + + /** + * @brief Hysteresis mode settings available to use when configuring. Check + * data sheet for more information. + * + */ + enum class hysteresis_config : u8 + { + off = 0, + lsb_1 = 0b0100, + lsb_2 = 0b1000, + lsb_3 = 0b1100 + }; + + /** + * @brief Magnet status object. + * + */ + struct magnet + { + /** + * @brief Magnet detected flag. Is true when magnet is detected. + * + */ + bool detected; + /** + * @brief Magnet too strong flag. Is true when magnetic field is too strong + * (possibly too close to sensor). + * + */ + bool too_strong; + /** + * @brief Magnet too weak flag. Is true when magnetic field is too weak + * (possibly too far from sensor). + * + */ + bool too_weak; + }; + + /** + * @brief Construct a new as5600 object + * + * @param p_i2c i2c bus of the device + */ + as5600(hal::strong_ptr const& p_i2c); + + /** + * @brief Get the start angle to use when using a narrower angular range. + * + * Used in combination with either a stop angle or the angular range + * register. The angular range must be greater than 18 degrees. + * + * @return hal::degrees - Start angle (0 - 360 degree range). + */ + hal::degrees start_angle(); + + /** + * @brief Get the stop angle to use when using a narrower angular range. + * + * @return hal::degrees - Stop angle (0 - 360 degree range). + */ + hal::degrees stop_angle(); + + /** + * @brief Get the angular range value. + * + * @return hal::degrees - Angular range (0 - 360 degree range). + */ + hal::degrees angular_range(); + + /** + * @brief Get the power mode setting for device. + * + * @return power_mode_config - Current power mode + */ + power_mode_config power_mode(); + + /** + * @brief Get the hysteresis setting for device. + * + * Check the AS5600 datasheet for more information about hysteresis. + * + * @return hysteresis_config - Current hysteresis mode. + */ + hysteresis_config hysteresis(); + + /** + * @brief Get the status of the watchdog timer + * + * The watchdog timer allows saving power by switching into low power mode 3 + * (100 ms polling time) if the angle stays within the watchdog threshold of 4 + * LSB for at least one minute + * + * @return true - Watchdog timer enabled + * @return false - Watchdog timer disabled + */ + bool watchdog_enabled(); + + /** + * @brief Set the start raw angle to use when using a narrower angular range. + * Any angle lower than this will be reported as a raw value of 0 until moved + * back into tracking range. + * + * The angular range is split up between 4096 steps. Reducing the range will + * increase the step resolution and must be greater than 18 degrees. + * + * @param p_angle - Start angle (0 - 360 degree range) + */ + void start_angle(hal::degrees p_angle); + + /** + * @brief Set the stop angle to use when using a narrower angular range. + * Any angle higher than this will be reported as a raw value of 4095 until + * moved back into tracking range. + * + * The angular range is split up between 4096 steps. Reducing the range will + * increase the step resolution and must be greater than 18 degrees. + * + * @param p_angle - Stop angle (0 - 360 degree range) + */ + void stop_angle(hal::degrees p_angle); + + /** + * @brief Set the angular range instead of manually setting the stop angle + * + * stop angle = start angle + angular range + * + * @param p_angle - Angular range (0 - 360 degree range) + */ + void angular_range(hal::degrees p_angle); + + /** + * @brief Set the power mode setting for device. + * + * @param p_power_mode - Power mode to use. + */ + void power_mode(power_mode_config p_power_mode); + + /** + * @brief Set the hysteresis mode setting for the device + * + * @param p_hysteresis - Hysteresis mode to use. + */ + void hysteresis(hysteresis_config p_hysteresis); + + /** + * @brief Enable or disable the watchdog timer. + * + * @param p_enabled - Enabled status of watchdog timer + */ + void watchdog(bool p_enabled); + + /** + * @brief Get the unscaled and unmodified angle + * + * @return hal::degrees - Raw angle (0 - 360 degree range) + */ + hal::degrees raw_angle(); + + /** + * @brief Get the angle scaled to narrower angle range specified with start + * position in combination with either stop position or angular range. + * + * Angle register has a hysteresis of 10-LSB at the limit of the 360 degree + * range to avoid discontinuity points or toggling of the output within one + * rotation. Check datasheet for more information. + * + * Explanation of hysteresis: the 10-LSB simply means that when the angle + * crosses from 4095 (max 12-bit unsigned integer value) to 0, the sensor + * waits until the angle represented by the value of 10 is reached, then the + * angle value jumps from 4095 to 10. Rotating the angle back towards 0 will + * decrease from 10 until 0 is reached. When the zero crossing is reached, the + * value will stay 0 until the angle represented by 4085 is reached, then the + * value will change. + * + * @return hal::degrees + */ + hal::degrees angle(); + + magnet magnet_status(); + + /** + * @brief Get the current gain of the automatic gain control. + * + * @return uint8_t - Current gain value. + */ + uint8_t auto_gain_control(); + + /** + * @brief Get the magnitude value of the internal CORDIC + * + * @return uint16_t - Magnitude value + */ + uint16_t magnitude(); + +private: + template + auto read_register(hal::byte p_register_address) + { + hal::write( + *m_i2c, m_address, std::array{ p_register_address }); + return hal::read(*m_i2c, m_address); + } + + void write_angle_to_register(hal::byte p_register, hal::degrees p_angle); + + hal::strong_ptr m_i2c; + static constexpr hal::byte m_address = 0x36; + + std::pair m_narrowed_range; +}; +} // namespace hal::sensor diff --git a/src/as5600.cpp b/src/as5600.cpp new file mode 100644 index 0000000..8b083e9 --- /dev/null +++ b/src/as5600.cpp @@ -0,0 +1,207 @@ +// Copyright 2026 Malia Labor and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include +#include +#include +#include + +namespace hal::sensor { + +constexpr auto as5600_raw_range = std::make_pair(0, 4095); +constexpr auto as5600_degree_range = std::make_pair(0.0f, 359.0f); + +as5600::as5600(hal::strong_ptr const& p_i2c) + : m_i2c(p_i2c) +{ + m_narrowed_range.first = start_angle(); + m_narrowed_range.second = stop_angle(); + if (m_narrowed_range.first == 0.0 && m_narrowed_range.second == 0) { + m_narrowed_range.second = m_narrowed_range.first + angular_range(); + if (m_narrowed_range.second == 0) { + m_narrowed_range.second = as5600_degree_range.second; + } + } +} + +void as5600::write_angle_to_register(hal::byte p_register, hal::degrees p_angle) +{ + hal::degrees const clamped_angle = + std::clamp(p_angle, as5600_degree_range.first, as5600_degree_range.second); + uint16_t const position = + hal::map(clamped_angle, as5600_degree_range, as5600_raw_range); + + hal::byte const hi_byte_base = + as5600::read_register<1>(p_register)[0] & 0b11110000; + hal::byte const low_byte = position; + hal::byte const hi_byte = hi_byte_base | (position >> 8); + hal::write(*m_i2c, + m_address, + std::array{ p_register, hi_byte, low_byte }); +} + +hal::degrees as5600::start_angle() +{ + auto register_bytes = as5600::read_register<2>(0x01); + + auto const low_byte = register_bytes[1]; + auto const hi_byte = (register_bytes[0] & 0b1111); + uint16_t const combined = (hi_byte << 8) | low_byte; + return hal::map(combined, as5600_raw_range, as5600_degree_range); +} + +hal::degrees as5600::stop_angle() +{ + auto register_bytes = as5600::read_register<2>(0x03); + + auto const low_byte = register_bytes[1]; + auto const hi_byte = (register_bytes[0] & 0b1111); + uint16_t const combined = (hi_byte << 8) | low_byte; + return hal::map(combined, as5600_raw_range, as5600_degree_range); +} + +hal::degrees as5600::angular_range() +{ + auto register_bytes = as5600::read_register<2>(0x05); + + auto const low_byte = register_bytes[1]; + auto const hi_byte = (register_bytes[0] & 0b1111); + uint16_t const combined = (hi_byte << 8) | low_byte; + return hal::map(combined, as5600_raw_range, as5600_degree_range); +} + +as5600::power_mode_config as5600::power_mode() +{ + return static_cast(as5600::read_register<1>(0x08)[0] & + 0b11); +} + +as5600::hysteresis_config as5600::hysteresis() +{ + return static_cast(as5600::read_register<1>(0x08)[0] & + 0b1100); +} + +bool as5600::watchdog_enabled() +{ + auto const conf_byte = as5600::read_register<1>(0x07)[0]; + std::bitset<8> conf_bits{ conf_byte }; + return conf_bits[5]; +} + +void as5600::start_angle(hal::degrees p_angle) +{ + auto const clamped_angle = + std::clamp(p_angle, as5600_degree_range.first, as5600_degree_range.second); + m_narrowed_range.first = clamped_angle; + write_angle_to_register(0x01, clamped_angle); +} + +void as5600::stop_angle(hal::degrees p_angle) +{ + auto const clamped_angle = + std::clamp(p_angle, as5600_degree_range.first, as5600_degree_range.second); + m_narrowed_range.second = clamped_angle; + write_angle_to_register(0x03, clamped_angle); +} + +void as5600::angular_range(hal::degrees p_angle) +{ + auto const clamped_angle = + std::clamp(p_angle, + as5600_degree_range.first, + as5600_degree_range.second - m_narrowed_range.first); + m_narrowed_range.second = m_narrowed_range.first + clamped_angle; + write_angle_to_register(0x05, p_angle); +} + +void as5600::power_mode(as5600::power_mode_config p_power_mode) +{ + hal::byte bits = std::to_underlying(p_power_mode); + auto conf_byte = as5600::read_register<1>(0x08)[0]; + conf_byte = conf_byte & 0b11111100; + conf_byte = conf_byte | bits; + hal::write(*m_i2c, m_address, std::array{ 0x08, conf_byte }); +} + +void as5600::hysteresis(as5600::hysteresis_config p_hysteresis) +{ + hal::byte bits = std::to_underlying(p_hysteresis); + auto conf_byte = as5600::read_register<1>(0x08)[0]; + conf_byte = conf_byte & 0b11110011; + conf_byte = conf_byte | bits; + hal::write(*m_i2c, m_address, std::array{ 0x08, conf_byte }); +} + +void as5600::watchdog(bool p_enabled) +{ + hal::byte conf_byte = as5600::read_register<1>(0x07)[0]; + std::bitset<8> conf_bits{ conf_byte }; + conf_bits.set(5, p_enabled); + conf_byte = static_cast(conf_bits.to_ulong()); + hal::write(*m_i2c, m_address, std::to_array({ 0x07, conf_byte })); +} + +hal::degrees as5600::raw_angle() +{ + auto register_bytes = as5600::read_register<2>(0x0C); + + auto const low_byte = register_bytes[1]; + auto const hi_byte = (register_bytes[0] & 0b1111); + uint16_t const combined = (hi_byte << 8) | low_byte; + return hal::map(combined, as5600_raw_range, as5600_degree_range); +} + +hal::degrees as5600::angle() +{ + auto register_bytes = as5600::read_register<2>(0x0E); + + auto const low_byte = register_bytes[1]; + auto const hi_byte = (register_bytes[0] & 0b1111); + uint16_t const combined = (hi_byte << 8) | low_byte; + return hal::map(combined, as5600_raw_range, m_narrowed_range); +} + +as5600::magnet as5600::magnet_status() +{ + auto const status_byte = as5600::read_register<1>(0x0B)[0]; + std::bitset<8> status_bits{ status_byte }; + + as5600::magnet magnet_status; + magnet_status.detected = status_bits[5]; + magnet_status.too_strong = status_bits[3]; + magnet_status.too_weak = status_bits[4]; + return magnet_status; +} + +uint8_t as5600::auto_gain_control() +{ + return as5600::read_register<1>(0x1A)[0]; +} + +uint16_t as5600::magnitude() +{ + auto register_bytes = as5600::read_register<2>(0x1B); + + auto const low_byte = register_bytes[1]; + auto const hi_byte = (register_bytes[0] & 0b1111); + + return (hi_byte << 8) | low_byte; +} + +} // namespace hal::sensor diff --git a/tests/as5600.cpp b/tests/as5600.cpp new file mode 100644 index 0000000..584d5ae --- /dev/null +++ b/tests/as5600.cpp @@ -0,0 +1,30 @@ +// Copyright 2026 Malia Labor and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +namespace hal::sensor { +boost::ut::suite test_as5600 = []() { + using namespace boost::ut; + using namespace std::literals; + + "as5600::as5600()"_test = []() { + // Setup + // Exercise + // Verify + }; +}; +} // namespace hal::sensor