pebble/src/fw/drivers/imu/lis3dh/config.c
Josh Soref 9fd89172fc spelling: inertial
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2025-01-29 00:03:24 -05:00

390 lines
11 KiB
C

/*
* Copyright 2024 Google LLC
*
* 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 "lis3dh.h"
#include "registers.h"
#include "drivers/i2c.h"
#include "drivers/legacy/accel.h"
#include "system/logging.h"
#include "util/math.h"
#include "util/size.h"
//! @file accel_config.c
//! procedures for dealing with I2C communication with the accel
struct I2CCommand {
uint8_t register_address;
uint8_t value;
};
//
// Boiler plate functions for talking over i2c
//
static uint8_t prv_read_reg(uint8_t address) {
uint8_t reg;
lis3dh_read(address, 1, &reg);
return (reg);
}
static bool prv_write_reg(uint8_t address, uint8_t value) {
return (lis3dh_write(address, 1, &value));
}
static bool send_i2c_commands(struct I2CCommand* commands, int num_commands) {
for (int i = 0; i < num_commands; ++i) {
bool result = prv_write_reg(commands[i].register_address, commands[i].value);
if (!result) {
return false;
}
}
return true;
}
void lis3dh_enable_fifo(void) {
uint8_t ctrl_reg5 = prv_read_reg(LIS3DH_CTRL_REG5);
ctrl_reg5 |= FIFO_EN;
prv_write_reg(LIS3DH_CTRL_REG5, ctrl_reg5);
}
void lis3dh_disable_fifo(void) {
uint8_t ctrl_reg5 = prv_read_reg(LIS3DH_CTRL_REG5);
ctrl_reg5 &= ~FIFO_EN;
prv_write_reg(LIS3DH_CTRL_REG5, ctrl_reg5);
}
bool lis3dh_is_fifo_enabled(void) {
uint8_t ctrl_reg5 = prv_read_reg(LIS3DH_CTRL_REG5);
return (ctrl_reg5 & FIFO_EN);
}
void lis3dh_disable_click(void) {
uint8_t ctrl_reg3 = prv_read_reg(LIS3DH_CTRL_REG3);
ctrl_reg3 &= ~I1_CLICK;
prv_write_reg(LIS3DH_CTRL_REG3, ctrl_reg3);
}
void lis3dh_enable_click(void) {
uint8_t ctrl_reg3 = prv_read_reg(LIS3DH_CTRL_REG3);
ctrl_reg3 |= I1_CLICK;
prv_write_reg(LIS3DH_CTRL_REG3, ctrl_reg3);
}
//
// Accel config Getter/Setters
//
void lis3dh_set_interrupt_axis(AccelAxisType axis, bool double_click) {
// get the current state of the registers
uint8_t reg_1 = prv_read_reg(LIS3DH_CTRL_REG1);
// clear the axis-enable bits
reg_1 = reg_1 & ~(0x1 | 0x2 | 0x4);
uint8_t click_cfg = 0;
switch (axis) {
case ACCEL_AXIS_X:
reg_1 |= 0x01;
click_cfg = 0x01;
break;
case ACCEL_AXIS_Y:
reg_1 |= 0x02;
click_cfg = 0x04;
break;
case ACCEL_AXIS_Z:
reg_1 |= 0x04;
click_cfg = 0x10;
break;
default:
PBL_LOG(LOG_LEVEL_ERROR, "Unknown axis");
}
if (double_click) {
click_cfg <<= 1;
}
if ((!prv_write_reg(LIS3DH_CTRL_REG1, reg_1))
|| (!prv_write_reg(LIS3DH_CLICK_CFG, click_cfg))) {
PBL_LOG(LOG_LEVEL_ERROR, "Failed to write axis selection");
}
}
uint8_t lis3dh_get_click_window() {
return prv_read_reg(LIS3DH_TIME_WINDOW);
}
void lis3dh_set_click_window(uint8_t window) {
if (!prv_write_reg(LIS3DH_TIME_WINDOW, MIN(window, LIS3DH_MAX_CLICK_WINDOW))) {
PBL_LOG(LOG_LEVEL_ERROR, "Failed to write click latency");
}
}
uint8_t lis3dh_get_click_latency() {
return prv_read_reg(LIS3DH_TIME_LATENCY);
}
void lis3dh_set_click_latency(uint8_t latency) {
if (!prv_write_reg(LIS3DH_TIME_LATENCY, MIN(latency, LIS3DH_MAX_CLICK_LATENCY))) {
PBL_LOG(LOG_LEVEL_ERROR, "Failed to write click latency");
}
}
uint8_t lis3dh_get_interrupt_threshold() {
return prv_read_reg(LIS3DH_CLICK_THS);
}
void lis3dh_set_interrupt_threshold(uint8_t threshold) {
if (!prv_write_reg(LIS3DH_CLICK_THS, MIN(threshold, LIS3DH_MAX_THRESHOLD))) {
PBL_LOG(LOG_LEVEL_ERROR, "Failed to set interrupt threshold");
}
}
uint8_t lis3dh_get_interrupt_time_limit() {
return prv_read_reg(LIS3DH_TIME_LIMIT);
}
void lis3dh_set_interrupt_time_limit(uint8_t time_limit) {
if (!prv_write_reg(LIS3DH_TIME_LIMIT, MIN(time_limit, LIS3DH_MAX_TIME_LIMIT))) {
PBL_LOG(LOG_LEVEL_ERROR, "Failed to set interrupt time limit");
}
}
bool lis3dh_set_fifo_wtm(uint8_t wtm) {
uint8_t fifo_ctrl_reg = prv_read_reg(LIS3DH_FIFO_CTRL_REG);
fifo_ctrl_reg &= ~THR_MASK;
fifo_ctrl_reg |= (wtm & THR_MASK);
return (prv_write_reg(LIS3DH_FIFO_CTRL_REG, fifo_ctrl_reg));
}
uint8_t lis3dh_get_fifo_wtm(void) {
uint8_t fifo_ctrl_reg = prv_read_reg(LIS3DH_FIFO_CTRL_REG);
return (fifo_ctrl_reg & THR_MASK);
}
AccelSamplingRate accel_get_sampling_rate(void) {
uint8_t odr = ODR_MASK & prv_read_reg(LIS3DH_CTRL_REG1);
if (odr == (ODR2 | ODR0)) {
return (ACCEL_SAMPLING_100HZ);
} else if (odr == ODR2) {
return (ACCEL_SAMPLING_50HZ);
} else if (odr == (ODR1 | ODR0)) {
return (ACCEL_SAMPLING_25HZ);
} else if (odr == (ODR1)) {
return (ACCEL_SAMPLING_10HZ);
} else {
PBL_LOG(LOG_LEVEL_ERROR, "Unrecognized ODR value %d", odr);
return (0);
}
}
bool accel_set_sampling_rate(AccelSamplingRate rate) {
uint8_t odr;
switch (rate) {
case ACCEL_SAMPLING_100HZ:
odr = ODR2 | ODR0;
break;
case ACCEL_SAMPLING_50HZ:
odr = ODR2;
break;
case ACCEL_SAMPLING_25HZ:
odr = ODR1 | ODR0;
break;
case ACCEL_SAMPLING_10HZ:
odr = ODR1;
break;
default:
PBL_LOG(LOG_LEVEL_ERROR, "Unsupported sampling rate %d", rate);
return (false);
}
uint8_t ctrl_reg_1 = prv_read_reg(LIS3DH_CTRL_REG1);
ctrl_reg_1 &= ~ODR_MASK;
ctrl_reg_1 |= (odr & ODR_MASK);
//TODO: fix hack below (enabling axes after lis3dh_power_down)
ctrl_reg_1 |= (Xen | Yen | Zen); //enable x, y and z axis
bool res = prv_write_reg(LIS3DH_CTRL_REG1, ctrl_reg_1);
// Update the click limit based on sampling frequency
uint8_t time_limit = rate * LIS3DH_TIME_LIMIT_MULT / LIS3DH_TIME_LIMIT_DIV;
lis3dh_set_interrupt_time_limit(time_limit);
PBL_LOG(LOG_LEVEL_DEBUG, "setting click time limit to 0x%x",
lis3dh_get_interrupt_time_limit());
// Update click latency
uint8_t time_latency = rate * LIS3DH_TIME_LATENCY_MULT / LIS3DH_TIME_LATENCY_DIV;
lis3dh_set_click_latency(time_latency);
PBL_LOG(LOG_LEVEL_DEBUG, "setting click time latency to 0x%x",
lis3dh_get_click_latency());
// Update click window
uint32_t time_window = rate * LIS3DH_TIME_WINDOW_MULT / LIS3DH_TIME_WINDOW_DIV;
time_window = MIN(time_window, 0xff);
lis3dh_set_click_window(time_window);
PBL_LOG(LOG_LEVEL_DEBUG, "setting click time window to 0x%x",
lis3dh_get_click_window());
return (res);
}
Lis3dhScale accel_get_scale(void) {
uint8_t fs = FS_MASK & prv_read_reg(LIS3DH_CTRL_REG4);
if (fs == (FS0 | FS1)) {
return (LIS3DH_SCALE_16G);
} else if (fs == FS1) {
return (LIS3DH_SCALE_8G);
} else if (fs == FS0) {
return (LIS3DH_SCALE_4G);
} else if (fs == 0) {
return (LIS3DH_SCALE_2G);
} else {
PBL_LOG(LOG_LEVEL_ERROR, "Unrecognized FS value %d", fs);
return (LIS3DH_SCALE_UNKNOWN);
}
}
bool accel_set_scale(Lis3dhScale scale) {
uint8_t fs;
switch (scale) {
case LIS3DH_SCALE_16G:
fs = (FS0 | FS1);
break;
case LIS3DH_SCALE_8G:
fs = FS1;
break;
case LIS3DH_SCALE_4G:
fs = FS0;
break;
case LIS3DH_SCALE_2G:
fs = 0;
break;
default:
PBL_LOG(LOG_LEVEL_ERROR, "Unsupported scale %d", scale);
return (false);
}
uint8_t ctrl_reg_4 = prv_read_reg(LIS3DH_CTRL_REG4);
ctrl_reg_4 &= ~FS_MASK;
ctrl_reg_4 |= (fs & FS_MASK);
bool res = prv_write_reg(LIS3DH_CTRL_REG4, ctrl_reg_4);
lis3dh_set_interrupt_threshold(scale * LIS3DH_THRESHOLD_MULT
/ LIS3DH_THRESHOLD_DIV);
PBL_LOG(LOG_LEVEL_DEBUG, "setting click threshold to 0x%x",
lis3dh_get_interrupt_threshold());
return (res);
}
bool lis3dh_set_fifo_mode(uint8_t mode) {
uint8_t fifo_ctrl_reg = prv_read_reg(LIS3DH_FIFO_CTRL_REG);
fifo_ctrl_reg &= ~MODE_MASK;
fifo_ctrl_reg |= (mode & MODE_MASK);
return (prv_write_reg(LIS3DH_FIFO_CTRL_REG, fifo_ctrl_reg));
}
uint8_t lis3dh_get_fifo_mode(void) {
uint8_t fifo_ctrl_reg = prv_read_reg(LIS3DH_FIFO_CTRL_REG);
return (fifo_ctrl_reg & MODE_MASK);
}
//! Configure the accel to run "Self Test 0". See S3.2.2 of the accel datasheet for more information
bool lis3dh_enter_self_test_mode(SelfTestMode mode) {
uint8_t reg4 = 0x8;
switch (mode) {
case SELF_TEST_MODE_ONE:
reg4 |= 0x2;
break;
case SELF_TEST_MODE_TWO:
reg4 |= (0x2 | 0x4);
break;
default:
break;
}
struct I2CCommand test_mode_config[] = {
{ LIS3DH_CTRL_REG1, 0x9f },
{ LIS3DH_CTRL_REG3, 0x00 },
{ LIS3DH_CTRL_REG4, reg4 }
};
return send_i2c_commands(test_mode_config, ARRAY_LENGTH(test_mode_config));
}
void lis3dh_exit_self_test_mode(void) {
lis3dh_config_set_defaults();
}
//
// Boot-time config
//
//! Ask the accel for a 8-bit value that's programmed into the IC at the
//! factory. Useful as a sanity check to make sure everything came up properly.
bool lis3dh_sanity_check(void) {
uint8_t whoami = prv_read_reg(LIS3DH_WHO_AM_I);
PBL_LOG(LOG_LEVEL_DEBUG, "Read accel whomai byte 0x%x, expecting 0x%x", whoami, LIS3DH_WHOAMI_BYTE);
return (whoami == LIS3DH_WHOAMI_BYTE);
}
bool lis3dh_config_set_defaults() {
// Follow the startup sequence from AN3308
struct I2CCommand accel_init_commands[] = {
{ LIS3DH_CTRL_REG1, (ODR1 | ODR0 | Zen | Yen | Xen) }, // 25MHz, Enable X,Y,Z Axes
{ LIS3DH_CTRL_REG2, 0x00 },
{ LIS3DH_CTRL_REG3, I1_WTM }, // FIFO Watermark on INT1
{ LIS3DH_CTRL_REG4, (BDU | FS0 | HR) }, // Block Read, +/- 4g sensitivity
{ LIS3DH_CTRL_REG6, I2_CLICK }, // Click on INT2
{ LIS3DH_INT1_THS, 0x20 }, // inertial threshold (MAX 0x7f)
{ LIS3DH_INT1_DURATION, 0x10 }, // interrupt duration (units of 1/(update frequency) [See CTRL_REG1])
{ LIS3DH_INT1_CFG, 0x00 }, // no inertial interrupts
// click threshold (MAX 0x7f)
{ LIS3DH_CLICK_THS, LIS3DH_SCALE_4G * LIS3DH_THRESHOLD_MULT
/ LIS3DH_THRESHOLD_DIV },
// click time limit (units of 1/(update frequency) [See CTRL_REG1])
{ LIS3DH_TIME_LIMIT, ACCEL_DEFAULT_SAMPLING_RATE * LIS3DH_TIME_LIMIT_MULT
/ LIS3DH_TIME_LIMIT_DIV},
{ LIS3DH_CLICK_CFG, (XS | YS | ZS) }, // single click detection on the X axis
{ LIS3DH_FIFO_CTRL_REG, MODE_BYPASS | 0x19 }, // BYPASS MODE and 25 samples per interrupt
// time latency, ie "debounce time" after the first of a double click
// (units of 1/(update frequency) [See CTRL_REG1])
{ LIS3DH_TIME_LATENCY, ACCEL_DEFAULT_SAMPLING_RATE * LIS3DH_TIME_LATENCY_MULT
/ LIS3DH_TIME_LATENCY_DIV },
// max time allowed between clicks for a double click (end to start)
// (units of 1/(update frequency) [See CTRL_REG1])
{ LIS3DH_TIME_WINDOW, ACCEL_DEFAULT_SAMPLING_RATE * LIS3DH_TIME_WINDOW_MULT
/ LIS3DH_TIME_WINDOW_DIV}
};
if (!send_i2c_commands(accel_init_commands, ARRAY_LENGTH(accel_init_commands))) {
accel_stop();
PBL_LOG(LOG_LEVEL_WARNING, "Failed to initialize accelerometer");
return false;
}
return true;
}