AVR Software Defined Radio (1) - Generating precision signals using ATtiny micro
Atmel AVR microcontrollers are very popular, not least because of the free development tools that are available. In this series we shall show how these processors can be pressed into service for digital signal processing tasks.
Atmel AVR microcontrollers are very popular, not least because of the free development tools that are available. In this series we shall show how these processors can be pressed into service for digital signal processing tasks. We shall cover the subject from the ground up, making the series suitable for beginners, and in true Elektor style the focus will be on practical experiments. You can build the hardware yourself or you can obtain boards from Elektor, and the software is as ever available for download as source code from our website. Let’s generate some signals!
Published in Elektor Magazine, no. 423, March 2012
Order from Elektor
SMD-populated board (USB AVR Programmer), 080083-71
Signal Generator kit; PCB and all components, 100180-71
Kit of parts Universal Receiver: PCB and all components, 100181-71
Kit of parts Active Antenna: PCB and all components, 100182-71
Signal Generator + Universal Receiver + Active Antenna: PCBs and all components + USB-FT232R breakout-board, 100182-72
PCB, assembled and tested, 110553-91
By Martin Ossmann
First a quick peek at what is in store in this series. The first board, which includes an ATtiny2313, a 20 MHz oscillator and an R-2R DAC, will be used to make a signal generator. The second board will fish signals out of the ether. It contains all the hardware needed to make a digital software-defined radio (SDR), with an RS-232 interface, an LCD panel, and a 20 MHz VCXO (voltage-controlled crystal oscillator), which can be locked to a reference signal. The third board provides an active ferrite antenna. The software for all these projects is written using the WinAVR GCC compiler in AVR Studio and can be downloaded as C source code (plus fuse settings) or as hex files.
The series is built around practical experiments. We can look forward to sine- and squarewave generators, an RMS voltmeter, experiments in FM, AM and PM, FIR and IIR filters, wireless data transmission, reception of the DCF timecode signal, RTTY weather messages, BBC long-wave radio transmissions and much more!
Before we get started, one word of warning: fluorescent energy-saving light bulbs are based on switching regulators which splatter interference all over the long-wave band. So we advise carrying out the more sensitive experiments with the fluorescents off and the mains halogen lights on (or by candlelight!).
Signal generator board
The signal generator board is based on an AVR microcontroller clocked at 20 MHz and an R-2R ladder forming a digital to analogue converter to produce the output voltages. This is hardly a novel circuit, but we will show how it can be used in a wide range of applications. In particular we will use it to generate outputs useful for testing other circuits, such as frequency- and phase-modulated signals. Then, for even greater precision, we will connect the signal generator to an external clock source which is in turn locked to a frequency standard such as the German DCF77 signal on 77.5 kHz or French TDF signal on 162 kHz.
The circuit of the signal generator is shown in Figure 1. The central component is the ATtiny2313 microcontroller, with the R-2R ladder connected to port B forming the digital-to-analogue converter. The analogue output signal appears on K3 (SINE). Note, however, that the output impedance of the circuit is relatively high at 10 k?. PWM output OC1A of the microcontroller is also available at K4 (SQUARE). We will use this output to generate square waves with frequencies of up to a few hundred kilohertz, as well as to modulate other signals. Another PWM output, OC0B, is brought out to K5 (PWM-LF) via a low pass filter comprising R19 and C3: this is suitable for generating low-frequency analogue signals.
The processor is clocked at 20 MHz by oscillator X1. It is a good idea to choose a relatively high-precision component here (50 ppm or better). Using a socket makes it easier to try out different types of oscillator or oscillators of different frequencies. Jumper JP2 allows the use of an external clock signal, which should be supplied at K2 (EXT-CLK).
The signal generator software programs allow a certain amount of external configuration using the microcontroller’s UART. The relevant pins are brought out to a connector on the board (which is available from Elektor in the form of a kit including all the components). The connector is suitable for directly attaching the BOB-FT232R USB-to-serial converter. JP1 allows power to be obtained over the USB connection when the unit is used with a PC: in this case no additional mains adaptor is needed.
Populating the printed circuit board (Figure 2) should present no particular difficulties: all the components are ordinary leaded types. It is worth using a socket for the processor in addition to the clock oscillator. Be sure to observe correct polarity on the programming connectors K6 and K7. Programming can be done using the Elektor AVRprog [2]. It is of course important to get the fuse configuration right: the source code gives this along with the compiler options in each case.
DDS sinewave generator
Our first application is a simple sinewave generator programmed in C. The basic sample clock is produced by one of the timers built in to the microcontroller, arranged to trigger an interrupt. The interrupt routine is responsible for calculating the next sample value of the sinewave (Figure 3). Call the kth sample S[k]. Writing p[k] for the phase of this sample, we have
S[k] = sin (p[k]).
Between one sample and the next the phase advances by a constant value d (the ‘phase increment’):
p[k+1] = p[k] + d.
In a perfect sinewave generator these calculations must be carried out exactly, which of course is not practical. Instead, the direct digital synthesiser (DDS) stores the current phase value DDSp to finite precision as an m-bit number in the so-called ‘phase accumulator’. One complete period of the sinewave corresponds to this value covering the range of values from 0 to 2m–1. The same precision is used for storage of the phase increment and for the phase addition operation.
The next step is to convert the phase value into the corresponding sinewave sample. This is done using a look-up table which stores a complete sinewave period. If we were to store a sample for each of the 2m possible values in the phase accumulator the table would be unmanageably big: instead we use just the top n (where n<m) bits of the phase accumulator to index the table, which now need only contain 2n samples. The values stored in the table are rounded from their exact values to r-bit samples S[k], where r is the number of bits in the digital-to-analogue converter. Figure 4 illustrates the process.
In our first program we use m=32 and n=8. A 32-bit phase accumulator gives us very precise frequency control over our signals. We use a sine table with 256=28 entries and an 8-bit DAC (r=8). The program EXP-SinusGeneratorDDS-T1INT-V01.c is configured to produce a fixed frequency output at 1 kHz; the result can be verified on an oscilloscope (Figure 5). The interrupt service routine code is as follows.
ISR(TIMER1_OVF_vect) {
PORTD |= _BV(4); // set sample timing flag
PORTB = pgm_read_byte(SIN8+(DDSp>>24)); // fetch and output sine sample
DDSp += DDSd; // advance DDS phase DDSp by DDSd
PORTD &= ~_BV(4); // clear sample timing flag
}
Timing
The DDS is clocked at fDDSCLK =100 kHz. To generate a desired output frequency f the required phase increment is calculated using
DDSd=2n×f/fDDSCLK
and so for f =1 kHz we have
DDSd=232×1 kHz/100 kHz=42949673
and you will find precisely this value in the C source code where DDSd is initialised.
We can see from this formula that the higher the sample rate the higher the frequencies we can generate. Why did we choose 100 kHz? The answer lies in the timing of the interrupt service routine. As you can see from the code snippet above, we have bracketed the calculation with commands to set and clear port pin PD.4. This allows us to observe the execution time of the calculation using an oscilloscope: in this case we see a total time of around 2.2 µs. However, we must be careful as this does not include other contributions to the total time needed to service the interrupt: for example, the time to save and restore processor registers will not be counted. However, with a relatively simple experiment we can determine these times as well.
Simply set up the main program as an infinite loop in which a port pin (say PD.5) is toggled as quickly as possible. We can then observe on the oscilloscope the periods when the toggling stops, which is when the interrupt service routine is active: see Figure 6.
In our experiment we measured the total time needed to process an interrupt at about 5.4 µs. The maximum allowable interrupt rate is therefore 180 kHz. Adding a safety margin, we arrive at our figure of 100 kHz.
As the output frequency f approaches fDDSCLKwe start to observe undesirable artefacts such as jitter, noise and aliases in the output spectrum. With a sample rate of 100 kHz it is best to keep f below about 10 kHz. Perhaps we can do a bit better if we use assembly code?
A faster DDS sinewave generator
In order to make our sinewave generator capable of higher frequencies we need to rewrite the DDS routine in assembler. With the help of a cunning arrangement of variables in registers we can manage to get the sample rate of the 32-bit DDS as high as 2 MHz. The code uses the T flag to allow it to break out of its loop.
loop:
add DDSphase0,DDSdelta0 // 1, LSB of 32 bit DDS adder
adc DDSphase1,DDSdelta1 // 1
adc DDSphase2,DDSdelta2 // 1
adc ZL ,DDSdelta3 // 1, MSB is in ZL as pointer
lpm R0,Z // 3, access sine table
out PORTB,R0 // 1, out to R-2R DAC on PORTB
brtcloop // 2 (1) loop until T flag set by
interrupt routine
//10 cycles in total for one loop
Our project now consists of a mixture of C and assembler code, and we need to store the sinewave table at a fixed address in memory. Configuring the project within WinAVR to achieve this is not a task for the beginner. If you do not plan to make any changes to the code it is probably best to program the ready-compiled hex file into the processor (paying attention to the fuse bit settings). The project is called EXP-SinusGenerator-DDS-ASM-C-V01.
To make the sinewave generator more flexible it includes the ability to be configured over the UART interface (19200 baud, 8N1 data format). Using a terminal program, simply type in the desired frequency followed by CR and LF. The maximum usable signal frequency is about 200 kHz. The theoretical frequency resolution is given by
fDDSCLK / 2n = 2 MHz / 232 = 0.00046... Hz.
To take advantage of this resolution the software allows you to enter a frequency with up to three digits after the decimal point, for example as ‘1000.045’ (followed byCR and LF). The internal calculations required to turn the entered frequency into a suitable parameter value for the DDS need to be carried out very accurately. To this end the author has written special-purpose arithmetic routines, including one for fixed-point division.
Figure 7 shows the spectrum of the sinewave output signal at frequency f =125.123 kHz over the range from 0 Hzto 2 MHz. As you can see, there are harmonics present, but all at more than 30 dB below the desired signal. A low level of wideband noise is also visible: this is a by-product of the DDS technique.
If an ordinary crystal oscillator is used the overall accuracy of the system will be in the region of plus or minus 100 ppm. In that context it hardly makes sense to claim that the generator can produce an output frequency of say 100.00005 kHz. For this reason the generator can accept an external 20 MHz clock signal, and in a later instalment of this series we will see how such a clock can be derived from a transmitted reference. This combination allows the generation of sinewaves with excellent frequency accuracy.
Trimming resonant circuits
Later in this series we will use an AVR microcontroller to receive and process longwave transmissions such as the DCF time code on 77.5 kHz, France Inter on 162 kHz and BBC Radio 4 on 198 kHz. Usually a ferrite antenna will be used, and adjusting such an antenna can be made much simpler using our sinewave generator: connect the circuit up as shown in Figure 8 and adjust the trimmer capacitor for maximum amplitude.
It is possible to use the phase relationship between the output voltage UOUT and the input voltage UIN to determine whether the resonant frequency of the circuit is higher or lower than that of the input sinewave. If the phase of UOUT leads UIN then the sinewave frequency is lower than the resonant frequency; if UOUT lags UIN the signal frequency is higher than the resonant frequency. When the frequencies agree UIN and UOUT are in phase.
The example circuit shows component values for a resonant frequency of 125 kHz; coil L1 is a small pot core inductor. We will use this circuit later to generate test signals at a frequency of 125 kHz. The trimmer capacitor can be adjusted to bring the resonant frequency of the circuit to exactly 125 kHz: it is possible to use either the signal from the R-2R ladder (K3) or the squarewave from the PWM output (K4) to do this.
PWM squarewave with a fractional divider
We will now look at another application of the DDS principle. If we use a timer with a PWM output to generate a squarewave, we are normally limited to producing frequencies that exactly divide the frequency at which the timer is clocked. If N is the division ratio and fCLK the timer’s clock frequency then the output frequency will be f = fCLK/N. However, if we adjust the division ratio on the fly (say between N and N+1) then we can also produce intermediate frequencies. So for example if we alternate between using a divisor of N and a divisor of N+1 then the overall effective division ratio will be N+0.5. If a division ratio of 10.333... is wanted, then this can be achieved by using a division ratio of 11 with ‘probability’ 0.333... and a division ratio of 10 the rest of the time.
How can we use this in practice? We need an algorithm that will tell us, given the desired division ratio, when to divide by N and when by N+1.
Again, the m-bit DDS synthesiser comes to the rescue, with a sufficiently large value of m to achieve the desired precision. In this case we make use of the overflow of the phase accumulator. The rate p at which an m-bit phase accumulator overflows is just
p = DDSd / 2m
and so we can control this rate as precisely as we need using the variable DDSd. Whenever the accumulator overflows the timer is instructed to divide by N+1 rather than N on the next cycle.
So, for example, if we wish to generate a 77.5 kHz squarewave from a 20 MHz master clock, the required overall division ratio is
20000 / 77.5 = 258.0645161...
and so we need to divide by either N=258 or N=259 on each cycle. The ‘probability’ of selecting N=259 will be p = 0.0645161..., which with a 24-bit DDS means that DDSd = p × 224 = 1082401.The following listing shows an interrupt service routine embodying this idea.
uint32_tDDS24; //DDS phase,24 bits used
volatile uint32_tdDDS24; //delta for DDS
uint16_tTOP1; //integer part of divider for PWM
ISR(TIMER1_OVF_vect) {
PORTD |= _BV(4);
DDS24 += dDDS24; // advance DDS phase
if (DDS24&0x1000000UL){ // check bit 24 for overflow
ICR1=TOP1; // on overflow PWM width = TOP1+1
}
else {
ICR1=TOP1-1; // else PWM width = TOP1
}
DDS24 &= 0xffffffUL; //make DDS24 24 bits again
PORTD &= ~_BV(4);
}
The squarewave produced by this code does of course suffer from a small amount of short-term jitter, but in the long term the agreement with the ideal frequency is excellent.
The whole routine, including interrupt overheads, has an execution time of about6 µs, which means that we can use the technique to generate frequencies of up to about 160 kHz. Rewriting the routine in assembler should allow considerably higher frequencies.
For convenience the squarewave generator program can also be controlled using a terminal. The source code is calledEXP-SquareGenerator-DDS-T1INT-V01.cin the downloadable zip archive [3].
The fractional divider has many other applications. For example, it can be used to generate a signal with any desired sample rate, or form part of a digital PLL.
FM generator
On its own the squarewave generator is perhaps not particularly exciting. However, since controlling the PWM output does not occupy all the processor’s time, we have some computing power left over to change the frequency dynamically to make an FM generator.
The German meteorological service [4] transmits weather information on 147.3 kHz using frequency shift keying (FSK) in radioteletype (RTTY) format. Later in this series we will build a receiver which can decode these messages. To adjust and test the receiver it is helpful to have a test signal. Using a fractional divider and the PWM output this is easy: we just use a stream of data bits to control the output frequency.
We will first program our test signal generator to work with a carrier frequency of f = 125 kHz. A circuit along the lines of Figure 8 is used to turn the squarewave output into a sinewave. We have already seen the interrupt service routine for the fractional divider; the new routine, ‘SendBit’, is responsible for sending a single bit.
void SendBit(uint8_t theBit) {
uint8_tk;
for (k=0; k
if (theBit==MARK) {
cli();
deltaDDS24 = MARK_deltaDDS24;
TOP1 = MARK_TOP1;
sei();
}
else {
cli();
deltaDDS24 = SPACE_deltaDDS24;
TOP1 = SPACE_TOP1;
sei();
}
}
First we wait for Timer 0 to run through COUNT2cycles: in other words, the bit rate is the Timer 0 overflow rate divided by COUNT2. Then, depending on the value of the bit to be sent, the values of deltaDDS24 and TOP1 are set. This is where the frequency modulation occurs. Note that the commands that set these values are enclosed between a cli() and sli() pair. If this is not done it is possible that the interrupt service routine could be invoked when one parameter has been changed but not the other, with potentially unpredictable results. The routines can be found in the program EXP-SQTX-FM-RTTY-V01.c. With further auxiliary routines we can transmit characters using the Baudot [5] code, emulating the meteorological service transmissions.
Figure 9 shows the spectrum of the FM RTTY signal. There are two narrow peaks adjacent to one another, at frequencies of 125 kHz ± 50 Hz. The spectrum is continuous, falling off rapidly beyond ± 1 kHz.
Having built such a transmitter, it is natural to want to test it to check that the modulation is correct. For that reason the next instalment in this series will start to look at the components that comprise a digital receiver.
Published in Elektor Magazine, no. 423, March 2012
Order from Elektor
SMD-populated board (USB AVR Programmer), 080083-71
Signal Generator kit; PCB and all components, 100180-71
Kit of parts Universal Receiver: PCB and all components, 100181-71
Kit of parts Active Antenna: PCB and all components, 100182-71
Signal Generator + Universal Receiver + Active Antenna: PCBs and all components + USB-FT232R breakout-board, 100182-72
PCB, assembled and tested, 110553-91
Discussion (1 commentaire(s))