/*
Copyright 2020 Mitch Lustig
Copyright 2026 Benjamin Vedder benjamin@vedder.se
This file is part of the VESC firmware.
The VESC firmware is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
The VESC firmware is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "lsm6ds3.h"
#include "terminal.h"
#include "commands.h"
#include "utils_math.h"
#include
#include
static thread_t *lsm6ds3_thread_ref = NULL;
static i2c_bb_state *m_i2c_bb;
static spi_bb_state *m_spi_bb;
static SPIDriver *m_hwspi_dev;
static volatile uint16_t lsm6ds3_addr;
static int rate_hz = 1000;
static IMU_FILTER filter;
static const uint8_t spi_tx_0_12[12] = {0};
static void terminal_read_reg(int argc, const char **argv);
static bool read_reg(uint8_t reg, uint8_t *res);
static bool write_reg(uint8_t reg, uint8_t value);
static bool read_gyro_accel(uint8_t *res);
static THD_FUNCTION(lsm6ds3_thread, arg);
// Function pointers
static void(*read_callback)(float *accel, float *gyro, float *mag) = 0;
void lsm6ds3_set_rate_hz(int hz) {
rate_hz = hz;
}
void lsm6ds3_set_filter(IMU_FILTER f) {
filter = f;
}
void lsm6ds3_init(i2c_bb_state *i2c_state, spi_bb_state *spi_state, SPIDriver *spi_hw,
stkalign_t *work_area, size_t work_area_size) {
read_callback = 0;
m_i2c_bb = i2c_state;
m_spi_bb = spi_state;
m_hwspi_dev = spi_hw;
uint8_t rxb[1];
lsm6ds3_addr = LSM6DS3_ACC_GYRO_ADDR_A;
bool res = read_reg(LSM6DS3_ACC_GYRO_WHO_AM_I_REG, rxb);
if (!res || (rxb[0] != 0x69 && rxb[0] != 0x6A && rxb[0] != 0x6C)) {
commands_printf("LSM6DS3 Address A failed, trying B (rx: %d)", rxb[0]);
lsm6ds3_addr = LSM6DS3_ACC_GYRO_ADDR_B;
res = read_reg(LSM6DS3_ACC_GYRO_WHO_AM_I_REG, rxb);
if (!res || (rxb[0] != 0x69 && rxb[0] != 0x6A && rxb[0] != 0x6C)) {
commands_printf("LSM6DS3 Address B failed (rx: %d)", rxb[0]);
return;
}
}
bool is_trc = false;
if (rxb[0] == 0x6A){
is_trc = true;
}
// Accelerometer resolution and data rate
uint8_t regv = LSM6DS3_ACC_GYRO_FS_XL_16g;
regv |= LSM6DS3_ACC_GYRO_ODR_XL_6660Hz;
// Accelerometer filtering
#define LSM6DS3TRC_BW0_XL 0x1
#define LSM6DS3TRC_LPF1_BW_SEL 0x2
if (is_trc) {
// Always use accelerometer analog low-pass at 400Hz
regv |= LSM6DS3TRC_BW0_XL;
} else if (rate_hz >= 208 && filter >= IMU_FILTER_MEDIUM) {
// Filter at ODR/4 for MEDIUM and ODR/8 for HIGH
// This filter also needs to be enabled in CTRL4_C
int scaled_rate = filter == IMU_FILTER_HIGH ? rate_hz / 2 : rate_hz;
if (scaled_rate <= 208) {
regv |= LSM6DS3_ACC_GYRO_BW_XL_50Hz;
} else if (scaled_rate <= 416) {
regv |= LSM6DS3_ACC_GYRO_BW_XL_100Hz;
} else if (scaled_rate <= 833) {
regv |= LSM6DS3_ACC_GYRO_BW_XL_200Hz;
}
}
res = write_reg(LSM6DS3_ACC_GYRO_CTRL1_XL, regv);
if (!res){
commands_printf("LSM6DS3 Accel Config FAILED");
return;
}
// Extra accelerometer filtering for TRC variant
if (is_trc) {
#define LSM6DS3TRC_LPF2_XL_EN 0x80
#define LSM6DS3TRC_HPCF_XL_ODR9 0x40
#define LSM6DS3TRC_HPCF_XL_ODR50 0x00
#define LSM6DS3TRC_HPCF_XL_ODR100 0x20
regv = 0;
if (filter == IMU_FILTER_MEDIUM) {
regv |= LSM6DS3TRC_LPF2_XL_EN | LSM6DS3TRC_HPCF_XL_ODR50;
} else if (filter == IMU_FILTER_HIGH) {
regv |= LSM6DS3TRC_LPF2_XL_EN | LSM6DS3TRC_HPCF_XL_ODR100;
}
res = write_reg(LSM6DS3_ACC_GYRO_CTRL8_XL, regv);
if (!res) {
commands_printf("LSM6DS3 Accel Filter Config FAILED");
return;
}
}
// Gyro resolution and data rate
regv = LSM6DS3_ACC_GYRO_FS_G_2000dps;
if (is_trc) {
regv |= LSM6DS3TRC_ACC_GYRO_ODR_G_6660Hz;
} else {
// On non-TRC there is no dedicated configurable gyro filter, the filtering
// seems to depend on the actual ODR, so we can't oversample it.
if (rate_hz <= 13) {
regv |= LSM6DS3_ACC_GYRO_ODR_G_13Hz;
} else if (rate_hz <= 26) {
regv |= LSM6DS3_ACC_GYRO_ODR_G_26Hz;
} else if (rate_hz <= 52) {
regv |= LSM6DS3_ACC_GYRO_ODR_G_52Hz;
} else if (rate_hz <= 104) {
regv |= LSM6DS3_ACC_GYRO_ODR_G_104Hz;
} else if (rate_hz <= 208) {
regv |= LSM6DS3_ACC_GYRO_ODR_G_208Hz;
} else if (rate_hz <= 416) {
regv |= LSM6DS3_ACC_GYRO_ODR_G_416Hz;
} else if (rate_hz <= 833) {
regv |= LSM6DS3_ACC_GYRO_ODR_G_833Hz;
} else {
regv |= LSM6DS3_ACC_GYRO_ODR_G_1660Hz;
}
}
res = write_reg(LSM6DS3_ACC_GYRO_CTRL2_G, regv);
if (!res){
commands_printf("LSM6DS3 Gyro Config FAILED");
return;
}
// Extra gyro filtering for TRC variant
if (is_trc) {
#define LSM6DS3TRC_FTYPE_L 0x00
#define LSM6DS3TRC_FTYPE_M 0x01
#define LSM6DS3TRC_FTYPE_H 0x10
regv = 0;
if (filter == IMU_FILTER_LOW) {
regv |= LSM6DS3TRC_FTYPE_L;
} else if (filter == IMU_FILTER_MEDIUM) {
regv |= LSM6DS3TRC_FTYPE_M;
} else if (filter == IMU_FILTER_HIGH) {
regv |= LSM6DS3TRC_FTYPE_H;
}
res = write_reg(LSM6DS3_ACC_GYRO_CTRL6_G, regv);
if (!res){
commands_printf("LSM6DS3 Gyro Filter FAILED");
return;
}
}
// Miscellaneous filtering configuration in CTRL4_C
// TRC Variant CTRL4 register is very different from other variants
regv = 0;
if (is_trc) {
// Enable gyroscope digital low-pass filter LPF1
regv = LSM6DS3_ACC_GYRO_LPF1_SEL_G_ENABLED;
} else if (rate_hz >= 208 && filter >= IMU_FILTER_MEDIUM) {
// Standard LSM6DS3 only: Set XL anti-aliasing filter to be manually configured
regv = LSM6DS3_ACC_GYRO_BW_SCAL_ODR_ENABLED;
}
res = write_reg(LSM6DS3_ACC_GYRO_CTRL4_C, regv);
if (!res) {
commands_printf("LSM6DS3 Misc Filter Config FAILED");
return;
}
// Configure block update and register auto-increment
regv = LSM6DS3_ACC_GYRO_BDU_BLOCK_UPDATE | LSM6DS3_ACC_GYRO_IF_INC_ENABLED;
res = write_reg(LSM6DS3_ACC_GYRO_CTRL3_C, regv);
if (!res) {
commands_printf("LSM6DS3 BDU Config FAILED");
return;
}
terminal_register_command_callback(
"lsm_read_reg",
"Read register of the LSM6DS3",
"[reg]",
terminal_read_reg);
lsm6ds3_thread_ref = chThdCreateStatic(work_area, work_area_size, NORMALPRIO, lsm6ds3_thread, NULL);
}
void lsm6ds3_stop(void) {
if (lsm6ds3_thread_ref != NULL){
chThdTerminate(lsm6ds3_thread_ref);
chThdWait(lsm6ds3_thread_ref);
}
lsm6ds3_thread_ref = NULL;
terminal_unregister_callback(terminal_read_reg);
}
void lsm6ds3_set_read_callback(void(*func)(float *accel, float *gyro, float *mag)) {
read_callback = func;
}
static bool read_reg(uint8_t reg, uint8_t *res) {
bool ok = false;
if (m_i2c_bb) {
uint8_t txb[1];
uint8_t rxb[1];
txb[0] = reg;
ok = i2c_bb_tx_rx(m_i2c_bb, lsm6ds3_addr, txb, 1, rxb, 1);
if (ok) {
*res = rxb[0];
} else {
*res = 0;
}
} else if (m_spi_bb) {
chMtxLock(&(m_spi_bb->mutex));
spi_bb_begin(m_spi_bb);
spi_bb_exchange_8_mode_3(m_spi_bb, reg | 0x80);
spi_bb_delay();
*res = spi_bb_exchange_8_mode_3(m_spi_bb, 0);
spi_bb_end(m_spi_bb);
chMtxUnlock(&(m_spi_bb->mutex));
ok = true;
} else if (m_hwspi_dev) {
// Use polled exchange here as the DMA-version seems to freeze
// the CAN process thread for some reason. Performance does not
// matter during config.
spiAcquireBus(m_hwspi_dev);
spiSelect(m_hwspi_dev);
spiPolledExchange(m_hwspi_dev, reg | 0x80);
spi_bb_delay();
*res = spiPolledExchange(m_hwspi_dev, 0);
spiUnselect(m_hwspi_dev);
spiReleaseBus(m_hwspi_dev);
ok = true;
}
return ok;
}
static bool write_reg(uint8_t reg, uint8_t value) {
bool ok = false;
if (m_i2c_bb) {
uint8_t txb[2];
uint8_t rxb[1];
txb[0] = reg;
txb[1] = value;
ok = i2c_bb_tx_rx(m_i2c_bb, lsm6ds3_addr, txb, 2, rxb, 1);
} else if (m_spi_bb) {
chMtxLock(&(m_spi_bb->mutex));
spi_bb_begin(m_spi_bb);
spi_bb_exchange_8_mode_3(m_spi_bb, reg & 0x7F);
spi_bb_delay();
spi_bb_exchange_8_mode_3(m_spi_bb, value);
spi_bb_end(m_spi_bb);
chMtxUnlock(&(m_spi_bb->mutex));
ok = true;
} else if (m_hwspi_dev) {
// Use polled exchange here as the DMA-version seems to freeze
// the CAN process thread for some reason. Performance does not
// matter during config.
spiAcquireBus(m_hwspi_dev);
spiSelect(m_hwspi_dev);
spiPolledExchange(m_hwspi_dev, reg & 0x7f);
spi_bb_delay();
spiPolledExchange(m_hwspi_dev, value);
spiUnselect(m_hwspi_dev);
spiReleaseBus(m_hwspi_dev);
ok = true;
}
return ok;
}
static bool read_gyro_accel(uint8_t *res) {
bool ok = false;
if (m_i2c_bb) {
uint8_t txb[1];
txb[0] = LSM6DS3_ACC_GYRO_OUTX_L_G;
ok = i2c_bb_tx_rx(m_i2c_bb, lsm6ds3_addr, txb, 1, res, 12);
if (!ok) {
memset(res, 0, 12);
}
} else if (m_spi_bb) {
chMtxLock(&(m_spi_bb->mutex));
spi_bb_begin(m_spi_bb);
spi_bb_exchange_8_mode_3(m_spi_bb, LSM6DS3_ACC_GYRO_OUTX_L_G | 0x80);
spi_bb_delay_short();
for (int i = 0;i < 12;i++) {
res[i] = spi_bb_exchange_8_mode_3(m_spi_bb, 0);
}
spi_bb_end(m_spi_bb);
chMtxUnlock(&(m_spi_bb->mutex));
ok = true;
} else if (m_hwspi_dev) {
uint8_t txb[1];
uint8_t rxb[1];
spiAcquireBus(m_hwspi_dev);
spiSelect(m_hwspi_dev);
txb[0] = LSM6DS3_ACC_GYRO_OUTX_L_G | 0x80;
spiExchange(m_hwspi_dev, 1, txb, rxb);
spi_bb_delay_short();
spiExchange(m_hwspi_dev, 12, spi_tx_0_12, res);
spiUnselect(m_hwspi_dev);
spiReleaseBus(m_hwspi_dev);
ok = true;
}
return ok;
}
static void terminal_read_reg(int argc, const char **argv) {
if (argc == 2) {
int reg = -1;
sscanf(argv[1], "%d", ®);
if (reg >= 0) {
uint8_t res = 0;
bool ok = read_reg(reg, &res);
if (ok) {
char bl[9];
utils_byte_to_binary(res & 0xFF, bl);
commands_printf("Reg: %s", bl);
} else {
commands_printf("Read failed\n");
}
} else {
commands_printf("Invalid argument(s)\n");
}
} else {
commands_printf("This command requires one argument\n");
}
}
static THD_FUNCTION(lsm6ds3_thread, arg) {
(void)arg;
chRegSetThreadName("LSM6DS3");
// One tick less to ensure the frequency is at least the configured one or a bit higher (US2ST rounds up)
const systime_t interval = US2ST(1000000 / rate_hz) - 1;
while (!chThdShouldTerminateX()) {
systime_t start_time = chVTGetSystemTimeX();
uint8_t rxb[12];
bool res = read_gyro_accel(rxb);
if (res) {
// Parse 6 axis values
float gx = (float)((int16_t)((uint16_t)rxb[1] << 8) + rxb[0]) * 4.375 * (2000 / 125) / 1000;
float gy = (float)((int16_t)((uint16_t)rxb[3] << 8) + rxb[2]) * 4.375 * (2000 / 125) / 1000;
float gz = (float)((int16_t)((uint16_t)rxb[5] << 8) + rxb[4]) * 4.375 * (2000 / 125) / 1000;
float ax = (float)((int16_t)((uint16_t)rxb[7] << 8) + rxb[6]) * 0.061 * (16 >> 1) / 1000;
float ay = (float)((int16_t)((uint16_t)rxb[9] << 8) + rxb[8]) * 0.061 * (16 >> 1) / 1000;
float az = (float)((int16_t)((uint16_t)rxb[11] << 8) + rxb[10]) * 0.061 * (16 >> 1) / 1000;
if (res && read_callback) {
float tmp_accel[3] = {ax,ay,az}, tmp_gyro[3] = {gx,gy,gz}, tmp_mag[3] = {1,2,3};
read_callback(tmp_accel, tmp_gyro, tmp_mag);
}
} else {
chThdSleep(1);
continue;
}
systime_t sleep_ticks = 1;
systime_t remaining_sleep_time = start_time + interval - chVTGetSystemTimeX();
if (remaining_sleep_time > 0 && remaining_sleep_time <= interval) {
sleep_ticks = remaining_sleep_time;
}
chThdSleep(sleep_ticks);
}
}