Use Tiny BLE as a joystick. The data is got from an external data source via a serial port.
Dependencies: BLE_API BLE_HID eMPL_MPU6050 mbed nRF51822
Fork of Seeed_Tiny_BLE_Get_Started by
Revision 4:89f37a17eba6, committed 2017-06-12
- Comitter:
- bowenfeng
- Date:
- Mon Jun 12 04:10:04 2017 +0000
- Parent:
- 3:24e365bd1b97
- Commit message:
- Use Tiny BLE as a BLE joystick. The direction data is got from an external data source via serial protocol.
Changed in this revision
diff -r 24e365bd1b97 -r 89f37a17eba6 BLE_HID.lib --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BLE_HID.lib Mon Jun 12 04:10:04 2017 +0000 @@ -0,0 +1,1 @@ +https://developer.mbed.org/users/bowenfeng/code/BLE_HID/#24365c168c4e
diff -r 24e365bd1b97 -r 89f37a17eba6 examples_common.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples_common.cpp Mon Jun 12 04:10:04 2017 +0000 @@ -0,0 +1,76 @@ +/* mbed Microcontroller Library + * Copyright (c) 2015 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ble/services/BatteryService.h" +#include "ble/services/DeviceInformationService.h" + +#include "examples_common.h" + +static void passkeyDisplayCallback(Gap::Handle_t handle, const SecurityManager::Passkey_t passkey) +{ + printf("Input passKey: "); + for (unsigned i = 0; i < Gap::ADDR_LEN; i++) { + printf("%c", passkey[i]); + } + printf("\r\n"); +} + +static void securitySetupCompletedCallback(Gap::Handle_t handle, SecurityManager::SecurityCompletionStatus_t status) +{ + if (status == SecurityManager::SEC_STATUS_SUCCESS) { + printf("Security success %d\r\n", status); + } else { + printf("Security failed %d\r\n", status); + } +} + +static void securitySetupInitiatedCallback(Gap::Handle_t, bool allowBonding, bool requireMITM, SecurityManager::SecurityIOCapabilities_t iocaps) +{ + printf("Security setup initiated\r\n"); +} + +void initializeSecurity(BLE &ble) +{ + bool enableBonding = true; + bool requireMITM = HID_SECURITY_REQUIRE_MITM; + + ble.securityManager().onSecuritySetupInitiated(securitySetupInitiatedCallback); + ble.securityManager().onPasskeyDisplay(passkeyDisplayCallback); + ble.securityManager().onSecuritySetupCompleted(securitySetupCompletedCallback); + + ble.securityManager().init(enableBonding, requireMITM, HID_SECURITY_IOCAPS); +} + +void initializeHOGP(BLE &ble) +{ + static const uint16_t uuid16_list[] = {GattService::UUID_HUMAN_INTERFACE_DEVICE_SERVICE, + GattService::UUID_DEVICE_INFORMATION_SERVICE, + GattService::UUID_BATTERY_SERVICE}; + + DeviceInformationService deviceInfo(ble, "ARM", "m1", "abc", "def", "ghi", "jkl"); + + BatteryService batteryInfo(ble, 80); + + ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | + GapAdvertisingData::LE_GENERAL_DISCOVERABLE); + ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, + (uint8_t *)uuid16_list, sizeof(uuid16_list)); + + // see 5.1.2: HID over GATT Specification (pg. 25) + ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED); + // 30ms to 50ms is recommended (5.1.2) + ble.gap().setAdvertisingInterval(50); +}
diff -r 24e365bd1b97 -r 89f37a17eba6 examples_common.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples_common.h Mon Jun 12 04:10:04 2017 +0000 @@ -0,0 +1,69 @@ +/* mbed Microcontroller Library + * Copyright (c) 2015 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HID_EXAMPLES_COMMON_H_ +#define HID_EXAMPLES_COMMON_H_ + +/** + * Functions and configuration common to all HID demos + */ + +#include "ble/BLE.h" + +#include "HIDServiceBase.h" + +/** + * IO capabilities of the device. During development, you most likely want "JustWorks", which means + * no IO capabilities. + * It is also possible to use IO_CAPS_DISPLAY_ONLY to generate and show a pincode on the serial + * output. + */ +#ifndef HID_SECURITY_IOCAPS +#define HID_SECURITY_IOCAPS (SecurityManager::IO_CAPS_NONE) +#endif + +/** + * Security level. MITM disabled forces "Just Works". If you require MITM, HID_SECURITY_IOCAPS must + * be at least IO_CAPS_DISPLAY_ONLY. + */ +#ifndef HID_SECURITY_REQUIRE_MITM +#define HID_SECURITY_REQUIRE_MITM false +#endif + +/** + * Disable debug messages by setting NDEBUG + */ +#ifndef NDEBUG +#define HID_DEBUG(...) printf(__VA_ARGS__) +#else +#define HID_DEBUG(...) +#endif + +/** + * Initialize security manager: set callback functions and required security level + */ +void initializeSecurity(BLE &ble); + +/** + * - Initialize auxiliary services required by the HID-over-GATT Profile. + * - Initialize common Gap advertisement. + * + * Demos only have to set a custom device name and appearance, and their HID + * service. + */ +void initializeHOGP(BLE &ble); + +#endif /* !BLE_HID_COMMON_H_ */
diff -r 24e365bd1b97 -r 89f37a17eba6 main.cpp --- a/main.cpp Thu Nov 05 06:58:30 2015 +0000 +++ b/main.cpp Mon Jun 12 04:10:04 2017 +0000 @@ -1,298 +1,140 @@ - #include "mbed.h" -#include "mbed_i2c.h" -#include "inv_mpu.h" -#include "inv_mpu_dmp_motion_driver.h" #include "nrf51.h" #include "nrf51_bitfields.h" -#include "BLE.h" +#include "ble/BLE.h" +#include "JoystickService.h" #include "DFUService.h" #include "UARTService.h" +#include "examples_common.h" #define LOG(...) { pc.printf(__VA_ARGS__); } #define LED_GREEN p21 #define LED_RED p22 #define LED_BLUE p23 -#define BUTTON_PIN p17 -#define BATTERY_PIN p1 -#define MPU6050_SDA p12 -#define MPU6050_SCL p13 +#define LED_OFF 1 +#define LED_ON 0 #define UART_TX p9 #define UART_RX p11 #define UART_CTS p8 #define UART_RTS p10 -/* Starting sampling rate. */ -#define DEFAULT_MPU_HZ (100) +#define DATA_TX p3 +#define DATA_RX p6 DigitalOut blue(LED_BLUE); DigitalOut green(LED_GREEN); DigitalOut red(LED_RED); -InterruptIn button(BUTTON_PIN); -AnalogIn battery(BATTERY_PIN); Serial pc(UART_TX, UART_RX); +Serial data(DATA_TX, DATA_RX); -InterruptIn motion_probe(p14); - -int read_none_count = 0; - -BLEDevice ble; -UARTService *uartServicePtr; +BLE ble; +JoystickService *joystickServicePtr; +static const char DEVICE_NAME[] = "BunnyJoystick"; +static const char SHORT_DEVICE_NAME[] = "joystick0"; volatile bool bleIsConnected = false; volatile uint8_t tick_event = 0; -volatile uint8_t motion_event = 0; -static signed char board_orientation[9] = { - 1, 0, 0, - 0, 1, 0, - 0, 0, 1 -}; - -void check_i2c_bus(void); -unsigned short inv_orientation_matrix_to_scalar( const signed char *mtx); - - -void connectionCallback(const Gap::ConnectionCallbackParams_t *params) -{ - LOG("Connected!\n"); - bleIsConnected = true; -} - -void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *cbParams) -{ - LOG("Disconnected!\n"); - LOG("Restarting the advertising process\n"); - ble.startAdvertising(); +static void onDisconnect(const Gap::DisconnectionCallbackParams_t *params) { + LOG("disconnected\r\n"); bleIsConnected = false; + red = LED_OFF; + blue = LED_OFF; + green = LED_ON; + ble.gap().startAdvertising(); // restart advertising } -void tick(void) -{ - static uint32_t count = 0; - - LOG("%d\r\n", count++); - green = !green; -} -void detect(void) -{ - LOG("Button pressed\n"); - blue = !blue; +static void onConnect(const Gap::ConnectionCallbackParams_t *params) { + LOG("connected\r\n"); + bleIsConnected = true; + red = LED_OFF; + blue = LED_ON; + green = LED_OFF; } -void motion_interrupt_handle(void) -{ - motion_event = 1; -} - -void tap_cb(unsigned char direction, unsigned char count) -{ - LOG("Tap motion detected\n"); -} - -void android_orient_cb(unsigned char orientation) -{ - LOG("Oriention changed\n"); +static void waiting() { + if (!joystickServicePtr->isConnected()) { + green = !green; + } else { + blue = !blue; + } } -int main(void) -{ - blue = 1; - green = 1; - red = 1; - - pc.baud(115200); - - wait(1); - - LOG("---- Seeed Tiny BLE ----\n"); - - mbed_i2c_clear(MPU6050_SDA, MPU6050_SCL); - mbed_i2c_init(MPU6050_SDA, MPU6050_SCL); - - - if (mpu_init(0)) { - LOG("failed to initialize mpu6050\r\n"); - } - - /* Get/set hardware configuration. Start gyro. */ - /* Wake up all sensors. */ - mpu_set_sensors(INV_XYZ_GYRO | INV_XYZ_ACCEL); - /* Push both gyro and accel data into the FIFO. */ - mpu_configure_fifo(INV_XYZ_GYRO | INV_XYZ_ACCEL); - mpu_set_sample_rate(DEFAULT_MPU_HZ); - - /* Read back configuration in case it was set improperly. */ - unsigned char accel_fsr; - unsigned short gyro_rate, gyro_fsr; - mpu_get_sample_rate(&gyro_rate); - mpu_get_gyro_fsr(&gyro_fsr); - mpu_get_accel_fsr(&accel_fsr); - - dmp_load_motion_driver_firmware(); - dmp_set_orientation( - inv_orientation_matrix_to_scalar(board_orientation)); - dmp_register_tap_cb(tap_cb); - dmp_register_android_orient_cb(android_orient_cb); +int main() { + blue = LED_OFF; + green = LED_OFF; + red = LED_OFF; - uint16_t dmp_features = DMP_FEATURE_6X_LP_QUAT | DMP_FEATURE_TAP | - DMP_FEATURE_ANDROID_ORIENT | DMP_FEATURE_SEND_RAW_ACCEL | DMP_FEATURE_SEND_CAL_GYRO | - DMP_FEATURE_GYRO_CAL; - dmp_enable_feature(dmp_features); - dmp_set_fifo_rate(DEFAULT_MPU_HZ); - mpu_set_dmp_state(1); - - dmp_set_interrupt_mode(DMP_INT_GESTURE); - dmp_set_tap_thresh(TAP_XYZ, 50); - - - motion_probe.fall(motion_interrupt_handle); + data.baud(115200); + + wait(4); + LOG("Bunny Joystick started.\n"); + LOG("initialising ticker\r\n"); + Ticker heartbeat; + heartbeat.attach(waiting, 1); - - Ticker ticker; - ticker.attach(tick, 3); + LOG("initialising ble\r\n"); + ble.init(); - button.fall(detect); + ble.gap().onDisconnection(onDisconnect); + ble.gap().onConnection(onConnect); + + initializeSecurity(ble); - LOG("Initialising the nRF51822\n"); - ble.init(); - ble.gap().onDisconnection(disconnectionCallback); - ble.gap().onConnection(connectionCallback); + LOG("adding hid service\r\n"); + JoystickService joystickService(ble); + joystickServicePtr = &joystickService; + LOG("adding dev info and battery service\r\n"); + initializeHOGP(ble); - /* setup advertising */ - ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED); - ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED); - ble.accumulateAdvertisingPayload(GapAdvertisingData::SHORTENED_LOCAL_NAME, - (const uint8_t *)"smurfs", sizeof("smurfs")); - ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS, - (const uint8_t *)UARTServiceUUID_reversed, sizeof(UARTServiceUUID_reversed)); - DFUService dfu(ble); - UARTService uartService(ble); - uartServicePtr = &uartService; - //uartService.retargetStdout(); + LOG("setting up gap\r\n"); + ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::JOYSTICK); + ble.gap().accumulateAdvertisingPayload( + GapAdvertisingData::COMPLETE_LOCAL_NAME, + (const uint8_t *)DEVICE_NAME, + sizeof(DEVICE_NAME)); + ble.gap().accumulateAdvertisingPayload( + GapAdvertisingData::SHORTENED_LOCAL_NAME, + (const uint8_t *)SHORT_DEVICE_NAME, + sizeof(SHORT_DEVICE_NAME)); + ble.gap().setDeviceName((const uint8_t *)DEVICE_NAME); - ble.setAdvertisingInterval(160); /* 100ms; in multiples of 0.625ms. */ + LOG("advertising\r\n"); ble.gap().startAdvertising(); + int xraw, yraw, zraw; + while (true) { - if (motion_event) { - - unsigned long sensor_timestamp; - short gyro[3], accel[3], sensors; - long quat[4]; - unsigned char more = 1; - - while (more) { - /* This function gets new data from the FIFO when the DMP is in - * use. The FIFO can contain any combination of gyro, accel, - * quaternion, and gesture data. The sensors parameter tells the - * caller which data fields were actually populated with new data. - * For example, if sensors == (INV_XYZ_GYRO | INV_WXYZ_QUAT), then - * the FIFO isn't being filled with accel data. - * The driver parses the gesture data to determine if a gesture - * event has occurred; on an event, the application will be notified - * via a callback (assuming that a callback function was properly - * registered). The more parameter is non-zero if there are - * leftover packets in the FIFO. - */ - dmp_read_fifo(gyro, accel, quat, &sensor_timestamp, &sensors, - &more); - - - /* Gyro and accel data are written to the FIFO by the DMP in chip - * frame and hardware units. This behavior is convenient because it - * keeps the gyro and accel outputs of dmp_read_fifo and - * mpu_read_fifo consistent. - */ - if (sensors & INV_XYZ_GYRO) { - // LOG("GYRO: %d, %d, %d\n", gyro[0], gyro[1], gyro[2]); - } - if (sensors & INV_XYZ_ACCEL) { - //LOG("ACC: %d, %d, %d\n", accel[0], accel[1], accel[2]); - } - - /* Unlike gyro and accel, quaternions are written to the FIFO in - * the body frame, q30. The orientation is set by the scalar passed - * to dmp_set_orientation during initialization. - */ - if (sensors & INV_WXYZ_QUAT) { - // LOG("QUAT: %ld, %ld, %ld, %ld\n", quat[0], quat[1], quat[2], quat[3]); - } - - if (sensors) { - read_none_count = 0; - } else { - read_none_count++; - if (read_none_count > 3) { - read_none_count = 0; - - LOG("I2C may be stuck @ %d\r\n", sensor_timestamp); - mbed_i2c_clear(MPU6050_SDA, MPU6050_SCL); - } + if (data.readable()) { + char buffer[256] = {0}; + char c; + int i = 0; + while(data.readable() && i < 256) { + buffer[i++] = c = data.getc(); + if (c == '\n') { + buffer[i] = 0; + break; } } + LOG("Received data from FTHR:\n"); + LOG(buffer); + sscanf(buffer, "%d,%d,%d", &xraw, &yraw, &zraw); + LOG("%d,%d,%d", xraw, yraw, zraw); + joystickServicePtr->setSpeed(xraw, yraw, zraw); - motion_event = 0; - } else { - ble.waitForEvent(); + wait(0.5); } + + ble.waitForEvent(); } } - -/* These next two functions converts the orientation matrix (see - * gyro_orientation) to a scalar representation for use by the DMP. - * NOTE: These functions are borrowed from Invensense's MPL. - */ -static inline unsigned short inv_row_2_scale(const signed char *row) -{ - unsigned short b; - - if (row[0] > 0) - b = 0; - else if (row[0] < 0) - b = 4; - else if (row[1] > 0) - b = 1; - else if (row[1] < 0) - b = 5; - else if (row[2] > 0) - b = 2; - else if (row[2] < 0) - b = 6; - else - b = 7; // error - return b; -} - -unsigned short inv_orientation_matrix_to_scalar( - const signed char *mtx) -{ - unsigned short scalar; - - /* - XYZ 010_001_000 Identity Matrix - XZY 001_010_000 - YXZ 010_000_001 - YZX 000_010_001 - ZXY 001_000_010 - ZYX 000_001_010 - */ - - scalar = inv_row_2_scale(mtx); - scalar |= inv_row_2_scale(mtx + 3) << 3; - scalar |= inv_row_2_scale(mtx + 6) << 6; - - - return scalar; -} -