
Introduction
MAX32630FTHR is a really powerful board with quite a lot of onboard peripherals. Most noticeably are the built-in accelerometer and the dual-mode Bluetooth low energy chip which unlock the door to use this board as a VR controller without any additional components.
The most unfortunate thing is as of my project finished, there has been no mbed library support for the onboard PAN1326B dual-mode ble chip, so I have to use some alternative design as a workaround. But the overall design principle will not change which is collecting the accelerometer data, converting to x, y, z axises data, and then send to the Bluetooth central device, in my case is an Android phone, but could be any VR device supporting BLE joystick.
BOM

Arduino Pro mini 3v3 x1 ($9.95)
Tiny BLE x1 ($30.09)
MAX32630FTHR x1 ($28.05)
Schematics

Instructions
This project could be easier and simpler, but because the Bluetooth library is not available, a lot of complexities come from the data transportation between boards.
So the goal is to collect the data from BMI160 accelerometer/gyroscope chip, move them to a board with BLE support, and send out as 3-axis data to a VR device.
Lucky for me I have some experience on Tiny BLE, so I decided to use it as if it is the BLE chip on the MAX32630FTHR board. My first attempt was to use I2C protocol, letting the FTHR board act as the I2C master and Tiny BLE as the I2C slave. But it turned out the I2CSlave is not supported on either board. So I turned to use Serial protocol. This should allow me to use one wire to send data from FTHR board to Tiny BLE on a predefined baud frequency. But that attempt was also without luck. For some reason, the serial data wasn't sent out of FTHR board correctly. I guess this may because of some interference between the built-in USB serial circuit and the UART2 port I used as the data output pins.
Finally, I chose the current solution, using an Arduino Pro Mini as the man-in-middle, converting the incoming I2C data from FTHR board, and send out through TX pin as serial data to Tiny BLE.
More details can be found in my source code listed below. But there are some notable considerations worth mentioning:
- FTHR board doesn't work well when sending data using serial protocol, but I2C works perfectly on the board.
- mbed I2C use 8-bit address, so the slave address must be shift one bit left before used in the API.
- I have to use a slightly lower frequency (10KHz) than the mbed default I2C frequency (100KHz) to allow the much slower Arduino Pro mini being able to recognize the signal.
- I have to use a much higher baud (115200) to send out the serial data otherwise the income data will overflow the output.
- The 3-axis data are uncalibrated, so you can see I found some difficulties while using it in the VR game. Further improvement will be done to provide a much better user experience.
The VR game is based on Unity engine. I modified the Roll a Ball tutorial by adding Google VR support. Here I choose the cardboard VR, not the daydream since I'll provide my own VR controller and that will based on FTHR board! By simply following the Google VR Unity support document and we added the VR support to a 3D game. We can now choose Android as the build target, compile our game and run on my Android phone. You can find the full project source code in the Source Code section.
The BLE controller is done by collecting the data from FTHR board, sending through I2C to Pro mini, then passing to Tiny BLE to be exposed as joystick input data. The reason that I need a Pro mini in middle was because neither board can act as I2C slave, and for some reason serial communication did not work.
UPDATE: MAXIM support team just notify me that their engineer team just released the BLE support library for FTHR board. My project will become much simpler by remove the other two boards!
https://developer.mbed.org/teams/MaximIntegrated/code/FTHR_BLE_Demo/

Video
Source Code
Roll A Ball VR Game source code (Unity project)
https://drive.google.com/file/d/0B-FHQj0xfENBcklnOXNqZHRVMk0/view?usp=sharing
MAX32630FTHR code
https://developer.mbed.org/users/bowenfeng/code/MAX32630FTHR_JOYSTICK/
Code (Text):
- #include "mbed.h"
- #include "max32630fthr.h"
- #include "bmi160.h"
- #define LOG(...) { printf(__VA_ARGS__); }
- int main() {
- DigitalOut rLED(LED1, LED_OFF);
- DigitalOut gLED(LED2, LED_OFF);
- DigitalOut bLED(LED3, LED_OFF);
- I2C i2cBus(P5_7, P6_0);
- i2cBus.frequency(400000);
- BMI160_I2C imu(i2cBus, BMI160_I2C::I2C_ADRS_SDO_LO);
- I2C dataOut(P3_4, P3_5);
- dataOut.frequency(10000);
- uint32_t failures = 0;
- MAX32630FTHR pegasus(MAX32630FTHR::VIO_3V3);
- if(imu.setSensorPowerMode(BMI160::GYRO, BMI160::NORMAL) != BMI160::RTN_NO_ERROR) {
- LOG("Failed to set gyroscope power mode\n");
- failures++;
- }
- wait_ms(100);
- if(imu.setSensorPowerMode(BMI160::ACC, BMI160::NORMAL) != BMI160::RTN_NO_ERROR) {
- LOG("Failed to set accelerometer power mode\n");
- failures++;
- }
- wait_ms(100);
- BMI160::AccConfig accConfig;
- //example of using getSensorConfig
- if(imu.getSensorConfig(accConfig) == BMI160::RTN_NO_ERROR) {
- LOG("ACC Range = %d\n", accConfig.range);
- LOG("ACC UnderSampling = %d\n", accConfig.us);
- LOG("ACC BandWidthParam = %d\n", accConfig.bwp);
- LOG("ACC OutputDataRate = %d\n\n", accConfig.odr);
- } else {
- LOG("Failed to get accelerometer configuration\n");
- failures++;
- }
- //example of setting user defined configuration
- accConfig.range = BMI160::SENS_4G;
- accConfig.us = BMI160::ACC_US_OFF;
- accConfig.bwp = BMI160::ACC_BWP_2;
- accConfig.odr = BMI160::ACC_ODR_8;
- if(imu.setSensorConfig(accConfig) == BMI160::RTN_NO_ERROR) {
- LOG("ACC Range = %d\n", accConfig.range);
- LOG("ACC UnderSampling = %d\n", accConfig.us);
- LOG("ACC BandWidthParam = %d\n", accConfig.bwp);
- LOG("ACC OutputDataRate = %d\n\n", accConfig.odr);
- } else {
- LOG("Failed to set accelerometer configuration\n");
- failures++;
- }
- BMI160::GyroConfig gyroConfig;
- if(imu.getSensorConfig(gyroConfig) == BMI160::RTN_NO_ERROR) {
- LOG("GYRO Range = %d\n", gyroConfig.range);
- LOG("GYRO BandWidthParam = %d\n", gyroConfig.bwp);
- LOG("GYRO OutputDataRate = %d\n\n", gyroConfig.odr);
- } else {
- LOG("Failed to get gyroscope configuration\n");
- failures++;
- }
- wait(1.0);
- if(failures == 0) {
- float imuTemperature;
- BMI160::SensorData accData;
- BMI160::SensorData gyroData;
- BMI160::SensorTime sensorTime;
- char buffer[256] = {0};
- while(1) {
- imu.getGyroAccXYZandSensorTime(accData, gyroData, sensorTime, accConfig.range, gyroConfig.range);
- imu.getTemperature(&imuTemperature);
- sprintf(buffer, "%d,%d,%d\n", accData.xAxis.raw, accData.yAxis.raw, accData.zAxis.raw);
- LOG(buffer);
- dataOut.write(0x8<<1, buffer, strlen(buffer) + 1);
- gLED = !gLED;
- }
- } else {
- while(1) {
- rLED = !rLED;
- wait(0.25);
- }
- }
- }
Code (Text):
- #include <Wire.h>
- void setup() {
- Serial.begin(115200);
- Wire.onReceive(receiveData);
- Wire.begin(0x8);
- delay(2);
- Serial.println("Logger Started.");
- }
- void loop() {
- delay(5);
- }
- void receiveData(int cnt) {
- while (Wire.available() > 0) {
- char c = Wire.read();
- Serial.print(c);
- }
- }
https://developer.mbed.org/users/bowenfeng/code/Seeed_Tiny_BLE_FTHR_Peripheral/
Code (Text):
- #include "mbed.h"
- #include "nrf51.h"
- #include "nrf51_bitfields.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 LED_OFF 1
- #define LED_ON 0
- #define UART_TX p9
- #define UART_RX p11
- #define UART_CTS p8
- #define UART_RTS p10
- #define DATA_TX p3
- #define DATA_RX p6
- DigitalOut blue(LED_BLUE);
- DigitalOut green(LED_GREEN);
- DigitalOut red(LED_RED);
- Serial pc(UART_TX, UART_RX);
- Serial data(DATA_TX, DATA_RX);
- 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;
- 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
- }
- static void onConnect(const Gap::ConnectionCallbackParams_t *params) {
- LOG("connected\r\n");
- bleIsConnected = true;
- red = LED_OFF;
- blue = LED_ON;
- green = LED_OFF;
- }
- static void waiting() {
- if (!joystickServicePtr->isConnected()) {
- green = !green;
- } else {
- blue = !blue;
- }
- }
- int main() {
- blue = LED_OFF;
- green = LED_OFF;
- red = LED_OFF;
- data.baud(115200);
- wait(4);
- LOG("Bunny Joystick started.\n");
- LOG("initialising ticker\r\n");
- Ticker heartbeat;
- heartbeat.attach(waiting, 1);
- LOG("initialising ble\r\n");
- ble.init();
- ble.gap().onDisconnection(onDisconnect);
- ble.gap().onConnection(onConnect);
- initializeSecurity(ble);
- LOG("adding hid service\r\n");
- JoystickService joystickService(ble);
- joystickServicePtr = &joystickService;
- LOG("adding dev info and battery service\r\n");
- initializeHOGP(ble);
- 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);
- LOG("advertising\r\n");
- ble.gap().startAdvertising();
- int xraw, yraw, zraw;
- while (true) {
- 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);
- wait(0.5);
- }
- ble.waitForEvent();
- }
- }