Experimental BLE project showing how IO can be made with an App over BLE. Pointer to matching App will be added when ready, initially this works with: - Android App [nRF-Master Control Panel], supports Write,Read,Notify - Android Project [BluetoothLeGatt]
Dependencies: BLE_API mbed nRF51822
This is an experimental project for BLE (Bluetooth LE == Bluetooth Low Energy == Bluetooth Smart).
- It supports general IO over BLE with Read/Notify/Write support.
- It is compatible with FOTA using Android App "nRF Master Control Panel" (20150126)
- IO supported by:
- Custom Android App is in the WIKI under: Android-App, developed from Android Sample "BluetoothLeGatt"
- Android App: nRF-MCP (Master Control Panel)
- iOS App LightBlue.
- General HRM, HTM, Battery and similar apps should be able to access the matching services.
- It includes combinations of code from other projects, alternative code included can be tried by moving comments (, //)
- 20150126 bleIO r25: It compiles for both "Nordic nRF51822" and "Nordic nRF51822 FOTA" platforms
- 20150126 The matching bleIO App (in wiki) doesn't support FOTA yet, use Android App "nRF Master Control Panel"
Feedback and ideas greatly appreciated!!!
Diff: main.cpp
- Revision:
- 11:7d02fe5ebea5
- Parent:
- 10:ee3a359f7d3f
- Child:
- 12:8bac5f5d3a3e
--- a/main.cpp Fri Dec 19 22:37:29 2014 +0000 +++ b/main.cpp Sun Dec 21 16:26:46 2014 +0000 @@ -25,8 +25,8 @@ // - Handle All return codes for all functions not void, including BLE not BLE_ERROR_NONE, as a minimum output some debug and log event in non-volatile memory for diagnostics. // - Re-check where voltatile needed // - Evaluate setting: IS_SRVC_CHANGED_CHARACT_PRESENT, see: https://devzone.nordicsemi.com/question/22751/nrftoobox-on-android-not-recognizing-changes-in-application-type-running-on-nordic-pcb/?answer=23097#post-id-23097 -// - delete all code with "//x " prefix as that was just for notes and doesn't actually work -// - change all valid code temporarily commended to "//a " prefix to indicate alternnative is valid code +// - invalid or untested reference code commented with "//x ", feel free to delete or experiment +// - valid alternate code commented with "//a " prefix to indicate alternnative is valid code //==========End of PR's Header //==========Historic Licencing from original imported sample from mbed website [BLE_HeartRate] ========== @@ -47,8 +47,8 @@ //==========Compile Options========== -#define ENABLE_SerialUSB_DEBUG_CONSOLE 1 //PR: Enable Debug on mbed's USB Serial Debug, Setup: Serial 9600,8,N,1,NoFlowControl (TeraTerm: http://en.sourceforge.jp/projects/ttssh2/releases/) -#define UPDATE_PARAMS_FOR_LONGER_CONNECTION_INTERVAL 0 //PR: Option to slow the connection intervsal possibly saving power (After Connected) +#define ENABLE_uSerial 1 //PR: Enable Debug on mbed's USB Serial Debug, Setup: Serial 9600,8,N,1,NoFlowControl (TeraTerm: http://en.sourceforge.jp/projects/ttssh2/releases/) +//x #define ENABLE_BLE_SLOW 0 //PR: Option to slow the connection interval possibly saving power (After Connected) -- Imported feature never tested //==========Includes========== #include "mbed.h" // mbed @@ -65,11 +65,12 @@ //#include "UARTService.h" //TODO: Add a UART Channel for streaming data like logs? //==========Debug Console========== -#if ENABLE_SerialUSB_DEBUG_CONSOLE - //Restart TeraTerm just before Pressing Reset on mbed, Default:9600-8N1(No Flow Control) +#if ENABLE_uSerial + //Restart TeraTerm just before Pressing Reset on mbed, Default Serual=9600-8N1(No Flow Control) //Using default baud rate to avoid issues with DEBUG in a constructor being at default baud rate before main() - Serial debug_serial(USBTX, USBRX); //PR: DebugSerialOverMbedUSB - #define DEBUG(...) { debug_serial.printf(__VA_ARGS__); } + //TeraTerm: http://en.sourceforge.jp/projects/ttssh2/releases/ + Serial debug_userial(USBTX, USBRX); //PR: DebugSerialOverMbedUSB + #define DEBUG(...) { debug_userial.printf(__VA_ARGS__); } #else #define DEBUG(...) //Do Nothing, DEBUG() lines are ignored #endif @@ -81,16 +82,29 @@ } const bool bPrep = u8_prep_dummy(); -//==========LEDs========== -//DigitalOut out_led1(LED1); //PR: Firmware heartbeat -//DigitalOut out_led2(LED2); //PR: Firmware heartbeat -PwmOut pwm_led1(LED1); //PR: Firmware Life Indicator -PwmOut pwm_led2(LED2); //TODO: Controlled by App -float f_led1level = 0.1; //Initial Brightness (Typically 0.0~0.5) -float f_led2level = 0.1; //Initial Brightness (Typically 0.0~0.5) +//========== IO Hardware: Buttons, LEDs, PWM ========== +// Inputs: +DigitalIn bB1in(BUTTON1); //if(bB1in){} +DigitalIn bB2in(BUTTON2); //if(bB2in){} +InterruptIn B1int(BUTTON1); //B1int.rise(&onB1rise); +InterruptIn B2int(BUTTON2); //B1int.fall(&onB1fall); + +// Outputs: +//DigitalOut bL1out(LED1); // Direct LED1 drive +//DigitalOut bL2out(LED2); // Direct LED2 drive +PwmOut fL1pwm(LED1); float fL1level = 0.1; // PWM LED1, brightness=float(0.0~1.0) +PwmOut fL2pwm(LED2); float fL2level = 0.1; // PWM LED2, brightness=float(0.0~1.0) + +// onButton Callbacks for InterruptIn +// *When direct driving hardware consider adjusting drive within the onCallback to reduce response time +//TODO: Check need of volatile for changes in interrupts affecting variables in non-interrupt code? +volatile uint8_t uB1rise; void onB1rise(void){ uB1rise++; };// Flag Event, Counter helps detect missed events since last cleared +volatile uint8_t uB1fall; void onB1fall(void){ uB1fall++; };// Flag Event, Counter helps detect missed events since last cleared +volatile uint8_t uB2rise; void onB2rise(void){ uB2rise++; };// Flag Event, Counter helps detect missed events since last cleared +volatile uint8_t uB2fall; void onB2fall(void){ uB2fall++; };// Flag Event, Counter helps detect missed events since last cleared //==========BLE========== -const static char pcDeviceName[] = "bleIO"; //PR: Why can App nRF-MCP modify this even though flagged as Const, maybe only temporary mod till App restarts? +const static char pcDeviceName[] = "bleIOv04_pr"; //PR: Why can App nRF-MCP modify this even though flagged as Const, maybe only temporary mod till App restarts? BLEDevice ble; //Pointers to services for accesses outside main() HealthThermometerService *pServiceHTM; //tempChar.getValueAttribute().getHandle() @@ -123,7 +137,7 @@ //a uint8_t puUUID128_Bluetooth_Base_Rev[16] = {0xFB,0x34,0x9B,0x5F,0x80,0, 0,0x80, 0,0x10, 0,0, 0x00,0x00, 0,0};//0000****-0000-1000-8000 00805F9B34FB, where ****==UUID16 //a uint8_t puUUID128_BatteryService[16] = {0xFB,0x34,0x9B,0x5F,0x80,0, 0,0x80, 0,0x10, 0,0, 0x0F,0x18, 0,0};//0000****-0000-1000-8000 00805F9B34FB, where ****==0x180F -//a //UUID16 List - Advertising these required by App nRF-Toolbox:HRM&HTM // Keep list short so advertizing not too long. +//a //UUID16 List - Advertising these required by App nRF-Toolbox:HRM&HTM // Keep list short so advertizing not over 31bytes. /*static const uint16_t uuid16_list[] = { //Service List (Pre-defined standard 16bit services) // *Order here doesn't affect order in nRF-MCP Discovery of Services //BLE_UUID_GAP UUID_GENERIC_ACCESS //0x1800 //Included by Default, DeviceName, Appearance, PreferredConnectionParam @@ -155,7 +169,7 @@ // 20141218PR: [4d3281c0-86d1-11e4-b084-0002a5d5c51b] == OID[2.25.102612802202735078710424021169255859483] // Base UUID128 (1 digit modified):{0x4d,0x32,0x81,0xc0,0x86,0xd1,0x11,0xe4,0xb0,0x84,0x00,0x02,0xa5,0xd5,0xc5,0x1*};// NormalOrder uint8_t puUUID128_IOService[16] = {0x4d,0x32,0x81,0xc0,0x86,0xd1,0x11,0xe4,0xb0,0x84,0x00,0x02,0xa5,0xd5,0xc5,0x10};// Service UUID - uint8_t puUUID128_IOAdvertise[16]= {0x10,0xc5,0xd5,0xa5,0x02,0x00,0x84,0xb0,0xe4,0x11,0xd1,0x86,0xc0,0x81,0x32,0x4d};// Advertising Service UUID (=FlippedOrder) + uint8_t puUUID128_IOAdvertise[16]= {0x10,0xc5,0xd5,0xa5,0x02,0x00,0x84,0xb0,0xe4,0x11,0xd1,0x86,0xc0,0x81,0x32,0x4d};// Advertising Service UUID (=ReversedOrder) // Characteristic UUID: Initially using generic blocks of IO data that may be manually dividied up into Buttons, LEDs, PWM, ADC, etc. uint8_t puUUID128_IOw8[16] = {0x4d,0x32,0x81,0xc0,0x86,0xd1,0x11,0xe4,0xb0,0x84,0x00,0x02,0xa5,0xd5,0xc5,0x11};// Write[8byte] to mbed from phone uint8_t puUUID128_IOr4[16] = {0x4d,0x32,0x81,0xc0,0x86,0xd1,0x11,0xe4,0xb0,0x84,0x00,0x02,0xa5,0xd5,0xc5,0x12};// Read[4byte] from mbed to phone @@ -168,7 +182,6 @@ //a uint8_t puUUID128_bleIn1[16] = {0x1X,0xc5,0xd5,0xa5,0x02,0x00,0x84,0xb0,0xe4,0x11,0xd1,0x86,0xc0,0x81,0x32,0x4d};// Input 1bit //a uint8_t puUUID128_bleInADC12[16] = {0x1X,0xc5,0xd5,0xa5,0x02,0x00,0x84,0xb0,0xe4,0x11,0xd1,0x86,0xc0,0x81,0x32,0x4d};// Input ADC 12bit - /* Android: //bleIO Service and Characteristics attributes.put("4d3281c0-86d1-11e4-b084-0002a5d5c51b", "bleIO base"); @@ -203,7 +216,7 @@ GattService ServiceIO(puUUID128_IOService, pCustomCharacteristics, (sizeof(pCustomCharacteristics)/sizeof(pCustomCharacteristics[0]))); GattService *pServiceIO= &ServiceIO; // Pointer to Service -//a Option: Allow device to modify its settings and inform host (Typically the device wouldn't change a host only setting) +//a Option: Allow device to modify its settings and inform host (Some devices may be able to self-modify host written settings) /*void vUpdate_IOw8(void) // Handle in device changes to w8 characteristic { static uint8_t uw8; //Level: 0..2 @@ -217,8 +230,8 @@ //pServiceHRM->updateHeartRate( u8_hrm );// Update Characteristic so sent by BLE }*/ -void vUpdate_IOr4(void) // Handle in device changes to r4 characteristic -{ +void vUpdate_IOr4(void) // Handle device updates r4 characteristics (Without Notify, Host must poll for these) +{ // For out project this would be: Temperature, Version Info, etc. static uint8_t ur4; //Level: 0..2 switch(++ur4){ case 2: memcpy(pIOr4, "r2r2", sizeof(pIOr4)); DEBUG(" r4c"); break; @@ -228,8 +241,8 @@ ble.updateCharacteristicValue(IOr4.getValueHandle(), pIOr4, sizeof(pIOr4)); } -void vUpdate_IOn4(void) // Handle in device changes to n4 characteristic -{ +void vUpdate_IOn4(void) // Handle device updates n4 characteristics (With notify) +{ // For our Device this would be: Dispenser count changes, Button Activity, Empty warning, Timeouts, etc. static uint8_t un4; //Level: 0..2 switch(++un4){ case 2: memcpy(pIOn4, "n2n2", sizeof(pIOr4)); DEBUG(" n4c"); break; @@ -264,24 +277,23 @@ { DEBUG("\nBLEi: Callback_BLE_Connect(Handle:%d, eType:%d, Add:%u ...)\n", tHandle, ePeerAddrType, c6PeerAddr); - #if UPDATE_PARAMS_FOR_LONGER_CONNECTION_INTERVAL //PR: Adopted optional code never tested - /* Updating connection parameters can be attempted only after a connection has been - * established. Please note that the ble-Central is still the final arbiter for - * the effective parameters; the peripheral can only hope that the request is - * honored. Please also be mindful of the constraints that might be enforced by - * the BLE stack on the underlying controller.*/ - #define MIN_CONN_INTERVAL 250 /**< Minimum connection interval (250 ms) */ - #define MAX_CONN_INTERVAL 350 /**< Maximum connection interval (350 ms). */ - #define CONN_SUP_TIMEOUT 6000 /**< Connection supervisory timeout (6 seconds). */ - #define SLAVE_LATENCY 4 - - Gap::ConnectionParams_t tGap_conn_params; - tGap_conn_params.minConnectionInterval = Gap::MSEC_TO_GAP_DURATION_UNITS(MIN_CONN_INTERVAL); - tGap_conn_params.maxConnectionInterval = Gap::MSEC_TO_GAP_DURATION_UNITS(MAX_CONN_INTERVAL); - tGap_conn_params.connectionSupervisionTimeout = Gap::MSEC_TO_GAP_DURATION_UNITS(CONN_SUP_TIMEOUT); - tGap_conn_params.slaveLatency = SLAVE_LATENCY; - ble.updateConnectionParams(tHandle, &tGap_conn_params); - #endif // #if UPDATE_PARAMS_FOR_LONGER_CONNECTION_INTERVAL + //x #if ENABLE_BLE_SLOW //UPDATE_PARAMS_FOR_LONGER_CONNECTION_INTERVAL //PR: Adopted optional code never tested + //x // Updating connection parameters can be attempted only after a connection has been + //x // established. Please note that the ble-Central is still the final arbiter for + //x // the effective parameters; the peripheral can only hope that the request is + //x // honored. Please also be mindful of the constraints that might be enforced by + //x // the BLE stack on the underlying controller. + //x #define MIN_CONN_INTERVAL 250 // Minimum connection interval (250 ms) + //x #define MAX_CONN_INTERVAL 350 // Maximum connection interval (350 ms) + //x #define CONN_SUP_TIMEOUT 6000 // Connection supervisory timeout (6 seconds) + //x #define SLAVE_LATENCY 4 + //x Gap::ConnectionParams_t tGap_conn_params; + //x tGap_conn_params.minConnectionInterval = Gap::MSEC_TO_GAP_DURATION_UNITS(MIN_CONN_INTERVAL); + //x tGap_conn_params.maxConnectionInterval = Gap::MSEC_TO_GAP_DURATION_UNITS(MAX_CONN_INTERVAL); + //x tGap_conn_params.connectionSupervisionTimeout = Gap::MSEC_TO_GAP_DURATION_UNITS(CONN_SUP_TIMEOUT); + //x tGap_conn_params.slaveLatency = SLAVE_LATENCY; + //x ble.updateConnectionParams(tHandle, &tGap_conn_params); + //x #endif // #if UPDATE_PARAMS_FOR_LONGER_CONNECTION_INTERVAL } static volatile bool bSent = false; //Volatile, don't optimize, changes under interrupt control @@ -326,12 +338,12 @@ if (tHandle == IOw8.getValueHandle()) { // This occurs from Write by App nRF-MCP since has Write Property set ble.readCharacteristicValue(tHandle, pIOw8, &uLen); DEBUG(" IOw8[%d]:%02X %02X %02X %02X %02X %02X %02X %02X\n", uLen, pIOw8[0], pIOw8[1], pIOw8[2], pIOw8[3],pIOw8[4], pIOw8[5], pIOw8[6], pIOw8[7]); - //TODO: Update Outputs - //} else if (tHandle == IOr4.getValueHandle()) { + //TODO: Update Outputs and settings: Direct LED Control or Output Control, Or Configuration (Includes flags for: Factory Reset, Enter Test Mode, Enter Demo Mode) + //} else if (tHandle == IOr4.getValueHandle()) { // Readonly, shouldn't occur // ble.readCharacteristicValue(tHandle, pIOr4, &uLen); // DEBUG(" IOr4[%d]:%02X %02X %02X %02X\n", uLen, pIOr4[0], pIOr4[1], pIOr4[2], pIOr4[3]); // //TODO: Update Outputs - //} else if (tHandle == IOn4.getValueHandle()) { + //} else if (tHandle == IOn4.getValueHandle()) { // Readonly, shouldn't occur // ble.readCharacteristicValue(tHandle, pIOn4, &uLen); // DEBUG(" IOn4[%d]:%02X %02X %02X %02X\n", uLen, pIOn4[0], pIOn4[1], pIOn4[2], pIOn4[3]); // //TODO: Update Outputs @@ -355,8 +367,8 @@ void Callback_BLE_onUpdatesEnabled(Gap::Handle_t tHandle) { //Triggered when click the UpdateContinuousIcon in nRF-MCP(ThreeDownArrows) if (tHandle == IOn4.getValueHandle()) { DEBUG(" onUpdates(Handle:%d) Enabled - IOn4\n", tHandle); //This Occurs when selected by App nRF-MCP as has Notify property set - //} else if (tHandle == IOr4.getValueHandle()) { DEBUG(" onUpdates(Handle:%d) Enabled - IOr4\n", tHandle); - //} else if (tHandle == IOw8.getValueHandle()) { DEBUG(" onUpdates(Handle:%d) Enabled - IOw8\n", tHandle); + //} else if (tHandle == IOr4.getValueHandle()) { DEBUG(" onUpdates(Handle:%d) Enabled - IOr4\n", tHandle); // Notify not enabled on this Characteristic + //} else if (tHandle == IOw8.getValueHandle()) { DEBUG(" onUpdates(Handle:%d) Enabled - IOw8\n", tHandle); // Notify not enabled on this Characteristic } else { DEBUG(" onUpdates(Handle:%d) Enabled - Characteristic?\n", tHandle); } } @@ -417,22 +429,24 @@ } //==========Functions:Timer========== -static volatile bool b_Ticker1 = false;//Volatile, don't optimize, changes under interrupt control +static volatile uint8_t uTicker1;//Volatile, don't optimize, changes under interrupt control void CallbackTicker1(void) { - static uint32_t u32_Counter; // Counter for Debug Output - f_led1level+=0.1; if (f_led1level>0.5){f_led1level = 0.1;}; pwm_led1=f_led1level;//PR: Ramp Blink - DEBUG("\nBLEi: Ticker1(%u) ", ++u32_Counter); - b_Ticker1 = true; //PR: Flag to handle Ticker1 Event in Main loop so interupts not blocked. + static uint32_t u32_Counter; // Counter for checking Timing + DEBUG("\nBLEi: Ticker(%04u) ", ++u32_Counter); + fL1level+=0.1; if (fL1level>0.5){fL1level = 0.1;}; fL1pwm=fL1level;//PR: Ramp Blink + uTicker1++; // Flag event, using counter so can detect missed events } + //==========main========== int main(void) { - f_led1level = 1; pwm_led1 = f_led1level;//Start LED1=OnMax - f_led2level = 1; pwm_led2 = f_led2level;//Start LED2=OnMax + fL1level = 1; fL1pwm = fL1level;//Start LED1=OnMax + fL2level = 1; fL2pwm = fL2level;//Start LED2=OnMax //Restart TeraTerm just before Pressing Reset on mbed, 9600-8N1(No Flow Control) DEBUG("\nBLE: ___%s___\n", pcDeviceName); + DEBUG(" Built:[ %s %s ] Compiler:[ %s ]\n", __DATE__, __TIME__, __VERSION__); DEBUG("BLE: Connect App for Data: nRF-MCP, nRF-Toolbox:HRM/HTM, Android Sample BluetoothLeGatt, etc.\n"); DEBUG("BLE: BluetoothLeGatt: App->mbed: LinkLoss->AlertLevel(UpArrow)\n"); DEBUG("BLE: BluetoothLeGatt: App->mbed: BatteryService->BatteryLevel(DownArrow)\n"); @@ -484,8 +498,8 @@ ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE); //PR: BLE Only (Option:LE_LIMITED_DISCOVERABLE) // Does "LE_GENERAL_DISCOVERABLE" affect whether UUID needs to be advertised to discover services? //a ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list)); // Multiple UUID16 for Standard Services - ble.accumulateAdvertisingPayload(GapAdvertisingData::INCOMPLETE_LIST_128BIT_SERVICE_IDS, (const uint8_t *)puUUID128_IOService, sizeof(puUUID128_IOService)); //Single UUID128 for primary Service - + ble.accumulateAdvertisingPayload(GapAdvertisingData::INCOMPLETE_LIST_128BIT_SERVICE_IDS, (const uint8_t *)puUUID128_IOAdvertise, sizeof(puUUID128_IOAdvertise)); //Single UUID128 for primary Service + //? PR: I'm not sure what these lines do, they were inherited from an example: //? ble.accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_HEART_RATE_SENSOR); //? ble.accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_THERMOMETER); @@ -505,11 +519,18 @@ // = Len07 Type02 Value 0918 0A18 0D18 (UUID16: 1809 180A 180D ) // = LocalName field wasn't appended as insufficient space, so Device won't be named when scanning. + // Setup InterruptIn for Buttons + DEBUG("BLE: Enable Button Interrupts\n"); + B1int.fall(&onB1fall);// Button1 Press + B1int.rise(&onB1rise);// Button1 Release + B2int.fall(&onB2fall);// Button2 Press + B2int.rise(&onB2rise);// Button2 Release + DEBUG("BLE: Main Loop\n"); uint32_t u32_wakeevents=0, u32_wakelast=0; // Counter&Register for tracking Wake Events (to see monitor their Frequency) while (true) { - if (b_Ticker1 && ble.getGapState().connected) { //If Ticker1 and Connected Update Data - b_Ticker1 = false; // Clear flag for next Ticker1, see CallbackTicker1() + if (uTicker1 && ble.getGapState().connected) { //If Ticker1 and Connected then Update Appropriate Data + uTicker1 = 0; // Clear flag for next Ticker1, see CallbackTicker1(), TODO: log if missed any ticks // Read Sensors, and update matching Service Characteristics (only if connected) // *Order here doesn't affect order in nRF-MCP Discovery of Services @@ -525,16 +546,16 @@ DEBUG(" BLE:Wakes:%u,Delta:%u ", u32_wakeevents, u32_wakeevents-u32_wakelast); //For Evaluating Timing u32_wakelast = u32_wakeevents; - } else if (b_Ticker1) { - b_Ticker1 = false; // Clear flag for next Ticker1, see CallbackTicker1() + } else if (uTicker1) { + uTicker1 = 0; // Clear flag for next Ticker1, see CallbackTicker1(), TODO: log if missed any ticks DEBUG("BLE: Tick while unconnected "); } else if (bSent){ bSent=false; //clear flag //DEBUG("BLE: Sent %ubytes ", uSentBLE); } else { - //DEBUG("BLE: Wait for Event\n\r"); //x Debug output here causes unnecessary wakes resulting in endless awakes. - ble.waitForEvent(); //PR: Wait for event - Yield control to BLE Stack and other events (Process ALL pending events before waiting again) - f_led2level+=0.25; if (f_led2level>0.5){f_led2level = 0.0;}; pwm_led2=f_led2level;//PR: Ramp Blink + //DEBUG("BLE: Wait for Event\n\r"); //x Debug output here causes endless wakes from sleep() + ble.waitForEvent(); //PR: Like sleep() with ble handling. TODO: Handle Error return + fL2level+=0.25; if (fL2level>0.5){fL2level = 0.0;}; fL2pwm=fL2level;//PR: Ramp Blink u32_wakeevents++; //PR: Count events for frequency monitoring (20141207PR: nRF51822 mbed HRM = 50Hz) } }