diff --git a/config/sample-duet3-1lc.cfg b/config/sample-duet3-1lc.cfg index 298c6fafb..ef7e40428 100644 --- a/config/sample-duet3-1lc.cfg +++ b/config/sample-duet3-1lc.cfg @@ -77,5 +77,14 @@ heater_temp: 50.0 pin: toolboard:PA9 z_offset: 20 +[samd_sercom sercom_i2c] +sercom: sercom1 +tx_pin: toolboard:PA16 +clk_pin: toolboard:PA17 + +[lis3dh] +i2c_mcu: toolboard +i2c_bus: sercom1 + [mcu toolboard] canbus_uuid: 4b194673554e diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 811f0b7a8..06a6c6d99 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -2068,8 +2068,40 @@ Support for LIS2DW accelerometers. ``` [lis2dw] -cs_pin: -# The SPI enable pin for the sensor. This parameter must be provided. +#cs_pin: +# The SPI enable pin for the sensor. This parameter must be provided +# if using SPI. +#spi_speed: 5000000 +# The SPI speed (in hz) to use when communicating with the chip. +# The default is 5000000. +#spi_bus: +#spi_software_sclk_pin: +#spi_software_mosi_pin: +#spi_software_miso_pin: +# See the "common SPI settings" section for a description of the +# above parameters. +#i2c_address: +# Default is 25 (0x19). If SA0 is high, it would be 24 (0x18) instead. +#i2c_mcu: +#i2c_bus: +#i2c_software_scl_pin: +#i2c_software_sda_pin: +#i2c_speed: 400000 +# See the "common I2C settings" section for a description of the +# above parameters. The default "i2c_speed" is 400000. +#axes_map: x, y, z +# See the "adxl345" section for information on this parameter. +``` + +### [lis3dh] + +Support for LIS3DH accelerometers. + +``` +[lis3dh] +#cs_pin: +# The SPI enable pin for the sensor. This parameter must be provided +# if using SPI. #spi_speed: 5000000 # The SPI speed (in hz) to use when communicating with the chip. # The default is 5000000. @@ -2079,6 +2111,15 @@ cs_pin: #spi_software_miso_pin: # See the "common SPI settings" section for a description of the # above parameters. +#i2c_address: +# Default is 25 (0x19). If SA0 is high, it would be 24 (0x18) instead. +#i2c_mcu: +#i2c_bus: +#i2c_software_scl_pin: +#i2c_software_sda_pin: +#i2c_speed: 400000 +# See the "common I2C settings" section for a description of the +# above parameters. The default "i2c_speed" is 400000. #axes_map: x, y, z # See the "adxl345" section for information on this parameter. ``` diff --git a/klippy/extras/lis2dw.py b/klippy/extras/lis2dw.py index 8392c54cb..da9468e58 100644 --- a/klippy/extras/lis2dw.py +++ b/klippy/extras/lis2dw.py @@ -5,13 +5,16 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import logging -from . import bus, adxl345, bulk_sensor + +from . import adxl345, bulk_sensor, bus # LIS2DW registers REG_LIS2DW_WHO_AM_I_ADDR = 0x0F REG_LIS2DW_CTRL_REG1_ADDR = 0x20 REG_LIS2DW_CTRL_REG2_ADDR = 0x21 REG_LIS2DW_CTRL_REG3_ADDR = 0x22 +REG_LIS2DW_CTRL_REG4_ADDR = 0x23 +REG_LIS2DW_CTRL_REG5_ADDR = 0x24 REG_LIS2DW_CTRL_REG6_ADDR = 0x25 REG_LIS2DW_STATUS_REG_ADDR = 0x27 REG_LIS2DW_OUT_XL_ADDR = 0x28 @@ -26,27 +29,61 @@ # REG_MOD_MULTI = 0x40 LIS2DW_DEV_ID = 0x44 +LIS3DH_DEV_ID = 0x33 + +LIS_I2C_ADDR = 0x19 +# Right shift for left justified registers. FREEFALL_ACCEL = 9.80665 -SCALE = FREEFALL_ACCEL * 1.952 / 4 +LIS2DW_SCALE = FREEFALL_ACCEL * 1.952 / 4 +LIS3DH_SCALE = FREEFALL_ACCEL * 3.906 / 16 BATCH_UPDATES = 0.100 +# "Enums" that should be compatible with all python versions + +LIS2DW_TYPE = "LIS2DW" +LIS3DH_TYPE = "LIS3DH" + +SPI_SERIAL_TYPE = "spi" +I2C_SERIAL_TYPE = "i2c" + # Printer class that controls LIS2DW chip class LIS2DW: - def __init__(self, config): + def __init__(self, config, lis_type): self.printer = config.get_printer() adxl345.AccelCommandHelper(config, self) - self.axes_map = adxl345.read_axes_map(config, SCALE, SCALE, SCALE) - self.data_rate = 1600 + self.lis_type = lis_type + if self.lis_type == LIS2DW_TYPE: + self.axes_map = adxl345.read_axes_map( + config, LIS2DW_SCALE, LIS2DW_SCALE, LIS2DW_SCALE + ) + self.data_rate = 1600 + else: + self.axes_map = adxl345.read_axes_map( + config, LIS3DH_SCALE, LIS3DH_SCALE, LIS3DH_SCALE + ) + self.data_rate = 1344 + # Check for spi or i2c + if config.get("cs_pin", None) is not None: + self.bus_type = SPI_SERIAL_TYPE + else: + self.bus_type = I2C_SERIAL_TYPE # Setup mcu sensor_lis2dw bulk query code - self.spi = bus.MCU_SPI_from_config(config, 3, default_speed=5000000) - self.mcu = mcu = self.spi.get_mcu() + if self.bus_type == SPI_SERIAL_TYPE: + self.bus = bus.MCU_SPI_from_config(config, 3, default_speed=5000000) + else: + self.bus = bus.MCU_I2C_from_config( + config, default_addr=LIS_I2C_ADDR, default_speed=400000 + ) + self.mcu = mcu = self.bus.get_mcu() self.oid = oid = mcu.create_oid() self.query_lis2dw_cmd = None mcu.add_config_cmd( - "config_lis2dw oid=%d spi_oid=%d" % (oid, self.spi.get_oid()) + "config_lis2dw oid=%d bus_oid=%d bus_oid_type=%s " + "lis_chip_type=%s" + % (oid, self.bus.get_oid(), self.bus_type, self.lis_type) ) mcu.add_config_cmd( "query_lis2dw oid=%d rest_ticks=0" % (oid,), on_restart=True @@ -71,7 +108,7 @@ def __init__(self, config): ) def _build_config(self): - cmdqueue = self.spi.get_command_queue() + cmdqueue = self.bus.get_command_queue() self.query_lis2dw_cmd = self.mcu.lookup_command( "query_lis2dw oid=%c rest_ticks=%u", cq=cmdqueue ) @@ -86,12 +123,18 @@ def check_connected(self): ) def read_reg(self, reg): - params = self.spi.spi_transfer([reg | REG_MOD_READ, 0x00]) - response = bytearray(params["response"]) - return response[1] + if self.bus_type == SPI_SERIAL_TYPE: + params = self.bus.spi_transfer([reg | REG_MOD_READ, 0x00]) + response = bytearray(params["response"]) + return response[1] + params = self.bus.i2c_read([reg], 1) + return bytearray(params["response"])[0] def set_reg(self, reg, val, minclock=0): - self.spi.spi_send([reg, val & 0xFF], minclock=minclock) + if self.bus_type == SPI_SERIAL_TYPE: + self.bus.spi_send([reg, val & 0xFF], minclock=minclock) + else: + self.bus.i2c_write([reg, val & 0xFF], minclock=minclock) stored_val = self.read_reg(reg) if stored_val != val: raise self.printer.command_error( @@ -125,27 +168,50 @@ def _start_measurements(self): # noise or wrong signal as a correctly initialized device dev_id = self.read_reg(REG_LIS2DW_WHO_AM_I_ADDR) logging.info("lis2dw_dev_id: %x", dev_id) - if dev_id != LIS2DW_DEV_ID: - raise self.printer.command_error( - "Invalid lis2dw id (got %x vs %x).\n" - "This is generally indicative of connection problems\n" - "(e.g. faulty wiring) or a faulty lis2dw chip." - % (dev_id, LIS2DW_DEV_ID) - ) - # Setup chip in requested query rate - # ODR/2, +-16g, low-pass filter, Low-noise abled - self.set_reg(REG_LIS2DW_CTRL_REG6_ADDR, 0x34) - # Continuous mode: If the FIFO is full - # the new sample overwrites the older sample. - self.set_reg(REG_LIS2DW_FIFO_CTRL, 0xC0) - # High-Performance / Low-Power mode 1600/200 Hz - # High-Performance Mode (14-bit resolution) - self.set_reg(REG_LIS2DW_CTRL_REG1_ADDR, 0x94) - + if self.lis_type == LIS2DW_TYPE: + if dev_id != LIS2DW_DEV_ID: + raise self.printer.command_error( + "Invalid lis2dw id (got %x vs %x).\n" + "This is generally indicative of connection problems\n" + "(e.g. faulty wiring) or a faulty lis2dw chip." + % (dev_id, LIS2DW_DEV_ID) + ) + # Setup chip in requested query rate + # ODR/2, +-16g, low-pass filter, Low-noise abled + self.set_reg(REG_LIS2DW_CTRL_REG6_ADDR, 0x34) + # Continuous mode: If the FIFO is full + # the new sample overwrites the older sample. + self.set_reg(REG_LIS2DW_FIFO_CTRL, 0xC0) + # High-Performance / Low-Power mode 1600/200 Hz + # High-Performance Mode (14-bit resolution) + self.set_reg(REG_LIS2DW_CTRL_REG1_ADDR, 0x94) + else: + if dev_id != LIS3DH_DEV_ID: + raise self.printer.command_error( + "Invalid lis3dh id (got %x vs %x).\n" + "This is generally indicative of connection problems\n" + "(e.g. faulty wiring) or a faulty lis3dh chip." + % (dev_id, LIS3DH_DEV_ID) + ) + # High Resolution / Low Power mode 1344/5376 Hz + # High Resolution mode (12-bit resolution) + # Enable X Y Z axes + self.set_reg(REG_LIS2DW_CTRL_REG1_ADDR, 0x97) + # Disable all filtering + self.set_reg(REG_LIS2DW_CTRL_REG2_ADDR, 0) + # Set +-8g, High Resolution mode + self.set_reg(REG_LIS2DW_CTRL_REG4_ADDR, 0x28) + # Enable FIFO + self.set_reg(REG_LIS2DW_CTRL_REG5_ADDR, 0x40) + # Stream mode + self.set_reg(REG_LIS2DW_FIFO_CTRL, 0x80) # Start bulk reading rest_ticks = self.mcu.seconds_to_clock(4.0 / self.data_rate) self.query_lis2dw_cmd.send([self.oid, rest_ticks]) - self.set_reg(REG_LIS2DW_FIFO_CTRL, 0xC0) + if self.lis_type == LIS2DW_TYPE: + self.set_reg(REG_LIS2DW_FIFO_CTRL, 0xC0) + else: + self.set_reg(REG_LIS2DW_FIFO_CTRL, 0x80) logging.info("LIS2DW starting '%s' measurements", self.name) # Initialize clock tracking self.ffreader.note_start() @@ -172,8 +238,8 @@ def _process_batch(self, eventtime): def load_config(config): - return LIS2DW(config) + return LIS2DW(config, LIS2DW_TYPE) def load_config_prefix(config): - return LIS2DW(config) + return LIS2DW(config, LIS2DW_TYPE) diff --git a/klippy/extras/lis3dh.py b/klippy/extras/lis3dh.py new file mode 100644 index 000000000..bb5f0558f --- /dev/null +++ b/klippy/extras/lis3dh.py @@ -0,0 +1,14 @@ +# Support for reading acceleration data from an LIS3DH chip +# +# Copyright (C) 2024 Luke Vuksta +# +# This file may be distributed under the terms of the GNU GPLv3 license. +from . import lis2dw + + +def load_config(config): + return lis2dw.LIS2DW(config, lis2dw.LIS3DH_TYPE) + + +def load_config_prefix(config): + return lis2dw.LIS2DW(config, lis2dw.LIS3DH_TYPE) diff --git a/src/Kconfig b/src/Kconfig index 3abbf4e00..21b0de2a0 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -112,7 +112,7 @@ config WANT_SENSORS default y config WANT_LIS2DW bool - depends on HAVE_GPIO_SPI + depends on HAVE_GPIO_SPI || HAVE_GPIO_I2C default y config WANT_LDC1612 bool @@ -151,8 +151,8 @@ config WANT_SENSORS bool "Support external sensor devices" depends on HAVE_GPIO_I2C || HAVE_GPIO_SPI config WANT_LIS2DW - bool "Support lis2dw 3-axis accelerometer" - depends on HAVE_GPIO_SPI + bool "Support lis2dw and lis3dh 3-axis accelerometers" + depends on HAVE_GPIO_SPI || HAVE_GPIO_I2C config WANT_LDC1612 bool "Support ldc1612 eddy current sensor" depends on HAVE_GPIO_I2C diff --git a/src/ar100/gpio.h b/src/ar100/gpio.h index b5eea2f94..2d29a7dcd 100644 --- a/src/ar100/gpio.h +++ b/src/ar100/gpio.h @@ -40,4 +40,8 @@ void spi_prepare(struct spi_config config); void spi_transfer(struct spi_config config, uint8_t receive_data , uint8_t len, uint8_t *data); +struct i2c_config { + void *cfg; +}; + #endif diff --git a/src/atsamd/i2c.c b/src/atsamd/i2c.c index 18506a27c..5897a7760 100644 --- a/src/atsamd/i2c.c +++ b/src/atsamd/i2c.c @@ -12,10 +12,11 @@ #include "i2ccmds.h" // I2C_BUS_SUCCESS #define TIME_RISE 125ULL // 125 nanoseconds -#define I2C_FREQ 100000 +#define I2C_FREQ 100000 +#define I2C_FREQ_FAST 400000 static void -i2c_init(uint32_t bus, SercomI2cm *si) +i2c_init(uint32_t bus, uint32_t rate, SercomI2cm *si) { static uint8_t have_run_init; if (have_run_init & (1<CTRLA.reg = areg; uint32_t freq = sercom_get_pclock_frequency(bus); - uint32_t baud = (freq/I2C_FREQ - 10 - freq*TIME_RISE/1000000000) / 2; + uint32_t baud = 0; + if (rate < I2C_FREQ_FAST) { + baud = (freq/I2C_FREQ - 10 - freq*TIME_RISE/1000000000) / 2; + } else { + baud = (freq/I2C_FREQ_FAST - 10 - freq*TIME_RISE/1000000000) / 2; + } si->BAUD.reg = baud; si->CTRLA.reg = areg | SERCOM_I2CM_CTRLA_ENABLE; while (si->SYNCBUSY.reg & SERCOM_I2CM_SYNCBUSY_ENABLE) @@ -47,7 +53,7 @@ i2c_setup(uint32_t bus, uint32_t rate, uint8_t addr) Sercom *sercom = sercom_enable_pclock(bus); sercom_i2c_pins(bus); SercomI2cm *si = &sercom->I2CM; - i2c_init(bus, si); + i2c_init(bus, rate, si); return (struct i2c_config){ .si=si, .addr=addr<<1 }; } diff --git a/src/generic/gpio.h b/src/generic/gpio.h index cb104d00d..9f86952d3 100644 --- a/src/generic/gpio.h +++ b/src/generic/gpio.h @@ -41,4 +41,8 @@ void spi_prepare(struct spi_config config); void spi_transfer(struct spi_config config, uint8_t receive_data , uint8_t len, uint8_t *data); +struct i2c_config { + uint32_t cfg; +}; + #endif // gpio.h diff --git a/src/sensor_lis2dw.c b/src/sensor_lis2dw.c index 83922003c..8a4a6a664 100644 --- a/src/sensor_lis2dw.c +++ b/src/sensor_lis2dw.c @@ -6,6 +6,8 @@ // This file may be distributed under the terms of the GNU GPLv3 license. #include // memcpy +#include "autoconf.h" // CONFIG_HAVE_GPIO_SPI +#include "board/gpio.h" // irq_disable #include "board/irq.h" // irq_disable #include "board/misc.h" // timer_read_time #include "basecmd.h" // oid_alloc @@ -13,9 +15,12 @@ #include "sched.h" // DECL_TASK #include "sensor_bulk.h" // sensor_bulk_report #include "spicmds.h" // spidev_transfer +#include "i2ccmds.h" // i2cdev_s #define LIS_AR_DATAX0 0x28 #define LIS_AM_READ 0x80 +#define LIS_MS_SPI 0x40 +#define LIS_MS_I2C 0x80 #define LIS_FIFO_SAMPLES 0x2F #define BYTES_PER_SAMPLE 6 @@ -23,8 +28,13 @@ struct lis2dw { struct timer timer; uint32_t rest_ticks; - struct spidev_s *spi; + union { + struct spidev_s *spi; + struct i2cdev_s *i2c; + }; + uint8_t bus_type; uint8_t flags; + uint8_t model; struct sensor_bulk sb; }; @@ -32,6 +42,20 @@ enum { LIS_PENDING = 1<<0, }; +enum { + SPI_SERIAL, I2C_SERIAL, +}; + +DECL_ENUMERATION("bus_oid_type", "spi", SPI_SERIAL); +DECL_ENUMERATION("bus_oid_type", "i2c", I2C_SERIAL); + +enum { + LIS2DW, LIS3DH, +}; + +DECL_ENUMERATION("lis_chip_type", "LIS2DW", LIS2DW); +DECL_ENUMERATION("lis_chip_type", "LIS3DH", LIS3DH); + static struct task_wake lis2dw_wake; // Event handler that wakes lis2dw_task() periodically @@ -50,9 +74,39 @@ command_config_lis2dw(uint32_t *args) struct lis2dw *ax = oid_alloc(args[0], command_config_lis2dw , sizeof(*ax)); ax->timer.func = lis2dw_event; - ax->spi = spidev_oid_lookup(args[1]); + + switch (args[2]) { + case SPI_SERIAL: + if (CONFIG_HAVE_GPIO_SPI) { + ax->spi = spidev_oid_lookup(args[1]); + ax->bus_type = SPI_SERIAL; + break; + } else { + shutdown("bus_type spi unsupported"); + } + case I2C_SERIAL: + if (CONFIG_HAVE_GPIO_I2C) { + ax->i2c = i2cdev_oid_lookup(args[1]); + ax->bus_type = I2C_SERIAL; + break; + } else { + shutdown("bus_type i2c unsupported"); + } + default: + shutdown("bus_type invalid"); + } + + switch (args[3]) { + case LIS2DW: + case LIS3DH: + ax->model = args[3]; + break; + default: + shutdown("model type invalid"); + } } -DECL_COMMAND(command_config_lis2dw, "config_lis2dw oid=%c spi_oid=%c"); +DECL_COMMAND(command_config_lis2dw, "config_lis2dw oid=%c" + " bus_oid=%c bus_oid_type=%c lis_chip_type=%c"); // Helper code to reschedule the lis2dw_event() timer static void @@ -68,25 +122,58 @@ lis2dw_reschedule_timer(struct lis2dw *ax) static void lis2dw_query(struct lis2dw *ax, uint8_t oid) { - uint8_t msg[7] = {0}; - uint8_t fifo[2] = {LIS_FIFO_SAMPLES| LIS_AM_READ , 0}; - uint8_t fifo_empty,fifo_ovrn = 0; - - msg[0] = LIS_AR_DATAX0 | LIS_AM_READ ; + uint8_t fifo_empty = 0; + uint8_t fifo_ovrn = 0; uint8_t *d = &ax->sb.data[ax->sb.data_count]; - spidev_transfer(ax->spi, 1, sizeof(msg), msg); + if (CONFIG_HAVE_GPIO_SPI && ax->bus_type == SPI_SERIAL) { + uint8_t msg[7] = {0}; + uint8_t fifo[2] = {LIS_FIFO_SAMPLES | LIS_AM_READ , 0}; + + msg[0] = LIS_AR_DATAX0 | LIS_AM_READ; + if (ax->model == LIS3DH) + msg[0] |= LIS_MS_SPI; + + spidev_transfer(ax->spi, 1, sizeof(msg), msg); - spidev_transfer(ax->spi, 1, sizeof(fifo), fifo); - fifo_empty = fifo[1]&0x3F; - fifo_ovrn = fifo[1]&0x40; + spidev_transfer(ax->spi, 1, sizeof(fifo), fifo); - d[0] = msg[1]; // x low bits - d[1] = msg[2]; // x high bits - d[2] = msg[3]; // y low bits - d[3] = msg[4]; // y high bits - d[4] = msg[5]; // z low bits - d[5] = msg[6]; // z high bits + if (ax->model == LIS3DH) + fifo_empty = fifo[1] & 0x20; + else + fifo_empty = fifo[1] & 0x3F; + + fifo_ovrn = fifo[1] & 0x40; + + for (uint32_t i = 0; i < BYTES_PER_SAMPLE; i++) + d[i] = msg[i + 1]; + } else if (CONFIG_HAVE_GPIO_I2C && ax->bus_type == I2C_SERIAL) { + uint8_t msg_reg[] = {LIS_AR_DATAX0}; + if (ax->model == LIS3DH) + msg_reg[0] |= LIS_MS_I2C; + uint8_t msg[6]; + uint8_t fifo_reg[1] = {LIS_FIFO_SAMPLES}; + uint8_t fifo[1]; + + int ret; + ret = i2c_dev_read(ax->i2c, sizeof(msg_reg), msg_reg + , sizeof(msg), msg); + i2c_shutdown_on_err(ret); + + ret = i2c_dev_read(ax->i2c, sizeof(fifo_reg), fifo_reg + , sizeof(fifo), fifo); + i2c_shutdown_on_err(ret); + + if (ax->model == LIS3DH) + fifo_empty = fifo[0] & 0x20; + else + fifo_empty = fifo[0] & 0x3F; + + fifo_ovrn = fifo[0] & 0x40; + + for (uint32_t i = 0; i < BYTES_PER_SAMPLE; i++) + d[i] = msg[i]; + } ax->sb.data_count += BYTES_PER_SAMPLE; if (ax->sb.data_count + BYTES_PER_SAMPLE > ARRAY_SIZE(ax->sb.data)) @@ -129,12 +216,32 @@ void command_query_lis2dw_status(uint32_t *args) { struct lis2dw *ax = oid_lookup(args[0], command_config_lis2dw); - uint8_t msg[2] = { LIS_FIFO_SAMPLES | LIS_AM_READ, 0x00 }; - uint32_t time1 = timer_read_time(); - spidev_transfer(ax->spi, 1, sizeof(msg), msg); - uint32_t time2 = timer_read_time(); + uint32_t time1 = 0; + uint32_t time2 = 0; + uint8_t status = 0; + + if (CONFIG_HAVE_GPIO_SPI && ax->bus_type == SPI_SERIAL) { + uint8_t msg[2] = { LIS_FIFO_SAMPLES | LIS_AM_READ, 0x00 }; + time1 = timer_read_time(); + spidev_transfer(ax->spi, 1, sizeof(msg), msg); + time2 = timer_read_time(); + status = msg[1]; + } else if (CONFIG_HAVE_GPIO_I2C && ax->bus_type == I2C_SERIAL) { + uint8_t fifo_reg[1] = {LIS_FIFO_SAMPLES}; + uint8_t fifo[1]; + + time1 = timer_read_time(); + int ret = i2c_dev_read(ax->i2c, sizeof(fifo_reg), fifo_reg + , sizeof(fifo), fifo); + time2 = timer_read_time(); + + i2c_shutdown_on_err(ret); + + status = fifo[0]; + } + sensor_bulk_status(&ax->sb, args[0], time1, time2-time1 - , (msg[1] & 0x1f) * BYTES_PER_SAMPLE); + , (status & 0x1f) * BYTES_PER_SAMPLE); } DECL_COMMAND(command_query_lis2dw_status, "query_lis2dw_status oid=%c");