Hi, I am using nRF52 DK with s112 and SDK V15.2.0. I have written an application based off of the uart example, and expanded it rather substantially to use the SAADC and read from various gpio inputs. I have developed a mobile application with flutter that can read/write data from this application no problem. My nordic application will be flashed onto a custom board with the nRF52810 which will read sensor data. I need a ble receiver that can accept sensor data for long periods of time (weeks-months) for testing/evaluating sensor performance, so I thought a raspberry pi would be great for this.
I ideally want to use bluepy to write a python script that can scan, connect, read data from the uart service, then log data in a csv file on the pi. I had some issues using bluepy, so i took a step back and tried to see if I can successfully connect just using bluetoothctl or gatttool on the raspberry pi. I am currently using the latest version of raspberry os lite (no DE installed), with the latest bluepy, bluez, etc libraries all installed. The problem I am having is that when using gatttool or bluetoothctl, my nRF52 DK always disconnects due to HCI TIMEOUT when I try and enable notifications or read data on my pi.
I can successfully connect to the nRF52 DK with gatttool. The connection is good until I try to read data on the characteristic, or set the CCCD handle to enable notifications. I can stay connected to the nRF52 DK for 10 seconds, 30 seconds, even 5 minutes, but the second I try to read a characteristic or set the CCCD handle I experience a disconnection. The reason is always "connection timeout", and I get p_ble_evt->evt.gap_evt.params.disconnected.reason == BLE_HCI_CONNECTION_TIMEOUT within my ble_evt_handler function on the DK. The DK says that pairing is successful, the MTU length is set, everything seems good, yet the hci_timeout still occurs.
The handle for CCCD is 0x0010, with UUID 00002902-0000-1000-8000-00805f9b34fb. When I read the handle with char-read-hnd 0x0010 using gatttool, I can see the value is 0000 which means notifications are not enabled. Then, when I use gatttool to set the handle value to enable notifications (char-write-req 0x0010 0100), I see handle is updated, can see one packet of data and then immediately disconnect due to HCI TIMEOUT. Attached are my commands that I run, as well as output from hcidump during the process:
First connection:
pi@raspberrypi:~ $ gatttool -t random -b CE:29:72:08:BE:04 -I
[CE:29:72:08:BE:04][LE]> connect
Attempting to connect to CE:29:72:08:BE:04
Connection successful
[CE:29:72:08:BE:04][LE]> char-write-req 0x0010 0100
Characteristic value was written successfully
Notification handle = 0x000f value: 30 30 30 30 2c 30 35 31 32 2c 31 30 36 32 2c 30 32 39 36 0a
[CE:29:72:08:BE:04][LE]>
(gatttool:7074): GLib-WARNING **: 08:28:29.582: Invalid file descriptor.
Second attempt at connection:
pi@raspberrypi:~ $ gatttool -t random -b CE:29:72:08:BE:04 -I
[CE:29:72:08:BE:04][LE]> connect
Attempting to connect to CE:29:72:08:BE:04
Connection successful
[CE:29:72:08:BE:04][LE]> char-write-req 0x0010 0100
Characteristic value was written successfully
notification handle = 0x000f value: 30 30 30 30 2c 30 34 30 38 2c 30 38 39 31 2c 30 32 38 37 0a
[CE:29:72:08:BE:04][LE]>
(gatttool:7074): GLib-WARNING **: 08:28:29.582: Invalid file descriptor.
Third attempt at connection:
pi@raspberrypi:~ $ gatttool -t random -b CE:29:72:08:BE:04 -I
[CE:29:72:08:BE:04][LE]> connect
Attempting to connect to CE:29:72:08:BE:04
Connection successful
[CE:29:72:08:BE:04][LE]> char-write-req 0x0010 0100
Characteristic value was written successfully
Notification handle = 0x000f value: 30 30 30 30 2c 30 33 39 31 2c 30 39 37 34 2c 30 33 34 33 0a
[CE:29:72:08:BE:04][LE]>
(gatttool:7074): GLib-WARNING **: 08:28:29.582: Invalid file descriptor.
You can see that the value changes every time - the packet is formatted as XXXX,XXXX,XXXX,XXXX ended with 0x10. You can see the "XXXX" values in between 0x2c (commas) changes, which is expected as my nRF52 DK is reading variable sensor data. Thus the data is being sent, i can read the data once, but then immediately disconnect. I've also done searching online and found that the final warning "GLib-WARNING **" is not cause for concern (see here).
Here is my hcidump:
HCI sniffer - Bluetooth packet analyzer ver 5.50
device: hci0 snap_len: 1500 filter: 0xffffffff
< HCI Command: LE Set Scan Parameters (0x08|0x000b) plen 7
type 0x00 (passive)
interval 60.000ms window 30.000ms
own address: 0x00 (Public) policy: white list only
> HCI Event: Command Complete (0x0e) plen 4
LE Set Scan Parameters (0x08|0x000b) ncmd 1
status 0x00
< HCI Command: LE Set Scan Enable (0x08|0x000c) plen 2
value 0x01 (scanning enabled)
filter duplicates 0x01 (enabled)
> HCI Event: Command Complete (0x0e) plen 4
LE Set Scan Enable (0x08|0x000c) ncmd 1
status 0x00
> HCI Event: LE Meta Event (0x3e) plen 32
LE Advertising Report
ADV_IND - Connectable undirected advertising (0)
bdaddr CE:29:72:08:BE:04 (Random)
Flags: 0x05
Complete local name: 'Lura_Health_Dan'
RSSI: -34
< HCI Command: LE Set Scan Enable (0x08|0x000c) plen 2
value 0x00 (scanning disabled)
filter duplicates 0x00 (disabled)
> HCI Event: Command Complete (0x0e) plen 4
LE Set Scan Enable (0x08|0x000c) ncmd 1
status 0x00
< HCI Command: LE Create Connection (0x08|0x000d) plen 25
bdaddr CE:29:72:08:BE:04 type 1
interval 96 window 96 initiator_filter 0
own_bdaddr_type 0 min_interval 24 max_interval 40
latency 0 supervision_to 42 min_ce 0 max_ce 0
> HCI Event: Command Status (0x0f) plen 4
LE Create Connection (0x08|0x000d) status 0x00 ncmd 1
> HCI Event: LE Meta Event (0x3e) plen 19
LE Connection Complete
status 0x00 handle 64, role master
bdaddr CE:29:72:08:BE:04 (Random)
< HCI Command: LE Read Remote Used Features (0x08|0x0016) plen 2
> HCI Event: Command Status (0x0f) plen 4
LE Read Remote Used Features (0x08|0x0016) status 0x00 ncmd 1
> HCI Event: Command Complete (0x0e) plen 14
LE Read Remote Used Features (0x08|0x0016) ncmd 1
> ACL data: handle 64 flags 0x02 dlen 7
ATT: MTU req (0x02)
client rx mtu 247
> HCI Event: LE Meta Event (0x3e) plen 12
LE Read Remote Used Features Complete
status 0x00 handle 64
Features: 0x05 0x00 0x00 0x00 0x00 0x00 0x00 0x00
< ACL data: handle 64 flags 0x00 dlen 9
ATT: Error (0x01)
Error: Request not supported (6)
MTU req (0x02) on handle 0x0000
> HCI Event: Number of Completed Packets (0x13) plen 5
handle 64 packets 1
< ACL data: handle 64 flags 0x00 dlen 9
ATT: Write req (0x12)
handle 0x0010 value 0x01 0x00
> ACL data: handle 64 flags 0x02 dlen 9
ATT: Error (0x01)
Error: Insufficient authentication (5)
Write req (0x12) on handle 0x0010
< ACL data: handle 64 flags 0x00 dlen 11
SMP: Pairing Request (0x01)
capability 0x03 oob 0x00 auth req 0x29
max key size 0x10 init key dist 0x0d resp key dist 0x0f
Capability: NoInputNoOutput (OOB data not present)
Authentication: Bonding (No MITM Protection)
Initiator Key Distribution: LTK CSRK
Responder Key Distribution: LTK IRK CSRK
> HCI Event: Number of Completed Packets (0x13) plen 5
handle 64 packets 2
> ACL data: handle 64 flags 0x02 dlen 11
SMP: Pairing Response (0x02)
capability 0x03 oob 0x00 auth req 0x00
max key size 0x10 init key dist 0x00 resp key dist 0x00
Capability: NoInputNoOutput (OOB data not present)
Authentication: No Bonding (No MITM Protection)
Initiator Key Distribution:
Responder Key Distribution:
< ACL data: handle 64 flags 0x00 dlen 21
SMP: Pairing Confirm (0x03)
key ce95948cd155a0799bbedef734611a59
> ACL data: handle 64 flags 0x02 dlen 21
SMP: Pairing Confirm (0x03)
key a3f6ff42c057da57a7998d10470337fe
< ACL data: handle 64 flags 0x00 dlen 21
SMP: Pairing Random (0x04)
random b58cea6faf65147e39ce74d61057f9c6
> HCI Event: Number of Completed Packets (0x13) plen 5
handle 64 packets 2
> ACL data: handle 64 flags 0x02 dlen 21
SMP: Pairing Random (0x04)
random e598003d1958ed62a8df058466a3c602
< HCI Command: LE Start Encryption (0x08|0x0019) plen 28
> HCI Event: Command Status (0x0f) plen 4
LE Start Encryption (0x08|0x0019) status 0x00 ncmd 1
> HCI Event: Encrypt Change (0x08) plen 4
status 0x00 handle 64 encrypt 0x01
< ACL data: handle 64 flags 0x00 dlen 9
ATT: Write req (0x12)
handle 0x0010 value 0x01 0x00
> HCI Event: Number of Completed Packets (0x13) plen 5
handle 64 packets 1
> ACL data: handle 64 flags 0x02 dlen 5
ATT: Write resp (0x13)
> ACL data: handle 64 flags 0x02 dlen 27
ATT: Handle notify (0x1b)
handle 0x000f
value 0x30 0x30 0x30 0x30 0x2c 0x30 0x34 0x34 0x32 0x2c 0x30 0x39 0x39 0x33 0x2c 0x30 0x34 0x30 0x31 0x0a
> HCI Event: Disconn Complete (0x05) plen 4
status 0x00 handle 64 reason 0x08
Reason: Connection Timeout
I do see "ATT: Error (0x01) Error: Request not supported (6) MTU req (0x02) on handle 0x0000" on line 52, and "ATT: Error (0x01) Error: Insufficient authentication (5) Write req (0x12) on handle 0x0010" on line 62. I imagine these may be a source of this issue I am having, but i cannot figure out for the life of me why this is happening. Also attached is my main.c I use for my nRF52 DK application. It is rather long and spaghetty-ish right now, I sincerely apologize but it may be helpful. Briefly, I advertise and then once a connection is made I start a short timer. When the timer ends, I read some data with SAADC, make a packet, and send the packet. I repeat this process throughout the entire connection time. There are some other functions in here for calibrating sensor data and handling data sent from the central, but that is not relevant for this issue. I also set my connection and security params as attached below.
Here is the output from my nRF52 DK when using the debugger:
<info> app: ENTERED PM_EVT_HANDLER
<info> app: evt id: 1
<info> app: ENTERED PM_EVT_HANDLER
<info> app: evt id: 5
<info> app: INSIDE BLE EVT HANDLER
<info> app: evt id: 19
<info> app: BLE_GAP_EVT_SEC_PARAMS_REQUEST
<info> app: BLUETOOTH DATA SENT
<info> app: INSIDE BLE EVT HANDLER
<info> app: evt id: 26
<info> app: ENTERED PM_EVT_HANDLER
<info> app: evt id: 2
<info> peer_manager_handler: Connection secured: role: Peripheral, conn_handle: 0, procedure: Pairing
<info> app: PM_EVT_CONN_SEC_SUCCEEDED
<info> app: INSIDE BLE EVT HANDLER
<info> app: evt id: 25
<info> app: BLE_GAP_EVT_AUTH_STATUS: status=0x0 bond=0x0 lv4: 0 kdist_own:0x0 kdist_peer:0x0
<info> app: INSIDE BLE EVT HANDLER
<info> app: evt id: 80
<info> app: BLUETOOTH DATA SENT
<info> app: INSIDE BLE EVT HANDLER
<info> app: evt id: 87
<info> app: BLUETOOTH DATA SENT
<info> app: INSIDE BLE EVT HANDLER
<info> app: evt id: 17
<info> app: connection timeout
<info> app: DISCONNECTED
Disclaimer is that in this firmware application, I send data in the "gatt_evt_handler" function after the MTU ATT exchange is completed, and don't call APP_ERROR_CHECK when I call ble_nus_data_send. I know this is bad practice, and I plan to change it, but I've tried placing the function where I send data inside nus_data_handler after p_evt->type == BLE_NUS_EVT_COMM_STARTED occurs, but I experience the same behavior.
Here is my main.c and some of the relevant functions where I set connection parameters for the nordic app:
/** General defines used for relevant application settings **/
#define APP_ADV_INTERVAL 510 /**< The advertising interval (in units of 0.625 ms. This value corresponds to 40 ms). */
#define APP_ADV_DURATION 18000 /**< 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(4000, 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
#define SEC_PARAM_BOND 0 /**< 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
/**@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 = 0;
sec_param.kdist_own.id = 0;
sec_param.kdist_peer.enc = 0;
sec_param.kdist_peer.id = 0;
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 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);
}
/**
* 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_Dan" /**< 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 510 /**< The advertising interval (in units of 0.625 ms. This value corresponds to 40 ms). */
#define APP_ADV_DURATION 18000 /**< 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(4000, 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 0 /**< 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 500
#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;
/* 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 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");
NRF_LOG_INFO("evt id: %u\n", p_evt->evt_id);
//NRF_LOG_FLUSH();
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 initializing the timer module.
*/
void timers_init(void)
{
uint32_t err_code;
err_code = app_timer_init();
APP_ERROR_CHECK(err_code);
}
/**@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;
//NRF_LOG_INFO("averaged avg_ph_val: %u\n");
//NRF_LOG_FLUSH();
// 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);
}
}
/**@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");
NRF_LOG_INFO("evt type: %u\n", p_evt->evt_type);
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)
{
uint32_t err_code;
switch (ble_adv_evt)
{
case BLE_ADV_EVT_FAST:
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;
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 BLE_GATTS_EVT_SYS_ATTR_MISSING:
NRF_LOG_INFO("INSIDE 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; // BLE_GATTS_EVT_SYS_ATTR_MISSINg
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)
{
NRF_LOG_INFO("connection timeout\n");
}
m_conn_handle = BLE_CONN_HANDLE_INVALID;
CONNECTION_MADE = false;
NRF_LOG_INFO("DISCONNECTED\n");
NRF_LOG_FLUSH();
nrfx_timer_uninit(&m_timer);
nrfx_ppi_channel_free(m_ppi_channel);
nrfx_saadc_uninit();
// *** DISABLE ENABLE ***
disable_isfet_circuit();
// restart advertising
advertising_start(false);
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("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);
CONNECTION_MADE = false;
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);
CONNECTION_MADE = false;
APP_ERROR_CHECK(err_code);
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 = 0;
sec_param.kdist_own.id = 0;
sec_param.kdist_peer.enc = 0;
sec_param.kdist_peer.id = 0;
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);
}
}
/* 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)
{
// Redundant, but follows design
// nrfx_gpiote_uninit();
nrfx_gpiote_out_clear(ENABLE_ISFET_PIN);
}
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;
}
//NRF_LOG_INFO("pH decimals: " NRF_LOG_FLOAT_MARKER " \n", NRF_LOG_FLOAT(pH_decimal_vals));
// 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;
uint16_t total_size = 20;
uint32_t avg_saadc_reading = 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 */
//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;
// Pack data and send via bluetooth if not in calibration mode
if (!CAL_MODE) {
// Create bluetooth data
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);
// Turn off peripherals
NRF_LOG_INFO("BLUETOOTH DATA SENT\n");
//NRF_LOG_FLUSH();
}
disable_pH_voltage_reading();
//NRF_LOG_INFO("SAADC DISABLED\n");
//NRF_LOG_FLUSH();
}
}
}
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();
}
nrf_pwr_mgmt_run();
}
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);
nrf_pwr_mgmt_run();
//NRF_LOG_INFO("TIMER RESTARTED (disable_ph_voltage_reading)\n");
//NRF_LOG_FLUSH();
}
/* 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();
if (!CAL_MODE) {
// Restart timer
restart_pH_interval_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();
// PWM output, ISFET capacitor, etc
nrf_delay_ms(10);
// Begin SAADC initialization/start
/* * * * * * * * * * * * * * *
* UNCOMMENT TO SEND DATA
*/
enable_pH_voltage_reading();
/*
* UNCOMMENT TO SEND DATA
* * * * * * * * * * * * * * */
}
/**@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);
ret_code_t err_code;
// Create application timer
err_code = app_timer_create(&m_timer_id,
APP_TIMER_MODE_SINGLE_SHOT,
single_shot_timer_handler);
APP_ERROR_CHECK(err_code);
// 1 second timer intervals
err_code = app_timer_start(m_timer_id, APP_TIMER_TICKS(DATA_INTERVAL), NULL);
APP_ERROR_CHECK(err_code);
//NRF_LOG_INFO("TIMER STARTED (gatt_evt_handler) \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)
{
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();
enable_isfet_circuit();
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();
advertising_start(erase_bonds);
// Enter main loop.
while (true)
{
idle_state_handle();
}
}
/*
* @}
*/
For reference, I actually have two different custom-built mobile applications that can succesfully connect to and read data from this nRF52 DK application as it is written right now. This application also works perfectly fine with nRF Connect, Adafruit BluefruitLE, and other uart central mobile applications (both iOS and Android). I think I am clearly forgetting some important step in the connection/pairing process that is handled automatically/more easily on mobile phone operating systems. Any help or insight would be greatly appreciated!