Thingy 91 SPI driver for ADXL362

Hello. I want to write my own driver for the SPI accelerometer (ADXL362).

Sadly, there is barely any information regarding the SPI, at least it is not easily available. I have gone through various documentation regarding SPI:

https://docs.zephyrproject.org/latest/hardware/peripherals/spi.html

About my setup:

  1. Development board used: Thingy 91
  2. Logic Analyzer used: Salae logic pro 8
  3. External programmer/debugger used: Segger J-Link compact plus

See image of my setup (I know it looks a bit chaotic):

As you can see from image above, I have soldered wires on the CS, MOSI, MISO and CLK and GND testpoints so I can connect logic analyzer.

Currently I am practising using spi_write.

See my code below:

#include <stdio.h>
#include <stdlib.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/device.h>



#define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(main);


#define SPI_MESSAGE 0xA5


#define DEFAULT_ADXL362_NODE DT_ALIAS(adxl362)
BUILD_ASSERT(DT_NODE_HAS_STATUS(DEFAULT_ADXL362_NODE, okay),
			 "ADXL362 not specified in DT");

//DEVICE TREE STRUCTURE
const struct device *const adxl1362_sens = DEVICE_DT_GET(DEFAULT_ADXL362_NODE);

//CHIP SELECT CONTROL
struct spi_cs_control ctrl = SPI_CS_CONTROL_INIT(DT_NODELABEL(adxl362), 2);

//SPI CONFIG
static const struct spi_config spi_cfg = {
	.operation = SPI_WORD_SET(8) | SPI_TRANSFER_MSB,
	.frequency = 4000000, // 8 mhz
	.slave = 0,
    .cs = &ctrl,
};

int main(void)
{
	int ret;
	if (!device_is_ready(adxl1362_sens))
	{
		LOG_INF("sensor: device %s not ready.\n", adxl1362_sens->name);
		return 0;
	}
	uint8_t cmd = SPI_MESSAGE;
	struct spi_buf tx_buf = {.buf = &cmd, .len = 1};
	struct spi_buf_set tx_bufs = {.buffers = &tx_buf, .count = 1};
	while (1) {
		LOG_INF("SPI writing test data \n");
		spi_write(adxl1362_sens, &spi_cfg, &tx_bufs);
		k_sleep(K_MSEC(1000));
	}
	
	return 0;
}


As you can see from the code above, I am expecting to send a simple SPI message periodically (every 1 second). But when I monitor signal using logic analyzer, it looks really weird:

It sends some garbage once and then never again so it does not work as expected. I believe it has to do something with the chip select as it stays HIGH.

Is below correct way to initialize spi_config with chip select? How does  SPI_CS_CONTROL_INIT(DT_NODELABEL(adxl362), 2); know which CS gpio to select because there are 2 chip selects declared for the SPI3 in the device tree? How does it select whether gpio0 7 or gpio0 8 is used?

struct spi_cs_control ctrl = SPI_CS_CONTROL_INIT(DT_NODELABEL(adxl362), 2);

//SPI CONFIG
static const struct spi_config spi_cfg = {
	.operation = SPI_WORD_SET(8) | SPI_TRANSFER_MSB,
	.frequency = 4000000, // 8 mhz
	.slave = 0,
    .cs = &ctrl,
};

In the nrf/boards/arm/thingy91_nrf9160/thingy91_nrf9160_common.dts the following is declared:

&spi3 {
	compatible = "nordic,nrf-spim";
	status = "okay";
	cs-gpios = <&gpio0 8 GPIO_ACTIVE_LOW>, <&gpio0 7 GPIO_ACTIVE_LOW>;

	pinctrl-0 = <&spi3_default>;
	pinctrl-1 = <&spi3_sleep>;
	pinctrl-names = "default", "sleep";
	adxl362: adxl362@0 {
		compatible = "adi,adxl362";
		spi-max-frequency = <8000000>;
		reg = <0>;
		int1-gpios = <&gpio0 9 0>;
	};

	adxl372: adxl372@1 {
		compatible = "adi,adxl372";
		spi-max-frequency = <8000000>;
		reg = <1>;
		int1-gpios = <&gpio0 6 0>;
	};
};




I am attaching Salae capture file below:

8311.adxl362_log3.sal


I would very much appreciate if someone could point me in the right direction. Perhaps you can spot an issue in my code? Why simple spi_write would not work ?

If you think I have missed some information or I need to clarify something, do not hesitate to ask me.

UPDATE

I have added some additional logs :

		err = spi_write(adxl1362_sens, &spi_cfg, &tx_bufs);
		if (err) {
			LOG_ERR("SPI write failed with error %d\n", err);
			return err;
		}

The logs:



What is also very interesting, is that the device is spitting the log <err> ADXL372: failed to read id (0xAD:0x0)[1B][0m

which is very strange. In my code, I do nothing related to ADXL372. I only use ADXL362. Why would it print this error related to ADXL372?

Also, I have looked at zephyr errno documentation:

https://docs.zephyrproject.org/apidoc/latest/errno_8h.html

I have discovered that error -134 corresponds to ENOTSUP

but sadly that does not provide any useful information for me.

So to summarise everything up:

1. What is the correct way to configure  spi_cs_control? Is method that I used correct? 

2. Why I am getting SPI write failed with error -134? Could that be related to the Chip select?

3. Why I am getting the following printed on the console:
[00:00:00.266,723] [1B][1;31m<err> ADXL372: failed to read id (0xAD:0x0)[1B][0m.
As you have seen from my code that I posted above, it has nothing to do with ADXL372

  • UPDATE

    I still havent figured out what could be an issue regarding SPI communication on the Thingy 91.

    For testing purposes, I have created a project that you can clone from github:

    https://github.com/krupis/thingy91_spi_test

    Steps to reproduce the issue:

    1. Clone the project from github
    2. Solder wires on the THINGY 91 testpoints (MOSI, MISO,CS, CLK and GND)
    3. Connect wires (MOSI, MISO CS, CLK and GND) to logic analyzer or osciloscope
    4. Flash the device
    5. Monitor the serial logs and logic analyzer signals

    Additionally, I have figured something out regarding below issue:

    . Why I am getting the following printed on the console:
    [00:00:00.266,723] [1B][1;31m<err> ADXL372: failed to read id (0xAD:0x0)[1B][0m.
    As you have seen from my code that I posted above, it has nothing to do with ADXL372

    The <err> ADXL372: failed to read id (0xAD:0x0) only happens when I got SPI wires connected to the logic analyzer. See image below:

     

    However, if I disconnect the logic analyzer as shown below:

    and reset the board, I will no longer get the error about adxl372.

    Could that be related that when I connect to the logic analyzer, CHIP select is pulled up and it does not fails to detect ADXL372 device? But it is quite strange why would it do that because in my code I dont have anything to do with ADXL372. Unless the zephyr automatically tries to detect whatever is defined in the device tree without any user code. Could that be the case?

    Anyways, my questions from the initial post remain unresolved. I hope to receive any clarifications. Thanks in advance!

     

  • UPDATE

    I have been working on this for quite a while now. I honestly think I have tried almost everything I could at this point:

    1. I have tried to use seperate spi_write and spi_read functions
    2. I have tried to use spi_trasnceive function
    3. I have tried to manually control chip select line
    4. I have tried to use automatic chip select line control via spi_cs_control

    I have also tried to run accell_polling sample project just to confirm whether my HW is not damaged and it is working and it is. I am able to read the sensor data without any issue but I am not interested in that. I just want to learn about SPI and how to write my own low level drivers.

    The latest source code can be found here:

    https://github.com/krupis/thingy91_spi_test

    I will also paste the source code here:

    #include <stdio.h>
    #include <stdlib.h>
    #include <zephyr/kernel.h>
    #include <zephyr/drivers/spi.h>
    #include <zephyr/device.h>
    #include <zephyr/drivers/gpio.h>
    
    #define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL
    #include <zephyr/logging/log.h>
    LOG_MODULE_REGISTER(main);
    
    #define ADXL362_WRITE_REG 0x0A
    #define ADXL362_READ_REG 0x0B
    #define ADXL362_READ_FIFO 0x0D
    
    #define ADXL362_REG_DEVID_AD 0x00
    #define ADXL362_REG_DEVID_MST 0x01
    #define ADXL362_REG_PARTID 0x02
    
    
    static int readRegister(uint8_t reg, uint8_t *values, uint8_t size);
    
    #define DEFAULT_ADXL362_NODE DT_ALIAS(adxl362)
    BUILD_ASSERT(DT_NODE_HAS_STATUS(DEFAULT_ADXL362_NODE, okay),
    			 "ADXL362 not specified in DT");
    
    // DEVICE TREE STRUCTURE
    const struct device *adxl1362_sens = DEVICE_DT_GET(DEFAULT_ADXL362_NODE);
    
    // CS CONTROL
    struct spi_cs_control ctrl = {
            .gpio = SPI_CS_GPIOS_DT_SPEC_GET(DT_NODELABEL(adxl362)),
            .delay = 0,
    };
    
    // SPI CONFIG
    static const struct spi_config spi_cfg = {
    	.operation = SPI_WORD_SET(8) | SPI_TRANSFER_MSB,
    	.frequency = 1000000, // 1 mhz
    	.slave = 0,
    	.cs = &ctrl,
    };
    
    int main(void)
    {
    	int err;
    	printk("Program started \n");
    	uint8_t values[1];
    	while (1)
    	{
    		int ret = readRegister(ADXL362_REG_DEVID_AD, values, 1);
    		if (ret == 0)
    		{
    			printk("Read chip ID failed \n");
    			k_msleep(1000);
    		}
    		else
    		{
    			printk("Register chip ID: %d\n", values[0]);
    			k_msleep(1000);
    		}
    	}
    	return 0;
    }
    
    
    //According to the ADXL362 datasheet (https://www.analog.com/media/en/technical-documentation/data-sheets/adxl362.pdf)
    //Figure 36, we follow multi byte structure where first byte is ADXL362_READ_REG and second byte is the register address
    static int readRegister(uint8_t reg, uint8_t *values, uint8_t size)
    {
    	int err;
    	uint8_t tx_buffer[2];
    	tx_buffer[0] = ADXL362_READ_REG;
    	tx_buffer[1] = reg;
    
    	struct spi_buf tx_spi_buf = {
    		.buf = tx_buffer,
    		.len = sizeof(tx_buffer)};
    
    	struct spi_buf_set spi_tx_buffer_set = {
    		.buffers = &tx_spi_buf,
    		.count = 1};
    
    	struct spi_buf rx_spi_buf = {
    		.buf = values,
    		.len = size};
    
    	struct spi_buf_set spi_rx_buffer_set = {
    		.buffers = &rx_spi_buf,
    		.count = 1};
    
    	err = spi_transceive(adxl1362_sens, &spi_cfg, &spi_tx_buffer_set, &spi_rx_buffer_set);
    	if (err)
    	{
    		printk("SPI error: %d\n", err);
    	}
    	else
    	{
    		/* Connect MISO to MOSI for loopback */
    		printk("TX sent: %x\n", tx_buffer[0]);
    		printk("RX recv: %x\n", values[0]);
    		tx_buffer[0]++;
    	}
    }

    I cannot wrap my head around why would the most simple spi_transceive would not work and why would I be getting error -134 when trying to request sensor ID.

  • Hi, I am looking into your questions here, and will let you know as I have answers.

    The first thing I can answer is that yes, if you enable the node with a compatible that matched a driver, the driver for that sensor will automatically be included in the build. Most drivers will also try to initialize the sensor and confirm that the sensor is responsive. This is what is causing the ADXL372 errors.

    If you do not want to use the Zephyr included drivers for these sensors, I suggest you disable them with the configs:

    CONFIG_ADXL362=n
    CONFIG_ADXL372=n

  • Driving long leads at 8MHz requires high-drive output pin levels; low-drive strength will not reliably work and the note that connecting the logic analyzer causes problems is a further indication of soggy clock and data edges as the logic analizer increases capacitive loading of the signals. The logic analyzer is digital and hides the slow rise and fall times of the clock and data edges on the display. This applies to CS, CLK and MOSI; CLK is the most susceptible to errors. Note only some port P0 i/o pins support H0H1 high drive levels; no port P1 signals are suitable.

    I think this is the format for the high-drive pin definition:

    	adxl362: adxl362@0 {
    		...
            nordic,drive-mode = <NRF_DRIVE_H0H1>;

    Edit: Some settings can unexpectedly override H0H1, for example in-ncs-unable-to-set-high-drive-mode-on-pwm-leds-anymore-from-v2-2-0-onwards

  • Thank you both for your answers. I still have not managed to get the SPI communication to work between nRF9160 on the Thingy91 and the ADXL362. Just to ensure the logic analyzer does not cause any issues as hmolesworth mentioned, I have disconnected it from the Thingy91 board (desoldered the wires)

    I have been further analyzing the Thingy 91 and SPI. My goal is to be able to read the device ID as described in the ADXL362 datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/adxl362.pdf

    Method1

    This method uses spi_dt_spec and spi_transceive_dt methods

    The full source code:

    #include <stdio.h>
    #include <stdlib.h>
    #include <zephyr/kernel.h>
    #include <zephyr/drivers/spi.h>
    #include <zephyr/device.h>
    
    #define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL
    #include <zephyr/logging/log.h>
    LOG_MODULE_REGISTER(main);
    
    #define ADXL362_WRITE_REG 0x0A
    #define ADXL362_READ_REG 0x0B
    #define ADXL362_READ_FIFO 0x0D
    
    #define ADXL362_REG_DEVID_AD 0x00
    
    static int readRegister(uint8_t reg, uint8_t *values, uint8_t size);
    
    #define MY_SPI_MASTER DT_ALIAS(adxl362)
    struct spi_dt_spec spec = SPI_DT_SPEC_GET(DT_NODELABEL(adxl362), SPI_WORD_SET(8) | SPI_MODE_GET(0), 1);
    
    int main(void)
    {
    	int err;
    	printk("Program started \n");
    	if (spi_is_ready(&spec) == true)
    	{
    		printk("SPI is ready \n");
    	}
    	else
    	{
    		printk("SPI is not ready \n");
    	}
    
    	uint8_t values[1];
    	while (1)
    	{
    		int ret = readRegister(ADXL362_REG_DEVID_AD, values, 1);
    		if (ret == 0)
    		{
    			printk("Read chip ID failed \n");
    			k_msleep(1000);
    		}
    		else
    		{
    			printk("Register chip ID:%.2x\n", values[0]);
    			k_msleep(1000);
    		}
    	}
    	return 0;
    }
    
    
    
    
    
    
    // According to the ADXL362 datasheet (https://www.analog.com/media/en/technical-documentation/data-sheets/adxl362.pdf)
    // Figure 36, we follow multi byte structure where first byte is ADXL362_READ_REG and second byte is the register address
    static int readRegister(uint8_t reg, uint8_t *values, uint8_t size)
    {
    	int err;
    	uint8_t tx_buffer[2];
    	tx_buffer[0] = ADXL362_READ_REG;
    	tx_buffer[1] = reg;
    
    	struct spi_buf tx_spi_buf = {
    		.buf = tx_buffer,
    		.len = sizeof(tx_buffer)};
    
    	struct spi_buf_set spi_tx_buffer_set = {
    		.buffers = &tx_spi_buf,
    		.count = 1};
    
    	struct spi_buf rx_spi_buf = {
    		.buf = values,
    		.len = size};
    
    	struct spi_buf_set spi_rx_buffer_set = {
    		.buffers = &rx_spi_buf,
    		.count = 1};
    
    	err = spi_transceive_dt(&spec, &spi_tx_buffer_set, &spi_rx_buffer_set);
    
    	if (err)
    	{
    		printk("SPI error: %d\n", err);
    		return 0;
    	}
    	return 1;
    }

    The serial logs:

    As you can see from above, I am not getting any SPI communication errors but still not able to read back the correct ID.

    Method2

    This method uses different API than the above method. 

    The full source code:

    #include <stdio.h>
    #include <stdlib.h>
    #include <zephyr/kernel.h>
    #include <zephyr/drivers/spi.h>
    #include <zephyr/device.h>
    #include <zephyr/drivers/gpio.h>
    
    #define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL
    #include <zephyr/logging/log.h>
    LOG_MODULE_REGISTER(main);
    
    #define ADXL362_WRITE_REG 0x0A
    #define ADXL362_READ_REG 0x0B
    #define ADXL362_READ_FIFO 0x0D
    
    #define ADXL362_REG_DEVID_AD 0x00
    
    static int readRegister(uint8_t reg, uint8_t *values, uint8_t size);
    
    #define DEFAULT_ADXL362_NODE DT_ALIAS(adxl362)
    BUILD_ASSERT(DT_NODE_HAS_STATUS(DEFAULT_ADXL362_NODE, okay),
    			 "ADXL362 not specified in DT");
    
    // DEVICE TREE STRUCTURE
    const struct device *adxl1362_sens = DEVICE_DT_GET(DEFAULT_ADXL362_NODE);
    
    // CS CONTROL
    struct spi_cs_control ctrl = {
    	.gpio = SPI_CS_GPIOS_DT_SPEC_GET(DT_NODELABEL(adxl362)),
    	.delay = 2,
    };
    
    // SPI CONFIG
    static const struct spi_config spi_cfg = {
    	.operation = SPI_WORD_SET(8) | SPI_TRANSFER_MSB,
    	.frequency = 1000000, // 1 mhz
    	.slave = 0,
    	.cs = &ctrl,
    };
    
    int main(void)
    {
    	int err;
    	printk("Program started \n");
    	if (!device_is_ready(adxl1362_sens))
    	{
    		printk("sensor: device %s not ready.\n", adxl1362_sens->name);
    		return 0;
    	}
    	uint8_t values[1];
    	while (1)
    	{
    		int ret = readRegister(ADXL362_REG_DEVID_AD, values, 1);
    		if (ret == 0)
    		{
    			printk("Read chip ID failed \n");
    			k_msleep(1000);
    		}
    		else
    		{
    			printk("Register chip ID: %d\n", values[0]);
    			k_msleep(1000);
    		}
    	}
    	return 0;
    }
    
    // According to the ADXL362 datasheet (https://www.analog.com/media/en/technical-documentation/data-sheets/adxl362.pdf)
    // Figure 36, we follow multi byte structure where first byte is ADXL362_READ_REG and second byte is the register address
    static int readRegister(uint8_t reg, uint8_t *values, uint8_t size)
    {
    	int err;
    	uint8_t tx_buffer[2];
    	tx_buffer[0] = ADXL362_READ_REG;
    	tx_buffer[1] = reg;
    
    	struct spi_buf tx_spi_buf = {
    		.buf = tx_buffer,
    		.len = sizeof(tx_buffer)};
    
    	struct spi_buf_set spi_tx_buffer_set = {
    		.buffers = &tx_spi_buf,
    		.count = 1};
    
    	struct spi_buf rx_spi_buf = {
    		.buf = values,
    		.len = size};
    
    	struct spi_buf_set spi_rx_buffer_set = {
    		.buffers = &rx_spi_buf,
    		.count = 1};
    
    	err = spi_transceive(adxl1362_sens, &spi_cfg, &spi_tx_buffer_set, &spi_rx_buffer_set);
    	if (err)
    	{
    		printk("SPI error: %d\n", err);
    		return 0;
    	}
    	return 1;
    }

    The serial logs:

    þ*** Booting nRF Connect SDK v2.5.0 ***
    Program started 
    SPI error: -134
    Read chip ID failed 
    SPI error: -134
    Read chip ID failed 
    SPI error: -134
    Read chip ID failed 
    SPI error: -134
    Read chip ID failed 
    

    As you can see from above, I cant even get the SPI communication to work using this method.

    I have been looking at the above code for too long and honestly I cannot spot any mistakes on either of methods myself. According to the numerous SPI videos on zephyr that I have watched and also Zephyr documentation, both should be valid SPI methods and work fine.

    You can access both of the projects in my git repository:

    Method 1https://github.com/krupis/thingy91_spi_test/tree/method1

    Method 2https://github.com/krupis/thingy91_spi_test/tree/method2

    You can easily recreate the issue if you have Thingy91 board by flashing it with either method1 or method2 program on the nRF9160 and have the connectivity bridge running on the nRF52840.

    Any questions regarding this, do not hesitate to task!

Related