Autonomous RC Car Part 4
Mount the Raspberry Pi on the chassis; Arduino servo control and telemetry.
If you haven’t caught up, here’s the links to parts zero, one, two, and three.
Chassis
This first part will largely be a build montage. Cue ’80s dance music.
Back in part zero we talked about using a Tamiya TT-02 model RC car as the base of this build. We’ve skipped documenting the car assembly because that’s what the instructions are for. They’re really good, full scale, and easy to follow. It takes a few hours and you’ll be left with something like this:
The important detail to note here is the four posts that stick up from the chassis. These are there to mount a body over the car to protect the electronics and — mostly — to just look cool. Sadly, we’ll have to look less cool and use these standoffs for mounting compute hardware and sensors.
A note on electronics: these cars use 7.2V, 5000mAh batteries. While we could certainly use a level shifter or something to pull 5V to run the RPi off this battery, the power would likely be really noisy due to the variable power draw from the brushed motor. To simplify the electronics I’ve opted for just using a small USB power bank for the electronics and leaving the full charge on the RC battery for propulsion.
PWM
This sounds simple at the outset. I’ve got a 6-channel transmitter/receiver combo. Pair them together, connect the output of the receiver to the PWM pins on the Arduino Nano.
Looks simple enough, right?
If you run this, you will never get ch_2
. The pulses between channels one and two are less than the 5ms delay we’ve added to the sampling loop. I’d prove this to you but I only have a single-probe oscilloscope at the moment. Setting the delay to 0 will only yield an alternating signal between ch_1
and ch_2
. I’m sure I could devise a scheme where it would use the last sample if it gets a zero (effectively cutting my sample rate in half), but without some pause in the loop the Arduino can’t do anything else. There must be a better way.
Interrupts
Arduino’s support interrupts. If you’re not familiar with how this works, just think of them as registering a callback on a hardware event. You can listen for events when the voltage goes HIGH, LOW, or CHANGE.
attachInterrupt(digitalPinToInterrupt(CH_1_PIN), callback, CHANGE);
Unfortunately, the Nano only has two pins capable of digital interrupts, so if we want to get our other RC channels we either change boards or switch methods. One option is to add a resister and capacitor in series and attach the PWM signal to an Analog pin on the Arduino. Then using the Analog-to-Digital converters to assign a voltage range to the pulses. This would work, but would need calibration and seems likely to be affected by changes in battery voltage. What we really want is to be able to use all the digital pins with interrupts.
More Interrupts
The EnableInterrupt library does exactly that. Unlike the pulseIn()
function, the interrupts don’t give us the PWM value, but we can calculate it ourselves. We’ll set up timers for each pin. We can listen for the RISING event of a pin, when the event is triggered we record the time and listen for the FALLING event instead. When the falling event is detected, we take the difference between the current time and the previous time and — viola! — PWM value.
There’s an added benefit of using interrupts. Since we’re waiting for events instead of constantly polling the digital pin, the Arduino can be idle most of the time. This is good since we have more to do on this project.
With some minor additions to the loop()
we can constrain the value between 0–255 and use analogWrite()
to set this to a pin.
analogWrite(CH_1_PIN_OUT, (pwmValue - 1000) / 1000.0 * 255);
This is easy to confirm with a resister and and LED, but since I have a little portable oscilloscope I thought I’d try to visualize the PWM signal. Moving the stick left-right makes the LED brighter or dimmer and changes the pulse widths on the scope.
PWM vs. PPM
We’ve been discussing this as PWM so far, and that’s not entirely accurate. PWM is a “duty cycle” where we reference the on/off ratio in a percentage. 25% means the voltage is high 1/4 of the time. The signal coming from the RC receiver is Pulse Position Modulation so it matters not just how much the signal’s voltage is high, but when it goes high in reference to a synchronization signal. This means we can’t use the same analogWrite()
function we used for an LED to run a servo. Fortunately, this is a common enough task that there are a few Arduino libraries to help out.
The Arduino IDE ships with a Servo library. Don’t use this. It relies on ‘Timer 1’ which will prevent you from doing other things with the Arduino while it’s active. The library you want is ServerTimer2. Download the “ServoTimer.h” and “ServoTimer.cpp” files and put them in your Arduino project directory. Eventually we’ll need a better vendor toolchain for automated builds, but this is fine for a prototype.
Alright! We’ve got proxied Arduino server motor control that works with our robot. We’re at about 18% of the memory footprint of the Nano with this code. Plenty of space. Maybe we’ll add an IMU.
Up Next
We need to expand the code to work with our other channels and then send them to the Raspberry Pi to record. I think I’ll use the constrained value from 0–255 (line 26 in the code block above) instead of the 1000–2000 PPM value. The constrained value seems like it would fit nicely in a I2C message but reducing precision at this point may be bad. We’ll see. The car is looking rather bristly with all these wires hanging off it so we need to figure out some better cable management.
Update: Continue to Part 5.