mirror of
https://github.com/google/pebble.git
synced 2025-05-21 10:55:04 +00:00
390 lines
11 KiB
C
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, ®);
|
|
|
|
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;
|
|
}
|