05/13/2018 at 02:12 •
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.
05/12/2018 at 22:08 •
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:
05/12/2018 at 15:43 •
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.
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.
You also need these parts:
- (1x) Amphenol VGA Connector, MFG# 10090926-P154XL, DigiKey# 609-4022-ND.
- (3x) Yageo 0805 86.6 ohm resistor, MFG# RC0805FR-0786R6L, DigiKey# 311-86.6CRCT-ND
- (3x) Yageo 0805 43.2 ohm resistor, MFG# RC0805FR-0743R2L, DigiKey# 311-43.2CRCT-ND
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.
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 extends to around 1.8MHz, then drops off quickly. See below for some test data.
On the PCB, I added pi networks on the front and back of the filter for attenuation or matching purposes, but I just populated the "through" element with a zero-ohm resistor for testing.
This schematic shows 1.5nF caps because I initially couldn't locate decent 1.6nF ones. 1.6nF is the correct value.
I used through-hole inductors because they were the best ones I could find in terms of SRF and Q. The three of them are mutually perpendicular to reduce magnetic coupling, since they are unshielded.
Here are the parts used:
- (1x) Amphenol VGA Connector, MFG# 10090926-P154XL, DigiKey# 609-4022-ND.
- (3x) Bourns 10uH inductor, MFG# 77F100K-TR-RC, DigiKey# 77F100K-TR-RCCT-ND.
- (2x) Murata 1600pF 0805 Capacitor, MFG# GRM2165C2A162JA01D, DigiKey# 490-12745-1-ND
- (2x) Murata 2700pF 0805 Capacitor, MFG# GRM2165C1H272JA01D, DigiKey# 490-1630-1-ND
- (1x) SMA connector. I got them cheap on ebay. They are 9mm wide.
I also used (2x) 0805 zero-ohm jumpers to bypass the pi networks on the filter. You can substitute a piece of wire or maybe a solder blob.
Here is the output of the voice frequency marker using the LPF:
The span extends to 20MHz, and you can see there's really no output above the approximately 1.7 MHz edge of the AM band. Contrast that with the output through the breakout board:
You can see the harmonics mirrored around 8.192 and 16.384 MHz, shaped by a sinc-function due to the zero-order-hold output of the DAC, as explained below. These harmonics are why you need the LPF.
Quick Noise Source
I wanted to test the response of the filter, but I didn't have the right cables and connectors to hook the output from the tracking generator on my spectrum analyzer to the VGA connector. So, I improvised a noise source using the FL2K dongle itself to test the LPF. I grabbed 4 seconds worth of random bytes at 8192k samples/second from /dev/urandom into a file using dd:
dd bs=8192 count=4000 if=/dev/urandom of=noise.dat
these samples can be played through the dongle with fl2k_file:
fl2k_file -s 8192000 noise.dat
The result, shown here played through the breakout, is white noise shaped by a sinc filter:
The zero is at the sampling frequency, 8192 kHz in this case. The reason this has a sin(x)/x envelope instead of being flat is the fact that the DAC doesn't have a reconstruction filter: the output consists of a series of discrete steps. In signal-processing parlance, this is known as a zero-order-hold, and you can think of it as a convolution of the input samples with a rectangular pulse as wide as the sample time. Since the Fourier transform of a rectangular pulse is a sinc function, this has the effect of applying a sinc-shaped filter in the frequency domain. For more information, you can check out this Maxim application note. I think I will experiment with some of the techniques in the app note for producing a flatter spectrum - a cheap wideband RF white noise source can be very handy.
Anyway, back to why I did this. Passing the same signal through the lowpass filter reveals an indication of the frequency response:
Since the noise input near the passband is close to flat, we can tell that the LPF response of pretty flat out to around 1.8MHz, then drops off at least 50dB. The stopband response shown here is at the noise floor of the analyzer, so it's at least this good, and maybe better.
Re-running the same experiment with a 100 MHz sampling frequency produces the same shape noise with a zero at 100 MHz:
With this signal applied to the LPF, there are no surprises in the stopband:
You can just make out the passband at the beginning of the span. Again, the stopband measurement is limited by the analyzer noise floor here.
I'm going to call the LPF a success.
05/01/2018 at 16:37 •
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.
05/01/2018 at 14:45 •
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.
04/30/2018 at 17:12 •
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 osmocom.org 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: make_fl2k_file.py [-h] [-b BEGIN_FREQ] [-e END_FREQ] [-s FREQ_STEP] [-o ORDER] [-a SCALE] [-c CARRIER_SAMPLE] [-r OUTPUT_SAMPLE] output 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, --order ORDER sigma-delta modulator order (default = 0) -a SCALE, --amplitude-scale SCALE scaling factor for amplitude (default = 1.47) -c CARRIER_SAMPLE, --carrier-sample-rate CARRIER_SAMPLE sample rate for carriers (default = 4096000.) -r OUTPUT_SAMPLE, --output-sample-rate OUTPUT_SAMPLE output sample rate (default = 65536000.)
For example, to generate the EU data file mentioned above, you can run it like this:
./make_fl2k_file.py eu_order0_8192k.dat -b 531 -e 1602 -s 9 -o 0 -c 4096000 -r 8192000
The sample files were generated with a 8192kHz sample rate. The speech synthesizer uses a fixed 16kHz sample rate, and the python code runs much faster if all the sampling frequencies are multiples of that. You theoretically could sample as slowly as 3410 kHz to generate the US AM band (1705 kHz maximum frequency), but the FL2000 seems to have a lower limit of 7777 kHz sampling, so I used 8192.
To increase the SNR, you can do two things. First, you can increase the sample rate. This spreads the quantization noise over a larger bandwidth, so the noise in the desired output bandwidth is smaller. I've gone up to 65536kHz (my dongle seems to be limited to around 84MHz, being driven through a USB 3.0 hub).
The program also includes an experimental digital-to-digital sigma-delta converter which can shape the quantization noise, pushing it into higher frequencies and away from the AM band. Use the -o option to select first order (-o 1) or second order (-o 2) modulators. Higher order is also supported, but is *very* experimental at this point.
I have yet to examine the effect of the sigma-delta modulation on the spectrum analyzer.
How it Works
This is really unbelievably simple to do. The heart of the code is two lines:
# AM modulate onto carrier and add to spectrum wc = 2. * np.pi * freq * 1.e3 R += scale * (0.5 + 0.5 * a_s) * np.cos(wc * t)
Each synthesized voice is AM modulated on the corresponding carrier and the results are summed.
This project is close to complete. The thing works.
Next, I'd like to move away from requiring a PC. The dongle will run off USB 2.0 at reduced sampling rates, so I should be able to get it to run from a Raspberry Pi.
I also want to expand the script to accept a list of frequencies and corresponding audio files to generate a spectrum of programs for antique radios. Many classic programs are available on-line, and it would be awesome to be able to tune around the dial on an antique radio and listen to programs from the same era.
04/12/2017 at 22:46 •
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.