chirimenBLE for TY51822

Dependencies:   mbed BLE_API nRF51822

main.cpp

Committer:
nakamura_bs
Date:
2018-12-24
Revision:
1:4d21403a5251
Parent:
0:6b1b97df8a36

File content as of revision 1:4d21403a5251:

/*
 * Chirimen BLE farmware
 * copylight KDDI Technorogy
 *
 * change log
 * 2018/11/28 初回リリース
 * 2018/12/03 GPIO端子の初期値不定を修正
 * 2018/12/19 GPIO ドライブ能力変更 (0.5mA -> 5mA)
 * 2018/12/20 nRF51ライブラリのバグ対策 -- RTS/CTSの割り当て禁止
 *            送信出力を +4dBmに規定
 */


#include "mbed.h"
#include "BLE.h"

#include "board.h"

#define VERSION_NUM 1 // 2018-11-28
const static char  DEVICE_NAME[] = "btGPIO2";

#define NEED_CONSOLE_OUTPUT 1 
/* Set this if you need debug messages on the console;
 * it will have an impact on code-size and power consumption. */

#if NEED_CONSOLE_OUTPUT
Serial  pc(DBG_TX, DBG_RX); // TX,RX default 9600bps
#define DEBUG(...) { pc.printf(__VA_ARGS__); }
#else
#define DEBUG(...) /* nothing */
#endif /* #if NEED_CONSOLE_OUTPUT */

#ifdef MICROBIT
// micro:bit matrix-led
DigitalOut col0(P0_4, 0);
#endif
DigitalOut LED_CONNECT(GPIO_LED_CONNECT);

#define CMD_EXPORT      0x10
#define CMD_GPIO_WRITE  0x11
#define CMD_GPIO_READ   0x12
#define CMD_UNEXPORT    0x13
#define CMD_GPIO_EVENT  0x14
#define CMD_I2C_INIT    0x20
#define CMD_I2C_WRITE   0x21
#define CMD_I2C_RDBYTE  0x22
#define CMD_I2C_READ    0x23
#define CMD_ADC_INPUT   0x30
#define CMD_ADC_READ    0x31

// Bluetooth Low Energy
BLE ble;
static Gap::ConnectionParams_t connectionParams;

// I2C 2-waire
I2C i2c(I2C_SDA,I2C_SCL);
#define MAX_I2C_TRANSFAR_SIZE 10

// Timer
Ticker timerTask;

/*
 * gpio bridge used UUID
 * ServiceUUID:    928a3d40-e8bf-4b2b-b443-66d2569aed50
 * NotifyDataUUID: 928a3d41-e8bf-4b2b-b443-66d2569aed50
 * WriteDataUUID:  928a3d42-e8bf-4b2b-b443-66d2569aed50
 */
static const uint8_t UUID_GPIO_SERVICE[]  = {0x92,0x8a,0x3d,0x40,0xe8,0xbf,0x4b,0x2b,0xb4,0x43,0x66,0xd2,0x56,0x9a,0xed,0x50};
static const uint8_t UUID_ADV_SERVICE[]   = {0x50,0xed,0x9a,0x56,0xd2,0x66,0x43,0xb4,0x2b,0x4b,0xbf,0xe8,0x40,0x3d,0x8a,0x92};
static const uint8_t UUID_NOTIFY_DATA[]   = {0x92,0x8a,0x3d,0x41,0xe8,0xbf,0x4b,0x2b,0xb4,0x43,0x66,0xd2,0x56,0x9a,0xed,0x50};
static const uint8_t UUID_WRITE_DATA[]    = {0x92,0x8a,0x3d,0x42,0xe8,0xbf,0x4b,0x2b,0xb4,0x43,0x66,0xd2,0x56,0x9a,0xed,0x50};
static const uint8_t UUID_STAT_DATA[]     = {0x92,0x8a,0x3d,0x43,0xe8,0xbf,0x4b,0x2b,0xb4,0x43,0x66,0xd2,0x56,0x9a,0xed,0x50};

uint8_t wrtData[20] = {0,};
GattCharacteristic  gWrtCharacteristic (UUID_WRITE_DATA, wrtData, sizeof(wrtData), sizeof(wrtData),
        GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE);

uint8_t notifyData[20] = {0,};
GattCharacteristic  gNotifyCharacteristic (UUID_NOTIFY_DATA, notifyData, sizeof(notifyData), sizeof(notifyData),
        GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY);

uint8_t statData[20] = {0,0};
GattCharacteristic  gStatCharacteristic (UUID_STAT_DATA, statData, sizeof(statData), sizeof(statData),
        GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE);

GattCharacteristic *gGpioChars[] = {&gNotifyCharacteristic,&gWrtCharacteristic,&gStatCharacteristic};
GattService gGpioService = GattService(UUID_GPIO_SERVICE, gGpioChars, sizeof(gGpioChars) / sizeof(GattCharacteristic *));

int gConnect = 0;

int gCmdLen = 0;
uint8_t gCmdData[20] = {0,};

int gNotifyLen = 0;
uint8_t gNotifyData[6] = {0x2,0x0,0x0,0x14,0,0};
int gScanGPIO = 0;

/* ----GPIO Task----------------------------------------------------------------------- */
void gpioTask() {
            
//    LED_CONNECT = !LED_CONNECT;
    
    if (gNotifyLen >0) {  // busy
        return;
    }

    for (int i=0; i<sizeof(validGpioPins); i++) {
        if (eventGpioPins[gScanGPIO] != 0) { // is event pins
            uint8_t rdGpio = gports[gScanGPIO];
            if ( currentGpioLevel[gScanGPIO] != rdGpio) {
                DEBUG("GPIO status chenged port%d:%d\r\n",validGpioPins[gScanGPIO],rdGpio);
                currentGpioLevel[gScanGPIO] = rdGpio;
                gNotifyData[4] = gScanGPIO;
                gNotifyData[5] = rdGpio;
                gNotifyLen = 6;
                continue;
            }
        }
        gScanGPIO++;
        if (gScanGPIO > sizeof(validGpioPins)) {
            gScanGPIO = 0;
        }
    }
}

void reloadGpioCondition() {
    for (int i=0; i<sizeof(validGpioPins); i++) {
        if (eventGpioPins[i] != 0) { // is event pins
            uint8_t rdGpio = gports[i];
            currentGpioLevel[i] = rdGpio;
        }
    }
}

void clearEventLists() {
    for (int i=0; i<sizeof(validGpioPins); i++) {
        eventGpioPins[i] = 0;
    }
}

void initGpio() {
    for (int i=0; i<sizeof(validGpioPins); i++) {
        int gpioNum = validGpioPins[i];
        DEBUG("initGpio() 0x%x\r\n", gpioNum);
        gports[gpioNum].input();        // 端子を入力モード
        gports[gpioNum].mode(PullNone); // pullアップ/ダウンなし
        eventGpioPins[gpioNum] = 0;     // イベント監視対象外
        reloadGpioCondition(); // 設定を更新
    }
    
    // nRF51ライブラリのバグ対策 -- RTS/CTSの割り当て禁止
    NRF_UART0->PSELRTS = 0xFFFFFFFF;
    NRF_UART0->PSELCTS = 0xFFFFFFFF;
}

// set high drive (Low/High 5mA)
void hiDriveGpio(int gpioNum) {
    NRF_GPIO->PIN_CNF[gpioNum] =
        (NRF_GPIO->PIN_CNF[gpioNum] & ~GPIO_PIN_CNF_DRIVE_Msk) |
        (GPIO_PIN_CNF_DRIVE_H0H1 << GPIO_PIN_CNF_DRIVE_Pos);
}
/* --- BLE callbacks ------------------------------------------------------------------- */
void onDisconnectionCallback(const Gap::DisconnectionCallbackParams_t *params)    // Mod
{
    DEBUG("Disconnected handle %u, reason %u\r\n", params->handle, params->reason);
    DEBUG("Restarting the advertising process\r\n");
    DEBUG("Disconnected!\n\r");
    timerTask.detach(); // stop timer
    gConnect = 0;
    clearEventLists();
    
    LED_CONNECT = 0;
    ble.startAdvertising();
}

#define BLE_INTERVAL_10MS   8
#define BLE_INTERVAL_12MS   10
#define BLE_INTERVAL_20MS   15
#define BLE_INTERVAL_30MS   23
#define BLE_INTERVAL_50MS   39
#define BLE_INTERVAL_62MS   50
#define BLE_INTERVAL_100MS  78
#define BLE_INTERVAL_125MS  100

void onConnectionCallback(const Gap::ConnectionCallbackParams_t *params)   //Mod
{
    DEBUG("Connected!\n\r");
    LED_CONNECT = 1;
    ble.getPreferredConnectionParams(&connectionParams);
//    connectionParams.minConnectionInterval        = BLE_INTERVAL_10MS;
    connectionParams.minConnectionInterval        = BLE_INTERVAL_50MS;
//    connectionParams.minConnectionInterval        = INTERVAL_100MS;

//    connectionParams.maxConnectionInterval        = BLE_INTERVAL_12MS;
    connectionParams.maxConnectionInterval        = BLE_INTERVAL_62MS;
//    connectionParams.maxConnectionInterval        = BLE_INTERVAL_125MS;
    if (ble.updateConnectionParams(params->handle, &connectionParams) != BLE_ERROR_NONE) {
        DEBUG("failed to update connection paramter\r\n");
    }
    gConnect = 1;
    timerTask.attach(&gpioTask, 0.1f); // start interval 100mS timer 
    
    // set ready flag
    statData[0] = 0;
    statData[1] = VERSION_NUM;
    ble.gattServer().write(gStatCharacteristic.getValueAttribute().getHandle(), statData, 4);
}

void ConnectTimeoutCallback(Gap::TimeoutSource_t source)
{
    ble.setAdvertisingTimeout(0); /* 0 is disable the advertising timeout. */
    ble.startAdvertising();
}

void DataWrittenCallback(const GattWriteCallbackParams *params)
{
    uint8_t *wrtData = (uint8_t *)params->data;
    uint16_t wrtLen = params->len;
        
    memcpy(gCmdData,wrtData,wrtLen);
    gCmdLen = wrtLen;
    LED_CONNECT = 0; // busy condition

    DEBUG("rcv\r\n");
//    DEBUG("DataWrittenCallback() len=%u data=", wrtLen);
//    for (int i=0; i< wrtLen; i++) {
//        DEBUG("0x%x,", *wrtData++);
//    }
//    DEBUG("\r\n");

}

void UpdatesEnabledCallBack(Gap::Handle_t handle)
{
    DEBUG("UpdatesEnabled\n\r");
    // ユーザ処理コード
}
 
void UpdatesDisabledCallBack(Gap::Handle_t handle)
{
    DEBUG("UpdatesDisabled\n\r");
    // ユーザ処理コード
}
 
void DataSentCallback(unsigned count)
{
//    DEBUG("DataSentCallback\n\r");
    // ユーザ処理コード
}

/* --- test code ----------------------------------------------------------------------- */
int tako() {
    i2c.frequency(I2C_SPEED);
    char wReg[2] = {0x0,0x0};
    char rReg[2] = {0,0};
    
    i2c.start();
    int val = i2c.write(0x48<<1);
    i2c.stop();
    DEBUG("write val %d\r\n",val);
    
#if 1
    if (i2c.write(0x48<<1, wReg, 0) == 0) {
        DEBUG("write ok\r\n");
    } else {
        DEBUG("write NG\r\n");
    }
#endif    
//    i2c.write(0x48, wReg, 1);
#if 1
    if (i2c.read(0x48<<1, rReg, 2) == 0) {
        DEBUG("read ok\r\n");
    } else {
        DEBUG("read ng\r\n");
    }

    DEBUG("read sensor data=");
    for (int i=0; i< 2; i++) {
      DEBUG("0x%x,", rReg[i]);
    }
    DEBUG("\r\n");
#endif
    return 0;
}

/* --- user task ----------------------------------------------------------------------- */
int checkGPIO(int gpioNum) {
    for (int i=0; i<sizeof(validGpioPins); i++) {
        if (validGpioPins[i] == gpioNum) {
            return i;
        }
    }
    DEBUG("Invalid GPIO number %d\r\n",gpioNum);
    return -1;    
}

// ホストからのコマンドを処理
void commandTask() {
    uint8_t response[20] = {1,2,3,4};
    int responseSize = 5;
    int gpioNum = -1;
    char i2cWrBuf[MAX_I2C_TRANSFAR_SIZE] = {0,};
    char i2cRdBuf[MAX_I2C_TRANSFAR_SIZE] = {0,};

    memcpy(response,gCmdData,4); // コマンドヘッダ部分をレスポンスデータにコピー

    switch (gCmdData[3]) {
      case CMD_EXPORT: // use GPIO port
        // input:
        //  gCmdData[4] : target port number
        //  gCmdData[5] : 0=outpt 1=input
        // output:
        //  response[4] : 1=accept 1=reject
        gpioNum = checkGPIO(gCmdData[4]);
        if (gpioNum < 0) {
            response[4] = 0; // error
        } else {
            if (gCmdData[5] == 0) {
                DEBUG("set GPIO output  %d\r\n",gCmdData[4]);
                gports[gpioNum] = 0; // inital state low
                gports[gpioNum].output();
                hiDriveGpio(gpioNum); // set high drive (Low/High 5mA)


            } else {
                DEBUG("set GPIO input %d\r\n",gCmdData[4]);
                gports[gpioNum].input();
                gports[gpioNum].mode(PullUp);  // 内蔵プルアップを使う
                // gports[gpioNum].mode( PullDown );    /  内蔵プルダウンを使う
                eventGpioPins[gpioNum] = 1; // イベント監視対象
                reloadGpioCondition();
            }
            response[4] = 1; // OK
        }
        break;

      case CMD_UNEXPORT: // GPIO port input
        // input:
        //  gCmdData[4] : target port number
        // output:
        //  response[4] : 1=accept 1=reject
        gpioNum = checkGPIO(gCmdData[4]);
        if (gpioNum < 0) {
            response[4] = 0; // error
        } else {
            gports[gpioNum].input(); // 端子を入力として解放
            gports[gpioNum].mode(PullNone);
            eventGpioPins[gpioNum] = 0; // イベント監視対象外
            reloadGpioCondition();
            response[4] = 1; // OK
        }
        break;

      case CMD_GPIO_WRITE: // GPIO port write
        // input:
        //  gCmdData[4] : target port number
        //  gCmdData[5] : 0=low 1=high
        // output:
        //  response[4] : 1=accept 0=reject
        gpioNum = checkGPIO(gCmdData[4]);
        if (gpioNum < 0) {
            response[4] = 0; // error
        } else {
            gports[gpioNum] = gCmdData[5];
            response[4] = 1; // OK
        }                
        break;

      case CMD_GPIO_READ: // GPIO port write
        // input:
        //  gCmdData[4] : target port number
        // output:
        //  response[4] : 1=accept 0=reject
        //  response[5] : port condition 0=low 1=high
        gpioNum = checkGPIO(gCmdData[4]);
        if (gpioNum < 0) {
            response[4] = 0; // error
        } else {
            gports[gpioNum] = gCmdData[5];
            if (gports[gpioNum] ==0) {
                response[5] = 0; // Low
            } else {
                response[5] = 1; // High
            }
            response[4] = 1; // OK
            responseSize += 1;
        }                
        break;

      case CMD_I2C_INIT: // I2C test access
        // input:
        //  gCmdData[4] : slave I2C address
        // output:
        //  response[4] : 1=accept 1=reject
        i2cWrBuf[0] =0;
        if (i2c.write(gCmdData[4]<<1, i2cWrBuf, 0) == 0) {
            response[4] = 1; // OK
        } else {
            DEBUG("I2C write error address=%x\r\n",gCmdData[4]);
            response[4] = 0; // error
        }
        break;

      case CMD_I2C_READ: // I2C read
        // input:
        //  gCmdData[4] : slave I2C address
        //  gCmdData[5] : sub-address
        //  gCmdData[6] : length
        // output:
        //  response[4] : read data length / 0=reject
        //  response[5] : read data
        if ((gCmdData[6] > MAX_I2C_TRANSFAR_SIZE) || (gCmdData[6] < 1)) {
            DEBUG("I2C write error invalid length=%d\r\n",gCmdData[6]);
            response[4] = 0; // error
            break;
        }
        i2cWrBuf[0] = gCmdData[5];
        if (i2c.write(gCmdData[4]<<1, i2cWrBuf, 1, true) != 0) { // Repeat condision
            DEBUG("I2C write error address=%x\r\n",gCmdData[4]);
            response[4] = 0; // error
            break;
        }
        if (i2c.read(gCmdData[4]<<1, i2cRdBuf, gCmdData[6]) != 0) {
            DEBUG("I2C read error address=%x\r\n",gCmdData[4]);
            response[4] = 0; // error
            break;
        }
//        DEBUG("I2C read ok\r\n");
        memcpy(&response[5],i2cRdBuf,gCmdData[6]);
        response[4] = gCmdData[6]; // read size
        responseSize += gCmdData[6];
        break;

      case CMD_I2C_RDBYTE:
        // input:
        //  gCmdData[4] : slave I2C address
        //  gCmdData[5] : length
        // output:
        //  response[4] : read data length / 0=reject
        //  response[5] : read data
        if ((gCmdData[5] > MAX_I2C_TRANSFAR_SIZE) || (gCmdData[5] < 1)) {
            DEBUG("I2C write error invalid length=%d\r\n",gCmdData[5]);
            response[4] = 0; // error
            break;
        }
        if (i2c.read(gCmdData[4]<<1, i2cRdBuf, gCmdData[5]) != 0) {
            DEBUG("I2C read error address=%x\r\n",gCmdData[4]);
            response[4] = 0; // error
            break;
        }
//        DEBUG("read ok\r\n");
        memcpy(&response[5],i2cRdBuf,gCmdData[5]);
        response[4] = gCmdData[5]; // read size
        responseSize += gCmdData[5];
        break;

      case CMD_I2C_WRITE: // I2C write
        // input:
        //  gCmdData[4] : slave I2C address
        //  gCmdData[5] : length
        //  gCmdData[6] : write data
        //  gCmdData[7] ...
        // output:
        //  response[4] : 1=accept 1=reject
        if ((gCmdData[5] > MAX_I2C_TRANSFAR_SIZE) || (gCmdData[5] < 1)) {
            DEBUG("I2C write error invalid length=%d\r\n",gCmdData[5]);
            response[4] = 0; // error
            break;
        }
        memcpy(i2cWrBuf,&gCmdData[6],gCmdData[5]);
        if (i2c.write(gCmdData[4]<<1, i2cWrBuf, gCmdData[5]) != 0) { // Repeat condision
            DEBUG("I2C write error address=%x\r\n",gCmdData[4]);
            response[4] = 0; // error
            break;
        }

        response[4] = gCmdData[5]; // return complite transfar size
        break;

#if 0
      case CMD_ADC_INPUT:
        // input:
        //  gCmdData[4] : target port number
        // output:
        //  response[4] : 1=accept 1=reject
        gpioNum = checkGPIO(gCmdData[4]);
        if ((gpioNum < 1) || (gpioNum > 6)) {
            response[4] = 0; // error
        } else {
            gports[gpioNum].input(); // 端子を入力として解放
            gports[gpioNum].mode(PullNone);
            eventGpioPins[gpioNum] = 0; // イベント監視対象外
            reloadGpioCondition();
            
            // ADC setting
            // 10bit Reference internal Bandcap(1.2V)
            NRF_ADC->CONFIG = (ADC_CONFIG_RES_10bit << ADC_CONFIG_RES_Pos) |
                (ADC_CONFIG_INPSEL_AnalogInputOneThirdPrescaling << ADC_CONFIG_INPSEL_Pos) |
                (ADC_CONFIG_REFSEL_VBG << ADC_CONFIG_REFSEL_Pos) |
                (ADC_CONFIG_EXTREFSEL_None << ADC_CONFIG_EXTREFSEL_Pos);
            response[4] = 1; // OK
        }
        break;
        
      case CMD_ADC_READ:
        // input:
        //  gCmdData[4] : target port number
        // output:
        //  response[4] : read data length(fixed 2) / 0=reject
        //  response[5] : read data(MSB)
        //  response[6] : read data(LSB)
        gpioNum = gCmdData[4];
        if ((gpioNum < 1) || (gpioNum > 6)) {
            response[4] = 0; // error
        } else {
            NRF_ADC->CONFIG     &= ~ADC_CONFIG_PSEL_Msk;
            NRF_ADC->CONFIG     |= (4 << gpioNum) << ADC_CONFIG_PSEL_Pos;
            NRF_ADC->TASKS_START = 1;
            while (((NRF_ADC->BUSY & ADC_BUSY_BUSY_Msk) >> ADC_BUSY_BUSY_Pos) == ADC_BUSY_BUSY_Busy) {
            }
            uint16_t advalue = (uint16_t)NRF_ADC->RESULT; // 10 bit
            DEBUG("read adc %d =%x\r\n",gpioNum, advalue);
            response[5] = advalue >> 8;
            response[6] = advalue && 0xff;
            responseSize += 2;
        }
        break;
#endif
              
      default:
        DEBUG("unknonwn command =%x\r\n",gCmdData[3]);
        response[4] = 0; // error
        break;    
                
    }

    DEBUG("reply\r\n");
//    DEBUG("send Notify len=%u data=", responseSize);
//    for (int i=0; i< responseSize; i++) {
//        DEBUG("0x%x,", response[i]);
//    }
//    DEBUG("\r\n");

    // send response (notify)
    ble.gattServer().write(gNotifyCharacteristic.getValueAttribute().getHandle(), response, responseSize);
    LED_CONNECT = 1; // ready

}

/* --- main code ----------------------------------------------------------------------- */
int main() {
    LED_CONNECT = 1;
    gCmdLen = 0;
    gConnect = 0;
    gScanGPIO = 0;

    initGpio();
    hiDriveGpio(GPIO_LED_CONNECT);
    DEBUG("PowerON version %d\n\r",VERSION_NUM);
    
    /* Initialise the nRF51822 */
    ble.init();

    /* Setup The event handlers */
    ble.onConnection(onConnectionCallback);
    ble.onDisconnection(onDisconnectionCallback);
    ble.onTimeout(ConnectTimeoutCallback);

    ble.onDataWritten(DataWrittenCallback);
    ble.onDataSent(DataSentCallback);
    ble.onUpdatesEnabled(UpdatesEnabledCallBack);
    ble.onUpdatesDisabled(UpdatesDisabledCallBack);
    
    // set TX power
    // Valid values are -40, -20, -16, -12, -8, -4, 0, 4)
    ble.setTxPower(4);

    /* setup advertising */
    ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);

    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS,
                                     (const uint8_t *)UUID_ADV_SERVICE, sizeof(UUID_ADV_SERVICE));
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME,
                                     (const uint8_t *)DEVICE_NAME,
                                     strlen(DEVICE_NAME));
    ble.gap().setAdvertisingInterval(80); /* 50ms; in multiples of 0.625ms. */
    ble.gap().startAdvertising();
    DEBUG("Start Advertising\r\n");

    ble.addService(gGpioService);

    LED_CONNECT = 0;
    for (;; ) {
        ble.waitForEvent();
        if (gCmdLen > 0) {
            commandTask();
            gCmdLen =0;
        } else if (gNotifyLen > 0) {
            ble.gattServer().write(gNotifyCharacteristic.getValueAttribute().getHandle(), gNotifyData, gNotifyLen);
            gNotifyLen = 0;
        }
    }

}