diff --git a/ISSUES.txt b/ISSUES.txt new file mode 100644 index 0000000..77d9587 --- /dev/null +++ b/ISSUES.txt @@ -0,0 +1,5 @@ +SPI does not work properly +-- writes may go to wrong addr +-- FIFO read out addr 6 loop seems to not work + +FIFO WTM/OVRN on INT1 does not fire when FTH (watermark) is not 31 (default/max) \ No newline at end of file diff --git a/Makefile b/Makefile index 6939abd..a30d1ed 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,12 @@ CC=gcc CFLAGS=-O0 -g3 -ggdb -std=c89 -W -Werror -Wall -Wextra -Wpedantic -pedantic-errors -Wformat-signedness -I. -CFLAGS+=-Wlogical-op -Wmissing-declarations -Wswitch-default -Wundef -Wformat=2 -lasan -lm -CFLAGS+=-fsanitize=pointer-overflow,undefined,shift,shift-exponent,shift-base,integer-divide-by-zero,null,signed-integer-overflow,bounds +CFLAGS+=-Wlogical-op -Wmissing-declarations -Wswitch-default -Wundef -Wformat=2 +LFLAGS=-lm CFILES=$(wildcard *.c) BINFILE=lis3dh all: - $(CC) $(CFLAGS) $(CFILES) -o $(BINFILE) + $(CC) $(CFLAGS) $(CFILES) -o $(BINFILE) $(LFLAGS) clean: rm -rf $(BINFILE) diff --git a/README.md b/README.md index c09e1c7..468bf6e 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ See the `examples/` dir for complete code examples ## Implementation This driver requires the user to implement the following interface functions: -This project has example interface code for I2C used on Raspberry Pi. +This project has example interface code for I2C and SPI (broken) used on Raspberry Pi 4. ```c /* initialise the "interface" */ int init(void); @@ -36,7 +36,7 @@ int deinit(void); ``` All above functions return `0` on success, and any non-zero value on error. -If `init` and `deinit` are set to `NULL`, they will be ignored. Useful on microcontrollers. +If `init` and/or `deinit` are set to `NULL`, they will be ignored. Useful on microcontrollers. --- diff --git a/i2c.c b/i2c.c index d4c9dcc..a32361b 100644 --- a/i2c.c +++ b/i2c.c @@ -15,6 +15,14 @@ Example I2C use on linux/raspberry pi #include "i2c.h" +/* + * Pinout config for this example + * + * LIS3DH SDA => Raspberry Pi GPIO 2 (Physical pin 3) + * LIS3DH SCL => Raspberry Pi GPIO 3 (Physical pin 5) + * + */ + #define I2C_DEVICE "/dev/i2c-1" #define I2C_LIS3DH_ADDRESS 0x18 @@ -40,6 +48,10 @@ int i2c_init(void) { int i2c_read(uint8_t reg, uint8_t *dst, uint32_t size) { uint8_t cmd[2]; + if (size > 1) { + reg |= 0x80; /* AUTO INC */ + } + cmd[0] = reg; cmd[1] = 0x00; diff --git a/lis3dh.c b/lis3dh.c index a52d0d1..52f93cd 100644 --- a/lis3dh.c +++ b/lis3dh.c @@ -28,7 +28,7 @@ int lis3dh_init(lis3dh_t *lis3dh) { memset(&lis3dh->src, 0, sizeof lis3dh->src); lis3dh->cfg.fifo.mode = 0xFF; /* in use if neq 0xFF */ - lis3dh->cfg.fifo.fth = 31; /* default watermark level. */ + lis3dh->cfg.fifo.fth = 31; /* default watermark level (0-indexed). */ lis3dh->cfg.filter.mode = 0xFF; /* in use if neq 0xFF */ lis3dh->cfg.filter.fds = 1; /* bypass OFF by default */ @@ -88,7 +88,6 @@ int lis3dh_configure(lis3dh_t *lis3dh) { click_ths |= (lis3dh->cfg.click_ths & 0x7F); click_ths |= (lis3dh->cfg.click.latch & 1) << 7; - /* set interrupt registers */ /* INT PIN 1 */ ctrl_reg3 |= (lis3dh->cfg.pin1.click & 1) << 7; @@ -112,7 +111,6 @@ int lis3dh_configure(lis3dh_t *lis3dh) { ctrl_reg5 |= (lis3dh->cfg.int2.latch & 1) << 1; ctrl_reg5 |= (lis3dh->cfg.int1.en_4d & 1) << 2; ctrl_reg5 |= (lis3dh->cfg.int1.latch & 1) << 3; - /* set INT1_CFG and INT2_CFG */ int1_cfg |= (lis3dh->cfg.int1.xl & 1); @@ -144,13 +142,7 @@ int lis3dh_configure(lis3dh_t *lis3dh) { /* set enable FIFO */ if (lis3dh->cfg.fifo.mode != 0xFF) { ctrl_reg5 |= 0x40; /* bit FIFO_EN */ - - /* restrict maximum fifo size */ - if (lis3dh->cfg.fifo.fth > 31) { - lis3dh->cfg.fifo.fth = 31; - } - - fifo_ctrl_reg |= (lis3dh->cfg.fifo.fth); + fifo_ctrl_reg |= (lis3dh->cfg.fifo.fth & 0x1F); fifo_ctrl_reg |= (lis3dh->cfg.fifo.mode << 6); fifo_ctrl_reg |= ((lis3dh->cfg.fifo.trig & 1) << 5); } @@ -274,11 +266,8 @@ int lis3dh_read(lis3dh_t *lis3dh) { scale = acc_shift(lis3dh->cfg.mode); sens = acc_sensitivity(lis3dh->cfg.mode, lis3dh->cfg.range); - /* must set MSbit of the address to multi-read and - have the device auto-increment the address. */ - err |= lis3dh->dev.read(REG_OUT_X_L | 0x80, data, 6); + err |= lis3dh->dev.read(REG_OUT_X_L, data, 6); - /* x,y,z are now in mg */ x = (((int16_t)((data[0] << 8) | data[1])) >> scale) * sens; y = (((int16_t)((data[2] << 8) | data[3])) >> scale) * sens; z = (((int16_t)((data[4] << 8) | data[5])) >> scale) * sens; @@ -298,17 +287,13 @@ int lis3dh_read_fifo(lis3dh_t *lis3dh, struct lis3dh_fifo_data *fifo) { int err = 0; int i, idx; - /* FIFO is always 10-bit */ + /* FIFO is always 10-bit / normal mode */ scale = 6; - sens = acc_sensitivity(lis3dh->cfg.mode, lis3dh->cfg.range); + sens = acc_sensitivity(LIS3DH_MODE_NORMAL, lis3dh->cfg.range); - /* fifo buffer is max 31 */ - fifo->size = lis3dh->cfg.fifo.fth > 31 ? 31 : lis3dh->cfg.fifo.fth; + fifo->size = lis3dh->cfg.fifo.fth; - /* must set MSbit of the address to multi-read and - have the device auto-increment the address. - see 5.1.5 in datasheet. */ - err |= lis3dh->dev.read(REG_OUT_X_L | 0x80, data, 192); + err |= lis3dh->dev.read(REG_OUT_X_L, data, 192); for (i=0, idx=0; isize * 6; i+=6, idx++) { x = (((int16_t)((data[i + 0] << 8) | data[i + 1])) >> scale) * sens; @@ -394,8 +379,9 @@ int lis3dh_read_adc(lis3dh_t *lis3dh) { int err = 0; uint8_t shift; float divisor; + /* read adc{1,2,3} LSB and MSB */ - err |= lis3dh->dev.read(REG_OUT_ADC1_L | 0x80, data, 6); + err |= lis3dh->dev.read(REG_OUT_ADC1_L, data, 6); if (lis3dh->cfg.mode == LIS3DH_MODE_LP) { shift = 8; diff --git a/lis3dh.h b/lis3dh.h index c6dfedd..6156b0b 100644 --- a/lis3dh.h +++ b/lis3dh.h @@ -250,18 +250,18 @@ struct lis3dh_accel { }; /* stores interrupt source registers read from the device */ -struct lis3dh_interrupt_src { +struct lis3dh_int_src { uint8_t int1; uint8_t int2; uint8_t click; }; struct lis3dh { - struct lis3dh_device dev; /* fn ptrs to interface w/ device */ - struct lis3dh_config cfg; /* config variables to write to device */ - struct lis3dh_accel acc; /* accel data read from device (not FIFO) */ - struct lis3dh_adc adc; /* adc and optionally temp read from device */ - struct lis3dh_interrupt_src src; /* INT_SRC registers read from device */ + struct lis3dh_device dev; /* fn ptrs to interface w/ device */ + struct lis3dh_config cfg; /* config variables to write to device */ + struct lis3dh_accel acc; /* accel data read from device (not FIFO) */ + struct lis3dh_adc adc; /* adc and optionally temp read from device */ + struct lis3dh_int_src src; /* INT_SRC registers read from device */ }; typedef struct lis3dh lis3dh_t; diff --git a/main.c b/main.c index 73f0d99..4bc6e8a 100644 --- a/main.c +++ b/main.c @@ -7,132 +7,94 @@ #include "interrupt.h" #include "i2c.h" -#define GPIO_INTERRUPT_PIN_INT1 12 +/* GPIO 12 or Pin 32 */ +#define GPIO_INTERRUPT_PIN 12 -/* print message then exit */ static void quit(const char *msg, lis3dh_t *lis) { lis->dev.deinit(); fprintf(stderr, "%s\n", msg); exit(1); } +static float mag(float x, float y, float z) { + return (float) sqrt(x*x + y*y + z*z); +} + int main() { lis3dh_t lis; + struct lis3dh_fifo_data fifo; + int k; - /* set fn ptrs to rw on bus (i2c or SPI) */ lis.dev.init = i2c_init; lis.dev.read = i2c_read; lis.dev.write = i2c_write; lis.dev.sleep = usleep; lis.dev.deinit = i2c_deinit; - /* initialise LIS3DH struct */ + /* initalise LIS3DH struct */ if (lis3dh_init(&lis)) { quit("init()", &lis); } - /* reset device because it sometimes corrupts itself */ + /* reset device just in case */ if (lis3dh_reset(&lis)) { quit("reset()", &lis); } /* register interrupt */ - if (int_register(GPIO_INTERRUPT_PIN_INT1)) { + if (int_register(GPIO_INTERRUPT_PIN)) { quit("int_register()", &lis); } - /* set up config */ lis.cfg.mode = LIS3DH_MODE_HR; lis.cfg.range = LIS3DH_FS_2G; lis.cfg.rate = LIS3DH_ODR_400_HZ; - lis.cfg.pin1.ia1 = 1; /* allow INT1 through INT_PIN1 */ - - - /* 1 LSb = 16 mg @ FS_2G - * 0.3g threshold = 300/16 = 18.75 - * add read error, +40mg => 240/16 = 21.25 ~= 21 - * if you for some reason don't want to use the HP filter, - * just add 1g to the threshold calculation. - */ - lis.cfg.int1_ths = 21; - - /* Duration time is measured in N/ODR where: - * --- N = The content of the intX_dur integer - * --- ODR = the data rate, eg 100, 400... - * [ODR] [1 LSb in milliseconds] - * 400 2.5 - * - * For ODR=400: - * 10 ms => 10/2.5 = 5 - * lis.cfg.int1_dur = 5; <== 10 ms minimum duration to wake up - */ - lis.cfg.int1_dur = 0; /* instantaneous */ - - /* enable X_high, Y_high and Z_high */ - lis.cfg.int1.yh = 1; - lis.cfg.int1.zh = 1; - lis.cfg.int1.xh = 1; - - /* OR mode. Think about the axis combinations for AND mode */ - lis.cfg.int1.aoi = 0; /* set to 1 for AND mode */ - lis.cfg.int1.en_6d = 0; - - - /* latch interrupt. might not work. */ - lis.cfg.int1.latch = 1; - - /* set up a HP filter to ignore constant earth acceleration */ - lis.cfg.filter.mode = LIS3DH_FILTER_MODE_NORMAL_REF; - lis.cfg.filter.cutoff = LIS3DH_FILTER_CUTOFF_8; - lis.cfg.filter.ia1 = 1; /* enable filter for INT1 generator */ + lis.cfg.fifo.mode = LIS3DH_FIFO_MODE_STREAM_TO_FIFO; + lis.cfg.fifo.trig = LIS3DH_FIFO_TRIG_INT1; /* trigger interrupt into int pin1 */ + lis.cfg.pin1.wtm = 1; /* trigger upon FIFO watermark level reached */ + lis.cfg.en_adc =1; + lis.cfg.en_temp = 1; /* write device config */ if (lis3dh_configure(&lis)) { - quit("configure()", &lis); - } - - /* read REFERENCE to set filter to current accel field */ - if (lis3dh_reference(&lis)) { - quit("reference()", &lis); - } - - /* read INT1_SRC to clear old interrupt if any */ - if (lis3dh_read_int1(&lis)) { - quit("read_int1()", &lis); - } - - for( ;; ) { - - /* wait for INT1 to go active */ - if (int_poll(GPIO_INTERRUPT_PIN_INT1)) { - quit("int_poll()", &lis); - } - - /* read INT1_SRC */ - if (lis3dh_read_int1(&lis)) { - quit("read_int1()", &lis); - } - - /* print received interrupt .. */ - printf("IA=%d ZH=%d ZL=%d YH=%d YL=%d XH=%d XL=%d\n", - LIS3DH_INT_SRC_IA(lis.src.int1), - LIS3DH_INT_SRC_Z_HIGH(lis.src.int1), - LIS3DH_INT_SRC_Z_LOW(lis.src.int1), - LIS3DH_INT_SRC_Y_HIGH(lis.src.int1), - LIS3DH_INT_SRC_Y_LOW(lis.src.int1), - LIS3DH_INT_SRC_X_HIGH(lis.src.int1), - LIS3DH_INT_SRC_X_LOW(lis.src.int1)); - - /* sleep for 5 ms because gpio sysfs is slow at clearing interrupts */ - /* not necessary with "real" IRQ */ - usleep(5000); + quit("configure()", &lis); } + /* wait for interrupt from LIS3DH */ + if (int_poll(GPIO_INTERRUPT_PIN)) { + + quit("int_poll()", &lis); + } + + /* read as many [x y z] sets as specified by watermark level (fth) */ + /* copy them to the fifo data struct given below as `fifo' */ + if (lis3dh_read_fifo(&lis, &fifo)) { + quit("read_fifo()", &lis); + } + + /* above function also writes out the qty of [x y z] sets stored in `fifo' */ + for(k=0; k +#include +#include +#include +#include +#include +#include +#include +#include + +#include "spi.h" + +#define SPI_DEVICE "/dev/spidev0.0" + + +/* + * Pinout config for this example + * + * MOSI - GPIO 10 (physical pin 19) => LIS3DH "SDA" or "SDI" + * MISO - GPIO 9 (physical pin 21) => LIS3DH "SDO" + * SCLK - GPIO 11 (physical pin 23) => LIS3DH "SCK" + * CE0 - GPIO 8 (physical pin 24) => LIS3DH "!CS" + * + * Broken for unknown reason on Pi 4 + * + */ + +#define SPI_SPEED 500 * 1000 /* 500 KHz */ + +static int fd; +static uint8_t rx[256]; +static uint8_t tx[256]; + +int spi_init(void) { + uint8_t mode = SPI_MODE_3; + uint8_t bits = 8; + uint32_t speed = SPI_SPEED; + + if ((fd = open(SPI_DEVICE, O_RDWR)) < 0) { + fprintf(stderr, "spi open(%s)\n", SPI_DEVICE); + return 1; + } + + if (ioctl(fd, SPI_IOC_RD_MODE, &mode) == -1) { + fprintf(stderr, "SPI_IOC_RD_MODE\n"); + return 1; + } + + if (ioctl(fd, SPI_IOC_WR_MODE, &mode) == -1) { + fprintf(stderr, "SPI_IOC_WR_MODE\n"); + return 1; + } + + if (ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits) == -1) { + fprintf(stderr, "SPI_IOC_WR_BITS_PER_WORD\n"); + return 1; + } + + if (ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits) == -1) { + fprintf(stderr, "SPI_IOC_RD_BITS_PER_WORD\n"); + return 1; + } + + if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) == -1) { + fprintf(stderr, "SPI_IOC_WR_MAX_SPEED_HZ\n"); + return 1; + } + + if (ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed) == -1) { + fprintf(stderr, "SPI_IOC_RD_MAX_SPEED_HZ\n"); + return 1; + } + + return 0; +} + +static int spi_transaction(uint8_t *tx, uint8_t *rx, uint32_t size) { + struct spi_ioc_transfer tr = {0}; + + tr.tx_buf = (unsigned long) tx; + tr.rx_buf = (unsigned long) rx; + tr.len = size; + + return ioctl(fd, SPI_IOC_MESSAGE(1), &tr); +} + +int spi_read(uint8_t reg, uint8_t *dst, uint32_t size) { + + /* clear 2 MSbits */ + reg &= 0x3F; + + /* set READ bit */ + reg |= 0x80; + + if (size > 1) { + /* set AUTO INC bit */ + reg |= 0x40; + } + + tx[0] = reg; + + if (spi_transaction(tx, rx, size + 1) < 0) { + fprintf(stderr, "spi_read()\n"); + return 1; + } + + memcpy(dst, rx + 1, size); + + return 0; +} + +int spi_write(uint8_t reg, uint8_t value) { + + /* clear 2 MSbits */ + reg &= 0x3F; + + tx[0] = reg; + tx[1] = value; + + if (spi_transaction(tx, rx, 2) < 0) { + fprintf(stderr, "spi_write()\n"); + return 1; + } + + return 0; +} + +int spi_deinit(void) { + + if (fd) { + close(fd); + } + + return 0; +} + diff --git a/spi.h b/spi.h new file mode 100644 index 0000000..69dc530 --- /dev/null +++ b/spi.h @@ -0,0 +1,11 @@ +#ifndef SPI_H +#define SPI_H + +#include + +int spi_init(void); +int spi_read(uint8_t reg, uint8_t *dst, uint32_t size); +int spi_write(uint8_t reg, uint8_t value); +int spi_deinit(void); + +#endif \ No newline at end of file