Tuesday 9 October 2018

nRF52832 BLE demo written in C++

Nordic's nRF5xxxxx series chips are very popular BLE chipsets, according to my own experience, comparing to the CSR solution, nRF is more flexible and powerful. For example, the nRF52832, which is used in this demo, is a full functioning Cortex M4 MCU with FPU, if you don't use their SoftDevice, it will just be a Cortex M4 MCU like any other one on the market plus a radio interface, while oh the other hand, the CSR1010 which I had a demo project with it here before is a more dedicated BLE chip with all the BLE stack inside and the user program has to stay in the external serial EEPROM. The user has very limited access to the hardwares inside, the only way is through their APIs.

Despite their chip being a really wonderful product, the nRF SDK is quiet disappointing. The only want you to use their examples and adopt it to your needs, but never tell you how you should use the SoftDevice. I felt very frustrated when I read the code in the examples, I've never read such bad structured C code ever. So many unnecessary macros and function calls, you often have to jump 3 or 4 times among the defined macros to find out which is the final SoftDevice API it calls.

Anyway, I finally figured out how to isolate the SDK code with my own, so then I can write my own code in C++, and don't use their provided peripheral libraries. I really don't like such libraries, you still have to learn how to use it and get yourself familiar with tons of API functions, won't save you any time. On the other side, controlling the hardware via the registers is much more strait forward and simpler.

The demo does a very simple job, it will generate a random value every a few seconds, and if the notification is turned on by the mobile device, the value is pushed to the mobile whenever it gets updated. Meanwhile, the mobile can send a message to the nRF52832, the message will be displayed on the 1602 LCD module when received. It is not necessarily a text message, but I use ascii text here to make it displayable.

Let's see how does it work now:

Here is the source code of the main.cpp:

#include "hd44780.h"
#include "hd44780if_nrf_4bit.h"
#include <nrf52.h> // definitions of nrf52 registers
#include <stdint.h> // definitions of common types such as uint32
#include <stdlib.h>
#include <ble.h>
#include <ble_advertising.h>
#include <ble_gatts.h>
#include <ble_gap.h>
#include <app_timer.h>
#include "start.h"

class Gpio
{
public:
Gpio()
{
NRF_P0->DIR = 0x001fc000; // set p14,15,16,17,18,19,20 output
NRF_P0->PIN_CNF[17] = 0; // connect p17 input
NRF_P0->PIN_CNF[18] = 0;
NRF_P0->PIN_CNF[19] = 0;
NRF_P0->PIN_CNF[20] = 0;
}
};

void update_value(void *p_attr_handle);

ble_gatts_hvx_params_t hvx_param;
uint16_t conn_handle;
APP_TIMER_DEF(user_timer);

Gpio gpio;

Hd44780if_nrf_4bit lcd_nrf_4bit;
Hd44780 lcd(lcd_nrf_4bit);




int main()
{
log_init();
app_timer_init();
app_timer_create(&user_timer, APP_TIMER_MODE_REPEATED, update_value);
    power_management_init();
    ble_stack_init();
    gap_params_init();
    gatt_init();
    conn_params_init();
    services_init();
    advertising_init();
    peer_manager_init();
    advertising_start();

// bt_start();
while (1)
{
idle_state_handle();
}
return 0;
}

extern "C" uint32_t bt_ext_handler(ble_evt_t const * p_ble_evt, void * p_context)
{
    uint32_t err_code;
    switch (p_ble_evt->header.evt_id)
    {
case BLE_GATTS_EVT_WRITE:
if (p_ble_evt->evt.gatts_evt.params.write.handle == svc_handles.value_handle)
{
lcd.print_string(40, (char*)p_ble_evt->evt.gatts_evt.params.write.data, p_ble_evt->evt.gatts_evt.params.write.len);
lcd.print_string("                ");
}
break;
case BLE_GAP_EVT_CONNECTED:
lcd.print_string(0, "Connected    ");
lcd.print_string(40, "                ");
conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
app_timer_start(user_timer,150000, &svc_handles2.value_handle);
break;
case BLE_GAP_EVT_DISCONNECTED:
conn_handle = BLE_CONN_HANDLE_INVALID;
app_timer_stop(user_timer);
break;
        case BLE_GATTC_EVT_TIMEOUT:
            // Disconnect on GATT Client timeout event.
            err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gattc_evt.conn_handle,
                                             BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
            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);
            break;
default:
break;
}
return 0;
}

extern "C" void adv_ext_handler(ble_adv_evt_t ble_adv_evt)
{
    uint32_t err_code;

    switch (ble_adv_evt)
    {
        case BLE_ADV_EVT_FAST:
lcd.print_string(0, "Fast Advertising");
lcd.print_string(40, "Interval 100ms  ");
            break;
case BLE_ADV_EVT_SLOW:
lcd.print_string(0, "Slow Advertising");
lcd.print_string(40, "Interval 500ms  ");
break;
        case BLE_ADV_EVT_IDLE:
lcd.print_string(0, "Idle.           ");
lcd.print_string(40, "                ");
//            sleep_mode_enter();
            break;
        default:
            break;
    }
}

void update_value(void *p_attr_handle)
{
uint16_t len = sizeof(sensor_value);

sensor_value = rand()%0xff;
hvx_param.type = BLE_GATT_HVX_NOTIFICATION;
hvx_param.handle = *(uint16_t*)p_attr_handle;
hvx_param.p_data = &sensor_value;
hvx_param.p_len = &len;
lcd.print_string(12, "0x");
lcd.print_hex(sensor_value);
if (conn_handle != BLE_CONN_HANDLE_INVALID)
{
sd_ble_gatts_hvx(conn_handle, &hvx_param);
}
}

The "Gpio" class initialises the I/O ports, and the important thing: to introduce the bt_ext_handler() and adv_ext_handler() functions to the original main.c (which is renamed as start.c) by the ext_handler.h file, and adopt these 2 event handlers to the BLE stack:

1.  in the ble_stack_init(), add this line to register BLE event handler:
NRF_SDH_BLE_OBSERVER(m_ext_observer, APP_BLE_OBSERVER_PRIO, bt_ext_handler, NULL);

2.  change the line to assign advertising event handler in advertising_init():
init.evt_handler = adv_ext_handler;

The reason to do this is these are the most likely two parts to have interactions with the user code.

No comments:

Post a Comment