Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion firmware/rmcs_board/CMakePresets.json
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
{
"version": 2,
"version": 4,
"configurePresets": [
{
"name": "base",
Expand Down
3 changes: 2 additions & 1 deletion firmware/rmcs_board/src/app.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,7 +11,7 @@
#include "firmware/rmcs_board/src/spi/bmi088/gyro.hpp"
#include "firmware/rmcs_board/src/uart/uart.hpp"
#include "firmware/rmcs_board/src/usb/vendor.hpp"
#include "firmware/rmcs_board/src/utility/interrupt_lock_guard.hpp"
#include "firmware/rmcs_board/src/utility/interrupt_lock.hpp"

int main() { librmcs::firmware::app.init().run(); }

Expand All@@ -37,6 +37,7 @@ App::App() {
usb::vendor.init();
}

// Non-static to ensure instantiation
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
[[noreturn]] void App::run() {
while (true) {
Expand Down
164 changes: 49 additions & 115 deletions firmware/rmcs_board/src/spi/bmi088/accel.hpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,26 +2,54 @@

#include <cstddef>
#include <cstdint>
#include <cstring>
#include <new>

#include <board.h>
#include <hpm_gpio_regs.h>
#include <hpm_soc.h>

#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/src/spi/bmi088/base.hpp"
#include "firmware/rmcs_board/src/spi/spi.hpp"
#include "firmware/rmcs_board/src/usb/vendor.hpp"
#include "firmware/rmcs_board/src/utility/lazy.hpp"

namespace librmcs::firmware::spi::bmi088 {

struct AccelerometerTraits {
static constexpr std::size_t kDummyBytes = 2;

enum class RegisterAddress : uint8_t {
kAccSoftReset = 0x7E,
kAccPwrCtrl = 0x7D,
kAccPwrConf = 0x7C,
kAccSelfTest = 0x6D,
kIntMapData = 0x58,
kInt2IoCtrl = 0x54,
kInt1IoCtrl = 0x53,
kAccRange = 0x41,
kAccConf = 0x40,
kTempLsb = 0x23,
kTempMsb = 0x22,
kAccIntStat1 = 0x1D,
kSensorTime2 = 0x1A,
kSensorTime1 = 0x19,
kSensorTime0 = 0x18,
kAccZMsb = 0x17,
kAccZLsb = 0x16,
kAccYMsb = 0x15,
kAccYLsb = 0x14,
kAccXMsb = 0x13,
kAccXLsb = 0x12,
kAccStatus = 0x03,
kAccErrReg = 0x02,
kAccChipId = 0x00,
};
};

class Accelerometer final
: private SpiModule
, private core::utility::Immovable {
: public AccelerometerTraits
, private Bmi088Base<AccelerometerTraits> {
public:
using Lazy = utility::Lazy<Accelerometer, Spi::Lazy*, ChipSelectPin>;

Expand All@@ -40,150 +68,56 @@ class Accelerometer final
explicit Accelerometer(
Spi::Lazy* spi, ChipSelectPin chip_select, Range range = Range::k6G,
DataRate data_rate = DataRate::k1600Hz)
: SpiModule(chip_select)
, spi_(spi->init()) {
: Bmi088Base(spi, chip_select) {

core::utility::assert_debug(spi_.try_lock());

auto read_blocked = [this](RegisterAddress address) {
spi_.transmit_receive_blocked(*this, prepare_tx_buffer_read(address, 1));
return static_cast<uint8_t>(spi_.rx_buffer[2]);
};
auto write_blocked = [this](RegisterAddress address, uint8_t value) {
spi_.transmit_receive_blocked(*this, prepare_tx_buffer_write(address, value));
};

constexpr int max_try_time = 3;
auto read_with_confirm = [&](RegisterAddress address, uint8_t value) {
for (int i = max_try_time; i-- > 0;) {
if (read_blocked(address) == value)
return true;
board_delay_ms(1);
}
return false;
};
auto write_with_confirm = [&](RegisterAddress address, uint8_t value) {
for (int i = max_try_time; i-- > 0;) {
write_blocked(address, value);
board_delay_ms(1);
if (read_blocked(address) == value)
return true;
}
return false;
};

// Dummy read to switch accelerometer to SPI mode.
read_blocked(RegisterAddress::kAccChipId);
read_register(RegisterAddress::kAccChipId);
board_delay_ms(1);

// Reset all registers to reset value.
write_blocked(RegisterAddress::kAccSoftreset, 0xB6);
write_register(RegisterAddress::kAccSoftReset, 0xB6);
board_delay_ms(1);

// "Who am I" check.
core::utility::assert_always(read_with_confirm(RegisterAddress::kAccChipId, 0x1E));
core::utility::assert_always(read_and_confirm(RegisterAddress::kAccChipId, 0x1E));

// Enable INT1 as output pin, push-pull, active-low.
core::utility::assert_always(write_with_confirm(RegisterAddress::kInt1IoCtrl, 0b00001000));
core::utility::assert_always(write_and_confirm(RegisterAddress::kInt1IoCtrl, 0b00001000));
// Map data ready interrupt to pin INT1.
core::utility::assert_always(write_with_confirm(RegisterAddress::kIntMapData, 0b00000100));
core::utility::assert_always(write_and_confirm(RegisterAddress::kIntMapData, 0b00000100));

// Set ODR (output data rate) = data_rate and OSR (over-sampling-ratio) = 1.
core::utility::assert_always(write_with_confirm(
RegisterAddress::kAccConf,
0x80 | (0x02 << 4) | (static_cast<uint8_t>(data_rate) << 0)));
core::utility::assert_always(write_and_confirm(
RegisterAddress::kAccConf, 0x80 | (0x02 << 4) | static_cast<uint8_t>(data_rate)));
// Set accelerometer range.
core::utility::assert_always(
write_with_confirm(RegisterAddress::kAccRange, static_cast<uint8_t>(range)));
write_and_confirm(RegisterAddress::kAccRange, static_cast<uint8_t>(range)));

// Switch the accelerometer into active mode.
core::utility::assert_always(write_with_confirm(RegisterAddress::kAccPwrConf, 0x00));
core::utility::assert_always(write_and_confirm(RegisterAddress::kAccPwrConf, 0x00));
// Turn on the accelerometer.
core::utility::assert_always(write_with_confirm(RegisterAddress::kAccPwrCtrl, 0x04));
core::utility::assert_always(write_and_confirm(RegisterAddress::kAccPwrCtrl, 0x04));
board_delay_ms(1); // Datasheet: wait >=450us after entering normal mode

spi_.unlock();
}

void data_ready_callback() { read(RegisterAddress::kAccXLsb, 6); }
void data_ready_callback() { read_async(RegisterAddress::kAccXLsb, 6); }

private:
enum class RegisterAddress : uint8_t {
kAccSoftreset = 0x7E,
kAccPwrCtrl = 0x7D,
kAccPwrConf = 0x7C,
kAccSelfTest = 0x6D,
kIntMapData = 0x58,
kInt2IoCtrl = 0x54,
kInt1IoCtrl = 0x53,
kAccRange = 0x41,
kAccConf = 0x40,
kTempLsb = 0x23,
kTempMsb = 0x22,
kAccIntStat1 = 0x1D,
kSensortime2 = 0x1A,
kSensortime1 = 0x19,
kSensortime0 = 0x18,
kAccZMsb = 0x17,
kAccZLsb = 0x16,
kAccYMsb = 0x15,
kAccYLsb = 0x14,
kAccXMsb = 0x13,
kAccXLsb = 0x12,
kAccStatus = 0x03,
kAccErrReg = 0x02,
kAccChipId = 0x00,
};

struct __attribute__((packed)) Data {
int16_t x;
int16_t y;
int16_t z;
};

bool write(RegisterAddress address, uint8_t value) {
if (!spi_.try_lock())
return false;

spi_.transmit_receive(*this, prepare_tx_buffer_write(address, value));
return true;
}

bool read(RegisterAddress address, size_t read_size) {
if (!spi_.try_lock())
return false;

spi_.transmit_receive(*this, prepare_tx_buffer_read(address, read_size));
return true;
}

void transmit_receive_completed_callback(size_t size) override {
core::utility::assert_debug(size == sizeof(Data) + 2);
auto& data = *std::launder(reinterpret_cast<Data*>(spi_.rx_buffer + 2));
void transmit_receive_async_callback(std::byte* rx_buffer, std::size_t size) override {
auto& data = parse_rx_data(rx_buffer, size);
handle_uplink(usb::vendor->serializer(), data);
spi_.unlock();
}

std::size_t prepare_tx_buffer_write(RegisterAddress address, uint8_t value) {
spi_.tx_buffer[0] = static_cast<std::byte>(address);
spi_.tx_buffer[1] = static_cast<std::byte>(value);
return 2;
}

std::size_t prepare_tx_buffer_read(RegisterAddress address, size_t read_size) {
spi_.tx_buffer[0] = std::byte{0x80} | static_cast<std::byte>(address);
std::memset(&spi_.tx_buffer[1], 0, read_size + 1);
return read_size + 2;
}

static void handle_uplink(core::protocol::Serializer& serializer, Data& data) {
core::utility::assert_debug(
serializer.write_imu_accelerometer(
data::AccelerometerDataView{.x = data.x, .y = data.y, .z = data.z})
serializer.write_imu_accelerometer({.x = data.x, .y = data.y, .z = data.z})
!= core::protocol::Serializer::SerializeResult::kInvalidArgument);
}

Spi& spi_;
};

inline Accelerometer::Lazy accelerometer(
Expand Down
101 changes: 101 additions & 0 deletions firmware/rmcs_board/src/spi/bmi088/base.hpp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
#pragma once

#include <cstddef>
#include <cstdint>
#include <cstring>
#include <new>
#include <type_traits>

#include <board.h>

#include "core/src/utility/assert.hpp"
#include "firmware/rmcs_board/src/spi/spi.hpp"

namespace librmcs::firmware::spi::bmi088 {

template <typename TraitsT>
class Bmi088Base : private SpiModule {
protected:
static constexpr std::size_t kDummyBytes = TraitsT::kDummyBytes;
static_assert(kDummyBytes >= 1);

using RegisterAddressType = TraitsT::RegisterAddress;
static_assert(
std::is_scoped_enum_v<RegisterAddressType>
&& std::is_same_v<std::underlying_type_t<RegisterAddressType>, uint8_t>);

static constexpr int kMaxRetries = 3;

struct [[gnu::packed]] Data {
int16_t x;
int16_t y;
int16_t z;
};

explicit Bmi088Base(Spi::Lazy* spi, ChipSelectPin chip_select_pin)
: SpiModule(chip_select_pin)
, spi_(spi->init()) {}

uint8_t read_register(RegisterAddressType addr) {
spi_.transmit_receive(*this, prepare_tx_buffer_read(static_cast<uint8_t>(addr), 1));
return static_cast<uint8_t>(spi_.rx_buffer[kDummyBytes]);
}

void write_register(RegisterAddressType addr, uint8_t val) {
spi_.transmit_receive(*this, prepare_tx_buffer_write(static_cast<uint8_t>(addr), val));
}

bool read_and_confirm(RegisterAddressType addr, uint8_t expected) {
for (int i = kMaxRetries; i-- > 0;) {
if (read_register(addr) == expected)
return true;
board_delay_ms(1);
}
return false;
}

bool write_and_confirm(RegisterAddressType addr, uint8_t val) {
for (int i = kMaxRetries; i-- > 0;) {
write_register(addr, val);
board_delay_ms(1);
if (read_register(addr) == val)
return true;
}
return false;
}

bool read_async(RegisterAddressType addr, std::size_t size) {
if (!spi_.try_lock())
return false;

spi_.transmit_receive_async(
*this, prepare_tx_buffer_read(static_cast<uint8_t>(addr), size));
return true;
}

Data& parse_rx_data(std::byte* rx_buffer, std::size_t size) {
core::utility::assert_debug(size == sizeof(Data) + kDummyBytes);
return *std::launder(reinterpret_cast<Data*>(rx_buffer + kDummyBytes));
}

Spi& spi_;

private:
std::size_t prepare_tx_buffer_read(uint8_t addr, std::size_t read_size) {
core::utility::assert_debug(read_size + kDummyBytes <= Spi::kMaxTransferSize);

spi_.tx_buffer[0] = static_cast<std::byte>(0x80 | addr);
std::memset(&spi_.tx_buffer[1], 0, read_size + kDummyBytes - 1);
return read_size + kDummyBytes;
}

std::size_t prepare_tx_buffer_write(uint8_t addr, uint8_t val) {
static_assert(2 <= Spi::kMaxTransferSize);

spi_.tx_buffer[0] = static_cast<std::byte>(addr);
spi_.tx_buffer[1] = static_cast<std::byte>(val);
return 2;
}
};

} // namespace librmcs::firmware::spi::bmi088
Loading