Project tutorial
Arduino 101 BLE Rover

Arduino 101 BLE Rover © CC BY-SA

Converting a rover to use the Arduino 101 including the Bluetooth Low Energy (BLE) radio and 6-axis Inertial Measurement Unit (IMU).

  • 3,605 views
  • 0 comments
  • 14 respects

Components and supplies

Apps and online services

About this project

I was getting ready to join a small team of folks in conducting a Maker Experience for a group of High School students in Oakland, CA. Maker can mean a lot of things – for us it meant a focus on thinking about real world problems that can be simulated or solved via the Arduino with sensors and actuators. We had chosen the Arduino 101 along with the Grove Starter Kit for Arduino as our platform and were looking for a capstone project that would reinforce lots of the concepts we had been teaching as well as being memorable and fun. Through my network, I found some awesome Rover kits that were based on Edison and used Wi-Fi - it was the Seeedstudio Skeleton Bot - a 4WD mobile robotic platform. This platform has been around for a few years, but they were really high performance and very cool, so I decided to try to convert the Rover kits to use Arduino 101 and Bluetooth Low Energy communication. We had a great project for our Maker Experience! This tutorial describes the build/conversion of that Edison based 4WD kit to use Arduino 101.

There are quite a few Arduino based rovers on the internet - so what do I hope to add with this tutorial?

1. A practical example using the Arduino 101. With built-in wireless Bluetooth Low Energy communications (BLE) and a 6-axis Inertial Measurement Unit (IMU) - it feels like the Arduino 101 is made for motion! However, I could not find many real projects that put these new capabilities to use. This tutorial utilizes both the Arduino 101 BLE and IMU features in a useful way.

2. I also highlight the use of the Grove Starter Kit for Arduino, since it allows me to quickly add sensors and actuators (from a large catalog of inexpensive sensors) without the need to breadboard, which is more difficult and time consuming for me. This is a personal preference and not intended as a "knock" on those who are skilled at breadboarding.

3. My Arduino code is very modular - so hopefully it is usable even if you don't use the same motors, motor controller or the grove kit.

All software and schematics are provided. First, I describe building a basic 4WD Rover - Arduino based motor control taking commands via BLE from a smartphone - Android or IOS. In testing, I crashed the Rover a few times and found that trying to pick it up after I "turtled" it, with its knobby tires spinning fast, wasn't very safe. I decided to add a routine to detect crashes and turn the motors off when I crashed. I also have a few ideas for improving the rover and I will outline some of those at the end.

During the build, I benefited so much from public sample code and tips, I wanted to share what I learned in the hope that others can benefit from my work.

The high level steps for this tutorial are as follows:

1. Assemble the Rover “Rolling Chassis”

2. Electrical Assembly

3. Testing motor control with the Arduino 101

4. Building the BLE Remote Control – covered in a separate Instructable

5. Programming the Arduino 101 for a “Base Model” Rover

6. Adding a “Crash Detect” safety option

7. Final Thoughts and Future Enhancements

Detailed Hardware Bill of Materials:

1. Arduino 101 board

2. Grove Starter Kit for Arduino

  • Grove Base Shield v2.0
  • Grove I2C RGB LCD Backlight Display v2.0
  • Grove Button
  • Grove LED Socket v1.3 with Blue LED
  • Grove Cables (4 ea.)

3. Skeleton Bot – 4WD Mobile Robotic Platform

  • Aluminum chassis and drilled acrylic plates - part of
  • 25GA-370 DC motor (4 ea.) with wires for + and -
  • Grove I2C Motor Driver Board with 3 ea. 0.25” standoffs, screws
  • 85mm Wheels, axels, screws (4 ea.)
  • 1” Aluminum standoffs, screws for mounting Arduino
  • Dean’s male pigtail for connecting to motor driver
  • Dean’s “T” – 2 female connectors tied to one male connector
  • Dean’s male with Arduino barrel jack
  • Dean’s female to Traxxas connector
  • Traxxas Li-Po 30C 5000mAh 2S 7.4V Battery

Step 1: Assemble the Rover “Rolling Chassis”

To create a “Rolling Chassis” from the Skeleton Bot – 4WD Mobile Robotic Platform – it is best to follow the recipe at Seeedstudio.com. I received the rover kit with the main body pre-assembled. This step can be completed by following the detailed instructions located at: http://www.seeedstudio.com/recipe/index.php?m=Hom...

I have included several pictures of the Rover kit at the completion of this step. Mounting the wheels at this step versus later is a matter of preference. I feel it is easier to test with the wheels off. But with the wheels on, the rover can sit on a block or box – allowing the wheels to spin freely - this works just as well.

Step 2: Electrical Assembly:

My DC motors came with wires already attached as can be observed in the previous step. I attached the wires from the left motors to Motor Driver 1 and from the right motors to Motor Driver 2 as can be seen in the schematic and photo. Securing the two sets of wires in the small screw-down connectors was challenging.

I assembled the power cable next. The different pieces of the power cable came with the kit and provide an ingenious way of connecting an RC car battery to the rover. I assembled it as shown.

As mentioned, the Lithium Polymer battery being used is a typical Radio Controlled car battery. It is a 2S battery – meaning is puts two of the 18650 battery elements in series. 18650s are rated at 3.7 volts, so having two in series, yields a 7.4 volt input for the motor driver.

In terms of output, the Grove I2C motor driver integrates an L298P dual H-bridge capable of delivering 2A per motor driver. It is also capable of driving DC motors in both directions and controlling speed and direction of each of the motor drivers independently. One additional feature of the Grove I2C Motor Driver board is that it has an onboard 5V regulator which can be used to power the Arduino through the I2C bus. I experimented and found that this worked well - the Arduino did not need to be powered by the barrel jack (or USB cable) when the battery was attached to the motor driver board. For more information on the this motor driver, please see the wiki entry at: http://www.seeedstudio.com/wiki/Grove_-_I2C_Motor_Driver_V1.3

I made sure to attach a grove cable and to feed the battery connector through a slot in the top acrylic plate before screwing it in place.

Step 3: Testing Motor Control

In order to ensure that the wiring was done correctly, I needed some simple Arduino code to spin the 4 motors at different speeds and different directions. The left motor in the front and the left motor in the back should spin identically. The same is true of the pair of motors on the right.

I found a good piece of code at: http://www.seeedstudio.com/wiki/images/8/8c/I2CMo...

I made a couple of modifications to adjust the speed of the motors in the loop. The code I used to test is attached.

Ensure that each motor turns, at three different speeds and in both directions. If that is not what you observe, check the wiring and retest until correct. I have attached a short video showing a successful motor test.

Step 4: Building the BLE Remote Control

Building the Arduino 101 BLE Rover Remote Control is covered in a different tutorial here.

Step 5: Programming the Arduino 101 for a “Base Model” Rover

I start with a "Base Model" rover code - no frills, minimal options. It must contain:

  • Rover control code for translating commands into actions for the motors
  • BLE communications code to connect the smartphone to the Arduino.

I structure my code in a traditional way:

  • Include libraries and declare global variables
  • Setup() function – for code intializing hardware - needed to run once
  • Loop() function – for code that runs repeatedly
  • User defined functions – for any chunk of code longer than a few lines in the loop

I find that creating user defined functions allows me to keep my code readable and structured – less chance of me writing spaghetti code; that is hard to understand and hard to debug.

The Base model code is a combination of 2 functions for controlling the DC motors – MotorSpeedSetAB and combined with the Rover state machine from deba168’s tutorial: Smartphone Controlled Arduino Rover, which I placed in a user defined function called RoverControl.

For communication, I modified the CallbackLED sample that showcases the use of Bluetooth LE on the Arduino 101. The main modifications were to define the UART profile (specific BLE characteristics and UUID’s) that the nRF Toolbox application expects to see. This is the application where I defined the remote control on the smartphone. There are several online tutorials on Bluetooth Low Energy (BLE) – I will not go into theoretical depth to explain its inner workings here. Let me walk you through each section of code briefly!

Section 1 (include and declare):

  • include the wire.h library to initialize the I2C bus
  • include the CurieBLE.h library to enable Bluetooth LE communications
  • declare variables defining D2 for the Blue LED, state to hold the RoverControl command char, vSpeedSet to hold the char for speed (0-100)
  • create a BLEPeripheral instance, create a BLEService for the UART profile, create a rx and tx characteristics for the service

Section 2 (setup function):

  • initialize the I2C bus and serial link
  • define D2 as an output – this is the Grove LED pin – blue LED indicating a Bluetooth connection or not
  • setup several items related to BLE – LocalName, add the tx and rx characteristics to the BLE service, define event handlers for connect, disconnect and rxCharacteristic written, then start advertising the BLE service
  • initialize the DC motors as “off” (both + and – pins low for both motors), DC motor speed set at 50%

Section 3 (loop function):

Here are all 4 lines of code in the loop() function:

blePeripheral.poll();    // check if the rxCharacteristic is written
if(state != prev_state) {   // check if the value from the remote control is new?
RoverControl(state);   // function to respond to control state changes - direction and/or speed
prev_state = state;   // keep a little history

I only want to respond to a command from the Remote Control if it is different from the command I am currently executing.

Section 4 (user defined functions):

  • motor control: MotorSetSpeedAB, MotorSetDirection
  • BLE event handlers: blePeripheralConnectHandler, blePeripheralDisconnectHandler, rxCharacteristicWritten
  • Rover “state machine” to parse and respond to commands: RoverControl

The BLE connect handler turns the LED on when connected and the BLE disconnect handler turns the LED off when disconnected. All the code is heavily documented and available for download.

Step 6: Adding a “Crash Detect” safety option

As I mentioned previously, I was inspired to add this feature after crashing the rover a few times. Either it would be up against a wall trying to push its gear motors into an immovable object, or it would be on its “back”, knobby wheels spinning quickly as it rocked back and forth – not what I consider to be safe to pick up without caution. Reducing “wear and tear” on the motors and reducing a need for first aid – both are a win.

I got the implementation idea from the Shock Detect sample for the Arduino 101. This is part of the Inertial Measurement Unit (IMU) functionality. After including the library, you initialize the IMU and attach an interrupt to the IMU and set it to trigger on “shock” in the setup() function. You set the peak and duration of a shock you want to detect and the Arduino 101 generates an interrupt, letting you take action in an event handler.

From a hardware perspective, I added the Grove Button to D2 and the Grove RGB LCD display to an I2C port on the Grove Shield. It should look like the pictures for this step.

In order to utilize the display, I include its library, initialize it in setup() and then write out the motor speed setting to the display as part of the RoverControl() function.

The interrupt service routine in the CurieIMU Shock Detect sample prints the orientation of the shock to the Serial Monitor. In my routine, I turn the motors off, change the RGB LDC display to have a RED backlight and writes a crash message to the LCD. I also set ‘crash’ - a Boolean variable that effectively blocks the rover from responding to new remote control commands until a reset button is pressed. The simple Loop() code now looks like the following:

if (crash == false) {   //rover is operating normally
previous block of 4 lines of code – used in normal operation
}
else {       //rover is in a crashed state
if (digitalRead(resetPin) == HIGH)  {   // look for reset condition - this occurs from a push button after a crash
CrashRecovery();  // function to recover from crash, start operating normally again
}
}

The CrashRecovery() function is called when the Grove button mounted at the back of the rover is pushed. It is responsible for resetting the display backlight to Green and the LCD message displays the motor power setting again. The motors direction is set to ‘off’, the state is set to stop (‘c’) and speed is set to half power again. Finally, the crash variable to false for normal operation.

For Shock detection, I found that the initial setting of 1500 mg was too sensitive and indicated a crash in normal operation when changing speed or direction. With a little experimentation I was able to find a reasonable setting that didn’t generate false positives and still triggered in a crash.

The full sketch is attached.

Step 7: Final Thoughts and Future Enhancements

This project worked out really well as a capstone project for our Maker Experience in Oakland. Our students were successful in building, testing and enjoying the rover.

I had a great time researching and building this rover. I learned a TON! As can be expected, not everything worked as expected the first time I tried. I learned way more when things didn't work, than when they did.

There are some things that worked really well on the Rover:

1. The BLE remote control is reliable and simple to create

2. The DC gear motors are high performance and work well

3. Using the Grove Starter kit made feature enhancements to the rover easy to create

4. Utilizing interrupts wherever possible for the IMU, BLE simplified the code and increased performance

Here are some things that did not work that well:

1. The I2C Grove Motor Driver would occasionally lock up and require a reset via small button on the board - only seen during code changes and debugging

2. The rapid direction and speed changes seems to be hard on the DC motors (a couple failures seen) - I should "smooth" out motor speed ramps and direction changes.

In addition to enhancements related to speed ramp smoothing I would like to add a range sensing in the front and rear to enable a "brake assist" to avoid collisions. Perhaps I will tackle these in my next tutorial.

Code

Rover Base Model CodeC/C++
/*
 * Arduino 101 based 4WD Rover code developed utilizing:  
 * - an Arduino 101 microcontroller board - using the Bluetooth LE radio (BLE), and the 6-axis Inertial Measurement Unit (IMU)
 * - motors and chassis from Skeleton Bot - 4WD hercules mobile robotic platform from Seeedstudio - early version 
 * - Grove I2C Motor Driver Board
 * - Grove Starter Kit for Arduino from Seeedstudio - Grove Button, Grove I2C RGB LCD Display, Grove LED Socket, 
 * - HC-SR04 Ultrasonic sensor, wired via a small breadboard
 * 
 * Most of this project's code is derived from other Arduino samples found 
 * on www.instructables.com and www.github.com
 * 
 * Much respect and appreciation for the "Makers" who have come before me
 * Lots of knowledge gained from their code and hope my example here is useful to others
 * 
 * the integration and other bits of original code were written by:
 * Author: Dave Shade
 * 
 * it is available for use under the GNU Lesser General Public License
 * see below for details
 * 
 * *** Significant pieces of code used from the following: ***
 * 
 * Code included for the Grove I2C Motor Driver from:
 * Grove - I2C motor driver demo v1.0
 * by: http://www.seeedstudio.com
 *
 * Code for the RoverControl function taken from the Basic_Robt.ino Sketch:
 * http://www.instructables.com/id/Smartphone-Controlled-Arduino-Rover/
 * by deba168
 * 
 * Code for the Crash Detection derived from the Arduino 101 sample: ShockDetect
 *    Copyright (c) 2015 Intel Corporation.  All rights reserved.
 * Code for the BlueTooth Remote Control derived from the Arduino 101 sample: CallbackLED
 *    Copyright (c) 2015 Intel Corporation.  All rights reserved.
 *    
 * Specifics for the BLE UART characteristics - to enable the use of the Nordic Semiconductor UART applicaion
 * found at: https://www.nordicsemi.com/eng/Products/Nordic-mobile-Apps/nRF-UART-App2
 * 
 * Code included from various Grove examples like: 
 * Grove RGB LCD display, Grove Button, Grove LED and they are:
 * 2013 Copyright (c) Seeed Technology Inc.  All right reserved.
 * 
 * All code utilized is covered under the license agreement described below: 
 *  
 *  This demo code is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
*/

#include <Wire.h>   // Library for the initializing the I2C bus.
#include <CurieBLE.h>   // CurieBLE library - pre-installed when Arduino 101 is selected in Arduino IDE 1.6.7 or later 

//I2C Grove Motor Driver defines
//Motor controller defines
#define MotorSpeedSet             0x82
#define PWMFrequenceSet           0x84
#define DirectionSet              0xaa
#define MotorSetA                 0xa1
#define MotorSetB                 0xa5
#define Nothing                   0x01
#define EnableStepper             0x1a
#define UnenableStepper           0x1b
#define Stepernu                  0x1c
#define I2CMotorDriverAdd         0x0f // Set the address of the I2CMotorDriver

// Variables for allocating the Digital I/O pins of the Arduino
//const int resetPin = 2;           // D2 used by Grove Button - reset from crash state
const int ledPin = 3;             // D3 used by Grove LED socket - showing bluetooth connected
//const int servoPin = 5;         // D5 used by the servo
//const int ultrasonic_back = 6;  // D6 used by the Back ultrasonic sensor
                                  // D7 used by the Back ultrsonic sensor
//const int ultrasonic_front = 8;   // D8 used by the Front ultrasonic sensor
                                  // D9 used by the Front ultrasonic sensor
// 0x0f is the I2C address used by Grove Motor Driver board
// 0x3e is an I2C address used by the Grove RGB LCD display (7c>>1)
// 0x62 is an I2C address used by the Grove RGB LCD display (c4>>1)


char vSpeedSet = 50;    // this variable holds the speed setting from the remote control - default speed is half
char vSpeedLimit = 50;  // this variable holds the limited value of the speed based on sensor values

char state = 'c';  // initial state is stop
char prev_state = 'c';

BLEPeripheral blePeripheral;  // BLE Peripheral Device (the board you're programming)
// ====  create Nordic Semiconductor BLE UART service =========
// Must use these UUIDs and BLE characteristics
BLEService uartService = BLEService("6E400001B5A3F393E0A9E50E24DCCA9E");
// create characteristics
BLECharacteristic rxCharacteristic = BLECharacteristic("6E400002B5A3F393E0A9E50E24DCCA9E", BLEWriteWithoutResponse, 20);   // == TX on central (android app)
BLECharacteristic txCharacteristic = BLECharacteristic("6E400003B5A3F393E0A9E50E24DCCA9E", BLENotify , 20);   // == RX on central (android app)
 

// Setup function - run once at reset or power-on
void setup()  {
  Wire.begin();   // join i2c bus (address optional for master)
  delay(100);
  Serial.begin(115200);
  // initialize the Reset button on D2 and the Blue Grove LED on D3
  pinMode(ledPin, OUTPUT);   // use the LED on pin 3 as an output

  // set advertised local name and service UUID:
  blePeripheral.setLocalName("4WD_RV");   //make unique name
  blePeripheral.setAdvertisedServiceUuid(uartService.uuid());

  // add service and characteristic:
  blePeripheral.addAttribute(uartService);
  blePeripheral.addAttribute(rxCharacteristic);
  blePeripheral.addAttribute(txCharacteristic);

  // assign event handlers for connected, disconnected to peripheral
  blePeripheral.setEventHandler(BLEConnected, blePeripheralConnectHandler);
  blePeripheral.setEventHandler(BLEDisconnected, blePeripheralDisconnectHandler);

  // assign event handler for characteristic
  rxCharacteristic.setEventHandler(BLEWritten, rxCharacteristicWritten);

  // begin advertising BLE service:
  blePeripheral.begin();
  //Serial.println(("Bluetooth device active, waiting for connections..."));  // for debugging
  
  // Initialize the motor controllers  
  MotorDirectionSet(0b0000);  //0b0000  stopped 
  delay(100);
  MotorSpeedSetAB(vSpeedSet,vSpeedSet); // on a scale of 1 to 100
  delay(100);  
}  // end setup

void loop()  {
 /* Key sections of the loop
  *  Check if we are in a "crash" condition
  *  if not crashed:
  *    poll for a new value written into state by the rxCharacteristic
  *    check if there is something new in "state"  
  *      if yes call rovercontrol
  *      if no call speedcontrol
  *  if crashed: 
  *    look for a reset button press
  */
    blePeripheral.poll();
    if(state != prev_state) {   // check if the value from the remote control new or the same as the previous one?
      RoverControl(state);   // function to respond to control state changes - direction and/or speed
      //Serial.println(char(state));
      prev_state = state;
    }
}

void blePeripheralConnectHandler(BLECentral& central) {   // central connected event handler
  Serial.print("Connected event, central: ");
  Serial.println(central.address());
  digitalWrite(ledPin, HIGH);     
  //Serial.println("LED on");
}

void blePeripheralDisconnectHandler(BLECentral& central) {   // central disconnected event handler
  Serial.print("Disconnected event, central: ");
  Serial.println(central.address());
  digitalWrite(ledPin, LOW);      
  //Serial.println("LED off");
  state = 'c';
  RoverControl(state);            // stop rover on BLE disconnect - should reduce runaway rover incidents
  //Serial.println(char(state));
  delay(100);
}

void rxCharacteristicWritten(BLECentral& central, BLECharacteristic& characteristic) {
  // central wrote new value to characteristic, update LED
  //Serial.print("Characteristic event, written: ");
  if (characteristic.value()) {   // NULL pointer check
    state = *characteristic.value();   // de-reference to get first byte
    //Serial.println(char(state));
  } 
}

// Functions to set the 2 DC motor's speed: motorSpeedA: the DC motor A speed; should be 0~100, motorSpeedB: the DC motor B speed; should be 0~100;
void MotorSpeedSetAB(unsigned char MotorSpeedA , unsigned char MotorSpeedB)  {
  MotorSpeedA=map(MotorSpeedA,0,100,0,255);
  MotorSpeedB=map(MotorSpeedB,0,100,0,255);
  Wire.beginTransmission(I2CMotorDriverAdd); // transmit to device I2CMotorDriverAdd
  Wire.write(MotorSpeedSet);   // set pwm header 
  Wire.write(MotorSpeedA);     // send pwma 
  Wire.write(MotorSpeedB);     // send pwmb    
  Wire.endTransmission();    // stop transmitting
}

// set the direction of DC motor. 
void MotorDirectionSet(unsigned char Direction)  {   //  Adjust the direction of the motors 0b0000 I4 I3 I2 I1
  Wire.beginTransmission(I2CMotorDriverAdd);   // transmit to device I2CMotorDriverAdd
  Wire.write(DirectionSet);   // Direction control header
  Wire.write(Direction);   // send direction control information
  Wire.write(Nothing);   // need to send this byte as the third byte(no meaning)  
  Wire.endTransmission();   // stop transmitting 
}

void RoverControl(char state) {
 /*
  * Respond to changes in direction - forward, left, stop, right, backward from Bluetooth LE Remote Control
  *
  * With a new "state" from the remote control - check for action to take
  * a - set gear to go forward (drive)
  * b - set gear to turn left and go forward
  * c - set gear to stop (park)
  * d - set gear to turn right and go forward
  * e - set gear to go backward (reverse)
  * 0 - set speed to 0 - not implemented in the remote control
  * 1 - set speed to 25%
  * 2 - set speed to 50%
  * 3 - set speed to 75% 
  * 4 - set speed to 100%
  */
  if (state == 'a') {
    MotorDirectionSet(0b1001); }  //0b1001    // If state is equal with letter 'a', Motors Rotating in the forward direction, rover will go forward 
  else if (state == 'b') {
    MotorDirectionSet(0b0001); }  //0b0001    // If state is equal with letter 'b', right motors rotating forward, left motors off, , rover will turn left
  else if (state == 'd') {
    MotorDirectionSet(0b1000); }  //0b1000    // If state is equal with letter 'd', left motors rotating forward, right motors off, rover will turn right
  else if (state == 'c'){
    MotorDirectionSet(0b0000); }  //0b0000    // If state is equal with letter 'c', Motors off - stop the rover
  else if (state == 'e') {
    MotorDirectionSet(0b0110); }  //0b0110    // If state is equal with letter 'e', rover will go backward, both motors rotating backward
  else if (state == '0') {   // Change speed if state is equal from 0 to 4. Values must be from 0 to 100 - this is mapped to 0 to 255 (PWM) by the MotorSpeedSet function
    vSpeedSet=0; }
  else if (state == '1') {
    vSpeedSet=25; }
  else if (state == '2') {
    vSpeedSet=50; }
  else if (state == '3') {
    vSpeedSet=75; }
  else if (state == '4') {
    vSpeedSet=100; }
  delay(10);
  vSpeedLimit = vSpeedSet;
  MotorSpeedSetAB(vSpeedSet,vSpeedSet);   // set motor speed based on changes on a scale of 1 to 100
}
Rover Option Model CodeC/C++
/*
 * Arduino 101 based 4WD Rover code developed utilizing:  
 * - an Arduino 101 microcontroller board - using the Bluetooth LE radio (BLE), and the 6-axis Inertial Measurement Unit (IMU)
 * - motors and chassis from Skeleton Bot - 4WD hercules mobile robotic platform from Seeedstudio - early version 
 * - Grove I2C Motor Driver Board
 * - Grove Starter Kit for Arduino from Seeedstudio - Grove Button, Grove I2C RGB LCD Display, Grove LED Socket, 
 * - HC-SR04 Ultrasonic sensor, wired via a small breadboard
 * 
 * Most of this project's code is derived from other Arduino samples found 
 * on www.instructables.com and www.github.com
 * 
 * Much respect and appreciation for the "Makers" who have come before me
 * Lots of knowledge gained from their code and hope my example here is useful to others
 * 
 * the integration and other bits of original code were written by:
 * Author: Dave Shade
 * 
 * it is available for use under the GNU Lesser General Public License
 * see below for details
 * 
 * *** Significant pieces of code used from the following: ***
 * 
 * Code included for the Grove I2C Motor Driver from:
 * Grove - I2C motor driver demo v1.0
 * by: http://www.seeedstudio.com
 *
 * Code for the RoverControl function taken from the Basic_Robt.ino Sketch:
 * http://www.instructables.com/id/Smartphone-Controlled-Arduino-Rover/
 * by deba168
 * 
 * Code for the Crash Detection derived from the Arduino 101 sample: ShockDetect
 *    Copyright (c) 2015 Intel Corporation.  All rights reserved.
 * Code for the BlueTooth Remote Control derived from the Arduino 101 sample: CallbackLED
 *    Copyright (c) 2015 Intel Corporation.  All rights reserved.
 *    
 * Specifics for the BLE UART characteristics - to enable the use of the Nordic Semiconductor UART applicaion
 * found at: https://www.nordicsemi.com/eng/Products/Nordic-mobile-Apps/nRF-UART-App2
 * 
 * Code included from various Grove examples like: 
 * Grove RGB LCD display, Grove Button, Grove LED and they are:
 * 2013 Copyright (c) Seeed Technology Inc.  All right reserved.
 * 
 * All code utilized is covered under the license agreement described below: 
 *  
 *  This demo code is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
*/

#include <Wire.h>   // Library for the initializing the I2C bus.
#include "rgb_lcd.h"   // Grove library for the I2C RGB LCD Display is found at: https://github.com/Seeed-Studio/Sketchbook_Starter_Kit_V2.0
#include "CurieIMU.h"   // CurieIMU library - pre-installed when Arduino 101 is selected in Arduino IDE 1.6.7 or later 
#include <CurieBLE.h>   // CurieBLE library - pre-installed when Arduino 101 is selected in Arduino IDE 1.6.7 or later 

//I2C Grove Motor Driver defines
//Motor controller defines
#define MotorSpeedSet             0x82
#define PWMFrequenceSet           0x84
#define DirectionSet              0xaa
#define MotorSetA                 0xa1
#define MotorSetB                 0xa5
#define Nothing                   0x01
#define EnableStepper             0x1a
#define UnenableStepper           0x1b
#define Stepernu                  0x1c
#define I2CMotorDriverAdd         0x0f // Set the address of the I2CMotorDriver

// Variables for allocating the Digital I/O pins of the Arduino
const int resetPin = 2;           // D2 used by Grove Button - reset from crash state
const int ledPin = 3;             // D3 used by Grove LED socket - showing bluetooth connected
//const int servoPin = 5;         // D5 used by the servo
//const int ultrasonic_back = 6;  // D6 used by the Back ultrasonic sensor
                                  // D7 used by the Back ultrsonic sensor
//const int ultrasonic_front = 8;   // D8 used by the Front ultrasonic sensor
                                  // D9 used by the Front ultrasonic sensor
// 0x0f is the I2C address used by Grove Motor Driver board
// 0x3e is an I2C address used by the Grove RGB LCD display (7c>>1)
// 0x62 is an I2C address used by the Grove RGB LCD display (c4>>1)

// Variables for the Grove RGB LCD Display - connected to the I2C bus
// common RGB values: Red=255,0,0  Green=0,255,0  Blue=0,0,255  Purple=255,0,255  Yellow=255,255,0  Aqua=0,255,255  White=255,255,255 Off=0,0,0
int colorR = 0;
int colorG = 255;
int colorB = 0;

char vSpeedSet = 50;    // this variable holds the speed setting from the remote control - default speed is half
char vSpeedLimit = 50;  // this variable holds the limited value of the speed based on sensor values

bool crash = false;  // initial crash state is false (operating)

char state = 'c';  // initial state is stop
char prev_state = 'c';

// Creating the instance of the LCD display
rgb_lcd lcd;

BLEPeripheral blePeripheral;  // BLE Peripheral Device (the board you're programming)
// ====  create Nordic Semiconductor BLE UART service =========
// Must use these UUIDs and BLE characteristics
BLEService uartService = BLEService("6E400001B5A3F393E0A9E50E24DCCA9E");
// create characteristics
BLECharacteristic rxCharacteristic = BLECharacteristic("6E400002B5A3F393E0A9E50E24DCCA9E", BLEWriteWithoutResponse, 20);   // == TX on central (android app)
BLECharacteristic txCharacteristic = BLECharacteristic("6E400003B5A3F393E0A9E50E24DCCA9E", BLENotify , 20);   // == RX on central (android app)
 

// Setup function - run once at reset or power-on
void setup()  {
  Wire.begin();   // join i2c bus (address optional for master)
  delay(100);
  Serial.begin(115200);
  // initialize the Reset button on D2 and the Blue Grove LED on D3
  pinMode(resetPin, INPUT);   //use the switch which has a pull-up resistor aand connects to ground
  pinMode(ledPin, OUTPUT);   // use the LED on pin 3 as an output

  // set advertised local name and service UUID:
  blePeripheral.setLocalName("4WD_RV");   //make unique name
  blePeripheral.setAdvertisedServiceUuid(uartService.uuid());

  // add service and characteristic:
  blePeripheral.addAttribute(uartService);
  blePeripheral.addAttribute(rxCharacteristic);
  blePeripheral.addAttribute(txCharacteristic);

  // assign event handlers for connected, disconnected to peripheral
  blePeripheral.setEventHandler(BLEConnected, blePeripheralConnectHandler);
  blePeripheral.setEventHandler(BLEDisconnected, blePeripheralDisconnectHandler);

  // assign event handler for characteristic
  rxCharacteristic.setEventHandler(BLEWritten, rxCharacteristicWritten);

  // begin advertising BLE service:
  blePeripheral.begin();
  //Serial.println(("Bluetooth device active, waiting for connections..."));  // for debugging
  
 /* Initialise the IMU and create an Interrupt service routine to deal with a shock - indicating a crash
  * Shutting off the electric motors after a big shock can help to minimize damage to rover motors and circuitry
  */
    
  CurieIMU.begin();
  CurieIMU.attachInterrupt(CrashEventCallback);   // Enable Shock Detection 
 /* 
  * The Threshold value should be adjusted through some experimentation 
  * so that crashes are detected at reasonable shock levels
  * this is to ensure the safety of the operator and to minimize stress on the motors
  * typical values for the function are from 1500 - 2000
  * there were updates to the Arduino 101 firmware and CurieIMU library in July 2016
  * please ensure you have the latest version using the board manager of the Arduino IDE
  * 
  */
  CurieIMU.setDetectionThreshold(CURIE_IMU_SHOCK, 1900);   // 2.0g = 2000mg; 1.5g = 1500mg, etc.
  CurieIMU.setDetectionDuration(CURIE_IMU_SHOCK, 50);   // 50ms
  CurieIMU.interrupts(CURIE_IMU_SHOCK);
  //Serial.println("IMU initialization complete, waiting for events...");   // for debugging

  //Serial.println("LCD setup begin");    // for debugging
  // set up the grove RGB LCD's number of columns and rows:
  lcd.begin(16, 2);
  lcd.setRGB(colorR, colorG, colorB);  

  // Print message headers to the LCD.
  lcd.setCursor(0,0);  // cursor to home position - it starts there...
  lcd.print("motor speed % =");
  //lcd.setCursor(0, 1);
  //lcd.print("speed  = ");

  // Initialize the motor controllers  
  MotorDirectionSet(0b0000);  //0b0000  stopped 
  delay(100);
  MotorSpeedSetAB(vSpeedSet,vSpeedSet); // on a scale of 1 to 100
  delay(100);  
}  // end setup

void loop()  {
 /* Key sections of the loop
  *  Check if we are in a "crash" condition
  *  if not crashed:
  *    poll for a new value written into state by the rxCharacteristic
  *    check if there is something new in "state"  
  *      if yes call rovercontrol
  *      if no call speedcontrol
  *  if crashed: 
  *    look for a reset button press
  */
  if (crash == false) {   //rover is operating normally
    blePeripheral.poll();
    if(state != prev_state) {   // check if the value from the remote control new or the same as the previous one?
      RoverControl(state);   // function to respond to control state changes - direction and/or speed
      //Serial.println(char(state));
      prev_state = state;
    }
  }
  else {       //rover is in a crashed state
    if (digitalRead(resetPin) == HIGH)  {   // look for reset condition - this occurs from a push button after a crash
      CrashRecovery();  // function to recover from crash, start operating normally again
    }
  }
}

void blePeripheralConnectHandler(BLECentral& central) {   // central connected event handler
  Serial.print("Connected event, central: ");
  Serial.println(central.address());
  digitalWrite(ledPin, HIGH);     
  //Serial.println("LED on");
}

void blePeripheralDisconnectHandler(BLECentral& central) {   // central disconnected event handler
  Serial.print("Disconnected event, central: ");
  Serial.println(central.address());
  digitalWrite(ledPin, LOW);      
  //Serial.println("LED off");
  state = 'c';
  RoverControl(state);            // stop rover on BLE disconnect - should reduce runaway rover incidents
  //Serial.println(char(state));
  delay(100);
}

void rxCharacteristicWritten(BLECentral& central, BLECharacteristic& characteristic) {
  // central wrote new value to characteristic, update LED
  //Serial.print("Characteristic event, written: ");
  if (characteristic.value()) {   // NULL pointer check
    state = *characteristic.value();   // de-reference to get first byte
    //Serial.println(char(state));
  } 
}

// Functions to set the 2 DC motor's speed: motorSpeedA: the DC motor A speed; should be 0~100, motorSpeedB: the DC motor B speed; should be 0~100;
void MotorSpeedSetAB(unsigned char MotorSpeedA , unsigned char MotorSpeedB)  {
  MotorSpeedA=map(MotorSpeedA,0,100,0,255);
  MotorSpeedB=map(MotorSpeedB,0,100,0,255);
  Wire.beginTransmission(I2CMotorDriverAdd); // transmit to device I2CMotorDriverAdd
  Wire.write(MotorSpeedSet);   // set pwm header 
  Wire.write(MotorSpeedA);     // send pwma 
  Wire.write(MotorSpeedB);     // send pwmb    
  Wire.endTransmission();    // stop transmitting
}

// set the direction of DC motor. 
void MotorDirectionSet(unsigned char Direction)  {   //  Adjust the direction of the motors 0b0000 I4 I3 I2 I1
  Wire.beginTransmission(I2CMotorDriverAdd);   // transmit to device I2CMotorDriverAdd
  Wire.write(DirectionSet);   // Direction control header
  Wire.write(Direction);   // send direction control information
  Wire.write(Nothing);   // need to send this byte as the third byte(no meaning)  
  Wire.endTransmission();   // stop transmitting 
}

void RoverControl(char state) {
 /*
  * Respond to changes in direction - forward, left, stop, right, backward from Bluetooth LE Remote Control
  *
  * With a new "state" from the remote control - check for action to take
  * a - set gear to go forward (drive)
  * b - set gear to turn left and go forward
  * c - set gear to stop (park)
  * d - set gear to turn right and go forward
  * e - set gear to go backward (reverse)
  * 0 - set speed to 0 - not implemented in the remote control
  * 1 - set speed to 25%
  * 2 - set speed to 50%
  * 3 - set speed to 75% 
  * 4 - set speed to 100%
  */
  if (state == 'a') {
    MotorDirectionSet(0b1001); }  //0b1001    // If state is equal with letter 'a', Motors Rotating in the forward direction, rover will go forward 
  else if (state == 'b') {
    MotorDirectionSet(0b0001); }  //0b0001    // If state is equal with letter 'b', right motors rotating forward, left motors off, , rover will turn left
  else if (state == 'd') {
    MotorDirectionSet(0b1000); }  //0b1000    // If state is equal with letter 'd', left motors rotating forward, right motors off, rover will turn right
  else if (state == 'c'){
    MotorDirectionSet(0b0000); }  //0b0000    // If state is equal with letter 'c', Motors off - stop the rover
  else if (state == 'e') {
    MotorDirectionSet(0b0110); }  //0b0110    // If state is equal with letter 'e', rover will go backward, both motors rotating backward
  else if (state == '0') {   // Change speed if state is equal from 0 to 4. Values must be from 0 to 100 - this is mapped to 0 to 255 (PWM) by the MotorSpeedSet function
    vSpeedSet=0; }
  else if (state == '1') {
    vSpeedSet=25; }
  else if (state == '2') {
    vSpeedSet=50; }
  else if (state == '3') {
    vSpeedSet=75; }
  else if (state == '4') {
    vSpeedSet=100; }
  delay(10);
  vSpeedLimit = vSpeedSet;
  MotorSpeedSetAB(vSpeedSet,vSpeedSet);   // set motor speed based on changes on a scale of 1 to 100
  lcd.setCursor(0, 1);
  lcd.print("   ");
  lcd.setCursor(0, 1);
  lcd.print(vSpeedSet);
  delay(10);
}


// function to reset the Rover after a crash - called when the reset push button is pushed
void CrashRecovery(void)  {
  MotorDirectionSet(0b0000);   //0b0000  motor stop
  vSpeedSet = 50;
  vSpeedLimit = vSpeedSet;
  MotorSpeedSetAB(vSpeedSet,vSpeedSet);   // restore motor speed to previous value - on a scale of 1 to 100
  // restore the LCD display to its precrash values
  lcd.setRGB(0,255,0);   // make backlight green
  lcd.setCursor (0,0);
  lcd.clear();
  lcd.print("motor speed % =");
  lcd.setCursor(0, 1);
  lcd.print(vSpeedSet);
  delay(10);
  crash = false;
  state = 'c';
}

// Callback function from the CurieIMU ShockDetect Sample
static void CrashEventCallback(void) {
  if (CurieIMU.getInterruptStatus(CURIE_IMU_SHOCK)) {
    // Here is where to define what to do in crash situation
    // should definitely turn off the motors for safety
    MotorDirectionSet(0b0000);  //0b0000  stop motors
    delay(10);
    MotorSpeedSetAB(50,50);  // set speed to 50 on a scale of 1 to 100
    lcd.setRGB(255,0,0);   // make backlight red
    lcd.setCursor(0,0);   // reset cursor to upper left
    lcd.clear();   // clear display
    lcd.println("CRASH!!!!!!");   // write crash message to lcd
    delay(10);
    state = 'c';
    prev_state = 'c'; 
    crash = true;
  }
}

Schematics

Rover Base Model schematic
Rover%20basic%20schematic
Rover Final Schematic
Rover%20option%20schematic

Comments

Similar projects you might like

Arduino101 BLE Autonomous Rover

Project tutorial by 4 developers

  • 15,101 views
  • 2 comments
  • 32 respects

Arduino 101 BLE Rover Remote Control

Project tutorial by shadeydave

  • 4,715 views
  • 4 comments
  • 12 respects

SMS alerts for arduino 101 BLE

Project tutorial by Alexis Santiago Allende

  • 2,073 views
  • 0 comments
  • 8 respects

Intel Arduino 101 BLE Blynk Tank

Project tutorial by Johnathan Hottell

  • 10,813 views
  • 6 comments
  • 42 respects

Arduino/Genuino 101 BLE Scanner

Project tutorial by Nenad Cetic

  • 3,686 views
  • 2 comments
  • 11 respects

Rocky Rover: Robotic Vision System PixyCam & Arduino 101

Project tutorial by Ron Dagdag

  • 15,075 views
  • 7 comments
  • 104 respects
Add projectSign up / Login