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();
}
}