So I was flipping though the pages of the latest issue of QST magazine when I came upon the section "25, 50, and 75 years ago in QST" (Actually it's also 100 years ago, but a century ago this date we were in the middle of the first world war and amateur radio, along with the ARRL was shut down for the duration).
The cover of the magazine for April 1968 looked familiar. It was in fact the very first issue of QST that I had ever bought from a new stand, about two years before I finally got my Novice license. I think I had been visiting what remained of NY's Radio Row, probably Harrison Electronics, or maybe Barry Electronics when I bought that magazine, along with a copy of the 1967 edition of the Radio Amateur's Handbook (I probably bought the previous years version at half price after seeing that it wasn't that much different from the then current version).
Surprisingly, the technology represented in that 50 year old magazine didn't feel like it was a half century old. The lead article on the front page was a construction project for an electronic keyer using three integrated circuits, after all. They were however RTL logic chips, now obsolete and probably no longer obtainable, except perhaps on Ebay from Chinese vendors with a history of selling counterfeit parts.
The other major construction article was for a 40 meter QRP transceiver using a mix of silicon and germanium transistors.
Cute looking little rig isn't it? After looking at the parts list for this rig, and comparing it to the contents of my junque box, I'm giving serious thought of actually building a copy of this little rig. Of course I'll probably make a few parts substitutions along the way, mostly to replace the few germanium transistors with silicon ones.
Now this WAS the April issue, and QST has had this nasty habit of putting a few articles in such that were meant to be jokes. While I doubt that the description of this project was intended as an April Fool's laugh, there certainly were a number of mistakes in the parts list that would have tripped up a constructor. For example, 2N3905 NPN transistors were specified, and I know damn well that this part is a PNP transistor, a lower spec version of the 2N3906, which is also called out for in the article, correctly as a PNP unit. I think the author meant 2N3904, but the mistake was made across the entire article. A 2N1305 transistor is also called for, in two places as a PNP (correct), and in one place as an NPN (oops!). Now there IS an NPN version of this germanium device with the part number of 2N1306, perhaps that's what he meant. Finally, the 15uh inductance value for the VFO coil was wrong, although the number of turns on the coil was correct. A quick bit of math showed that coils needed to be about 5uh to cover the required frequency spread.
One of the PNP germanium units is the audio output stage (driving 2000 ohm magnetic headphones), the other is a keying switch for the transmitting mixer and pre-driver stages. The NPN germanium device is used in the VFO. I'll probably look at adjusting the bias resistors in the VFO and switch to a silicon device here, and I'll substitute a 2N2905 silicon transistor for the keyer switch (again adjusting the bias resistors). I'll leave the audio output stage as is, but maybe I'll add an output transformer to drive lower impedance headsets (like my David Clark aviation headphones).
Here is a image of the inside of this little rig. Note the use of compression mica trimmer capacitors, transistor sockets, and half watt resistors. The one "modern" construction feature is the use of toroid core inductors. (Actually toroids have been around a LONG time, and were 'rediscovered' in the middle 1960's).
The receiver has an rf amplifier stage using a complementary circuit with 2N3904 NPN and 2N3906 PNP transistors. The complementary circuit is similar to the cascode amplifier, however the devices are NOT connected in series for DC. Like the cascode circuit, the complementary amplifier has the first stage operating in the common emitter mode, and the second in grounded base mode. The IF amplifier uses the identical topology, and the gain of both stages are controlled by varying the bias on the second stage's base via a panel mounted control. There is no audio gain control in the receiver.
A single NPN transistor is used as the receiver's first mixer, and a simple two crystal IF filter is used.
Here you can see the rf stage, mixer, IF filter and half of the IF stage. A search in my junque box discovered a half dozen 5.068 mhz crystals, close enough to the 5.010 mhz called for by the author. They will work by changing the adjustment of the VFO slightly.
That's all for now, I'll try to post an update later in the month.
Monday, March 19, 2018
Saturday, February 10, 2018
Calculating DDS tuning words
DDS chips, such as the AD9850, AD9851, and AD9951-4, generate HF sine waves via direct digital to analog construction. They use a form of digital counter know as a digital accumulator which counts up a fixed amount on each pulse of a supplied reference clock. The output of this counter is used to address a lookup rom sine (or cosine) table. This value is fed to an DAC (Digital to Analog converter) to generate a sine wave at a rate determined by the increment value (tuning word) that has been programmed into the DDS chip. The output of the DAC must be filtered by a sharp cutoff low pass filter to remove aliasing products before the resulting signal can be used.
Different DDS devices have different widths to their tuning words and D/A converters. The above mentioned parts in the AD985x series have 10 bit DACs, while the AD995x series have 14 bit converters. Both series have 32 bit tuning words. The purity of the output is better with more precise DACs, and the resolution of tuning (frequency steps) is determined by the width of the tuning word. With a 32 bit tuning word these parts can tune in fractional hertz resolutions.
The tuning word is determined by multiplying the desired frequency by a tuning constant. This constant (for a 32 bit tuning word) is equal to (2^32) / (DDS clock frequency). So for a typical 180 MHZ clock rate for the popular AD9851 device, the tuning constant is 23.86092942. Unfortunately, this leaves us with the ugly problem of performing floating point math in our micro controller to determine the value of the tuning word in real time (for a VFO application, anyway).
We can however play a few tricks to allow us to make use of simpler integer math functions to replace the floating point ones. Since the tuning constant is going to be a small number for the typically used clock frequencies and word widths (in the range of 10-100 for clocks of 50 - 400 mhz and 32 bit tuning words), the whole part of the constant fits into less than 8 bits. We can multiply this floating point number by a suitable power of two and throw away any small fractional remainder, and then multiply our resulting larger constant by the desired frequency. We then right shift the result by the same power of two before applying the answer as our frequency word.
This will require 64 bit math of course, but building a 64 bit multiply routine on an 8 bit processor isn't quite as bad as programming a floating point multiply followed by an integer conversion. The generation of our constant can be done on a calculator, and we then enter this value directly into our program, where we let our compiler do the heavy lifting. The AVR GCC "C" library has the required 64 bit multiply code in it, so all we need do (in "C") is to define the necessary long long integer variables and simply perform the multiplication.
So how many bits do we left and right shift? It turns out that by multiplying the floating point constant with an integer part value of less than 2^8 in size to fit into a 32 bit word, we end up with a binary floating point format looking like this: IIIIIIII.FFFFFFFFFFFFFFFFFFFFFFFF , that is a 'real' or 'integer' portion of 8 bits and a fractional portion of 24 bits. IE: we will multiply the floating point constant by 2^24, or a left shift of 24 bits.
As an example, consider a tuning word of 32 bits and a clock frequency of 180MHZ. Our tuning constant is 23.86092942 as stated before. To build our binary floating point format constant follow the following formula: Konst = ((2^32) / RefClockFrequency) * (2^24).
So (4294967296 / 180000000) * (1677216) = 400319966.8 We throw away the 0.8 at the end, and enter the result into our code. In hex, our constant is 0x17dc65de.
Now for a few tricks in programming. We'll use a union to make handling things a bit easier.
First we call init_dds_calibration to initialize the tuning constant. In this code we either read the value from eeprom, or set the initial eeprom value. Later on we can fine tune the constant to a value suited for the ACTUAL value of our clock, since the frequency value stamped on our crystal isn't going to be exact. We will use Adjust_DDS_Konst() to perform this tweaking, allowing the user to zero beat the DDS against a frequency standard by turning the tuning knob.
CalculateDDS() is used to perform the math. Note that instead of right shifting by 24 bits at the end, we have left shifted by eight and return the UPPER part of the 64 bit result. It's a bit faster that way, and the use of the union gives us the flexibility to do this.
The functions for actually writing to the DDS chip, and accepting user input are not included here.
//Konst is a 32 bit number, but is stored in a 64 bit variable
//because it will be multiplied by a 64 bit number
union K
{
uint64_t K64;
uint32_t K32[2];
}K;
// perform a floating point mult of the frequency by the
// dds constant for 180mhz clock and a 32 bit accumulator.
// Integer math is performed and the fractional portion
// of the result is shifted away.
unsigned long CalculateDDS(unsigned long freq)
{
union dds{
uint64_t Product; //64 bits
uint32_t Part[2]; //32 bits * 2
uint16_t i[4]; //16 bits * 4
}dds;
//store the 32 bit frequency number as the low half of a 64 bit number
dds.Part[0] = freq;
dds.Part[1] = 0;
//multpliy 32 bit frequency by 32 bit constant to get 64 bit number
dds.Product *= K.K64;
//shift left to remove fractional part of result
dds.Product = dds.Product << 8; //shift left one byte
//result is the integer part of the result. We have
//performed a floating point multiply using integer math.
return dds.Part[1]; //return upper long int as result
}
Different DDS devices have different widths to their tuning words and D/A converters. The above mentioned parts in the AD985x series have 10 bit DACs, while the AD995x series have 14 bit converters. Both series have 32 bit tuning words. The purity of the output is better with more precise DACs, and the resolution of tuning (frequency steps) is determined by the width of the tuning word. With a 32 bit tuning word these parts can tune in fractional hertz resolutions.
The tuning word is determined by multiplying the desired frequency by a tuning constant. This constant (for a 32 bit tuning word) is equal to (2^32) / (DDS clock frequency). So for a typical 180 MHZ clock rate for the popular AD9851 device, the tuning constant is 23.86092942. Unfortunately, this leaves us with the ugly problem of performing floating point math in our micro controller to determine the value of the tuning word in real time (for a VFO application, anyway).
We can however play a few tricks to allow us to make use of simpler integer math functions to replace the floating point ones. Since the tuning constant is going to be a small number for the typically used clock frequencies and word widths (in the range of 10-100 for clocks of 50 - 400 mhz and 32 bit tuning words), the whole part of the constant fits into less than 8 bits. We can multiply this floating point number by a suitable power of two and throw away any small fractional remainder, and then multiply our resulting larger constant by the desired frequency. We then right shift the result by the same power of two before applying the answer as our frequency word.
This will require 64 bit math of course, but building a 64 bit multiply routine on an 8 bit processor isn't quite as bad as programming a floating point multiply followed by an integer conversion. The generation of our constant can be done on a calculator, and we then enter this value directly into our program, where we let our compiler do the heavy lifting. The AVR GCC "C" library has the required 64 bit multiply code in it, so all we need do (in "C") is to define the necessary long long integer variables and simply perform the multiplication.
So how many bits do we left and right shift? It turns out that by multiplying the floating point constant with an integer part value of less than 2^8 in size to fit into a 32 bit word, we end up with a binary floating point format looking like this: IIIIIIII.FFFFFFFFFFFFFFFFFFFFFFFF , that is a 'real' or 'integer' portion of 8 bits and a fractional portion of 24 bits. IE: we will multiply the floating point constant by 2^24, or a left shift of 24 bits.
As an example, consider a tuning word of 32 bits and a clock frequency of 180MHZ. Our tuning constant is 23.86092942 as stated before. To build our binary floating point format constant follow the following formula: Konst = ((2^32) / RefClockFrequency) * (2^24).
So (4294967296 / 180000000) * (1677216) = 400319966.8 We throw away the 0.8 at the end, and enter the result into our code. In hex, our constant is 0x17dc65de.
Now for a few tricks in programming. We'll use a union to make handling things a bit easier.
First we call init_dds_calibration to initialize the tuning constant. In this code we either read the value from eeprom, or set the initial eeprom value. Later on we can fine tune the constant to a value suited for the ACTUAL value of our clock, since the frequency value stamped on our crystal isn't going to be exact. We will use Adjust_DDS_Konst() to perform this tweaking, allowing the user to zero beat the DDS against a frequency standard by turning the tuning knob.
CalculateDDS() is used to perform the math. Note that instead of right shifting by 24 bits at the end, we have left shifted by eight and return the UPPER part of the 64 bit result. It's a bit faster that way, and the use of the union gives us the flexibility to do this.
The functions for actually writing to the DDS chip, and accepting user input are not included here.
//Konst is a 32 bit number, but is stored in a 64 bit variable
//because it will be multiplied by a 64 bit number
union K
{
uint64_t K64;
uint32_t K32[2];
}K;
//
// read the stored calibration konstant from eeprom. If the
// eeprom has not been programmed with a stored konstant, use
// the default value based on an EXACT clock frequency of 180.0000000 mhz.
void init_dds_calibration(void)
{
K.K32[1]= 0; //zero upper part
K.K32[0]= (uint32_t)eeprom_read_dword(EE_DDS_KONST);
//see if we have a valid konstant in the eeprom and use it if so
if(K.K32[0] == 0xffffffff){
//eeprom is unprogrammed at this location, so use the default
K.K32[0] = Konst;
}
}
// adjust the dds constant by a small amount.
unsigned long Adjust_DDS_Konst(int adj)
{
if(adj > 0)
K.K64 += (unsigned long long)abs(adj);
else if(adj < 0)
K.K64 -= (unsigned long long)abs(adj);
return K.K32[0];
}
// dds constant for 180mhz clock and a 32 bit accumulator.
// Integer math is performed and the fractional portion
// of the result is shifted away.
unsigned long CalculateDDS(unsigned long freq)
{
union dds{
uint64_t Product; //64 bits
uint32_t Part[2]; //32 bits * 2
uint16_t i[4]; //16 bits * 4
}dds;
//store the 32 bit frequency number as the low half of a 64 bit number
dds.Part[0] = freq;
dds.Part[1] = 0;
//multpliy 32 bit frequency by 32 bit constant to get 64 bit number
dds.Product *= K.K64;
//shift left to remove fractional part of result
dds.Product = dds.Product << 8; //shift left one byte
//result is the integer part of the result. We have
//performed a floating point multiply using integer math.
return dds.Part[1]; //return upper long int as result
}
Monday, January 22, 2018
Hello!
I'm a Brooklyn born ham transplanted to South Florida via the Boston area. I'm a frustrated home brewer who loves playing with Arduino microcontrolers, as well as radio stuff. Watch this space for eventual posts on technical stuff.
Subscribe to:
Posts (Atom)


