A VR Cycling Experience for $40

I’ve made an Arduino thing that can wirelessly talk to a mobile device over BLE and can now meter the revolutions of a wheel with an optical tachometer. I’m using these two hardware features to make a virtual reality (VR) cycling experience and I’ve got a working demo to share! Here’s how this works:

First, the Arduino thing is positioned to point at the back tires of a stationary bike. (I’m using a mountain bike on an indoor trainer but the beauty of this non-invasive approach is that you could use it with treadmills, ellipticals, rowing machines, or anything that has a looping/revolving surface.)

IMG_5155.JPG

A strip of paper is taped to the tire. Each time the wheel makes a complete rotation, the Arduino will detect when the piece of paper passes by and then sends a wireless message to the mobile phone.

The mobile phone is placed in a viewer that is strapped to my face. (I used a $10 headset that more comfortably fits the iPhone 6S Plus, but any ol’ viewer will do.)

Screen Shot 2016-01-23 at 2.30.54 AM.png

In software, a virtual bike is created to travel in a virtual environment. The virtual bike will only nudge forward whenever the app receives a message from the Arduino reporting that the physical bike’s wheel has made a complete rotation. We are effectively mapping the physical action of pedaling to movement in the virtual space to make an oversized gaming controller.

Screen Shot 2016-01-23 at 1.23.26 AM.png

The virtual environment is constructed in Unity. To render for virtual reality, I’m using Google Cardboard  – a free software SDK for mobile VR. The SDK for Unity is drop dead simple – just drag and drop a prefab into your scene and you’ll instantly have a stereo camera rig to manipulate.

Screen Shot 2016-01-23 at 1.34.51 AM.png

This camera rig is set to move along a spline path whenever the app receives a BLE ping from the Arduino. That’s pretty much all it takes! So there you have it, a VR cycling experience for a whopping total of $40:

  • Arduino thing? $30.
  • Mobile VR headset? $10.
  • Software? Priceless.

Yes it’s DIY, but to put things in perspective, take a look at the current offerings on the market today:

  • Peloton: $2,000 bike with built in touchscreen monitor. No VR. Pedaling does not affect the video screensaver 😦
  • Virzoom: $300 bike ($600 Oculus + ~$2,000 PC not included)
  • Ciclotte: $10,700 bike?!

Screen Shot 2016-01-24 at 1.25.26 AM.png

This was a fun little personal project and I’ve learned what I set out to learn, so this is where I move on – but if you’re going to try something similar, here’s some things I’d consider in retrospect: It wouldn’t be too much of a stretch to have the physical bike steer the direction of the virtual bike. Why not give the player total freedom to explore? Also, does the virtual bike have to be a bike? I’ll just leave this right here: http://goo.gl/fRWY6Z.

For the interested Makers out there, below is a schematic of the IR sensor along with the Arduino code. Tinker away!

IRsensor_bb.png

…and here’s my Arduino sketch:

#include SPI.h
#include "Adafruit_BLE_UART.h"

// nRF8001 pins: SCK:13, MISO:12, MOSI:11, REQ:10, ACI:X, RST:9, 3Vo:X
#define ADAFRUITBLE_REQ 10
#define ADAFRUITBLE_RST 9
#define ADAFRUITBLE_RDY 2
Adafruit_BLE_UART uart = Adafruit_BLE_UART(ADAFRUITBLE_REQ, ADAFRUITBLE_RDY, ADAFRUITBLE_RST);
unsigned long time = 0l;
boolean connection = false;
uint8_t btm = 65;
uint8_t out = btm;
uint8_t cap = 90;
#define persec 30
#define sendat (1000/persec)

int irPin = 7;
int irSensorPin = 5;
int testLEDPin = 4;
int tripTime = 0;
int lastTrip = 0;
int tripBetween;
boolean detectState = false;
boolean lastDetectState = false;

void setup(void)
{
  Serial.begin(9600);

  pinMode(irPin, OUTPUT);
  pinMode(irSensorPin, INPUT);
  pinMode(testLEDPin, OUTPUT);

  uart.setDeviceName("YanBLE"); /* define BLE name: 7 characters max! */

  uart.setRXcallback(rxCallback);
  uart.setACIcallback(aciCallback);
  uart.begin();
}

void loop()
{
  pollIR(); // IR sensor
  uart.pollACI(); // BLE
}

void pollIR() {
  digitalWrite(irPin, HIGH);

  if (digitalRead(irSensorPin) == LOW) {
    detectState = true;
    if (detectState != lastDetectState) {
      // run the first time reflection is detected

      Serial.println("message sent via BLE");
      if (connection == true) {
        sendBlueMessage("1"); // dummy data passed here, this can be any value. We just need to ping the app
      }
      
      lastDetectState = true;
    }
    else {
      // here we are seeing the same reflection over several frames
      // turn test LED on to give visual indication of a positive reflection
      digitalWrite(testLEDPin, HIGH);
    }
  }
  else {
    detectState = false;
    lastDetectState = false;
    digitalWrite(testLEDPin, LOW);
  }
}

/**************************************************************************/
/*!
  BLE-related functions below this point
*/
/**************************************************************************/
void aciCallback(aci_evt_opcode_t event)
{
  // this function is called whenever select ACI events happen
  switch (event)
  {
    case ACI_EVT_DEVICE_STARTED:
      Serial.println(F("Advertising started"));
      break;
    case ACI_EVT_CONNECTED:
      Serial.println(F("Connected!"));
      connection = true;
      break;
    case ACI_EVT_DISCONNECTED:
      Serial.println(F("Disconnected"));
      connection = false;
      break;
    default:
      break;
  }
}

void rxCallback(uint8_t *buffer, uint8_t len)
{
  // this function is called whenever data arrives on the RX channel
}

void sendBlueMessage(String message) {

  uint8_t sendbuffer[20];
  message.getBytes(sendbuffer, 20);
  char sendbuffersize = min(20, message.length());

  Serial.print(F("\n* Sending -> \"")); Serial.print((char *)sendbuffer); Serial.println("\"");

  // write the data
  uart.write(sendbuffer, sendbuffersize);

}

101 responses to “A VR Cycling Experience for $40”

  1. Thank You so much on teaching us how it is done. Can you teach me how it works after the sensor gets reflection and measure the current speed? How did the sensor know I just stopped? Thank You in advance~

    • In my implementation, the sensor doesn’t actually calculate speed (I started with that approach but didn’t end with it). All it does is ping the mobile device each time it detects a reflection. The virtual bike applies a force forward and it has its own decay in speed. The sensor knows nothing about the fact that I’ve stopped, it just fails to ping the mobile device more. Here’s some psuedo-code to give you a hint:

      Set IRLightPin to HIGH;
      if (IRSensorPin == LOW) {
      detectState = true;
      if (detectState != lastDetectState) {
      sendBlueToothMessageJustOnce();
      lastDetectState = true;
      }
      }
      else {
      detectState = false;
      lastDetectState = false;
      }

  2. hey i tried your program but its telling that
    Arduino: 1.0.6 (Windows NT (unknown)), Board: “Arduino Uno”
    sketch_dec13a.ino:1:10: error: #include expects “FILENAME” or
    sketch_dec13a.ino:2:31: error: Adafruit_BLE_UART.h: No such file or directory
    sketch_dec13a:12: error: variable or field ‘aciCallback’ declared void
    sketch_dec13a:12: error: ‘aci_evt_opcode_t’ was not declared in this scope
    sketch_dec13a:8: error: ‘Adafruit_BLE_UART’ does not name a type
    sketch_dec13a.ino: In function ‘void setup()’:
    sketch_dec13a:34: error: ‘uart’ was not declared in this scope
    sketch_dec13a:37: error: ‘aciCallback’ was not declared in this scope
    sketch_dec13a.ino: In function ‘void loop()’:
    sketch_dec13a:44: error: ‘uart’ was not declared in this scope
    sketch_dec13a.ino: At global scope:
    sketch_dec13a:80: error: variable or field ‘aciCallback’ declared void
    sketch_dec13a:80: error: ‘aci_evt_opcode_t’ was not declared in this scope

    What should i do man?????

  3. Dear Paul,
    I am working with Prof. Steven LaValle to help obtain permissions for borrowing figures or pictures in his upcoming book Virtual Reality, to be published by Cambridge University Press. The book is online here:

    http://vr.cs.uiuc.edu/

    We are hoping to include the picture of yours (https://pauldyan.files.wordpress.com/2016/01/vrbikethumb.png?w=1200) in this book (Chapter 10, Figure 10.8b ). Could we please have your permission for this? Thank you.

    Sincerely,

    Adam Warkoczewski

  4. Hey Paul, i would like to build the arduino system same as yours
    what i Have is :
    1 . arduino ,
    2.cables
    3. IR sensors,
    4. Battary,
    5.Matrix (to lay the arduino on)
    5. led for indicate arduino works.

    Any thing i forgot b4 i run wild with that?

    • Don’t forget you’ll need a bike, a stand, a phone, VR goggles, a computer to program the Arduino, soldering equipment.

      I am constantly finding out I need something new along the way. I don’t think I’ve ever gotten my full list of parts ready up front ever. Just start and you’ll quickly realize what you’ll need along the way.

      • Paul I’ve tried to build the arduino part, and didnt understand something.
        Why do we need the OUTPUT irPin (pin 7)?

        Sholdn’t the Ir sensor only send data to the arduino?

      • Pin 7 is to power/control the IR LED. You need to shine the light to bounce off something before receiving it with the sensor.

  5. I’m using that sensor –
    ( https://www.aliexpress.com/item/Free-shipping-1pcs-lot-KY-026-Flame-Sensor-Module-IR-Sensor-Detector-For-Temperature-Detecting-Suitable/32700810819.html?spm=2114.search0304.2.72.H8v9HP),
    which already have the nob i think, i’ve changed your code a bit.
    to only indicate if the sensor is respond or not. ITS NOT WORKING :(.

    #include
    #include
    int irPin = 7;
    int irSensorPin = 5;
    int testLEDPin = 4;

    void setup(void)
    {
    Serial.begin(9600);

    pinMode(irPin, OUTPUT);
    pinMode(irSensorPin, INPUT);
    pinMode(testLEDPin, OUTPUT);
    }

    void loop()
    {
    pollIR(); // IR sensor
    //uart.pollACI(); // BLE
    }

    void pollIR() {
    //digitalWrite(irPin, HIGH);

    if (digitalRead(irSensorPin) == LOW) {

    Serial.println(“no signal”);
    digitalWrite(testLEDPin, LOW);

    }
    else {

    Serial.println(“signal received”);
    digitalWrite(testLEDPin, HIGH);
    }
    }

    i connected those :
    1. 5v => +.
    2. G => GND
    3.DO => pin 5

    Any Idea?

    • I’m not familiar with that component but it doesn’t look like the same sensor to me. I think this is just the sensor. You need to have an LED firing IR light so it can bounce off your reflective material to be received by the sensor.

  6. Hi Paul,
    Thanks for this very creative project
    Kindly let me have a link to the 1st person bike you are using in Unity
    Greetings from Grenoble, France

  7. I understand and respect your position
    I am looking for a suitable bike to include in Unity
    Do you have a link to such an asset ??
    TIA

  8. I think you’ve only published part of the development and not everything complete. I do not think you’re generous, quite the contrary. I would prefer not to have found this project since I lost time investigating it and finally I discovered that you have published to us how the connection between the app and Arduino is made. You have not shared the app either.

    Lack of generosity

  9. Hi Paul,

    I’m working in a project that I need to communicate hall sensor from Arduino to unity like yours. Could u help me with a code?
    Could u share a code with me?!?

    I really need to finish my work!
    Currently I have a code but when I stop to pedal my bike keeps running.

    • If you’re already able to get the bike to move forward based on input from your sensor, you’re very close! In my implementation, each time I detected a rotation from the sensor, the Arduino sends a BLE message to the Unity app. When that BLE message is received, the Unity app applies a force to the bike – this is *not* the same thing as setting its velocity. Think of a force as a single PUSH forward, it will naturally decelerate over time if it doesn’t get additional pushes. So, if the input no longer detects rotations, it will no longer send messages and if no additional messages are received, the bike will naturally slow to a stop.

      Hope that helps!

  10. Hey Paul from Germany,
    I am working on quite similar project. I have few problems related to that.:
    1. I have captured the 360° video from my locality and now I want to use it in place of the 3D model. How can it be done? I am new to Unity
    2. I have attached a motor to provide resistance and support to the rear wheel for the inclination and declination in the video. How could this be done? I have the complete data of the altitude of every point.

    Thanks in advance.

  11. Hello Paul.. This is Athik from India. I have a competition on VR cycling game @ my college and im in a situation to create it. Since my dominie is Civil Engineering i don’t have any knowledge about this gaming process. I would like to buy the entire prototype setup with both hardware and software.

Leave a reply to Paul Cancel reply