|
Home Updates Prior Products - no longer available Documents Book Modulation Loads Lattice Crystal Filter IMD Measurements Using LP100 Coupler Prototyping Software Updates K2 Measurements Oscillator Noise Measurements Bypassing Capacitor Voltage Change K2 Freq Stability Cohn Crystal Filter Receiver AGC Curves K2 RX Sensitivity Canned Osc Phase Noise K2 Interface K2 Filter Surface Mount Assembly TL750L Low Dropout Regulator Swordfish DDS Swordfish GLCD Module Bessel Nulls AM Modulation Z10000 with FT-920 Z100 Tuning Aid Dayton 2007 Softrock Lite 6.2 Header Adapter Carbon Composition Resistors Thermometers Hakko FT-800 Thermal Wire Stripper Heat Sinks Diode Turn-On Time Bill Hewlett and his Magic Lamp Tektronix P6022 Current Probe 1N400x Diode Family Forward Voltage Temperature Chamber Diode Vf vs If Ferrite Transformers 6 dB Hybrid Combiner Type 43 Ferrite B-H Curve K3 IF Bandpass Filter Estimating Q of Ferrite Cores Z10000 Buffer Amp Z10010 Bandpass Filter Using Softrock as a Panadapter for the K2 Signal Generator Phase Noise & Elecraft K2 Audio Transformer Data and Modeling Measuring 60 Hz Frequency Compact Fluorescent Lamp Z10000-U Buffer Amp and FT-1000MP WJ-8617B Receiver Impressions Weather in Clifton VA Radio Intelligence Example Diodes for RF Probes PIC Development Boards and Programmers Elecraft K3 and Panadapters Elecraft K3 AGC and S-Meter Elecraft K3 Noise Blanker and Crystal/DSP Filtering Jackson Harbor Press VLF Converter Elecraft K3 Receive Audio Headphone Impedance
| |
|
|
|
What is DDS?
DDS is an acronym standing for "direct digital synthesis."
It's sometimes called a "numerically controller oscillator," but DDS seems to be
the more popular term.
I devoted most of a chapter in my book
to using a 16F877 PIC to build an audio DDS generator, and I don't propose to
duplicate that effort here. In brief, a DDS generates its output by computing
the value of the desired sine wave (or other waveform, as DDS is not limited to
sinusoidal waveforms) at specific time intervals and then sending the value to a
digital-to-analog converter, where the numerical data is converted to a
corresponding voltage level. |
|
The upper trace in the oscilloscope capture shows a
typical DDS output. Note that the desired sine wave is approximated by discrete
voltage steps. The PIC calculates the appropriate voltage level as a
byte-range number (0...255) and sends it to the DAC where it is converted to a
voltage. In this case, the output is a 730 Hz sine wave. |
 |
|
The lower trace is the DAC's output after it is run through a
low pass filter, which cleans up most of the digital step artifacts seen in the
raw DAC's output. |
|
To show you that the DDS is not restricted to sine
wave outputs, the lower trace in the following oscilloscope capture is a
DDS-generated sawtooth or ramp waveform. The upper trace is a synchronization
waveform.
|
 |
|
That's enough for tonight. You can think about what uses you
might make of a PIC-based audio DDS . |
|
DAC - Digital to Analog Converter
Let's look at how a DAC works. We'll start with a simple
4-bit R-RL ladder type DAC. The two oscilloscope plots above were taken using
this circuit, breadboarded up with five discrete resistors. |
|
The four switches, DB0...DB3 switch between ground and +5V.
Of course, our circuit will not use real SPDT switches, but rather the PIC's
output pins will switch between logic high (+5V, more or less) and logic low
(0.2V or so).
|
 |
|
How does this simple circuit work? The easiest way to
understand how it works is to regard the four resistors as current sources. Into
a low impedance load (such as Rload, the 47 ohm resistor), when DB3 is switched
to the +5V source, it will force 5 mA (5V/1000 ohms) through Rload. In this
case, the output voltage is 235 mV, calculated as 0.005 A * 47 ohms = 0.235V
Likewise, when DB2 is switched to +5V, Rload has 2.5 mA
through it, DB1 controls a 1.25 mA source and DB0 controls a 0.63 mA source.
With the switches set as shown in the diagram, the total
current through Rload is 2.5 mA (from DB2, through R2) and 1.25 mA (from DB1,
through R3), totaling 3.75 mA, for an output voltage of 176 mV.
What about the shunting effect of R1 and R4, you may be
thinking. These clearly drain current away from Rload. And, how can you just sum
the currents?
The answer is yes, these are all real effects. However,
we've chosen R1...R4 and Rload carefully. A 1K resistor shunting 47 ohms is
negligible, at least at the resolution we expect from a crude 4-bit DAC.
Likewise, a couple hundred millivolts across Rload won't make our assumption
that R1...R4 are current sources that bad. Hence, we can simply sum the currents
and be "close enough" for our purposes.
Don't believe me? Dig out your calculator and work through
Ohm's law and send me an E-mail with the exact answer. It'll be within 10% of
our simplistic analysis.
Oh yes, why is this called an R-2R ladder? Looking at the
resistor values and diagram should answer this question for you. (I used real
values at 3.9K and 8.2K; in theory these should be 4.0K and 8.0K).
And, why make the ratios 2:1? It has something to do with
the binary system doesn't it? Yes, indeed it does. If we look at a 4-bit binary
number (usually called a nibble) defined by DB3...DB0, we see that the DC output
of our R-2R ladder DAC automatically tracks the binary value of the nibble. The
binary value to voltage multiplier is the current generated by the least
significant bit (R4, or 0.625 mA) times Rload, or 29.4 mV.
Let's see if this works. The nibble data value represented
by these switches is 0110 binary, or in decimal 6. Hence, the output voltage
should be 6 * 29.4 mV = 176 mV, exactly what we calculated before.
Nice when it all fits together, isn't it? |
|
|
|
Simple Swordfish DDS Code--Program DDS-1
OK, time for some code. I'll first list the complete
program and then explain what is behind it. The program assumes we have two of
the simple 4-bit resistive DAC circuits connected to the PIC's Port B.
The following schematic fragment shows the resistive DAC
connection. |
 |
|
|
|
Our first code generates two 4-bit sine waves, one on
Chan_A_Out and one on Chan_B_Out, at different frequencies. |
|
|
|
{
****************************************************************
* Name :
Toner.BAS *
* Author :
Jack R. Smith *
* Notice :
Copyright (c) 2006 Clifton Laboratories *
* : All
Rights Reserved *
* Date :
7/22/2006 *
* Version :
1.0 *
* Notes : *
* : *
****************************************************************
}
'Program to
output two simultaneous DDS-generated sine waves
'into 4-bit
DACs on Port B. Channel A is lower nibble
'Channel B
is upper nibble.
Device
= 18F4620
Clock
= 40
'remember to enable HSPLL in configuration
Config
OSC =
HSPLL,
//turn 4x PLL ON
LVP = OFF,
DEBUG = OFF,
PWRT = ON,
BOREN = ON,
WDT = OFF,
PBADEN =
OFF,
'for 18F4620 - makes port B digital
XINST=OFF
Const
SinTable(16) As Byte = (8,10,13,14,15,14,13,10,8,5,2,1,0,1,2,5)
Dim
aStep,
bStep As Word,
PhaseAccumA
As Word,
'use these as a 4.12 (15 bit total)
PhaseAccumB As Word,
'phase accumulators
Temp As Byte
TRISB = $00
'make
output
'adjust
these values when loop code is finished as the
'output
frequency depends on Repeat / Until loop timing
aStep = 935
'generates 2.56 KHz with current timing
bStep = 267
'generates 730 Hz with current timing
PhaseAccumA
= 0
PhaseAccumB
= 0
Repeat
High(PORTC.0)
PhaseAccumA = PhaseAccumA + aStep
PhaseAccumB = PhaseAccumB + bStep
Temp = SinTable(PhaseAccumA >> 12)
Temp = Temp + 16 * (SinTable(PhaseAccumB >> 12))
PORTB = Temp
Low(PORTC.0)
Until False
End
|
|
Tomorrow, we'll start analyzing the code.
[Note added 06 October 2006. I fixed an error in the main
code body, replacing erroneous references to variables i and j with aStep and
bStep respectively. |
|
Program DDS-1 Analysis
|
|
Device
= 18F4620
Clock
= 40
'remember to enable HSPLL in configuration
|
The first part of a Swordfish program identifies the PIC to
be used and the clock speed in MHz. This is done using Swordfish's keywords "Device"
to identify the PIC and "Clock" to define the clock speed. I used
an 18F4620 for the DDS experiments, because I happened to have one set up in a
plug-type breadboard, but an 18F4620 is gross over-kill to demonstrate how a DDS
works. DDS is a demanding application for a PIC, and
the faster the clock, the better off you are, particularly when programming in a
high level language, such as Swordfish. If you look at Chapter 16 of my book,
Programming the PIC Microcontroller with MBasic, you
will find a chapter on Digital-to-Analog conversion and several DDS programs
written in MBasic, for the 16F877 PICs. In order to wring acceptable
performance, I had to code time-sensitive segments in assembler. We'll see later
that Swordfish produces such efficient code that we do not need assembler
routines to achieve respectable performance from the 18F devices. |
|
|
|
Config
|
The next part of our program follows Swordfish's keyword "Config,"
establishes the PIC's "configuration" which sets certain options that must be
set during programming. This process is also known as setting the configuration
"fuses." Swordfish will supply a set of default
configuration fuses, but it's a good idea to establish any necessary
configuration settings in your code, rather than depend on defaults. |
|
|
|
OSC =
HSPLL,
//turn 4x PLL ON
|
Microchip added a built-in clock multipler phase locked loop
to the 18F series chips. Thus, to obtain a 40 MHz clock rate, we have two
options:
- Use a 40 MHz crystal or resonator or external clock; or
- Use a 10 MHz crystal, or resonator or clock source and
enable the built-in 4x PLL multiplier
It's much easier to use a 10 MHz resonator and enable the
PLL, via the OSC=HSPLL statement. |
|
|
|
LVP = OFF,
|
LVP is the "low voltage programming" command. I won't delve
into the details of low voltage programming versus high voltage programming, but
our program requires LVP to be disabled.
LVP is a feature added by Microchip that turns out to be
more of a hindrance than a benefit. Microchip ships its devices with LVP
enabled, so it's necessary to explicitly turn it off the first time it is
programmed. |
|
|
|
DEBUG = OFF,
|
The 18F-series, along with some late 16F PICs has an internal
option that allows easy extraction of internal settings and variables when DEBUG
is enabled. However, the compiler and the test board must also support DEBUG, so
we disable this feature. |
|
PWRT = ON,
|
This line enables the "power-up" timer (PWRT). The power-up
timer provides a 65 ms (nominal) delay from the time the PIC detects that it has
been powered up until program code begins execution. This delay allows the
device's power supply to stabilize and you will normally wish to set the PWRT to
ON.
|
|
BOREN = ON,
|
BOREN is Microchip's mnemonic for the "brownout reset"
function. A "brown-out" is detected by the PIC when the supply voltage drops
below safe operating levels, but not necessarily down to zero. Enabling BOREN,
as in our test program, is the safest option, as a momentary power interruption
may cause unknown side effects. A clean reset on brown-out (re-starting the code
as if the circuit had its power removed) will help ensure proper code operation
should a power abnormality occur.
|
|
WDT = OFF,
|
WDT is the mnemonic for the "watch-dog timer." The watch-dog
timer causes the PIC to reset, at a specific time interval. The reset interval
has several optional durations settable by the programmer.
Why would you want your PIC code to reset? The answer is
normally you don't want it to reset, so if you wish to use the WDT, your code
will periodically disarm the WDT and thus prevent it from resetting the PIC.
But, if your code has a problem and enters an endless loop, or gets hung up
waiting for an external input, it won't be able to disarm the WDT and hence the
WDT will force a reset, thereby extricating your program from an unexpected
freeze.
If you do not need this level of supervision, keep the
watch-dog timer disabled, as in the example. Otherwise, you will have to add
code to periodically disarm the WDT.
|
|
PBADEN =
OFF,
'for 18F4620 - makes port B digital
|
Reading the
18F4620
data sheet quickly shows that each pin has several potential uses. In the
18F4620's case, for example, the Port B pins may either be used for normal
digital logic input and output, or they may serve as additional analog inputs to
the PIC's analog-to-digital converter.
The default configuration is for the Port B pins RB0...RB4
to be analog inputs on power-on reset. Since we use these pins for digital
output, we must over-ride the default configuration and force these pins to be
digital. (RB5...RB7 are assigned to digital mode at power up.)
|
|
XINST=OFF |
The last configuration control we set is to disable the
"extended instruction set." The extended instruction set allows the 18F-series
chips to execute assembler instructions that were not available in older
devices. Unfortunately, some of these extended instruction set operations have
proven to be buggy, and to avoid trouble we'll disable them.
|
|
|
|
|
If I were writing a book on programming the 18F
Microcontroller with Swordfish Basic, I would have covered the configuration
settings in a separate chapter, and we could therefore jump directly into the
DDS-related code. Since I'm not writing that book, I've tried to make this
page more stand-alone and unfortunately this means we have to slog through
some things related to the nuts and bolts of how a PIC works before we get to
the interesting code.
One more thing. I've mentioned that Swordfish will
automatically set many configuration bits for you. Where do you find out what
configuration bits Swordfish sets?
Assuming you have a normal Swordfish installation, look in
the directory ..\Swordfish\Includes\ for the file with the same name as the
DEVICE you set at the outset of the program, with the extension .BAS. In this
case, the file will be 18F4620.BAS. (If you were using an 18F2580, the file
would be 18F2580.BAS.)
If you open this file with Swordfish's IDE, or with a text
editor such as Notepad, you will find a list of all the possible configuration
fuses that you can access via Swordfish. Here's the first few lines of the
public configuration fuses for the 18F4620:
// configuration fuses...
Public
Config
OSC(OSC)
= [LP, XT, HS, RC, EC, ECIO6, HSPLL, RCIO6, INTIO67, INTIO7],
FCMEN(FCMEN)
= [OFF, ON],
IESO(IESO) =
[OFF, ON],
Then, towards the bottom of the file you will find the
configuration fuse settings Swordfish will use, by default:
// default fuses...
Config
OSC =
HS,
FCMEN = OFF,
IESO = OFF,
PWRT = ON,
BOREN = ON,
WDT = OFF,
WDTPS = 128,
PBADEN =
OFF,
STVREN = ON,
LVP = OFF,
XINST = OFF,
DEBUG = OFF
|
|
If you do not explicitly define a setting for each of these
configuration fuses, Swordfish will apply these default values.
By in large, the default values are good choices. In our
case, the default oscillator parameter would be wrong, but the rest of the
default settings match our manual overrides.
So, then, why did we go on a long detour to look at
configuration fuses that do nothing but duplicate existing settings? There are
two reasons:
- Default configuration fuses are a relative recent
addition to Swordfish, and the earlier versions I used had no
Swordfish-defined defaults. Either you manually entered the desired
configuration fuse setting or you lived with whatever Microchip established as
the power-on configuration.
- Although Swordfish does a very good job at defining
default configuration fuses, your program may need something different. Hence,
it's a good habit to set the configuration fuses the way you want them to be,
and not depend upon the default. And, it's possible that a later release file
might change the default settings, without you being aware of it.
You might be tempted to modify the 18F4620.BAS file, for
example, to make the default oscillator mode HSPLL. That's not a good idea for
several reasons. One is that a Swordfish update might replace your modified
18F4620.BAS file without you being aware of it. Another is that you might wish
to provide a copy of your program source code to a friend or colleague for
testing or other purpose. If your code depends upon a specially modified
18F4620.BAS file, you must also include it, and that file must be copied into
the other system, displacing the standard file. All in all, it not a good idea
to modify the files in the INCLUDE directory unless you have an excellent
reason.
|
|
Next time, we'll look at a bit of real code. |
|
|
|
OK, here we go with some "real" code, not this setup stuff. |
|
Const
SinTable(16) As Byte = (8,10,13,14,15,14,13,10,8,5,2,1,0,1,2,5)
|
Swordfish allows you to define array of constants that may be
used just like a standard array. The syntax is to
name the constant [SInTable], dimension the constant [16], define the type of
data to be stored in the constant array [byte] and, finally, enter the data,
with each value separated by a comma and with the data enclosed in parentheses.
|
|
Values the constant array SinTable are accessed just like any
other array; a = SinTable(3) assigns the value of 14 to the variable a. Remember
that the first value in the constant array has an index of 0. |
|
|
|
So much for the mechanics, now what the heck is SinTable?
I'll try to keep the explanation simple, but a bit of math is involved.
The values in SinTable are the values of the sine
function, taken every 22.5 degrees, with an offset of 8 and scaled for a maximum
peak-to-peak value of 0...15.
In pseudo-code, the values of SinTable are computed as:
For i = 0 to 15
SinTable(i) = Integer(8 + 8 * Sin(i*22.5) )
Next i
For clarity, this assumes the Sin function operates with
the argument in degrees, not radians as is normally the case. There's also a bit
of a discontinuity in that the offset should be 7.5, not 8, but since our byte
variable is integer, we use 8. Then we force the positive peak to be 15, not 16
as would be expected from the equation.
Now, since SinTable is a constant, we can't directly
compute it as part of the normal program code. We could let Swordfish compute
some constants, but for SinTable, we use an electronic calculator or a
spreadsheet, such as Excel.
Here's the result in Excel:
|
|
Step |
Sin(22.5*Step) |
Integer(8+ 8*Sin(22.5*Step)) |
|
0 |
0.00000 |
8 |
|
1 |
0.38268 |
11 |
|
2 |
0.70711 |
13 |
|
3 |
0.92388 |
15 |
|
4 |
1.00000 |
16 |
|
5 |
0.92388 |
15 |
|
6 |
0.70711 |
13 |
|
7 |
0.38268 |
11 |
|
8 |
0.00000 |
8 |
|
9 |
-0.38268 |
4 |
|
10 |
-0.70711 |
2 |
|
11 |
-0.92388 |
0 |
|
12 |
-1.00000 |
0 |
|
13 |
-0.92388 |
0 |
|
14 |
-0.70711 |
2 |
|
15 |
-0.38268 |
4 |
|
|
|
Hence, the Excel spreadsheet says that SinTable(3), for
example, should be 15. I've taken some liberties with these computed values to
better fit the real (in this sense, I mean a real number, i.e., not an integer)
sin values into our limited range of 0...15. Hence, the values in SinTable do
not quite match these computed quantities. |
|
|
|
So, you are probably thinking, I know how you computed the
values in SinTable, but why did you fill it with a crude approximation of the
sine function? Here's a hint to think about until
the next installment. Our 4-bit DAC can convert values from 0...15 into
voltages. Perhaps there's some relationship between that fact and converting our
sine wave data into a range of numbers between 0 and 15. |
|
More tomorrow, work permitting. |
|
|
|
Let's think about how we determine the frequency of our
output sine wave. We know that by sending the correct 4-bits to the DAC, we can
create a waveform that approximates a sine wave. But, how do we make the output
at a specific frequency? If we want a 1000 Hz output, how do do it? Here's the
output from both 4-bit DACs, one generating a 733 Hz sine waveform and the
second at 2.56 KHz. But why does the program generate these frequencies, not,
for example, 1234 Hz, or 4321 Hz? |
|
|
|

|
|
We start with the basic concept that all DDS programs,
including ours, are sampled systems, in that at certain intervals the program
outputs a new DAC value. In some cases, the new DAC value may be identical with
the previous value, so don't understand this to mean that the output voltage
always goes up or down with each new step interval.
In our sample Swordfish program, the interval between successive sample outputs
is 5.6 microseconds. How do we know that? I'll analyze it in detail later, but
our code sets an output pin (Port C, bit 0) to logic high and clears it to logic
low once every sample interval, so we can measure the interval. (Or, we could
look at Swordfish's output assembler code and count the instructions and
calculate the interval. It's much easier to throw the oscilloscope onto the
waveform and measure it.) Here's the output of this "sentinel" pin:
|
|

|
|
We see that our sample interval is 5.576 microseconds, which
we will round to 5.6 microseconds. If you would rather think in terms of
frequency, it's 179.3 KHz, but we should focus on time duration here, not
frequency. So, we know that once every 5.6
microseconds, we must calculate and output a DAC value that corresponds to the
instantaneous value of our desired sine wave output.
I should add that for a high quality output waveform, the
step interval must not vary. This can be a problem with an optimizing compiler
such as Swordfish, as it may "short circuit" some mathematical operations
depending on the particular values. Hence the time to execute a certain program
loop might vary depending on the calculations made within the loop. If there's
enough interest, we can look at ways to force the output steps to always be at
the same interval.
Let's consider a simple example. The illustration below
shows our objective-- a target sinusoidal waveform, at 2.77 Hz. The red vertical
lines represent the output step interval, in our simple case, once every 79
milliseconds. The black squares show our desired output voltage at each sample
interval, assuming, of course, that our DAC has infinite resolution and can
perfectly assume any output voltage between -1 and +1 volts. |
 |
|
If this illustration looks familiar--performing an
analog-to-digital sample of 2.77 Hz input waveform with a A/D sample interval of
79 milliseconds--you are perfectly correct. In fact, the DAC with DDS program is
the inverse of a sampled ADC system. I'll leave you
to ponder these similarities, and how we generate our desired output frequency
until a later installment. |
|
|
|
Let's start by noting that our 2.77 Hz waveform goes through
2.77 X 360 = 997.2 degrees every second. (The example is actually based on 1,000
degrees / sec, but rounding error gives us 997.2.)
If, in one second, our waveform goes through 997.2
degrees, and if an output sample occurs once every 79 milliseconds, then our
waveform must advance by 977.2 degrees/sec x 0.079 sec/sample = 78.8
degrees/sample between every sample point.
Our waveform generator program must then output the
following every sample interval: Output(n) = Sin(78.8 degrees * n). The first 13
values of the output can be computed as:
|
|
Sample |
Time (ms) |
Degrees |
Sin (Degrees) |
|
0 |
0 |
0 |
0 |
|
1 |
78 |
78.8 |
0.980955 |
|
2 |
156 |
157.6 |
0.38107 |
|
3 |
234 |
236.4 |
-0.83292 |
|
4 |
312 |
315.2 |
-0.70463 |
|
5 |
390 |
394 |
0.559193 |
|
6 |
468 |
472.8 |
0.921863 |
|
7 |
546 |
551.6 |
-0.20108 |
|
8 |
624 |
630.4 |
-0.99998 |
|
9 |
702 |
709.2 |
-0.18738 |
|
10 |
780 |
788 |
0.927184 |
|
11 |
858 |
866.8 |
0.547563 |
|
12 |
936 |
945.6 |
-0.71447 |
|
|
Suppose we wanted to generate a slower waveform, say one half
cycle per second, or 0.5 Hz. What is the increment angle between successive
samples? 0.5 Hz is 180 degrees per second. Hence,
every sample interval the desired waveform advances 180 degrees/sec x 0.079
sec/sample = 14.22 degrees per sample.
Here are the first 10 output values needed to generate our
hypothetical 0.5 Hz sinewave with a 0.079 second sample interval:
|
Sample |
Time (ms) |
Degrees |
Sin (Degrees) |
|
0 |
0 |
0 |
0 |
|
1 |
79 |
14.22 |
0.245646 |
|
2 |
158 |
28.44 |
0.476238 |
|
3 |
237 |
42.66 |
0.677646 |
|
4 |
316 |
56.88 |
0.837528 |
|
5 |
395 |
71.1 |
0.946085 |
|
6 |
474 |
85.32 |
0.996666 |
|
7 |
553 |
99.54 |
0.98617 |
|
8 |
632 |
113.76 |
0.915241 |
|
9 |
711 |
127.98 |
0.788226 |
|
|
Let's plot these data points and other data points for the
first 3 seconds or so of our synthesized 0.5 Hz waveform. |
 |
|
Looks a lot like a 0.5 Hz sine wave, doesn't it? Of course,
we assume in this simple example that our DAC has infinite resolution (we'll get
to the 4-bit DAC shortly).
Usually, we will run the DAC output through a low pass
filter (also known as an anti-alias filter, for reasons we may touch upon in a
later installment, time and interest permitting). We can simulate a low pass
filter with our graph program by connecting the discrete output points with an
interpolated waveform, using a cubic spline algorithm. Don't worry if this does
not mean much to you -- consider it a way of smoothing the waveform similar to
how a draftsman would have done it before the days of CAD. He would have taken
out a flexible bit of thin wood or plastic and bent it so that it touched three
or more adjacent data points and then drawn a fair curve between the data points
using the natural curve of the wood or plastic under tension.
Here is the cubic spline interpolated output. |
 |
|
Sure looks like a nice, smooth pure sinewave doesn't it.
Oh, you don't believe it's the same stepped waveform run
through a mathematical equivalent of a low pass filter? Here is the original
stepped response and the spline response. |
 |
|
Next time, we'll get back to our program and determine many
degrees we must advance each sample interval to obtain 733 Hz output with a 5.6
microsecond sample.
Why don't you compute it and compare your results with my
answer in the next installment. |
|
|
|
We use the same approach as in our 0.5 Hz example. We'll
start our calculations with degrees and then consider the 4-bit DAC and the
length 16 sine table. In one second, a 733 Hz sine
outputs 733 /second x 360 degrees = 263880 degrees/second. Therefore in 5.6
microseconds, the output advances by 2.6388 x 105 degrees/second x
5.6 x 10-6 seconds/sample = 1.4777 degrees/sample.
Compared with the relatively coarse step interval in our
examples, we would expect (DAC resolution permitting) a relatively clean output,
with the need for exotic low pass filter designs.
To understand how this translates to our limited sine
table and 4-bit DAC, let's go back to our Swordfish code:
|
|
Dim
aStep,
bStep As Word,
PhaseAccumA
As Word,
'use these as a 4.12 (15 bit total)
PhaseAccumB As Word,
'phase accumulators
Temp As Byte
|
|
|
For simplicity, I'll concentrate on the part of the
code generating the 733 Hz signal and temporarily ignore the 2.56 KHz related
code.
The word length (two bytes, or 16 bits) variable
PhaseAccumA holds the total phase information that we use to index into the
constant SinTable. This variable is usually called the "phase accumulator" in
DDS terminology. Many high end DDS chips have a 32-bit or more phase
accumulator. (The Z90 uses an Analog Devices AD9851 DDS integrated circuit with
a 32-bit phase accumulator and a 10-bit DAC. Its clock rate is 180 MHz,
corresponding to a 5.5 ns sample interval .) Ours is 16 bits, more than enough
to demonstrate the concept.
Let's look in more detail at how we use the phase
accumulator variable. We will consider the 16 bits of PhaseAccumA as 4-bit and
12-bit parts, separated by an imaginary "binary point."
| |
Integer Index into SinTable |
Implicit
"Binary"
Point |
Fractional Part |
| |
MSB |
LSB |
| |
4-bits |
. |
12 bits |
The upper four bits (this is usually called a nibble, but
Swordfish does not support nibble variables) is our index into the constant
array SinTable. The lower 12 bits are the "fractional" parts of the phase
accumulator.
If we look at a traditional decimal number, such as 1.234,
we can regard the digits to the right of the decimal point as the "fractional"
part of the number, and the digits to the left of the decimal point the
"integer" part of the number. Our phase accumulator has exactly the same
structure, except that being binary, we'll call the separator between the
integer and fractional parts the "binary point," instead of the "decimal point."
If we convert PhaseAccumA's structure into decimal, the
integer value runs from 0 to 15. The fractional part runs from 0 to 0.999755...
in steps of 0.000244... (1/212 or 1/4096)
|
|
Next installment will delve into PhaseAccumA's structure in
more detail and show how we use it to index into the sine table. |
|
|
|
|
|
|
|