// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include "bsp/magnetometer/magnetometer.hpp" #include // for byte conversion functions. it is included first because of magic enum define #include "ALS31300.hpp" #include "bsp/BoardDefinitions.hpp" #include "drivers/i2c/DriverI2C.hpp" #include #include using namespace drivers; using namespace utils; static std::shared_ptr i2c; static I2CAddress addr = {.deviceAddress = als31300::I2C_ADDRESS, .subAddress = 0, .subAddressSize = 1}; union i2c_buf_t { uint8_t buf[sizeof(als31300::whole_reg_t)]; als31300::whole_reg_t whole_reg; }; static i2c_buf_t i2c_buf; static xQueueHandle qHandleIrq = NULL; namespace bsp { namespace magnetometer { enum class LPDCM_INACTIVE_TIME { inactive_500us, inactive_1ms, inactive_5ms, inactive_10ms, inactive_50ms, inactive_100ms, inactive_500ms, inactive_1s }; enum class BANDWIDTH_SELECT { bandwidth_3500Hz = 0, bandwidth_7kHz = 1, bandwidth_14kHz = 2, bandwidth_10kHz = 4, bandwidth_20kHz = 5, bandwidth_40kHz = 6 }; namespace { bool isTimeToCompleteWriteDefinedForRegistry(std::uint8_t address) { const auto it = std::find(als31300::EEPROM_REGS.begin(), als31300::EEPROM_REGS.end(), address); return it != als31300::EEPROM_REGS.end(); } } // namespace std::shared_ptr gpio; bsp::KeyCodes current_parsed = bsp::KeyCodes::Undefined; static TimerHandle_t timerHandle; static constexpr uint16_t MAGNETOMETER_POLL_INTERVAL_MS = 500; static void TimerHandler(TimerHandle_t xTimer) { if (qHandleIrq != nullptr) { uint8_t val = 0x01; xQueueSend(qHandleIrq, &val, 0); } } bool setActive(als31300::PWR_REG_SLEEP_MODE sleep_mode); bool i2cRead(const uint8_t reg_addr, als31300::whole_reg_t &whole_reg) { addr.subAddress = reg_addr; if (i2c->Read(addr, i2c_buf.buf, sizeof(als31300::whole_reg_t)) != sizeof(als31300::whole_reg_t)) { return false; } #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ // magnetometr talks big endian i2c_buf.whole_reg = swapBytes(i2c_buf.whole_reg); #endif whole_reg = i2c_buf.whole_reg; return true; } bool i2cWrite(const uint8_t reg_addr, const als31300::whole_reg_t whole_reg) { addr.subAddress = reg_addr; #if __BYTE_ORDER__ != __ORDER_BIG_ENDIAN__ // magnetometer talks big endian i2c_buf.whole_reg = swapBytes(whole_reg); #else i2c_buf.whole_reg = whole_reg; #endif auto wrote = i2c->Write(addr, i2c_buf.buf, sizeof(als31300::whole_reg_t)) == sizeof(als31300::whole_reg_t); if (isTimeToCompleteWriteDefinedForRegistry(reg_addr)) { vTaskDelay(pdMS_TO_TICKS(als31300::EEPROM_REG_WRITE_DELAY_MS.count())); } return wrote; } int32_t init(xQueueHandle qHandle) { i2c = DriverI2C::Create( static_cast(BoardDefinitions::MAGNETOMETER_I2C), DriverI2CParams{.baudrate = static_cast(BoardDefinitions::MAGNETOMETER_I2C_BAUDRATE)}); qHandleIrq = qHandle; // any configuration must be proceeded in active state setActive(als31300::PWR_REG_SLEEP_MODE::active); // GET WRITE ACCESS if (!i2cWrite(als31300::CUSTOMER_ACCESS_REG, als31300::CUSTOMER_ACCESS_REG_code)) { LOG_ERROR("magneto: CANNOT INIT SLIDER SENSOR"); return kStatus_Fail; } // CONFIGURATION register read als31300::whole_reg_t read_reg; i2cRead(als31300::CONF_REG, read_reg); const als31300::conf_reg current_reg_conf(read_reg); LOG_DEBUG("CONF read:\t%" PRIu32, static_cast(current_reg_conf)); als31300::conf_reg reg_conf = current_reg_conf; reg_conf.I2C_threshold = als31300::CONF_REG_I2C_THRES_1v8; reg_conf.int_latch_enable = als31300::CONF_REG_LATCH_disabled; // we want to detect stable positions reg_conf.channel_X_en = als31300::CONF_REG_CHANNEL_enabled; reg_conf.channel_Y_en = als31300::CONF_REG_CHANNEL_enabled; reg_conf.channel_Z_en = als31300::CONF_REG_CHANNEL_disabled; reg_conf.bandwidth = static_cast(BANDWIDTH_SELECT::bandwidth_7kHz); if (current_reg_conf != reg_conf) { [[maybe_unused]] auto ret = i2cWrite(als31300::CONF_REG, reg_conf); assert(ret); LOG_DEBUG("CONF wrote:\t%" PRIu32, static_cast(reg_conf)); i2cRead(als31300::CONF_REG, read_reg); LOG_DEBUG("CONF verify:\t%" PRIu32, static_cast(als31300::conf_reg(read_reg))); } else { LOG_DEBUG("CONF is fine, sparing a write"); } // INTERRUPTS register i2cRead(als31300::INT_REG, read_reg); const als31300::int_reg current_reg_int = read_reg; LOG_DEBUG("INT read:\t%" PRIu32, static_cast(current_reg_int)); als31300::int_reg reg_int = current_reg_int; reg_int.int_eeprom_en = als31300::INT_REG_INT_EEPROM_disable; reg_int.int_mode = als31300::INT_REG_INT_MODE_threshold; reg_int.int_threshold_signed = als31300::INT_REG_THRESHOLD_absolute; reg_int.int_X_en = als31300::INT_REG_INT_CHANNEL_disabled; reg_int.int_Y_en = als31300::INT_REG_INT_CHANNEL_disabled; reg_int.int_Z_en = als31300::INT_REG_INT_CHANNEL_disabled; reg_int.int_X_threshold = 1; reg_int.int_Y_threshold = 4; reg_int.int_Z_threshold = 0; if (current_reg_int != reg_int) { [[maybe_unused]] auto ret = i2cWrite(als31300::INT_REG, reg_int); assert(ret); LOG_DEBUG("INT wrote:\t%" PRIu32, static_cast(reg_int)); i2cRead(als31300::INT_REG, read_reg); LOG_DEBUG("INT verify:\t%" PRIu32, static_cast(als31300::int_reg(read_reg))); } else { LOG_DEBUG("INT is fine, sparing a write"); } // INTERRUPT PIN gpio = DriverGPIO::Create(static_cast(BoardDefinitions::MAGNETOMETER_GPIO), DriverGPIOParams{}); // INTERRUPT PIN gpio->ClearPortInterrupts(1 << static_cast(BoardDefinitions::MAGNETOMETER_IRQ)); gpio->ConfPin(DriverGPIOPinParams{.dir = DriverGPIOPinParams::Direction::Input, .irqMode = DriverGPIOPinParams::InterruptMode::IntFallingEdge, .defLogic = 0, .pin = static_cast(BoardDefinitions::MAGNETOMETER_IRQ)}); // NOTE: irq not yet enabled // this version uses timer to poll the sensor // the timer requests to read the magnetometer periodically if (timerHandle == nullptr) { timerHandle = xTimerCreate( "SliderTimer", pdMS_TO_TICKS(MAGNETOMETER_POLL_INTERVAL_MS), true, nullptr, TimerHandler); if (timerHandle == nullptr) { LOG_FATAL("Could not create the timer for Headset insertion/removal detection"); return kStatus_Fail; } } // POWER register i2cRead(als31300::PWR_REG, read_reg); const als31300::pwr_reg current_reg_pwr = read_reg; LOG_DEBUG("POWER read:\t%" PRIu32, static_cast(current_reg_pwr)); als31300::pwr_reg reg_pwr = current_reg_pwr; reg_pwr.I2C_loop_mode = als31300::PWR_REG_LOOP_MODE_single; // we don't want constant data flow reg_pwr.sleep = als31300::PWR_REG_SLEEP_MODE_active; reg_pwr.count_max_LP_mode = static_cast(LPDCM_INACTIVE_TIME::inactive_10ms); i2cWrite(als31300::PWR_REG, reg_pwr); LOG_DEBUG("POWER wrote:\t%" PRIu32, static_cast(reg_pwr)); xTimerStart(timerHandle, 1000); return kStatus_Success; } std::pair getMeasurement() { als31300::whole_reg_t read_reg; if (!i2cRead(als31300::MEASUREMENTS_MSB_REG, read_reg)) { LOG_DEBUG("magneto: CANNOT READ"); return std::make_pair(false, Measurements()); // todo: nullopt } // is there anything new ? als31300::measurements_MSB_reg reg_msb = read_reg; if (reg_msb.int_flag == true) { LOG_DEBUG("magneto: INT flag in register"); } if (reg_msb.new_data_flag != als31300::MEAS_REG_NEW_DATA_available) { return std::make_pair(false, Measurements()); } else { if (reg_msb.int_flag == true) { // clear INT flag if (!i2cWrite(als31300::MEASUREMENTS_MSB_REG, reg_msb)) { return std::make_pair(false, Measurements()); // todo: null opt } } Measurements meas; i2cRead(als31300::MEASUREMENTS_LSB_REG, read_reg); als31300::measurements_LSB_reg reg_lsb = read_reg; meas.X = als31300::measurement_sign_convert(reg_msb.X_MSB << 4 | reg_lsb.X_LSB); meas.Y = als31300::measurement_sign_convert(reg_msb.Y_MSB << 4 | reg_lsb.Z_LSB); meas.Z = als31300::measurement_sign_convert(reg_msb.Z_MSB << 4 | reg_lsb.Z_LSB); return std::pair(true, meas); } } bool setActive(als31300::PWR_REG_SLEEP_MODE sleep_mode) { // POWER register als31300::whole_reg_t read_reg; if (!i2cRead(als31300::PWR_REG, read_reg)) { return false; } als31300::pwr_reg reg_pwr = read_reg; reg_pwr.sleep = sleep_mode; if (!i2cWrite(als31300::PWR_REG, reg_pwr)) { return false; } if (sleep_mode == als31300::PWR_REG_SLEEP_MODE::active || sleep_mode == als31300::PWR_REG_SLEEP_MODE::periodic_active) { vTaskDelay(pdMS_TO_TICKS(als31300::PWR_ON_DELAY_MS)); // give it some time to wake up } return true; } bool isPresent(void) { uint8_t buf; addr.subAddress = 0x00; auto read = i2c->Read(addr, &buf, 1); return read == 1; } bsp::KeyCodes parse(const Measurements &measurements) { // X is tri-stable const auto X_lower_boundary = -150; const auto X_upper_boundary = 150; const auto X_lower_threshold = -65; const auto X_upper_threshold = 60; // Y is bi-stable const auto Y_threshold = -175; // Y is used only for proofing X, so no strict thresholds // Z is useless if (measurements.X > X_lower_boundary && measurements.X < X_upper_boundary) { if (measurements.X < X_lower_threshold) { if (measurements.Y > Y_threshold) { return bsp::KeyCodes::SSwitchDown; } } else if (measurements.X > X_upper_threshold) { if (measurements.Y > Y_threshold) { return bsp::KeyCodes::SSwitchUp; } } else { if (measurements.Y < Y_threshold) { return bsp::KeyCodes::SSwitchMid; } } } return bsp::KeyCodes::Undefined; } void resetCurrentParsedValue() { current_parsed = bsp::KeyCodes::Undefined; } std::optional WorkerEventHandler() { // try to get new data from active magneto setActive(als31300::PWR_REG_SLEEP_MODE::active); auto [new_data, measurement] = getMeasurement(); setActive(als31300::PWR_REG_SLEEP_MODE::sleep); if (new_data) { auto incoming_parsed = parse(measurement); if (incoming_parsed != bsp::KeyCodes::Undefined and incoming_parsed != current_parsed) { current_parsed = incoming_parsed; return current_parsed; } } return std::nullopt; } BaseType_t IRQHandler() { gpio->DisableInterrupt(1 << static_cast(BoardDefinitions::MAGNETOMETER_IRQ)); BaseType_t xHigherPriorityTaskWoken = pdFALSE; if (qHandleIrq != NULL) { uint8_t val = 0x01; xQueueSendFromISR(qHandleIrq, &val, &xHigherPriorityTaskWoken); } return xHigherPriorityTaskWoken; } bsp::Board GetBoard(void) { if (isPresent()) { return bsp::Board::T4; } return bsp::Board::T3; } void enableIRQ() { gpio->EnableInterrupt(1 << static_cast(BoardDefinitions::MAGNETOMETER_IRQ)); } } // namespace magnetometer } // namespace bsp