Hello, I am using nRF52 DK and SDK V15.2.0 with a Nordic PPK to measure current draw. I have read tons and tons of questions posted here but am still unable to figure out what is going on with my application. I have implemented a protocol as follows:
- read some sensor data with SAADC
- turn off SAADC/gpiote/etc
- advertise for 5 seconds
- After connection and sending of data, or timeout, wait for 10 seconds with app timer
- repeat (read data, turn off peripherals, advertise)
I would like my device to enter low power sleep mode during the 10 second waiting period between reading data/advertising. However, I am finding that the device only enters system ON "low power sleep mode" once, and then always returns to a high power mode. As you can see in the attached graphs, the first iteration of this protocol is relatively low power (~500uA draw) throughout advertising and the app timer. However, when the timer ends and data reading/advertising occurs again, the device begins to draw ~7.5mA. The device never returns to the low power state of 500uA draw. Even if I power cycle my nRF52 DK, it remains in the 7.5mA draw range.
500uA is seen in the start, then 7.5mA is seen in the consecutive iterations of the protocol:
Even when I power cycle the DK, once it enters the 7.5mA draw it never goes back to 500mA (even if I power off, shut down power profiler app and reopen again):
My protocol for flashing and testing power is as follows (I have SB9 cut):
- Connect jumper across current measurement pins and connect DK to my mac
- Flash the softdevice and application hex files using nRF connect, erasing the chip before flashing occurs
- Power off DK, remove jumper across power measurement pins, and connect power profiler board
- Open up the power profiler program in nRF connect, turn on the DK, then select the board
- Program the PPK when nRF connect asks, then begin monitoring current draw
The nRF DK only is in the 500uA draw range during the first iteration after flashing the code. Once it begins to draw 7.5mA, this never changes even if I power cycle the DK and reconnect/reopen the power profiling app. I'll attach my complete main.c and sdk_config.h below, however I use the following general method to enter low power system ON mode, with nrf_pwr_mgmt_run:
// This function is called after data is sent successfully or // advertising timeout is reached void init_and_start_app_timer() { ret_code_t err_code; err_code = app_timer_start(m_timer_id, APP_TIMER_TICKS(DATA_INTERVAL), NULL); APP_ERROR_CHECK(err_code); idle_state_handle(); NRF_LOG_INFO("TIMER STARTED (single shot) \n"); NRF_LOG_FLUSH(); } // This is the callback function for my app timer void single_shot_timer_handler() { // disable timer ret_code_t err_code; err_code = app_timer_stop(m_timer_id); APP_ERROR_CHECK(err_code); // Delay to ensure appropriate timing enable_isfet_circuit(); // Slight delay for ISFET to achieve optimal ISFET reading nrf_delay_ms(10); // Begin SAADC initialization/start enable_pH_voltage_reading(); } // This function disables all peripherals associated with reading // data using the SAADC void disable_pH_voltage_reading(void) { nrfx_timer_uninit(&m_timer); nrfx_ppi_channel_free(m_ppi_channel); nrfx_saadc_uninit(); while(nrfx_saadc_is_busy()) { // make sure SAADC is not busy } // *** DISABLE BIASING CIRCUITRY *** disable_isfet_circuit(); } // This function sets enable pin for ISFET circuitry to LOW // and uninitializes the gpiote peripheral void disable_isfet_circuit(void) { nrfx_gpiote_out_clear(ENABLE_ISFET_PIN); nrfx_gpiote_uninit(); } /**@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) { nrf_pwr_mgmt_run(); } /**@brief Application main function. */ int main(void) { bool erase_bonds = false; // Call function very first to turn on the chip turn_chip_power_on(); timers_init(); power_management_init(); // Initialize fds and check for calibration values fds_init_helper(); check_calibration_state(); // Continue with adjusted calibration state ble_stack_init(); gap_params_init(); gatt_init(); services_init(); advertising_init(); conn_params_init(); peer_manager_init(); // Start intermittent data reading <> advertising protocol enable_isfet_circuit(); nrf_delay_ms(10); enable_pH_voltage_reading(); // Enter main loop for power management while (true) { idle_state_handle(); } }
/** * Copyright (c) 2014 - 2018, Nordic Semiconductor ASA * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form, except as embedded into a Nordic * Semiconductor ASA integrated circuit in a product or a software update for * such product, must reproduce the above copyright notice, this list of * conditions and the following disclaimer in the documentation and/or other * materials provided with the distribution. * * 3. Neither the name of Nordic Semiconductor ASA nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * 4. This software, with or without modification, must only be used with a * Nordic Semiconductor ASA integrated circuit. * * 5. Any software provided in binary form under this license must not be reverse * engineered, decompiled, modified and/or disassembled. * * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ /** @file * * @defgroup ble_sdk_uart_over_ble_main main.c * @{ * @ingroup ble_sdk_app_nus_eval * @brief UART over BLE application main file. * * This file contains the source code for a sample application that uses the * Nordic UART service. * This application uses the @ref srvlib_conn_params module. */ #include <stdint.h> #include <string.h> #include <time.h> #include <stdlib.h> #include <math.h> #include "nordic_common.h" #include "boards.h" #include "nrf.h" #include "nrf_sdh.h" #include "nrf_sdh_soc.h" #include "nrf_sdh_ble.h" #include "nrf_ble_qwr.h" #include "nrf_pwr_mgmt.h" #include "nrf_ble_gatt.h" #include "nrf_saadc.h" #include "nrf_drv_clock.h" #include "nrf_delay.h" #include "nrf_drv_gpiote.h" #include "nrf_drv_saadc.h" #include "nrf_drv_ppi.h" #include "nrf_drv_timer.h" #include "nrfx_ppi.h" #include "nrf_timer.h" #include "nrfx_saadc.h" #include "ble_nus.h" #include "ble_hci.h" #include "ble_advdata.h" #include "ble_advertising.h" #include "ble_conn_params.h" #include "app_timer.h" #include "app_uart.h" #include "app_util_platform.h" #include "app_fifo.h" #include "app_pwm.h" #include "app_error.h" #include "nrf_log.h" #include "nrf_log_ctrl.h" #include "nrf_log_default_backends.h" // Included for peer manager #include "nrf_fstorage.h" #include "nrf_fstorage_sd.h" #include "fds.h" #include "peer_manager.h" #include "peer_manager_handler.h" #include "ble_conn_state.h" // Included for persisent flash reads/writes #include "fds.h" #include "nrf_fstorage.h" #if defined (UART_PRESENT) #include "nrf_uart.h" #endif #if defined (UARTE_PRESENT) #include "nrf_uarte.h" #endif #define APP_BLE_CONN_CFG_TAG 1 /**< A tag identifying the SoftDevice BLE configuration. */ #define DEVICE_NAME "Lura_Health_Test" /**< Name of device. Will be included in the advertising data. */ #define NUS_SERVICE_UUID_TYPE BLE_UUID_TYPE_VENDOR_BEGIN /**< UUID type for the Nordic UART Service (vendor specific). */ #define APP_BLE_OBSERVER_PRIO 3 /**< Application's BLE observer priority. You shouldn't need to modify this value. */ #define APP_ADV_INTERVAL 325 /**< The advertising interval (in units of 0.625 ms. This value corresponds to 40 ms). */ #define APP_ADV_DURATION 500 /**< The advertising duration (180 seconds) in units of 10 milliseconds. */ #define MIN_CONN_INTERVAL MSEC_TO_UNITS(20, UNIT_1_25_MS) /**< Minimum acceptable connection interval (20 ms), Connection interval uses 1.25 ms units. */ #define MAX_CONN_INTERVAL MSEC_TO_UNITS(200, UNIT_1_25_MS) /**< Maximum acceptable connection interval (200 ms), Connection interval uses 1.25 ms units. */ #define SLAVE_LATENCY 0 /**< Slave latency. */ #define CONN_SUP_TIMEOUT MSEC_TO_UNITS(5000, UNIT_10_MS) /**< Connection supervisory timeout (4 seconds), Supervision Timeout uses 10 ms units. */ #define FIRST_CONN_PARAMS_UPDATE_DELAY APP_TIMER_TICKS(5000) /**< Time from initiating event (connect or start of notification) to first time sd_ble_gap_conn_param_update is called (5 seconds). */ #define NEXT_CONN_PARAMS_UPDATE_DELAY APP_TIMER_TICKS(30000) /**< Time between each call to sd_ble_gap_conn_param_update after the first call (30 seconds). */ #define MAX_CONN_PARAMS_UPDATE_COUNT 3 /**< Number of attempts before giving up the connection parameter negotiation. */ #define DEAD_BEEF 0xDEADBEEF /**< Value used as error code on stack dump, can be used to identify stack location on stack unwind. */ #define UART_TX_BUF_SIZE 256 /**< UART TX buffer size. */ #define UART_RX_BUF_SIZE 256 /**< UART RX buffer size. */ #define SEC_PARAM_BOND 1 /**< Perform bonding. */ #define SEC_PARAM_MITM 0 /**< Man In The Middle protection not required. */ #define SEC_PARAM_LESC 0 /**< LE Secure Connections not enabled. */ #define SEC_PARAM_KEYPRESS 0 /**< Keypress notifications not enabled. */ #define SEC_PARAM_IO_CAPABILITIES BLE_GAP_IO_CAPS_NONE /**< No I/O capabilities. */ #define SEC_PARAM_OOB 0 /**< Out Of Band data not available. */ #define SEC_PARAM_MIN_KEY_SIZE 7 /**< Minimum encryption key size. */ #define SEC_PARAM_MAX_KEY_SIZE 16 /**< Maximum encryption key size. */ #define SAMPLES_IN_BUFFER 50 /**< SAADC buffer > */ #define DATA_INTERVAL 10000 #define NRF_SAADC_CUSTOM_CHANNEL_CONFIG_SE(PIN_P) \ { \ .resistor_p = NRF_SAADC_RESISTOR_DISABLED, \ .resistor_n = NRF_SAADC_RESISTOR_DISABLED, \ .gain = NRF_SAADC_GAIN1_5, \ .reference = NRF_SAADC_REFERENCE_INTERNAL, \ .acq_time = NRF_SAADC_ACQTIME_10US, \ .mode = NRF_SAADC_MODE_SINGLE_ENDED, \ .burst = NRF_SAADC_BURST_DISABLED, \ .pin_p = (nrf_saadc_input_t)(PIN_P), \ .pin_n = NRF_SAADC_INPUT_DISABLED \ } /* UNDEFS FOR DEBUGGING */ #undef RX_PIN_NUMBER #undef RTS_PIN_NUMBER #undef LED_4 #undef LED_STOP BLE_NUS_DEF(m_nus, NRF_SDH_BLE_TOTAL_LINK_COUNT); /**< BLE NUS service instance. */ NRF_BLE_GATT_DEF(m_gatt); /**< GATT module instance. */ NRF_BLE_QWR_DEF(m_qwr); /**< Context for the Queued Write module.*/ BLE_ADVERTISING_DEF(m_advertising); /**< Advertising module instance. */ APP_TIMER_DEF(m_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} }; /* Lura Health nRF52810 port assignments */ #define ENABLE_ISFET_PIN 8 #define CHIP_POWER_PIN 12 /* GLOBALS */ uint32_t AVG_PH_VAL = 0; uint32_t AVG_BATT_VAL = 0; uint32_t AVG_TEMP_VAL = 0; bool PH_IS_READ = false; bool BATTERY_IS_READ = false; bool SAADC_CALIBRATED = false; bool CONNECTION_MADE = false; bool CAL_MODE = false; bool READ_CAL_DATA = false; bool PT1_READ = false; bool PT2_READ = false; bool PT3_READ = false; double PT1_PH_VAL = 0; double PT1_MV_VAL = 0; double PT2_PH_VAL = 0; double PT2_MV_VAL = 0; double PT3_PH_VAL = 0; double PT3_MV_VAL = 0; int NUM_CAL_PTS = 0; float MVAL_CALIBRATION = 0; float BVAL_CALIBRATION = 0; float RVAL_CALIBRATION = 0; float CAL_PERFORMED = 0; static volatile uint8_t write_flag=0; // Byte array to store total packet uint8_t total_packet[] = {48,48,48,48,44, /* real pH value, comma */ 48,48,48,48,44, /* Temperature, comma */ 48,48,48,48,44, /* Battery value, comma */ 48,48,48,48,10}; /* raw pH value, EOL */ // Total size of bluetooth packet uint16_t total_size = 20; /* Used for reading/writing calibration values to flash */ #define MVAL_FILE_ID 0x1110 #define MVAL_REC_KEY 0x1111 #define BVAL_FILE_ID 0x2220 #define BVAL_REC_KEY 0x2221 #define RVAL_FILE_ID 0x3330 #define RVAL_REC_KEY 0x3331 #define CAL_DONE_FILE_ID 0x4440 #define CAL_DONE_REC_KEY 0x4441 static const nrf_drv_timer_t m_timer = NRF_DRV_TIMER_INSTANCE(1); static nrf_saadc_value_t m_buffer_pool[1][SAMPLES_IN_BUFFER]; static nrf_ppi_channel_t m_ppi_channel; // Forward declarations void init_and_start_app_timer (void); void send_data_and_restart_timer(void); void enable_pH_voltage_reading (void); void disable_pH_voltage_reading (void); void saadc_init (void); void enable_isfet_circuit (void); void disable_isfet_circuit (void); void turn_chip_power_on (void); void turn_chip_power_off (void); void restart_saadc (void); void restart_pH_interval_timer (void); void write_cal_values_to_flash (void); void linreg (int num, double x[], double y[]); void perform_calibration (uint8_t cal_pts); double calculate_pH_from_mV (uint32_t ph_val); static void advertising_start (bool erase_bonds); uint32_t saadc_result_to_mv (uint32_t saadc_result); /**@brief Function for assert macro callback. * * @details This function will be called in case of an assert in the SoftDevice. * * @warning This handler is an example only and does not fit a final product. * You need to analyse how your product is supposed to react in case of * Assert. * @warning On assert from the SoftDevice, the system can only recover on reset. * * @param[in] line_num Line number of the failing ASSERT call. * @param[in] p_file_name File name of the failing ASSERT call. */ void assert_nrf_callback(uint16_t line_num, const uint8_t * p_file_name) { app_error_handler(DEAD_BEEF, line_num, p_file_name); } /**@brief Function for handling Peer Manager events. * * @param[in] p_evt Peer Manager event. */ static void pm_evt_handler(pm_evt_t const * p_evt) { NRF_LOG_INFO("ENTERED PM_EVT_HANDLER\n"); pm_handler_on_pm_evt(p_evt); pm_handler_flash_clean(p_evt); switch (p_evt->evt_id) { case PM_EVT_CONN_SEC_SUCCEEDED: NRF_LOG_INFO("PM_EVT_CONN_SEC_SUCCEEDED"); NRF_LOG_FLUSH(); break; case PM_EVT_PEERS_DELETE_SUCCEEDED: NRF_LOG_INFO("PM_EVT_PEERS_DELETE_SUCCEEDED"); NRF_LOG_FLUSH(); advertising_start(false); break; case PM_EVT_BONDED_PEER_CONNECTED: NRF_LOG_INFO("PM_EVT_BONDED_PEER_CONNECTED"); NRF_LOG_FLUSH(); break; case PM_EVT_CONN_SEC_CONFIG_REQ: { // Allow pairing request from an already bonded peer. NRF_LOG_INFO("PM_EVT_CONN_SEC_CONFIG_REQ"); NRF_LOG_FLUSH(); pm_conn_sec_config_t conn_sec_config = {.allow_repairing = true}; pm_conn_sec_config_reply(p_evt->conn_handle, &conn_sec_config); } break; default: break; } } /**@brief Function for the GAP initialization. * * @details This function will set up all the necessary GAP (Generic Access * Profile) parameters of the device. It also sets the permissions * and appearance. */ void gap_params_init(void) { uint32_t err_code; ble_gap_conn_params_t gap_conn_params; ble_gap_conn_sec_mode_t sec_mode; BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode); err_code = sd_ble_gap_device_name_set(&sec_mode, (const uint8_t *)DEVICE_NAME, strlen(DEVICE_NAME)); APP_ERROR_CHECK(err_code); memset(&gap_conn_params, 0, sizeof(gap_conn_params)); gap_conn_params.min_conn_interval = MIN_CONN_INTERVAL; gap_conn_params.max_conn_interval = MAX_CONN_INTERVAL; gap_conn_params.slave_latency = SLAVE_LATENCY; gap_conn_params.conn_sup_timeout = CONN_SUP_TIMEOUT; err_code = sd_ble_gap_ppcp_set(&gap_conn_params); APP_ERROR_CHECK(err_code); } /**@brief Function for handling Queued Write Module errors. * * @details A pointer to this function will be passed to each service which may * need to inform the application about an error. * * @param[in] nrf_error Error code containing information about what went wrong. */ static void nrf_qwr_error_handler(uint32_t nrf_error) { APP_ERROR_HANDLER(nrf_error); } // Helper function void substring(char s[], char sub[], int p, int l) { int c = 0; while (c < l) { sub[c] = s[p+c-1]; c++; } sub[c] = '\0'; } // Read saadc values for temperature, battery level, and pH to store for calibration void read_saadc_for_calibration(void) { int NUM_SAMPLES = 50; nrf_saadc_value_t temp_val = 0; ret_code_t err_code; disable_pH_voltage_reading(); AVG_PH_VAL = 0; READ_CAL_DATA = true; PH_IS_READ = false; // Make sure isfet circuit is enabled for saadc readings enable_isfet_circuit(); nrf_delay_ms(10); // Take saadc readings for pH, temp and battery enable_pH_voltage_reading(); for (int i = 0; i < NUM_SAMPLES; i++) { err_code = nrfx_saadc_sample_convert(0, &temp_val); APP_ERROR_CHECK(err_code); AVG_PH_VAL += saadc_result_to_mv(temp_val); } AVG_PH_VAL = AVG_PH_VAL / NUM_SAMPLES; // Assign averaged readings to the correct calibration point if(!PT1_READ){ PT1_MV_VAL = (double)AVG_PH_VAL; PT1_READ = true; } else if (PT1_READ && !PT2_READ){ PT2_MV_VAL = (double)AVG_PH_VAL; PT2_READ = true; } else if (PT1_READ && PT2_READ && !PT3_READ){ PT3_MV_VAL = (double)AVG_PH_VAL; PT3_READ = true; } disable_pH_voltage_reading(); } /* Helper function to clear calibration global state variables */ void reset_calibration_state() { CAL_MODE = false; READ_CAL_DATA = false; CAL_PERFORMED = 1.0; PT1_READ = false; PT2_READ = false; PT3_READ = false; PH_IS_READ = false; BATTERY_IS_READ = false; } /* * Use the values read from read_saadc_for_calibration to reset the M, B and R values * to recalibrate accuracy of ISFET voltage output to pH value conversions */ void perform_calibration(uint8_t cal_pts) { if (cal_pts == 1) { // Compare mV for pH value to mV calculated for same pH with current M & B values, // then adjust B value by the difference in mV values (shift intercept of line) double incorrect_pH = calculate_pH_from_mV((uint32_t)PT1_MV_VAL); double cal_adjustment = PT1_PH_VAL - incorrect_pH; BVAL_CALIBRATION = BVAL_CALIBRATION + cal_adjustment; NRF_LOG_INFO("MVAL: " NRF_LOG_FLOAT_MARKER " \n", NRF_LOG_FLOAT(MVAL_CALIBRATION)); NRF_LOG_INFO("BVAL: " NRF_LOG_FLOAT_MARKER " \n", NRF_LOG_FLOAT(BVAL_CALIBRATION)); NRF_LOG_INFO("RVAL: " NRF_LOG_FLOAT_MARKER " \n", NRF_LOG_FLOAT(RVAL_CALIBRATION)); //NRF_LOG_INFO("incorrect: %d, pt1: %d, adjustment: %d, BVAL: %d\n", (int)incorrect_pH, (int)PT1_PH_VAL, (int)cal_adjustment, (int)(BVAL_CALIBRATION)); } else if (cal_pts == 2) { // Create arrays of pH value and corresponding mV values (change all line properties) double x1 = PT1_MV_VAL; double x2 = PT2_MV_VAL; double y1 = PT1_PH_VAL; double y2 = PT2_PH_VAL; double x_vals[] = {x1, x2}; double y_vals[] = {y1, y2}; linreg(2, x_vals, y_vals); } else if (cal_pts == 3) { // Create arrays of pH value and corresponding mV values (change all line properties) double x1 = PT1_MV_VAL; double x2 = PT2_MV_VAL; double x3 = PT3_MV_VAL; double y1 = PT1_PH_VAL; double y2 = PT2_PH_VAL; double y3 = PT3_PH_VAL; double x_vals[] = {x1, x2, x3}; double y_vals[] = {y1, y2, y3}; linreg(3, x_vals, y_vals); } } /* * Checks packet contents to appropriately perform calibration */ void check_for_calibration(char **packet) { // Possible Strings to be received by pH device char *STARTCAL1 = "STARTCAL1"; char *STARTCAL2 = "STARTCAL2"; char *STARTCAL3 = "STARTCAL3"; char *PWROFF = "PWROFF"; char *PT1 = "PT1"; char *PT2 = "PT2"; char *PT3 = "PT3"; // Possible strings to send to mobile application char *CALBEGIN = "CALBEGIN"; char *PT1CONF = "PT1CONF"; char *PT2CONF = "PT2CONF"; char *PT3CONF = "PT3CONF"; // Variables to hold sizes of strings for ble_nus_send function uint16_t SIZE_BEGIN = 9; uint16_t SIZE_CONF = 8; // Used for parsing out pH value from PT1_X.Y (etc) packets char pH_val_substring[4]; uint32_t err_code; if (strstr(*packet, PWROFF) != NULL){ nrfx_gpiote_out_clear(CHIP_POWER_PIN); } if (strstr(*packet, STARTCAL1) != NULL){ CAL_MODE = true; NUM_CAL_PTS = 1; // Make sure other processes are stopped and reset so calibration can occur disable_pH_voltage_reading(); err_code = ble_nus_data_send(&m_nus, CALBEGIN, &SIZE_BEGIN, m_conn_handle); APP_ERROR_CHECK(err_code); } else if (strstr(*packet, STARTCAL2) != NULL){ CAL_MODE = true; NUM_CAL_PTS = 2; // Make sure other processes are stopped and reset so calibration can occur disable_pH_voltage_reading(); err_code = ble_nus_data_send(&m_nus, CALBEGIN, &SIZE_BEGIN, m_conn_handle); APP_ERROR_CHECK(err_code); } else if (strstr(*packet, STARTCAL3) != NULL) { CAL_MODE = true; NUM_CAL_PTS = 3; // Make sure other processes are stopped and reset so calibration can occur disable_pH_voltage_reading(); err_code = ble_nus_data_send(&m_nus, CALBEGIN, &SIZE_BEGIN, m_conn_handle); APP_ERROR_CHECK(err_code); } if (strstr(*packet, PT1) != NULL) { // Parse out the pH value from the packet, will always be format "X.Y" substring(*packet, pH_val_substring, 5, 3); PT1_PH_VAL = atof(pH_val_substring); read_saadc_for_calibration(); err_code = ble_nus_data_send(&m_nus, PT1CONF, &SIZE_CONF, m_conn_handle); APP_ERROR_CHECK(err_code); // Restart normal data transmision once calibration is complete if (NUM_CAL_PTS == 1) { perform_calibration(1); write_cal_values_to_flash(); reset_calibration_state(); restart_pH_interval_timer(); } } else if (strstr(*packet, PT2) != NULL) { // Parse out the pH value from the packet, will always be format "X.Y" substring(*packet, pH_val_substring, 5, 3); PT2_PH_VAL = atof(pH_val_substring); read_saadc_for_calibration(); err_code = ble_nus_data_send(&m_nus, PT2CONF, &SIZE_CONF, m_conn_handle); APP_ERROR_CHECK(err_code); // Restart normal data transmision once calibration is complete if (NUM_CAL_PTS == 2) { perform_calibration(2); write_cal_values_to_flash(); reset_calibration_state(); restart_pH_interval_timer(); } } else if (strstr(*packet, PT3) != NULL) { // Parse out the pH value from the packet, will always be format "X.Y" substring(*packet, pH_val_substring, 5, 3); PT3_PH_VAL = atof(pH_val_substring); read_saadc_for_calibration(); err_code = ble_nus_data_send(&m_nus, PT3CONF, &SIZE_CONF, m_conn_handle); APP_ERROR_CHECK(err_code); // Restart normal data transmision once calibration is complete if (NUM_CAL_PTS == 3) { perform_calibration(3); write_cal_values_to_flash(); reset_calibration_state(); restart_pH_interval_timer(); } } } /**@brief Function for handling the data from the Nordic UART Service. * * @details This function will process the data received from the Nordic UART * BLE Service and send it to the UART module. * * @param[in] p_evt Nordic UART Service event. */ /**@snippet [Handling the data received over BLE] */ void nus_data_handler(ble_nus_evt_t * p_evt) { if (p_evt->type == BLE_NUS_EVT_RX_DATA) { // Array to store data received by smartphone will never exceed 9 characters char data[10]; // Pointer to array char *data_ptr = data; uint32_t err_code; NRF_LOG_DEBUG("Received data from BLE NUS.\n"); NRF_LOG_HEXDUMP_DEBUG(p_evt->params.rx_data.p_data, p_evt->params.rx_data.length); for (uint32_t i = 0; i < p_evt->params.rx_data.length; i++) { do { // Parse data into array data[i] = p_evt->params.rx_data.p_data[i]; if ((err_code != NRF_SUCCESS) && (err_code != NRF_ERROR_BUSY)) { NRF_LOG_ERROR("Failed receiving NUS message. Error 0x%x. ", err_code); APP_ERROR_CHECK(err_code); } } while (err_code == NRF_ERROR_BUSY); } // Check pack for calibration protocol details check_for_calibration(&data_ptr); } if (p_evt->type == BLE_NUS_EVT_COMM_STARTED) { send_data_and_restart_timer(); } } /**@snippet [Handling the data received over BLE] */ /**@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; NRF_LOG_INFO("INSIDE CONN PARAMS EVT\n"); 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; // Go to system-off mode (function will not return; wakeup causes 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) { switch (ble_adv_evt) { case BLE_ADV_EVT_FAST: NRF_LOG_INFO("FAST ADVERTISING STARTED"); break; case BLE_ADV_EVT_IDLE: NRF_LOG_INFO("on_adv_evt IDLE EVENT"); // Restart timer init_and_start_app_timer(); 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; NRF_LOG_INFO("INSIDE BLE EVT HANDLER\n"); NRF_LOG_INFO("evt id: %u\n", p_ble_evt->header.evt_id); switch (p_ble_evt->header.evt_id) { case 38: NRF_LOG_INFO("CASE 38\n"); init_and_start_app_timer(); break; case BLE_GAP_EVT_CONNECTED: 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); CONNECTION_MADE = true; NRF_LOG_INFO("CONNECTION MADE (ble_gap_evt) \n"); break; case BLE_GAP_EVT_DISCONNECTED: if(p_ble_evt->evt.gap_evt.params.disconnected.reason == BLE_HCI_CONNECTION_TIMEOUT) { //disconnect_reason is BLE_HCI_CONNECTION_TIMEOUT NRF_LOG_INFO("connection timeout\n"); } // LED indication will be changed when advertising starts. m_conn_handle = BLE_CONN_HANDLE_INVALID; CONNECTION_MADE = false; NRF_LOG_INFO("DISCONNECTED\n"); init_and_start_app_timer(); 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_GATTC_EVT_TIMEOUT: NRF_LOG_INFO("TIMEOUT GATTC EVT\n"); // 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); m_conn_handle = BLE_CONN_HANDLE_INVALID; // restart timer init_and_start_app_timer(); break; case BLE_GATTS_EVT_TIMEOUT: NRF_LOG_INFO("TIMEOUT GATTS EVT\n"); // 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); m_conn_handle = BLE_CONN_HANDLE_INVALID; APP_ERROR_CHECK(err_code); // restart timer init_and_start_app_timer(); break; case BLE_GAP_EVT_TIMEOUT: NRF_LOG_INFO("TIMEOUT GAP EVT\n"); // Restart timer init_and_start_app_timer(); break; case BLE_GAP_EVT_AUTH_STATUS: NRF_LOG_INFO("BLE_GAP_EVT_AUTH_STATUS: status=0x%x bond=0x%x lv4: %d kdist_own:0x%x kdist_peer:0x%x", p_ble_evt->evt.gap_evt.params.auth_status.auth_status, p_ble_evt->evt.gap_evt.params.auth_status.bonded, p_ble_evt->evt.gap_evt.params.auth_status.sm1_levels.lv4, *((uint8_t *)&p_ble_evt->evt.gap_evt.params.auth_status.kdist_own), *((uint8_t *)&p_ble_evt->evt.gap_evt.params.auth_status.kdist_peer)); break; case BLE_GAP_EVT_SEC_PARAMS_REQUEST: NRF_LOG_INFO("BLE_GAP_EVT_SEC_PARAMS_REQUEST"); 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 the Peer Manager initialization. */ static void peer_manager_init(void) { ble_gap_sec_params_t sec_param; ret_code_t err_code; err_code = pm_init(); APP_ERROR_CHECK(err_code); memset(&sec_param, 0, sizeof(ble_gap_sec_params_t)); // Security parameters to be used for all security procedures. sec_param.bond = SEC_PARAM_BOND; sec_param.mitm = SEC_PARAM_MITM; sec_param.lesc = SEC_PARAM_LESC; sec_param.keypress = SEC_PARAM_KEYPRESS; sec_param.io_caps = SEC_PARAM_IO_CAPABILITIES; sec_param.oob = SEC_PARAM_OOB; sec_param.min_key_size = SEC_PARAM_MIN_KEY_SIZE; sec_param.max_key_size = SEC_PARAM_MAX_KEY_SIZE; sec_param.kdist_own.enc = 1; sec_param.kdist_own.id = 1; sec_param.kdist_peer.enc = 1; sec_param.kdist_peer.id = 1; err_code = pm_sec_params_set(&sec_param); APP_ERROR_CHECK(err_code); err_code = pm_register(pm_evt_handler); APP_ERROR_CHECK(err_code); } /**@brief Clear bond information from persistent storage. */ static void delete_bonds(void) { ret_code_t err_code; NRF_LOG_INFO("Erase bonds!"); err_code = pm_peers_delete(); APP_ERROR_CHECK(err_code); } /**@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; init.advdata.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_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; 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 the nrf log module. */ static void log_init(void) { ret_code_t err_code = NRF_LOG_INIT(NULL); APP_ERROR_CHECK(err_code); NRF_LOG_DEFAULT_BACKENDS_INIT(); } /**@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) { //UNUSED_RETURN_VALUE(NRF_LOG_PROCESS()); nrf_pwr_mgmt_run(); } /**@brief Function for starting advertising. */ static void advertising_start(bool erase_bonds) { if (erase_bonds == true) { delete_bonds(); // Advertising is started by PM_EVT_PEERS_DELETED_SUCEEDED event } else { ret_code_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST); APP_ERROR_CHECK(err_code); } NRF_LOG_INFO("Advertising started"); NRF_LOG_FLUSH(); } /* This function sets enable pin for ISFET circuitry to HIGH */ void enable_isfet_circuit(void) { nrf_drv_gpiote_out_config_t config = NRFX_GPIOTE_CONFIG_OUT_SIMPLE(false); if(nrf_drv_gpiote_is_init() == false) { nrf_drv_gpiote_init(); } nrf_drv_gpiote_out_init(ENABLE_ISFET_PIN, &config); nrf_drv_gpiote_out_set(ENABLE_ISFET_PIN); } /* This function holds POWER ON line HIGH to keep chip turned on */ void turn_chip_power_on(void) { nrf_drv_gpiote_out_config_t config = NRFX_GPIOTE_CONFIG_OUT_SIMPLE(false); if(nrf_drv_gpiote_is_init() == false) { nrf_drv_gpiote_init(); } nrf_drv_gpiote_out_init(CHIP_POWER_PIN, &config); nrf_drv_gpiote_out_set(CHIP_POWER_PIN); } /* This functions turns POWER ON line LOW to turn the chip completely off */ void turn_chip_power_off(void) { nrfx_gpiote_out_clear(CHIP_POWER_PIN); } /* This function sets enable pin for ISFET circuitry to LOW */ void disable_isfet_circuit(void) { nrfx_gpiote_out_clear(ENABLE_ISFET_PIN); nrfx_gpiote_uninit(); } void timer_handler(nrf_timer_event_t event_type, void * p_context) { // To Add Later } void saadc_sampling_event_init(void) { ret_code_t err_code; nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG; timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32; err_code = nrf_drv_timer_init(&m_timer, &timer_cfg, timer_handler); APP_ERROR_CHECK(err_code); /* setup m_timer for compare event every 15us */ uint32_t ticks = nrf_drv_timer_us_to_ticks(&m_timer, 35); nrf_drv_timer_extended_compare(&m_timer, NRF_TIMER_CC_CHANNEL0, ticks, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false); nrf_drv_timer_enable(&m_timer); uint32_t timer_compare_event_addr = nrf_drv_timer_compare_event_address_get(&m_timer, NRF_TIMER_CC_CHANNEL0); uint32_t saadc_sample_task_addr = nrf_drv_saadc_sample_task_get(); /* setup ppi channel so that timer compare event triggers task in SAADC */ err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel); APP_ERROR_CHECK(err_code); err_code = nrf_drv_ppi_channel_assign(m_ppi_channel, timer_compare_event_addr, saadc_sample_task_addr); APP_ERROR_CHECK(err_code); } void saadc_sampling_event_enable(void) { ret_code_t err_code = nrf_drv_ppi_channel_enable(m_ppi_channel); APP_ERROR_CHECK(err_code); } void restart_saadc(void) { nrfx_timer_uninit(&m_timer); nrfx_ppi_channel_free(m_ppi_channel); nrfx_saadc_uninit(); while(nrfx_saadc_is_busy()) { // make sure SAADC is not busy } enable_pH_voltage_reading(); } double calculate_pH_from_mV(uint32_t ph_val) { // pH = (ph_val - BVAL_CALIBRATION) / (MVAL_CALIBRATION) return ((double)ph_val * MVAL_CALIBRATION) + BVAL_CALIBRATION; } // Pack integer values into byte array to send via bluetooth void create_bluetooth_packet(uint32_t ph_val, uint32_t batt_val, uint32_t temp_val, uint8_t* total_packet) { /* {0,0,0,0,44, pH value arr[0-3], comma arr[4] 0,0,0,0,44, temperature arr[5-8], comma arr[9] 0,0,0,0,44, battery value arr[10-13], commar arr[14] 0 0 0 0,10}; raw pH value arr[15-18], EOL arr[19] */ uint32_t temp = 0; // hold intermediate divisions of variables uint32_t ASCII_DIG_BASE = 48; // If calibration has not been performed, store 0000 in real pH field [0-3], // and store the raw SAADC data in the last field [15-18] if (!CAL_PERFORMED) { temp = ph_val; for(int i = 3; i >= 0; i--){ total_packet[i] = 0 + ASCII_DIG_BASE; } } // If calibration has been performed, store eal pH in [0-3], // and store the raw millivolt data in the last field [15-18] else if (CAL_PERFORMED) { double real_pH = calculate_pH_from_mV(ph_val); double pH_decimal_vals = (real_pH - floor(real_pH)) * 100; // Round pH values to 0.25 pH accuracy pH_decimal_vals = round(pH_decimal_vals / 25) * 25; // If decimals round to 100, increment real pH value and set decimals to 0.00 if (pH_decimal_vals == 100) { real_pH = real_pH + 1.0; pH_decimal_vals = 0.00; } // If pH is 9.99 or lower, format with 2 decimal places (4 bytes total) if (real_pH < 10.0) { total_packet[0] = (uint8_t) ((uint8_t)floor(real_pH) + ASCII_DIG_BASE); total_packet[1] = 46; total_packet[2] = (uint8_t) (((uint8_t)pH_decimal_vals / 10) + ASCII_DIG_BASE); total_packet[3] = (uint8_t) (((uint8_t)pH_decimal_vals % 10) + ASCII_DIG_BASE); } // If pH is 10.0 or greater, format with 1 decimal place (still 4 bytes total) else { total_packet[0] = (uint8_t) ((uint8_t)floor(real_pH / 10) + ASCII_DIG_BASE); total_packet[1] = (uint8_t) ((uint8_t)floor((uint8_t)real_pH % 10) + ASCII_DIG_BASE); total_packet[2] = 46; total_packet[3] = (uint8_t) (((uint8_t)pH_decimal_vals / 10) + ASCII_DIG_BASE); } } // Pack temp_val into appropriate location // Packing protocol for number abcd: // [... 0, 0, 0, d] --> [... 0, 0, c, d] --> [... 0, b, c, d] --> [... a, b, c, d] temp = temp_val; for(int i = 8; i >= 5; i--){ if (i == 8) total_packet[i] = (uint8_t)(temp % 10 + ASCII_DIG_BASE); else { temp = temp / 10; total_packet[i] = (uint8_t)(temp % 10 + ASCII_DIG_BASE); } } // Pack batt_val into appropriate location // Packing protocol for number abcd: // [... 0, 0, 0, d] --> [... 0, 0, c, d] --> [... 0, b, c, d] --> [... a, b, c, d] temp = batt_val; for(int i = 13; i >= 10; i--){ if (i == 13) total_packet[i] = (uint8_t)(temp % 10 + ASCII_DIG_BASE); else { temp = temp / 10; total_packet[i] = (uint8_t)(temp % 10 + ASCII_DIG_BASE); } } // Pack batt_val into appropriate location // Packing protocol for number abcd: // [... 0, 0, 0, d] --> [... 0, 0, c, d] --> [... 0, b, c, d] --> [... a, b, c, d] temp = ph_val; for(int i = 18; i >= 15; i--){ if (i == 18) total_packet[i] = (uint8_t)(temp % 10 + ASCII_DIG_BASE); else { temp = temp / 10; total_packet[i] = (uint8_t)(temp % 10 + ASCII_DIG_BASE); } } } uint32_t saadc_result_to_mv(uint32_t saadc_result) { float adc_denom = 4096.0; float adc_ref_mv = 600.0; float adc_prescale = 5.0; float adc_res_in_mv = (((float)saadc_result*adc_ref_mv)/adc_denom) * adc_prescale; return (uint32_t)adc_res_in_mv; } /** * Function is called when SAADC reading event is done. First done event * reads pH input, stores in global variable. Second reading stores * pH data, combines pH and temp data into a comma-seperated string, * then transmits via BLE. * * BUG: p_buffer[0] is always '0' when reading pH at high frequency. * Workaround is to average values besides 1, divide by * samples_in_buffer -1 . */ void saadc_callback(nrf_drv_saadc_evt_t const * p_event) { if (p_event->type == NRF_DRV_SAADC_EVT_DONE) { ret_code_t err_code; uint32_t avg_saadc_reading = 0; //err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, // SAMPLES_IN_BUFFER); //APP_ERROR_CHECK(err_code); // Sum and average SAADC values for (int i = 1; i < SAMPLES_IN_BUFFER; i++) { if (p_event->data.done.p_buffer[i] < 0) { avg_saadc_reading += 0; } else { avg_saadc_reading += p_event->data.done.p_buffer[i]; } //NRF_LOG_INFO("%d\n", p_event->data.done.p_buffer[i]); } avg_saadc_reading = avg_saadc_reading/(SAMPLES_IN_BUFFER - 1); // If ph has not been read, read it then restart SAADC to read temp if (!PH_IS_READ) { AVG_PH_VAL = saadc_result_to_mv(avg_saadc_reading); PH_IS_READ = true; // Uninit saadc peripheral, restart saadc, enable sampling event NRF_LOG_INFO("read pH val, restarting: %d", AVG_PH_VAL); //NRF_LOG_FLUSH(); restart_saadc(); } // If pH has been read but not battery, read battery then restart else if (!(PH_IS_READ && BATTERY_IS_READ)) { AVG_BATT_VAL = saadc_result_to_mv(avg_saadc_reading); NRF_LOG_INFO("read batt val, restarting: %d", AVG_BATT_VAL); //NRF_LOG_FLUSH(); BATTERY_IS_READ = true; restart_saadc(); } // Once temp, battery and ph have been read, create and send data in packet // or adjust the calibration points as necessary else { AVG_TEMP_VAL = saadc_result_to_mv(avg_saadc_reading); NRF_LOG_INFO("read temp val: %d\n", AVG_TEMP_VAL); NRF_LOG_FLUSH(); //NRF_LOG_ERROR( "MVAL: " NRF_LOG_FLOAT_MARKER "\n", NRF_LOG_FLOAT(MVAL_CALIBRATION)); //NRF_LOG_ERROR( "BVAL: " NRF_LOG_FLOAT_MARKER "\n", NRF_LOG_FLOAT(BVAL_CALIBRATION)); // reset global control boolean PH_IS_READ = false; BATTERY_IS_READ = false; // Disable all resources, turn off adc/etc, advertise disable_pH_voltage_reading(); advertising_start(false); } } } void saadc_blocking_callback(nrf_drv_saadc_evt_t const * p_event) { // Don't need to do anything } void init_saadc_for_buffer_conversion(nrf_saadc_channel_config_t channel_config) { ret_code_t err_code; err_code = nrf_drv_saadc_init(NULL, saadc_callback); APP_ERROR_CHECK(err_code); err_code = nrf_drv_saadc_channel_init(0, &channel_config); APP_ERROR_CHECK(err_code); err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[0], SAMPLES_IN_BUFFER); APP_ERROR_CHECK(err_code); } void init_saadc_for_blocking_sample_conversion(nrf_saadc_channel_config_t channel_config) { ret_code_t err_code; err_code = nrf_drv_saadc_init(NULL, saadc_blocking_callback); APP_ERROR_CHECK(err_code); err_code = nrf_drv_saadc_channel_init(0, &channel_config); APP_ERROR_CHECK(err_code); } /* Reads pH transducer output */ void saadc_init(void) { nrf_saadc_input_t ANALOG_INPUT; // Change pin depending on global control boolean if (!PH_IS_READ) { //NRF_LOG_INFO("Setting saadc input to AIN2\n"); ANALOG_INPUT = NRF_SAADC_INPUT_AIN2; } else if (!(PH_IS_READ && BATTERY_IS_READ)) { //NRF_LOG_INFO("Setting saadc input to AIN3\n"); ANALOG_INPUT = NRF_SAADC_INPUT_AIN3; } else { //NRF_LOG_INFO("Setting saadc input to AIN1\n"); ANALOG_INPUT = NRF_SAADC_INPUT_AIN1; } nrf_saadc_channel_config_t channel_config = NRF_SAADC_CUSTOM_CHANNEL_CONFIG_SE(ANALOG_INPUT); if (!CAL_MODE) init_saadc_for_buffer_conversion(channel_config); else init_saadc_for_blocking_sample_conversion(channel_config); } /* This function initializes and enables SAADC sampling */ void enable_pH_voltage_reading(void) { saadc_init(); if (!CAL_MODE) { saadc_sampling_event_init(); saadc_sampling_event_enable(); } } void restart_pH_interval_timer(void) { ret_code_t err_code; err_code = app_timer_start(m_timer_id, APP_TIMER_TICKS(DATA_INTERVAL), NULL); APP_ERROR_CHECK(err_code); } /* Function unitializes and disables SAADC sampling, restarts 1 second timer */ void disable_pH_voltage_reading(void) { nrfx_timer_uninit(&m_timer); nrfx_ppi_channel_free(m_ppi_channel); nrfx_saadc_uninit(); while(nrfx_saadc_is_busy()) { // make sure SAADC is not busy } // *** DISABLE ENABLE *** disable_isfet_circuit(); } void single_shot_timer_handler() { // disable timer ret_code_t err_code; err_code = app_timer_stop(m_timer_id); APP_ERROR_CHECK(err_code); // Delay to ensure appropriate timing enable_isfet_circuit(); // Slight delay for ISFET to achieve optimal ISFET reading nrf_delay_ms(10); // Begin SAADC initialization/start /* * * * * * * * * * * * * * * * UNCOMMENT TO SEND DATA */ enable_pH_voltage_reading(); /* * UNCOMMENT TO SEND DATA * * * * * * * * * * * * * * */ } /**@brief Function for initializing the timer module. */ void timers_init(void) { uint32_t err_code; err_code = app_timer_init(); APP_ERROR_CHECK(err_code); err_code = app_timer_create(&m_timer_id, APP_TIMER_MODE_SINGLE_SHOT, single_shot_timer_handler); APP_ERROR_CHECK(err_code); } void send_data_and_restart_timer() { uint32_t err_code; // Create packet create_bluetooth_packet(AVG_PH_VAL, AVG_BATT_VAL, AVG_TEMP_VAL, total_packet); // // Send data // err_code = ble_nus_data_send(&m_nus, total_packet, // &total_size, m_conn_handle); // APP_ERROR_CHECK(err_code); // Send data do { err_code = ble_nus_data_send(&m_nus, total_packet, &total_size, m_conn_handle); NRF_LOG_INFO("error code: %u", err_code); if ((err_code != NRF_ERROR_INVALID_STATE) && (err_code != NRF_ERROR_RESOURCES) && (err_code != NRF_ERROR_NOT_FOUND)) { APP_ERROR_CHECK(err_code); } } while (err_code == NRF_ERROR_RESOURCES); // Disconnect err_code = sd_ble_gap_disconnect(m_conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION); APP_ERROR_CHECK(err_code); } /**@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) { uint32_t err_code; NRF_LOG_INFO("INSIDE GATT EVT HANDLER"); 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); } void init_and_start_app_timer() { ret_code_t err_code; err_code = app_timer_start(m_timer_id, APP_TIMER_TICKS(DATA_INTERVAL), NULL); APP_ERROR_CHECK(err_code); idle_state_handle(); NRF_LOG_INFO("TIMER STARTED (single shot) \n"); NRF_LOG_FLUSH(); } /**@brief Function for initializing the GATT library. */ void gatt_init(void) { ret_code_t err_code; err_code = nrf_ble_gatt_init(&m_gatt, gatt_evt_handler); APP_ERROR_CHECK(err_code); err_code = nrf_ble_gatt_att_mtu_periph_set(&m_gatt, NRF_SDH_BLE_GATT_MAX_MTU_SIZE); APP_ERROR_CHECK(err_code); } // Helper function for linreg inline static double sqr(double x) { return x*x; } /* * Function for running linear regression on two and three point calibration data */ void linreg(int num, double x[], double y[]) { double sumx = 0.0; /* sum of x */ double sumx2 = 0.0; /* sum of x**2 */ double sumxy = 0.0; /* sum of x * y */ double sumy = 0.0; /* sum of y */ double sumy2 = 0.0; /* sum of y**2 */ for (int i=0;i<num;i++){ sumx += x[i]; sumx2 += sqr(x[i]); sumxy += x[i] * y[i]; sumy += y[i]; sumy2 += sqr(y[i]); } double denom = (num * sumx2 - sqr(sumx)); if (denom == 0) { // singular matrix. can't solve the problem. NRF_LOG_INFO("singular matrix, cannot solve regression\n"); } MVAL_CALIBRATION = (num * sumxy - sumx * sumy) / denom; BVAL_CALIBRATION = (sumy * sumx2 - sumx * sumxy) / denom; RVAL_CALIBRATION = (sumxy - sumx * sumy / num) / sqrt((sumx2 - sqr(sumx)/num) * (sumy2 - sqr(sumy)/num)); NRF_LOG_INFO("MVAL **CAL**: " NRF_LOG_FLOAT_MARKER "", NRF_LOG_FLOAT(MVAL_CALIBRATION)); NRF_LOG_INFO("BVAL **CAL**: " NRF_LOG_FLOAT_MARKER "", NRF_LOG_FLOAT(BVAL_CALIBRATION)); NRF_LOG_INFO("RVAL **CAL**: " NRF_LOG_FLOAT_MARKER " \n", NRF_LOG_FLOAT(RVAL_CALIBRATION)); } void my_fds_evt_handler(fds_evt_t const * const p_fds_evt) { //NRF_LOG_INFO("INSIDE FDS EVENT HANDLER"); //NRF_LOG_INFO(" FDS ID: %d, FDS RESULT: %d \n", p_fds_evt->id, p_fds_evt->result); //NRF_LOG_FLUSH(); switch (p_fds_evt->id) { case FDS_EVT_INIT: if (p_fds_evt->result != FDS_SUCCESS) { NRF_LOG_INFO("ERROR IN EVENT HANDLER\n"); NRF_LOG_FLUSH(); } break; case FDS_EVT_WRITE: if (p_fds_evt->result == FDS_SUCCESS) { write_flag=1; } break; default: break; } } static void fds_write(float value, uint16_t FILE_ID, uint16_t REC_KEY) { fds_record_t record; fds_record_desc_t record_desc; // Set up record. record.file_id = FILE_ID; record.key = REC_KEY; record.data.length_words = 1; if(FILE_ID == MVAL_FILE_ID && REC_KEY == MVAL_REC_KEY){ record.data.p_data = &MVAL_CALIBRATION; } else if(FILE_ID == BVAL_FILE_ID && REC_KEY == BVAL_REC_KEY){ record.data.p_data = &BVAL_CALIBRATION; } else if(FILE_ID == RVAL_FILE_ID && REC_KEY == RVAL_REC_KEY){ record.data.p_data = &RVAL_CALIBRATION; } else if(FILE_ID == CAL_DONE_FILE_ID && REC_KEY == CAL_DONE_REC_KEY) { record.data.p_data = &CAL_PERFORMED; } ret_code_t ret = fds_record_write(&record_desc, &record); if (ret != FDS_SUCCESS) { NRF_LOG_INFO("ERROR WRITING TO FLASH\n"); } NRF_LOG_INFO("SUCCESS WRITING TO FLASH\n"); NRF_LOG_FLUSH(); } static void fds_update(float value, uint16_t FILE_ID, uint16_t REC_KEY) { fds_record_t record; fds_record_desc_t record_desc; fds_find_token_t ftok ={0}; // Set up record. record.file_id = FILE_ID; record.key = REC_KEY; record.data.length_words = 1; if(FILE_ID == MVAL_FILE_ID && REC_KEY == MVAL_REC_KEY){ record.data.p_data = &MVAL_CALIBRATION; fds_record_find(MVAL_FILE_ID, MVAL_REC_KEY, &record_desc, &ftok); } else if(FILE_ID == BVAL_FILE_ID && REC_KEY == BVAL_REC_KEY){ record.data.p_data = &BVAL_CALIBRATION; fds_record_find(BVAL_FILE_ID, BVAL_REC_KEY, &record_desc, &ftok); } else if(FILE_ID == RVAL_FILE_ID && REC_KEY == RVAL_REC_KEY){ record.data.p_data = &RVAL_CALIBRATION; fds_record_find(RVAL_FILE_ID, RVAL_REC_KEY, &record_desc, &ftok); } else if(FILE_ID == CAL_DONE_FILE_ID && REC_KEY == CAL_DONE_REC_KEY) { record.data.p_data = &CAL_PERFORMED; fds_record_find(CAL_DONE_FILE_ID, CAL_DONE_REC_KEY, &record_desc, &ftok); } ret_code_t ret = fds_record_update(&record_desc, &record); if (ret != FDS_SUCCESS) { NRF_LOG_INFO("ERROR WRITING UPDATE TO FLASH\n"); } NRF_LOG_INFO("SUCCESS WRITING UPDATE TO FLASH\n"); NRF_LOG_FLUSH(); } float fds_read(uint16_t FILE_ID, uint16_t REC_KEY) { fds_flash_record_t flash_record; fds_record_desc_t record_desc; fds_find_token_t ftok ={0};//Important, make sure you zero init the ftok token float *p_data; float data; uint32_t err_code; NRF_LOG_INFO("Start searching... \r\n"); // Loop until all records with the given key and file ID have been found. while (fds_record_find(FILE_ID, REC_KEY, &record_desc, &ftok) == FDS_SUCCESS) { err_code = fds_record_open(&record_desc, &flash_record); if ( err_code != FDS_SUCCESS) { NRF_LOG_INFO("COULD NOT FIND OR OPEN RECORD\n"); return 0.0; } p_data = (float *) flash_record.p_data; data = *p_data; for (uint8_t i=0;i<flash_record.p_header->length_words;i++) { NRF_LOG_INFO("Data read: " NRF_LOG_FLOAT_MARKER "\n", NRF_LOG_FLOAT(data)); } NRF_LOG_INFO("\r\n"); // Access the record through the flash_record structure. // Close the record when done. err_code = fds_record_close(&record_desc); if (err_code != FDS_SUCCESS) { NRF_LOG_INFO("ERROR CLOSING RECORD\n"); } NRF_LOG_FLUSH(); } NRF_LOG_INFO("SUCCESS CLOSING RECORD\n"); return data; } static void fds_find_and_delete(uint16_t FILE_ID, uint16_t REC_KEY) { fds_record_desc_t record_desc; fds_find_token_t ftok; ftok.page=0; ftok.p_addr=NULL; // Loop and find records with same ID and rec key and mark them as deleted. while (fds_record_find(FILE_ID, REC_KEY, &record_desc, &ftok) == FDS_SUCCESS) { fds_record_delete(&record_desc); NRF_LOG_INFO("Deleted record ID: %d \r\n",record_desc.record_id); } // call the garbage collector to empty them, don't need to do this all the time, // this is just for demonstration ret_code_t ret = fds_gc(); if (ret != FDS_SUCCESS) { NRF_LOG_INFO("ERROR DELETING RECORD\n"); } NRF_LOG_INFO("RECORD DELETED SUCCESFULLY\n"); } static void fds_init_helper(void) { ret_code_t ret = fds_register(my_fds_evt_handler); if (ret != FDS_SUCCESS) { NRF_LOG_INFO("ERROR, COULD NOT REGISTER FDS\n"); } ret = fds_init(); if (ret != FDS_SUCCESS) { NRF_LOG_INFO("ERROR, COULD NOT INIT FDS\n"); } NRF_LOG_INFO("FDS INIT\n"); } /* Check words used in fds after initialization, and if more than 4 (default) * words are used then read MVAL, BVAL and RVAL values stored in flash. Assign * stored values to global variables respectively */ static void check_calibration_state(void) { fds_stat_t fds_info; fds_stat(&fds_info); NRF_LOG_INFO("open records: %u, words used: %u\n", fds_info.open_records, fds_info.words_used); // fds_read will return 0 if the CAL_DONE record does not exist, // or if the stored value is 0 if(fds_read(CAL_DONE_FILE_ID, CAL_DONE_REC_KEY)) { CAL_PERFORMED = 1.0; NRF_LOG_INFO("Setting CAL_PERFORMED to true\n"); // Read values stored in flash and set to respective global variables MVAL_CALIBRATION = fds_read(MVAL_FILE_ID, MVAL_REC_KEY); BVAL_CALIBRATION = fds_read(BVAL_FILE_ID, BVAL_REC_KEY); RVAL_CALIBRATION = fds_read(RVAL_FILE_ID, RVAL_REC_KEY); NRF_LOG_INFO("MVAL: " NRF_LOG_FLOAT_MARKER " \n", NRF_LOG_FLOAT(MVAL_CALIBRATION)); NRF_LOG_INFO("BVAL: " NRF_LOG_FLOAT_MARKER " \n", NRF_LOG_FLOAT(BVAL_CALIBRATION)); NRF_LOG_INFO("RVAL: " NRF_LOG_FLOAT_MARKER " \n", NRF_LOG_FLOAT(RVAL_CALIBRATION)); } } /* If calibration has already been performed then update existing records with new * values. If calibration has not already been performed, then write values to * new records */ void write_cal_values_to_flash(void) { // Update the existing flash records if (CAL_PERFORMED) { fds_update(MVAL_CALIBRATION, MVAL_FILE_ID, MVAL_REC_KEY); fds_update(BVAL_CALIBRATION, BVAL_FILE_ID, BVAL_REC_KEY); fds_update(RVAL_CALIBRATION, RVAL_FILE_ID, RVAL_REC_KEY); fds_update(CAL_PERFORMED, CAL_DONE_FILE_ID, CAL_DONE_REC_KEY); } // Write values to new records else { fds_write(MVAL_CALIBRATION, MVAL_FILE_ID, MVAL_REC_KEY); fds_write(BVAL_CALIBRATION, BVAL_FILE_ID, BVAL_REC_KEY); fds_write(RVAL_CALIBRATION, RVAL_FILE_ID, RVAL_REC_KEY); fds_write(CAL_PERFORMED, CAL_DONE_FILE_ID, CAL_DONE_REC_KEY); } } /**@brief Application main function. */ int main(void) { bool erase_bonds = false; // Call function very first to turn on the chip turn_chip_power_on(); log_init(); timers_init(); power_management_init(); // Initialize fds and check for calibration values fds_init_helper(); check_calibration_state(); // Continue with adjusted calibration state ble_stack_init(); gap_params_init(); gatt_init(); services_init(); advertising_init(); conn_params_init(); peer_manager_init(); // Start intermittent data reading <> advertising protocol enable_isfet_circuit(); nrf_delay_ms(10); enable_pH_voltage_reading(); // Enter main loop for power management while (true) { idle_state_handle(); } } /* * @} */
Can anybody help me figure out why my device only enters the low power 500uA once, and then perpetually goes into a 7.5mA draw? There are no errors when I run my code. I also think that the 500uA draw is way too high for low power, but first I want to make sure my device can reliably enter/stay in the 500uA draw range. Then I can make the 500uA a lower value, but this issue is more pressing.