Friday 19 October 2018

A MQTT demo using ESP-01 (ESP8266)

I created a very simple demo based on ESP-01 module and MQTT protocol.

A video of the demo is also available here:



MQTT is a popular protocol for IoT, the client implementation is small in footprint and there are all kinds of example codes available on the internet, from C to Java or Java Script.

This demo is based on C code, which is provided as an example in the esp-open-rtos SDK. The circuit is very simple, it has 2 LEDs connected to the ESP-01 and a mobile phone is used to control these LEDs.

There are several open MQTT servers providing free access to public on the internet, such as test.mosquitto.org, and a private server is also very easy to setup, if you use Ubuntu, you can simply use command
sudo apt-get install mosquitto
to install the open source MQTT server on your computer. I'm using a VPS as the server, to verify that this concept will actually work in the cloud.

The red and blue LEDs are belonging to different topics, and are subscribed to the topic. The mobile phone App has two buttons, one for each LED, when you press the button, the App will publish a new message to the corresponding topic. The published message will toggle between '0' and '1' every time the button is pressed. Then the message will be delivered to the subscribed client (ESP8266), the microcontroller will decide whether to turn the LED on or off according to the message content.

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.