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

  • The same issue cam up just recently; the received data you are looking for is the 3rd byte not the first, and all 3 bytes have to be received.

    	uint8_t values[3]; <=== change
    	while (1)
    	{
    		int ret = readRegister(ADXL362_REG_DEVID_AD, values, 3);   <=== change
    		if (ret == 0)
    		{
    			printk("Read chip ID failed \n");
    			k_msleep(1000);
    		}
    		else
    		{
    			printk("Register chip ID: %d\n", values[2]);   <=== change
    

    Edit: Here's a link to a similar case spi-receives-ff-each-time-miso-in-high-impedance

  • Thanks for response. I have wired up logic analyzer and run method1. See the waveforms below:

    The most obvious issue is that the MOSI line is not sending 2 bytes in a row. Instead, it sends 1 byte then does a 20uS pause before sending a new byte and that is obviously wrong according to the ADXL362 datasheet.

    After trying your suggestion, I am able to read the correct ID and the waveforms are as following:

    An alternative suggestion has been posted in the zephyr git regarding the same issue.

    https://github.com/zephyrproject-rtos/zephyr/issues/65512

    It has been suggested to replace:

    	 struct spi_buf rx_spi_buf = {
    	 	.buf = values,
    	 	.len = size};
    
    	 struct spi_buf_set spi_rx_buffer_set = {
    	 	.buffers = &rx_spi_buf,
    	 	.count = 1};

    with 

    	struct spi_buf rx_spi_buf[] = {
    		{.buf = NULL,
    		 .len = sizeof(tx_buffer)},
    		{.buf = values,
    		 .len = size}};
    
    	struct spi_buf_set spi_rx_buffer_set = {
    		.buffers = rx_spi_buf,
    		.count = ARRAY_SIZE(rx_spi_buf)};
    

    for unknown reasons.

    The logic analyzer after applying suggested change:

    As you can see the above solution provides simillar results to your suggested method.


    Despite the fact that I am now able to read the correct data from the sensor device, I still have some questions:


    1. Why is there a gap after sending 2 bytes and receiving data? Is that expected?


    2. With your suggested solution, why do I need read 3 bytes instead of 1 byte? That does not make any sense to me at the moment... Since I have 2 seperate buffers (one for tx and one for rx) as described below:

    the RX and TX parts are seperated so I can choose however many bytes I want to send and however many I want to receive. Why would I need to read 3 bytes when only 1 byte is received? 

    Is that something that only applies to this particular accelerometer? Or the same applies to other SPI sensors as well?

    Do you understand my confusion and what I mean?

  • Unfortunately - having chosen to use nRFConnect and Zephyr - you are restricted to use their interpretation of the world. There is an alternative, however. First the underlying SPI hardware is similar for most devices, for every transmitted bit there is a received bit; send 3 bytes get 3 bytes back. 2 (say) transmitted  bytes may be meaningful with further dummy (ORC) bytes added to keep SCLK ie reception going until (say) all 3 bytes have been received. So yes, zephyr could just present the 3rd byte and not the first 2 but no it doesn't understand which bytes you are really interested in.

    You can use the nRFx SPI stuff with zephyr, but the same issue replies, you have to decide which are the relevant bytes in the received stream. Also zephyr is causing the separation delay between the first 2 and 3rd byte, totally unnecessarily. Bare Metal code doesn't do that. I posted some 3- and 4-wire Bare Metal SPI code, but if using zephyr that's probably not your best option; better to live with the beast oops I mean elegant solution.

Related