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. 


  • Circling back. Completed testing - no stability issues. Custom board has very stable 3.3V on VCC throughout SPI initialization

Reply Children
  • Who am i is 0xB3, not 0xB1 methinks ..

    #define LPS22HH_WHO_AM_I_REG_ADDR 0x0F
    #define LPS22HH_WHO_AM_I_RESP     0xB3

    Also 125kHz is pretty slow, I would bump the clock speed to 8MHz and double-check that the default is SPI Mode 0 when using the default NRF_DRV_SPI_DEFAULT_CONFIG

  • Hi,

    I apologize - I had miswrote earlier. The WHO_AM_I was constantly returning 0, not a variable value. I believe the WHO_AM_I is still 0xB1 on the LPS22, but am having no luck either way. 

    Ramped it up to 8MHz and set default SPI Mode to 0, still running into the same issue. We are ordering some additional custom boards to check if hardware, but there doesn't seem to be any visible difference between the non-working board we are using and the development kit. 

  • 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

Related