Introduction
Earlier in the 2013 year while working with the Emerging Experiences group at Razorfish I had the opportunity to do some work with the Netduino, Arduino, and several other technologies that seem to play a significant role in the Maker Movement. Among other things I had converted an RC car to be controlled over Bluetooth with a Windows Phone. A few months ago someone saw a video of the car and asked me how I did it. The comments section on a video wasn't large enough to contain what I thought to be a sufficient response, so I'm writing this article instead.
Recognizing that there's a spectrum of backgrounds and experience levels that one may have when approaching this I've decided to start over from square one and make some changes along the way. The original RC to Bluetooth conversion was done with a Netduino Plus 2. This time around I will be using the Arduino because it is more ubiquitous and generally less expensive. For this revision of the car I'm staying away from the other improvements that could be added for the sake of simplicity. I'm also keeping my focus narrow and staying on the path to get the car up and running. Right now the goal is to cover what is minimally sufficient to get the car running with an understanding of what is going on. In later writings I plan to make improvements on the UI of the control application and add more capabilities to the car.
Prerequisites
To understand this article you'll need to have an understanding of basic digital electronics and programming. You don't need to know or remember the difference between CMOS and TTL gates to understand whats being written here. I'm staying as basic as I can. You will want to have familiarity with one of the C based languages (C, C++, C#) to understand what is being written within this article. I'm using Windows Phone 8 for this first revision of the article but I may return to add Android and iOS in later revisions.
What is the Arduino
I'm going to borrow from a description of the Arduino that I gave before. The Arduino is a single board computer based on the 32-bit Amtel ARM processor. The development environment for it is free (C++ based) It has the support of a large community of hardware and software developers ranging from professionals to hobbiest. From the software side there’s a number of existing solution components that are included in the development software or are available for download. From the hardware side you’ll find a wide range of hardware additions and accessories that can be added to a solution by plugging them into the Arduino.
While there are several forms that are recognizable as an Arduino there are new variants being released be various companies and individuals all the time. Information on constructing them is readily available and the required parts are easily acquired. Most of the Arduinos available available preconstructed will take on a common form and have a name that indicates it's configuration. To avoid the inundating number a variants in the Arduino I'm going to only consider Arduino boards that are available through The Arduino site at the time of this writing.
Any of these can be called an Arduino
How is one Arduino Different from Another?
Just as PCs are available with different configurations (different amounts of storage, ports, so on) so are Arduinos. The differences tend to be in processor speed, the number of different types of ports, or other peripherials. Here are some of the hardware components that may be on an Arduino. Note that note all Arduinos will have all of these available. You can find a table of some of the available Arduino configurations on the Arduino site.
Input/Output Voltages
Many of the peripherals that you connect to are going to expect (or produce) input and output signals that are either 3.3 volts or 5 volts. Some devices can work either either, some cannot. So you'll want to make sure that the Arduino that you use is mated with peripherals that have agreable signal voltages. Some Arduinos will have a switch that will let you change whether it is operating in the 3.3 volt range or the 5.5 volt range. If you want to interface two devices that operate on different voltages without risking damage additional circuits will be needed.
The Seeeduino
Arduino Mega Adk that I'm using runs in 3.3V or 5V mode. But I must remember to ensure the switch is set to the needed voltage.
Processor Clock Rates
The Arduino boards available presently range in speed from 8 MHz up to 84 MHz. 16 MHz is the most typical.
USB Ports
Most of the Arduino boards have a USB port for programming and for getting information back from the Arduino. When connected to your computer it will appear as another Serial port. So you could use any terminal software that uses a serial port for interacting with the Arduino. Some of the Arduinos can emulate other devices such as keyboards or mice. Some Arduinos also have a USB host port that will allow it to interact with other USB peripherals.
Digital Port
Quickly glancing at the Arduinos that I have on my desk right now the number of ports that they have range from a little more than 5 to a little under 50. The digital ports can act as both outputs or inputs. When running in output mode they can programmatically be set to either a high or low voltage. When operating as an input a program can see if something connected to the Arduino is outputting a high or a low voltage. This extremely basic ability is an important building block for other capabilities. Turning on a light or motor or even communicating with other devices can be done with basic operations over a digital port. Some of the digital ports also support interrupts. So you could associate that port with a routine that automatically gets called if the ports status changes.
Analog Port
If you need to set a pin's output to something between high and low then the analog port is what you'll want to use. This can be used to control the speed of a motor instead of turning it on or off, or to control the brightness of a light. When used as an input an analog port can be used to detect the position of a potentiometer or the intensity of light on a sensor .
PWM Port
The PWM ports are able to output square waves. In general the signals outputted on these ports will all have the same frequency. But the pulse width of the wave can be varied. The following two waves have the same frequency but the widths of the waves differ.
The percentage of time that the wave spends in its high state is also called the wave's duty cycle. When a PWM signal has a 0% duty cycle it is always off. When it has a 100% duty cycle it is always on. PWM signals can be used to control the speed of a motor or the brightness of a lights.
Communications Port
While it's possible to communicate with other hardware through careful timing of turning a port on and off you will generally not want to (or need to) do this yourself. The Arduino supports some of the standard communications protocols used by many other circuits and sensors that you may want to interact with. All of these communications ports work serially; they will send or receive a message one bit at a time.
- Serial Port - Connects a single device to another. Uses two lines for communication, one for sending, another for receiving. Devices that use these ports will have a set speed at which they can send and receive bits (baud rate). Two devices connected with a serial port must agree on the speed at which they will transmit information (baud rate, ranging from 300 baud to 57600 baud) and how a sequesnce is to be terminated (number of stop bits). Devices using serial port communication are able to track time passed so that they can write to or read from the communications lines at the correct rate. Serial ports on an Arduino can either be implemented through hardware or software. Only specific pins can be used for hardware serial ports, but any two pins can be used for the software serial port. However the software serial port cannot send and receive at the same time while the hardware serial ports can.
- I2C - Connects a device to multiple other devices. Like the serial port the I2C port uses two lines from the Arduino. But unlike the serial port (which connects the arduino to only one other device) the I2C lines can be connected to multiple devices. Each device connect to the I2C ports has an address. When interacting with one of the devices the Arduino must send the address of the device that it wishes to respond and only that device will respond. An I2C address is composed of either 7 or 10 bits. So either 128 or 1024 devices could be connected to an I2C lines. Timing for communication is controlled through a clock line (usually labeled SCL) and data, whether transmitted or received, travels over a single data line (labeled SDA). Unlike the Serial Port (which allows data to travel in both directions at the same time) I2C communication is one direction at a time. But the I2C bus speed on an Arduino is at 100 KHz.
- SPI - connects one device to multiple othe rdevices. SPI communication uses four lines; one for clock synchronization, one for data from the master device, one for all the devices that may respond, and a device selection line. Instead of transmitting an address the selection line for the device that is to respond must be driven high (each device will have its own line). The speed of the SPI bus is going to depend on the clock speed of the Arduino and a clock divider value (possible values will depend on the Arduino board being used). The default values on most Arduinos will result in a communications rate of 4 MHz.
What peripherals are Available for the Arduino
You can connect Arudinos to a wide range of products even if those products were not designed with the Arduino in mind. Your ability to do that is going to depend on your knowledge level with the Arduino. But what of the products that were made with the Arduino in mind or can be interfaced with litter effort? There are a number of products that can be used by aligning some pins and stacking them on top of the Arduino. Peripherals like this are referred to as "shields."
- Wi-Fi and Ethernet Shields - host and consume simple web services or control your project over a network ( 1,2 )
- GPRS Radio - have your circuite send text messages, make and receive calls, or access the Internet on the go (1)
- Memory Shields - Read from and write to memory cards (a)
- Bluetooth - One of the many ways that you can communicate with the Arduino wirelessly. One of the best options for devices controled by other mobile devices. (1).
There are other devices that while not made with the Arduino in mind interface to it with little effort because they implement a simple or common interface
- Bluetooth
- Servos
- Relays
- LCD Displays
Getting Started
Let's start working towards a "hello world". For the Arduino this will mean using it to turn on a light. You'll need the following
- Computer (PC, Mac, Linux) with a free USB port
- An Arduino board
- Light emiting Diode
- Resistor (about 220 Ohms)
- Arduino IDE
Software Installation
The software is available for Linux, OS X, and Windows. I'm using the Windowa version which can be installed in one of two ways. The method I would suggest is downloading the "installer" version of the download. Like many other Windows installations the only thing the installer requires you to do is click on a "next" button a few times. The Zip version of the download will let you unzip the IDE to the arbitrary place of your choosing. After the installation has completed connect the Arduino board to your computer with a USB cable. Arduino's usually ship with a program preloaded that blinks a light on the board. Once plugged into the USB you should see the light on the Arduino strt to blink and the drivers should install.
It's possible that you might not be prompted for the drivers. I've connected the Arduino Mega to my computer and it wasn't recognized (unlike the other Arduinos it uses a different hardware component for it's connection). It originally showed up in the device manager as an unrecognized device. The drivers can be found in the Arduino application folder.
Creating a New Program
Start the Arduino IDE. By default the Arduino IDE will open blank and ready for you to start writing code. If you wanted to start another blank project selecting New
from the File
menu will open a new instance of the IDE. If you are using version 1.5.5 of the IDE (a beta at the time of this writing) then the window will open with two empty functions defined. If not then the window will be completely blank. If it's blank trype the following code.
void setup() {
}
void loop() {
}
We are going to compile this code and upload it to the board. We'll need to tell the IDE what board that we have and which com port that it's attached to. Under the Tools
menu there is a Board
submenu that contains the Arduino Board models supported by the IDE. Select the board name that matches yours. You'll need to do the same thing for the port. If you don't know which port your Arduino is using unplug it and access the menu again to see which port name has disappeared from the listing. The one that has been removed is the one that your Arduino board is using.
In the toolbar there is a button that you can use to check to code for syntax errors (a check mark) and a button that can be used to upload the compiled program to the Arduino (an arrow).
Click on the arrow. If all goes well at the bottom of the IDE you will receive a message that says "Upload Successful" and the light should stop blinking. Let's update the program so that we can blink the light ourselves. To do this we need to configure a pin as an output pin. Many times you can use the same pins across different Arduino boards. It's a good practive to define the pins being used in a #define section or as a variable instead of using the literal pin number. If it never needs to be changed then it can be changed in one place. Pin 13 is usually connected to a led. Writing a HIGH
value to it will turn the led on. So let's defined the pint that we are using to control the light as 13.
#define LIGHT_PIN 13
Then we write a HIGH or LOW value to it. Initialization steps are done in the setup function. Pin 13 could either be an input or output pin. We need to set it as an output pin using the pinMode
function. When the device first starts (or is restart) it first runs the code in the setup()
function. It will then run the code in the loop
function until the device is turned off. In the loop we want to turn the light on, wait for a second (1000 ms), turn the light off, and then wait for another second.
#define LIGHT_PIN 13
void setup() {
pinMode(LIGHT_PIN, OUTPUT);
}
void loop() {
digitalWrite(LIGHT_PIN, HIGH);
delay(1000);
digitalWrite(LIGHT_PIN, LOW);
delay(1000);
}
Deploy the application again. After a few seconds the light should start to blink. Congradulations! You've completed your first Arduino program. Before moving on let's make a small change. Instead of just turning it on and off let's adjust the brightness so that it fades in and out. For this we'll need to start adding external circuitry. For this next part you'll need a LED (light emitting diode), a resistor with a value in the range of 300 Ω to 1 KΩ value, and a breadboard. The resistor is for lowering the current that goes through the LED to ensure that we don't burn anything out with too much current. We are going to adjust the brightness of a led by using the analogWrite()
function to change the amount of power supplied to the ligh. Pin 13 on many Arduinos doesn't support the analogWrite()
function.
You need to look up which pins on the Arduino that you are using can be used for PWM output. Go to the Arduino Products Page and click on the model for the Arduino that matches the one you have. On the page that opens search the page for "Input and Output" and look for the line in that section about PWM. The pin numbers that you can use will be listed there. Below is the information for some of the arduino boards
Board Model | Available Pins | Resolution |
Arduino Uno | 3, 5, 6, 9, 10, and 11 | 8 bit |
Due | 2-13 | 8-12 bit |
Mega ADK/Mega 2560 | 2-13, 44-46 | 8-bit |
Yún | 3, 5, 6, 9, 10, 11, and 13 | 8 bit |
Nano | 3, 5, 6, 9, 10, 11 | 8 bit |
See analogWriteResolution() for information on changing resolution. |
About using a Bread Board
If you've used a bread boardbefore you can skip this section. Breadboards allow you to prototype a circuit more quickly and reduce the amount of wires that you would need to construct the same circuite without a breadboard. Underneath the holes in the breadboard are conductors that electrically connect components. The The conductors all run paralle to each other and are usually perpendicular to the length of the breadboard. The exception being the powerlines which run down the length of the board. There's a depression down the center of the board. The conductors do not cross this depression. Any components that are inserted on the same row (the rows are numbered) that are on the same side of the depression are connected. jumper wires need to be plugged in to connect components that are not on the same row and the same side of the divide.
Closeup of a breadboard. The red outline mars sets of holes that have a common conductor underneath.
Wiring the LED to the Arduino
LEDs
will only work if current passes through them in the correct direction. There are two wires on the LED that are of different length. The longer of the two wires must be connected to a positive source while the shorter to a negative. Use a jumper to connect one of the pins labeled GND to a row of wires on the bread board. Plug the LED into the board so that the shorter wire is in the same row as the GND
wire. Connect the resistor into the board so that one of the wires is in the same row as the long wire of the LED
and the other end is in an unoccupied row of the breadboard. Using a second jumper wire to connect the other end of the resistor to the pin that you are using for PWM
.
Analog Code
Once the circuit is wired we can write the code to control the brightness of the led. When we wrote the previous program it was necessary to use use pinMode
to set the pin to output mode before controlling it. It isn't necessary to do this when using analogWrite
on a pin. We'll use the PWM
port in 8-bit mode (which is much more widely supported than 10-bit mode). The variable containing the value to be written to the pin is going to start at 0, count up to 255, and then back down to zero. Each new value will be sent to the PWM pin. The result is that the LED will grow bright and then dim in cycles.
#define LIGHT_PIN 13
int brightness = 0;
int delta = 1;
void setup() {
}
void loop() {
analogWrite(LIGHT_PIN, brightness);
brightness+= delta;
if(brightness == 255)
delta = -1;
else if(brightness == 0)
delta = 1;
delay(10);
}
Input and Output with the Serial Port
The serial port can be used to communicate with peripherals connected to the Arduino or to communicate back to the computer. There will be 1 to 4 hardware serial ports on your Arduino board. They will be named Serial
, Serial1
, Serial2
, and Serial3
. Each of these ports (if present in Arduino board) will be assigned to specific sets of pins. You can also instantiate software serial ports. Software serial ports can be assigned to any two pins. The hardware and software serial ports appear similar in capabilities. But the software serial port can only send or receive at any given point in time but can't do both simultaniously. The hardware serial serial port can do both at the same time.
The port Serial
is connected to the computer. Communication between your computer and the Arduino occurs through this port when your programs are uploaded to the Arduino. You can also make your own applications on a computer that interact with a connected Arduino by opening the appropriate port. The computer and the Arduino will need to agree on a baud rate (data transfer speed).
The Arduino IDE has an interface called the Serial Monitor (can be opened by pressing CTRL
+Shift
+M
or from the menu path Tools
->Serial Monitor
) that can be used to see the values coming back from the serial port or being written to it. For a simple demonstration we'll have the arduino count from one to one hundred and output it to the serial port.
void setup() {
Serial.begin(9600);
}
void loop() {
for(int i=0;i<=100;++i)
{
Serial.println(i);
delay(100);
}
}
After you upload the program open the serial port monitor. You'll see the stream of numbers coming back. Here I want to point out that there's a difference in behavior in how the Arduino Yun, Micro and Arduingo Leonardo behave when compared to the other Arduinos. Many of the other Arduinos use a chip that is separate from the main processor for handling communication between the computer and the Arduino's processor. When you open the Serial Port Monitor on these boards the device will reset. So you'll be able to catch all of the information that the devices outputs from begining to end. On the Leonardo, Micro and the Yun the serial communication is not being handled by a separate chip. The Amtega processor in these devices have a built in USB interface that presents itself as a serial port and can also present itself as a mouse, mouse, and serial port. While the serial connection can remain open on the other devices when they are reset if the processor is reset on the Yun, Mico, or Leonardo the virtual serial port would disappear while it restarts and the computer's connection to it will be lost. So these devices do not reset when you open a serial connection from your computer and the first few numbers that are written will not show up in the serial monitor. You could have the processor wait until the serial port is open before it starts writing numbers with a slight modification to the program. The statement if(Serial)
wrapped around the code we just wrote to prevent it from executing until the serial port is open.
void setup() {
Serial.begin(9600);
}
void loop() {
if(Serial)
{
for(int i=0;i<=100;++i)
{
Serial.println(i);
delay(200);
}
}
}
All the programs we have done until now have been non-interactive. We are about to do our first interactive program. In this one the Arduino will listen for text to come in over the serial port and will turn the light on or off in response to it. The program will also send a small bit of feedback on what it's doing. In the updated program the delta
variable is going to default to zero. So the light's brightness will not start changing as soon as the program starts up. Instead when this program received the character '1'
on the Serial
object it will change delta
to one so that the brightness will grow until it reaches its maximum value (then delta is reset back to 0 to prevent the brightness level from changing). When the program receives the value '0'
it will set delta
to -1 until the brightness variable reaches its minimum value. All other characters received through the Serial
object will be ignored. When the program does receive a value that it's processing it will print back a message that will show up on the computer. Here is the source code to the updated program.
#define LIGHT_PIN 13
int brightness = 0;
int delta = 0;
void setup() {
Serial.begin(9600);
}
void loop() {
if(Serial.available())
{
char c = Serial.read();
if((c == '1')&&(brightness!=255))
{
delta = 1;
Serial.println("Turning Light On");
}
else if((c=='0')&&(brightness!=0))
{
delta = -1;
Serial.println("Turning Light Off");
}
}
brightness+= delta;
analogWrite(LIGHT_PIN, brightness);
if(brightness == 255)
delta = 0;
else if(brightness == 0)
delta = 0;
delay(10);
}
Run the program and open the Serial Port Monitor. From the monitor type the value 1 and press enter. You'll receive a confirmation back and will see the light's brightness begin to grow. Send it a 0 and you'll see the light's brightness diminish until it's extinguished.
Servos
There is only one more concept to cover before we are ready to interface to the car. And that is the concept of the servo. Servo is short for servomechanism. The term can refer to any device that uses negative feedback to correct its performance in achieving its built in function. Most of the time when some one mentions a servo in the context of an Arduino the person is refering to a servomotor. Servomotors receive a signal that coresponds to a desired position. The servo will rotate its shaft until the desired position is reached and will hold its position. RC (Remote Control) servos receive the position signal as a PWM signal. While you could directly send the PWM value for a desired position using analogWrite
it is usually easier to use the <Servo.h>
library instead. It will take care of the mappings between the angular position that we want and the value that would need to be sent through analogWrite()
for that position. Just as analogWrite()
will only work on certain ports the <Servo.h>
library will only work on certain ports. Also note that with the exception of the the Arduino Mega use of the <Servo.h>
library will disable the use of analogWrite()
on ports 9 and 10, even if you are not using those ports to control a servo.
Method | Description |
void attach(pin, [max, min]) | Attach the servo variable to a pin. One of the overloaded versions of this method can be used to adjust the minimum and maximum pulse width. |
void write(angle) | Writes a value to the servo setting the shaft position accordingly. |
int read() | Returns the last value written to the servo. |
void detach() | Detaches the servo object from the pin. |
RC servos have three wires. Two of the wires will be for power. A red wire must be connected to a positive power source and the black or brown wire must be connected to the ground (GND). The third wire is connected to the PWM source. If you are going to drive several motors you will want to have the motors connected to a power source that can handle enough current. But we can connect a small servo to the Arduino without much concern.
This program will be much like the previous one. Only instead of controlling brightness we will be controlling the position of the motor. Servos generally have a range of 180 degrees. The minimum value is 0, the maximum value is 180, and middle value is 90. The exact range of your servo may vary. Don't try to drive the servo past its physical limit. Doing so can cause the servo to overheat. Confession: I burned out somewhat expensive servo this way when I was first programming with on.
In the previous program I used a delta variable to increment or decrement the brightness value. While you could do this for the Servo it is not necessary unless you need to control the rate of the turn. For light brightness jumping from one value to another will result in what looks like a simultanious change in brightness. The servo moves at a limited speed. Jumping from one value to another will result in in the servo changing position over time. There's no instantanious transition in position. So in this program we will not worry about incrementing the position over time and will just jump to the position that we need. Because there are positions between absolute left and absolute right in which we will have interest this program will respond to values between 0-9 instead of just 0 and 1.
#include <Servo.h>
#define MOTOR_PIN 13
Servo steeringServo;
void setup() {
Serial.begin(9600);
steeringServo.attach(MOTOR_PIN);
}
void loop() {
if(Serial.available())
{
char c = Serial.read();
switch(c)
{
case '0': steeringServo.write(0); break;
case '1': steeringServo.write(20); break;
case '2': steeringServo.write(40); break;
case '3': steeringServo.write(60); break;
case '4': steeringServo.write(80); break;
case '5': steeringServo.write(100); break;
case '6': steeringServo.write(120); break;
case '7': steeringServo.write(140); break;
case '8': steeringServo.write(160); break;
case '9': steeringServo.write(180); break;
default:
break;
}
}
else
delay(100);
}
Connect a servo to your Arduino. The red wire should be connected to the pin labeled 5V and the black/brown wire should be connected to one of the pins labeled GND. The remaining wire is to be connected to the PWM pin that you are using. Run the program and open the serial monitor. As you are sending the values keep an eye on your servo to ensure it's not trying to go past it's physical range. If it does immediately return it back to a position that is within its range.
Controlling the Drive Motor
Something to keep in mind before you start activating the drive motor on your vehicle. Make sure you lift the drive wheels so that they are not touching anything. Last thing you'd want to happen is for the car to take off at full speed while it's still connected to your computer or on a desk. Depending on your car you may be able to do this by turning it upside down. I took some camera rig parts that I had laying around and assembled them to support the vehicle.
RC car supported by camera rig pieces.
The drive motor on the car that I'm uses uses an ESC (Electronic Speed Control) unit. The ESC does a couple of things. It contains a power regulator that's outputting 5 volts from the 7.2 voltage from the battery and it activates the drive motor. The 5 volt output is also routed to the steering servo and the radio receiver/control circuit. We are unplugging the car's receiver and connecting the Arduino in it's place. If we use the same program that we used to control the servo but connect the arduino to the ESC instead of the servo we can use it to control the motor's speed. We are going to wire the two together a little differently this time. The ESC is going to need to be connected to it's own power source (the car's battery). There Arduino would not be able to power the drive motor. For this test you do not need to connect the red wire. If you want to connect the red wire anyway connect it to the VIN
pin so that the voltage from the ESC is passed through the Arduino's voltage regulator.
After you are done with the wiring run the program again. This time you should be able to make the car's drive motor go in forward and reverse. If your ESC behaves as mine does if you try to go from forward to reverse without going through the neutral position the ESC will interpret it to mean you want the vehicle to brake. So you may need to select the neutral position before you change directions.
The ESC that I am using is the Dynamite Tazer 15T. Despite documentation that is available on the Internet (PDF) this ESC does not allow any calibration. If you are using another esc calibration may be required so that the esc knows what speeds />directions to map to the range of values that it needs to work with. Since I don't have an ESC that supports calibration I won't be able to address that myself within this document.
The Dynamite Tazer 15T
Bluetooth Communication
There are a number of Bluetooth solutions available that will work with the Arduino. Some are available as shields. I didn't want to use a shield as a solution since it would restrict which pins that I could use for bluetooth communication. I used one that needed to have its pins connected to the arduino individually. Most of these will have 4 connections that you'll care about. Two of them are for power. They will need to be connected to the 5 volt line and the GND
. The other two lines are for data to be sent and data received. We are also finally to the point where we are going to make use of the Windows Phone. Before we get started with coding go ahead and connect your power and GND
to power it up and pair your phone with it. On the bluetooth device that I'm using the pairing code is 1234
. You'll need to check with the documentation for the Bluetooth circuit that you are using to know what code to use.
Picture of the Bluetooth Module that I am using
You'll need to write code that will do a a few things
- Get a list of the paired bluetooth devices
- Select the paired device to be used for communication
- Open streams to the device for listening and sending information
For getting a list of the Bluetooth devices paired with the phone Microsoft has provided the PeerFinder
class. You'll need to add the Proximity
permission to your application to use this class. The list of devices paired with the phone can be retrieved with two lines of code.
PeerFinder.AlternateIdentities["Bluetooth:Paired"] = "";
var pairedDevices = await PeerFinder.FindAllPeersAsync();
For the sake of improving the user's experience you would probably want to give your Bluetooth receiver a distinct name that the application could look for to distinguish it from the other Bluetooth devices. On many of the Bluetooth receivers it's possible to change the name that the device uses. On the one that I have handy I've noticed that the company that produces it seems to have explicitly stricken out the portion of the documentation that explained how to change my adapter's ID (probably appropriate, as the device isn't responding to name change commands). I'm going to stick with the name that seems to be burned into my device, linvor
. Check the documentation in your Bluetooth adapter to see what name you need to use.
The pairedDevices
collection in the above code will contain PeerInformation
instances. Grab the one that represents your device and use it to create a new Socket
object. The Socket
will be used for reading from and writing to the Bluetooth adapter.
_socket = new StreamSocket();
await _socket.ConnectAsync(_selectedDevice.HostName, "1");
Now we can send and receive messages between the Windows Phone and the car circuit. Read and write operations are asynchronous. Using the InputStream
member of the socket we can initiate a read operation. If there is nothing to read the call will [perceivably] wait until there is data available to be read (the nuances of what really goes on while interesting is beyond the scope of this document). Once the bytes are available and read I'm converting them to a string for later processing.
byte[] bytes = new byte[128];
await socket.InputStream.ReadAsync(bytes.AsBuffer(),(uint)bytes.Length, InputStreamOptions.Partial);
bytes = bytes.TakeWhile((v, index) => bytes.Skip(index).Any(w => w != 0x00)).ToArray();
string str = Encoding.UTF8.GetString(bytes, 0, bytes.Length);
OnMessageReceived(str);
For this first iteration of the project the car doesn't need to send back any vital data. It's only going to be listening for instructions for what it should be doing. For now the only instructions to send are of what the state of the throttle and the steering servo should be. I'm sending the commands in strings that look like a REST request.
public void TransmitState()
{
if ((_communicationController != null)&&(_communicationController.IsConnected))
{
_communicationController.Write(String.Format("/car/throttle/{0}\r\n", ThrottleValue));
_communicationController.Write(String.Format("/car/steering/{0}\r\n", SteeringValue));
}
}
With that the basics of what need to happen on the Windows Phone side for communicating with the car have been covered. There is still work to on deciding on a user interface in the application. Let's go back to the car to implement the code for responding to the received commands.
Don't Let the Car Get Away!
Since we are approaching a point at which the car is almost prepared to be sat down on the ground to be driven there's something I want to address. You must be prepared to respond to a loss of a connection. If you don't the car could end up continuing to do the last thing that it was doing until it gets a connection again. If the last thing it was doing was going ahead full throttle chances are you won't be able to reestablish a connection without sprinting after the car. There are a couple of solutions that come to mind to handle this. On many of the Bluetooth receivers that I've seen there is an additional line that connects to a led that will change its blinking sequence to indicate whether or not the Bluetooth receiver has a connection. It's possible to connect to this and put the car in a neutral state if it indicates that the Bluetooth connection has dropped. Another method (the one that I will go with) is to keep track of how long it's been since an instruction was received. If more than a certain amount of time passes and no instructions are received stop the car.
The millis()
function returns how much time has passed since the Arduino was started in milliseconds. Every time an instruction is received we can save the value returned by this function and use it as a reference for knowing how long it's been since an instruction was received. With each execution of the main loop the the millis()
function is called again and compared to the saved value. The difference between the two values is the amount of time passed since the last instruction. Once the difference has become greater than a certain amount we know it's been a while since an instruction was received. The car will be put in a neutral state. If an instruction is received before this condition occurs then the saved value would have been updated so the difference between the saved time value and the current time value would not be great enough to reach the expiration period.
Parsing the Commands
Parsing is easy since all the commands are being sent in what looks like a rest request. Each segment of the path in the REST like request is associated with a method. Each time an instruction is received it is passed to ProcessCommand(string)
which will parse out the first segment and use it to decide which method to call next. Right now all of the instructions will start with /car/
. The root level method in the processing call stack only checks to see if an instruction starts this way and does nothing if the command is something else.
void ProcessCommand(String command)
{
int startPosition, endPosition;
String segment;
startPosition = command.indexOf('/', 0);
endPosition = command.indexOf('/', startPosition+1);
if((startPosition>-1) && (endPosition == -1))
{
segment = command.substring(startPosition + 1, endPosition - startPosition+1);
startPosition = endPosition + 1;
}
if(segment == "car")
ProcessCarCommand(command.substring(endPosition));
}
The ProcessCarCommand()
method is similar. It is testing to see if the next part of the instruction is "throttle" or "steering" and called the ProcessThrottle()
or ProcessSteering()
methods respectively.
void ProcessCarCommand(String command)
{
int startPosition, endPosition;
String segment;
startPosition = command.indexOf('/', 0);
endPosition = command.indexOf('/', startPosition+1);
if((startPosition>-1) && (endPosition>-1))
{
segment = command.substring(startPosition + 1, endPosition - startPosition);
}
if(segment=="throttle")
ProcessThrottleCommand(command.substring(endPosition));
else if (segment=="steering")
ProcessSteeringCommand(command.substring(endPosition));
}
The methods that follow are finally getting to where the rubber meets the road; they are controlling the steering and throttle lines. Their implementation is almost identical. They strip the numeric value off the end of the command (which will be between the values of -1 and 1). The value of 1 for the throttle represents full throttle forward, -1 would be for full reverse, and 0 for neutral. Similarly -1 for the steering is full left, 1 is full right, and 0 is straight ahead. These values are being scaled and shifted before being sent to the Servo object. The continuous value range of -1 to +1 needs to be remapped to the the values 0 to 180. The Arduino function library already contains a function for doing this appropriatenamed map
. It takes 5 arguments. The first is the value to be remapped. The next two are the lower and uper range for the value. The fourth and fifth are the range to which the value needs to be mapped. For our specific case the call will look like map(value,-1,1,0,180)
. The mapped value is then written to the servo object for either the steering or throttle. The code for the two functions follows.
float StringToFloat(String str)
{
char buf[str.length()];
str.toCharArray(buf, str.length());
float retVal = atof(buf);
return retVal;
}
void ProcessThrottleCommand(String command)
{
if(command.length() < 2) return;
float newthrottlevalue=StringToFloat(command.substring(1));
if ((newthrottlevalue < -1)&&(newThrottleValue > 1))
return;
int servoValue =(int) map(newThrottleValue,-1,1,0,180);
throttleESC.write( servoValue );
}
void ProcessSteeringCommand(String command)
{
Serial.println("Processing Steering Command");
if(command.length() < 2) return;
float newsteeringvalue=StringToFloat(command.substring(1));
if((newsteeringvalue < -1) && (newSteeringValue > 1))
return;
int servoValue = map(newSteeringValue,-1,1,0,180);
steeringServo.write( servoValue );
}
Control Interface
All the code needed for sending control signals to the car and to have the car interpret and act on them has been presented. I skipped over discussing a user interface for controlling the car though. Some of the other phone controlled systems I've seen use the accelrometer for control. This is a matter of personal preference, but I don't like the accelerometer based approaches. When playing games on my phone the method of control that prefer second is the virtual joystick with a dead zone that is centered on whereever one's finger happen come in contact with the screen. My most preferred method of control is a physical controller.
I made an extremely simple virtual joystick for the phone in the form of a user control. When some one first place their finger down on the control that point becomes the new center point for the dead zone. Sliding ones finger will move the virtual joystick out of the dead zone and be registered as a direction. Lifting one's finger off of the control will set the virtual joystick back to it's neutral position.
Second best isn't bad, but we can do better. I've got a Moga bluetooth game controller that is compatible with Windows Phone. So I've added support for that too. Normally I would have the code organized so that there are a number of logical joysticks (whether physical or virtual) and then some mechanism for deciding which one of the joysticks will be listened to. I'm avoiding that organization in this article for the sake of simplicity. The code handling input from the Moga is in the same class as the virtual joy stick and there's absolutely no resulution implemented for what to do if some one decides to use both the touch screen and the game controller at the same time.
Moga Bluetooth controller.
There's an SDK at Moga's web site that contains an assembly (Moga.Windows.Phone
) that simplifies interaction with the Moga controller. There are two modes of interaction with the controller, polling and listening. In polling mode every time the code wants to know the state of the controller it will need to query the controller. In listening mode as the state of the controller changes methods on your code are called through an interface indicating what has changed. Xaml based programs will usually want to use the listening mode while DirectX based programs will use polling. To use listening mode my VirtualJoyStick
class implements the IControllerListener
interface. More development information on the Moga series of controllers can be found on their site.
IControllerListener methods |
Method | Description |
void OnMotionEvent(MotionEvent e) | One of the analog inputs on the controller has changed. This includes both thumb sticks and the the analog triggers. |
void OnStateEvent(StateEvent e) | Indicates the connection and battery state of the controller. |
void OnKeyEvent(KeyEvent e) | The pressed state of one of the buttons has changed. |
Remember what I said earlier about responding to the potential loss of the connection between the phone and the car? That also applies to the connection between the phone and the game controller. If the connection to the controller drops (battery goes dead, or the user walks away from the phone) the code on the phone needs to be prepared to start transmitting a neutral state. Otherwise the phone will continue to transmit what ever the last known state of the game controller indicated. One of the IControllerListener
events is used for this, OnStateEvent(StateEvent e)
.
public void OnStateEvent(StateEvent e)
{
if (e.StateKey == ControllerState.Connection)
{
if(e.StateValue == ControllerResult.Disconnected)
{
SetNeutralState();
}
}
}
I've decided to use the left thumb stick for steering and the right thumb stick for the throttle. I'm ignoring the buttons all together.
public void OnMotionEvent(MotionEvent e)
{
this.Dispatcher.BeginInvoke(() => {
if(e.Axis==Axis.Z)
{
Y = e.AxisValue;
OnVirtualJoystickUpdated();
}
else if(e.Axis == Axis.X)
{
X = e.AxisValue;
OnVirtualJoystickUpdated();
}
});
}
Loose Connections
I've said next to nothing about the physical assembly of the finished product. In part I've done this because it's not my final design. Dependind on the car that you have you may be able to screw the Arduino in place on the vehicle. You are not going to want to use loose wires to connect the servo and ESC to the Arduino though; with just a little bit of strees the wires would come loose. Instead check out your local electronics store or other provider to see what prototype shields are available. Using one of those you may be able to securly hold the connections in place through either soldering or with screws.
What's Next
Writing additional code so that the car can be controlled by an Android phone isn't much effort. The same concepts carry over. When time allows I may update this article or add another one to explain what needs to be done to control the car with an Android phone. Also keep in mind that my personal goal is to make the car more autonomous. This means that the car must be able to know something about it's environment and position. In the next update I plan to walk through the various sensors that will be attached to the car and how to use them. These include GPS, distance sensors, and motion sensors such as an accelerometer and gyrometer/rate sensor. Because of the low distance range on bluetooth radios I'll be using a different radio (Wi-Fi) so that more distance can be put between the phone and the car. I may eventually move up to adding a GSM radio.
Revision History
- 2014 February 12 - Initial Publication
RCCarCode.zip (2.36 mb)
alt download site