顯示具有 nRF5X 標籤的文章。 顯示所有文章
顯示具有 nRF5X 標籤的文章。 顯示所有文章

2018年11月27日 星期二

nRF51/nRF52 Runs BLE Peripheral and ESB Protocol Concurrently


   The code for this post is here.   

  Continue my previous post about Nordic's Enhanced Shockburst™(ESB)  reliable transceiving, In this post I would like to discuss how to combine ESB with Bluetooth low energy(BLE).

       BLE protocol is widely adopted on the consumer mobile devices, ALL mainstream mobile operation systems support that, and ALL device have been implemented the function. If is extremely easy to find out a device supporting BLE at hand. The BLE network topology is point to point, it is very straightforward and intuitive. For the simplest topology, the connection setting is quick and instinct, easy to be accepted by dummy consumer. Thus, lots mobile peripherals use this protocol connecting with mobile to transfer data or fetch command.
     In most case, one to one structure is enough for the personal device, lots  sporting wrist/belt products have existed in the market.
     However, in home or small commercial cases, one to one structure is not enough, even enough, it is only adequate. You could try to consider a user scenario :  there is a large busking,  several installation separated in an performance stage, ,the performer needs to interactive those device. The controller needs to use phone or computer to ignite fire, spray gas.. etc. Besides, It is better for the stuffs that they could use phone to control those devices, because their working space is limited.

     WIFI is a solution for the small performance issue, but WIFI might be not a good solution, It is because the setting of WIFI is troublesome, and once the phone connect to a router which is not on internet, the phone is not able to access internet either (even the phone's LTE function running).

     ESB protocol provides star and mesh  topology, but the phones do not support ESB.  However, nRF51/nRF52chip is able to  run the two protocols at the same time, that fills the gap to use phone controlling or receiving data from multi-devices.

   In this demonstration, there is a nRF51/nRF52 device conveying BLE data to ESB and vice versa. For demonstrating the transfer in detail,  I also provide a device based on nRF24LE1 for sending/receiving ESB packets.

 
※ My code is based on nRF5X SDK 11, you could download that from here.

   零. Download the code from Github.
Place the whole folder under nRF5X_SDK11_ROOT\examples\ble_peripheral. The project files are as the same as SDK default examples, in pca10028 (nRF51) and pca10040 (nRF52) repectively. All projects are for Keil-C 5.
       nRF24LE1 device code is in under folder nRF24LE1, in this folder,  all required files have been included, you do not need to download nRFgo SDK.
        UART are in baudrate 38400 in all device.

 一. About the nRF5x code : 

   The main.c is based on ble_app_template, macro _INTACT denotes where I modified. I added:

    UART function for print output.
    A timer, to print time series per second.
    A BLE service, providing to send/receive data  to/from BLE central (phone).

 
   The code under esb and esb_timeslot folder I downloaded from here. notice that the code in esb folder is not the same as SDK default containing (nRF5X_SDK11_ROOT\components\properitary_rf\esb), the adopted IRQs have been changed. And the esb_timeslot\esb_timeslot.c and esb_timeslot\esb_timeslot.h have been modified for my application.

   Due to the IRQs collision might bring the ESB function hanging. Hance, as this thread mentioned, about line 467, esb_timeslot\esb_timeslot.c should be modified:


void nrf_esb_event_handler(nrf_esb_evt_t const * p_event)
{
    :
    :
    if (p_event->evt_id & NRF_ESB_EVENT_RX_RECEIVED)
    {
#if !defined(_INTACT)   
    fetch_esb_rx_data();
#else   
        /* Data reception is handled in a lower priority interrup. */
        /* Call UESB_RX_HANDLE_IRQHandler later. */
        NVIC_SetPendingIRQ(UESB_RX_HANDLE_IRQn);
#endif   
    }
}


The nrf_esb_event_handler function is called by function ESB_EVT_IRQHandler (esb/esb.c, line 909), which is a software IRQ. But UESB_RX_HANDLE_IRQn is the re-used of WDT_IRQ.  It is not appropriate to manipulate the other IRQ inside a non-software IRQ. Thus, it is necessary to modify the code for directly fetching ESB data  in the function nrf_esb_event_handler.


 二. The code nRF24LE1 :
  The code is copied from folder txrx of my previous post's repository. Note that function, about 100 of file esb_app.c :
 

hal_nrf_enable_dynamic_ack(false);

It should be consisted with  nRF5X code esb_timeslot/esb_timeslot.c, about line 523:


nrf_esb_config.selective_auto_ack = false;

If you set that as false in nRF24LE1 but leave that as true in nRF51 the UART output of nRF24LE1  would display rf_max_retry_count_reached  but the packet indeed delivered.  Disable dynamic_ack/auto_ack means you need to manually sendback ack packet(zero length packet) to sender.

  Result :

  In BLE scanner,  the peripheral name shows as "NRF51 ESB to/from ":
※ the device name in code has been changed as "ESB to/from BLE" for the original name was "NRF51 ESB to/from BLE", which is too long to display completely(over 20 bytes). But below images were captured before the fixing .





After the phone connected with the nRF5x and set notification(subscribing). the values would be received periodically and vary with time.
 



I sent a data as to the nRF5x:



And the nRF24LE1 would received this data, as its UART showing:




Reference :

Make Nordic Enhanced ShockBurst™ (ESB) Transceivers as Multiceiver and Reliable

Migration from µESB to nrf_esb

Running micro-ESB concurrently with BLE



2017年4月18日 星期二

nRF51/52 ROM Protection : Prevent Someone Else From Dumping the Hex File


   To avoid being piracy is very important for software and firmware development. Of course, you should protect your source code from being spreaded out. But from firmware perspective, it is a way to piracy your intelligence: burglar might clone your WHOLE device, including the mechanism, circuit and firmware for mass production directly. It is not necessary to obtain your source code.
  In nRF51822 and 52832, There is a mechanism to prevent that happens. I will demonstrate that:
 My SDK versions are 10 for nRF51, and 11 for nRF52.

  To Simplify the demonstration. I copy the header file from peripheral\uicr_config\uicr_config.h to my project folder, and the main.c includes the file.
    Open the uicr_config.h, you could read that there is many address listed, you could modify the values on those addresses. The values I changing be :

// const uint32_t UICR_CLENR0    __attribute__((at(0x10001000))) __attribute__((used)) = 0xFFFFFFFF;
// const uint32_t UICR_RBPCONF   __attribute__((at(0x10001004))) __attribute__((used)) = 0xFFFFFFFF;
// const uint32_t UICR_XTALFREQ  __attribute__((at(0x10001008))) __attribute__((used)) = 0xFFFFFFFF;

const uint32_t UICR_ADDR_0x80 __attribute__((at(0x10001080))) __attribute__((used)) = 0x12345678;
// const uint32_t UICR_ADDR_0x84 __attribute__((at(0x10001084))) __attribute__((used)) = 0xFFFFFFFF;
// const uint32_t UICR_ADDR_0x88 __attribute__((at(0x10001088))) __attribute__((used)) = 0xFFFFFFFF;
// const uint32_t UICR_ADDR_0x8C __attribute__((at(0x1000108C))) __attribute__((used)) = 0xFFFFFFFF;
 const uint32_t UICR_ADDR_0x90 __attribute__((at(0x10001090))) __attribute__((used)) = 0x11223344;
 const uint32_t UICR_ADDR_0x94 __attribute__((at(0x10001094))) __attribute__((used)) = 0x55667788;
// const uint32_t UICR_ADDR_0x98 __attribute__((at(0x10001098))) __attribute__((used)) = 0xFFFFFFFF;
// const uint32_t UICR_ADDR_0x9C __attribute__((at(0x1000109C))) __attribute__((used)) = 0xFFFFFFFF;
// const uint32_t UICR_ADDR_0xA0 __attribute__((at(0x100010A0))) __attribute__((used)) = 0xFFFFFFFF;
// const uint32_t UICR_ADDR_0xA4 __attribute__((at(0x100010A4))) __attribute__((used)) = 0xFFFFFFFF;
// const uint32_t UICR_ADDR_0xA8 __attribute__((at(0x100010A8))) __attribute__((used)) = 0xFFFFFFFF;
// const uint32_t UICR_ADDR_0xAC __attribute__((at(0x100010AC))) __attribute__((used)) = 0xFFFFFFFF;
// const uint32_t UICR_ADDR_0xB0 __attribute__((at(0x100010B0))) __attribute__((used)) = 0xFFFFFFFF;
 const uint32_t UICR_ADDR_0xB4 __attribute__((at(0x100010B4))) __attribute__((used)) = 0xAABBCCDD;
// const uint32_t UICR_ADDR_0xB8 __attribute__((at(0x100010B8))) __attribute__((used)) = 0xFFFFFFFF;
// const uint32_t UICR_ADDR_0xBC __attribute__((at(0x100010BC))) __attribute__((used)) = 0xFFFFFFFF;
// const uint32_t UICR_ADDR_0xC0 __attribute__((at(0x100010C0))) __attribute__((used)) = 0xFFFFFFFF;
// const uint32_t UICR_ADDR_0xC4 __attribute__((at(0x100010C4))) __attribute__((used)) = 0xFFFFFFFF;
// const uint32_t UICR_ADDR_0xC8 __attribute__((at(0x100010C8))) __attribute__((used)) = 0xFFFFFFFF;
// const uint32_t UICR_ADDR_0xCC __attribute__((at(0x100010CC))) __attribute__((used)) = 0xFFFFFFFF;
// const uint32_t UICR_ADDR_0xD0 __attribute__((at(0x100010D0))) __attribute__((used)) = 0xFFFFFFFF;
// const uint32_t UICR_ADDR_0xD4 __attribute__((at(0x100010D4))) __attribute__((used)) = 0xFFFFFFFF;

  By the way,  What is UICR ? UICR is the short for User Information Configuration Registers, where the values are non-alterable. The purpose for UICR is to make a space for manufacturer signing.


Build and download the hex to your device, and type the command line (I use nRF52832 as my target device):


> nrfjprog --family NRF52 --memrd 0x10001080 --n 128
0x10001080: 12345678 FFFFFFFF FFFFFFFF FFFFFFFF   |xV4.............|
0x10001090: 11223344 55667788 FFFFFFFF FFFFFFFF   |D3"..wfU........|
0x100010A0: FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF   |................|
0x100010B0: FFFFFFFF AABBCCDD FFFFFFFF FFFFFFFF   |................|
0x100010C0: FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF   |................|
0x100010D0: FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF   |................|
0x100010E0: FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF   |................|
0x100010F0: FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF   |................|

 The address values have been changed.

You could use the command the dump the whole hex :


> nrfjprog --family NRF52 --readcode output.hex

That is what I prevent from.


To avoid the dumping, Add below code in the beginning of main function :


#include "ble_flash.h"

/**@brief Function for application main entry.
 */
int main(void)
{
#if(1)
 if(UICR_RBPCONF_PALL_Enabled !=
  (uint32_t)((NRF_UICR->RBPCONF & UICR_RBPCONF_PALL_Msk) >> UICR_RBPCONF_PALL_Pos)) 
 {
  ble_flash_word_write((uint32_t *)&NRF_UICR->RBPCONF, 
   (NRF_UICR->RBPCONF & ~UICR_RBPCONF_PALL_Msk));
 }/*if */
#endif 
    uint32_t err_code;
    bool erase_bonds;

and you should include folder SDK_ROOT\components\drivers_nrf\ble_flash and the file SDK_ROOT\components\drivers_nrf\ble_flash\ble_flash.c in your working porject.

Take Keil C as the project modification example:




After download to the device, you could find the readback command would not work:


> nrfjprog --family NRF52 --memrd 0x10001080 --n 128
ERROR: The operation attempted is unavailable due to readback protection in
ERROR: your device. Please use --recover to unlock the device.

  That is, the SWD (Serial Wired Debug) function has been forbidden, the SWD could accept recover request from now.

NOTE :If you use Keil C, the IDE would not reset the device after the downloading. but you could press the download button twice to make sure the SWD has been disabled:

* JLink Info: Found SWD-DP with ID 0x2BA01477
* JLink Info: Found Cortex-M4 r0p1, Little endian.
* JLink Info: FPUnit: 6 code (BP) slots and 2 literal slots
* JLink Info: CoreSight components:
* JLink Info: ROMTbl 0 @ E00FF000
* JLink Info: ROMTbl 0 [0]: FFF0F000, CID: B105E00D, PID: 000BB00C SCS
* JLink Info: ROMTbl 0 [1]: FFF02000, CID: B105E00D, PID: 003BB002 DWT
* JLink Info: ROMTbl 0 [2]: FFF03000, CID: B105E00D, PID: 002BB003 FPB
* JLink Info: ROMTbl 0 [3]: FFF01000, CID: B105E00D, PID: 003BB001 ITM
* JLink Info: ROMTbl 0 [4]: FFF41000, CID: B105900D, PID: 000BB9A1 TPIU
* JLink Info: ROMTbl 0 [5]: FFF42000, CID: B105900D, PID: 000BB925 ETM
ROMTableAddr = 0xE00FF000
* JLink Info: SYSRESETREQ has confused core. Trying to reconnect and use VECTRESET.
* JLink Info: Found SWD-DP with ID 0x2BA01477
**JLink Warning: Failed to reset CPU. VECTRESET has confused core.
* JLink Info: Core is locked-up, trying to disable WDT.
**JLink Warning: Could not set S_RESET_ST
* JLink Info: Found SWD-DP with ID 0x2BA01477
* JLink Info: SYSRESETREQ has confused core. Trying to reconnect and use VECTRESET.
* JLink Info: Found SWD-DP with ID 0x2BA01477
**JLink Warning: Failed to reset CPU. VECTRESET has confused core.
* JLink Info: Core is locked-up, trying to disable WDT.
**JLink Warning: Could not set S_RESET_ST


Now you could see, the dumping has not been permitted, the only way to manipulate the storage (ROM) is to recover whole space: it is, erase all.

2017年4月4日 星期二

nRF51/52 : Disable Unused GPIOs


      

          By this forum thread, for unused GPIOs in nRF51822/nRF52832, programmer should set those as input pin, and set those in disconnection (from input buffer).

Following the rule, the code be:


//#include "nrf_gpio.h"

/**@brief Function to disable all gpio
*/
static void disable_all_gpio(void)
{
 uint32_t i;

 for( i = 0; i< NUMBER_OF_PINS; i++){
  NRF_GPIO->PIN_CNF[i] = 
  (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos)
  | (GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos)
  | (GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos)
  | (GPIO_PIN_CNF_INPUT_Disconnect << GPIO_PIN_CNF_INPUT_Pos)
  | (GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos);
 }/*for i*/

}/*disable_all_gpio*/



        If you avoid to manipulate the GPIO registers directly, you could call the wrapping function, nrf_gpio_input_disconnect.


//#include "nrf_gpio.h"

/**@brief Function to disable all gpio
*/
static void disable_all_gpio(void)
{
 uint32_t i;
 
 for( i = 0; i< NUMBER_OF_PINS; i++)
  nrf_gpio_input_disconnect(i);
}/*disable_all_gpio*/


      Theoretically, if you set the unused GPIOs in that way, the power-consumption will be lower.

2017年2月7日 星期二

BLE Gatt Date Time Service Implimentation in nRF51/52


    There are current time client library(components\ble\ble_services\ble_cts_c) and example code(examples\ble_peripheral\ble_app_cts_c) in the nRF SDK : the nRF device would adjust its real timer while the phone be connected. It is client mode : phone is real-time server, and device is real time client.
    But for some application, it is necessary that there is a real-time server in the BLE device. for example, it could make the phone to sure if the time adjusting is requisite or not.

    The post give a library code which follows GATT date time and GATT day of week.

The library code be :

ble_dts.h :



/* Copyright (c) 2012 Nordic Semiconductor. All Rights Reserved.
 *
 * The information contained herein is property of Nordic Semiconductor ASA.
 * Terms and conditions of usage are described in detail in NORDIC
 * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT.
 *
 * Licensees are granted free, non-transferable use of the information. NO
 * WARRANTY of ANY KIND is provided. This heading must NOT be removed from
 * the file.
 *
 */

/** @file
 *
 * @defgroup ble_sdk_srv_time current time Service
 * @{
 * @ingroup ble_sdk_srv
 * @brief current timer module.
 *
 * @details This module implements the current timer Service with the current time 
 *    , local time information(option) and reference time information(option) characteristic.
 *          During initialization it adds the current time Service and current time characteristic
 *          to the BLE stack database. Optionally it can also add local time,reference time descriptor
 *          to the current time server.
 *
 *          If specified, the module will support notification of the current time characteristic
 *          through the ble_time_current_time_update() function.
 *          If an event handler is supplied by the application, the current time Service will
 *          generate current timer Service events to the application.
 *
 * @note The application must propagate BLE stack events to the current timer Service module by calling
 *       ble_time_on_ble_evt() from the from the @ref ble_stack_handler callback.
 */

#ifndef _BLE_DTS_H_
#define _BLE_DTS_H_

#include <stdint.h>
#include <stdbool.h>
#include "ble.h"
#include "ble_srv_common.h"

#include "ble_date_time.h"

/**@brief current timer Service event type. */
typedef enum
{
    BLE_TIME_EVT_NOTIFICATION_ENABLED,                             /**< current time notification enabled event. */
    BLE_TIME_EVT_NOTIFICATION_DISABLED                             /**< current time notification disabled event. */
} ble_dts_evt_type_t;


/**@brief time Service event. */
typedef struct
{
    ble_dts_evt_type_t evt_type;                                  /**< Type of event. */
} ble_dts_evt_t;

// Forward declaration of the ble_time_t type. 
typedef struct ble_dts_s ble_dts_t;


/**@brief current time Service event handler type. */
typedef void (*ble_time_evt_handler_t) (ble_dts_t *p_time, ble_dts_evt_t * p_evt);



/**@brief current time Service init structure. This contains all options and data needed for
 *        initialization of the service.*/
typedef struct
{
    ble_time_evt_handler_t   evt_handler;     /**< Event handler to be called for handling events in the current time Service. */
    bool       is_notification_supported;    /**< TRUE if notification of current time measurement is supported. */
 ble_date_time_t     init_date_time;
} ble_dts_init_t;


/**@brief current time Service structure. This contains various status information for the service. */
struct ble_dts_s
{
    ble_time_evt_handler_t        evt_handler;                    /**< Event handler to be called for handling events in the current time Service. */
    uint16_t                      service_handle;                 /**< Handle of current time Service (as provided by the BLE stack). */
    ble_gatts_char_handles_t      date_time_handles;            /**< Handles related to the current time characteristic. */
 ble_gatts_char_handles_t      day_of_week_handles;    /**< Handles related to the day of week characteristic. */
    uint16_t                      conn_handle;                    /**< Handle of the current connection (as provided by the BLE stack, is BLE_CONN_HANDLE_INVALID if not in a connection). */
    bool                          is_notification_supported;      /**< TRUE if notification of current time is supported. */
 ble_date_time_t      current_date_time;             /**< Last current time passed to the current time Service. */
};


uint32_t ble_dts_init(ble_dts_t *p_dts, const ble_dts_init_t *p_dts_init);

void ble_dts_on_ble_evt(ble_dts_t *p_dts, ble_evt_t *p_ble_evt);

uint32_t ble_dts_update(ble_dts_t *p_dts, ble_date_time_t *p_current_time);

#endif // _BLE_DTS_H_

/** @} */



ble_dts.c :

/* Copyright (c) 2012 Nordic Semiconductor. All Rights Reserved.
 *
 * The information contained herein is property of Nordic Semiconductor ASA.
 * Terms and conditions of usage are described in detail in NORDIC
 * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT.
 *
 * Licensees are granted free, non-transferable use of the information. NO
 * WARRANTY of ANY KIND is provided. This heading must NOT be removed from
 * the file.
 *
 */

#include "ble_dts.h"
#include <string.h>
#include "nordic_common.h"
//#include "app_util.h"


static void on_connect(ble_dts_t *p_dts, ble_evt_t * p_ble_evt)
{
    p_dts->conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
}/*on_connect*/


static void on_disconnect(ble_dts_t *p_dts, ble_evt_t * p_ble_evt)
{
    UNUSED_PARAMETER(p_ble_evt);
    p_dts->conn_handle = BLE_CONN_HANDLE_INVALID;
}/*on_disconnect*/


static void on_write(ble_dts_t *p_dts, ble_evt_t * p_ble_evt)
{
    
        ble_gatts_evt_write_t * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;
        
        if (p_evt_write->handle == p_dts->date_time_handles.cccd_handle)
        {
            // CCCD written, call application event handler
            if (p_dts->evt_handler != NULL)
            {
                ble_dts_evt_t evt;
                
                if (ble_srv_is_notification_enabled(p_evt_write->data))
                {
                    evt.evt_type = BLE_TIME_EVT_NOTIFICATION_ENABLED;
                }
                else
                {
                    evt.evt_type = BLE_TIME_EVT_NOTIFICATION_DISABLED;
                }

                p_dts->evt_handler(p_dts, &evt);
            }
        }/*if */
}/*on_write*/


void ble_dts_on_ble_evt(ble_dts_t *p_date_time_svc, ble_evt_t * p_ble_evt)
{
    switch (p_ble_evt->header.evt_id)
    {
        case BLE_GAP_EVT_CONNECTED:
            on_connect(p_date_time_svc, p_ble_evt);
            break;
            
        case BLE_GAP_EVT_DISCONNECTED:
            on_disconnect(p_date_time_svc, p_ble_evt);
            break;
            
        case BLE_GATTS_EVT_WRITE:
            on_write(p_date_time_svc, p_ble_evt);
            break;
            
        default:
            break;
    }
}


/**@brief Add current time characteristic.
 *
 * @param[in]   p_dts     Date and time Service structure.
 * @param[in]   p_time_init   Information needed to initialize the service.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t date_and_time_char_add(ble_dts_t *p_dts, 
 const ble_dts_init_t * p_dts_init)
{
    uint32_t            err_code;
 ble_uuid_t          ble_uuid;
    ble_gatts_char_md_t char_md;
    ble_gatts_attr_md_t cccd_md;
    ble_gatts_attr_t    attr_char_value;
    ble_gatts_attr_md_t attr_md;

    
    // Add date and time characteristic
    if (false != p_dts->is_notification_supported)
    {
        memset(&cccd_md, 0, sizeof(cccd_md));
  
        BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm);
  BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.write_perm);
        cccd_md.vloc = BLE_GATTS_VLOC_STACK;
    }
    
    memset(&char_md, 0, sizeof(char_md));
    
    char_md.char_props.read   = 1;
 char_md.char_props.write = 0;
    char_md.char_props.notify = (p_dts->is_notification_supported) ? 1 : 0;
    char_md.p_char_user_desc  = NULL;
    char_md.p_char_pf         = NULL;
    char_md.p_user_desc_md    = NULL;
    char_md.p_cccd_md         = (p_dts->is_notification_supported) ? &cccd_md : NULL;
    char_md.p_sccd_md         = NULL;
    
 
 
    BLE_UUID_BLE_ASSIGN(ble_uuid, BLE_UUID_DATE_TIME_CHAR);
    
    memset(&attr_md, 0, sizeof(attr_md));

 BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm);
    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm); 
    attr_md.vloc       = BLE_GATTS_VLOC_STACK;
    attr_md.rd_auth    = 0;
    attr_md.wr_auth    = 0;
    attr_md.vlen       = 0;
 
    
    memset(&attr_char_value, 0, sizeof(attr_char_value));

    attr_char_value.p_uuid       = &ble_uuid;
    attr_char_value.p_attr_md    = &attr_md;
    attr_char_value.init_len     = sizeof(ble_date_time_t);
    attr_char_value.init_offs    = 0;
    attr_char_value.max_len      = sizeof(ble_date_time_t);
    attr_char_value.p_value      = (uint8_t*)&p_dts_init->init_date_time;
 
 
    err_code = sd_ble_gatts_characteristic_add(p_dts->service_handle, &char_md,
                                               &attr_char_value,
                                               &p_dts->date_time_handles);
    if (err_code != NRF_SUCCESS)
  return err_code;
        
    return NRF_SUCCESS;
}/*current_time_char_add*/


static uint8_t determine_day_of_week_by_gauss(ble_date_time_t *p_date)
{
 int16_t first_2_digits_of_year, last_2_digits_of_year;
 int16_t shift_month, shift_year;
 
 int16_t temp;
 
 int16_t day_of_week;
 
 shift_year = p_date->year;
 if(1== p_date->month || 2 == p_date->month)
  shift_year -= 1;
 
 first_2_digits_of_year = shift_year/100;
 last_2_digits_of_year = shift_year%100;
 
 shift_month = (p_date->month + 10)%12;
 if(0 == shift_month)
  shift_month = 12;
 
 temp = 2.6*shift_month - 0.2;
 
 day_of_week = (p_date->day + temp + last_2_digits_of_year 
  + (last_2_digits_of_year/4) + (first_2_digits_of_year/4) - 2*first_2_digits_of_year);
 day_of_week = day_of_week % 7;
 
 if(0 == day_of_week)
  day_of_week = 7;
 
 return (uint8_t)(day_of_week);
 
}/*determine_day_of_week_by_gauss*/


static uint32_t week_of_day_char_add(ble_dts_t *p_dts, 
 const ble_dts_init_t *p_time_init)
{
 uint32_t            err_code;
 ble_uuid_t          ble_uuid;
    ble_gatts_char_md_t char_md;
//    ble_gatts_attr_md_t cccd_md;
    ble_gatts_attr_t    attr_char_value;
    ble_gatts_attr_md_t attr_md;

 
 BLE_UUID_BLE_ASSIGN(ble_uuid, BLE_UUID_DAY_OF_WEEK_CHAR);
 
 memset(&char_md, 0, sizeof(char_md));
    
    char_md.char_props.read   = 1;
    char_md.char_props.notify = 0;
    char_md.p_char_user_desc  = NULL;
    char_md.p_char_pf         = NULL;
    char_md.p_user_desc_md    = NULL;
    char_md.p_cccd_md         = NULL;
    char_md.p_sccd_md         = NULL;
 
 
    memset(&attr_md, 0, sizeof(attr_md));

 BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm);
    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm);
    attr_md.vloc       = BLE_GATTS_VLOC_STACK;
    attr_md.rd_auth    = 0;
    attr_md.wr_auth    = 0;
    attr_md.vlen       = 0;
 
    
    memset(&attr_char_value, 0, sizeof(attr_char_value));

 uint8_t day_of_week;
 
 day_of_week = determine_day_of_week_by_gauss(&p_dts->current_date_time);
 
    attr_char_value.p_uuid       = &ble_uuid;
    attr_char_value.p_attr_md    = &attr_md;
    attr_char_value.init_len     = sizeof(uint8_t);
    attr_char_value.init_offs    = 0;
    attr_char_value.max_len      = sizeof(uint8_t);
    attr_char_value.p_value      = (uint8_t*)&day_of_week;
 
 
    err_code = sd_ble_gatts_characteristic_add(p_dts->service_handle, &char_md,
                                               &attr_char_value,
                                               &p_dts->day_of_week_handles);
    if (err_code != NRF_SUCCESS)
  return err_code;
 
 return NRF_SUCCESS;
}/*week_of_day_char_add*/


uint32_t ble_dts_init(ble_dts_t *p_dts, const ble_dts_init_t *p_dts_init)
{
    uint32_t   err_code;
    ble_uuid_t ble_uuid;

    // Initialize service structure
    p_dts->evt_handler                = p_dts_init->evt_handler;
    p_dts->conn_handle                = BLE_CONN_HANDLE_INVALID;
    p_dts->is_notification_supported = p_dts_init->is_notification_supported;
    p_dts->current_date_time    = p_dts_init->init_date_time;
 
    // Add service
    BLE_UUID_BLE_ASSIGN(ble_uuid, BLE_UUID_CURRENT_TIME_SERVICE);

    err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, 
  &ble_uuid, &p_dts->service_handle);
    if (err_code != NRF_SUCCESS)
        return err_code;
    
    
    err_code = date_and_time_char_add(p_dts, p_dts_init);
 if(err_code != NRF_SUCCESS)
  return err_code;
 
 err_code = week_of_day_char_add(p_dts, p_dts_init);
 if(err_code != NRF_SUCCESS)
  return err_code;
 
 return NRF_SUCCESS;
}/*ble_time_init*/


uint32_t ble_dts_update(ble_dts_t *p_dts, ble_date_time_t *p_current_date_time)
{
 uint32_t err_code;
 err_code = NRF_SUCCESS;
 ble_gatts_value_t gatts_value;
 
 if (0 == memcmp(&p_dts->current_date_time, p_current_date_time, sizeof(ble_date_time_t)))
  return NRF_SUCCESS;
 
 p_dts->current_date_time = *p_current_date_time;
 
 
 gatts_value.len     = sizeof(ble_date_time_t);
 gatts_value.offset  = 0;
 gatts_value.p_value = (uint8_t*)p_current_date_time;

  
 err_code = sd_ble_gatts_value_set(p_dts->conn_handle, 
  p_dts->date_time_handles.value_handle, &gatts_value);
 
 if (err_code != NRF_SUCCESS)
  return err_code;
 
 // Send value if connected and notifying
 if ((p_dts->conn_handle != BLE_CONN_HANDLE_INVALID) 
  && p_dts->is_notification_supported)
 {
  ble_gatts_hvx_params_t hvx_params;
  uint16_t len;
  
  memset(&hvx_params, 0, sizeof(hvx_params));
  len = sizeof(ble_date_time_t);
  
  hvx_params.handle   = p_dts->date_time_handles.value_handle;
  hvx_params.type     = BLE_GATT_HVX_NOTIFICATION;
  hvx_params.offset   = 0;
  hvx_params.p_len    = &len;
  hvx_params.p_data   = (uint8_t*)p_current_date_time;
  
  err_code = sd_ble_gatts_hvx(p_dts->conn_handle, &hvx_params);
 }
 else
 {
  err_code = NRF_ERROR_INVALID_STATE;
 }
 
 

 uint8_t day_of_week;
 
 day_of_week = determine_day_of_week_by_gauss(p_current_date_time);
 
 gatts_value.len     = sizeof(uint8_t);
 gatts_value.offset  = 0;
 gatts_value.p_value = (uint8_t*)&day_of_week;
 
 err_code = sd_ble_gatts_value_set(p_dts->conn_handle, 
  p_dts->day_of_week_handles.value_handle, &gatts_value);
 
 if (err_code != NRF_SUCCESS)
  return err_code; 
 
 return NRF_SUCCESS;
}/*ble_data_time_update*/



To use the library, as add others BLE service, you should add the code in function services_init of main.c :


#include "ble_dts.h"
static ble_dts_t  m_dts;
static void services_init(void)
{
//:
//:
ble_dts_init_t  bts_init_obj; 
 memset(&bts_init_obj, 0, sizeof(ble_dts_init_t)); 
 
 ble_date_time_t init_date_time;
 init_date_time.year = 2017;
 init_date_time.month = 1;
 init_date_time.day = 22;
 init_date_time.hours = 2;
 init_date_time.minutes = 3;
 init_date_time.seconds = 40;
 
 
 bts_init_obj.evt_handler          = NULL;
    bts_init_obj.is_notification_supported = true; 
 bts_init_obj.init_date_time = init_date_time;
    err_code = ble_dts_init(&m_dts, &bts_init_obj);
 APP_ERROR_CHECK(err_code);
//:
}



And add ble on event  function in fuction ble_evt_dispatch of main.c:


static void ble_evt_dispatch(ble_evt_t * p_ble_evt)
{
//:
    ble_dts_on_ble_evt(&m_dts, p_ble_evt);
//:
}


While the time value needs to be update, call the function ble_dts_update. the most typical application is, create a app timer to update date time value periodically:


/* app periodic timer updates date time value*/
static void date_time_timeout_handler(void * p_context)
{
 UNUSED_PARAMETER(p_context);

 uint32_t err_code;
 
 ble_date_time_t new_date_time;
 memcpy(&new_date_time, &m_dts.current_date_time, sizeof(ble_date_time_t));
 
 /*
 TODO :
   implement the new_date_time value updating ..
 */
 err_code = ble_dts_update(&m_dts, &new_date_time);
 
 APP_ERROR_CHECK(err_code); 
 
}/*date_time_timeout_handler*/


How to read the sevice simplely :

  You could use the app, nRF Connect for this goal:




Connect to the nRF device, in here, it is DHT11_temp&Humi


It is the service and characteristics, you could just press the buttons.




The date time and day of week have been read and displayed successfully. It prove the library works.

2017年1月4日 星期三

nRF51/52 : Pstorage (Flash , Non-volatile Memory) Operation


    It is very requisite for internet of thing (IoT) application to have a non-volatile memory, it could keep the status when the battery runs out.
   In nRF51/52 series, the flash is called as "Persistent storage", the APIs of this kind are begin from pstorage.

   There are examples and guild for how to exploit the pstorag, but I found those are not concise enough. So in here, I would demonstrate how to call the pstorage api in practice case.

  Continue from my previous post, I want to add code of pstorage in that.

  Add Below code about line 603 :


static void power_manage(void)
{
    uint32_t err_code = sd_app_evt_wait();
    APP_ERROR_CHECK(err_code);
}

#ifndef FALSE
 #define FALSE       (0)
 #define TRUE       (1)
#endif

#define UART_TX_BUF_SIZE      (256) 
#define UART_RX_BUF_SIZE      (256)  
#define APP_BLOCK_SIZE      (32)

pstorage_handle_t m_app_storage_base_handle;
static uint8_t m_is_need_to_wait_pstorage_operation = FALSE;
static pstorage_block_t m_pstorage_operation_block_id = 0;

unsigned char m_pstorage_buffer[APP_BLOCK_SIZE];

void app_storage_save_data(void);
void app_storage_print_stored_data(void);
 
/**
 * @brief UART events handler.
 */
void uart_events_handler(app_uart_evt_t * p_event)
{

  switch (p_event->evt_type)
  {
  case APP_UART_DATA_READY:
  {
  unsigned char received_char;
  UNUSED_VARIABLE(app_uart_get(&received_char));
  
  if('~' == received_char)
  {
   app_storage_print_stored_data();
   NVIC_SystemReset();
  }
  
  { 
   sprintf((char*)&m_pstorage_buffer[0], 
    " received char = %c\r\n", received_char);
   
   pstorage_handle_t block_handle;
   pstorage_block_identifier_get(&m_app_storage_base_handle, 
    0, &block_handle);
  
   /*
   m_pstorage_operation_block_id = block_handle.block_id;
   m_is_need_to_wait_pstorage_operation = TRUE;
   */
   
   uint32_t  err_code;
   err_code = pstorage_update(&block_handle, &m_pstorage_buffer[0],
    APP_BLOCK_SIZE, 0);
   APP_ERROR_CHECK(err_code); 
   printf("storing str :\"%s\" into flash\r\n", 
    (char*)&m_pstorage_buffer[0]);
  }/*store data to flash block*/
  }
      break;


  case APP_UART_COMMUNICATION_ERROR: 
    APP_ERROR_HANDLER(p_event->data.error_communication);
    break;

  case APP_UART_FIFO_ERROR:          
    APP_ERROR_HANDLER(p_event->data.error_code);
    break;

  case APP_UART_TX_EMPTY:
    break;

  case APP_UART_DATA:
    break;

  default: 
   break;
  }
}

/**
 * @brief UART initialization.
 */
void uart_config(void)
{
    uint32_t                     err_code;
  const app_uart_comm_params_t comm_params =
    {
        RX_PIN_NUMBER,
        TX_PIN_NUMBER,
        RTS_PIN_NUMBER,
        CTS_PIN_NUMBER,
        APP_UART_FLOW_CONTROL_DISABLED,
        false,
        UART_BAUDRATE_BAUDRATE_Baud38400
    };

  
  
    APP_UART_FIFO_INIT(&comm_params,
                       UART_RX_BUF_SIZE,
                       UART_TX_BUF_SIZE,
                       uart_events_handler,
                       APP_IRQ_PRIORITY_LOW,
                       err_code);
  
    APP_ERROR_CHECK(err_code);
}/*uart_config*/

 
// Event Notification Handler.
static void app_storage_callback_handler(pstorage_handle_t  * handle,
                               uint8_t              op_code,
                               uint32_t             result,
                               uint8_t            * p_data,
                               uint32_t             data_len)
{
 if(handle->block_id == m_pstorage_operation_block_id)
   m_is_need_to_wait_pstorage_operation = FALSE;
 
 switch(op_code)
 {
  
 case PSTORAGE_STORE_OP_CODE:
 if(NRF_SUCCESS != result)
  printf("pstorage STORE ERROR callback received \r\n");
 break; 

 case PSTORAGE_LOAD_OP_CODE:
 if(NRF_SUCCESS != result)
  printf("pstorage LOAD ERROR callback received \r\n");
 break; 
  
 case PSTORAGE_CLEAR_OP_CODE:
 if(NRF_SUCCESS != result) 
  printf("pstorage CLEAR ERROR callback received \r\n");
 break;  

 case PSTORAGE_UPDATE_OP_CODE:
 if(NRF_SUCCESS != result) 
   printf("pstorage UPDATE ERROR callback received \r\n");
 break; 
 
 default:
  printf("pstorage ERROR callback received \r\n");
 break;
 }
}/*app_storage_callback_handler*/


/**
* @brief pstorage initializing and block register
*/
void app_storage_init()
{
 uint32_t err_code;
 
 err_code = pstorage_init(); 
 APP_ERROR_CHECK(err_code);
 
 pstorage_module_param_t pstorage_param;
 
 pstorage_param.block_size = APP_BLOCK_SIZE;
 pstorage_param.block_count = 1;
 pstorage_param.cb = app_storage_callback_handler;
 
 err_code = pstorage_register(&pstorage_param, &m_app_storage_base_handle);
 APP_ERROR_CHECK(err_code);
#if(0) 
 {
  pstorage_handle_t block_handle;
  pstorage_block_identifier_get(&m_app_storage_base_handle, 
   0, &block_handle);
  
  m_pstorage_operation_block_id = block_handle.block_id;
  m_is_need_to_wait_pstorage_operation = TRUE;
  
  err_code = pstorage_clear(&block_handle, APP_BLOCK_SIZE); 
  APP_ERROR_CHECK(err_code);
  
  while(FALSE != m_is_need_to_wait_pstorage_operation) 
   power_manage();
 }/*clear data block*/
#endif
 
}/*pstorage_init_store_and_update*/


void app_storage_save_data(void)
{
 uint32_t err_code;
 
#if(0) 
 {
  pstorage_handle_t block_handle;
  pstorage_block_identifier_get(&m_app_storage_base_handle, 
   0, &block_handle);
  
  m_pstorage_operation_block_id = block_handle.block_id;
  m_is_need_to_wait_pstorage_operation = TRUE;
  
  err_code = pstorage_clear(&block_handle, APP_BLOCK_SIZE); 
  APP_ERROR_CHECK(err_code);
  
  while(FALSE != m_is_need_to_wait_pstorage_operation) 
   power_manage();
 }/*clear data block*/
#endif 
 {
  pstorage_handle_t block_handle;
  pstorage_block_identifier_get(&m_app_storage_base_handle, 0, &block_handle);
  
  m_pstorage_operation_block_id = block_handle.block_id;
  m_is_need_to_wait_pstorage_operation = TRUE;
  
  //err_code = pstorage_store(&block_handle, &m_pstorage_buffer[0], 
  // APP_BLOCK_SIZE, 0);
  err_code = pstorage_update(&block_handle, &m_pstorage_buffer[0], 
   APP_BLOCK_SIZE, 0);
  APP_ERROR_CHECK(err_code);
  
  while(FALSE != m_is_need_to_wait_pstorage_operation) 
   power_manage();     
 }/*store code block*/
 
}/*app_storage_save_data*/


void app_storage_print_stored_data(void)
{
 uint32_t err_code;
 unsigned char storedData[APP_BLOCK_SIZE];
 
 pstorage_handle_t block_handle;
 
 err_code = pstorage_block_identifier_get(&m_app_storage_base_handle, 
  0, &block_handle);
 APP_ERROR_CHECK(err_code);
 
 err_code = pstorage_load(&storedData[0], 
  &block_handle, APP_BLOCK_SIZE, 0);
 APP_ERROR_CHECK(err_code);
 
 {
  printf("%s\r\n", &storedData[0]);
 }
}/*app_storage_print_stored_data*/


/**@brief Function for application main entry.
 */
int main(void)
{
    uint32_t err_code;
    bool erase_bonds;
 
 uart_config();
 printf("Start...\r\n");
 
    // Initialize.
    timers_init();
 
    buttons_leds_init(&erase_bonds);
    ble_stack_init();
 
 app_storage_init();
 app_storage_print_stored_data();
 sprintf((char*)&m_pstorage_buffer[0], "new boot string\n\r");
 
 {
  pstorage_handle_t block_handle;
  pstorage_block_identifier_get(&m_app_storage_base_handle, 
   0, &block_handle);
  
  m_pstorage_operation_block_id = block_handle.block_id;
  m_is_need_to_wait_pstorage_operation = TRUE;
  
  //err_code = pstorage_store(&block_handle, &m_pstorage_buffer[0], 
  // APP_BLOCK_SIZE, 0);
  err_code = pstorage_update(&block_handle, 
   &m_pstorage_buffer[0], APP_BLOCK_SIZE, 0);
  APP_ERROR_CHECK(err_code);
  
  while(FALSE != m_is_need_to_wait_pstorage_operation) 
   power_manage();     
 }/*store code block*/
 app_storage_print_stored_data();
 
    device_manager_init(erase_bonds);
    gap_params_init();  



You could use serial tool (like sscom..etc) to write data (a character) to pstorage.
 for example, when I send 'a' via uart, the flash would be written the string" received char = a".

When you send '~' the nRF51/52 would reboot,  you could find that the string in pstorage would be kept.

  


























Here is the knacks for using pstorage:

零. The block size should be align of 4, of course.

一. The initialization function pstorage_init (in my case, within the function pstorage_init), should be called after function ble_stack_init. If you call it before  ble_stack_init, the function pstorage_init might lead the system reboot ceaselessly. It is not necessary to call pstorage_init after  ble_stack_init Immediately, you could call it after the advertise has been started.

二. Before calling the function pstorage_store, it is necessary to call function pstorage_clear, Or the storing action should not be done : the flash contain would not be update. The alternative way is to call function pstorage_update, which is the combination of pstorage_clear and pstorage_store.

三. When the functions pstorage_store/pstorage_update/pstorage_clear, it costs time to complete the action after the functions have returned. In the main "thread", I adopt a global flag (m_is_need_to_wait_pstorage_operation) , while loop and function power_manage to wait the pstorage has been updated indeed, that is like:


{
  pstorage_handle_t block_handle;
  pstorage_block_identifier_get(&m_app_storage_base_handle, 0, &block_handle);
  
  m_pstorage_operation_block_id = block_handle.block_id;
  m_is_need_to_wait_pstorage_operation = TRUE;
  
  //err_code = pstorage_store(&block_handle, &m_pstorage_buffer[0], 
  // APP_BLOCK_SIZE, 0);
  err_code = pstorage_update(&block_handle, &m_pstorage_buffer[0], 
   APP_BLOCK_SIZE, 0);
  APP_ERROR_CHECK(err_code);
  
  while(FALSE != m_is_need_to_wait_pstorage_operation) 
   power_manage();     
 }/*store code block*/

In the pstorage handler callback function :

// Event Notification Handler.
static void app_storage_callback_handler(pstorage_handle_t  * handle,
                               uint8_t              op_code,
                               uint32_t             result,
                               uint8_t            * p_data,
                               uint32_t             data_len)
{
 if(handle->block_id == m_pstorage_operation_block_id)
   m_is_need_to_wait_pstorage_operation = FALSE;
 
 switch(op_code)
 {
  
 case PSTORAGE_STORE_OP_CODE:


that could ensure the data be stored into pstorage.

But inside the interrupt callback, calling power_manage would lead system hanging. Therefore, in the uart event handler callback, i do not call the power_manage function:


/**
 * @brief UART events handler.
 */
void uart_events_handler(app_uart_evt_t * p_event)
{

  switch (p_event->evt_type)
  {
  case APP_UART_DATA_READY:
  {
  unsigned char received_char;
  UNUSED_VARIABLE(app_uart_get(&received_char));
  
  if('~' == received_char)
  {
   app_storage_print_stored_data();
   NVIC_SystemReset();
  }
  
  { 
   sprintf((char*)&m_pstorage_buffer[0], 
    " received char = %c\r\n", received_char);
   
   pstorage_handle_t block_handle;
   pstorage_block_identifier_get(&m_app_storage_base_handle, 
    0, &block_handle);
  
   /*
   m_pstorage_operation_block_id = block_handle.block_id;
   m_is_need_to_wait_pstorage_operation = TRUE;
   */
   
   uint32_t  err_code;
   err_code = pstorage_update(&block_handle, &m_pstorage_buffer[0],
    APP_BLOCK_SIZE, 0);
   APP_ERROR_CHECK(err_code); 
   printf("storing str :\"%s\" into flash\r\n", 
    (char*)&m_pstorage_buffer[0]);
  }/*store data to flash block*/
  }
      break;


四. Do not update your pstorage too frequently, It would cost much electric power, and the written times is limited : it be written 10000 times before broken typically.