Audio Frequency Shift Keying
Telemetry data for my balloon flight will be transmitted onto the APRS (Amateur Packet Reporting System) network, which is a well established network of Amateur Radio Operators here in the US. Data intended for the APRS network is transmitted using the AX.25 protocol (will cover this in a later post) using something called AFSK (Audio Frequency Shift Keying) at 1200 baud rate.
(* Disclaimer *) I am not any kind of scientist, nor an electrical engineer. I’m a software engineer by trade. Any information I provide here is accurate to the best of my understanding, but it could be incorrect, and you should verify anything I say for accuracy if it matters in your application. Polite corrections in the comments are welcomed.
AFSK is a system for transmitting binary data by alternating between two tones, each tone representing either a binary one or zero. The APRS network uses Bell 202 modem tones of 2200Hz (representing binary zero) and 1200Hz (representing binary one) to encode it’s data packets, at 1200 baud. [EDIT:] The previous sentence is incorrect. I learned that either 2200Hz or 1200Hz tones represent a binary one while the tone remains unchanged, switching from one tone to the other denotes a binary zero. This mistake appears in many documents detailing APRS and is critical in that getting it wrong will lead to a system which simply does not function.
[Edit: I still got this the wrong way around in my original correction, thanks to Chuck Faber for setting me straight – see comments]
So we need to generate a sine wave at these two tones, and transition between these two tones 1200 times per second (for 1200 baud).
Transmitting the tones involves putting the waveform onto the transmit (TX) pin of the radio, but here we have a problem. The Arduino Uno doesn’t actually have analog outputs. Though there may be pins labeled as analog pins, depending on your board supplier, there is no DAC (Digital to Analog Converter) hardware on the uno board. Instead, Arduino Uno uses a technique called Pulse Width Modulation (PWM) to approximate analog output.
Pulse Width Modulation
True DAC hardware would receive a value, expressed in binary on it’s inputs, and adjust the voltage of it’s output pin to represent that value in some measure of volts. In our case however, we have a digital Arduino output pin which may be set at zero volts or 5 volts, nothing in between. This is where PWM comes in.
If you’re familiar with Ohm’s law (aren’t we all? well don’t worry, I’ll explain), you’ll recognize that altering any one value in the triangle, necessitates a change in another. Essentially, you can’t alter the voltage without affecting either the resistance or the power part of the equation. As the radio is wired directly to the Arduino, our resistance is fixed. So if we want to alter the voltage output, we must adjust the power. We do this by averaging the power over a given period of time.
If we turn our digital output pin on and off very quickly, in pulses (ala, pulse width modulation), there will be periods in which the pin is at full power, and periods at which it will be at zero power. Averaging over a given period of time, we’re can adjust the amount of power output from our transmit pin. The wiki article I posted earlier, explains this far better than I can, go take a look at the section entitled “power delivery”. If this is still confusing, try reading this excellent article from the Arduino website.
So what we must do, is use pulse width modulation to approximate a sine wave.
PWM on Arduino Uno
The Atmega328 microcontroller at the heart of the Arduino Uno board, has a pulse width modulation feature. This feature comes with several modes, of which FastPWM is the simplest mode. FastPWM works like this:
- A timer fires 16,000,000 times per second (for 16Mhz clock speed.)
- Each time the timer fires, it increments a counter between 0 and 255.
- When the counter reaches 255 it is reset to 0.
This becomes interesting when we consider the comparator.
- We are able to set a value called the comparator.
- When the counter is at zero, the digital output pin is set high (turned on).
- When the counter reaches the value of our comparator, the output pin is set low (turned off).
- As the counter reaches 255, it wraps around to zero and our output pin is restored to high (turned on).
So, as you can see, every 256Hz (256 because the counter is indexed from zero), we can specify the amount of time the output remains off, and how much time it spends turned on.
Note: I’m ignoring the prescaler feature here. That-is, the main clock speed can be divided by some value to control the speed at which the counter is incremented. I’m ignoring it for simplicity.
The amount of time the pin spends turned on is referred to as it’s Duty Cycle.
Lets name each 1Hz as a pulse, since that’s what it is under PWM with no prescaler. The Atmega328p is going to take care of turning the pulse off for us after it reaches our comparator level, and it’ll turn the pulse on again when it reaches the 256th pulse (counter hits 255, rolls over to zero). So we’ll call this collection of 256 pulses a sample. Our sine wave will be made up of multiple samples, each with values (controlled by the comparator) representing a data-point within the sine wave.
The Sine Wave
Before we set up the PWM timer to emit the signal, lets take a look at the signal we’re going to emit. This is our sine wave.
Regardless of the frequency of the wave, we call this our carrier wave. That’s basically just another way of saying a wave which has a sine shape. If you think about it, regardless of the frequency at which the wave is emitted, it always has the same characteristic shape. When adjusting the signal frequency, the wave may appear to become wider, or narrower, but in fact it remains the same shape, it’s simply moving more quickly, giving us the perception of it being narrower.
The basic sine wave that we emit is called the carrier wave, because it’s the wave shape that will carry our data signal. When we adjust the frequency of the wave, by altering it’s speed, we are then said to be modulating the carrier wave…
In our case, the wave at a frequency of 2200Hz represents a binary zero, and at 1200Hz represents a binary 1. Since 2200Hz is the default state for our wave we can refer to a 2200Hz wave as the carrier wave, which we modulate to 1200Hz as required to denote a binary 1.
Since the shape never alters, lets take a closer look at the carrier wave…
If we take a measurement in the vertical (Y) direction, for any particular point in the horizontal (X) dimension, we get a single sample of the sine wave.
Now, suppose we divide the horizontal axis by, say, 256. We take the sample at X=0 and use that number as the comparator for our PWM signal. We then take a sample at X=1 and use that to set the comparitor for our PWM. We continue this process for all 256 samples (upto X=255). What we’ll have done is to play a sine wave out through the PWM output pin!
But hold those horses for a moment. If we divide the X axis of our sine wave by 256, and play out one X-axis sample for every 256 pulses, then it’ll take 256 samples to play one wave, and at most we can output 224.14 complete sine waves in a second. That’s far too slow. At 1200 baud (1200 bits per second) if we output nothing but binary 1’s at 1200Hz, we need to output at least 1200 full sine waves in a second. We need to speed things up!
I’ll show you just how to do that in a moment, but before I do, I’d like to introduce this useful online tool…
http://www.daycounter.com/Calculators/Sine-Generator-Calculator.phtml
This is an online sine wave generator. It’ll generate a sine wave for you, and then it’ll take a measure for every location on the x-axis, and output the y-axis as a number and a comma. This is ideal for copying and pasting into your source code to provide the source sine wave look up data. I generated a sine wave using the following parameters:
- Number of points: 512
- Max Amplitude: 255
- Numbers Per Row: 8
- HEX
This data provides a constant array, describing a sine wave, by breaking up the X-axis by 512. Wait, isn’t that twice as slow still as the 256 we used in the example above?! I’d better show you how to speed up and slow down the wave…
Modulating the carrier wave.
So, we have a carrier wave as a constant array of 512 data-points (samples), which we want to present using pulse width modulation. For the first data point, lets say it’s a value of 128, we will set the comparator value for PWM to 128 (50% duty cycle), and then allow 256 clock cycles to pass, during which the PWM timer counts it’s way from 0 to 255, modulating the output as per the comparator.
At the end of that 256 pulse period, we move on to the second data point in our sine wave and set the comparator accordingly. Let the PWM timer do it’s thing, and repeat the process again.
So long as we continue like this, we’re going to output all 512 data points from our source data, that-is, 512 samples will be played. It’ll take 512*256 = 131072, clock cycles to do so, and given that our system clock is 16Mhz, that’s 16,000,000 / 131072 = 122.07 samples per second. That’s half what we calculated earlier when we looked at playing 256 samples from the sine wave and came to 244.x samples per second.
In order to speed things up, what we’ll do is simply not transmit every single sample from the source data. Instead, we’ll use the desired output frequency, and the amount of time that we know to have passed, to calculate our X-axis position through the wave, and output that value. 256 clock cycles later, we’ll do the same again, we’ll calculate how much further through the wave we should be, given our desired output frequency, and use that value to take a sample from our source data and play it.
It looks something like this…
- Clock_Rate = 16Mhz = 16,000,000
- Play_Rate = Clock_Rate / 256 (samples per second) = 62,500 samples per second
- Data_Size = 512 (number of samples in our source data set)
- Frequency = 2200Hz (wave output frequency)
- Data_Frequency = Play_Rate / Frequency = 62,500/2200 = 28.409
- Stride = 512 / Data_Frequency
So lets read that aloud. Our clock frequency is 16 million cycles per second, but each sample that we play using PWM takes 256 cycles, so 16 million divided by 256 gives us 62,500 samples per second as our play rate.
We want to play our wave at 2200Hz, that’s 2200 times per second, so divide the playable samples per second, 62,500 by 2200 and that gives us the number of samples we’re able to play in a single period of a 2200th of a second, which is 28.40. We must therefore divide our data source up into 28.4 chunks, that’s 512 data points / 28.4 samples = 18.02.
Lets work that backwards to make sure it all makes sense.
For each output sample, we jump 18.02 places through our source data. If we do this 28.4 times, we’ll have transmitted 28.4*18.02 data points = 511.768, lets call this 512 to account for rounding issues in my calculations. (*note, you can do this calculation more precisely if you’d prefer, just grab your calculator.) 512 is the size of our data set, which contains a single wave, so we’ve transmitted a full sine wave. If we transmit 28.4 samples, 2200 times, that gives us 62480 samples transmitted, which we call 62,500, again to account for rounding issues. Each of those samples costs 256 clock cycles, so 62,500 * 256 = 16,000,000 = 16Mhz the speed of our clock.
As an exercise, do the same again for a target of 1200Hz instead of 2200Hz.
A word on Phase
The method we’re using gives us an unexpected (well, planned actually) benefit. You’ll notice that in the example above, we’re jumping 18.02 places through our source data (X-Axis). So we are going to keep a running accumulator that tells us where we are within our source data. Each 256 clock cycles, we add 18.02 to that accumulator and we find ourselves at the next X-axis location within the source data. If you did the same calculation for 1200Hz frequency, you’ll have discovered that the stride size is 9.831. So, suppose we’re half way through transmitting a sine wave at a stride of 18.02, and we switch the stride to 9.831. What happens is, we get half a wave at 2200Hz, followed by the remainder of the wave at 1200Hz. We can essentially switch our transmission frequency at any point in time, and the wave shape continues unaffected, but at a different speed!
This is perfect for our application because our baud rate of 1200 forces us to transmit bits at a 1200th of a second intervals. At 1200Hz that’s great, it’s one full wave, but at 2200Hz it’s two and a bit waves, so the first time we transmit 2200Hz, our signal falls out of phase with the baud rate. Being able to switch back and forth between 1200Hz and 2200Hz at every baud period, regardless of our position within the wave, allows us to keep continuity in our signal regardless of the phase discrepancy.
Armed!
If you read my most recent post, you’ll know that I’m planning a flight and that my time is limited. I really wanted to complete this post with a source-code walk through, however, I don’t have time to do that at present.
I think I’ve provided enough information here for you to make your own AFSK1200 modem without further help from me. Perhaps I’ll come back after the flight and do a source-code walk through, but no promises yet.
Until then, make sure you understand what I’ve posted here and then work with this great PWM tutorial http://arduino.cc/en/Tutorial/SecretsOfArduinoPWM and the AtMega328p data sheet.
Until next time,
Thanks for reading!







I’m pleased that it was helpful to you. I wrote this a long time ago, and having just read it back, I’m not certain I still understand it myself! That’s one of the reasons I write such posts, so I can read it back when I need it later. Good luck with your project.
Thanks. One part I am a bit confused about is that you mention that no changes in the frequency represents a zero, but this page (https://www.tapr.org/pdf/DCC1998-PICet-W2FS.pdf) states that packet radio is NRZI- meaning that if there is no change, it represents a 1, and that the changes represent a zero, and even goes on to add code to add a zero every 5 ones.
I was wondering where you got your information in your edit that the lack of change is zero and that a change is 1, because I can’t seem to find/understand information that makes a statement on this either way besides your article and that article I linked.
So upon further research, the AX.25 protocol uses HDLC NRZI, which states that in fact, no transition signifies a 1 and a transition signifies a 0.
https://en.wikipedia.org/wiki/Non-return-to-zero#NRZI
Thanks, it’s been so long that I don’t recall where this information came from.
I’ll update the post to reflect the correct state change.
Thank you so so much for this clear explanation on the theory behind AFSK1200 modulation on the Arduino! I’ve been looking everywhere and could only find documentation that seemed near impossible to understand. This helps a lot!