I am using the S140 softdevice on NRF52840.
I am using NRF 5 SDK 15.2
As part of evaluating NRF52840 for a product I want to see how well i can perform proprietary accurately timed regular data transmissions (would be audio streaming in the product we want to develop) while my NRF 52840 is connected to 2 peripheral devices via the softdevice.
To do this I am using the Time slot API.
For my evaluation I am requesting a 600 us time slot every 10 ms. When the time slot is denied, i request another time slot 20.1 ms later (The added .1 is to try and move my 10 ms time slots so they might not collide with softdevice traffic in the future).
I see a large amount of denied time slots, and my .1 ms time modifications does not help me find a timing sweet spot to avoid that the soft device denies many of my time slot requests.
I have observed the BLE transmissions and the time slot patterns by measuring the NFR52840 power with an oscilloscope.
I am seeing that after each connection event Tx to the pripherals, the softdevice will deny all time slot requests for about 7 ms.
This leaves me with the following question:
1. Why does the soft device deny time slots for about 7 ms after each connection event transmission? It seems like an excessive time, and it prevents me from regularly transmitting my proprietary data.
2. Can I change and minimize the amount of time after connection events that my time slots are denied?
I have attached screen shots of my power measurements. The large dips (it measures voltage over a resistor, so larger power draw is lower voltage) every 10 ms is my proprietary transmissions. The smaller double dips are connection events (I assume). It shows how the time slots are denied (gaps in the 10 ms pattern) for about 7 ms after each connection event.I have attached my Central device source code and .hex, .map and .out files. (it runs on nRF52840-DK board)
I have replaced the 2 connected devices with the Blinky peripheral example code to ensure the issue is not related to the peripheral device I am connecting to, and to ease support.
My evaluation application is originally based on the Blinky central example, but has been modified quite a bit.





#include "TxBuffer.h"
#include "nrf_log.h"
void TxBufferInit(STxBuffer* pThis, tx_message_t* pStorage, uint32_t iCapacity)
{
TxBufferReset(pThis);
pThis->pStorage = pStorage;
pThis->iCapacity = iCapacity;
}
void TxBufferReset(STxBuffer* pThis)
{
pThis->iPushCount = 0;
pThis->iPullCount = 0;
}
/**@brief Function for passing any pending request from the buffer to the stack.
*/
void TxBufferProcess(STxBuffer* pThis)
{
if (pThis->iPushCount == pThis->iPullCount) { return; }
uint32_t err_code;
uint32_t iIndex = pThis->iPullCount % pThis->iCapacity;
tx_message_t* pMsg = &(pThis->pStorage[iIndex]);
if(pMsg->type == READ_REQ)
{
err_code = sd_ble_gattc_read(pMsg->conn_handle,
pMsg->req.read_handle,
0);
}
else
{
err_code = sd_ble_gattc_write(pMsg->conn_handle,
&(pMsg->req.write_req.gattc_params));
}
if (err_code == NRF_SUCCESS)
{
NRF_LOG_DEBUG("SD Read/Write API returns Success..");
pThis->iPullCount++;
}
else
{
NRF_LOG_DEBUG("SD Read/Write API returns error %d. This message sending will be "
"attempted again..", err_code);
}
}
tx_message_t* TxBufferPush(STxBuffer* pThis)
{
uint32_t iCount = pThis->iPushCount - pThis->iPullCount;
if(iCount >= pThis->iCapacity) { return 0; }
uint32_t iIndex = pThis->iPushCount % pThis->iCapacity;
tx_message_t* pMsg = &(pThis->pStorage[iIndex]);
pThis->iPushCount++;
return pMsg;
}
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "TxBuffer.h"
#include "sdk_config.h"
#include "nordic_common.h"
#include "nrf_sdh.h"
#include "nrf_sdh_ble.h"
#include "app_timer.h"
#include "bsp_btn_ble.h"
#include "ble.h"
#include "ble_hci.h"
#include "ble_advertising.h"
#include "ble_conn_params.h"
#include "ble_db_discovery.h"
#include "nrf_ble_gatt.h"
#include "ble_conn_state.h"
#include "nrf_ble_gatt.h"
#include "nrf_pwr_mgmt.h"
#include "nrf_ble_scan.h"
#include "peer_manager_types.h"
#include "peer_manager.h"
#include "peer_manager_handler.h"
#include "nrf_soc.h"
#include "nrf_sdh_soc.h"
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"
#define APP_BLE_CONN_CFG_TAG 1 /**< Tag that refers to the BLE stack configuration that is set with @ref sd_ble_cfg_set. The default tag is @ref APP_BLE_CONN_CFG_TAG. */
#define APP_BLE_OBSERVER_PRIO 3 /**< BLE observer priority of the application. There is no need to modify this value. */
#define APP_SOC_OBSERVER_PRIO 1
#define CENTRAL_SCANNING_LED BSP_BOARD_LED_0
#define CENTRAL_CONNECTED_LED BSP_BOARD_LED_1
#define LEDHA0_LED BSP_BOARD_LED_2 /**< LED to indicate if HA is in program 0. */
#define LEDHA1_LED BSP_BOARD_LED_3 /**< LED to indicate if HA is in program 0. */
#define HA0VOLUP_BUTTON BSP_BUTTON_0 /**< Button performs volume up on first connected HA. */
#define HA0VOLDOWN_BUTTON BSP_BUTTON_1 /**< Button performs volume down on first connected HA. */
#define HA1VOLUP_BUTTON BSP_BUTTON_2 /**< Button performs volume up on second connected HA. */
#define HA1VOLDOWN_BUTTON BSP_BUTTON_3 /**< Button performs volume down on second connected HA. */
#define BUTTON_DETECTION_DELAY APP_TIMER_TICKS(50) /**< Delay from a GPIOTE event until a button is reported as pushed (in number of timer ticks). */
NRF_BLE_GATT_DEF(m_gatt); /**< GATT module instance. */
//NRF_BLE_GATTS_C_ARRAY_DEF(m_gatts_c, NRF_SDH_BLE_CENTRAL_LINK_COUNT); /**< GATT service client instances. */
BLE_DB_DISCOVERY_ARRAY_DEF(m_db_disc, NRF_SDH_BLE_CENTRAL_LINK_COUNT); /**< Database discovery module instances. */
NRF_BLE_SCAN_DEF(m_scan); /**< Scanning Module instance. */
static char const m_target_periph_name[] = "Nordic_Blinky"; /**< Name of the device to try to connect to. This name is searched for in the scanning report data. */
#define LBS_UUID_BASE {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15, \
0xDE, 0xEF, 0x12, 0x12, 0x00, 0x00, 0x00, 0x00}
#define LBS_UUID_SERVICE 0x1523
#define LBS_UUID_BUTTON_CHAR 0x1524
#define LBS_UUID_LED_CHAR 0x1525
/**@brief Structure containing the handles related to the LED Button Service found on the peer. */
typedef struct
{
uint16_t button_cccd_handle; /**< Handle of the CCCD of the Button characteristic. */
uint16_t button_handle; /**< Handle of the Button characteristic as provided by the SoftDevice. */
uint16_t led_handle; /**< Handle of the LED characteristic as provided by the SoftDevice. */
} lbs_db_t;
typedef struct
{
uint16_t ConHandle;
pm_peer_id_t PeerId;
bool Secure;
bool Discovered;
lbs_db_t PeerDb;
// ServiceCache_t PeerCache;
} LbsDevice_t;
static tx_message_t m_aTxBufferStorage[8];
static STxBuffer m_TxBuffer;
static uint8_t m_ble_uuid_type;
static LbsDevice_t m_aLbsDevices[NRF_SDH_BLE_CENTRAL_LINK_COUNT];
static void OnLbsDeviceDisconnect(LbsDevice_t* pDevice);
static void SendPacket(uint8_t * pPacket);
/**@brief Function for handling asserts in the SoftDevice.
*
* @details This function is called in case of an assert in the SoftDevice.
*
* @warning This handler is only an example and is not meant for the final product. You need to analyze
* how your product is supposed to react in case of an 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(0xDEADBEEF, line_num, p_file_name);
}
/**@brief Function for initializing the LEDs.
*
* @details Initializes all LEDs used by the application.
*/
static void leds_init(void)
{
bsp_board_init(BSP_INIT_LEDS);
}
static uint32_t GetConHandleIndex(uint16_t ConHandle)
{
for(uint32_t i = 0; i < ARRAY_SIZE(m_aLbsDevices); i++)
{
if( ConHandle == m_aLbsDevices[i].ConHandle)
{
return i;
}
}
return 0xFFFFFFFF; // Not found value
}
static LbsDevice_t* GetLbsDevice(uint16_t ConHandle)
{
for(uint32_t i = 0; i < ARRAY_SIZE(m_aLbsDevices); i++)
{
if( ConHandle == m_aLbsDevices[i].ConHandle)
{
return &m_aLbsDevices[i];
}
}
return 0;
}
static void LbsDeviceClear(LbsDevice_t* pDevice)
{
pDevice->ConHandle = BLE_CONN_HANDLE_INVALID;
pDevice->PeerId = PM_PEER_ID_INVALID;
pDevice->Secure = false;
pDevice->Discovered = false;
memset(&pDevice->PeerDb, 0x00, sizeof(lbs_db_t));
}
static LbsDevice_t* LbsDeviceGetFreeSlot(void)
{
uint32_t iIndex = GetConHandleIndex(BLE_CONN_HANDLE_INVALID);
if(0xFFFFFFFF == iIndex)
{
return 0;
}
return &m_aLbsDevices[iIndex];
}
static void scan_evt_handler(scan_evt_t const * p_scan_evt)
{
ret_code_t err_code;
switch(p_scan_evt->scan_evt_id)
{
case NRF_BLE_SCAN_EVT_CONNECTING_ERROR:
{
err_code = p_scan_evt->params.connecting_err.err_code;
APP_ERROR_CHECK(err_code);
} break;
default:
break;
}
}
/**@brief Function for initializing the scanning and setting the filters.
*/
static void scan_init(void)
{
ret_code_t err_code;
nrf_ble_scan_init_t init_scan;
memset(&init_scan, 0, sizeof(init_scan));
init_scan.connect_if_match = true;
init_scan.conn_cfg_tag = APP_BLE_CONN_CFG_TAG;
err_code = nrf_ble_scan_init(&m_scan, &init_scan, scan_evt_handler);
APP_ERROR_CHECK(err_code);
err_code = nrf_ble_scan_filter_set(&m_scan, SCAN_NAME_FILTER, m_target_periph_name);
APP_ERROR_CHECK(err_code);
err_code = nrf_ble_scan_filters_enable(&m_scan, NRF_BLE_SCAN_NAME_FILTER, false);
APP_ERROR_CHECK(err_code);
}
/**@brief Function for starting scanning. */
static void scan_start(void)
{
ret_code_t ret;
NRF_LOG_INFO("Start scanning for device name %s.", (uint32_t)m_target_periph_name);
ret = nrf_ble_scan_start(&m_scan);
APP_ERROR_CHECK(ret);
// Turn on the LED to signal scanning.
bsp_board_led_on(CENTRAL_SCANNING_LED);
}
/*
static void ButtonChange(LbsDevice_t* pDevice, int8_t iButton)
{
tx_message_t * p_msg;
p_msg = TxBufferPush(&m_TxBuffer);
if(!p_msg)
{
NRF_LOG_INFO("TxBufferFull");
return;
}
NRF_LOG_DEBUG("Sending Button %d", NewButton);
p_msg->req.write_req.gattc_params.handle = pDevice->PeerDb.button_handle;
p_msg->req.write_req.gattc_params.len = 1;
p_msg->req.write_req.gattc_params.p_value = p_msg->req.write_req.gattc_value;
p_msg->req.write_req.gattc_params.offset = 0;
p_msg->req.write_req.gattc_params.write_op = BLE_GATT_OP_WRITE_REQ;
p_msg->req.write_req.gattc_value[0] = 0x01;
p_msg->conn_handle = pDevice->ConHandle;
p_msg->type = WRITE_REQ;
TxBufferProcess(&m_TxBuffer);
}
*/
static void ButtonUpdate(LbsDevice_t* pDevice, const uint8_t* pButtonData)
{
// Debug info - report button
NRF_LOG_INFO("Conn %d, button = %d", GetConHandleIndex(pDevice->ConHandle), pButtonData[0]);
// Update LEDs
uint32_t iIndex = GetConHandleIndex(pDevice->ConHandle);
uint32_t LedId = (0 == iIndex) ? LEDHA0_LED : LEDHA1_LED;
uint32_t Button = pButtonData[0];
if(!Button)
{
bsp_board_led_on(LedId);
}
else
{
bsp_board_led_off(LedId);
}
}
/**@brief Function for configuring the CCCD.
*
* @param[in] conn_handle The connection handle on which to configure the CCCD.
* @param[in] handle_cccd The handle of the CCCD to be configured.
* @param[in] enable Whether to enable or disable the CCCD.
*
* @return NRF_SUCCESS if the CCCD configure was successfully sent to the peer.
*/
static uint32_t cccd_configure(uint16_t conn_handle, uint16_t handle_cccd, bool enable)
{
NRF_LOG_DEBUG("Configuring CCCD. CCCD Handle = %d, Connection Handle = %d",
handle_cccd,conn_handle);
tx_message_t * p_msg;
uint16_t cccd_val = enable ? BLE_GATT_HVX_NOTIFICATION : 0;
p_msg = TxBufferPush(&m_TxBuffer);
if(!p_msg)
{
NRF_LOG_INFO("TxBufferFull");
return NRF_ERROR_BUSY;
}
p_msg->req.write_req.gattc_params.handle = handle_cccd;
p_msg->req.write_req.gattc_params.len = BLE_CCCD_VALUE_LEN;
p_msg->req.write_req.gattc_params.p_value = p_msg->req.write_req.gattc_value;
p_msg->req.write_req.gattc_params.offset = 0;
p_msg->req.write_req.gattc_params.write_op = BLE_GATT_OP_WRITE_REQ;
p_msg->req.write_req.gattc_value[0] = LSB_16(cccd_val);
p_msg->req.write_req.gattc_value[1] = MSB_16(cccd_val);
p_msg->conn_handle = conn_handle;
p_msg->type = WRITE_REQ;
TxBufferProcess(&m_TxBuffer);
return NRF_SUCCESS;
}
/**@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)
{
ret_code_t err_code;
// For readability.
// ble_common_evt_t const * p_common_evt = &p_ble_evt->evt.common_evt; /**< Common Event, evt_id in BLE_EVT_* series. */
ble_gap_evt_t const * p_gap_evt = &p_ble_evt->evt.gap_evt; /**< GAP originated event, evt_id in BLE_GAP_EVT_* series. */
ble_gattc_evt_t const * p_gattc_evt = &p_ble_evt->evt.gattc_evt; /**< GATT client originated event, evt_id in BLE_GATTC_EVT* series. */
if(p_ble_evt->header.evt_id == 29) // I cant figure out what type of event this is, but its spamming like crazy
{
return;
}
NRF_LOG_DEBUG("BLE Event ID %d", p_ble_evt->header.evt_id);
switch (p_ble_evt->header.evt_id)
{
// Upon connection, check which peripheral is connected, initiate DB
// discovery, update LEDs status, and resume scanning, if necessary.
case BLE_GAP_EVT_CONNECTED:
{
NRF_LOG_INFO("Connection 0x%x established", p_gap_evt->conn_handle);
APP_ERROR_CHECK_BOOL(p_gap_evt->conn_handle < NRF_SDH_BLE_CENTRAL_LINK_COUNT);
// Store the conHandle
LbsDevice_t* pDevice = LbsDeviceGetFreeSlot();
if(!pDevice)
{
NRF_LOG_INFO("Out of Device storage");
break;
}
else
{
pDevice->ConHandle = p_gap_evt->conn_handle;
}
uint32_t iIndex = GetConHandleIndex(pDevice->ConHandle);
err_code = ble_db_discovery_start(&m_db_disc[iIndex],
pDevice->ConHandle);
if (err_code != NRF_ERROR_BUSY)
{
APP_ERROR_CHECK(err_code);
}
// Update LEDs status and check whether it is needed to look for more
// peripherals to connect to.
bsp_board_led_on(CENTRAL_CONNECTED_LED);
if (ble_conn_state_central_conn_count() == NRF_SDH_BLE_CENTRAL_LINK_COUNT)
{
bsp_board_led_off(CENTRAL_SCANNING_LED);
}
else
{
// Resume scanning.
bsp_board_led_on(CENTRAL_SCANNING_LED);
scan_start();
}
} break; // BLE_GAP_EVT_CONNECTED
// Upon disconnection, reset the connection handle of the peer that disconnected, update
// the LEDs status and start scanning again.
case BLE_GAP_EVT_DISCONNECTED:
{
if(BLE_HCI_CONNECTION_TIMEOUT == p_gap_evt->params.disconnected.reason)
{
NRF_LOG_INFO("central link 0x%x disconnected (reason: Connection timeout)",
p_gap_evt->conn_handle);
}
else
{
NRF_LOG_INFO("central link 0x%x disconnected (reason: 0x%x)",
p_gap_evt->conn_handle,
p_gap_evt->params.disconnected.reason);
}
// Clean up
LbsDevice_t* pDevice = GetLbsDevice(p_gap_evt->conn_handle);
if(!pDevice)
{
NRF_LOG_INFO("Disconnected Con handle not recognized");
}
else
{
OnLbsDeviceDisconnect(pDevice);
}
if (ble_conn_state_central_conn_count() == 0)
{
// Turn off the LED that indicates the connection.
bsp_board_led_off(CENTRAL_CONNECTED_LED);
}
// Start scanning.
scan_start();
// Turn on the LED for indicating scanning.
bsp_board_led_on(CENTRAL_SCANNING_LED);
} break;
case BLE_GAP_EVT_TIMEOUT:
{
// Timeout for scanning is not specified, so only the connection requests can time out.
if (p_gap_evt->params.timeout.src == BLE_GAP_TIMEOUT_SRC_CONN)
{
NRF_LOG_DEBUG("Connection request timed out.");
}
} break;
case BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST:
{
NRF_LOG_DEBUG("BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST.");
// Accept parameters requested by peer.
err_code = sd_ble_gap_conn_param_update(p_gap_evt->conn_handle,
&p_gap_evt->params.conn_param_update_request.conn_params);
APP_ERROR_CHECK(err_code);
} 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:
{
// Disconnect on GATT client timeout event.
NRF_LOG_DEBUG("GATT client timeout.");
err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gattc_evt.conn_handle,
BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
APP_ERROR_CHECK(err_code);
} break;
case BLE_GATTS_EVT_TIMEOUT:
{
// Disconnect on GATT server timeout event.
NRF_LOG_DEBUG("GATT server timeout.");
err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gatts_evt.conn_handle,
BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
APP_ERROR_CHECK(err_code);
} break;
// The next two cases are Gatt stuff
// GATT Value Notification received from the SoftDevice
case BLE_GATTC_EVT_HVX:
{
// Check if the event is on the link for this instance
LbsDevice_t* pDevice = GetLbsDevice(p_gattc_evt->conn_handle);
if(!pDevice) { return; }
NRF_LOG_DEBUG("Notification received on Con %d", pDevice->ConHandle);
// Check if this is a Program volume fine tuning notification.
if (p_gattc_evt->params.hvx.handle == pDevice->PeerDb.button_handle)
{
// Ensure we got at least expected length
if (p_gattc_evt->params.hvx.len >= 17)
{
ButtonUpdate(pDevice, p_gattc_evt->params.hvx.data);
}
}
} break;
// GATT Write response received from the SoftDevice
case BLE_GATTC_EVT_WRITE_RSP:
{
// Check if this conn handle is interesting
LbsDevice_t* pDevice = GetLbsDevice(p_ble_evt->evt.gattc_evt.conn_handle);
if(!pDevice) { return; }
//If it is, Process pending Tx in the Tx Buffer
TxBufferProcess(&m_TxBuffer);
} break;
// GATT read response received from the SoftDevice
case BLE_GATTC_EVT_READ_RSP:
{
const ble_gattc_evt_read_rsp_t* pReadResp = &p_gattc_evt->params.read_rsp;
if(pReadResp->len < 1)
{
TxBufferProcess(&m_TxBuffer);
break;
}
LbsDevice_t* pDevice = GetLbsDevice(p_ble_evt->evt.gattc_evt.conn_handle);
if(pReadResp->handle == pDevice->PeerDb.button_handle)
{
if(pReadResp->len >= 1)
{
ButtonUpdate(pDevice, pReadResp->data);
}
}
} break;
default:
// Update GATT
nrf_ble_gatt_on_ble_evt(p_ble_evt, p_context); // FIXME is this needed?
// No further implementation needed.
break;
}
}
/**@brief Function for initializing the BLE stack.
*
* @details Initializes the SoftDevice and the BLE event interrupts.
*/
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);
}
static void ClearBondings(void)
{
// Abort if there is no peers to clear
if(0 == pm_peer_count()) { return; }
// Abort if we have any active BLE connections
if(0 != ble_conn_state_central_conn_count()) { return; }
nrf_ble_scan_stop();
ret_code_t err_code;
//err_code = softdevice_handler_sd_disable();
err_code = pm_peers_delete();
if(NRF_SUCCESS == err_code)
{
NRF_LOG_INFO("Starting bondings clearing");
}
else
{
NRF_LOG_INFO("Bondings clear failed, code %d", err_code);
}
}
/**@brief Function for handling events from the button handler module.
*
* @param[in] pin_no The pin that the event applies to.
* @param[in] button_action The button action (press or release).
*/
static void button_event_handler(uint8_t pin_no, uint8_t button_action)
{
static uint8_t ButtonStatus; // Keeps an overview of the state of all buttons
uint8_t iIndex = 0;
// int8_t iDeltaVolume = 0;
uint8_t Mask;
switch (pin_no)
{
case HA0VOLUP_BUTTON:
iIndex = 0;
// iDeltaVolume = 1;
Mask = 1;
break;
case HA0VOLDOWN_BUTTON:
iIndex = 0;
// iDeltaVolume = -1;
Mask = 2;
break;
case HA1VOLUP_BUTTON:
iIndex = 1;
// iDeltaVolume = 1;
Mask = 4;
break;
case HA1VOLDOWN_BUTTON:
iIndex = 1;
// iDeltaVolume = -1;
Mask = 8;
break;
default:
APP_ERROR_HANDLER(pin_no);
return;
break;
}
// Only adjust volume on button pressed, ignore releases
if(button_action)
{
if(m_aLbsDevices[iIndex].Secure && m_aLbsDevices[iIndex].Discovered)
{
//ButtonChange(&m_aLbsDevices[iIndex], iButton);
}
}
// Update button status overview
if(button_action)
{
ButtonStatus |= Mask;
}
else
{
ButtonStatus &= ~Mask;
}
// Check for clear bondings
if(
(button_action) && // if event is button pressed
(ButtonStatus == (1 | 4)) // And only the Clear bonding buttons are currently pressed
) // Its because the second of the two Clear bonding buttons was just pressed
{
ClearBondings();
}
}
/**@brief Function for initializing the button handler module.
*/
static void buttons_init(void)
{
ret_code_t err_code;
// The array must be static because a pointer to it is saved in the button handler module.
static app_button_cfg_t buttons[] =
{
{HA0VOLUP_BUTTON, false, BUTTON_PULL, button_event_handler},
{HA0VOLDOWN_BUTTON, false, BUTTON_PULL, button_event_handler},
{HA1VOLUP_BUTTON, false, BUTTON_PULL, button_event_handler},
{HA1VOLDOWN_BUTTON, false, BUTTON_PULL, button_event_handler}
};
err_code = app_button_init(buttons, ARRAY_SIZE(buttons), BUTTON_DETECTION_DELAY);
APP_ERROR_CHECK(err_code);
err_code = app_button_enable();
APP_ERROR_CHECK(err_code);
}
static void EnableLbsNotifications(const LbsDevice_t* pDevice)
{
// Enable button notifications
uint32_t Status = cccd_configure(pDevice->ConHandle, pDevice->PeerDb.button_cccd_handle, true);
if(Status != NRF_SUCCESS)
{
NRF_LOG_INFO("cccd_configure error %d", Status);
NRF_LOG_INFO("Handle Button %d, CCCd %d", pDevice->PeerDb.button_handle, pDevice->PeerDb.button_cccd_handle);
}
}
static void ReadCharacteristic(uint16_t ConHandle, uint16_t CharHandle)
{
tx_message_t * p_msg = TxBufferPush(&m_TxBuffer);
p_msg->req.read_handle = CharHandle;
p_msg->conn_handle = ConHandle;
p_msg->type = READ_REQ;
TxBufferProcess(&m_TxBuffer);
NRF_LOG_DEBUG("Reading characteristic Char %d on con %d", CharHandle, ConHandle);
}
static void OnLbsDeviceReady(const LbsDevice_t* pDevice)
{
EnableLbsNotifications(pDevice);
ReadCharacteristic(pDevice->ConHandle, pDevice->PeerDb.button_handle);
}
static void OnLbsDeviceDisconnect(LbsDevice_t* pDevice)
{
uint32_t iIndex = GetConHandleIndex(pDevice->ConHandle);
uint32_t LedId = (0 == iIndex) ? LEDHA0_LED : LEDHA1_LED;
bsp_board_led_off(LedId);
LbsDeviceClear(pDevice);
}
/**@brief Function for handling database discovery events.
*
* @details This function is a callback function to handle events from the database discovery module.
* Depending on the UUIDs that are discovered, this function forwards the events
* to their respective services.
*
* @param[in] p_event Pointer to the database discovery event.
*/
static void db_disc_handler(ble_db_discovery_evt_t * p_evt)
{
NRF_LOG_INFO("Call to db_disc_handler for link 0x%x!", p_evt->conn_handle);
// Check if the Led Button Service was discovered.
if (
p_evt->evt_type == BLE_DB_DISCOVERY_COMPLETE &&
p_evt->params.discovered_db.srv_uuid.uuid == LBS_UUID_SERVICE &&
p_evt->params.discovered_db.srv_uuid.type == m_ble_uuid_type
)
{
NRF_LOG_INFO("LSB service discovered");
uint16_t ConHandle = p_evt->conn_handle;
lbs_db_t DiscoveredDb;
for (uint32_t i = 0; i < p_evt->params.discovered_db.char_count; i++)
{
const ble_gatt_db_char_t * p_char = &(p_evt->params.discovered_db.charateristics[i]);
NRF_LOG_DEBUG("UUID %x", p_char->characteristic.uuid.uuid);
NRF_LOG_DEBUG("notify %d, read %d, write %d, write_command %d",
p_char->characteristic.char_props.notify,
p_char->characteristic.char_props.read,
p_char->characteristic.char_props.write,
p_char->characteristic.char_props.write_wo_resp
);
switch (p_char->characteristic.uuid.uuid)
{
case LBS_UUID_LED_CHAR:
DiscoveredDb.led_handle = p_char->characteristic.handle_value;
break;
case LBS_UUID_BUTTON_CHAR:
DiscoveredDb.button_handle = p_char->characteristic.handle_value;
DiscoveredDb.button_cccd_handle = p_char->cccd_handle;
break;
default:
NRF_LOG_INFO("Unused characteristic");
break;
}
}
LbsDevice_t* pDevice = GetLbsDevice(ConHandle);
if(!pDevice)
{
NRF_LOG_INFO("Discovery before saved device");
return;
}
// Store the discovered service parameters if entry is clear
if (
(pDevice->PeerDb.led_handle == BLE_GATT_HANDLE_INVALID)&&
(pDevice->PeerDb.button_handle == BLE_GATT_HANDLE_INVALID)&&
(pDevice->PeerDb.button_cccd_handle == BLE_GATT_HANDLE_INVALID)
)
{
pDevice->PeerDb = DiscoveredDb;
pDevice->Discovered = true;
// See if we are already bonded
if(!pDevice->Secure)
{
pm_peer_id_get(pDevice->ConHandle, &pDevice->PeerId);
if(PM_PEER_ID_INVALID == pDevice->PeerId)
{
// If not start bonding
NRF_LOG_INFO("New LBS Device - Securing connection and bonding");
ret_code_t Status = pm_conn_secure(pDevice->ConHandle, false);
if( NRF_SUCCESS != Status)
{
NRF_LOG_INFO("Securing connection failed with code %d", Status);
}
}
}
else
{
OnLbsDeviceReady(pDevice);
}
}
else
{
NRF_LOG_INFO("Discovery info rejected due to peer info already set");
}
}
else
{
if(p_evt->evt_type == BLE_DB_DISCOVERY_COMPLETE)
{
NRF_LOG_INFO("Discovery complete");
NRF_LOG_INFO("Failed to discover Button LED Service");
NRF_LOG_INFO("Found UUID %d, type %d", p_evt->params.discovered_db.srv_uuid.uuid, p_evt->params.discovered_db.srv_uuid.type);
NRF_LOG_INFO("Expected UUID %d, type %d", LBS_UUID_SERVICE, m_ble_uuid_type);
}
else
{
if(p_evt->evt_type == BLE_DB_DISCOVERY_SRV_NOT_FOUND)
{
NRF_LOG_INFO("DISC: Service not found");
}
NRF_LOG_INFO("Disc event type %d", p_evt->evt_type);
}
}
}
/** @brief Database discovery initialization.
*/
static void db_discovery_init(void)
{
ret_code_t err_code = ble_db_discovery_init(db_disc_handler);
APP_ERROR_CHECK(err_code);
}
static void PeerManagerEventHandler(pm_evt_t const * p_event)
{
NRF_LOG_DEBUG("PeerManagerEvent %d", p_event->evt_id);
pm_handler_on_pm_evt(p_event);
pm_handler_flash_clean(p_event);
switch(p_event->evt_id)
{
case PM_EVT_CONN_SEC_SUCCEEDED:
{
LbsDevice_t* pDevice = GetLbsDevice(p_event->conn_handle);
if(!pDevice)
{
NRF_LOG_INFO("Secured connection to non LBS device!!")
return;
}
pDevice->PeerId = p_event->peer_id;
pDevice->Secure = true;
if(pDevice->Discovered)
{
OnLbsDeviceReady(pDevice);
}
} break;
case PM_EVT_PEERS_DELETE_SUCCEEDED:
{
NRF_LOG_INFO("Bondings cleared");
scan_start();
} break;
case PM_EVT_PEERS_DELETE_FAILED:
{
NRF_LOG_INFO("Failed to clear Bondings");
} break;
default:
break;
}
}
/**@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 This function handles any pending log operations, then sleeps until the next event occurs.
*/
static void idle_state_handle(void)
{
if (NRF_LOG_PROCESS() == false)
{
//nrf_pwr_mgmt_run(); // Disabled, as the Time slot events does not cause main process to exit this sleep
}
}
/** @brief Function for initializing the 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 the timer.
*/
static void timer_init(void)
{
ret_code_t err_code = app_timer_init();
APP_ERROR_CHECK(err_code);
}
static void gatt_event_handler(nrf_ble_gatt_t *p_gatt, nrf_ble_gatt_evt_t const *p_evt)
{
// FIXME this is called when a parameter value is changed
NRF_LOG_INFO("Gatt event handler triggered.");
}
/**@brief Function for initializing the GATT module.
*/
static void gatt_init(void)
{
ret_code_t err_code = nrf_ble_gatt_init(&m_gatt, gatt_event_handler);
APP_ERROR_CHECK(err_code);
}
static void PeerManagerInit(void)
{
ret_code_t err_code;
err_code = pm_init();
APP_ERROR_CHECK(err_code);
// Configure peer manager
static ble_gap_sec_params_t sec_params =
{
.bond = true,
.mitm = false,
.lesc = 1,
.keypress = 0,
.io_caps = BLE_GAP_IO_CAPS_NONE,
.oob = false,
.min_key_size = 7,
.max_key_size = 16,
.kdist_own =
{
.enc = 1,
.id = 1,
.sign = 0,
.link = 0
},
.kdist_peer =
{
.enc = 1,
.id = 1,
.sign = 0,
.link = 0
}
};
err_code = pm_sec_params_set(&sec_params);
APP_ERROR_CHECK(err_code);
}
ret_code_t ble_MyStuff_client_init(void)
{
ret_code_t err_code;
ble_uuid_t lbs_uuid;
ble_uuid128_t lbs_base_uuid = {LBS_UUID_BASE};
TxBufferInit(&m_TxBuffer, m_aTxBufferStorage, ARRAY_SIZE(m_aTxBufferStorage));
// Initialize con handle and peer db storage
for(uint32_t i = 0; i < NRF_SDH_BLE_CENTRAL_LINK_COUNT; i++)
{
LbsDeviceClear(&m_aLbsDevices[i]);
}
// This function sets p_ble_lbs_c->uuid_type to a suitable value
err_code = sd_ble_uuid_vs_add(&lbs_base_uuid, &m_ble_uuid_type);
VERIFY_SUCCESS(err_code);
lbs_uuid.type = m_ble_uuid_type;
lbs_uuid.uuid = LBS_UUID_SERVICE;
// Register discovery event handler
err_code = ble_db_discovery_evt_register(&lbs_uuid);
VERIFY_SUCCESS(err_code);
// Register Peer Manager event handler
err_code = pm_register(PeerManagerEventHandler);
VERIFY_SUCCESS(err_code);
return NRF_SUCCESS;
}
// Struct for requesting first time slot
static const nrf_radio_request_t RadioRequestFirst =
{
.request_type = NRF_RADIO_REQ_TYPE_EARLIEST,
.params =
{
.earliest =
{
.hfclk = NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED,
.priority = NRF_RADIO_PRIORITY_NORMAL,
.length_us = 600, // Time slot duration of 600 us
.timeout_us = 1000000 // Error if time slot not aquired within 1 second
}
}
};
//Struct for periodic time slot requesting
static const nrf_radio_request_t RadioRequestPeriodic =
{
.request_type = NRF_RADIO_REQ_TYPE_NORMAL,
.params =
{
.normal =
{
.hfclk = NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED,
.priority = NRF_RADIO_PRIORITY_HIGH,
.length_us = 600, // Time slot duration of 600 us
.distance_us = 10000 // Request periodic time slot every 10 ms
// .distance_us = 1000000 // Request periodic time slot every 1 s
}
}
};
static uint8_t aTestPacket[80];
/**@brief Function for configuring radio for raw transmission of BLE like packets.
*
* @details Never use function outside of a timeslot given by the BLE stack, or when BLE stack is disabled
*
* @param[in] iFrequency Frequency in MHz.
* @param[in] bUse2Mbit true: 2 Mbit mode, false: 1 Mbit mode.
* @param[in] iPower Power in db(note that not all values are legal, consult NRF radio peripheral manual).
* @param[in] Address Address is not implemented!!
* @param[in] CrcSeed 24 bit CRC seed.
*/
static void RadioConfigure(uint32_t iFrequency, bool bUse2Mbit, int8_t iPower, uint32_t Address, uint32_t CrcSeed)
{
NRF_RADIO->POWER = 0x01; // Power on the radio, no side effect if already powered on
NRF_RADIO->INTENSET = 0x200001; // DEBUG
NRF_RADIO->INTENSET = 0xFFFFFFFF; // DEBUG
NRF_RADIO->SHORTS = 0x40002; // END->Disable and TxReady->Start
NRF_RADIO->FREQUENCY = iFrequency - 2400;
NRF_RADIO->TXPOWER = iPower;
if(bUse2Mbit)
{
NRF_RADIO->MODE = 0x04; // 2 mbit BLE
NRF_RADIO->PCNF0 = 0x1000008; // 2 bytes pre-amble (2 mbit BLE), Length field on air is 8 bits
}
else
{
NRF_RADIO->MODE = 0x03; // 1 mbit BLE
NRF_RADIO->PCNF0 = 0x08; // 1 byte1 pre-amble (non 2 mbit BLE), Length field on air is 8 bits
}
NRF_RADIO->PCNF1 = 0x2030000; // FIXME
//NRF_RADIO->BASE0 - Radio base addr, controls something about the address, cant quite figure it out
//NRF_RADIO->BASE1
//NRF_RADIO->PREFIX0 - Radio prefix addr, controls something about addr.
//NRF_RADIO->PREFIX1 -
NRF_RADIO->TXADDRESS = 0;
NRF_RADIO->CRCCNF = 0x0103;
NRF_RADIO->CRCPOLY = 0x80032C; // Polynomium config for BLE
NRF_RADIO->CRCINIT = CrcSeed; // 0x555555 used for advertising, something agreed on connection for non advertising. Defined in WICL advertising for WICL
uint32_t WhiteningSeed = (iFrequency + 1); // BLE whitening seed is ((channel no * 2) + 1)
NRF_RADIO->DATAWHITEIV = (__RBIT(WhiteningSeed)) >> 25; // Reverse and move to bits 0-6, as thats how NRF radio HW wants it
}
static nrf_radio_signal_callback_return_param_t TimeslotCbReturn;
static uint32_t iLostSlots = 0;
static uint32_t aStates[10];
/**@brief Function for requesting a timeslot with the periodic configuration, when a timeslot has been denied.
*
* @param[in] iFrequency Number of consecutively denied time slots, so it requests a time slot at the correct time.
*/
static void RequestPeriodic(uint32_t iIntervalMultiplier)
{
static nrf_radio_request_t RadioRequest;
memcpy(&RadioRequest, &RadioRequestPeriodic, sizeof(RadioRequest));
//RadioRequest.params.normal.distance_us *= iIntervalMultiplier;
// Debug - try and adjust timing, by adding an extra 0,1 ms. to get out of sync with connection events
RadioRequest.params.normal.distance_us *= iIntervalMultiplier;
RadioRequest.params.normal.distance_us += 100;
// Turn on LED, Time slot denied (function is only called when being denied a time slot)
bsp_board_led_on(LEDHA1_LED);
// debug end
sd_radio_request(&RadioRequest);
}
/**@brief Function called by stack on time slot start and for events during time slot.
*
* @details Note this function is called in high priority IRQ, blocking even the stack.
*
* @param[in] Signal Reason for function being called
*/
nrf_radio_signal_callback_return_param_t* RadioTimeslotCb(uint8_t Signal)
{
static nrf_radio_request_t RadioRequest;
// NRF_RADIO_SIGNAL_CALLBACK_ACTION_NONE
// NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND
// NRF_RADIO_SIGNAL_CALLBACK_ACTION_END
// NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END
// Time stamp radio timeslot start
NRF_TIMER0->TASKS_CAPTURE[0] = 1U;
aStates[0] = NRF_TIMER0->CC[0];
switch(Signal)
{
case NRF_RADIO_CALLBACK_SIGNAL_TYPE_START:
{
SendPacket(aTestPacket);
// Turn off LED, as we got the time slot
bsp_board_led_off(LEDHA1_LED);
iLostSlots = 0;
// Request next timeslot
memcpy(&RadioRequest, &RadioRequestPeriodic, sizeof(RadioRequest)); // Use copy as return arg is not const
TimeslotCbReturn.callback_action = NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END;
TimeslotCbReturn.params.request.p_next = &RadioRequest;
} break;
case NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0:
{
NRF_LOG_INFO("A");
APP_ERROR_HANDLER(Signal);
return 0;
} break;
case NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO:
{
NRF_LOG_INFO("B");
APP_ERROR_HANDLER(Signal);
return 0;
} break;
case NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_FAILED:
{
NRF_LOG_INFO("C");
APP_ERROR_HANDLER(Signal);
return 0;
} break;
case NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_SUCCEEDED:
{
NRF_LOG_INFO("D");
APP_ERROR_HANDLER(Signal);
return 0;
} break;
default:
NRF_LOG_INFO("E");
APP_ERROR_HANDLER(Signal);
return 0;
break;
}
// Time stamp radio timeslot end
NRF_TIMER0->TASKS_CAPTURE[0] = 1U;
aStates[6] = NRF_TIMER0->CC[0];
return &TimeslotCbReturn;
}
/**@brief Function called by stack on SOC events.
*
* @details Among other things, called by stack with new of time slots being cancelled.
*
* @param[in] sys_evt Reason for function being called
* @param[in] pContext Unused.
*/
static void soc_evt_handler(uint32_t sys_evt, void * pContext)
{
switch(sys_evt)
{
case NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN:
{
NRF_LOG_INFO("Timeslot Invalid");
} break;
case NRF_EVT_RADIO_SESSION_IDLE:
{
NRF_LOG_INFO("Timeslot idle");
} break;
case NRF_EVT_RADIO_SESSION_CLOSED:
{
NRF_LOG_INFO("Timeslot closed");
} break;
case NRF_EVT_RADIO_BLOCKED:
{
NRF_LOG_INFO("Timeslot blocked");
iLostSlots++;
// Request next timeslot
RequestPeriodic(iLostSlots + 1);
} break;
case NRF_EVT_RADIO_CANCELED:
{
NRF_LOG_INFO("Timeslot canceled");
iLostSlots++;
// Request next timeslot
RequestPeriodic(iLostSlots + 1);
} break;
case NRF_EVT_FLASH_OPERATION_SUCCESS:
{
NRF_LOG_INFO("Flash Wr OK");
break;
}
default:
NRF_LOG_INFO("Unknown soc event %d", sys_evt);
APP_ERROR_HANDLER(sys_evt);
break;
}
}
static bool bTxing = false;
/**@brief Sends pPacket as 2Mbit BLE on 2404 MHz with advertising CRC and some undefined address
*
* @details Sends BLE like 2 Mbit package and saves a time stamp for each radio state during the activity.
* NOTE: Only call when BEL stack does not control the radio, e.g. in time slots or when stack is disabled.
*
* @param[in] pPacket First byte of Packet is length of the packet. The following bytes is the payload.
*/
static void SendPacket(uint8_t * pPacket)
{
//Power : int8_t(8), int8_t(4), int8_t(0), int8_t(-40)
uint32_t iFrequency = 2404;
bool bUse2Mbit = true;
int8_t iPower = (int8_t)4;
uint32_t Address = 0; // FIXME
uint32_t CrcSeed = 0x555555; // 0x555555 used for advertising, something agreed on connection for non advertising. Defined in WICL advertising for WICL
RadioConfigure(iFrequency, bUse2Mbit, iPower, Address, CrcSeed);
NRF_RADIO->PACKETPTR = (uint32_t)pPacket;
// send the packet:
NRF_RADIO->TASKS_TXEN = 1;
// Time stamp the radio states, for debug information
// Time stamp TX start
NRF_TIMER0->TASKS_CAPTURE[0] = 1U;
aStates[1] = NRF_TIMER0->CC[0];
// Wait for radio to exit disabled
while(0x00 == NRF_RADIO->STATE);
// Time stamp disabled exited
NRF_TIMER0->TASKS_CAPTURE[0] = 1U;
aStates[2] = NRF_TIMER0->CC[0];
// Wait for radio to exit Tx ramp up
while(0x09 == NRF_RADIO->STATE);
// Time stamp TX ramp up exited
NRF_TIMER0->TASKS_CAPTURE[0] = 1U;
aStates[3] = NRF_TIMER0->CC[0];
// Wait for radio to exit TX
while(0x0B == NRF_RADIO->STATE);
// Time stamp radio TX exited
NRF_TIMER0->TASKS_CAPTURE[0] = 1U;
aStates[4] = NRF_TIMER0->CC[0];
// Wait for radio to exit Tx disable state
while(0x0C == NRF_RADIO->STATE);
// Time stamp radio Tx disable exit
NRF_TIMER0->TASKS_CAPTURE[0] = 1U;
aStates[5] = NRF_TIMER0->CC[0];
bTxing = true;
}
int main(void)
{
NRF_NVMC->ICACHECNF |= 0x0100;
// Initialize.
log_init();
timer_init();
leds_init();
buttons_init();
power_management_init();
ble_stack_init();
gatt_init();
db_discovery_init();
PeerManagerInit();
uint32_t InitStatus = ble_MyStuff_client_init();
if(NRF_SUCCESS != InitStatus)
{
NRF_LOG_INFO("INIT FAILED!!");
APP_ERROR_CHECK(InitStatus);
}
NRF_SDH_SOC_OBSERVER(m_soc_observer, APP_SOC_OBSERVER_PRIO, soc_evt_handler, NULL);
ble_conn_state_init();
scan_init();
// Start execution.
NRF_LOG_INFO("Time slot Tx example.");
scan_start();
memset(aTestPacket, 0xDEADBEEF, 80);
aTestPacket[0] = 79;
ret_code_t Status = sd_radio_session_open(RadioTimeslotCb);
APP_ERROR_CHECK(Status);
Status = sd_radio_request(&RadioRequestFirst);
APP_ERROR_CHECK(Status);
for (;;)
{
if(bTxing)
{
bTxing = false;
// Debug
// Print debug info
// NRF_LOG_INFO("States0 %X, %X, %X, %X", aStates[0], aStates[1], aStates[2], aStates[3]);
// NRF_LOG_INFO("States1 %X, %X, %X, %X", aStates[4], aStates[5], aStates[6], aStates[7]);
// Debug end
}
else
{
idle_state_handle();
}
}
}