This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

SPI operations failing on custom board versus development kit

Hi,

I have been developing an application that reads sensor data (over SAADC and SPI) over an interval, serializes the data, and transmits the data over BLE_NUS to a companion device. Current development has been happening on the nRF52-Development Kit, and have been flashing a custom board for production. However, while we have been able to have no issues communicating over SPI on the development kit, we have not been able to get the SPI feature to work on the custom board. We modified a breadboard off the nRF Development Kit that perfectly matches the custom PCB hardware, and the SPI code also worked perfectly then. 

I know it very well could be a manufacturing issue, but wanted to also reach out and see if this had happened to anyone before, and / or what main differences exist between the nRF32-Development Kit and a stand-alone nRF32832 chip that could cause this issue? Really appreciate your help and time!

Software Notes:

The device runs in the pwr_mgmt_run() mode, and every second a repeated_timer gets called, which if a sufficient number of seconds have passed triggers a SAADC read (blocking), and then uses SPI to read from a LPS22 pressure / temperature sensor. A condensed version of the code is: (I redacted a lot of irrelevant parts, but if something is important that isn't included please let me know)!

#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>

#include "nordic_common.h"
#include "nrf.h"
#include "ble_hci.h"
#include "ble_advdata.h"
#include "ble_advertising.h"
#include "ble_conn_params.h"
#include "nrf_gpio.h"
#include "nrf_sdh.h"
#include "nrf_sdh_soc.h"
#include "nrf_sdh_ble.h"
#include "nrf_ble_gatt.h"
#include "nrf_ble_qwr.h"
#include "app_timer.h"
#include "ble_nus.h"
#include "app_uart.h"
#include "app_util_platform.h"
#include "bsp_btn_ble.h"
#include "nrf_pwr_mgmt.h"

#include "nrf_drv_saadc.h"
#include "nrf_drv_spi.h"
#include "nrfx_saadc.h"
#include "app_error.h"
#include "nrf_drv_power.h"
#include "nrf_drv_clock.h"
#include "nrf_drv_rtc.h"
#include "nrf_nvic.h"

#include "state.h"
#include "command.h"

#if defined (UART_PRESENT)
#include "nrf_uart.h"
#endif
#if defined (UARTE_PRESENT)
#include "nrf_uarte.h"
#endif

#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"


#define LPS22_MISO                       15
#define LPS22_MOSI                       14
#define LPS22_SCK                        12
#define LPS22_SS                         16

/************************
 * SPI Config
 * *******************/

// Pressure Sensor (LPS22) Registers
#define LPS22_WHO_AM_I                      0x0F    // Sensor WhoAmI register
#define LPS22_WHO_AM_I_VALUE                0xB1    // Expected WhoAmI value
#define LPS22_PRESS_OUT_XL                  0x28    // Low part
#define LPS22_PRESS_OUT_L                   0x29    // Middle part
#define LPS22_PRESS_OUT_H                   0x2A    // High part
#define LPS22_TEMP_OUT_L                    0x2B    // Low part
#define LPS22_TEMP_OUT_H                    0x2C    // High part

#define LPS22_RES_CONF                      0x1A    // Normal (0) or low current (1)
#define LPS22_CTRL_REG1                     0x10    // Output rate
#define LPS22_CTRL_REG2                     0x11    // Type of boot
#define LPS22_STATUS_REG                    0x27    // Stores if data is available

// SPI General
#define SPI_BUFSIZE                         8
#define SPI_INSTANCE                        0

#define SET_READ_SINGLE_CMD(x)  (x | 0x80)      // Indication that we are reading data
#define SET_WRITE_SINGLE_CMD(x) (x & ~(0xC0))   // Indication that we are writing data

uint8_t spi_tx_buf[SPI_BUFSIZE];
uint8_t spi_rx_buf[SPI_BUFSIZE];

/* Holds whether a SPI transfer was completed or not */
static volatile bool spi_xfer_done;

static const nrf_drv_spi_t m_spi = NRF_DRV_SPI_INSTANCE(SPI_INSTANCE);

BLE_ADVERTISING_DEF(m_advertising);                                                 /**< Advertising module instance. */
APP_TIMER_DEF(m_repeated_timer_id);

static uint16_t   m_conn_handle          = BLE_CONN_HANDLE_INVALID;                 /**< Handle of the current connection. */
static uint16_t   m_ble_nus_max_data_len = BLE_GATT_ATT_MTU_DEFAULT - 3;            /**< Maximum length of data (in bytes) that can be transmitted to the peer by the Nordic UART service module. */
static ble_uuid_t m_adv_uuids[]          =                                          /**< Universally unique service identifier. */
{
    {BLE_UUID_NUS_SERVICE, NUS_SERVICE_UUID_TYPE}
};

/*
    State of the app
*/
static struct state app_state;


/**
 * @brief Callback function called on saadc event
 * 
 * @param p_event SAADC event
 */
void saadc_callback(nrf_drv_saadc_evt_t const * p_event)
{
   // Empty - only use single-shot
   // Unless is calibrate done event, then release the calibration lock
   if (p_event->type == NRF_DRV_SAADC_EVT_CALIBRATEDONE) {
       app_state.calibrating_saadc = false;
       safe_log(1, "Calibration event completed\r\n");
   }
}


/**
 * @brief Triggers a non-blocking callibrate SAADC event
 *      Warning: This does not check if the SAADC is busy, and should only be run
 *      when we are sure it is not
 * 
 */
void calibrate_saadc() {
    app_state.calibrating_saadc = true;
    ret_code_t err_code = nrf_drv_saadc_calibrate_offset();
    safe_log(1, "Calibration ret code: %lu\r\n", err_code);
}


/**
 * @brief Function for initializing the timer module.
 */
static void timers_init(void)
{
    ret_code_t err_code = app_timer_init();
    APP_ERROR_CHECK(err_code);
}
/**
 * @brief Initializes the SAADC module
 */
void saadc_init(void)
{
    ret_code_t err_code;
    nrf_saadc_channel_config_t channel_config = NRFX_SAADC_DEFAULT_CHANNEL_CONFIG_SE(SAADC_BATTERY_PIN);
    channel_config.burst = NRF_SAADC_BURST_ENABLED;

    //Initialize SAADC
    err_code = nrf_drv_saadc_init(NULL, saadc_callback);                         //Initialize the SAADC with configuration and callback function. The application must then implement the saadc_callback function, which will be called when SAADC interrupt is triggered
    APP_ERROR_CHECK(err_code);
	
    //Initialize SAADC channel
    err_code = nrf_drv_saadc_channel_init(0, &channel_config);                            //Initialize SAADC channel 0 with the channel configuration
    APP_ERROR_CHECK(err_code);
}


/**
 * @brief Handler for SPI event
 * 
 * @param p_event The event
 * @param p_context The context
 */
void spi_event_handler(nrf_drv_spi_evt_t const * p_event, void * p_context) {
    spi_xfer_done = true;
}

/**
 * @brief Initializes the SPI module
 */
static void spi_init(void) {
    nrf_drv_spi_config_t spi_config = NRF_DRV_SPI_DEFAULT_CONFIG;
    spi_config.ss_pin       = LPS22_SS;
    spi_config.miso_pin     = LPS22_MISO;
    spi_config.mosi_pin     = LPS22_MOSI;
    spi_config.sck_pin      = LPS22_SCK;
    spi_config.frequency    = NRF_DRV_SPI_FREQ_125K;

    APP_ERROR_CHECK(nrf_drv_spi_init(&m_spi, &spi_config, spi_event_handler, NULL));

    nrf_gpio_cfg(LPS22_MOSI, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_CONNECT, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
    nrf_gpio_cfg(LPS22_SCK,  NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_CONNECT, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
    nrf_gpio_cfg(LPS22_SS,   NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_CONNECT, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
}

/**
 * @brief Write to an SPI register on the LPS22 device
 * 
 * @param reg The register address
 * @param data The data to write
 */
void spi_register_write(int reg, int data) {
    spi_tx_buf[0] = SET_WRITE_SINGLE_CMD(reg);
    spi_tx_buf[1] = data;
    spi_xfer_done = false;
    APP_ERROR_CHECK(nrf_drv_spi_transfer(&m_spi, spi_tx_buf, 2, spi_rx_buf, 0));

    while(spi_xfer_done == false) {};
}

/**
 * @brief Reads from a specific registrar on a SPI-connected peripherial
 * 
 * @param reg The register to read from
 * @return int The response
 */
uint8_t spi_register_read(int reg) {
    spi_tx_buf[0] = SET_READ_SINGLE_CMD(reg);
    spi_xfer_done = false;
    APP_ERROR_CHECK(nrf_drv_spi_transfer(&m_spi, spi_tx_buf, 2, spi_rx_buf, 2));
    while(spi_xfer_done == false) {};
    return spi_rx_buf[1];
}


/**
 * @brief Poll LPS22 sensor status until reads positive
 * 
 * @param status Which status bit to check for
 * @return uint8_t 0 for success, 1 otherwise
 */
uint8_t lps22_status(uint8_t status) {
    int count = 1000;
    uint8_t data = 0xff;
    do {
        data = spi_register_read(LPS22_STATUS_REG);
        --count;
        if (count < 0) {
            break;
        }
    } while ((data & status) == 0);

    if (count < 0) { return 1; }
    else { return 0;}
}

/**
 * @brief Function to initialize a read from the LPS22 device
 * 
 * @return uint16_t Whether the read begin was successful or not
 */
uint16_t lps22_read_begin() {
    int intRegValue = spi_register_read(LPS22_WHO_AM_I);
    if (intRegValue != LPS22_WHO_AM_I_VALUE) {
        safe_log(5, "The WHO_AM_I is not correct for the LPS22\r\n");
        return 1;
    }
    safe_log(1, "The WHO_AM_IS is correct for the LPS22\r\n");
    // Begin configuration settings
    spi_register_write(LPS22_RES_CONF, 0x0);
    spi_register_write(LPS22_CTRL_REG1, 0x00);
    return 0;
}

/**
 * @brief Trigger SPI to read pressure on LPS22 device
 * 
 * @param pressure_read Pointer to pressure read
 * @return uint16_t Returns >0 if an error occured
 */
uint16_t lps22_read_pressure(uint32_t *pressure_read) {
    if (lps22_read_begin() > 0) {
        return 1;
    }
    spi_register_write(LPS22_CTRL_REG2, 0x1);
    if (lps22_status(0x1) > 0) {
        safe_log(5, "Cannot read LPS22 Pressure Status\r\n");
        return 1;
    }

    uint8_t press_out_h = spi_register_read(LPS22_PRESS_OUT_H) & 0xFF;
    uint8_t press_out_l = spi_register_read(LPS22_PRESS_OUT_L) & 0xFF;
    uint8_t press_out_xl = spi_register_read(LPS22_PRESS_OUT_XL) & 0xFF;

    safe_log(1, "Pressure read high bits: %d\r\n", press_out_h);
    uint32_t value = ((press_out_h << 16) | (press_out_l << 8) | (press_out_xl)) & 0x00FFFFFF;
    safe_log(2, "Pressure read: %lu\r\n", value);
    *pressure_read = value;
    return 0;
}
    

/**
 * @brief Triggert SPI to read temperature on LPS22 device
 * 
 * @param temp_read Pointer to temperature read
 * @return uint16_t Returns >0 if an error occured
 */
uint16_t lps22_read_temperature(uint16_t *temp_read) {
    if (lps22_read_begin() > 0) {
        return 1;
    }
    spi_register_write(LPS22_CTRL_REG2, 0x1);
    if (lps22_status(0x2) > 0) {
        safe_log(5, "Cannot read LPS22 Temperature Status\r\n");
        return 1;
    }

    uint8_t temp_out_h = spi_register_read(LPS22_TEMP_OUT_H) & 0xFF;
    uint8_t temp_out_l = spi_register_read(LPS22_TEMP_OUT_L) & 0xFF;

    uint16_t value = ((temp_out_h << 8) | (temp_out_l));
    safe_log(2, "Temperature read: %d\r\n", value);
    *temp_read = (uint16_t) value;
    return 0;
}


/**
 * @brief Triggers the SAADC to get a voltage read
 * 
 * @return nrf_saadc_value_t SAADC voltage read
 */
ret_code_t single_shot_saadc(nrf_saadc_value_t *battery_read) {
    return nrfx_saadc_sample_convert(0, battery_read);
}


/**
 * @brief Indication function to initiate events to the 
 *      onboard sensor suite. Updates the global state with
 *      sensor reads.
 */
static void read_sensors() {
    safe_log(2, "Reading sensors\r\n");
    struct reading sensor_reads = empty_reading;

    /*
        Battery Reading
    */
    uint16_t battery_read;
    if (single_shot_saadc(&battery_read) > 0) {
        safe_log(5, "Error reading temperature. Setting to invalid read\r\n");
        set_app_status(&app_state, TEMP_MEAS_FAILURE);
        sensor_reads.battery_reading = (uint16_t) INVALID_SENSOR_READ;
    } else {
        sensor_reads.battery_reading = (uint16_t) battery_read;
    }
    safe_log(1, "SAADC value: %d\r\n", sensor_reads.battery_reading);

    /*
        Pressure Reading
    */
    spi_init();
    uint32_t pressure_read;
    if (lps22_read_pressure(&pressure_read) > 0) {
        safe_log(5, "Error reading pressure. Setting to Invalid Read\r\n");
        set_app_status(&app_state, PRESSURE_MEAS_FAIULRE);
        sensor_reads.pressure_reading = (uint32_t) INVALID_SENSOR_READ;
    } else {
        sensor_reads.pressure_reading = pressure_read;
    }
    safe_log(1, "LPS22 Pressure value: %d\r\n", sensor_reads.pressure_reading);

    /*
        Temperature Reading
    */
    uint16_t temperature_read;
    if (lps22_read_temperature(&temperature_read) > 0) {
        safe_log(5, "Error reading temperature. Setting to Invalid Read\r\n");
        set_app_status(&app_state, TEMP_MEAS_FAILURE);
        sensor_reads.temp_reading = (uint16_t) INVALID_SENSOR_READ;
    } else {
        sensor_reads.temp_reading = (uint16_t) temperature_read;
    }
    safe_log(1, "LPS22 Temperature value %d\r\n", sensor_reads.temp_reading);

    nrf_drv_spi_uninit(&m_spi);
    
    add_reading_to_state(&app_state, &sensor_reads);
}



/**
 * @brief Function for initializing services that will be used by the application.
 */
static void services_init(void)
{
    uint32_t           err_code;
    ble_nus_init_t     nus_init;
    nrf_ble_qwr_init_t qwr_init = {0};

    // Initialize Queued Write Module.
    qwr_init.error_handler = nrf_qwr_error_handler;

    err_code = nrf_ble_qwr_init(&m_qwr, &qwr_init);
    APP_ERROR_CHECK(err_code);

    // Initialize NUS.
    memset(&nus_init, 0, sizeof(nus_init));

    nus_init.data_handler = nus_data_handler;

    err_code = ble_nus_init(&m_nus, &nus_init);
    APP_ERROR_CHECK(err_code);
}


/**
 * @brief Function for handling an event from the Connection Parameters Module.
 *
 * @details This function will be called for all events in the Connection Parameters Module
 *          which are passed to the application.
 *
 * @note All this function does is to disconnect. This could have been done by simply setting
 *       the disconnect_on_fail config parameter, but instead we use the event handler
 *       mechanism to demonstrate its use.
 *
 * @param[in] p_evt  Event received from the Connection Parameters Module.
 */
static void on_conn_params_evt(ble_conn_params_evt_t * p_evt)
{
    uint32_t err_code;

    if (p_evt->evt_type == BLE_CONN_PARAMS_EVT_FAILED)
    {
        err_code = sd_ble_gap_disconnect(m_conn_handle, BLE_HCI_CONN_INTERVAL_UNACCEPTABLE);
        APP_ERROR_CHECK(err_code);
    }
}


/**
 * @brief Function for handling errors from the Connection Parameters module.
 *
 * @param[in] nrf_error  Error code containing information about what went wrong.
 */
static void conn_params_error_handler(uint32_t nrf_error)
{
    APP_ERROR_HANDLER(nrf_error);
}


/**
 * @brief Function for initializing the Connection Parameters module.
 */
static void conn_params_init(void)
{
    uint32_t               err_code;
    ble_conn_params_init_t cp_init;

    memset(&cp_init, 0, sizeof(cp_init));

    cp_init.p_conn_params                  = NULL;
    cp_init.first_conn_params_update_delay = FIRST_CONN_PARAMS_UPDATE_DELAY;
    cp_init.next_conn_params_update_delay  = NEXT_CONN_PARAMS_UPDATE_DELAY;
    cp_init.max_conn_params_update_count   = MAX_CONN_PARAMS_UPDATE_COUNT;
    cp_init.start_on_notify_cccd_handle    = BLE_GATT_HANDLE_INVALID;
    cp_init.disconnect_on_fail             = false;
    cp_init.evt_handler                    = on_conn_params_evt;
    cp_init.error_handler                  = conn_params_error_handler;

    err_code = ble_conn_params_init(&cp_init);
    APP_ERROR_CHECK(err_code);
}


/**
 * @brief Function for putting the chip into sleep mode.
 *
 * @note This function will not return.
 */
static void sleep_mode_enter(void)
{
    uint32_t err_code = bsp_indication_set(BSP_INDICATE_IDLE);
    APP_ERROR_CHECK(err_code);

    // Prepare wakeup buttons.
    err_code = bsp_btn_ble_sleep_mode_prepare();
    APP_ERROR_CHECK(err_code);

    // Go to system-off mode (this function will not return; wakeup will cause a reset).
    err_code = sd_power_system_off();
    APP_ERROR_CHECK(err_code);
}


/**
 * @brief Function for handling advertising events.
 *
 * @details This function will be called for advertising events which are passed to the application.
 *
 * @param[in] ble_adv_evt  Advertising event.
 */
static void on_adv_evt(ble_adv_evt_t ble_adv_evt)
{
    uint32_t err_code;

    switch (ble_adv_evt)
    {
        case BLE_ADV_EVT_FAST:
            err_code = bsp_indication_set(BSP_INDICATE_ADVERTISING);
            APP_ERROR_CHECK(err_code);
            break;
        case BLE_ADV_EVT_IDLE:
            sleep_mode_enter();
            break;
        default:
            break;
    }
}


/**
 * @brief Function for handling BLE events.
 *
 * @param[in]   p_ble_evt   Bluetooth stack event.
 * @param[in]   p_context   Unused.
 */
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
{
    uint32_t err_code;

    switch (p_ble_evt->header.evt_id)
    {
        case BLE_GAP_EVT_CONNECTED:
            set_app_status(&app_state, BLE_IS_CONNECTED);
            err_code = bsp_indication_set(BSP_INDICATE_CONNECTED);
            APP_ERROR_CHECK(err_code);
            m_conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
            err_code = nrf_ble_qwr_conn_handle_assign(&m_qwr, m_conn_handle);
            APP_ERROR_CHECK(err_code);
            break;

        case BLE_GAP_EVT_DISCONNECTED:
            // LED indication will be changed when advertising starts.
            clear_app_status(&app_state, BLE_IS_CONNECTED);
            m_conn_handle = BLE_CONN_HANDLE_INVALID;
            break;

        case BLE_GAP_EVT_PHY_UPDATE_REQUEST:
        {
            // NRF_LOG_DEBUG("PHY update request.");
            ble_gap_phys_t const phys =
            {
                .rx_phys = BLE_GAP_PHY_AUTO,
                .tx_phys = BLE_GAP_PHY_AUTO,
            };
            err_code = sd_ble_gap_phy_update(p_ble_evt->evt.gap_evt.conn_handle, &phys);
            APP_ERROR_CHECK(err_code);
        } break;

        case BLE_GAP_EVT_SEC_PARAMS_REQUEST:
            // Pairing not supported
            err_code = sd_ble_gap_sec_params_reply(m_conn_handle, BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP, NULL, NULL);
            APP_ERROR_CHECK(err_code);
            break;

        case BLE_GATTS_EVT_SYS_ATTR_MISSING:
            // No system attributes have been stored.
            err_code = sd_ble_gatts_sys_attr_set(m_conn_handle, NULL, 0, 0);
            APP_ERROR_CHECK(err_code);
            break;

        case BLE_GATTC_EVT_TIMEOUT:
            // Disconnect on GATT Client timeout event.
            err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gattc_evt.conn_handle,
                                             BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
            APP_ERROR_CHECK(err_code);
            break;

        case BLE_GATTS_EVT_TIMEOUT:
            // Disconnect on GATT Server timeout event.
            err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gatts_evt.conn_handle,
                                             BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
            APP_ERROR_CHECK(err_code);
            break;

        default:
            // No implementation needed.
            break;
    }
}


/**
 * @brief Function for the SoftDevice initialization.
 *
 * @details This function initializes the SoftDevice and the BLE event interrupt.
 */
static void ble_stack_init(void)
{
    ret_code_t err_code;

    err_code = nrf_sdh_enable_request();
    APP_ERROR_CHECK(err_code);

    // Configure the BLE stack using the default settings.
    // Fetch the start address of the application RAM.
    uint32_t ram_start = 0;
    err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start);
    APP_ERROR_CHECK(err_code);

    // Enable BLE stack.
    err_code = nrf_sdh_ble_enable(&ram_start);
    APP_ERROR_CHECK(err_code);

    // Register a handler for BLE events.
    NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);
}


/**
 * @brief Function for handling events from the GATT library.
 */
void gatt_evt_handler(nrf_ble_gatt_t * p_gatt, nrf_ble_gatt_evt_t const * p_evt)
{
    if ((m_conn_handle == p_evt->conn_handle) && (p_evt->evt_id == NRF_BLE_GATT_EVT_ATT_MTU_UPDATED))
    {
        m_ble_nus_max_data_len = p_evt->params.att_mtu_effective - OPCODE_LENGTH - HANDLE_LENGTH;
        // NRF_LOG_INFO("Data len is set to 0x%X(%d)", m_ble_nus_max_data_len, m_ble_nus_max_data_len);
    }
    // NRF_LOG_DEBUG("ATT MTU exchange completed. central 0x%x peripheral 0x%x",
    //               p_gatt->att_mtu_desired_central,
    //               p_gatt->att_mtu_desired_periph);
}



/**
 * @brief Function for initializing the Advertising functionality.
 */
static void advertising_init(void)
{
    uint32_t               err_code;
    ble_advertising_init_t init;

    memset(&init, 0, sizeof(init));

    init.advdata.name_type          = BLE_ADVDATA_FULL_NAME;
    init.advdata.include_appearance = false;
    // FIXME: Remove always-advertising
    // init.advdata.flags              = BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE;
    init.advdata.flags              = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;

    init.srdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
    init.srdata.uuids_complete.p_uuids  = m_adv_uuids;

    init.config.ble_adv_fast_enabled  = true;
    init.config.ble_adv_fast_interval = APP_ADV_INTERVAL;
    init.config.ble_adv_fast_timeout  = APP_ADV_DURATION;

    // Don't advertise when we turn off the radio.
    init.config.ble_adv_on_disconnect_disabled = true;  

    init.evt_handler = on_adv_evt;

    err_code = ble_advertising_init(&m_advertising, &init);
    APP_ERROR_CHECK(err_code);

    ble_advertising_conn_cfg_tag_set(&m_advertising, APP_BLE_CONN_CFG_TAG);
}


/**
 * @brief Function for initializing power management.
 */
static void power_management_init(void)
{
    ret_code_t err_code;
    err_code = nrf_pwr_mgmt_init();
    APP_ERROR_CHECK(err_code);
}


/**
 * @brief Function for handling the idle state (main loop).
 *
 * @details If there is no pending log operation, then sleep until next the next event occurs.
 */
static void idle_state_handle(void)
{
    if (NRF_LOG_PROCESS() == false)
    {
        nrf_pwr_mgmt_run();
    }
}


/**
 * @brief Function for starting advertising.
 */
static void advertising_start(void)
{
    uint32_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);
    APP_ERROR_CHECK(err_code);
}

/**
 * @brief Shut off advertising and the BLE connection
 * 
 */
static void advertising_stop(void)
{
    uint32_t err_code;

    // Disconnect if we're connecting to a peripheral
    if (m_conn_handle != BLE_CONN_HANDLE_INVALID) {
        err_code = sd_ble_gap_disconnect(m_conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
        if (err_code != NRF_ERROR_INVALID_STATE) {
            APP_ERROR_CHECK(err_code);
        }
        // printf("Disconnecting from peripheral: %d\n", err_code);
    }

    // Turn off advertising, if we currently are
    err_code = sd_ble_gap_adv_stop(m_advertising.adv_handle);
    // printf("Disabling advertising: %d\n", err_code);
    if (err_code != NRF_ERROR_INVALID_STATE) {
        APP_ERROR_CHECK(err_code);
    }
}

/**
 * @brief Switch between advertising (forming a connection) and not
 * 
 * @param is_advertising What to change advertising to
 */
static void change_advertising_state(bool is_advertising)
{
    if (!is_advertising) {
        advertising_stop();
        clear_app_status(&app_state, BLE_IS_ADVERTISING);
        clear_app_status(&app_state, BLE_IS_CONNECTED);
        app_state.no_advertising_cycles = 0;
        app_state.num_sensor_reads_since_transmission = 0;
        app_state.already_sent_packet = false;
    } else {
        advertising_start();
        set_app_status(&app_state, BLE_IS_ADVERTISING);
        app_state.advertising_cycles = 0;
    }
}

/**
 * @brief Timeout handler for the repeated timer.
 */
static void repeated_timer_handler(void * p_context)
{

    // Read sensors
    if (should_read_sensor()) {
        read_sensors();
    }

    // Update advertising state
    if (get_app_status(&app_state, BLE_IS_ADVERTISING)) { 
        if (app_state.advertising_cycles >= app_state.params.SET_CYCLES_ADV_WINDOW) {
            safe_log(4, "Shutting off advertising\r\n");
            change_advertising_state(false);
        }
    } else {
        if (app_state.no_advertising_cycles >= app_state.params.SET_CYCLES_NOADV_WINDOW) {
            // Get fresh sensor reads before having BLE reading
            safe_log(4, "Turning on advertising\r\n");
            change_advertising_state(true);
        }
    }


    if (get_app_status(&app_state, BLE_IS_ADVERTISING) && 
        get_app_status(&app_state, BLE_IS_CONNECTED) &&
        !app_state.already_sent_packet) {
        transmit_data();
    }
}

/**
 * @brief Function for setting up the GPIO pins.
 */
static void gpio_init(void)
{
    nrf_gpio_cfg_output(PUMP_PIN_LEFT_FWD);
    nrf_gpio_cfg_output(PUMP_PIN_LEFT_REV);
    nrf_gpio_cfg_output(PUMP_PIN_RIGHT_FWD);
    nrf_gpio_cfg_output(PUMP_PIN_RIGHT_REV);

    gpio_stop_pins();

}

/**
 * @brief Create timers.
 */
static void create_timers()
{
    ret_code_t err_code;

    // Create timers
    err_code = app_timer_create(&m_repeated_timer_id,
                                APP_TIMER_MODE_REPEATED,
                                repeated_timer_handler);
    APP_ERROR_CHECK(err_code);
}


/**
 * @brief Application main function.
 */
int main(void)
{
    bool erase_bonds;
    initialize_state(&app_state);

    // Initialize.
    #if defined(ENABLE_PRINTING)
    uart_init();
    #endif
    safe_log(1, "----------------------\r\n");
    safe_log(1, "Starting application\r\n");
    log_init();
    timers_init();
    buttons_leds_init(&erase_bonds);
    power_management_init();
    ble_stack_init();
    gap_params_init();
    gatt_init();
    services_init();
    advertising_init();
    conn_params_init();
    gpio_init();
    app_timer_init();
    create_timers();

    saadc_init();
    calibrate_saadc();

    change_advertising_state(true);

    ret_code_t err_code = app_timer_start(m_repeated_timer_id, CYCLE_LEN, NULL);
    APP_ERROR_CHECK(err_code);

    // Enter main loop.
    for (;;)
    {
        idle_state_handle();
    }
}

Parents
  • "we have not been able to get the SPI feature to work" -  does the BLE work? If not, most likely cause is not using a 32kHz crystal (or using a module with no 32kHz crystal) without setting the defines up for internal 32kHz RC operation.

    If the BLE does work ok then maybe post the schematic; you already have high drive SPI signals, so unless there is an FTDI preventing proper board reset or the SPI output signals MOSI/SS/SCK are driven high before the LPS22 VCC (if LPS22 VCC is provided by a nRF52 port pin) causing phantom power issues there's not much that can go wrong. If a port pin is used to power the LPS22 the VCC must be stable (say 10mSecs) before driving MOSI/SS/SCK  high which happens on the SPI init. It's ok to let those pins go high if there is a pull-up to LPS22 VCC but not to drive them from a different supply (eg nRF52832 VCC) prematurely. If both VCCs are the same (electrically connected) then of course no issues with sequencing.

  • Thanks for your fast response - I greatly appreciate it.

    The BLE with advertising and UART-send works great, so it sounds like it is not a 32kHz crystal issue.

    Looking over the schematic, the Nordic chip and the LPS22 have the same electrically-connected VCC, though we don't have a decoupling capacitor next to VCC next to LPS22 as recommended in the datasheet. However, we were able to get SPI reads on a modified version of the Adafruit LPS22 Breakout board that also removed the decoupling capacitator. 

    We are presently checking the stability of that VCC during initialization.

    We have observed also that the WHO_AM_I register on the LPS22 is variable on each read as well (on the custom boards); don't know if that gives any other useful information. 


  • Is the trace length between nRF52832 and LPS22 longer than the dev kit? More than (say) 2" (50mm) and the reflections on the SCK input at the LPS22 causes incorrect clocking without suitable termination or at least a series resistor at signal source; often a series resistor of 22R is used, not correct impedance but sufficient without degrading the signal. What is VCC by the way? LPS22HH is 0xB3, I checked - and I use the same part.

    Assuming 4-wire SPI these are the pin mappings I use:

    //  1 Vdd_IO Power supply for I/O pins
    //  2 SCL SPI serial port clock
    //  3 GND
    //  4 4-wire SPI MOSI
    //  5 4-wire SPI MISO
    //  6 /CS SPI active-low enable
    //  7 INT_DRDY Interrupt (typically NC)
    //  8 GND 0 V supply
    //  9 GND 0 V supply
    // 10 VDD Power supply

    3-wire also works

    Edit: also note that sending a second unknown byte is not a good idea; either send 1 byte (receive 2 anyway) with NRF_SPIMn->ORC set to 0x00 or set 2nd byte in Tx to 0x00 and send 2.

    // for who am i change
     APP_ERROR_CHECK(nrf_drv_spi_transfer(&m_spi, spi_tx_buf, 2, spi_rx_buf, 2));
    to
     APP_ERROR_CHECK(nrf_drv_spi_transfer(&m_spi, spi_tx_buf, 1, spi_rx_buf, 2));
    .. provided ORC is set to 0x00
    
    .. or set 2nd tx byte to something other than whatever was left in the spi_tx_buf
       buffer from previous transfer ..

  • Thanks for the reply and continued support - I really do appreciate it. 

    On the nRF52 Development Kit (the working one) our trace is roughly 4" as wires connect to the external breakout board. However, on our custom board (not working), the trace is ~2mm (factory manufactured PCB board). 

    I had interchanged VCC and VDD, but we operate the custom board at 3.3V. 

    And I should have specified further - we are using LPS22BH, which has WHO_AM_I of 0xB1. Hadn't realized there was a LPS22HH version, apologies for the confusion.

    Cleaned up the buffer size as suggested but still running into the same issue. 

    Our pin mappings are:

    #define LPS22_MISO                       15
    #define LPS22_MOSI                       14
    #define LPS22_SCK                        12
    #define LPS22_SS                         17


    With the LPS22 getting power from the VCC powering the board.

    I appreciate all the ideas on what could be incorrect - I really appreciate. I think I also remember reading something on another log about UART causing a difference between nRF52 Dev Kits and stand-alone nRF52 boards? Is there any module side effects like this I should be sure to watch out for?

    Thanks again,
    Conner



  • Those mappings are different to the one you posted in the code snippet above, as originally SS was pin 16 not pin 17; is that intentional?. Other than that the only pins which don't work well as SPI pins are those designated as Quiet, or potential NFC or potential Reset, but none of those pins are in the four you are using.

    Given that the part is the LPS22HB I would suggest trying the 4 different SPI modes and see if any work; the HH works with this mode, but not sure if the HB is the same:

    // CPOL 0 -- clock polarity active high, CPHA 1 -- sample on trailing clock edge, send Msb first

  • Thanks for the response!

    Apologies for the pin confusion! About the pins, I had made a mistake when copying the code into the website, sorry about that. We have 

    #if defined(USING_DEV_BOARD)
    /* SAADC Pin */
    #define SAADC_BATTERY_PIN               NRF_SAADC_INPUT_AIN3 // 5
    /* SPI Pins */
    #define LPS22_MISO                       15
    #define LPS22_MOSI                       14
    #define LPS22_SCK                        12
    #define LPS22_SS                         16
    #else
    /* SAADC Pin */
    #define SAADC_BATTERY_PIN               NRF_SAADC_INPUT_AIN6 // 30
    /* SPI Pins */
    #define LPS22_MISO                       15
    #define LPS22_MOSI                       14
    #define LPS22_SCK                        12
    #define LPS22_SS                         17
    #endif

    to block off flashing to the nRF52 Development Board versus the production chip, and in cutting away fluff chose the wrong half of that. 

    Thanks for the advice on switching the modes. I have been circling through, and while the received WHO_AM_I value is changing (MODE_0: 0, MODE_1: 102, MODE_2: 152, MODE_3: 0), none are matching the expected value. 

    Here is just the current consolidated SPI code that yielded these numbers.

    #define LPS22_MISO                       15
    #define LPS22_MOSI                       14
    #define LPS22_SCK                        12
    #define LPS22_SS                         17
    
    #define SPI_BUFSIZE                         8
    #define SPI_INSTANCE                        0
    
    #define SET_READ_SINGLE_CMD(x)  (x | 0x80)      // Indication that we are reading data
    #define SET_WRITE_SINGLE_CMD(x) (x & ~(0xC0))   // Indication that we are writing data
    
    uint8_t spi_tx_buf[SPI_BUFSIZE];
    uint8_t spi_rx_buf[SPI_BUFSIZE];
    
    static volatile bool spi_xfer_done;
    
    static const nrf_drv_spi_t m_spi = NRF_DRV_SPI_INSTANCE(SPI_INSTANCE);
    
    void spi_event_handler(nrf_drv_spi_evt_t const * p_event, void * p_context) {
        spi_xfer_done = true;
    }
    
    static void spi_init(void) {
        nrf_drv_spi_config_t spi_config = NRF_DRV_SPI_DEFAULT_CONFIG;
        spi_config.ss_pin       = LPS22_SS;
        spi_config.miso_pin     = LPS22_MISO;
        spi_config.mosi_pin     = LPS22_MOSI;
        spi_config.sck_pin      = LPS22_SCK;
        spi_config.frequency    = NRF_DRV_SPI_FREQ_4M;
        spi_config.mode         = NRF_DRV_SPI_MODE_0;
    
        APP_ERROR_CHECK(nrf_drv_spi_init(&m_spi, &spi_config, spi_event_handler, NULL));
    
        nrf_gpio_cfg(LPS22_MOSI, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_CONNECT, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
        nrf_gpio_cfg(LPS22_SCK,  NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_CONNECT, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
        nrf_gpio_cfg(LPS22_SS,   NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_CONNECT, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
    }
    
    
    void spi_register_write(int reg, int data) {
        spi_tx_buf[0] = SET_WRITE_SINGLE_CMD(reg);
        spi_tx_buf[1] = data;
        spi_xfer_done = false;
        APP_ERROR_CHECK(nrf_drv_spi_transfer(&m_spi, spi_tx_buf, 2, spi_rx_buf, 0));
        while(spi_xfer_done == false) {};
    }
    
    uint8_t spi_register_read(int reg) {
        spi_tx_buf[0] = SET_READ_SINGLE_CMD(reg);
        spi_xfer_done = false;
        APP_ERROR_CHECK(nrf_drv_spi_transfer(&m_spi, spi_tx_buf, 1, spi_rx_buf, 2));
        while(spi_xfer_done == false) {};
        return spi_rx_buf[1];
    }
    


  • Talking to the hardware folks - just realized something that may be pretty important. We also have other SPI sensors (one at pin 9 and one at pin 13) that share the same MISO, MOSI, SCK lines. This would also be a difference between the nRF52 Development Kit and the custom boards. We don't intend to work with these boards for this custom board iteration, though they are on the boards. Could they be interfering the signal? Is there special consideration we should take for them.

    Thanks again for your time.

Reply
  • Talking to the hardware folks - just realized something that may be pretty important. We also have other SPI sensors (one at pin 9 and one at pin 13) that share the same MISO, MOSI, SCK lines. This would also be a difference between the nRF52 Development Kit and the custom boards. We don't intend to work with these boards for this custom board iteration, though they are on the boards. Could they be interfering the signal? Is there special consideration we should take for them.

    Thanks again for your time.

Children
  • One assumes the SPI devices all share the same VCC and that their SS (/CS) lines on pins 9 and 13 are all set high; if not the issue you describe may be caused by that difference, shared VCC is particularly important. Pin 9 in particular is particularly troublesome as it is not an i/o pin by default; CONFIG_NFCT_PINS_AS_GPIOS is required to be defined in your project preprocessor settings. Probability that this is the root cause of the problem is 83%.

    Ah, time to run a simple pin check test which should be at the start of main() before any SPI initialisation is done. Two sets of code, first tests that each pin is not accidentally connected to another and the second that each pin is not connected to VCC or GND. The tests rely on a datasheet mistake, namely that each i/o pin has an optional pull-up or pull-down of about 14k which can be applied regardless of whether the pin is input, output or used as a peripheral pin. Note you can write code or just manually use the debugger SPI peripheral. Using code is better because it can be incorporated as a board power-on-test (POST).

    Test sequence pin cross-talk:

    set MOSI, MISO, SCK, SS, SS2, SS3 to inputs with pull-down enabled
    for each pin of 6 pins
      set just the 1 pin as input with pull-up enabled
      test that pin with pull-up, should be '1'
      test other 5 pins, all 3 pins should be '0'
    
    set MOSI, MISO, SCK, SS, SS2, SS3 to inputs with pull-up enabled
    for each pin of 6 pins
      set just the 1 pin as input with pull-down enabled
      test that pin with pull-down, should be '0'
      test other 5 pins, all 3 pins should be '1'

    Test sequence pin shorted to VCC or GND or another pin

    stuck-at-0 test
    set MOSI, MISO, SCK, SS, SS2, SS3 to inputs with pull-down enabled
    for each pin of 6 pins
      set just the 1 pin as output with pull-up disabled
      set that pin to '1'
      test all 6 pins, all 6 pins should be '0' except that pin which should be '1'
      set that pin to '0'
      test all 6 pins, all 6 pins should be '0'
      revert that pin to input with pull-down enabled
      
    stuck-at-1 test
    set MOSI, MISO, SCK, SS, SS2, SS3 to inputs with pull-up enabled
    for each pin of 6 pins
      set just the 1 pin as output with pull-up disabled
      set that pin to '0'
      test all 6 pins, all 6 pins should be '1' except that pin which should be '0'
      set that pin to '1'
      test all 6 pins, all 6 pins should be '1'
      revert that pin to input with pull-up enabled

Related