Bluetooth BLE HID Keyboard for the AlterErgo device, based on Seeed Studio Tiny BLE.

Dependencies:   BLE_API BLE_HID mbed nRF51822

Fork of BLENano_HID by Yuuichi Akagawa

Files at this revision

API Documentation at this revision

Comitter:
shervinemami
Date:
Sun Aug 26 10:19:01 2018 +0000
Parent:
0:3435302551b3
Commit message:
Initial working version of keyboard-only BLE HID

Changed in this revision

BLE_API.lib Show annotated file Show diff for this revision Revisions of this file
battery.h Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
mbed.bld Show annotated file Show diff for this revision Revisions of this file
nRF51822.lib Show annotated file Show diff for this revision Revisions of this file
tiny_ble.h Show annotated file Show diff for this revision Revisions of this file
diff -r 3435302551b3 -r c6659c8882c9 BLE_API.lib
--- a/BLE_API.lib	Sat Feb 27 05:08:02 2016 +0000
+++ b/BLE_API.lib	Sun Aug 26 10:19:01 2018 +0000
@@ -1,1 +1,1 @@
-http://mbed.org/teams/Bluetooth-Low-Energy/code/BLE_API/#ff83f0020480
+http://mbed.org/teams/Bluetooth-Low-Energy/code/BLE_API/#65474dc93927
diff -r 3435302551b3 -r c6659c8882c9 battery.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/battery.h	Sun Aug 26 10:19:01 2018 +0000
@@ -0,0 +1,43 @@
+
+
+#ifndef __BATTERY_H__
+#define __BATTERY_H__
+
+#include "mbed.h"
+
+class Battery {
+public:
+    Battery(PinName pin) {
+        uint32_t n = (uint32_t) pin;
+        channel = 1 << (1 + n);
+    }
+    
+    float read() {
+        uint32_t pre_enable_register = NRF_ADC->ENABLE;
+        uint32_t pre_config_register = NRF_ADC->CONFIG;
+        
+        
+        NRF_ADC->ENABLE = ADC_ENABLE_ENABLE_Enabled;
+        NRF_ADC->CONFIG = (ADC_CONFIG_RES_10bit << ADC_CONFIG_RES_Pos) |
+                          (ADC_CONFIG_INPSEL_AnalogInputNoPrescaling << ADC_CONFIG_INPSEL_Pos) |
+                          (ADC_CONFIG_REFSEL_VBG << ADC_CONFIG_REFSEL_Pos) |
+                          (channel << ADC_CONFIG_PSEL_Pos) |
+                          (ADC_CONFIG_EXTREFSEL_None << ADC_CONFIG_EXTREFSEL_Pos);
+                          
+        NRF_ADC->TASKS_START = 1;
+        while (((NRF_ADC->BUSY & ADC_BUSY_BUSY_Msk) >> ADC_BUSY_BUSY_Pos) == ADC_BUSY_BUSY_Busy) {
+        } 
+        
+        uint16_t value = NRF_ADC->RESULT;
+        
+        NRF_ADC->ENABLE = pre_enable_register;
+        NRF_ADC->CONFIG = pre_config_register;
+        
+        return (float)value * (1.0f / (float)0x3FF) * 1.2 * 12.2 / 2.2;    
+    }
+    
+private:
+    uint32_t channel;
+};
+
+#endif // __BATTERY_H__
diff -r 3435302551b3 -r c6659c8882c9 main.cpp
--- a/main.cpp	Sat Feb 27 05:08:02 2016 +0000
+++ b/main.cpp	Sun Aug 26 10:19:01 2018 +0000
@@ -1,3 +1,76 @@
+/**
+ * Handheld BLE finger input device based on Seeed Studio Tiny BLE (TinyBLE).
+ * By Shervin Emami (http://shervinemami.info/), Apr 2018.
+ * If the analog voltage on a "finger" pin is below a threshold voltage,
+ * then it sends a rare keypress using Bluetooth, so that any computer or
+ * smartphone can be configured to perform a desired function due to this input event.
+ *
+ * 
+ * This program implements a complete HID-over-Gatt Profile:
+ *  - HID is provided by KeyboardService
+ *  - Battery Service
+ *  - Device Information Service
+ *
+ * Complete strings can be sent over BLE using printf. Please note, however, than a 12char string
+ * will take about 500ms to transmit, principally because of the limited notification rate in BLE.
+ * KeyboardService uses a circular buffer to store the strings to send, and calls to putc will fail
+ * once this buffer is full. This will result in partial strings being sent to the client.
+ 
+ * Tested with mbed libraries for April 2nd, 2018. If you use a newer version of mbed or other
+ * libs and you have problems, try rolling back to April 2nd, 2018.
+ */ 
+
+// Configure the settings of my board.
+#include "tiny_ble.h"   // Seeed Studio Tiny BLE
+
+
+// Define LOG_MESSAGES if you want various debug messages to be sent to the PC via a UART cable.
+#define LOG_MESSAGES
+
+// Various USB Keyboard keycodes that we can send.
+#define KEYCODE_F4  131
+#define KEYCODE_F5  132
+#define KEYCODE_F6  133
+#define KEYCODE_F7  134
+#define KEYCODE_F8  135
+#define KEYCODE_F9  136
+#define KEYCODE_F10 137
+#define KEYCODE_F11 138
+#define KEYCODE_F12 139
+#define KEYCODE_HOME     145
+#define KEYCODE_PAGEUP   146
+#define KEYCODE_PAGEDOWN 147
+#define KEYCODE_RIGHT    148
+#define KEYCODE_LEFT     149
+#define KEYCODE_DOWN     150
+#define KEYCODE_UP       151
+#define KEYCODE_ESCAPE   157
+#define KEYCODE_RIGHTCLK 188
+#define KEYCODE_ACCENT    96
+#define KEYCODE_NUMLOCK  193
+
+
+
+
+const int FING1_KEYCODE = KEYCODE_F8;         // Set the keypress that will be sent over bluetooth.
+const int FING2DOWN_KEYCODE = KEYCODE_F4;         //
+const int FING2UP_KEYCODE = KEYCODE_NUMLOCK;
+const int FING3_KEYCODE = KEYCODE_UP;         //
+const int FING4_KEYCODE = KEYCODE_DOWN;         //
+
+const float FINGER1_PRESS = 0.42f;   // Set how much the person needs to press their finger in to trigger a keypress.
+const float FINGER2_PRESS = 0.41f;   // Note that each finger is slightly different strength, due to the mechanical design.
+const float FINGER3_PRESS = 0.38f;   //
+const float FINGER4_PRESS = 0.37f;   //
+
+const float FINGER1_RELEASE = 0.35f;   // Set how much the person needs to release their finger to finish a keypress.
+const float FINGER2_RELEASE = 0.35f;   // Note that each finger is slightly different strength, due to the mechanical design.
+const float FINGER3_RELEASE = 0.34f;   //
+const float FINGER4_RELEASE = 0.34f;   //
+
+const float BATTERY_NEEDS_CHARGE = 0.198f;  // 0.20 means >= 3.4V, 0.19 means >= 2.4V.
+
+ 
 /* mbed Microcontroller Library
  * Copyright (c) 2015 ARM Limited
  *
@@ -20,79 +93,146 @@
 #include "KeyboardService.h"
  
 #include "examples_common.h"
- 
-/**
- * This program implements a complete HID-over-Gatt Profile:
- *  - HID is provided by KeyboardService
- *  - Battery Service
- *  - Device Information Service
- *
- * Complete strings can be sent over BLE using printf. Please note, however, than a 12char string
- * will take about 500ms to transmit, principally because of the limited notification rate in BLE.
- * KeyboardService uses a circular buffer to store the strings to send, and calls to putc will fail
- * once this buffer is full. This will result in partial strings being sent to the client.
- */
- 
-DigitalOut waiting_led(LED1);
-DigitalOut connected_led(LED2);
- 
-InterruptIn button1(D2);
+
+
+
+#undef HID_DEBUG
+#undef LOG
+#ifdef LOG_MESSAGES
+    #define LOG(...)    { pc.printf(__VA_ARGS__); }
+    Serial pc(UART_TX, UART_RX);
+#else
+    #define LOG(...)
+#endif
+#define HID_DEBUG   LOG
+
+
+AnalogIn    battery(BATTERY_PIN);   // For measuring the battery level
+AnalogIn    finger1(FINGER1_PIN);   // For measuring a finger level
+AnalogIn    finger2(FINGER2_PIN);   //
+AnalogIn    finger3(FINGER3_PIN);   //
+AnalogIn    finger4(FINGER4_PIN);   //
+DigitalOut waiting_led(LED_RED);      // For showing BLE is trying to connect
+DigitalOut connected_led(LED_GREEN);  // For showing BLE is connected
+DigitalOut status_led(LED_BLUE);      // For showing BLE is busy
+DigitalOut enable_out3v3(OUT3V3_PIN); // For using OUT_3V3 pin of TinyBLE
+
+#define LED_ON   0      // LEDs on Tiny BLE need 0V to light up.
+#define LED_OFF  1      //
+
+
+bool haveAskedForRecharge = false;  // Only ask for a recharge once per poweron. Requires user to turn device off once per day!
+
+
+InterruptIn button1(BUTTON_PIN);    // For sending a BLE command
  
 BLE ble;
 KeyboardService *kbdServicePtr;
  
-static const char DEVICE_NAME[] = "uKbd";
-static const char SHORT_DEVICE_NAME[] = "kbd1";
- 
+static const char DEVICE_NAME[] = "ShervFingerControl";
+static const char SHORT_DEVICE_NAME[] = "ShFingerCtl";
+
+
+// Status flags that are updated in ISR callback functions.
+volatile bool check_fingers = false;
+volatile bool press_keyup[5] = {false};
+
+
+
 static void onDisconnect(const Gap::DisconnectionCallbackParams_t *params)
 {
-    HID_DEBUG("disconnected\r\n");
-    connected_led = 0;
+    HID_DEBUG("discon\n\r");
+    waiting_led = LED_ON;
+    connected_led = LED_OFF;
  
     ble.gap().startAdvertising(); // restart advertising
 }
  
 static void onConnect(const Gap::ConnectionCallbackParams_t *params)
 {
-    HID_DEBUG("connected\r\n");
-    waiting_led = false;
+    HID_DEBUG("conn\n\r");
+    waiting_led = LED_OFF;
+    connected_led = LED_ON;
 }
+
+
+
+void send_keypress(int keycode, int finger) {
+    if (!kbdServicePtr)
+        return;
  
-static void waiting() {
-    if (!kbdServicePtr->isConnected())
-        waiting_led = !waiting_led;
-    else
-        connected_led = !connected_led;
+    if (!kbdServicePtr->isConnected()) {
+        HID_DEBUG("no conn yet\n\r");
+    } else {
+        //LOG("send_keypress(%d, %d)\n\r", keycode, finger);
+        kbdServicePtr->keyDownCode(keycode, 0);
+        press_keyup[finger] = true;
+        
+    }
 }
- 
+
 void send_string(const char * c) {
     if (!kbdServicePtr)
         return;
  
     if (!kbdServicePtr->isConnected()) {
-        HID_DEBUG("we haven't connected yet...");
+        HID_DEBUG("no conn yet\n\r");
     } else {
         int len = strlen(c);
         kbdServicePtr->printf(c);
-        HID_DEBUG("sending %d chars\r\n", len);
+        HID_DEBUG("sending %d chars\n\r", len);
     }
 }
+
+
+// Call the button1_ISR function whenever the pushbutton is pressed.
+// Make sure there isn't anything slow like printf in this ISR!
+void button1_ISR() {
+    send_string("x\n");
+}
+
+
+// ISR callback function that is automatically called every 0.1 seconds or so.
+// Make sure there isn't anything slow like printf in this ISR!
+static void heartbeat_ISR() {
+    if (!kbdServicePtr->isConnected())
+        waiting_led = !waiting_led;
+    else {
+        //connected_led = !connected_led;
+        //waiting_led = 0;
+    }
+
+    // Signal that we should check the finger sensors soon.
+    check_fingers = true;
+}
  
-void send_stuff() {
-    send_string("hello world!\n");
-}
  
 int main()
 {
-    Ticker heartbeat;
- 
-    button1.rise(send_stuff);
+#ifdef LOG_MESSAGES
+    pc.baud(115200);  // Use fast UART for log messages instead of default 9600 bps.
+#endif
+    
+    waiting_led = LED_OFF;    // Set LEDs to blue, until ready.
+    connected_led = LED_OFF;
+    status_led = LED_ON;
+    
+    wait(1);    
+    LOG("---- Shervin's Finger Input device, using TinyBLE + BLE HID keyboard service ----\n\r");
+
+    // Call the button1_ISR function whenever the pushbutton is pressed.
+    button1.rise(button1_ISR);
  
-    HID_DEBUG("initialising ticker\r\n");
+    HID_DEBUG("initialising ticker\n\r");
+
+    // Call the heartbeat_ISR function every 0.1 seconds 
+    Ticker heartbeat;
+    heartbeat.attach(heartbeat_ISR, 0.1f);
+
+    HID_DEBUG("enabling finger sensors\n\r");
+    enable_out3v3 = 1;
  
-    heartbeat.attach(waiting, 1);
- 
-    HID_DEBUG("initialising ble\r\n");
+    HID_DEBUG("initialising ble\n\r");
     ble.init();
  
     ble.gap().onDisconnection(onDisconnect);
@@ -100,24 +240,146 @@
  
     initializeSecurity(ble);
  
-    HID_DEBUG("adding hid service\r\n");
+    HID_DEBUG("adding hid service\n\r");
     KeyboardService kbdService(ble);
     kbdServicePtr = &kbdService;
  
-    HID_DEBUG("adding device info and battery service\r\n");
+    HID_DEBUG("adding device info and battery service\n\r");
     initializeHOGP(ble);
  
-    HID_DEBUG("setting up gap\r\n");
+    HID_DEBUG("setting up gap\n\r");
     ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::KEYBOARD);
     ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME,
                                            (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
     ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::SHORTENED_LOCAL_NAME,
                                            (uint8_t *)SHORT_DEVICE_NAME, sizeof(SHORT_DEVICE_NAME));
  
-    HID_DEBUG("advertising\r\n");
+    HID_DEBUG("advertising\n\r");
     ble.gap().startAdvertising();
- 
+
+    // Turn LEDs to green connected.
+    connected_led = LED_ON;
+    waiting_led = LED_OFF;
+    status_led = LED_OFF;
+
+    // Run forever ...
+    int counter1 = 0; 
+    int counter2 = 0; 
     while (true) {
         ble.waitForEvent();
+        
+        // Signal that we should check the finger sensors soon.
+        if (check_fingers) {
+            check_fingers = false;
+
+            // Measure all the finger sensors and battery level.
+            float fing1 = finger1.read();
+            float fing2 = finger2.read();
+            float fing3 = finger3.read();
+            float fing4 = finger4.read();
+            float batt = battery.read();
+            
+            // If a finger was pressed and now has been released, send the keyUp event soon after.
+            if (press_keyup[1] && fing1 < FINGER1_RELEASE) {
+                press_keyup[1] = false;    // Clear the flag
+                kbdServicePtr->keyUpCode();
+                HID_DEBUG("sent key up for 1.\n\r");
+            }
+            if (press_keyup[2] && fing2 < FINGER2_RELEASE) {
+                press_keyup[2] = false;    // Clear the flag
+                send_keypress(FING2UP_KEYCODE, 2);
+                HID_DEBUG("sent final key down for 2.\n\r");
+                ble.waitForEvent(); // Add a slight delay
+                //wait(0.1);          // Add a slight delay
+                //ble.waitForEvent(); // Add a slight delay
+                kbdServicePtr->keyUpCode();
+                HID_DEBUG("sent final key up for 2.\n\r");
+                press_keyup[2] = false;    // Clear the flag
+            }
+            if (press_keyup[3] && fing3 < FINGER3_RELEASE) {
+                press_keyup[3] = false;    // Clear the flag
+                kbdServicePtr->keyUpCode();
+                HID_DEBUG("sent key up for 3.\n\r");
+            }
+            if (press_keyup[4] && fing4 < FINGER4_RELEASE) {
+                press_keyup[4] = false;    // Clear the flag
+                kbdServicePtr->keyUpCode();
+                HID_DEBUG("sent key up for 4.\n\r");
+            }
+            
+
+            // Very occasionally, show the connected LED
+            counter1++;
+            if (counter1 == 1) {
+                if (kbdServicePtr->isConnected()) {
+                    connected_led = LED_ON;
+                    waiting_led = LED_OFF;
+                }
+            }
+            if (counter1 == 2) {
+                //counter2 = 0;
+                connected_led = LED_OFF;
+                waiting_led = LED_OFF;
+            }
+            
+
+            // Occasionally show some debug info
+            if (counter1 > 15) {
+                //counter2++;
+                LOG("%.2f%. F1=%.2f, F2=%.2f, F3=%.2f, F4=%.2f\n\r", batt, fing1, fing2, fing3, fing4);
+
+                // Check for low battery
+                if (batt < BATTERY_NEEDS_CHARGE) {
+                    // Toggle blue LED
+                    waiting_led = LED_OFF;
+                    connected_led = LED_OFF;
+                    status_led = !status_led;
+                    if (!haveAskedForRecharge) {
+                        send_string("RECHARGE BATTERY!");
+                        haveAskedForRecharge = true;
+                    }
+                    LOG("RECHARGE BATTERY!\n\r");
+                }
+                //else {
+                //}
+                counter1 = 0;
+            }
+            
+            // Check if a finger was pressed
+            if (fing1 > FINGER1_PRESS && !press_keyup[1]) {
+                send_keypress(FING1_KEYCODE, 1);
+                HID_DEBUG("sent keypress %d for 1.\n\r", FING1_KEYCODE);
+                //counter2+=20;
+                //LOG("%d\n\r", counter2);
+            }
+            if (fing2 > FINGER2_PRESS && !press_keyup[2]) {
+                send_keypress(FING2DOWN_KEYCODE, 2);
+                HID_DEBUG("sent keypress %d for 2.\n\r", FING2DOWN_KEYCODE);
+                // Finger 2 is treated differently. We want to be able to hold down finger 2
+                // Without causing repeated keystrokes, so we will send an up press straight after the down press, 
+                // and a different key down and up when the fingure is released, so the client software can
+                // figure out the duration that it was held for.
+                ble.waitForEvent(); // Add a slight delay
+                //wait(0.1);          // Add a slight delay
+                //ble.waitForEvent(); // Add a slight delay
+                kbdServicePtr->keyUpCode();
+                HID_DEBUG("sent initial key up for 2.\n\r");
+            }
+            if (fing3 > FINGER3_PRESS && !press_keyup[3]) {
+                send_keypress(FING3_KEYCODE, 3);               
+                HID_DEBUG("sent keypress %d for 3.\n\r", FING3_KEYCODE);
+                //counter2++;
+                //send_keypress(counter2, 3);
+                //LOG("%d\n\r", counter2);
+            }
+            if (fing4 > FINGER4_PRESS && !press_keyup[4]) {
+                send_keypress(FING4_KEYCODE, 4);
+                HID_DEBUG("sent keypress %d for 4.\n\r", FING4_KEYCODE);
+                //counter2--;
+                //send_keypress(counter2, 4);
+                //LOG("%d\n\r", counter2);
+                
+            }
+        }
     }
-}
+}
\ No newline at end of file
diff -r 3435302551b3 -r c6659c8882c9 mbed.bld
--- a/mbed.bld	Sat Feb 27 05:08:02 2016 +0000
+++ b/mbed.bld	Sun Aug 26 10:19:01 2018 +0000
@@ -1,1 +1,1 @@
-http://mbed.org/users/mbed_official/code/mbed/builds/6f327212ef96
\ No newline at end of file
+http://mbed.org/users/mbed_official/code/mbed/builds/25aea2a3f4e3
\ No newline at end of file
diff -r 3435302551b3 -r c6659c8882c9 nRF51822.lib
--- a/nRF51822.lib	Sat Feb 27 05:08:02 2016 +0000
+++ b/nRF51822.lib	Sun Aug 26 10:19:01 2018 +0000
@@ -1,1 +1,1 @@
-http://mbed.org/teams/Nordic-Semiconductor/code/nRF51822/#1751e2e2637a
+http://mbed.org/teams/Nordic-Semiconductor/code/nRF51822/#c90ae1400bf2
diff -r 3435302551b3 -r c6659c8882c9 tiny_ble.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tiny_ble.h	Sun Aug 26 10:19:01 2018 +0000
@@ -0,0 +1,26 @@
+
+#ifndef __TINY_BLE_H__
+#define __TINY_BLE_H__
+
+#define LED_GREEN   p21
+#define LED_RED     p22
+#define LED_BLUE    p23
+#define BUTTON_PIN  p17
+//#define BATTERY_PIN p1    // Already defined in a TinyBLE system header?
+
+#define OUT3V3_PIN  p30     // Needs to be enabled if you want to use the 3.3V output pin.
+
+#define FINGER1_PIN p3
+#define FINGER2_PIN p4
+#define FINGER3_PIN p5
+#define FINGER4_PIN p6
+
+#define MPU6050_SDA p12
+#define MPU6050_SCL p13
+
+#define UART_TX     p9
+#define UART_RX     p11
+#define UART_CTS    p8
+#define UART_RTS    p10
+
+#endif // __TINY_BLE_H__