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
Revision 1:c6659c8882c9, committed 2018-08-26
- 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
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__