Help us understand the problem. What is going on with this article?

土日で出来るBLEリモコン

More than 1 year has passed since last update.

はじめに

BLEとはBluetooth Low Energyの略で、低消費電力の無線通信です。
低消費電力で機器に内蔵しやすいこと、最近のスマホ(iOS6以降、Android4.4以降)がBLEに対応していることなどの理由から、機器データをスマホなどの外部へ送信するのに広く用いられています。
BLEは流行りのIoT分野で重宝されていることから、マイコンなどに縁のない方の中にも興味を持たれている方々が増えていると感じています。

今回は、そんなBLEを用いた機器に興味があるけれど、仕事が忙しく、電子工作もできないので手を出さないでいるという人が、仕事の合間のわずかな空き時間だけを利用して、とりあえず動作するBLE機器を簡単に作れるように案内したいと思います。

作る物

以下の物を作成します。

作る物.png

BLEには、ペリフェラル役とセントラル役があります。
ペリフェラル側は、自分の存在を周囲に報せるアドバタイジングパケットを送信しています。
セントラル側は、そのアドバタイジングパケットをスキャンして、接続相手を取捨選択します。
ペリフェラルとセントラル間で接続が確立すると、無線通信が開始されます。

BLEの設計思想は、電力の要する作業をセントラル側に任せ、ペリフェラル側はなるべく電力を消費しないようにするというものです。
その為、機器に内蔵するBLEモジュールはペリフェラル役にして、電力を頻繁に給電できるスマホなどをセントラル役にして使用することが多くなります。

今回は、両方ともBLEモジュールで作成します。
『物』を使って、『物』を制御するのが、今回の目標です。
スマホを使って『物』を制御したり、スマホに『物』からのデータを集めるには、スマホアプリを作成する必要があり、私は解説できるほど詳しくないので、解説書の 『iOS×BLE Core Bluetoothプログラミング』などをご参照ください。

ソフト動作仕様

以下にソフト動作の概要図を示します。
通信フロー.png

電源を供給すると、ペリフェラル側はアドバタイジングパケットを発信し、セントラル側はそのスキャンを開始します。
セントラル側は、アドバタイジングパケット内に格納されているDevice Name "SAMPLE_A"を検知して、ペリフェラルとの接続をします。それ以外のDevice Nameを持つアドバタイジングパケットを受信しても接続しません。

BLE接続が確立した後は、無線通信で『Service』と呼ばれる情報をやり取りします。温度計や心拍計のような、よく使うServiceは、あらかじめ中身が定義されている温度計Serviceとか、心拍計Serviceがあります。
今回は、オリジナルのServiceを作ります。

Serviceは、複数のCharacteristicで構成されています。
送信用か、受信用か? セキュリティ設定はどうするか? 何バイトのパケット通信をするのか?
1つ1つのCharacteristicに異なる設定をすることで、目的の通信を実現させます。

今回は、Notify(ペリフェラル側からセントラル側へのデータ送信)を行う1つのCharacteristicを作成します。
他にはWrite(セントラル側からペリフェラル側へのデータ送信)やIndicate(ペリフェラル側からセントラル側へのデータ送信後、セントラル側からペリフェラル側への『応答確認』を返す)がよく使われます。

用意するもの(ハード)

BLEモジュール(2個)

単価 4,730円(税抜)

Nordic社のBLEモジュールnRF51822 rev3を搭載した物を使用します。
このBLEモジュールでは、技適を取得済みの太陽誘電製EYSGCNZWYが使用されています。
スイッチサイエンスで販売されており、Amazonからも購入できます。

Switch Science mbed TY51822r3

ただし、この製品はピンヘッダがハンダ付けされていないので、自分でハンダ付けしないといけません。

あらかじめピンヘッダがハンダ付けされた以下のBLEモジュールが使用できればいいのですが、今回作成するプログラムは、こちらでは動作しません。
Switch Science mbed HRM1017r3

電子部品など

ブレッドボード(2個)、単価 200円(税込)

ブレッドボード・ジャンパーワイヤ、単価 400円(税込)

2.54mmピッチタイプの一列ピンコネクタ(4個)、単価 35円(税込)

LED(1個)、10個 150円(税込)

タクトスイッチ(1個)、単価 10円(税込)

抵抗1kオーム(2個)、1袋 100円(税込)

組み立て

電子部品などをブレッドボードに差し込み、以下の回路図をブレッドボード上に実現させます。
回路図.png

BLEモジュールのピン配置番号はこちらをご参照ください。
Switch Science mbed TY51822r3の端子配置

VDDとGND、P0_19とP0_28の端子に、LEDやスイッチをつなげていきます。配線はジャンパーワイヤか針金などをご使用ください。

例えば、セントラル側はこのようになります。
セントラル側.jpg

開発環境の構築

mbedは、一般的にクラウド上に用意されたオンラインコンパイラを利用して開発します。
ですが、今回は開発環境を自PCに用意します。

Keil 5.0

ARMマイコン開発環境であるKeilを使用します。
無償版は32kバイトまでしかコンパイルできないという制限があります。
(趣味の範囲ならこの制限下でも特に問題はないのですが、セキュリティ設定などを行うpeer managerを使用しようとすると32kには収まらなくなるので、製品版や別の開発環境を使用しないといけません。)

以下のサイトからダウンロードしてください。
MDK-ARM

パッケージインストール

Keilのインストール後は、nRF51822に関するパッケージをインストールします。
Keilの初回起動時、もしくは以下の赤丸部分をクリックするとPack Installerが起動します。

左のDevice欄からNordicの対応する製品を選びます。(今回はnRF51822_XACです。)
すると、右のPack欄に対応するパッケージが表示されるので、必要なものをインストールします。
NordicSemiconducterから始まるパッケージのDrivers、BLE、Examples、Libraries、S130が必要になります。(他のパッケージも必要になるかもしれないので、メモリに余裕があればインストールください。)
それとGenericのARM::CMSIS、Keil::ARM_Compiler、Keil::ARM_Middlewareの最新版もインストールします。
手順2.jpg

SDKとソフトデバイス

NoridicのサイトからSDKとソフトデバイスをダウンロードします。
SDKはサンプルプログラム集、ソフトデバイスはSDKを使用する為に必要なライブラリで、BLEモジュールのROMに書き込む必要があります。

BLEモジュールのバージョンにより、使用できるSDKとソフトデバイスには制限があります。
またSDKのバージョンには、それに対応するソフトデバイスを使用する必要があります。

今回は以下の組み合わせを選択します。
・SDK rev12.3
・Soft Device 130

以下のURLのサイドメニューのnRF51 Series→Compatibility matrix→nRF51 Series IC revisions compatibility with SDK and SoftDevicesを見れば、ソフトデバイスと製品バージョンの対応が確認できます。
nRF51 Series

nRF51822 rev3に対応しているSDKはver6.1.0以降になります。
今回は、SDK rev12.3を使用するので、ソフトデバイスはS130のrev2.01以降を選択します。
それらは以下からダウンロードできます。
nRF5-SDK-v12-zip
S130-SD-v2

古いrevのSDKなどは以下からダウンロード可能になります。
Location: /nRF5_SDK/

ペリフェラル側ファームウェア

SDKのサンプルプログラムを土台にして、今回の仕様に合わせた修正を行います。

ダウンロードSDKフォルダ内の以下の階層にあるプロジェクトファイルを開いて、修正をします。

examples\ble_peripheral\ble_app_template\pca10028\s130\arm5_no_packs\ble_app_template_pca10028_s130.uvprojx

nRf51822用の設定

サンプルプログラムはnRF51422用の設定になっているので、これをnRF51822用の設定に直します。

nRF51422_xxacフォルダアイコンを右クリックして、Options...を選択してください。
1.png

DeviceタグでnRF51822_xxacを選択してください。
2.png

Targetタグで、IROMサイズとIRAMサイズを指定します。
3.png
SD130 rev2.0.1は以下の設定になります。

・IRAM base - 0x20001FE8
・IRAM size(32 kB RAM) - 0x6018

・IROM base - 0x1B000
・IROM size(256 kB Flash) - 0x25000
(参照:nRF51822_xxaa SDK12 S130 v2.0.1 IROM and IRAM settings

メモリ領域の説明や他のソフトデバイスのバージョンでの設定は以下を参照ください。
ROM and RAM Management

C/C++タグのDefineを変更します。
NRF51422 BOARD_PCA10028をNRF51822 BOARD_CUSTOMに変更します。
5.png

4.png

boards.hファイルに各ボード用の定義が用意されています。
BOARD_PCA10028をBOARD_CUSTOMに変更した場合、独自のボード用の設定hファイルを作成する必要があります。

Userタグでビルド時にソフトデバイスのhexファイルを統合する設定にします。
6.png

After Build?RebuildのRUN#1のチェックボックスをチェックを入れ、以下のコマンドを設定します。

mergehex -m !H  (ファイルの階層)\s130_nrf51_2.0.1_softdevice.hex  -o $H@H.hex

これは、nRFgoStudioなどのツールを使用してソフトデバイスを別途書き込む場合には、不必要な設定です。

アドバタイジングパケットの設定

設定可能なアドバタイジングパケット要素には以下があります。
・サービスUUID
・Local Name(Device Name)
・送信電力
・Manifacturer Specific Data
・発信周期
・発信タイムアウトの有無
etc...

今回は、Local Nameのみを変更します。

main.c
#define DEVICE_NAME                     "Nordic_Template" 

main.c
#define DEVICE_NAME                     "SAMPLE_A" 

とします。

サービスの作成

独自のサービスとしてTEST Serviceを作成します。

今回は、サンプルプロジェクトに、以下の2つのファイルを追加します。

test_service.h
#ifndef BLE_ACTIVE_H__
#define BLE_ACTIVE_H__

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

#ifdef __cplusplus
extern "C" {
#endif

//サービスUUID。ベースとなるTEST_UUID_BASEの0x00, 0x00の4バイトに個別のサービスUUID番号が入る。
//例えばNotifyのUUIDは、112233445566778899AABBCCAAAADDEE になる。
#define TEST_UUID_BASE {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0x00, 0x00, 0xDD, 0xEE}
#define TEST_UUID_SERVICE             0xFFFF
#define TEST_UUID_NOTIFY_CHAR         0xAAAA
#define TEST_UUID_WRITE_CHAR          0xBBBB
#define TEST_UUID_INDICATE_CHAR       0xCCCC

//最大送信バイト数
#define MAX_NOTIFY_NUM  20
#define MAX_WRITE_NUM   20

typedef enum
{
    BLE_TEST_EVT_NOTIFICATION_ENABLED,  
    BLE_TEST_EVT_NOTIFICATION_DISABLED  
} ble_test_evt_type_t;

typedef struct
{
    ble_test_evt_type_t evt_type; 
} ble_test_evt_t;

// Forward declaration of the ble_test_t type.
typedef struct ble_test_s ble_test_t;

typedef void (*ble_test_write_handler_t) (ble_test_t * p_test, uint8_t *receive_buff, uint8_t length);

typedef struct
{
  ble_test_write_handler_t         test_write_handler; 
} ble_test_init_t;


typedef struct ble_test_s
{
    uint16_t                    service_handle;      
    ble_gatts_char_handles_t    test_write_handles;    
    ble_gatts_char_handles_t    test_notify_handles; 
    ble_gatts_char_handles_t    test_indicate_handles; 
    uint8_t                     uuid_type;           
    uint16_t                    conn_handle;         
    ble_test_write_handler_t    test_write_handler;   
}ble_test_t;


uint32_t ble_test_init(ble_test_t * p_test, const ble_test_init_t * p_test_init);

void ble_test_on_ble_evt(ble_test_t * p_test, ble_evt_t * p_ble_evt);

uint32_t ble_test_notify(ble_test_t * p_test, uint8_t test_state);

uint32_t notify_cmd(ble_test_t * p_test, uint8_t cmd, uint8_t *buff, uint16_t data_len);

#ifdef __cplusplus
}
#endif

#endif // BLE_test_H__

/** @} */
test_service.c
#include "sdk_common.h"
#include <string.h>
#include "ble_srv_common.h"
#include "test_service.h"


static void on_connect(ble_test_t * p_test, ble_evt_t * p_ble_evt)
{
    p_test->conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
}

static void on_disconnect(ble_test_t * p_test, ble_evt_t * p_ble_evt)
{
    UNUSED_PARAMETER(p_ble_evt);
    p_test->conn_handle = BLE_CONN_HANDLE_INVALID;
}

//セントラル側から送信されるwriteを受信時に実行される処理
static void on_write(ble_test_t * p_test, 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_test->test_write_handles.value_handle) &&
        //(p_evt_write->len == 1) &&    //複数bytesのWriteを受信するときは不要
        (p_test->test_write_handler != NULL))
    {
        p_test->test_write_handler(p_test, &p_evt_write->data[0], p_evt_write->len);
    }
}


void ble_test_on_ble_evt(ble_test_t * p_test, ble_evt_t * p_ble_evt)
{
    switch (p_ble_evt->header.evt_id)
    {
        //BLE接続時に実行される処理
        case BLE_GAP_EVT_CONNECTED:
            on_connect(p_test, p_ble_evt);
            break;

        //BLE切断時に実行される処理
        case BLE_GAP_EVT_DISCONNECTED:
            on_disconnect(p_test, p_ble_evt);
            break;

        //セントラル側から送信されるwriteを受信時に実行される処理
        case BLE_GATTS_EVT_WRITE:
            on_write(p_test, p_ble_evt);
            break;

        default:
            // No implementation needed.
            break;
    }
}

//write Characteristicを追加する初期化処理
static uint32_t test_write_char_add(ble_test_t * p_test, const ble_test_init_t * p_test_init)
{
    ble_gatts_char_md_t char_md;
    ble_gatts_attr_t    attr_char_value;
    ble_uuid_t          ble_uuid;
    ble_gatts_attr_md_t attr_md;

    // Add TEST Write characteristic
    memset(&char_md, 0, sizeof(char_md));

    char_md.char_props.read   = 1;
    char_md.char_props.write  = 1;
    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;

    ble_uuid.type = p_test->uuid_type;
    ble_uuid.uuid = TEST_UUID_WRITE_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       = 1;

    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(uint8_t);
    attr_char_value.init_offs = 0;
    attr_char_value.max_len   = MAX_WRITE_NUM;//sizeof(uint8_t);
    attr_char_value.p_value   = NULL;

    return sd_ble_gatts_characteristic_add(p_test->service_handle, 
                                               &char_md,
                                               &attr_char_value,
                                               &p_test->test_write_handles);
}

//notify Characteristicを追加する初期化処理
static uint32_t test_notify_char_add(ble_test_t * p_test, const ble_test_init_t * p_test_init)
{
    ble_gatts_char_md_t char_md;
    ble_gatts_attr_md_t cccd_md;
    ble_gatts_attr_t    attr_char_value;
    ble_uuid_t          ble_uuid;
    ble_gatts_attr_md_t attr_md;

    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.notify = 1;
    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         =  &cccd_md;
    char_md.p_sccd_md         = NULL;

    ble_uuid.type = p_test->uuid_type;
    ble_uuid.uuid = TEST_UUID_NOTIFY_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_NO_ACCESS(&attr_md.write_perm);
    attr_md.vloc       = BLE_GATTS_VLOC_STACK;
    attr_md.rd_auth    = 0;
    attr_md.wr_auth    = 0;
    attr_md.vlen       = 1;

    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(uint8_t);
    attr_char_value.init_offs = 0;
    attr_char_value.max_len   = MAX_NOTIFY_NUM;//sizeof(uint8_t);
    attr_char_value.p_value   = NULL;

    return sd_ble_gatts_characteristic_add(p_test->service_handle,                       
    &char_md,
    &attr_char_value,
    &p_test->test_notify_handles);
}

//indicate Characteristicを追加する初期化処理
static uint32_t test_indicate_char_add(ble_test_t * p_test, const ble_test_init_t * p_test_init)
{
    ble_gatts_char_md_t char_md;
    ble_gatts_attr_md_t cccd_md;
    ble_gatts_attr_t    attr_char_value;
    ble_uuid_t          ble_uuid;
    ble_gatts_attr_md_t attr_md;

    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.indicate = 1;
    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         =  &cccd_md;
    char_md.p_sccd_md         = NULL;

    ble_uuid.type = p_test->uuid_type;
    ble_uuid.uuid = TEST_UUID_INDICATE_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_NO_ACCESS(&attr_md.write_perm);
    attr_md.vloc       = BLE_GATTS_VLOC_STACK;
    attr_md.rd_auth    = 0;
    attr_md.wr_auth    = 0;
    attr_md.vlen       = 1;

    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(uint8_t);
    attr_char_value.init_offs = 0;
    attr_char_value.max_len   = MAX_NOTIFY_NUM;//sizeof(uint8_t);
    attr_char_value.p_value   = NULL;

    return sd_ble_gatts_characteristic_add(p_test->service_handle, 
    &char_md,
    &attr_char_value,
    &p_test->test_indicate_handles);
}

//BLEサービス初期化処理
uint32_t ble_test_init(ble_test_t * p_test, const ble_test_init_t * p_test_init)
{
    uint32_t   err_code;
    ble_uuid_t ble_uuid;

    // Initialize service structure
    p_test->conn_handle       = BLE_CONN_HANDLE_INVALID;
    p_test->test_write_handler = p_test_init->test_write_handler;

    // Add service
    ble_uuid128_t base_uuid = {TEST_UUID_BASE};
    err_code = sd_ble_uuid_vs_add(&base_uuid, &p_test->uuid_type);
    VERIFY_SUCCESS(err_code);

    ble_uuid.type = p_test->uuid_type;
    ble_uuid.uuid = TEST_UUID_SERVICE;

    err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &ble_uuid, &p_test->service_handle);
    VERIFY_SUCCESS(err_code);

    // Add test write characteristic
    err_code = test_write_char_add(p_test, p_test_init);
    VERIFY_SUCCESS(err_code);

    // Add test notify characteristic
    err_code = test_notify_char_add(p_test, p_test_init);
    VERIFY_SUCCESS(err_code);

    // Add test indicate characteristic
    //err_code = test_indicate_char_add(p_test, p_test_init);
    //VERIFY_SUCCESS(err_code);

    return NRF_SUCCESS;
}

//Notifyデータ送信関数
uint32_t ble_test_notify(ble_test_t * p_test, uint8_t test_state)
{
    ble_gatts_hvx_params_t params;
    uint16_t len = sizeof(test_state);

    memset(&params, 0, sizeof(params));
    params.type = BLE_GATT_HVX_NOTIFICATION;
    params.handle = p_test->test_notify_handles.value_handle;
    params.p_data = &test_state;
    params.p_len = &len;

    return sd_ble_gatts_hvx(p_test->conn_handle, &params);
}


//Notifyコマンド送信関数
uint32_t notify_cmd(ble_test_t * p_test, uint8_t cmd, uint8_t *buff, uint16_t data_len)
{
    uint8_t                encoded_buff[data_len+2];
    uint8_t                i;
    ble_gatts_hvx_params_t params;

    uint16_t len = (data_len+2); //sizeof(buff);

    encoded_buff[0] =  cmd;

    encoded_buff[data_len+1] = 0;
    encoded_buff[data_len+1] ^= encoded_buff[0];

    for(i=1; i<data_len+1; i++)
    {
        encoded_buff[i] =  buff[i-1];
        encoded_buff[data_len+1] ^= encoded_buff[i] ;
    }

    memset(&params, 0, sizeof(params));
    params.type = BLE_GATT_HVX_NOTIFICATION;
    params.handle = p_test->test_notify_handles.value_handle;
    params.p_data = encoded_buff;

    params.p_len = &len;

    return sd_ble_gatts_hvx(p_test->conn_handle, &params);
}

//indicateコマンド送信関数
uint32_t indicate_cmd(ble_test_t * p_test, uint8_t cmd, uint8_t *buff, uint16_t data_len)
{
    uint8_t                encoded_buff[data_len+2];
    uint8_t                i;
    ble_gatts_hvx_params_t params;

    uint16_t len = (data_len+2); //sizeof(buff);

    encoded_buff[0] =  cmd;     
    encoded_buff[data_len+1] = 0;
    encoded_buff[data_len+1] ^= encoded_buff[0];

    for(i=1; i<data_len+1; i++)
    {
        encoded_buff[i] =  buff[i-1];
        encoded_buff[data_len+1] ^= encoded_buff[i] ;
    }

    memset(&params, 0, sizeof(params));
    params.type = BLE_GATT_HVX_INDICATION;
    params.handle = p_test->test_indicate_handles.value_handle;
    params.p_data = encoded_buff;
    params.p_len = &len;

    return sd_ble_gatts_hvx(p_test->conn_handle, &params);
}

サービスUUID

BLEのサービスには固有のUUIDが割り当てられます。そして、そのサービスが持つCharacteristicにも固有のUUIDが割り当てられます。
頻繁に使用する温度計や心拍系のUUIDには、あらかじめ短縮UUIDが用意されています。
今回は独自のBLEサービスを作成するので16バイトのUUIDを作成する必要があります。必要なのは、サービスUUIDとNotify CharacteristicのUUIDです。
一般的にはUUID作成ツールを使用しますが、今回は適当な値を設定しました。

UUIDは13バイト目と14バイト目以外は共通です。

#define TEST_UUID_BASE {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0x00, 0x00, 0xDD, 0xEE}
#define TEST_UUID_SERVICE             0xFFFF
#define TEST_UUID_NOTIFY_CHAR         0xAAAA

特殊設定

太陽誘電のBLEモジュールは32MHzクロックを内蔵していますが、サンプルプロジェクトは16MHzを使するようになっています。
その為、以下の2箇所の修正が必要になります。

system_nrf51.c.c
void SystemInit(void)
{
    if (*(uint32_t *)0x10001008 == 0xFFFFFFFF)
    {
        NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Wen << NVMC_CONFIG_WEN_Pos;
        while (NRF_NVMC->READY == NVMC_READY_READY_Busy){}
         *(uint32_t *)0x10001008 = 0xFFFFFF00;
        NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Ren << NVMC_CONFIG_WEN_Pos;
        while (NRF_NVMC->READY == NVMC_READY_READY_Busy){}
        NVIC_SystemReset();
        while (true){}
    }
***.h
#define NRF_CLOCK_LFCLKSRC      {.source        = NRF_CLOCK_LF_SRC_RC, 
                                 .rc_ctiv       = 16,  
                                 .rc_temp_ctiv  = 2,   
                                 .xtal_accuracy = NRF_CLOCK_LF_XTAL_ACCURACY_20_PPM}

スイッチ割り込み処理とLED点灯処理

以下のファイルはペリフェラル側とセントラル側のどちらでも使えます。
NordicのBLEモジュールは、端子の端子機能を自分で定義できるという特徴を持ちます。(他社製のマイコンでは、端子に割り当てられている機能は、何パターンか選択できるものの、あらかじめ決められている場合が多いです。)その為、nRF51822は自由な端子配置が可能であり、プログラム実行中に端子機能を入れ替えることさえできます。

gpio.c
#include "nrf_gpio.h"
#include "boards.h"

uint8_t  notify_flag;
extern uint8_t  notify_flag;

//LED用出力端子を「組み立て」で決めた端子番号に設定
#define LED_A   25
#define LED_B   28

// スイッチ用入力端子を「組み立て」で決めた端子番号に設定
#define TEST_SW_A   19  
#define TEST_SW_B   17      

// IO端子初期化処理
void gpio_init(void)
{
    //出力端子設定
    nrf_gpio_cfg_output(LED_A);
    nrf_gpio_cfg_output(LED_B);

    nrf_gpio_pin_set(LED_A);
    nrf_gpio_pin_set(LED_B);

    //スイッチ用入力端子設定
    nrf_gpio_cfg_sense_input(TEST_SW_A, NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW);
    nrf_gpio_cfg_sense_input(TEST_SW_B, NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW);
}

// 割り込み許可処理
void interrupt_enable(void)
{
    // Set the GPIOTE PORT event as interrupt source, and enable interrupts for GPIOTE
    NRF_GPIOTE->INTENSET = GPIOTE_INTENSET_PORT_Msk;
    NRF_GPIOTE->INTENSET = GPIOTE_INTENSET_PORT_Enabled << GPIOTE_INTENSET_PORT_Pos;    NVIC_EnableIRQ(GPIOTE_IRQn);    
}

// スイッチ割込み処理
void GPIOTE_IRQHandler(void)
{   
    if(NRF_GPIOTE->EVENTS_PORT)
    {
        //ここでイベントフラグをクリアしないと、無限に外部割込み処理を呼び続けます。
        NRF_GPIOTE->EVENTS_PORT = 0;

        //スイッチ押下を確認し、notify送信フラグを立てます。
        //割込み処理内からNotify送信ができないので、main関数内でフラグを監視して、Notify送信を行います。
        if(0 == nrf_gpio_pin_read(TEST_SW_A))
        {                       
            //nrf_gpio_pin_toggle(LED_A);   
            notify_flag = 0x01;
        }
        else if(0 == nrf_gpio_pin_read(TEST_SW_B))
        {
            //nrf_gpio_pin_toggle(LED_B);   
            notify_flag = 0x02;
        }
    }
}

main処理

main関数では、初期化処理でアドバタイジングパケットとサービスの設定を行い、main loop内でスイッチ割り込み処理を待ち、Notify送信を行います。

サンプルプログラムのmain.cから不要な関数を取り除き、幾つかの修正を行います。
主な修正だけを以下に挙げますが、不明点があればお気軽に本記事コメントでご質問ください。

以下のファイルを追加ください。

main.c
#include "test_service.h"

幾つかの変数と関数を追加します。

main.c
ble_test_t                        m_test;

//Master BLEからのWriteデータ受信バッファ
uint8_t write_buff[20];
uint8_t write_len;
extern uint8_t  notify_flag;

static void advertising_start(void);
void gpio_init(void);
void interrupt_enable(void);

今回は使用しませんが、セントラル側からのwrite受信時の割込み処理を以下に記述します。

main.c
// BLE Write処理:BLE_Master -> BLE_Slave
static void test_write_handler(ble_test_t * p_test, uint8_t *receive_buff, uint8_t length)
{
    uint8_t  i;

    for(i=0;  i<length;  i++)
    {
        write_buff[i] = receive_buff[i];
    }                       
    write_len = length;
}

サービス初期化処理です。

main.c
static void services_init(void)
{
    uint32_t err_code;
    ble_test_init_t init;

    init.test_write_handler = test_write_handler;

    err_code = ble_test_init(&m_test, &init);
    APP_ERROR_CHECK(err_code);
}

BLE設定を以下で有効にします。

main.c
static void ble_evt_dispatch(ble_evt_t * p_ble_evt)
{
    ble_test_on_ble_evt(&m_test, p_ble_evt);
    ble_conn_state_on_ble_evt(p_ble_evt);
    pm_on_ble_evt(p_ble_evt);
    ble_conn_params_on_ble_evt(p_ble_evt);
    on_ble_evt(p_ble_evt);
    ble_advertising_on_ble_evt(p_ble_evt);
}

main関数は以下になります。

main.c
int main(void)
{
    uint8_t buff[5];
    uint8_t length;
    uint32_t i;
    uint32_t err_code;
    bool     erase_bonds;

    // Initialize.
    err_code = NRF_LOG_INIT(NULL);
    APP_ERROR_CHECK(err_code);

    timers_init();
    ble_stack_init();
    peer_manager_init(erase_bonds);
    if (erase_bonds == true)
    {
        NRF_LOG_INFO("Bonds erased!\r\n");
    }
    gap_params_init();
    advertising_init();
    services_init();
    conn_params_init();

    gpio_init();
    interrupt_enable();

    // Start execution.
    application_timers_start();
    err_code = ble_advertising_start(BLE_ADV_MODE_FAST);
    APP_ERROR_CHECK(err_code);

    // Enter main loop.
    for (;;)
    {               
        if(notify_flag == 0x01)
        {
            notify_flag = 0;
            buff[0] = 0x01;
            length = 1; 
            notify_cmd(&m_test, 0x11, &buff[0], length);
        }
        else if(notify_flag == 0x02)
        {
            notify_flag = 0;
            buff[0] = 0x02;
            length = 1; 
            notify_cmd(&m_test, 0x11, &buff[0], length);
        }

        if (NRF_LOG_PROCESS() == false)
        {
            power_manage();
        }
    }
}

セントラル側ファームウェア

SDKのサンプルプログラムを土台にして、今回の仕様に合わせた修正を行います。

ダウンロードSDKフォルダ内の以下の階層にあるプロジェクトファイルを開いて、修正をします。

examples\ble_central\experimental\ble_app_blinky_c\pca10028\s130\arm5_no_packs\ble_app_blinky_c_pca10028_s130.uvprojx

スキャン設定

サービスの作成

static void on_hvx(ble_test_c_t * p_ble_test_c, const ble_evt_t * p_ble_evt)
{
       uint32_t        index = 0;
         uint8_t   i, j, z;

    // Check if the event is on the link for this instance
    if (p_ble_test_c->conn_handle != p_ble_evt->evt.gattc_evt.conn_handle)
    {
        return;
    }
    // Check if this is a notification.
    if (p_ble_evt->evt.gattc_evt.params.hvx.handle == p_ble_test_c->peer_test_db.test_notify_handle)
    {
        //if (p_ble_evt->evt.gattc_evt.params.hvx.len == 1)  //データ長さが1byteかどうか(複数byte受信する場合は、不要な条件)
        //{
            ble_test_c_evt_t ble_test_c_evt;

            ble_test_c_evt.evt_type                   = BLE_TEST_C_EVT_NOTIFICATION;
            ble_test_c_evt.conn_handle                = p_ble_test_c->conn_handle;  //この情報を見ることで、どのデバイスからの送信であるのかを判定する。
            ble_test_c_evt.params.test_notify.test_notify_state = p_ble_evt->evt.gattc_evt.params.hvx.data[index++];
            p_ble_test_c->evt_handler(p_ble_test_c, &ble_test_c_evt);

                        notify_buf[notify_index] =  p_ble_evt->evt.gattc_evt.params.hvx.data[0];
                        notify_index += 1;
                        notify_buf[notify_index] =  p_ble_evt->evt.gattc_evt.params.hvx.data[1];
                        notify_index += 1;
                        notify_buf[notify_index] =  p_ble_evt->evt.gattc_evt.params.hvx.data[2];
                        notify_index = 0;                       
        //}
    }
    // Check if this is a indication.
    else if (p_ble_evt->evt.gattc_evt.params.hvx.handle == p_ble_test_c->peer_test_db.test_indicate_handle)
    {
        //if (p_ble_evt->evt.gattc_evt.params.hvx.len == 1)  //データ長さが1byteかどうか(複数byte受信する場合は、不要な条件)
        //{
            ble_test_c_evt_t ble_test_c_evt;

            ble_test_c_evt.evt_type                   = BLE_TEST_C_EVT_INDICATION;
            ble_test_c_evt.conn_handle                = p_ble_test_c->conn_handle;
            ble_test_c_evt.params.test_indicate.test_indicate_state = p_ble_evt->evt.gattc_evt.params.hvx.data[index++];
            p_ble_test_c->evt_handler(p_ble_test_c, &ble_test_c_evt);

                        for(i=0; i<20; i++)
                        {
                                indicate_buf[indicate_index] =  p_ble_evt->evt.gattc_evt.params.hvx.data[i];
                                indicate_index += 1;
                        }


                            for(j=0; j<16; j++)
                            {
                                    measure_buf[j+8] = indicate_buf[i+j+1];
                            }                                           


                        //Indicateの場合は、ACK応答を返す以下の関数を追加しないといけない。
                        sd_ble_gattc_hv_confirm(ble_test_c_evt.conn_handle,  p_ble_test_c->peer_test_db.test_indicate_handle);

    }
}
void ble_test_on_db_disc_evt(ble_test_c_t * p_ble_test_c, const ble_db_discovery_evt_t * p_evt)
{
    // Check if the TEST Service was discovered.
    if (p_evt->evt_type == BLE_DB_DISCOVERY_COMPLETE &&
        p_evt->params.discovered_db.srv_uuid.uuid == TEST_UUID_SERVICE &&
        p_evt->params.discovered_db.srv_uuid.type == p_ble_test_c->uuid_type)
    {
        ble_test_c_evt_t evt;

        evt.evt_type    = BLE_TEST_C_EVT_DISCOVERY_COMPLETE;
        evt.conn_handle = p_evt->conn_handle;

        //取得したcharacteristic数であるchar_countの上限まで網羅的に検索を行う。
        uint32_t i;
        for (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]);
                        //取得したcharacteristicから登録しているCHARACTER UUIDと一致するものを探す。
            switch (p_char->characteristic.uuid.uuid)
            {
                case TEST_UUID_WRITE_CHAR:
                    evt.params.peer_db.test_write_handle = p_char->characteristic.handle_value;
                    break;
                case TEST_UUID_NOTIFY_CHAR:
                    evt.params.peer_db.test_notify_handle      = p_char->characteristic.handle_value;
                    evt.params.peer_db.test_notify_cccd_handle = p_char->cccd_handle;
                    break;
                case TEST_UUID_INDICATE_CHAR:
                    evt.params.peer_db.test_indicate_handle      = p_char->characteristic.handle_value;
                    evt.params.peer_db.test_indicate_cccd_handle = p_char->cccd_handle;
                    break;

                default:
                    break;
            }
        }

        NRF_LOG_DEBUG("TEST Service discovered at peer.\r\n");
        //If the instance has been assigned prior to db_discovery, assign the db_handles
        if (p_ble_test_c->conn_handle != BLE_CONN_HANDLE_INVALID)
        {
            if ((p_ble_test_c->peer_test_db.test_write_handle         == BLE_GATT_HANDLE_INVALID)&&
                (p_ble_test_c->peer_test_db.test_notify_handle      == BLE_GATT_HANDLE_INVALID)&&
                (p_ble_test_c->peer_test_db.test_notify_cccd_handle == BLE_GATT_HANDLE_INVALID)&&
                (p_ble_test_c->peer_test_db.test_indicate_handle      == BLE_GATT_HANDLE_INVALID)&&
                (p_ble_test_c->peer_test_db.test_indicate_cccd_handle == BLE_GATT_HANDLE_INVALID))
            {
                p_ble_test_c->peer_test_db = evt.params.peer_db;
            }
        }

        p_ble_test_c->evt_handler(p_ble_test_c, &evt);

    }
}

main処理

main処理は以下のようになります。
scan_start();によりSCANが開始され、ペリフェラルからのアドバタイジングパケットを受信可能になります。

main loop内では、Notifyからの受信バッファを監視して、LEDの点灯と消灯を実行します。

int main(void)
{
    ret_code_t err_code;

    err_code = NRF_LOG_INIT(NULL);
    APP_ERROR_CHECK(err_code);
    APP_TIMER_INIT(APP_TIMER_PRESCALER, APP_TIMER_OP_QUEUE_SIZE, NULL);


    //ソフトデバイス初期化
    ble_stack_init();
    db_discovery_init();

    //サービス初期化
    test_c_init();

    gpio_init();
    interrupt_enable();

    // SCAN開始
    scan_start();

    for (;;)
    {
        if (notify_buf[0] == 0x11)
        {
            notify_buf[0] = 0;

            if (notify_buf[1] == 0x01)
            {
                notify_buf[1] = 0;
                nrf_gpio_pin_toggle(LED_A); 
            }
            else if (notify_buf[1] == 0x02)
            {
                notify_buf[1] = 0;
                nrf_gpio_pin_toggle(LED_B); 
            }
        }

        if (NRF_LOG_PROCESS() == false)
        {
            // Wait for BLE events.
            power_manage();
        }
    }
}

書き込み

動作確認

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away