This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

SPI communication with ADXL372

My problem seems to be a lot like case numbers 217967 and 243215.

I do not have an oscilloscope or logic analyzer.

I am using a nRF52840 DK (PCA10056 1.0.0 2018-17) and a nRF52840 (PCA10056 2.0.0 2019-47). I am using the nRF5 SDK v16.0.0 along with the S140 v7.0.1 in a Ubuntu 18.04.3 LTS environment with Segger Embedded Studio and JLink v6.6.0 utilities i.e. J-Link RTT Viewer V6.60f. I have an ADXL372Z breakout board that has headers soldered to it. That assembly is plugged into a standard solder-less breadboard. I'm using the newer DK as a SPI Master. The adxl372 is powered from the DK's VDD to its VS and DK's GND to its GND.

I started out by using the SDK->examples->peripheral->spi and SDK->examples->peripheral->spis. I programmed the older DK as the slave and the newer DK as the master. After some initial confusion such as setting the slave's nrf_drv_spis_config_t->bit_order to NRF_SPIS_BIT_ORDER_LSB_FIRST, I got that working as it should. I also took the time to set up J-Link Viewer specifically so I could use the SEGGER_RTT_WaitKey() function in my main loop. The idea being that the DK waits for me to hit a key before executing any SPI communications. This gives me all the time I need to examine received output.

I know the lack of equipment is a real detriment but I only started on SPI a week ago. (Saleae Logic 8 is getting here). However, I'm confident that the newer DK as the spi master is functioning as it should. I.E. there is a good SCLK and I'm providing the correct buffer sizes and I have a multimeter on the CS line so I know when its high and low.

I moved on to my main goal of using the newer DK board as the SPI MAster connected to the ADXL372 MEMs. I'm using the SDK->examples->peripheral->spi as the basis for my code. My first step was to put a framework together that initializes a nrf_drv_spi_t and configures it to the specifications of the adxl372. My next step was to just read the adxl372's first four registers which contain constant values, (Analog's ID, MEMS device ID, device ID, and the revision ID). This doesn't work. The values returned are incorrect and variable.

The code is attached. You'll need to google the adxl372 data sheet. I would ask for help by looking at my code and providing any advice, hints, help, any pointers. I have tried many different things with no result. I know the adxl372z is good because I can get it to work with an Arduino DUE using the sketches from github.com/analogdevicesinc/arduino. I have tried changing the nrf_drv_spi_config_t fields in many ways and combinations, higher and lower frequencies, different pins (Case ID 127130) (I have stuck with the default pins since they worked for the two DK slave/master setup.) I could really use another set of eyes on my code. Any help/advice is greatly appreciated.

sdk_config.zip

/**
 * Copyright (c) 2015 - 2019, Nordic Semiconductor ASA
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form, except as embedded into a Nordic
 *    Semiconductor ASA integrated circuit in a product or a software update for
 *    such product, must reproduce the above copyright notice, this list of
 *    conditions and the following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *
 * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * 4. This software, with or without modification, must only be used with a
 *    Nordic Semiconductor ASA integrated circuit.
 *
 * 5. Any software provided in binary form under this license must not be reverse
 *    engineered, decompiled, modified and/or disassembled.
 *
 * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
#include "nrf_drv_spi.h"
#include "app_util_platform.h"
#include "nrf_gpio.h"
#include "nrf_delay.h"
#include "boards.h"
#include "app_error.h"
#include <string.h>
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"

#include "SEGGER_RTT.h"

#include "XL372.h"
#include "adxl372_spi.h"

#define SPI_INSTANCE  0
static const nrf_drv_spi_t    m_spi0_master = NRF_DRV_SPI_INSTANCE(SPI_INSTANCE);
static volatile bool spi_xfer_done;

#define TEST_STRING "Nordic"
static uint8_t       m_tx_buf[] = TEST_STRING;           /**< TX buffer. */
static const uint8_t m_tx_length = sizeof(m_tx_buf);
static uint8_t       m_rx_buf[sizeof(TEST_STRING) + 1];    /**< RX buffer. */
static const uint8_t m_rx_length = sizeof(m_rx_buf);        /**< Transfer length. */


/**
 * @brief SPI user event handler.
 * @param event
 */
void spi_event_handler(nrf_drv_spi_evt_t const *p_event, void *p_context)
{
    char str[128], str1[16];
    short i;

    spi_xfer_done = true;
    NRF_LOG_INFO("Transfer completed.");
    switch (p_event->type)
    {
        // The One and Only SPI event!
        case NRF_DRV_SPI_EVENT_DONE:
        {
            if(m_rx_buf[0] != 0xCC)   // High Probability that something changed
            {
                sprintf (str, " rxBuf Changed: ");
                for (i = 0; i < m_rx_length; i++)
                {
                    sprintf (str1, "[%d]= 0x%x, ", i, m_rx_buf[i]);
                    strcat (str, str1);
                }

                NRF_LOG_INFO("%s", str);
            }
            else
            {
                NRF_LOG_INFO(" rxBuf apparently didn't change");
            }
        }
    }

}


/**@brief Function for initializing a SPI master driver.
 *
 * @param[in] p_instance    Pointer to SPI master driver instance.
 * @param[in] lsb           Bits order LSB if true, MSB if false.
 */
static void spi_master_init(nrf_drv_spi_t const *p_instance)
{
    uint32_t              err_code;

    nrf_drv_spi_config_t  config;
    
    config.ss_pin       = NRF_DRV_SPI_PIN_NOT_USED;
    config.irq_priority = APP_IRQ_PRIORITY_LOW;
    config.orc          = 0xFF;
    config.frequency    = NRF_DRV_SPI_FREQ_125K;
    config.mode         = NRF_DRV_SPI_MODE_0;  // Corresponds to the datasheet specification CPHA = CPOL = 0
    config.bit_order    = NRF_DRV_SPI_BIT_ORDER_MSB_FIRST;  // Put it out the way you put it in
    
    #if (SPI0_ENABLED == 1)
    if (p_instance == &m_spi0_master)
    {
        config.sck_pin  = SPI0_SCK_PIN;
        config.mosi_pin = SPI0_MOSI_PIN;
        config.miso_pin = SPI0_MISO_PIN;
        err_code = nrf_drv_spi_init(p_instance, &config, spi_event_handler, NULL);
        
        APP_ERROR_CHECK(err_code);
    }
    else
    {
        // Do nothing
    }
    #endif // (SPI0_ENABLED == 1)

    // Set the SPI Chip Select pin as output and initial its state to high
    nrf_gpio_cfg_output(SPI0_SS_PIN);
    nrf_gpio_pin_set(SPI0_SS_PIN);
    
}


int main(void)
{
    char        c = 0;
    uint8_t     address = 0;
    uint32_t    err_code = 0;

    bsp_board_init(BSP_INIT_LEDS);

    err_code = NRF_LOG_INIT(NULL);
    APP_ERROR_CHECK(err_code);
    NRF_LOG_DEFAULT_BACKENDS_INIT();

    spi_master_init(&m_spi0_master);
    ADXL372_SPI_init(&m_spi0_master);

    NRF_LOG_INFO("SPI example started.");
    NRF_LOG_FLUSH();

    while (1)
    {
       
        // Reset rx & tx buffers and transfer done flag
        memset(m_rx_buf, 0, m_rx_length);
        memset(m_tx_buf, 0, m_tx_length);
        spi_xfer_done = false;

        // Artificial value that has to change on the transmission of the
        // register address. That byte in will produce a garbage byte out.
        // Betting on a low probability that the garbage will be 0xCC, so
        // I can use this as a flag in the spi_event_handler.
        // If the first byte of the received buffer isn't this value then
        // something did something somewhere along the process.
        m_rx_buf[0] = 0xCC;

        NRF_LOG_INFO("Hit a key to do a nrf_drv_spi_transfer.");
        NRF_LOG_FLUSH();
        c = SEGGER_RTT_WaitKey(); // will block until you're ready to try
        switch (c)
        {
            case 'q':
            {
                address = XL372_DEVID_AD;
                break;
            }
            case 'w':
            {
                address = XL372_DEVID_MST;
                break;
            }
            case 'e':
            {
                address = XL372_PARTID;
                break;
            }
            case 'r':
            {
                address = XL372_REVID;
                break;
            }
            default:
                spi_xfer_done = true;
        }
        if (!spi_xfer_done)
        {
            ADXL372_SPI_readRegister(address, m_rx_buf, 2);

            while (!spi_xfer_done)
            {
                __WFE();
            }

            NRF_LOG_FLUSH();

            bsp_board_led_invert(BSP_BOARD_LED_0);
            nrf_delay_ms(200);
        }
        else
            NRF_LOG_INFO("Wrong Key!");

    }
}
//
// Thanks to Nordic Semiconductor and bjorn-spockeli for important bits and pieces from
// around 2009, (Nordic Case ID 111509).
//

#include <stdbool.h>
#include "XL372.h"
#include "adxl372_spi.h"
#include "SEGGER_RTT.h"
#include "nrf_drv_spi.h"
#include "boards.h"
#include "app_error.h"
#include "app_util_platform.h"
#include "nrf_gpio.h"
#include "nrf_delay.h"
#include <string.h>

static const nrf_drv_spi_t    *p_spi_master_0;


/**@brief Function for initializing the ADXL372 SPI driver.
 *
 * @param[in] nrf_drv_spi_t *m_spi_master    Pointer to spi instance
 *
 */
void ADXL372_SPI_init(const nrf_drv_spi_t *m_spi_master)
{
    uint8_t    value = 0;

    p_spi_master_0 = m_spi_master;
    
    // Since I couldn't get the read register to work I thought maybe you have to
    // turn the adxl372 "on". The data sheet says that upon power up, the device
    // is in Stand-By mode. I thought that maybe a stand-by mode would disallow
    // regester reading. I still don't know. Anyhow, this stuff is my effort to
    // get the device out of stand-by and ready for action, like letting me read
    // a register.
    // Initialize the device per your specifications.
    // Set Data Output Rate
    value = (ODR_400Hz << TIMING_ODR_POS) | (WUR_104ms << TIMING_WUR_POS);
    ADXL372_SPI_writeRegister(XL372_TIMING, &value, 1);
    // Set the Power Control Register
    value = 0;
    value = FULL_BW_MEASUREMENT;
    // Disable High Pass Filter, Low Pass Filter at Bit 3 is enabled with default zero
    value |= 1 << 2;
    ADXL372_SPI_writeRegister(XL372_POWER_CTL, &value, 1); 
    // Set offset values
    value = 0;
    value = BW_200Hz;
    value |= 1 << 3;
    ADXL372_SPI_writeRegister(XL372_MEASURE, &value, 1);
}


/**@brief Function for reading to the ADXL375 registers over SPI.
 *
 * @param[in] address   ADXL372 Register address    
 * @param[in] p_data    Pointer to rx buffer 
 * @param[in] bytes     Number of bytes to be read 
 *
 */
void ADXL372_SPI_readRegister(uint8_t reg_address, uint8_t *p_data, uint8_t bytes)
{
    uint32_t   err_code;
        
    uint8_t    tx_data_spi[bytes];
    memset(tx_data_spi, 0, bytes);

    // Shift the register's address to the left by one, inorder to
    // clear bit 0, and set it high per the adxl372 data sheet's
    // specification for the read command structure of the device's SPI Protocal.
    tx_data_spi[0] = (reg_address << 1) + 1;
    
    // Pull Chip Select line low
    nrf_gpio_pin_clear(SPI0_SS_PIN);
    nrf_delay_ms(100);
    
    err_code = nrf_drv_spi_transfer(p_spi_master_0,
                                    tx_data_spi, bytes,
                                    p_data, bytes);
    
    APP_ERROR_CHECK(err_code);
    
    // Set Chip Select line high
    nrf_delay_ms(100);
    nrf_gpio_pin_set(SPI0_SS_PIN);
}


/**@brief Function for writing to the ADXL372 registers over SPI.
 *
 * @param[in] address   ADXL372 Register address    
 * @param[in] p_data    Pointer to tx buffer 
 * @param[in] bytes     Number of bytes to be written
 *
 */
void ADXL372_SPI_writeRegister(uint8_t reg_address, uint8_t *p_data, uint8_t bytes)
{
         
    uint32_t    err_code;
    
    uint8_t     m_tx_data_spi[bytes + 1];
    memset(m_tx_data_spi, 0, (bytes + 1));      
    memcpy((m_tx_data_spi + 1), p_data, bytes);
    
    // Shift the register's address to the left by one, inorder to
    // clear bit 0, and keep it low per the adxl372 data sheet's
    // specification for the write command structure of the device's SPI Protocal.
    m_tx_data_spi[0] = (reg_address << 1);
    
    // Pull Chip Select line low
    nrf_gpio_pin_clear(SPI0_SS_PIN);
    nrf_delay_ms(10);
     
    err_code = nrf_drv_spi_transfer(p_spi_master_0,
                                    m_tx_data_spi, sizeof(m_tx_data_spi),
                                    NULL, 0);
    
    APP_ERROR_CHECK(err_code);
    
    // Set Chip Select line high
    nrf_delay_ms(10);
    nrf_gpio_pin_set(SPI0_SS_PIN);
}



adxl372_spi.hXL372.h

  • Hi,

    Looking at the code, I can't find any obvious wrong no. Seems you have very relaxed timing, so that should not be an issue. I guess you can try to connect MISO to MOSI without the adxl372, and see that you are loop backing the data as expected. I think waiting for the logic analyzer is the best way forward, then you can compare working and failing (it may for instance be polarity and phase of the SPI, possible voltage levels, or anything else at the moment unfortunately).

    Best regards,
    Kenneth

  • You mention testing with an Arduino; hopefully that was with 3.5 volt i/o and not 5 volt given the max supply to the ADXL372 is 3.5 volts. The implication is that the ADXL372 is connected by some leads rather than being close to the nRF52 via PCB traces. The Arduino also has higher pin drive, I imagine, so I would suggest trying boosted drive levels on the nRF52 after the SPI port is initialised with nrf_drv_spi_init() as that function uses S0S1 or standard drive. Using a higher SCK frequency of 8MHz also shows more clearly on a 'scope any edge bounce caused by the transmission lines being unterminated (I kid you not, this happens).

    // Change nRF52 driven pins to High Drive 0 and 1:
      nrf_gpio_cfg(SPI0_MOSI_PIN. NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
      nrf_gpio_cfg(SPI0_SCK_PIN,  NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
      nrf_gpio_cfg(SPI0_SS_PIN,   NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
    
    .

  • hmolesworth, Thank you! Those are great ideas. Ever since I burnt a Cypress chip many years ago, I don't connect anything without double checking. Also Analog Devices is assiduous in there warnings about supply voltages. I will implement your ideas and report back. Thanks again.

    5 hours latter and there is joy in Mudville! To paraphrase Marco in Case ID 116690, I was concentrating on software so much I forgot about the hardware. Thank you hmolesworth. Used shorter jumper wires and gave SCK and MOSI 5mA via H0HI. Prototyping proceeds and this shouldn't be a problem when the nRF52480 and the ADXL372 are on the same board.

    Also check out Case ID 211119 for good S0S1 vs H0H1 explanation.

Related