This Instructable describes a remote control car I've put together. The car has no real purpose - it was just a challenge, to see what could be made with commonly available hardware. It has the following features:
- 4x4 rock crawler chassis
- PC Based Control - The car is controlled through a custom Windows application (C#)
- "Analog" throttle (electronic speed control) and steering, using an XBox 360 controller
- Controller vibration / force feedback effects based on the car's accelerometer
- First Person View Digital video stream
- Movable camera
- Configurable RGB lights (Adafruit NeoPixels)
Watch the embedded YouTube video to see the car in action.
Or, click the direct link: https://www.youtube.com/watch?v=FwRHbJV4zV0
All of the Arduino and C# code is provided within this Instructable. The software has been written specifically to work with the hardware items (eg IP camera) I've used. If you would like to make your own car with different hardware, you will probably need to make minor changes to the C# and/or Arduino code.
Note: This project has a lot of software content in it, and is a long read! (I've tried to add detailed comments into the code, but beginner programmers might find it a little challenging to follow)
Note 2: Skip to the conclusion to see the upgraded version of the car (Raspberry Pi 2 camera + pan/tilt servos)
Step 1: Hardware Overview
The car consists of the following hardware components:
- RC 1/10 scale rock crawler chassis
- Axial Racing Electronic Speed Controller (ESC)
- Steering Servo motor
- Aluminium base plate and aluminium angle for mounting other parts
- Arduino Mega (you will need a Mega for this project)
- Sparkfun Arduino XBee shield
- XBee Series 1 Pro (60mW) wireless module
- Adafruit ADXL335 Accelerometer
- Adafruit NeoPixel stick
- IP camera with Pan/Tilt and IR Night Vision
- Portable Wi-Fi router
- 7.2V NiMH battery (power supply for ESC and steering servo motor)
- 11000mAH dual USB power bank (power supply for Arduino Mega, Wi-Fi router, IP camera, NeoPixel stick)
A laptop is required to communicate with and control the car. The laptop must have the following:
- Windows 7 / 8
- At least 2 USB ports
- Optional - Ethernet port (but if the laptop doesn't have one, you need access to a computer with an Ethernet port)
The following hardware components are also required on the PC side:
- XBox 360 controller (wired)
- XBee Series 1 Pro (60mW) wireless module
- XBee USB Explorer
A full parts list is attached below.
Step 2: RC Car Selection and Physical Modifications
The type of RC vehicle I've chosen for the platform is a 1/10 scale Electric Rock Crawler. RC Rock crawlers are 4x4, have locked diffs, (relatively) high ground clearance and large suspension articulation, so they are less likely to get stuck when driving over obstacles. However, they are generally not as fast as regular RC cars (geared for high torque instead of high speed). The specific model I chose is a HSP Pangolin because it was cheap. It is also a knock-off of the Axial Racing AX10, so it is easy to find compatible spare parts :-P .
The car came with a 7.2V NiMH battery, electronic speed controller (ESC) and a brushed motor. The original ESC had no documentation on how to calibrate it, or program its features. Since I intended to control the ESC through an Arduino, I needed an ESC that could be calibrated and programmed. I found that the Axial AE-2 ESC would be the most suitable replacement, since they have clear instructions for setup and configuration (see http://www.axialracing.com/blog_posts/787000000).
To mount all of the parts, I removed the original plastic battery/ESC tray. I then used some aluminium angle and large aluminium plates to create a flat mounting surface.
The various parts are either screwed to the aluminium plates or cable-tied. For electronic components such as the Arduino, some plastic was placed on the aluminium plate for insulation.
After mounting most of the components onto the car, I found that the original suspension did not cope well with the added weight - the car dropped about 4cm from its original height. To fix this, I purchased and installed four super-firm springs. These springs are much better than the original springs (the car only drops 0.5cm), but are still a little too soft - mashing the throttle will cause the car to list like a sinking boat!
Step 3: Power and Wiring Configuration
The car has two batteries - a 7.2V NiMH battery and a 11000mAh powerbank. The NiMH battery provides power for the ESC and steering servo. Note that the steering servo is powered by the ESC's Battery Eliminator Circuit (BEC), which provides regulated 5V DC output. Also, the drive motor is powered / controlled by the ESC.
The powerbank provides power for the Arduino, WiFi router, IP camera and NeoPixel stick (all of these devices operate on 5V DC power). The specific model I chose is the Cygnett 11000mAh Charge Up Pro because it has dual USB ports - one port can provide up to 2A, and the other port can provide up to 1A. It is also one of the highest capacity powerbanks currently available, so I don't need to worry about it dying before the car battery dies.
Since the powerbank needs to power four items with only two USB ports, I built some USB power splitters. These can be considered to be USB power double adapters. The USB power splitter for the NeoPixel stick also has some female 0.1" headers in addition to the USB ports, to allow the use of stripped 22 AWG solid core wire to connect power to the NeoPixel stick.
Also, since two batteries are involved, the grounds for devices attached to these batteries should be common, so that all signal voltages are based on the same reference value. To do this, I connected the ground pin for the ESC Battery Eliminator Circuit (BEC) to the Arduino GND. There is also a connection between a USB power splitter and the Arduino GND. This common ground is required so that the steering servo and throttle signals sent from the Arduino are received at the correct voltages.
The last diagram shows a full overview of the wires / cables on the car, including the Arduino Mega pins that were used.
Step 4: Software Overview
The software in this project includes a PC-based C# program and an Arduino sketch. The program is a WinForms application, with a Graphical User Interface (GUI). The C# program performs a lot of functionality and handles a diverse range of inputs and outputs. A summary of these is below:
- receives XBox 360 controller input
- receives mouse / keyboard input for some functions
- receives video data from IP camera (via http)
- receives telemetry/status data from Arduino (via serial)
- sends instructions to Arduino (via serial)
- sends instructions to IP camera (via http)
- sends vibration instructions to XBox 360 controller
- GUI - displays status of controller, serial communications, video stream.
The Arduino sketch is not as complex as the C# program, but still does many things:
- receives instructions from C# program (via serial)
- receives telemetry data from accelerometer module (via analog inputs)
- sends control signal to Electronic Speed Controller
- sends control signal to Steering Servo
- sends control data to NeoPixel stick (digital output)
-sends status/telemetry data to C# program (via serial)
I've attached the C# project and the Arduino sketch below. For the next few steps, I recommend downloading the files, and leaving them open so you can switch between the steps and code. The steps aren't very useful without the code, and the code can be a bit confusing without the steps!
Note: You may need to manually recreate the C# project from the source files if the project doesn't load successfully in your version of Visual Studio.
Step 5: Software Development Environments and Utilities
The C# program was developed using Visual Studio Express 2012. This is a free version of Visual Studio, but provides enough features to develop complex C# applications.
Unfortunately, Microsoft doesn't include native support for its own XBox 360 controller when developing C# applications. Luckily, I'm not the only one to encounter this problem, so someone has made a C# wrapper around "xinput" (the API that allows developers to communicate with the XBox 360 controller). The particular version I've used is attached below. The current version of the wrapper can be downloaded from github:https://github.com/speps/XInputDotNet. Refer to the github page or the readme file for detailed installation / configuration instructions. My summary of the instructions is: Add "XInputDotNetPure.dll" as a reference in the C# project, and put "XInputInterface.dll" in the same folder as the .exe file.
The Arduino sketch was developed using the Arduino IDE. Two additional libraries were required in order to use the NeoPixel stick and two servo outputs (for steering and throttle), simultaneously. These libraries are the Adafruit "TicoServo" library and the Adafruit "NeoPixel" library. These libraries can be downloaded from github:
Installation instructions are provided in each library. For a generic Arduino library installation guide, refer to: https://learn.adafruit.com/adafruit-all-about-arduino-libraries-install-use/how-to-install-a-library
Note: The standard Arduino servo library couldn't be used due to the NeoPixel stick. An explanation for this incompatibility is provided by Adafruit: https://learn.adafruit.com/neopixels-and-servos
Step 6: C# Application Threading and XBox 360 Controller Input
The first bit of software I wrote for this project was for the XBox 360 controller (you may have noticed that the original Visual Studio project name has carried through to the end!). You will find the XBox controller code in the first part of the "Form1.cs" file. As mentioned in the previous step, this code only works if the Xinput dotnet wrapper has been configured (i.e. "XInputDotNetPure.dll" has been added as a reference in the C# project, and "XInputInterface.dll" is in the same folder as the .exe file).
The code works by repeatedly polling the controller to retrieve the status of the buttons and analog controls. Due to this polling, I realised that the program would need multiple threads, in order to work at all. This was a bit of a challenge, as I hadn't actually written a multi-threaded application before this one. After looking at some examples and tutorials, I decided that there would be a main application thread for the graphical user interface (GUI), a separate thread for handling the XBox 360 controller, and several more threads for other things. Multiple threads allow all these things to work in parallel, without any noticeable lag in the GUI.
In the Xbox controller thread, the controller status is polled at about 50Hz, so it always has very current data. This controller data is also echoed on the GUI, and button presses are highlighted in orange. To do this, cross-thread data transfers were needed. I used the concept of "delegates" to allow threads to "affect" each other. You'll also notice a number of "shared_...." variables that are used across multiple threads.
This section of code also has a counter that keeps track of controller "frames". In the context of my code, the frames refer to when the controller status has been read. The counters "samplecount" and "oldsamplecount" are used so that long button presses are handled correctly. As an example, I have assigned some digital buttons so that one press activates a function, and the next press deactivates that function. If a long press isn't handled correctly, the program will toggle that function on and off continuously. The code correctly handles a long press as a single press by comparing the value of oldsamplecount against samplecount. If the difference between the two variables is too small, it is detected as a long press.
Step 7: Serial Communication (Part 1)
I considered several options for sending control data between the laptop and the car. Wi-Fi or bluetooth seemed to be the obvious solutions, but neither can provide the range I would like. Most wireless arduino projects seem to use XBee modules, which rely on serial communication, so I decided to use a serial interface.
The car/laptop communication works like this:
1. The car sends a serial string (with a fixed structure), with known start and end of message characters.
2. The C# program continuously polls the laptop's serial interface. When it receives a serial string, containing both the expected start and end characters, the C# program prepares its own serial string (with a fixed structure), and sends it to the car.
3. The car receives the serial string, does a quick check (based on an end of message character and message length), pulls out and processes the information if the message is "valid", and waits for a specified delay before preparing and sending another message - and the cycle repeats.
Note: If the received message is not considered "valid" at the Arduino, the Arduino sketch will stop the car and centre the steering.
The message structure for messages sent to the car, from the C# program, is:
The meaning of this string is:
First field: Throttle (centred around 500)
Second field: Steering (centred around 500)
Third field: Slowmode - F: Fast, S: Slow (max throttle is halfspeed)
Fourth field: Headlights
First character: D: Dark (lights off), L: Lights on
Second character: brightness (0 - 9)
Third character: light sequence (0 - 9)
Final field: End of message character (Z)
More details about some fields will be provided in later steps
The message structure for messages sent from the car, to the C# program, is:
First field: Start of message character (A)
Second Field: Throttle (centred around 500)
Third Field: Steering (centred around 500)
Fourth field: Slowmode - F: Fast, S: Slow (max throttle is halfspeed)
Fifth field: Headlights
First character: D: Dark (lights off), L: Lights on
Second character: brightness (0 - 9)
Third character: light sequence (0 - 9)
Sixth field: rumble (R - rumble, O - off)
Final field: End of message character (Z)
Most of these fields are just echoing what was received by the Arduino. This was done as a visual indicator of the health of the serial interface. If the interface is working properly, the output and input strings displayed in the C# program should match.
This serial communication method was initially developed using a cabled serial connection (i.e. using the Arduino USB cable). I was able to set the delay on the Arduino side to 20ms, so that the message frequency was about 50Hz. With the USB cable, I had no problems with communication, and could get stable message receipt at both ends. The baud rate was set to 38400.
Step 8: Serial Communication (Part 2) - Wireless Serial
For wireless serial communication, I initially bought some XBee Series 1 1mW modules, just to try them out. An XBee shield is required for the Arduino, and an XBee USB Explorer (or equivalent) is required on the laptop side. Since I used an Arduino Mega, I connected the relevant pins on the XBee shield into the Arduino Mega pins for the second hardware serial interface.
I found that the Series 1 modules were pretty easy to setup using the XCTU application (see the Sparkfun guide: https://learn.sparkfun.com/tutorials/exploring-xbees-and-xctu). However, I ran into some limitations. The first one is an obvious one - with 1mW modules, the usable range was extremely limited, and for no dropped messages, I could only go as far as about 3m! I could get better range with a long USB cable. The second one is a more important limitation and seems to be hardware based. I found that I could not get reliable communication at 50Hz, even with the XBees configured to a baud rate of 38400. I increased the delay, so that the message rate would be about 25Hz, and I had much better success. At 25Hz, I could get stable communication within 3m. I also found that when I went out of range, the messages would be dropped, but would recover straight away when brought back in range. To check if the serial communication was working properly, I watched the serial input / output textboxes on the C# program GUI. The serial input box should show a very stable string of characters, that matches the serial output box. If the serial input box started flashing with blank or corrupted data, that meant that the messages weren't getting through.
After getting these modules working, I ordered some higher powered modules. Seeing that both high powered Series 1 and Series 2 modules were available, I went for the slightly cheaper XBee Series 2 Pro (63mW) modules, rather than the XBee Series 1 Pro (60mW) modules. I had read that they were harder to setup, but could be used in transparent (AT) mode, just like the Series 1 modules. I configured the modules and got them to talk to each other at 38400 baud (note: one module has to be flashed as a "coordinator" and the other module has to be a "router"). I then installed one into the Arduino shield, and immediately ran into problems. At 25Hz, I could not get reliable communication at all. To check what was happening, I echoed the Arduino's received messages through the Arduino's first serial port (i.e. USB cable). Using the Arduino serial monitor on my laptop, I found that the Series 2 XBees were chopping messages in half or mashing consecutive messages together. So, the Arduino would sometimes receive the end half of a string stuck to the first half of the next string. I had to increase the delay, so that the frequency dropped to about 13Hz before I could get seemingly reliable serial communication over short ranges. The downside of the lower frequency message is that it introduces more lag between controller input, and the car's movement. Unfortunately, the lower frequency did not resolve the problems completely. Over longer distances, the serial data corruption still happened, and the interface did not recover when brought back in to close range.
So, with all these problems, I ended up buying a pair of Series 1 Pro 60mW modules. In terms of the serial interface, they work just as well as the 1mW Series 1 modules - there is no data corruption or strange message behaviour when in range. Their range is also much better than the 1mW modules (as expected).
In summary, if you're using XBees in a project for simple point to point communication, STAY AWAY from the Series 2 XBee modules! The Series 1 modules perform infinitely better and are less problematic (that probably explains their popularity and higher price).
(On a related note, is there anyone in Australia who wants to buy some lightly used XBee Series 2 Pro 63mW modules? :-P)
Step 9: Car Throttle and Steering
The throttle and steering on the car are "analog", meaning that by using the XBox controller, the car can move at a range of speeds, and turn at a range of steering angles. The right trigger is used for forwards, the left trigger is used for reverse and the left thumbstick is used for steering (these are the standard controls for XBox racing games). In order to transmit these "analog" inputs from the XBox controller to the car, the data travels through the programs as follows:
Throttle Data - C#:
1. The XBox Controller status is read. Left/right trigger values are saved as floats
2. A comparison is made between the two trigger values. The larger value is used, and the smaller value is ignored (eg, if both forwards and backwards are pressed, the larger value "wins")
3. The value is multiplied by 100, and manipulated so that 0 speed is the value 500. This means that full speed reverse is equivalent to the value "400", and full speed forwards is the value "600". This was done to prevent needing to use negative numbers, and the choice of 500 as the centre point was completely arbitrary.
4. The new throttle value is converted to a string (as 3 digits).
5. The throttle string is added to the C# output string, and sent to the Arduino when required.
Throttle Data - Arduino:
1. The Arduino receives the full string, and saves the throttle characters to a character array.
2. The throttle character array is converted to an int
3. Using the "map" function, the throttle value is converted into a compatible value for the ESC. The ESC accepts the same control signals as servo motors, so the Servo type is used to write a "degree" value to the ESC pin. The ESC has been calibrated such that 0 degrees is full speed reverse and 180 degrees is full speed forwards. 90 degrees is 0 speed.
4. The value is written to the ESC.
In the C# code, you'll also see some extra functions that manipulate the throttle. These were included to change the driving characteristics, but I haven't found them that useful. One of these functions involves a linear/squared/cubed throttle mode. Linear mode provides a 1:1 relationship between the trigger position and the speed. So, pressing the forwards trigger halfway results in the car moving at half of its max speed. Square mode involves squaring the raw value, so pressing the trigger halfway results in the car moving at a quarter of its max speed. This offers more range of control at lower speeds, and less at higher speeds. These throttle modes can be selected using the drop down box on the C# program GUI (using a mouse).
The other function involves the ability to enable/disable "slow mode", by pressing the B button on the XBox controller. Slow mode limits the car's top speed to half of its max speed. So, you could use the full range of the triggers to control lower speeds. This mode actually comes in handy sometimes - it is great for precise control at slow speeds.
Steering is quite similar to throttle. The way the data travels is:
Steering Data - C#:
1. The XBox Controller status is read. The left thumbstick x-axis value is saved as a float
2. The value is multiplied by 100, and 500 is added. This means that full left is equivalent to the value "400", and full right is the value "600". Like the throttle, this was done to prevent needing to use negative numbers, and the choice of 500 as the centre point was completely arbitrary.
3. The new steering value is converted to a string (as 3 digits).
4. The steering string is added to the C# output string, and sent to the Arduino when required.
Steering Data - Arduino:
1. The Arduino receives the full string, and saves the steering characters to a character array.
2. The steering character array is converted to an int
3. Using the "map" function, the steering value is converted into a compatible value for the steering servo. Note that the steering servo does NOT have the full range of 0 to 180. On my car, full left is 133 degrees and full right is 60 degrees, with a centre value of about 96 degrees
4. The value is written to the steering servo.
Like the throttle, the steering can be configured to linear/squared/cubed mode in the C# program. I found that for my car this was a bit pointless, due to the relatively small range in steering angle. Since the wheels don't turn far, it is easy to get them to the right angle in linear mode.
Note about Arduino Servo libraries:
When I was developing the Arduino sketch, I used the standard Arduino Servo library for both the ESC and steering servo. Once I introduced the NeoPixel stick, the Servo library could not be used anymore (see https://learn.adafruit.com/neopixels-and-servos). The TiCoServo library works just as well in my RC car, but there are limits - only two servos can be used simultaneously, only a small set of pins can be used (even on an Arduino Mega), AND only specific pairs of those pins can work together. The last one was figured out through trial and error...
Step 10: IP Camera (Part 1) - Hardware
The Arduino isn't powerful enough for video, so I needed a self-contained video solution. This ruled out webcams, which would require an onboard computer to process and forward the video to the laptop. Also, GoPros are a bit pricy, and the models (when I started looking in mid-2014) did not live stream without a significant delay (3 seconds). The best option I found for a camera that could stream live digital video with minimal lag was an IP camera. These are typically used as surveillance cameras, which actually brings added benefits, such as IR night vision. I then put together a list of features that I needed/wanted:
- Ethernet and/or Wi-Fi interface
- Ability to set a static IPv4 address
- MJPEG video streaming (simpler to include in C# program)
- Documentation for how to control the camera via http commands
- Wide angle lens
- IR-Cut filter for daylight use (better colour accuracy)
- IR night vision
- 5V DC input
- (Optional) Pan/tilt control
I was able to find a model that fits all of these criteria. The IP camera I chose is the Foscam FI8910W IP camera. This is a non-HD IP camera that sends MJPEG video (max resolution: 640 x 480). I didn't choose a HD model, because they generally use H.264 video, and I don't know how to display a H.264 video stream in a C# winforms application (Is it even possible? Add a comment if you've done it before, or know how to).
The IP camera is powered by 5V DC (up to 0.7A required), which makes it compatible with a USB powerbank. However, the connector is a barrel connector, so I made a custom cable - one end has the barrel connector, and the other end has a USB connector.
The IP camera has both Wi-Fi and Ethernet interfaces. In theory, I could set up a direct ad-hoc Wi-Fi connection between the laptop and IP camera, but I had doubts about the performance of its Wi-Fi antenna. So, I decided to add a portable Wi-Fi router on the car. The model I chose is the Netgear Trek PR2000. I chose this model because it is tiny, can be powered from a USB supply and has Ethernet ports. Having a Wi-Fi router also allows flexibility for upgrades later on - other Ethernet/Wi-Fi based capabilities can be added easily.
IP Camera Configuration:
The only thing that needed to be set up was the static IP address. I chose the address 192.168.1.10 (subnet mask 255.255.255.0). To set up the camera, I plugged it into the Ethernet port in my laptop, and first made sure that the auto-assigned IP addresses were ok for communication. Then, I opened a web browser, and typed the IP address of the camera to load its configuration page. The static IP address was then set to 192.168.1.10. Note: This will break the connection to the computer - to recover the connection, the IP address of the laptop was manually changed to the same subnet (eg 192.168.1.11, subnet mask 255.255.255.0).
The username and password can also be configured, but since the camera will only be used on a private network, I left these settings at the defaults (user = admin, no password).
Wi-Fi Router Configuration:
Like most other consumer grade routers, the Netgear Trek router is configured via a web browser interface, and initial setup requires a wired Ethernet connection. For use in the car, the router needs to be configured to use the subnet 192.168.1.XXX (subnet mask 255.255.255.0). The router also needs to have DHCP enabled (it's on by default), and the basic Wi-Fi settings need to be configured (SSID and password). This configuration only needs to be done once.
The initial router configuration is pretty painless, but unfortunately the general startup procedure is not as simple as I expected. Whenever the router is turned on, it does not enter its operational mode until it detects a device connected to its "Internet" port, or a wired computer. With the IP camera attached to the yellow port, the router seems to power up to a "limbo" mode. To force the router to boot fully, the camera cable has to be disconnected and plugged into the blue "Internet" port. After a few seconds, the router will then boot properly and Wi-Fi will come on, allowing the laptop to connect wirelessly. The IP camera does not work in the blue port, so the cable has to be unplugged, and reconnected to the yellow port. A simple ping test can be executed to check if the laptop can see the IP camera on the network.
Note: Although this boot up procedure is annoying, the router does everything I need it to do. I'm not sure if other compact travel routers have the same boot up "feature".
Step 11: IP Camera (Part 2) - Software
The C# program can send commands to the camera, and display its video stream. By default, the camera functions are disabled on application startup. This is to prevent program crashes if the camera is not available.
The camera receives commands via http. For example, to stream video at 640x480 resolution, the command is:
Refer to your IP camera's SDK documentation or user manual for the command structure. I've attached the documentation for my camera to this step. These commands can also be entered directly into an Internet browser address bar.
Once the camera gets the command shown above, it will begin sending out an MJPEG video stream. My program processes this stream and displays it in the GUI. I wasn't able to find any libraries that process the stream, so I made my own function to do it. The function works by scanning the contents of the stream. When it finds the JPEG "Start of Image" bytes (bytes 0,1: 0xFF,0xD8), it will begin saving the stream into a separate byte array. It will keep adding the stream contents to the array until it finds the JPEG "End of Image" bytes (0xFF, 0xD9). This byte array is then converted into a "memorystream", then a bitmap, before finally being displayed on the GUI.
The program offers two video streaming resolutions. The higher resolution (640x480) offers the better clarity, but has a noticeably lower frame rate. The lower resolution (320x240) is not good for image quality, but the higher frame rate makes it the better choice if driving the car by camera.
There is also a button to show a still picture. This functionality relies on some code I found on a website (http://www.codeproject.com/Articles/15537/Camera-Vision-video-surveillance-on-C). Initially, I only used it for debugging purposes, when making my video streaming functions. I was going to remove it, but decided to leave it in.
Since the camera has pan/tilt motion built in, I also added these capabilities into the C# program. The right thumbstick on the Xbox controller has been assigned to the control of the camera position (this control assignment is typically assigned to "free look" in XBox racing games). The camera gets its pan/tilt instructions via http commands. For example, to move the camera up, the command is:
There are separate http commands for motor start and stop. To convert motion into measurable "steps", I used delays, so that when the thumbstick is moved within a single controller "frame", the camera just moves one step. However, the motors are far from precise, so these steps are very inconsistent in size. I initially wanted to include a "return to centre" function for the camera, but found that due to the wonky step sizes, the camera rarely returned to the centre position. (The function is still there in the source code, but not in use).
Step 12: NeoPixel Stick
Since it is an unwritten rule that Arduino projects should have obnoxious LEDs (just kidding!), I added a NeoPixel stick to the car. NeoPixels are a type of indvidually addressable RGB LED produced by AdaFruit, and the NeoPixel stick has 8 addressable LEDs on a compact PCB. The stick requires a third party Arduino library (https://github.com/adafruit/Adafruit_NeoPixel) to work, but it is quite easy to configure (both hardware and software). I soldered on some female headers so I could connect to it with 22AWG solid core wire.
The lights are controlled using the XBox 360 controller. The "Y" button toggles the NeoPixel stick on/off. The DPad controls the brightness and "sequence". The brightness is controlled by up/down on the DPad, and sequence is DPad left/right. The sequence refers to the preprogrammed modes configured for the NeoPixel stick.
The list of sequences in the current software version is:
Sequence 0: All LEDs set to white (adjustable brightness)
Sequence 1: All LEDs set to red (adjustable brightness)
Sequence 2: All LEDs set to green (adjustable brightness)
Sequence 3: All LEDs set to blue (adjustable brightness)
Sequence 4: Hazard lights - the end LEDs flash orange
Sequence 5: Police mode - the end LEDs flash red/blue (don't use this on a road at night time, haha)
Sequence 6: Pulsing/fading green LEDs
Sequence 7: Knight Rider mode (Red)
Sequence 8: Knight Rider mode (Blue)
Sequence 9: Blank sequence, currently set to white (adjustable brightness)
The C# program sends the NeoPixel instructions to the Arduino, using a 3 character field in the serial output message, such as "L51" The first character controls on/off (L = lights on, D = off(dark)). The second character is the brightness (if applicable for the sequence), and the last character is the sequence number.
To process this information, the Arduino receives the serial string, saves the light string to a separate 3 character array, and then checks each character. The NeoPixel status is updated in each iteration of the main loop.
Note: For animated sequences, such as the hazard lights and police sequence, the millis() function was used to track time. Most NeoPixel examples use the delay() function, but this can't be used for the car, since it will block the execution of all other code in the Arduino, until the delay completes.
Note 2: Since the NeoPixel stick is only updated in each iteration of the main loop, the minimum time between different NeoPixel states is the delay set in the main loop. As you know from the serial section, this delay sets the serial message update frequency between the Arduino and PC. So, very high frequency updates to the NeoPixel stick aren't possible - the highest frequency for the NeoPixels is directly tied to the serial message frequency (about 20Hz).
Step 13: Accelerometer + XBox 360 Controller Vibration
Accelerometers are used to measure acceleration in one or more axes, so I thought it would be fun to put one in the car. I added an Adafruit ADXL335 3-axis accelerometer to measure the acceleration on the car, so that collisions will be detected and cause the XBox controller to vibrate/rumble. This accelerometer provides its measurements as 3 separate analog inputs to the Arduino. The range of measurements is -/+3g, which maps to 0 - 3.3V. Since the max voltage is only 3.3V, the 3.3V reference pin on the accelerometer board was connected to the Arduino's external reference pin.
The Arduino reads the accelerometer values, on each iteration of the main loop, and saves the values. The Arduino sketch compares the current acceleration values with the previously saved values. If the difference exceeds a threshold, the character "R" is written to the Arduino's serial output string, to indicate that the controller should rumble. If the difference is too small, the character "O" is written to the Arduino's serial output.
The C# program receives the serial string from the Arduino. If it finds the character "R", a "countdown" flag (rumblecount) is set, to indicate that the controller should rumble. In each XBox controller "frame", the status of this flag is checked. If it is above zero, the controller rumble is activated, and "rumblecount" is decremented. On each subsequent frame, the controller will rumble until the countdown flag hits zero. The reason for this countdown flag is to enable the controller to rumble for a minimum of multiple frames, since a single frame is too quick to notice (approx 20ms).
With the current thresholds in the Arduino sketch, the controller will rumble when the car crashes into something, rolls over, goes over large obstacles or drives down stairs. It will also rumble periodically when driving over grass, but not on smooth concrete, so in a way, you can get a feel for the terrain through the controller. It also rumbles when applying full throttle or full reverse.
Note about XBox 360 controller vibration: There are two vibration motors - one is low frequency and the other is a higher frequency. The intensity of vibration can also be controlled precisely (it uses a float for intensity), so you could get a very wide range of rumble effects (as anyone who has used an XBox / Playstation already knows). My program doesn't utilise these advanced features, because it will take too long to program and requires too much effort... :-P
Step 14: Car Startup/Shutdown Procedure
Since there are many individual hardware items on the car, the startup/shutdown process is a bit longer than a standard RC car. The start/shutdown procedures are summarised below:
Car Startup Procedure
- Connect the USB cables from the USB splitters into the powerbank. (IP camera + NeoPixels into the 2A connector, Arduino + WiFi router into the 1A connector). Wait a few seconds.
- Temporarily unplug the Ethernet cable going into the yellow port on the WiFi router, and plug it into the blue (Internet) port.
- Wait a few seconds, and the WiFi should be active. Connect to it with the laptop.
Unplug the Ethernet cable from the blue port, and plug it into the yellow port.
- Connect the USB XBee Explorer (with module) to the laptop.
- Connect the XBox 360 controller
- Start the C# program on the laptop.
- Press the button to connect the serial interface (change COM number in textbox if required)
- Make sure the Serial Input text box is populated and STABLE - no flickering of invalid data
- Connect power to the ESC on the car, and turn it on
- Start video on C# program (change IP address in textbox if required)
- The car is ready to go (finally!)
Car Shutdown procedure
- Turn off ESC first.
- Close the C# program
- Unplug powerbank cables, disconnect NiMH battery from ESC
If the Serial input starts to flicker, shows incorrect data, and does not recover within a few seconds, the ESC should be turned off as quickly as possible. This problem can occur due to the XBee modules not coping well with the range or the data being transmitted. The C# program should then be restarted. After the serial connection is re-established, the ESC can be turned on again.
Note: Dropping the frequency of messages (by increasing the delay in the Arduino sketch) can improve reliability over longer distances, but unfortunately also increases lag.
Step 15: Conclusion
The car works well, but there is definitely room for upgrades and improvements! Some of these potential improvements are below:
Camera video quality - The IP camera I used only provides low resolution video, at low frame rates. I think that replacing it with a Raspberry Pi, with camera module, will improve the video (both resolution and frame rate) a lot. The Raspberry Pi can be configured to output an MJPEG stream, so it should be a simple upgrade. Alternatively, it would be great to get live H264 video (from a HD IP camera) through the C# program. However, I'm not sure where to start, or if it's even possible.
I have changed the camera solution to a Raspberry Pi 2 + Raspberry Pi camera module. The quality is much better than the original IP camera. The camera module is attached to a new pan/tilt mount, with two additional servo motors. Due to the extra servos, the NeoPixel stick has to be disabled. The camera change required software changes to both the arduino sketch and the C# program. The updated files are attached to this step.
Also, the YouTube video above shows the new configuration with the Raspberry Pi camera.
Camera pan/tilt speed - for simplicity, I chose an IP camera with built in pan and tilt motors. Although they are convenient, they are slow and imprecise. It would be better to use a fixed camera, mounted on standard servos. However, this will need a servo controller board if NeoPixels are also used, as the TiCoServo library only supports two servos (already used by the ESC and steering servo).
A new pan/tilt mount was added at the same time as the Raspberry Pi camera - see the YouTube video above.
Rollover Protection - with a high centre of gravity and (relatively) soft suspension, the car is prone to rolling over! I've used some tall screws at the front to protect the Arduino, but there is no protection for the IP camera or WiFi router. However, a roll cage will add more weight, high up, encouraging it to roll over even more...
Speed - I'm still using the original motor that came with the car. It is a little slow at top speed, so I might swap in a higher speed (lower torque) motor later on.
More functions - There are still heaps of spare buttons on the XBox controller, that haven't been assigned to any function. What are your suggestions for new functions?