Turn your Raspberry Pi into a radio controller for RC vehicles

Ever since I tried turning the Raspberry Pi into an FM transmitter, I had wondered if it would be possible to make it drive a radio-controlled car. It turns out that it is! With no modifications to your Pi, you can be driving around a toy-grade RC car iFe, and I’ve read about a few other ones online that should work as well. Any RC toy that works at a frequency in the range of 1-250 MHz should be controllable by the Pi once you figure out the command signals.

I’m going to talk a little bit about how RC cars work, how the code and hardware works on the Pi, and about RC controls in general. If you just want to start driving around, you can jump to the end.

Raspberry Pi FM radio

The first version of the Raspberry Pi FM transmitter was written by Oliver Mattos and Oskar Weigl. It works by using the hardware that is normally meant to produce spread spectrum signal clock signals on the GPIO pins to generate a radio frequency. The GPIO pin itself acts as an antenna, which you can extend by attaching a length of wire such as a jumper cable. By reading samples from an audio file and writing them out to the GPIO pin at a certain rate, it is possible to tune in a radio and hear the audio.

The original version of this code had a small problem though. FM requires a continuous stream of radio signals, but the Raspberry Pi normally runs Linux, which is not a real-time operating system. This means that you can start, stop, and run different programs at the same time, but because the Pi only has a single CPU core, it has to simulate multiple programs running at the same time by constantly switching between them. Whenever there was a context switch away from the program that was writing to the GPIO pins, the radio signal would skip and pop. Richard Hirst came up with a great solution to this problem: use the DMA controller to continually write data to the GPIO pins. Because DMA bypasses the CPU altogether, it is not affected by context switches, and it has the side effect of reducing the total CPU load because the controlling program now only has to periodically refill a circular buffer. Unfortunately, it also means that it’s not possible to stop broadcasting for short periods of time (at least not that I’ve been able to figure out), which will be problematic later when we try to control an RC car.

Frequency Modulation, Amplitude Modulation, and Pulse Modulation

There are two common ways of encoding audio over a radio signal: frequency modulation (FM) and amplitude modulation (AM). FM works by continually varying the frequency of the signal in small amounts while keeping the amplitude the same, while AM keeps the frequency the same while varying the amplitude. The difference is best described with a picture:

Animated diagram representing the difference between radio waves modulated by amplitude and by frequency. By Berserkus, from Wikipedia. Used under CC BY-SA 2.5.

The Raspberry Pi FM program uses the fractional divider to control and change the frequency of the generated signal. This allows it to generate an FM audio signal. Because the Pi is just writing values to the GPIO pin, I don’t think that it’s possible to control the amplitude of the signal, so I don’t think that it’s possible to have the Pi generate AM audio signals.

There are a few different control schemes that remote control toys use. Most toy-grade RC cars (ones with forward/back + left/right control) use pulse modulation. The radio controller broadcasts a signal at a constant frequency for a certain amount of time, and then stops broadcasting for another amount of time. It then repeats this cycle with varying lengths of broadcast and pause time in a particular pattern. The RC car recognizes this pattern as a command, such as “forward” or “reverse right” and starts moving.

The specific pattern typically consists of some base time length, such as 400 microseconds, and all pulses and pauses are multiples of that length. A control signal starts with a synchronization pattern consisting of a broadcast for some length of time and a pause, which is repeated a certain number of times. Then a number of bursts and pauses are repeated for the command. For example, the New Bright 1:24 truck sends synchronization signals of 1200 microsecond bursts with 400 microsecond pauses repeated 4 times, followed by a number of 400 microsecond bursts and 400 microsecond pauses. If that signal is repeated 40 times, the vehicle drives forward, while 28 bursts tell it to drive reverse and right.

Remember earlier when we had to use DMA to ensure a constant signal? Because of this, I haven’t been able to figure out how to make the Pi stop broadcasting for a consistent amount of time, which is needed for the signal pauses. However, think back to how FM radio signals work. Because the Pi send FM, it can very quickly change the frequency that it is broadcasting on. To simulate a broadcast pause, it instead broadcasts at a different frequency for a length of time. Because the RC car is only listening for signals at one particular frequency, as far as it is concerned, the Pi has stopped broadcasting altogether.

A step up from toy-grade cars usually have proportional steering and throttle control, so you can more precisely control the vehicle. Most high-end hobby grade devices use pulse-position modulation. Andrew Hazelden has an excellent explanation of how PPM works. I wanted to get a car with proportional steering and throttle control, but most RC cars that I looked at operate in gigahertz range which is too high for the Pi, so I instead bought a RadioShack Dune Warrior which runs in the 27 MHz range.

Finding the signal

With toy-grade cars, I’ve found that the easiest way to find the signals that control them is to just use brute force. They mostly use very similar signal patterns, so you can simply iterate through different control pattens until the car responds. For more complex patterns, you’ll probably need to use an oscilloscope to analyze the patterns.

Brute force

For toy-grade RC cars, you can just turn on the car and iterate through different control patterns until you find one that causes the car to move. Every car that I’ve looked at uses one synchronization pattern for all of the controls that it can respond to, so once you find one signal, it’s just a matter of guessing how many signal repeats control the remaining actions.

Iterating through all possible patterns can take a few hours, and I didn’t want to wait around for the car to move, so I instead pointed a webcam at the car and then waited until the image changed. Once the car moves, the computer will see that the image has changed and will save the image and the pattern.

I tried a few different techniques to monitor the webcam to decide if the car has moved. First, I tried just computing the percent difference of the color values of the pixels between a base photo and the most recent photo. This generated a lot of false positives due to changing lighting conditions, such as when a cloud passes overhead. If you run this program, I recommend placing the car in a closet or some other place where you can keep the lighting consistent.

Next, I converted the image to greyscale and then normalized the colors so that the darkest color was black and the lightest color was white. This reduced the noise between pictures a lot and helped with my varying lighting conditions, but I was still worried about false positives. I tried one more thing: reducing the image depth to 1 bit, so it was only black and white.


This reduced the noise to a really low level, and because the wall behind the car was white, once it moved, the difference was very clear. This reduced my false positives to 0, so I stopped here. I had considered running edge detection on the base image and then ignoring differences along the detected edges because that’s where most of the difference between photos appeared but I ended up not needing to.


For more complex vehicles, it’s easiest to hook an oscilloscope up to the radio controller and then observing the changes in the command signal as you move the controller. This is what we ended up doing with the Dune Warrior.

Dune Warrior, straight and full throttle

We hooked up the oscilloscope and watched how the signal changed as we adjusted the throttle and the steering. If you want to do this yourself, I strongly suggest recording the signal to video so that it can be reviewed later. The signal starts with a 500 microsecond synchronization burst and pause, followed by 22 more bursts. Each burst is either 127 microseconds or 200 microseconds, with a pause of the same length. The burst lengths are used to encode a binary pattern, where the shorter bursts are a 0 and the longer bursts are a 1. 6 bits are for steering, 5 are used for the throttle, and 1 is used as an even parity bit; the remainder never appear to change and I’m not sure what their function is. What’s strange is that the bits for steering are not contiguous; bits 7-8 form the 2 most significant bits, while 1-4 form the rest. 32 is centered for steering, while 0 is sharp left and 63 is sharp right. Similarly, 16 is idle for throttle while 0 is full reverse and 31 is full forward.

Running your own car

If you have a cheap toy-grade RC car (i.e. one that only has controls for forward/backward and left/right) then you should be able to find the command signals and control the car pretty easily. Clone the repository from my GitHub on your Raspberry Pi and compile pi_pcm by running scons. Run pi_pcm as root. By default, the program listens for JSON commands on port 5432.

Turn on the car and put it in a location where you can control the lighting (such as a closet). Point a computer that has a webcam at the car (it can be the same computer or a separate one), run python watch.py -f [frequency] -s [Pi IP address] and enter the frequency of your RC car (this should normally be 27 or 49 MHz). You’ll need to have gstreamer installed to capture the webcam stream. Most cars operate on one of a several different channels in the 27 or 49 MHz range; if you don’t know the exact channel, then just enter 27 or 49 and the program will iterate through each channel for you.

Once the program sees the car move, it will save a picture and rename it according to the command it just sent. The filename format is frequency-microseconds-synchronizationBurstLength-synchronizationBurstCount-signalCount. From there, you can run python control.py -f [frequency] -s [Pi IP address]. It will prompt you for the command information and ask you for different signal bursts; try different ones (1 – 100 or so) and observe how the RC car reacts. Once you have all of the signals, save the information in a JSON file (see control-specs/pro-dirt.json for an example). Now you should be able to run python interactive_control.py -s [Pi IP address] [JSON control file] and drive the car using the arrow keys.

More thoughts

There are a few other projects that you cna build from here. For one, I’m planning on entering the SparkFun Autonomous Vehicle Competition with my Dune Warrior. You can make an autonomous vehicle yourself by buying a USB battery pack for the Pi and taping both to the top of your car and writing a simple control program. I’m also planning on buying a Raspberry Pi camera, slapping on a WiFi USB dongle, and implementing Twitch drives your car in my apartment.


30 Responses to Turn your Raspberry Pi into a radio controller for RC vehicles

  1. Pingback: Controlling RC Toys With The Raspi

  2. opc0de says:

    How about caputring the signal with a SDR ? Do you think that will work ?

    • bskari says:

      Huh, that’s a really interesting thought. I’ve never worked with SDR before, but from what i’ve just briefly read, I think it should work fine. Probably easier than using an oscilloscope. Great idea!

  3. Pingback: Controlling RC Toys With The Raspi - Tech key | Techzone | Tech data

  4. Pingback: Controlling RC Toys With The Raspi | Hack The Planet

  5. Interesting strategy to figure out the different pulse trains. Opening the toy/remote will also allow you to peek at the ICs that’s transmitting and receiving the signals. You can note the markings on the ICs and search for a datasheet. The pulse trains should be documented. This how I did it: https://github.com/monsieurDavid/rx2-hacks although there is some issues with range from the Rasperry Pi.

  6. Pingback: Control RC vehicles using your #RaspberryPi | Raspberry PiPod

  7. MrBrie says:

    Very cool.
    You got me wondering about the AM bit. A long time ago I wrote some software that played samples on a PC speaker — the kind that can only do “on/off”. I think it managed 8KHz * 8 bits wide. Amplitude was simulated by doing pulse width modulation, i.e. in each 8 bit slot send more ones for a higher amplitude or more zeroes for a lower one. The 8KHz “carrier” is very audible though, so using a bit pattern worked better.

    Roughly this was the idea:
    Original audio was 8KHz * 8bit samples giving 0-255 levels.
    I’d chop the precision off to get 8 levels instead of 255,
    0-31 => 0
    32-63 => 1,

    First go then took those and mapped
    0 -> 00000000
    1 -> 10000000
    2 -> 11000000
    3 -> 11100000

    This will leave you with a 8Khz beep.
    It improves if you map but scatter the bits:
    0 -> 00000000
    1 -> 10000000
    2 -> 10001000
    3 -> 10101000

    Not sure this would work for you, but there’s an idea for you.

  8. Pingback: Graupner 33112 – Sistema de mando radio control (MX-12 Graupner Hott, 6 canales) | Radiocontrol

  9. Gaurav Ghosal says:

    Reblogged this on Cool Pythonist and commented:
    This is a great tutorial

  10. Pingback: RC Toys Info | Radio Controlled Toy Store

  11. Thomas says:

    Hi, I tried your instructions on a toy grade rc car from ALDI (so a really cheap one). Your system with the raspberry webcam worked like a charm, I just had to make sure that the yellow car is on a white background.

    I’m currently stuck on the fact, that the rc car seems to respond to any possible combination of frequency, sync-multiplier and sync-repeats. I’m really confused about that.
    The car responds on 26.995, 27.045, 27.095, 27.145, 27.195, 27.255 – so all of the usual channels.

    It responds on -u 300, -u 400, -u 500 and with a bunch of sync-multiplier/repeats combinations. The problem is, that all possible signal repeats (mostly up from 9 to 50) lead to not really going forward or backward (or left and right), but always some strange combination. In almost every combination if I wait a couple seconds, it will drive forward, drive backward, steer to one side (and as it’s a truck it has a little loading area which can be pushed up and down).

    I think I already spend three hours finding any sense in that strange behavoir, but I’m just more and more confused.
    I made a little video to demonstrate that strange behavoir:

    Do you have any idea what’s going on there?

    Anyways – Thank you very much for your awesome instructions. I had a lot of fun with that project and that was only possible because you shared you knowledge 🙂

    • bskari says:

      Hmmm. I haven’t ever seen a car behave like that before. That’s really strange. With some cheap cars that I have purchased, I have seen them respond on signal patterns that are close to the the correct ones, but the car would alternate between doing what it was supposed to do (like drive forward) and doing nothing, not do a bunch of things at once.

      Does the truck operate in the 27 MHz range? Usually remote controlled cars will have their frequency written somewhere on the it. Maybe it’s actually supposed to run at a different frequency.

      Other than that, if you have access to an oscilloscope, you could try analyzing the signal directly. I did that with a different car over here: https://www.youtube.com/watch?v=7_hTBV5O44Q

      Does the car have the manufacturer listed anywhere on it? I’m really curious and if I can find it for sale locally I’d love to take a closer look and figure out what’s going on.

    • Sande R Jones says:

      Can I ask how you are running the server an the python program?

  12. samirdfini says:

    Thank you very interesting project , but bruteforce is quite slow and painful , so i got an idea to connect one of the gpio to the board of the toy ,once you captur signal of gpio from the board the server stop sending sequences , this would increase hundred of times the capture of the right code .

  13. bill turner says:

    put a couple of micro switches on the car so if it moves forward or the steering changes it will feed back to the PI directly. That may make hunting for the right code very reliable and easy…

  14. Ralph says:

    Does your program work if it’s executed on a raspberry pi 2? Or does it only work on older versions of the pi?

  15. LMO says:

    Have you ever looked at using a hobby grade RF transmitter with the PI, that way the transmitter could produce the higher frequency ranges ( Ghz) and allow you to play with the proportional control. I’m curious as if you could properly get the PI to communicate with the hobby transmitter, I need a better grasp on it but would love to try it – what are your thoughts ?

  16. jeremierichardesupacouk says:

    Trying to get this code to work with a Tamiya / Futaba receiver. Not having much luck. The car is reacting is ecclectic ways. Is there a more complex sync sequence for those receivers ?

  17. Yuval says:

    Isn’t there a way to connect the RC car own’s radio receiver to the Pi, then let it show the radio codes once you aim its RC remote to it and press the forward/back etc. buttons?
    Instead of using brute force to try to find them…

  18. Navin says:

    Hi Brandon,
    This is a great post I came across.
    I think you can reduce brute for time. I see that you are using most of your time on discovering that first code which works on car. I wanted to suggest you to first find the exact freq on which car works. This can be done by first keep car moving and by sending stream of zero/one for sometime. Repeat this for all freq. The freq at which car stops moving is freq. of the car. This will work as we cannot have two transmitters working on same freq. Later we can brute force codes on the determined freq. Please let me know what you think about it.

  19. Rob Leach says:

    My stereo has an RF remote that I believe operates at 27Mhz. Do you think this trick can be used to make the Pi control my stereo?

  20. bskari says:

    Yes, similar techniques should work. One complication is that my program is designed to continually replay the same signal, whereas I would bet your remote only sends a signal once. In addition, I don’t know what the signal looks like – it might be frequency modulated or amplitude modulated, which my program can’t do. That being said though, https://github.com/F5OEO/rpitx should be able to do it. Here’s a tutorial that describes how to record and replay signals https://www.rtl-sdr.com/tutorial-replay-attacks-with-an-rtl-sdr-raspberry-pi-and-rpitx/

    • hepcat72 says:

      I should probably look more closely into rpitx I suppose. However, I noted that I can successfully (albeit repeatedly) use pi-rc to send signals to my bose stereo. I did what was described on https://github.com/probonopd/Lifestyle

      I tried using his code, but I would need some extra equipment to find the right modifications to get it to work. I figured it might be easier to fork your repo and see if I could remove the loop and add an option to take the json file so I could send the signal once. Seems easier to me to alter your code to add a file option and remove the loop, since I already know it can send the right signal.

    • hepcat72 says:

      I forked pi-rc and managed to get the code to work how I’d like last night, using the mute code to test. I learned a lot about what the code is doing, but I’m still a bit fuzzy on some things. Perhaps you can illuminate them.

      I tried to make minimal changes to achieve my goal since I didn’t quite understand what the code was doing, so I copied serve_forever and renamed it as serve_once. I added an option to take `-i file.json`. I open the file and supply the file descriptor to read_from_fd and then set the command to new_command. Initially, I changed the while loop to be `while(used < 1)`, but it it wasn't working. I changed it back to `while(1)` and was able to reproduce the same behavior since before my modifications (i.e. it continually mutes and unmutes my bose stereo). So on a hunch, I let it loop twice and stop. I was surprised that that worked.

      Also, I added a call to terminate after the loop. I don't know if that's necessary. I just didn't want to skip any cleanup you might have coded for when ctrl-c is used to stop the program.

      Oddly, there's a 1-2 second delay before the mute actually happens. I know that when I use the unmodified code to start the server and then output the json to the udp(?) port, the mute is immediate. I'm not sure whether I can make my edited version faster. I tried commenting the usleep(10000) at the bottom of the while loop. The code worked, but there was still a delay. But after I commented my debug prints, it stopped working again and the program would exit immediately. When I uncommented the debug prints, it started working again. So I commented the debug prints and uncommented the usleep, but changed it to `usleep(1)` and to my surprise, that worked… but the delay before the mute code transmission took effect seemed unchanged. There was still a 1-2 second delay between running the command and the stereo muting.

      I had to stop at that point because my wife was bugging me to go to bed. I plan to poke around the code some more.

      So I'm wondering if either 1. the hardware has to "warm up" or takes time to initialize and before that's done, it can't effectively transmit anything (which happens when I start the server in the unmodified pi-rc package) or 2. whether there's a better way for me to re-organize the code to send the transmission faster… Or 3. maybe after the mute code is transmitted and used is set to 1, there's still a frequency change that needs to happen in the next loop iteration…

      Any insights you can add to my observations?

      • hepcat72 says:

        I was experimenting some more this morning. I ruled out the 3rd case (“after the mute code is transmitted and used is set to 1, there’s still a frequency change that needs to happen in the next loop iteration”). I have some indirect evidence to rule out case 2 as well (re-organize the code to send the transmission faster). That may still be the case, but I determined that the original pi_pcm takes a second or 2 before sending it the json via udp actually mutes the stereo the first time, which suggests case 1 is the limiting factor. It takes a second or 2 for something to initialize.

        So I’m thinking that there might be some way to minimize the delay. Right now, I’m still looping to send the mute code twice. It takes a second or so to send the mute code (or perhaps there’s a sleep somewhere between transmissions?). Instead of transmitting the mute code twice (which presumably at some point during the first transmission/sleep, the resources become ready to transmit), it would probably be a better strategy to detect when the resources are ready and then send only one transmission.

        Another possibility is for me to re-write serve_forever so that it simply waits for a code to transmit, transmits once upon receipt of the data to transmit, and then go back to waiting for the next code to send. Then, based on my experience with the unmodified code, each transmission should happen immediately.

        My concern there is that, based on your comments at the top, it sounds a bit dangerous to keep this server running all the time (“THIS CODE MAY WELL CRASH YOUR PI, TRASH YOUR FILE SYSTEMS, AND POTENTIALLY EVEN DAMAGE YOUR HARDWARE.”). At one point, I took out the call to terminate at the bottom of my “serve_once” method, hoping that it would be automatically triggered on exit, but when I tried it, I didn’t see the “Terminating with signal” message, so I rebooted and put it back in. I suppose the alternative would be to use a processor strategy as opposed to dma, but my guess is it wouldn’t be as reliable. In any case, I’d need to buy some more hardware to tweak the settings to find the right timings.

Leave a Reply to hepcat72 Cancel reply


WordPress.com Logo


Google photo


Twitter picture


Facebook photo


Connecting to %s