AM Band Voice Frequency Marker

SDR generating 118 simultaneous stations, each announcing their own frequency

Similar projects worth following
A frequency marker generator is a very useful tool for calibrating antique or experimental (even crystal!) medium wave (aka AM broadcast band) radios. While an RF marker can be as simple as a fixed frequency oscillator, that's not the most convenient way to calibrate multiple frequencies. This project provides a software-defined-radio solution for a deluxe marker generator using a cheap USB-to-VGA dongle. The output signal consists of 118 individual AM "stations" in the MW band - one for each assigned channel in the US - each announcing their own frequency in a synthesized voice.

The idea is simple: generate an entire broadcast band of stations at once. The first prototype (software-only) makes each station announce its own frequency in a synthesized voice. This is convenient for aligning and testing antique or simple radios, such as crystal sets.  Now, I've started using the osmo-fl2k code and a cheap USB-to-VGA dongle.

The initial tests had been software-only, using python code to call the flite speech synthesizer and generate IQ data that the gqrx SDR receiver software decodes as if it were received by SDR hardware. As a proof of concept, this works great - you can see so in the video:

There's to reason to stop there, though. I can imagine generating an entire band full of old radio shows to send to an antique radio.

The python code to generate the samples file is in the github repo.

The next step is to get some hardware to emit the RF signal. The MW band in the US has 10kHz channels centered on 530 to 1700 kHz every 10 kHz, so the edges are at 525 and 1705kHz.  The easiest path forward is just to sample the signal directly.  As long as you sample at a rate greater than 3.41 MHz (2x 1705 kHz), you theoretically can reconstruct the signals exactly.

In practice, things can be a little different.  I've been experimenting with the osmo-fl2k code using a cheap USB 3.0-to-VGA dongle I ordered from ebay.  The output uses the red channel of the VGA adapter and is limited to 8-bit resolution (I'm assuming).  This limits the signal-to-noise ratio to 49.92 dB (1.76 + 6.02*8).  While this is sufficient for getting a voice announcement on each channel, it's not ideal for putting many old radio programs on at once (a secondary goal of this project).

One of the things I'm experimenting with is oversampling.  Although you need only a 3.41 MHz sampling frequency to reproduce the frequencies in the band, you can increase the signal-to-noise ratio by increasing the sampling rate.  This is possible because the quantization noise introduced by the DAC is uniformly spread over the output bandwidth.  When you oversample, less of the noise falls within the desired signal bandwidth, increasing the SNR.  The effective SNR can be calculated as:

Where fs is the sampling frequency and B is the signal bandwidth.  For example, if oversample the signal at 96 MHz, we can achieve an effective SNR of 49.92 + 10 log(96e6/(2*1.706e3)) =  64.4 dB.  The only expense for this improvement will be some larger files.

But, we can do better than that.  If we use a sigma-delta modulator, we can shape the noise even more, pushing more of the quantization noise into frequencies above the MW band, where it can be filtered out before the signal is applied to the radio.  You often see sigma-delta modulators driving 1-bit DACs, but there's no reason they can't be used to drive multi-bit converters.  The result is a digital-to-digital converter which converts samples at N bits to higher-frequency samples at M bits (N>M).  This concept is described in this paper. I'm currently experimenting with various order modulators of this type.

I think all that remains to be done is build a small PCB with a VGA male connector and a lowpass filter for the MW band (which I've already designed).  Coupling to the radio will depend on the radio of course, but at this point, I imagine either a direct connection for radios with antenna terminals or a loop coupler for those without.

The code is now working and documented in a build log.

Next, I'm planning to enhance the software to enable a collection of old radio programs to be modulated onto the band simultaneously.

I'll be documenting these experiments as I go.


AM voice marker for EU frequency allocations (531-1602 kHz, 9kHz channels) sampled at 8192 kHz.

dat - 16.45 MB - 04/30/2018 at 16:36



AM voice marker for US frequency allocations (530-1700 kHz, 10kHz channels) sampled at 8192 kHz.

dat - 16.41 MB - 04/30/2018 at 16:35


  • Coupling Coil

    Ted Yapo05/13/2018 at 02:12 0 comments

    I threw together a design for a printed coupling coil form in OpenSCAD, then wrapped it with 28Ga wire and attached an SMA pigtail to attach it to the LPF board.

    I'm thinking of the coil as the primary side of a transformer, with the "loopstick" antenna inside the receiver as the secondary.  The rule of thumb is that transformer windings should have at least four times the system impedance at the lowest frequency of interest.  This coil form of around 100mm diameter, wound with 24 turns of 28Ga wire, measures around 165uH.  At 500 kHz, this represents a 518 ohm impedance, which is plenty in either a 50 or 75-ohm system.

    The form was designed in OpenSCAD, and is in the GitHub repo.

    The idea was to save material and keep as much non-air out of the core as possible.  I have to imagine that PLA isn't an ideal core material.  In any case, it came together quickly, so could probably be improved.  After printing, it seems stronger than necessary, so more material could be removed.

    How does it work?  Compared to the smaller coil (which was only around 17uH), this one gives a stronger signal and alignment with the radio is much less critical.  It's sufficient to just have the coil nearby.  As an RF alignment tool it's probably as good as it needs to be.

  • DAC Rolloff Compensation

    Ted Yapo05/12/2018 at 22:08 0 comments

    As mentioned in the last log, the DAC on the FL2K VGA chipset introduces a frequency roll-off due to the first-order hold.  This roll-off is sinc-shaped, with the first zero at the sampling frequency.  As a result, an output at the Nyquist frequency is down 3.92dB, while one at 50% of the Nyquist frequency is 0.91dB down.

    I wrote a little python code to implement a compensation filter for this issue.  Using a filter like this, you can pre-equalize the signal to flatten the post-DAC frequency response.  You can see the results in the above spectra.  Both signals start as random samples from the python numpy.rand() function: their spectra should be flat.  The yellow trace shows the signal sent directly to the DAC with the resulting frequency roll-off.  For the magenta trace, the samples were first filtered with a 31-tap FIR filter designed to compensate for the rolloff.  Note that the sampling frequency here is 8.192 MHz, putting the Nyquist frequency at 4.096MHz, very near the marker at 4.1MHz.

    To generate the filter, I used the scipy.signal.firls() function to least-sqaures fit a filter to 1/sinc(f):

    import numpy as np
    from scipy import signal
    N = 100
    bands = np.linspace(0, 1, N)[1:-1]
    bands = np.repeat(bands, 2)
    bands = np.append(bands, 1)
    bands = np.insert(bands, 0, 0)
    desired = 1/np.sinc(bands/2)
    b = signal.firls(31, bands, desired)

    Most of the work is setting up the frequency bands.  The desired response is the inverse of the sinc rolloff.  The noise was generated with the np.random.rand() function, and the noise is compensated by filtering with the signal.lfilter() function:

    n_samples = 8192 * 1000 * 20
    noise = np.random.rand(n_samples)
    comp_noise = signal.lfilter(b, 1, noise)
    comp_noise *= 1./np.amax(np.abs(comp_noise))

    Because the filter has a gain greater than one, I divide the result by the maximum signal level to keep the filtered signal within the DAC range.

    There are several other ways to deal with this roll-off issue.  Oversampling can reduce the impact because the sinc function is relatively flat near zero.  The more you oversample, the less you'll notice the sinc rolloff.  But, for signals using a significant portion of the available DAC bandwidth, this type of pre-compensation makes sense.

    And of course, this issue doesn't only apply to noise, but to any signal generated with this DAC.

    Measuring LPF Again

    So, now that we have a flat noise spectrum, we can measure the LPF again, more confident that features in the stopband won't be hidden by the noise rolloff.  In this case, I've used a 20MHz sample frequency, so the flat spectrum extends up to 10 MHz, as shown:

    Inserting the LPF (now with the span set to 10 MHz), we have a better picture of the filter response:

  • PCBs assembled; LPF tested; boards shared

    Ted Yapo05/12/2018 at 15:43 0 comments

    The PCBs for the breakout board and lowpass filter arrived yesterday. 

    The VGA connector fit, but the holes don't leave much room for tolerances.  I updated the PCBs in the GitHub repo with the hole sizes recommended on the datasheet, although they seem a bit big to me: tolerances will not be an issue :-).  I also added plated-though holes for the VGA connector snap-in tabs so they can be soldered down for a more secure mounting (thanks @K.C. Lee).  On the prototype PCBs, I scraped away some of the soldermask near the outer tabs to solder them.  It's not electrically necessary, but adds some extra mechanical strength.

    In between designing these two PCBs, I drew a new footprint for the edge-launch SMA connector for better impedance matching, but forgot to remove the soldermask on the bottom layer so the bottom tabs of the connector could be soldered. 

    There are generous vias in the footprints, with ground planes top and bottom, so it's more of a mechanical thing at least for the frequencies I'm worried about at the moment, and these are fine for prototypes.  But, if you are going to make some of these PCBs, grab the latest from GitHub (or the OSH Park links below), since I did fix this after ordering the boards.

    I also reduced the size of the breakout so the longest dimension is less than 50mm, which might matter if you order PCBs from somewhere else.

    Details about each board, including BOMs and test results are shown below.

    VGA Breakout

    I discussed the breakout in a previous log.  Here's the schematic again for reference:

    The resistor networks match the 75-ohm VGA outputs to the 50-ohm SMA connectors.  You incur almost 6dB of loss for this, so if you don't care about matching, you can just use zero-ohm 0805 jumpers at R1, R3, and R5, and not populate R2, R4 and R6.

    PCBs can be ordered from OSH Park.

    You also need these parts:

    The header is just a standard 5-pin 2.54mm spaced header.  I ordered 9mm wide (not the narrow 6.5mm) SMA connectors from ebay - make sure you get ones that match your PCB thickness (typically 1.6mm from OSH Park).  You only need one for now, because the osmo-fl2k code only uses the red channel, but should it be expanded to use green and blue, you may need all three.

    AM LPF

    I designed a lowpass filter for the AM broadcast band.  Obviously, it's your responsibility to obey the regulations regarding RF emissions wherever you are, but this might help you stay out of trouble.  At least in the US, you are allowed to operate very-low powered transmitters in the AM broadcast band (I'll leave locating and interpreting the regulations as an exercise for the reader).  What could easily get you into trouble, though, is any emissions in the amateur HF allocations.  Amateur radio operators guard their spectrum like a dog with a bone, and will find you and harass you at the slightest provocation.  This filter will kill harmonics that might interfere with the ham bands.

    Here's the final version of the lowpass filter as simulated in LTspice:

    The original design had 9.1uH inductors at L1 and L3, but I substituted 10uH because high-Q ones with a decent SRF were available.  The passband...

    Read more »

  • fl2k rates through different ports

    Ted Yapo05/01/2018 at 16:37 0 comments

    I used the fl2k_test program to check the sample rate I could get through various USB interfaces.

    First, through a motherboard USB3.0 connector:

    $ fl2k_test -s 162e6
    Reporting PPM error measurement every 10 seconds...
    Press ^C after a few minutes.
    real sample rate: 109697891 current PPM: -322853 cumulative PPM: -322853
    real sample rate: 109648409 current PPM: -323158 cumulative PPM: -323012

    109 MHz.  Not bad.  Now through a USB 3.0 hub:

    $ fl2k_test -s 162e6
    WARNING: Failed to set sample rate.
    Reporting PPM error measurement every 10 seconds...
    Press ^C after a few minutes.
    real sample rate: 86140872 current PPM: -468266 cumulative PPM: -468266
    real sample rate: 84019530 current PPM: -481361 cumulative PPM: -474994

    84 MHz is a little worse, but still not bad.

    I seem to get "WARNING: Failed to set sample rate" randomly.

    Now, through a USB 2.0 port:

    $ fl2k_test -s 162e6
    Reporting PPM error measurement every 10 seconds...
    Press ^C after a few minutes.
    real sample rate: 14560550 current PPM: -910120 cumulative PPM: -910120
    real sample rate: 14561029 current PPM: -910117 cumulative PPM: -910119

    14 MHz.  Still good enough for the MW generator.  I could even bump the sampling rate up from the current 8192 kHz.

    I still have to try this on a Raspberry Pi.

  • FL2K Breakout

    Ted Yapo05/01/2018 at 14:45 5 comments

    I threw together a quick breakout for the fl2k dongle so I can hook it up with all the connectorized microwave junk I've accumulated over the years from ebay.  There are min-loss pads on there for converting the 75-ohm output of the VGA channels to 50-ohms.  You can populate them with jumpers if you don't care about matching and want to avoid the 5.72 dB loss.

    I also broke out the I2C lines and the horizontal and vertical sync for good measure.  This PCB should make quick experiments easier.  I'm putting the design in GitHub, but not sharing it on OSH Park yet because I haven't tested the VGA connector footprint.  Once it checks out, I'll share the design and post a BOM.

    The 75-ohm traces taste like 75-ohms, and the 50-ohm traces taste like 50-ohms, at least according to AppCAD.

    The osmo-fl2k code seems to just use the red channel at this point, but I can imagine using two of them for I/Q outputs, which would be pretty cool.  Maybe you can even do some primitive beam forming with just three outputs?

    Here's the schematic:

    It's really embarrassingly simple.

    I cribbed the matching pad design from this Maxim app note.  I used to enjoy working through the algebra for those kinds of things, but lately I don't feel like spending the time.

    I also sent out a lowpass filter design, which I'll write up in a separate log.

  • osmo-fl2k FTW!

    Ted Yapo04/30/2018 at 17:12 0 comments

    I read about the osmo-fl2k program on the blog last week, and ordered one of the USB-VGA dongles from ebay immediately.  Within a few minutes of opening the package I had it spewing RF all over the bands (which is awesome and terrible at the same time).  It took a little while to get my original python code in shape, but the whole thing works now.

    This project has been smoldering for a year, so it's good to finally see it work!

    The output is a bit garbled on certain frequencies because there are licensed broadcasters on there too (the nerve!).

    To couple the output to the radio, I just used a random coil I had around.  The coil connects to the red VGA channel and ground through a 75-ohm resistor I included just so I didn't have to worry about dissipation in the dongle.  I have a design for a decent lowpass filter, and as soon as I can get the footprint done for a male VGA connector, I'll post a PCB for it on OSH Park.  I'll give some design consideration to the coupling coil, too.

    On a spectrum analyzer, you can see the block of 118 stations generated by the program run with defaults (US frequencies):

    The amplitude scale is arbitrary, since I connected the dongle to the SA using an oscilloscope probe and an external attenuator.  No sense in blowing the first mixer with some unknown signal.  A little calculation shows that VGA maximum output is 0.7V into 75 Ohms which is around -1dBm.  Nothing to be afraid of, after all.

    Zooming in the span, you can make out the individual carriers spaced at 10 kHz:


    If you just want to generate the marker output with your USB dongle and osmo-fl2k, grab the pre-made data files: you can choose either the US allocation (10kHz channels) or EU allocation (9kHz channels).  Both are sampled at 8192kHz and use an order-0 sigma-delta modulator (nothing more than direct 8-bit output).  They're also in GitHub.

    There are three pre-requisites, although only the osmo-fl2k code is required if you use the pre-generated files.  You need to download and compile the osmo-fl2k code, and install the flite speech synthesizer and cmu_us_ljm.flitevox voice.  The osmo-fl2k code can be obtained from the web site.  I installed the flite synthesizer on linux with:

    sudo apt-get install flite

    and finally, you can get the voice I used from this page.  Put the cmu_us_ljm.flitevox voice file in the same directory as the python script.  You could substitute another voice, too - I really should add a command-line option for that.

    You generate the output by running the fl2k_file command (built from the osmo-fl2k code):

    fl2k_file -s 8192000 us_order0_8192k.dat   

    Don't forget the sample rate!

    The program substitutes 8196720 Hz, which is 576 ppm off. It doesn't matter.

    If you want some other options, you can run the program from the command line.  The options look like this:

    usage: [-h] [-b BEGIN_FREQ] [-e END_FREQ] [-s FREQ_STEP]
                             [-o ORDER] [-a SCALE] [-c CARRIER_SAMPLE]
                             [-r OUTPUT_SAMPLE]
    create synthesized AM frequency markers for use with osmo-fl2k
    positional arguments:
      output                output file
    optional arguments:
      -h, --help            show this help message and exit
      -b BEGIN_FREQ, --begin-frequency BEGIN_FREQ
                            first frequency in kHz (default = 530)
      -e END_FREQ, --end-frequency END_FREQ
                            last frequency in kHz (default = 1700)
      -s FREQ_STEP, --spacing FREQ_STEP
                            frequency spacing in kHz (default = 10)
     -o ORDER,...
    Read more »

  • rpitx??

    Ted Yapo04/12/2017 at 22:46 2 comments

    I had heard of the rpitx program before - it uses a PLL on the Raspberry Pi to generate RF signals directly. I'm not sure what the bandwidth is, but I saw a reference to someone transmitting HDTV signals with one, which would imply 6 MHz of bandwidth, much more than I need. Now I have to dig a Pi out of a box or project... (to be continued)

    So, after thinking a bit (instead of playing with rpitx), I decided dropping the bandwidth to the minimum required is probably a good first step. The AM broadcast band in the US covers frequencies between 525 and 1705 kHz, for a bandwidth of 1180 kHz (the end channels are 10 kHz wide centered on 530 and 1700). The band is centered on 1115 kHz, so using a local oscillator of 1115 kHz with IQ samples of at least 1180 kHz will provide enough bandwidth.

    Since the speech synthesizer natively outputs audio samples at 16 kHz, a minimum upsampling factor of 73.75 is required before modulating the I and Q signals; in practice integer ratios are much easier, so 74 is probably the practical minimum, which yields a total bandwidth of 1.2 MHz. I'll have to test if rpitx can handle this (somehow, I'm skeptical).

    This approach differs from my initial idea of baseband sampling, where I'd need 1.705 MHz of bandwidth - but If I end up using the teensy with a mid-speed DAC, baseband is still the easiest approach.

View all 7 project logs

Enjoy this project?



cswiger wrote 05/09/2018 at 18:25 point

Going to play with this tonight - I did an antique radio show broadcaster using a usrp1 - encoding live from wav files got up to about 8 channels before the notebook was overwhelmed.  Looking forward to playing every episode of Jack Armstrong or Dragnet on a massive band wide emitter :) 

  Are you sure? yes | no

cswiger wrote 05/10/2018 at 01:21 point

here's an rf spectrum over a short coax to a usrp1     got it to work with a little amp and a good antenna to a GE SuperRadio :)

  Are you sure? yes | no

Ted Yapo wrote 04/20/2019 at 23:53 point

Nice! Somehow I didn't get a notification for this comment. Saw it almost a year late!

  Are you sure? yes | no

martin_ehrenfried wrote 05/04/2018 at 13:37 point

Hi Ted,

I really like this - it's a good basis for an 'all frequency' broadcast transmitter, which could have a lot of interesting applications for emergency or other wide area information services.

I wonder if you could do the same for the FM broadcast band with 50KHz channel spacings ?

  Are you sure? yes | no

Ted Yapo wrote 05/05/2018 at 23:15 point

You probably could.  While you were at it, you could put signals on the ham bands, too - in CW, SSB, AM, digital modes, etc.  You're generating a whole swath of spectrum, so why not? 

I mean as long as you keep the signal inside coax and don't let it radiate, that is :-)

  Are you sure? yes | no

danjovic wrote 05/02/2018 at 10:10 point

Hi Ted, what an awesome project!!!!!

  Are you sure? yes | no

Ted Yapo wrote 05/02/2018 at 11:17 point

Thanks!  I was really psyched to see the fl2k code/hack appear out of nowhere; the project had been stalled for a while as I looked at various ram & DAC schemes for making my own hardware.

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates