Wireless Microcontroller / PC Interface for $3.21

Here I demonstrate a dirt-cheap method of transmitting data from any microchip to any PC using $3.21 in parts.  I’ve had this idea for a while, but finally got it working tonight. On the transmit side, I’m having a an ATMEL AVR microcontroller (ATMega48) transmit data (every number from 0 to 200 over and over) wirelessly using 433mhz wireless modules. The PC receives the data through the microphone port of a sound card, and a cross-platform Python script I wrote decodes the data from the audio and graphs it on the screen. I did something similar back in 2011, but it wasn’t wireless, and the software wasn’t nearly as robust as it is now.

This is a proof-of-concept demonstration, and part of a larger project. I think there’s a need for this type of thing though! It’s unnecessarily hard to transfer data from a MCU to a PC as it is. There’s USB (For AVR V-USB is a nightmare and requires a precise, specific clock speed, DIP chips don’t have native USB, and some PIC DIP chips do but then you have to go through driver hell), USART RS-232 over serial port works (but who has serial ports these days?), or USART over USB RS-232 interface chips (like FTDI FT-232, but surface mount only), but both also require precise, specific clock speeds. Pretend I want to just measure temperature once a minute. Do I really want to etch circuit boards and solder SMT components? Well, kinda, but I don’t like feeling forced to. Some times you just want a no-nonsense way to get some numbers from your microchip to your computer. This project is a funky out-of-the-box alternative to traditional methods, and one that I hope will raise a few eyebrows.


Ultimately, I designed this project to eventually allow multiple “bursting” data transmitters to transmit on the same frequency routinely, thanks to syncing and forced-sync-loss (read on). It’s part of what I’m tongue-in-cheek calling the Scott Harden RF Protocol (SH-RFP). In my goal application, I wish to have about 5 wireless temperature sensors all transmitting data to my PC.  The receive side has some error checking in that it makes sure pulse sizes are intelligent and symmetrical (unlike random noise), and since each number is sent twice (with the second time being in reverse), there’s another layer of error-detection.  This is *NOT* a robust and accurate method to send critical data. It’s a cheap way to send data. It is very range limited, and only is intended to work over a distance of ten or twenty feet. First, let’s see it in action!

The RF modules are pretty simple. At 1.56 on ebay (with free shipping), they’re cheap too! I won’t go into detail documenting the ins and out of these things (that’s done well elsewhere). Briefly, you give them +5V (VCC), 0V (GND), and flip their data pin (ATAD) on and off on the transmitter module, and the receiver module’s DATA pin reflects the same state. The receiver uses a gain circuit which continuously increases gain until signal is detected, so if you’re not transmitting it WILL decode noise and start flipping its output pin. Note that persistent high or low states are prone to noise too, so any protocol you use these things for should have rapid state transitions. It’s also suggested that you maintain an average 50% duty cycle. These modules utilize amplitude shift keying (ASK) to transmit data wirelessly. The graphic below shows what that looks like at the RF level. Transmit and receive is improved by adding a quarter-wavelength vertical antenna to the “ANT” solder pad. At 433MHz, that is about 17cm, so I’m using a 17cm copper wire as an antenna.

Transmitting from the microcontroller is easy as pie! It’s just a matter of copying-in a few lines of C.  It doesn’t rely on USART, SPI, I2C, or any other protocol. Part of why I developed this method is because I often use ATTiny44A which doesn’t have USART for serial interfacing. The “SH-RFP” is easy to implement just by adding a few lines of code. I can handle that.  How does it work? I can define it simply by a few rules:


SHRFP (Scott Harden RF Protocol)

Pulses can be one of 3 lengths: A (0), B (1), or C (break).

Each pulse represents high, then low of that length.

Step 1: prime synchronization by sending ten ABCs

Step 2: indicate we’re starting data by sending C.

Step 3: for each number you want to send:

A: send your number bit by bit (A=0, B=1)

B: send your number bit by bit (A=1, B=0)

C: indicate number end by sending C.

 Step 4: tell PC to release the signal by sending ten Cs.

Decoding is the same thing in reverse. I use an eBay sound card at $1.29 (with free shipping) to get the signal into the PC. Syncronization is required to allow the PC to know that real data (not noise) is starting. Sending the same number twice (once with reversed bit polarity) is a proofchecking mechanisms that lets us throw-out data that isn’t accurate.

From a software side, I’m using PyAudio to collect data from the sound card, and the PythonXY distribution to handle analysis with numpy, scipy, and plotting with QwtPlot, and general GUI functionality with PyQt. I think that’s about everything.

 The demonstration interface is pretty self-explanatory. The top-right shows a sample piece of data. The top left is a histogram of the number of samples of each pulse width. A clean signal should have 3 pulses (A=0, B=1, C=break). Note that you’re supposed to look at the peaks to determine the best lengths to tell the software to use to distinguish A, B, and C. This was intentionally not hard-coded because I want to rapidly switch from one microcontroller platform to another which may be operating at a different clock speed, and if all the sudden it’s running 3 times slower it will be no problem to decide on the PC side. Slick, huh? The bottom-left shows data values coming in. The bottom-right graphs those values. Rate reporting lets us know that I’m receiving over 700 good data points a second. That’s pretty cool, especially considering I’m recording at 44,100 Hz. 

Here’s the MCU code I used. It’s an ATMega48 ATMEL AVR microcontroller. Easy code!

#define F_CPU 8000000UL

#include <avr/io.h>
#include <util/delay.h>

void tick(char ticks){
	while (ticks>0){

void pulse(char ticks){

void send_sync(){
	char i;
	for (i=0;i<10;i++){

void send_lose(){
	char i;
	for (i=0;i<5;i++){

void sendByte(int val){
	// TODO - make faster by only sending needed bytes
	char i;
	for (i=0;i<8;i++){
		if ((val>>i)&1){pulse(2);}

void send(int val){
	sendByte(val);  // regular
	sendByte(~val); // inverted

int main (void)
    DDRB = 255;
	int i;

    while(1) {
		for (i=0;i<200;i++){

Here’re some relevant snippits of the PC code. Download the full project below if you’re interested.

import matplotlib
matplotlib.use('TkAgg') # -- THIS MAKES IT FAST!
import numpy
import pyaudio
import threading
import pylab
import scipy
import time
import sys

class SwhRecorder:
    """Simple, cross-platform class to record from the microphone.
    This version is optimized for SH-RFP (Scott Harden RF Protocol)
    Pulse data extraction. It's dirty, but it's easy and it works.

    continuously record sound in buffers.
    if buffer is detected:

        ### POPULATE DELAYS[] ###
        downsample data
        find Is where data>0
        use ediff1d to get differences between Is
        append >1 values to delays[]
        --if the old audio[] ended high, figure out how many
        --on next run, add that number to the first I

        only analyze delays through the last 'break'
        values[] is populated with decoded delays.


    def __init__(self):
        """minimal garb is executed when class is loaded."""
        print "BUFFER:",self.BUFFERSIZE






    def setup(self):
        """initialize sound card."""
        #TODO - windows detection vs. alsa or something for linux
        #TODO - try/except for sound card selection/initiation
        self.p = pyaudio.PyAudio()
        self.inStream = self.p.open(input_device_index=None,

    def close(self):
        """cleanly back out and release sound card."""

    def decodeBit(self,s):
        "given a good string 1001101001 etc, return number or None"
        if len(s)<2:return -2
        A=s[:len(s)/2] #INVERTED
        B=s[len(s)/2:] #NORMAL

        if A<>B:
            return -1
            return int(A,2)

    def analyzeDataString(self):
        while i<len(self.dataString):
            if self.dataString[i]=="B":
            if self.dataString[i] in ['B','?']:
        if len(self.data)>self.dataToKeep:

    def continuousAnalysis(self):
        """keep watching newAudio, and process it."""
        while True:
            while len(self.newAudio)< self.BUFFERSIZE:



            # TODO - insert previous audio sequence here

            # GET Is where data is positive

            # remove some of the audio buffer, leaving the overhang

            if len(Icross)>0:


            if False:
                # chart audio data (use it to check algorythm)

                for i in range(len(Icross)):
                    # plot each below-zero pulse whose length is measured

                # plot the hangover that will be carried to next chunk

            # TODO - histogram of this point to assess quality
            for pulse in pulses:
                if (self.SIZE0-self.SIZEF)<pulse<(self.SIZE0+self.SIZEF):
                elif (self.SIZE1-self.SIZEF)<pulse<(self.SIZE1+self.SIZEF):
                elif (self.SIZE2-self.SIZEF)<pulse<(self.SIZE2+self.SIZEF):


            print "[%.02f ms took %.02f ms] T: 0=%d 1=%d B=%d ?=%d"%(




    def continuousRecord(self):
        """record forever, adding to self.newAudio[]. Thread this out."""
        while self.threadsDieNow==False:
            while len(self.newAudio)>(maxSecBack*self.RATE):
                print "DELETING NEW AUDIO!"

    def continuousDataGo(self):
        self.t = threading.Thread(target=self.continuousRecord)
        self.t2 = threading.Thread(target=self.continuousAnalysis)

    def continuousEnd(self):
        """shut down continuous recording."""

if __name__ == "__main__":


    print "---DONE---"

Finally, if you’re interested, here’s the full code (and demo audio WAV files):


If you use these concepts, hardware, or ideas in your project, let me know about it! Send me an email showing me your project – I’d love to see it. Good luck!


35 thoughts on “Wireless Microcontroller / PC Interface for $3.21

  1. Looks great. Any possibility of capturing the audio signal into a .wav file? I’d be interested in taking a look and possibly doing a decode/transmit app in IOS. I saw you soldered the Rx module onto a USB sound card. Would soldering to a audio jack which would plug into most laptops have worked too? Thanks

  2. This caught my fancy – folks jokingly say “Stan’s Law” = “You can never have too many thermometers” – but approaches with cheap PICAXE micros., DS18B20 & cheap USB-serial cables may be far easier. The PICAXE programming editor also offers an inbuilt terminal, but the likes of StampPlot & StampDAQ offer brilliant displays.

    UHF data modules abound, but note that 433 MHz offerings may not be US legal – use 315MHZ instead! For simple ASK work Chinese Dorji work a treat -see => http://www.picaxe.orconhosting.net.nz/dorjiask.pdf , although sophisticated GFSK transceivers are increasingly preferred to ease data loss & avoid band noise.

  3. Hmm first post disappeared . It tried url tags. I used transmitter/receiver pairs from http://www.oatleyelectronics.com/ for about $20 and got a line of sight range of about a mile and 100 meters through a couple of concrete block walls. Your antenna might be improved by using finer wire which will sharpen the bandwith some and help prevent breakage at the board.
    Very interesting post Thanks

    • I believe the larger the diameter of an antenna the more bandwidth it will exhibit. Not sure if the bandwidth of the antenna would be an issue in these project anyway. At 70 cm RF doesn’t go through concrete block walls. The shorter wave lengths are easily reflected, and can make better use of any openings in a building.

  4. Nice.

    About time someone got rid of the idea of fixed baud rate.

    I too like the idea of a simple wireless way to plot data. Eliminates MANY ground loop problems from measurements.

    Nice job.


  5. Downloaded it. Does not run :(.

    Says cannot import name Qwt5 (from PyQt4.

    I am running Knoppix [linux]. I didn’t find a simple way to install pythonxy since the only copy I find on
    the pythonxy website is an .exe file?

    Does pythonxy really run on Linux? Also, I didn’t find pythonxy in the debian apt pkgnames list.

    Looks like it would be most useful if I can get it to run.

    Help please.


  6. Did you consider using Manchester encoding? I’m guessing you did, but decided against it because the timing is clock-dependent?

    I like the approach of using relative-timed pulses to make the implementation independent from the clock, though.

  7. This is great Scott. What about using frequency relationships verses pulse width? The reason being is that it would be neat to be able to send the data very slow too, using QRSS methods. So a “1” would be say a 1000hz tone a “0” 1200hz and maybe 1500hz for the “break”. Great work in any case.

    -Dan WA6PZB

    • I considered this, and a potential plan is still on the table. However, the reason I decided against it is because, to detect a frequency, it would have to take several ms transmitting that tone (maybe a few hundred up/down cycles) which ultimately is slower than measuring the width of individual pulses. The thing that would make it nice is if I could combine frequencies and transmit 1k, 1.1k, 1.2k, up through 8 tones. Then each tone would contain 8 possible frequencies, and their presence or absence can signify 1 or 0. Thus, each “pulse” transmits an 8-bit number. Finally, strength detection can be done by measuring the SNR. Lots of ideas to play with…

  8. Thanks Scott, I figured it was in your head too. Your on the right track trying to keep it simple (and cheap!). I just got my python environment up and duplicated your demo, I hope to try a few things. I would like to see if it is possible to get better range with these 433mhz TX devices for a potential balloon project. You probably know about the RFM22’s devices, but they require a more advanced interface/MCU. Folks are using these RFM22 on balloons that are going 100,000 feet in the UK but using SSB radio’s and RTTY. -73 Dan WA6PZB

  9. Hi All,

    Is anyone running this on Linux? Looks like I gotta do the necessary work?

    Of possible interest, Bell 202 actually used 3 frequencies as well.
    One was 2200Hz, zero was 1200Hz and 900Hz +/- for end of data burst.
    Not too far from this scheme. But not synchronous.
    Also Bell 202 could carry up to 1800 BPS on some lines.

    Caller ID uses Bell 202 and adds a leading training burst somewhat similar to this scheme.


  10. These modules are great Scott, I’ve used them a bunch before with a similar perpous, monitoring temperature and humidity, with some DHT-11 (http://www.ebay.com/itm/DHT11-DHT-11-Digital-Temperature-and-Humidity-Sensor-Temperature-sensor-Arduino-/400489574221?pt=LH_DefaultDomain_0&hash=item5d3f09ef4d) sensors.

    My main problem was the range, like you I get at best 15-20 feet before the noise from all the other 433mhz stuff in my area drowns them out. I’ve been trying to figure out a simple solution to amplify the signal but have came up empty handed, there are some single chip amplifiers, but they’re kinda expensive. Have you tried anything to get a better range (other than adding an antenna!)

    • I haven’t tried extending the range. For anything further, I’d go ahead and upgrade to something with more power. Those 2.6ghz modules linked here look cool too. Xbee is a nice option. Also there are 900mhz wireless data modems you can buy for a little higher power professional solutions. I’ve always wondered about hijacking the audio connection of a cordless telephone – would be an easy way to send data a good distance with stuff you might have in your junk box.

      • I’ve used the RFM-22 modules, you can also get a nRF24L01 for $1~ each on ebay too, I never got over 100 feet out of them though, they have some that have a PA/LNA that claim to do 1000m!

        The cordless phone idea is unique… but I imagine they’d the drain batteries fast, I’ve used FRS/GMRS radios in a similar fashion before, but never considered using one with a USB sound card to get capture the data… so now I’m tempted to try it again…. but its not quite legal, as data is not allowed on GMRS/FRS frequencies.

  11. Any ideas on how to send data to the microcontroller using a related strategy?
    It would be useful, even if just to synchronize when a bunch of sensors send their data to the pc.

      • Electronics is not my expertise, which is why I loved your idea to transmit from the microcontrollers (sound cards are in the post!)
        Your solution for the microcontrollers to receive data seems a lot more complicated? Is it?
        Is there any way, using python, to get the sound card to transmit a signal that is clean enough to be interpreted by the microcontroller and its rf module?

  12. Mr.Scott ,your project concerns about transferring from controller to pc…i thought of transferring code from pc to controller wirelessly using dumper..is it possible???im an engineering student…