Learn DSP By Programming Your PC, Part 1

advertisement
DSP for Beginners
Learn DSP By Programming Your PC, Part 1
Introduction
For at least fifteen years now it seems
DSP (Digital Signal Processing) has been
slowly infiltrating the Amateur Radio and
electronic hobbyist community. A year ago,
AMSAT announced that its Eagle satellite,
currently in development, will carry DSP
technology in the form of Software Defined
Transponders. Then there’s the HPSDR
(High Performance Software Defined Radio)
project that involves many of the same folks
working on Eagle (http://hpsdr.org/). And
there’s even talk that SuitSat II will carry
DSP based radios.
So what is this DSP thing anyway? How
does it really work? How can you write your
own DSP software to do what YOU want?
In the early days of Amateur Radio, hams
built, operated and experimented with their
own homemade equipment to learn the new
science of radio for themselves. My goal is to
explain, step by step, in the simplest possible
terms, how to write, run and experiment
with your own DSP software so you can
learn the new science of DSP for yourself.
And I’ve tried to do this so that no previous
programming experience is required.
Specifically, using a series of articles, I
propose to show you how to program a PC
running Windows XP to access the signal
samples coming into and out-of your sound
card. I will then build on this capability to
show you how to write the software for a
simple 3 KHz FIR (Finite Impulse Response)
low pass filter. More exactly, I’ll show you
how to create a 31 tap, windowed-sinc,
convolution-based filter using a Blackman
window. Not sure what this means? All will
be explained as we go along, or I’ll tell you
where to look in reference [4]. Once you
understand one FIR filter, you’ll understand
how to write the software for similar high
pass, band pass or notch filters; whatever
you need.
OK, OK, this hardly covers the width and
breath of DSP. But to attempt to teach all
aspects of DSP programming I would have
to write a six page article for every AMSAT
Journal for the next 100 years (Get busy
Richard! – Ed.). Just to explain the FIR
filter, I’ll have to crunch into several AMSAT
Journals the equivalent of one or more
chapters from each of several textbooks
in a dozen different subject areas. In any
by Richard F. Crow, n2spi@amsat.org
Copyright © 2007 by Richard F. Crow
event, if you write and run all the software
in these articles, you should have a solid
foothold from which to experiment with
other techniques for creating convolutionbased filters, not to mention, investigate
other areas of DSP.
What You’ll Need
To run the software in these articles, you’ll
need:
1) A PC running Windows XP
2) A s t a n d a r d P C s o u n d c a r d , o r
equivalent
3) A microphone connected to your sound
card, or other signal source
4) A set of speakers connected to your
sound card
5) The capability to download free software
from the Internet
Now, for the record, a PC running Windows
is not my first choice to write DSP programs.
Programming DSP using Windows is
unnecessarily complicated and problematic.
I would much rather have done this using
a system designed specifically for DSP
such as the legendary ADSP-2181 EZ-Kit
Lite from Analog Devices or its current
replacement, the KK7P DSPx module plus
KDSP10 interface board (www. kk7p.com/
dsp.html). Unfortunately, the DSPx plus
KDSP10 together cost $148 at the time this
was written and the KDSP10 is a kit, which
requires soldering.
So, not wanting to lose 99% of my audience,
I eventually decided to settle for the PC that
would cost most of you $0 beyond what you
have already spent. Perhaps in the future, I’ll
rewrite these articles to target the DSPx and
KDSP10 or their equivalents. Having made
this decision, it suggested that I also select
software tools for this project that also cost
$0. This in turn dictates that we use the C
programming language since, if we want to
use free software that’s reasonable in size
and simplicity, the Windows interface, which
is available to us, is written in C.
Now, I realize many of you don’t know C.
But if nothing else, type the program listings
exactly as shown and the DSP software
should run just fine. Along the way, however,
I hope you do start to learn C as it is also the
high level language of DSP. For example,
the professional software development
tools for the Analog Devices family of DSP
processors, such as in the DSPx, are based
on C. As we go along, I’ll try to assist you
in the C learning process.
Now, as much as I would like to start chasing
signal samples right away, we first need to
build the programming infrastructure for
cranking out C code. Specifically, we need
to put together a text editor, C compiler,
linker and other software. Doing all this will
consume the rest of Part 1 of this article. So,
let’s develop a C programming environment
by going through the C programming
process step by step. Traditionally, the first
C program taught to beginners displays the
message “Hello world!”. Consider Figure
1.
Step 1: Select a Text Editor
The first thing we need to do is to type the
text in Figure 1 into a C source code file
named, say, “Pgm1.c”. For the C compiler to
work, the file Pgm1.c must contain plain text
only. The software that produces such files is
called a text editor. Every copy of Windows
XP comes with Notepad and DOS Edit,
which are simple text editors. There are also
zillions of more feature-packed text editors
out there, some free and some not. Do not
use a word processor, such as WordPad or
Word, unless you know what you’re doing.
I’ll leave it up to you to find a text editor you
like. Right now, take a moment to boot up
your PC and use NotePad, if nothing else,
to type up the listing in Figure 1. If you
never heard of NotePad, click on Start > All
programs > Accessories > NotePad.
Step 2: Install a C Complier/Linker
Now that we have Pgm1.c, we need a C
compiler to convert it into a “Pgm1.obj”
object code file that contains the machine
language version of Pgm1.c. We then need
a Linker to combine any and all “.obj”
files, plus any needed “.lib” files, into an
executable “Pgm1.exe” file. (Each .lib or
library file consists of a related collection of
computer code for doing frequently needed
things like inputting keyboard characters
and displaying text, or performing standard
math operations.)
To this end, I’ve selected the free C compiler/
linker from Digital Mars. To download it, go
to http://www.digitalmars.com/download/
freecompiler.html, scroll down and click
on “Digital Mars C/C++ Complier Version
8.49”. This will downloaded a file named
“dm849c.zip”. Then, scroll down some
The AMSAT Journal  July/August 2007  www.amsat.org
21
more and click on “Basic Utilities”. This
will download a file named “bup.zip”. And
last but not least, send if you can Digital
Mars the modest $39.00 they’re asking for
their more comprehensive C/C++ CD. Good
work like this needs to be encouraged so that
it doesn’t dry up.
Now, get some documentation by going to the
Digital Mars home page at www.digitalmars.
com. Look for the word “Documentation”
on the upper right side and, under that,
click on “Compiler & Tools Guide”. When
you get a chance, read as much of this as
you can. Realize, however, that the C code
examples given here will only work in a
DOS command line environment such as
the DOS window described below.
Now, unzip “dm849c.zip” and you’ll get
bunch of new folders at “c:\dm849c\dm\...”
filled with a bunch of new files. In the old
days, some 15 years ago, the resulting
C compiler and linker would have been
sufficient. But try as hard as I might to
minimize the number of tools and files so
as to keep things simple, the complexity
of Windows forces us to need at least one
more tool, a “resource compiler”. So, unzip
“bup.zip” to get “rcc.exe” the Digital Mars
Resource Compiler. We won’t need the
other tools from “bup.zip”, so delete them
if you wish.
The good news is the Digital Mars package
is a nice little free C compiler/linker.
The bad news is (well, not that bad) it’s a
command line compiler/linker suitable for
a PC operating system from a previous era,
namely DOS. Perhaps that’s why it’s free.
Some of you remember DOS I’m sure. But
if you don’t, I’ll review it next.
Step 3: Learn a Little DOS
Now, I realize using DOS to do anything
these days is horribly unfashionable. But
the truth is, I can explain how to perform a
particular action faster and more concisely
typing one DOS command than I can
navigating through a maze of mouse clicks
intertwined with keyboard entries. Also, if I
were to do this with Windows, the Window’s
user interface can be custom configured in
so many ways it’s impossible to know if the
window I see will be the window you’ll see.
And despite what the proponents of Windows
say, DOS is so simple I can describe how to
use it in one sentence. Specifically, to get
DOS to do something, type in the appropriate
DOS command, followed by any needed
command arguments and press the Enter key.
This is the classic command line interface.
To open a DOS window in Windows XP,
click on Start > All Programs > Accessories >
Command Prompt. Right click on Command
Prompt then click on Create Shortcut. Drag
the Shortcut out onto your Desktop. You
can right click on this shortcut to access the
DOS window’s Properties. Investigate and
experiment with these properties. Chances
are, you’ll want to personalize many of them.
For example, I like to open with a maximized
DOS window using the 8x12 font. From
now on, to open a DOS Window for this
project, double-click on this shortcut. Take
a moment right now to boot up your PC and
open a DOS window. Looking at the DOS
window, the line of text closest to the bottom
of it should look something like:
C:\ ... >
where “...” is some file directory path. The
last character, the “>”, indicates this is a
command prompt, meaning it’s your turn to
type a command on the keyboard if you wish.
Let’s type the following command. (Where
you see “<enter>”, don’t type it, just press
the Enter key.)
cd \ <enter>
The DOS command “cd” means change
directory. (A directory in DOS is the same as
a folder in Windows.) The command “cd \”
means wherever you are in the file structure,
change directory to the root directory. Now,
all of you should see:
//001) Filename:
Pgm1.c, version 1.0
//002) Description: A first C program for Windows XP
//003)
which creates a Message Box that
//004)
displays the message “Hello world!”
//005) Programmer: Richard F. Crow, N2SPI, July 25, 2007
//007)(this line reserved)
//008)(this line reserved)
//009)(this line reserved)
//010)(this line reserved)
#include <windows.h>
//012)(preproc’r inserts text in “.h” files..
//013)
...so pgm gets needed’C’definitions)
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR
szCmdLine,
int
iCmdShow )
{
MessageBox(
NULL,
TEXT( “Hello world!” ),
TEXT( “Pgm1, v1.0” ),
0 );
//015)WinMain() starts applicat’n:
//016)=a number that identifies...
//017)..this occurance of this app
//018)=ptr to cmd line text,if any
//019)=init’l window(maximzd?,etc)
}
return( 0 );
//021)call MessageBox() function:
//022)=I.D. of owner window (none)
//023)=pointer to text in MessgBox
//024)=pointer to MsgBx title text
//025)=style of MsgBx(DefaultUsed)
//027)quit WinMain, rtn 0 to WinOS
Figure 1: Pgm1.c
22
The AMSAT Journal  July/August 2007  www.amsat.org
C:\>
Now, let’s test our compiler/linker by
navigating to “c:\dm849c\dm\bin”, clearing
the DOS window, and executing the C
compiler/linker “dmc.exe”. If the compiler/
linker is working, you should see a multipart message telling experts how to use it.
Type:
Next, type the command:
help |more <enter>
(Note: the “|” character before more is the
upper case character of the \ key, just above
the Enter key.) This displays as much text
as will fit in the DOS window without
scrolling off the top. Press the Enter key
to get the next line of text. Keep pressing
the Enter key until you reach the command
prompt again. This is a list of all the DOS
commands, and essentially, that’s all there is.
Once you learn a DOS command, but forget
the details, you can use the help command
to refresh your memory. For example, to
get help on the purpose and use of the “cd”
command, type:
cd c:\dm849c\dm\bin <enter>
cls <enter>
dmc <enter>
Next, let’s set up and test the Resource
Compiler. First, remember where you left the
file rcc.exe and use the DOS copy command
to copy it into “c:\dm849c\dm\bin”. For
example, if rcc.exe in is in c:\temp1, you
might navigate to c:\dm849c\dm\bin, if not
already there, and type:
copy c:\temp1\rcc.exe rcc.exe <enter>
help cd <enter>
There are easier, shortcut ways to do this
in DOS. But I don’t have time to explain
all the details, so I’ll leave it to you to find
out how. For now, you may find it easier to
copy files by dragging them around inside
Windows Explorer. When ready, navigate to
“c:\dm849c\bm\bin” (if not already there),
clear the DOS window, and execute the file
rcc.exe. If rcc.exe is working, you should see
its message describing its use. Type:
Finally, to terminate a DOS window, at the
DOS command prompt type:
exit <enter>
Though not absolutely necessary for these
articles, it would be helpful to learn more
about DOS from the Internet, various books
and the help command. If you absolutely
abhor DOS, you can use Windows Explorer
(not Internet Explorer) to accomplish the
same tasks. If you never heard of Windows
Explorer, click on Start > All Programs >
Accessories > Windows Explorer or right
click on Start and click Explore.
cd c:\dm849c\dm\bin <enter>
cls <enter>
rcc <enter>
And finally, let’s create a subdirectory, or
folder, off of c:\dm849c\dm\bin named
Program1. (Note: DOS likes directory and
file names of 8 characters or less.) Program1
will be the unique location where we will
store Pgm1.c along with the many files that
issue from it during the compilation and
linking process. Other C programs will be
stored in their own subdirectories that will be
Step 4: Test Tools, Make Folders
Now that we know a little DOS we can
test things out and set up a file structure
to bring some order to our programming
environment. Take a moment to open a DOS
window, if one’s not open already, then enter
the following two DOS commands, one at
a time:
cd c:\dm849c\dm <enter>
dir /p <enter>
#include <windows.h>
These commands navigate to “c:\dm849c\
int WINAPI WinMain(
dm” and then display the files and
HINSTANCE hInstance,
subdirectories there. Note the readme.txt
HINSTANCE hPrevInstance,
file. Let’s use DOS Edit to read it. To do
PSTR
szCmdLine,
this, type:
int
iCmdShow )
edit readme.txt <enter>
{
MessageBox(
Once Edit has been opened note the menu
NULL,
bar across the top. To access the File menu,
TEXT( “Hello world!” ),
TEXT( “Pgm1, v1.0” ),
hold down the Alt key and press the F key,
0 );
and so forth. Press the Esc key to erase a
menu. Press F1 for help, such as it is. To
return( 0 );
quit Edit, hold Alt and press F then X. The
}
readme file will mean something to expert
readers. If it doesn’t mean much to you, don’t Figure 2: Pgm1.c without comments.
worry about it.
created as needed. To do this, be sure you’re
still in c:\dm849c\dm\bin, then type:
md Program1 <enter>
md is the DOS command for make directory.
Next, remember where you left Pgm1.c and
copy it into c:\dm849c\dm\bin\Program1.
Then, use the cd command to navigate to c:\
dm849c\dm\bin\Program1, if necessary and
use the dir command to verify that Pgm1.c is
there, and in fact, is the only file there.
Step 5: Compile/Link the Source
Code
While still in c:\dm849c\dm\bin\Program1,
compile and link Pgm1.c by typing:
..\dmc pgm1.c <enter>
(“..\” means navigate up one level in the
directory tree. In other words, momentarily
move up to c:\dm849c\dm\bin to find dmc.
exe, which is where it should be.) Now see
what’s in c:\dm849c\dm\bin\Program1. That
is, provided there have been no compiler/
linker error messages in the mean time.
Among other new files, you should see
Pgm1.exe that is the executable version of
Pgm1.c. If so, type:
Pgm1 <enter>
Now, a little message box should pop
up which displays “Hello world!”. If so,
congratulations! Many of you just wrote,
compiled and ran your first C program for
Windows.
Step 6: Learn a Little C
Let’s go through Pgm1.c line by line. In
line 001, the first two characters “//” signal
the beginning of a comment. When the
compiler sees “//” it ignores the rest of the
characters on that line and goes to the next
line. The next four characters, “001)” are the
line number. I put these in every comment
because it’s the quickest way to identify a
particular line when discussing it. Read the
rest of the comments in Pgm1.c to see what
each line does. I realize some comments may
be a little cryptic, so study them carefully.
To keep program listings compact, I kept
comments short.
To ease your burden when typing up the
C source files, you may leave out the
comments. See Figure 2. If you remove
the “//”, be sure to remove the text which
follows. Take a moment to experiment with
this. In Pgm1.c, eliminate the “//” in line 001
but not the text that follows. Now save it and
compile it (repeat the first part of Step 5) to
see what a compiler error looks like. When
done, fix this so the error goes away.
The AMSAT Journal  July/August 2007  www.amsat.org
23
//001) Filename:
//002) Description:
//003)
//004)
//005) Programmer:
#define IDC_TEXT1 1000
#define IDC_TEXT2 1001
#define IDC_SCROLL 1002
#include<windows.h>
//007)(this
//008)(must
//009)(must
//010)(must
Pgm1.rc, v1.0 (to be copied/renamed d2a.rc later)
Resource script to make d2a.res resource file
that goes with d2a.c, a pgm that generates sine
wave samples xmitted thru sound card’s D/A output
Richard F. Crow, N2SPI, July 25, 2007
line reserved)
match same #define in d2a.c)
match same #define in d2a.c)
match same #define in d2a.c)
//012)(preproc’r inserts text in”.h”files..
//013) ...so pgm gets needed’C’definitions)
d2a_resources DIALOG DISCARDABLE 100, 100, 200, 50
STYLE WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION TEXT(“D2A, v1.0: Pgm to Output D/A Samples”)
FONT 8, TEXT(“MS Sans Serif”)
BEGIN
CTEXT TEXT(“Sine Wave Generator”), IDC_TEXT1,
CTEXT TEXT(“Frequency: 5000 Hz”), IDC_TEXT2,
SCROLLBAR
IDC_SCROLL,
END
60,
60,
10,
4, 80,
14, 80,
28, 180,
8
8
12
Figure 3: Pgm1.rc
Going to line 012, any command which
begins with ‘#’ is C preprocessor command,
or directive. The preprocessor directive,
#include <windows.h>, causes the text in
the file windows.h to be inserted into Pgm1.
c at line 012. In a nutshell, a preprocessor
directive adds, selects or alters text in the
C source file before the compiler scans it.
An.h file, as in windows.h, is called a header
file or include file and it defines certain
standard, boilerplate C entities which may
be referenced in the C source file.
Now go to line 015 where the action really
starts with WinMain. WinMain is the name of
a function. Functions are blocks of computer
code that perform a particular, well, function.
Functions are like subroutines. Functions
are invoked, or called, by some higher level
function or software. When you execute
a program, you’re signaling the Windows
OS (Operating System) that you have an
application to run. In response, the OS
allocates some RAM for your program,
loads it, and then runs it, or more correctly,
calls it as a function. When programming
for Windows, you must name your top-level
function WinMain.
Right after WinMain is a ‘(‘ followed by a
list of variables, known as arguments, which
ends with a ‘)’. In Pgm1.c, WinMain() has
four arguments, hInstance, hPrevInstance,
24
szCmdLine and iCmdShow. In a nutshell,
the software which calls WinMain() assigns
values to these arguments and then calls
WinMain(). WinMain() can use these values,
or not, as inputs to its computations. The text
“int WINAPI WinMain( arg1, ..., arg_last )”
is called the function header.
of same. In addition, these basic types can be
reincarnated into more sophisticated variables
known as arrays, pointers, structures and
unions. To become skillful at this kind of
Windows programming, it’s important to get
comfortable with using pointers, structures
and pointers to structures.
After the function header is the function body
which starts with a {, in line 020 and ends
with a matching }, in line 028. The function
body consists of statements. In C, computer
instructions are called statements. The end
of a statement is denoted by a ‘;’. Because of
this, there can be several C statements on a
single line, or one statement may be spread
over several lines. An example of several C
statements is:
In Pgm1.c, there are two statements in
the WinMain() function body. The first
statement, spread across lines 021 to 025,
calls the function MessageBox() which is
part of Windows and creates the Hello world!
message. Note that values are assigned
to the MessageBox() arguments right in
its argument list. The second statement,
in line 027, returns a value of zero to the
Windows OS. The int before WinMain()
specifies this return value will be type int.
The WINAPI before WinMain() is #defined
inside windows.h so as to be replaced with
the string __stdcall, a technical detail. In
conclusion, functions are the computational
backbone of C. A C program basically
consists of functions, calling functions,
calling functions. Though not absolutely
necessary, it would be helpful to learn
more about classic C (not C++, not C #,
not C++.net, nor C#.net) from the Internet
and books.
float ItemPrice;
float SaleTaxPercentage;
float MyCost;
ItemPrice = 5.57;
SaleTaxPercentage = 0.04;
MyCost = (SalesTaxPercentage * ItemPrice)
+ ItemPrice;
(* is the operator for multiplication.) Note
that a statement can also be used to declare a
variable. This is where a variable is defined
by first stating its type, such as float and
then stating its name, such as ItemPrice.
Basic variable types are void, char, int, float,
double along with extensions and variations
The AMSAT Journal  July/August 2007  www.amsat.org
Step 7: Create a Resource Script
In the world according to Windows, icons,
fonts, menus, dialog boxes, etc., standard
objects used to make the Windows user
interface, are generated from resource data.
Unless resource data is predefined, as for
some stock object, it has to come from
a resource file, a file with extension .res.
A resource file is produced by a resource
compiler which takes its input from a
resource script, a file with extension .rc. A
resources script is another plain text file you
create with your text editor.
capability. A batch file is a plain text list of
DOS commands which has the file extension
.bat. Instead of typing the same sequence of
DOS commands over and over, we can put
them a batch file and then simply execute
it. Right now, let’s take a moment to create
a batch file. First, open a DOS window and
navigate to c:\dm849c\dm\bin\Program1, if
not there already. Then, create a batch file
named Make.bat by typing:
Let’s take a moment and create a resource
script. Now, before going on, note that no
resource file is needed for Pgm1.c. But next,
in Step 8, we’ll need to feed a resource file
to the compiler/linker, even if it’s a dummy
file. And in the next article in this series,
Part 2, we’ll need a resource file named
d2a.res. So while we’re at it, let’s type up
the resource script to make this resource
file. For file name consistency, we’ll call
it Pgm1.rc, for now. Later, when we get to
Part 2, we’ll copy it and rename it d2a.rc.
To do this, open a DOS window, navigate
to c:\dm849c\dm\bin\Program1, if not there
already, and type:
Type in the progam listing shown in Figure 4
and save it in c:\dm849c\dm\bin\Program1.
When you get back to the DOS command
prompt, type:
edit Pgm1.rc <enter>
Type in the program listing shown in Figure
3 and make sure it gets saved in c:\dm849c\
dm\bin\Program1.
(Note: The ‘|’ character in line 017 of Pgm1.
rc is the upper case of the ‘\’ character.)
Step 8: Automate the Compile/Link
Step
The compiler/linker so far has been a little
out of control. This is because none of the
many compiler/linker options have been
specified, forcing it to assume things which
ain’t necessarily so. Also, DOS sometimes
requires a lot of tiresome typing, such as
“cd ... “ this and “cd ... “ that. Let’s solve
both problems.
A nice feature of DOS is its batch file
REM
REM
REM
REM
REM
REM
edit Make.bat <enter>
make <enter>
Now, Pgm1.c will be compiled and linked,
just as before. But this time you don’t have to
type as much, and now, the compiler/linker
is doing what we want.
Let’s learn a little about batch files and
compiler/linker options by going through
Make.bat line by line. In line 001, REM
denotes a batch file comment, so this line is
ignored. In line 007, the batch file command
echo on insures that lines in the batch file will
be displayed as they are executed. In line
008, the DOS command cls clears the DOS
window, as we’ve seen before. In line 009,
the resource compiler, rcc.exe, is invoked
using its full file path to compile Pgm1.rc.
After that, the resource compiler switch, -32
causes a 32 bit resource file to be generated.
In line 010, the C compiler/linker, dmc.exe,
is invoked using its full file path to compile
and link Pgm1.c. After that:
winmm.lib
links Windows MultiMedia
library for sound card access
comctl32.lib links common controls such as
progress bar used later
-mn
selects the default Win32
memory model
-WA
generates a Windows .exe
file
-L/RC
tells linker to copy resources
from the .res file
-L/SUBSYSTEM:WINDOWS:4.0
tells linker to make a Windows
XP app
And finally, in line 011, the batch file pause
command keeps the DOS window from
disappearing if Make.bat is invoked from
Windows Explorer and the close on exit
property is selected or built-in. This gives
you the chance to view resource compiler
or compiler/linker errors, if any.
Since the programming environment is now
in place, we don’t need to repeat everything
we’ve done so far. From now on, for this
series of articles, the C programming process
is simply:
1) Type up the C source code and save as
a “.c” file
2) Type up the “.rc” resource script
3) Run Make.bat to produce an “.exe”
file
4) Run the “.exe” file
Step 9: In Case of Trouble
First, check your typing very carefully to
make sure you’ve entered text from the
various listings exactly as shown.
Second, use NotePad to open any and all
files you typed from this article. If you see
any garbage characters, you’re not using a
suitable text editor.
Third, I’m going to try to make it possible
to download the files mentioned in these
article, including the .exe executables, from
the AMSAT Web site, www.amsat.org. If
and when this happens, download the files
and compare them to yours. Try compiling/
linking the downloaded files and running
them to see if any problems exist with your
compiler/linker or PC.
Filename: Make.bat, version 1.0
Description: Compiles .res resource file from .rc resource script.
Then, compiles .c source file and links it using the specified options.
NOTE: To make .exe file for source other than Pgm1.c, change “Pgm1” in
lines 9 and 10 to new “name”. Name must match for both .rc and .c files.
Programmer: Richard F. Crow, N2SPI, July 25, 2007
echo on
cls
c:\dm849c\dm\bin\rcc Pgm1.rc -32
c:\dm849c\dm\bin\dmc Pgm1.c winmm.lib comctl32.lib -mn -WA -L/RC -L/SUBSYSTEM:WINDOWS:4.0
pause
Figure 4: Make.bat
The AMSAT Journal  July/August 2007  www.amsat.org
25
Step 10: Homework (Optional)
First, if you have little or no programming
experience, get and read Code by Charles
Petzold to learn basic computer concepts.
Even if you’re an experienced programmer,
read this excellent book. See reference
[1]. Written for the layman in an engaging
style, it starts with an interesting technical
discussion of Morse Code which alone will
be worth the purchase price for many hams.
And when you finish you’ll know more about
how computer hardware actually works than
99% of the programmers out there.
Second, to learn the C programming
language, read Sams Teach Yourself C In 24
Hours by Tony Zhang and John Southmayd.
See reference [2]. This very good book will
teach you classic ANSI C step-by-step.
Third, the interface to Windows we are using
is the venerable Win32 API (Application
Programming Interface). Read Programming
Windows, Fifth Edition by Charles Petzold
to learn the ins and outs of this vast subject.
See reference [3]. This excellent book
assumes you know C, but otherwise explains
things clearly and thoroughly with lots of
programming examples.
if you can, order this book for the $64.00
asking price so Mr. Smith can keep up the
good work.
References
[1] Petzold, Charles (2000), Code,
Microsoft Press, Redmond, WA, ISBN
0-7356-0505-X, ISBN 0-7356-1131-9
(paperback)
[2] Zhang, Tony, and Southmayd, John
(2000), Sams Teach Yourself C In 24
Hours, Sams Publishing, Indianapolis,
IN, ISBN 0-672-31861-X
[3] Petzold, Charles (1999), Programming
Windows, Fifth Edition, Microsoft
Press, Redmond, WA, ISBN 1-57231995-X
[4] Smith, Steven W. (1997), The Scientist
and Engineer’s Guide To Digital Signal
Processing, California Technical
Publishing, P.O. Box 502407, San Diego,
CA 92150-2407, www.DSPguide.com,
ISBN 0-9660176-3-3
Fourth, some day there may be a better
book that explains DSP in practical, easyto-understand terms. But until then, The
Scientist and Engineer’s Guide to Digital
Signal Processing, by Steven W. Smith is, in
my view, the very best there is. See reference
[4]. Read this book to learn about Linear
Systems theory and convolution, which is
the serious magic behind FIR filters. This
book is available as a free download. But
AMSAT is proud to offer this quality white polo
shirt made of 100% combed cotton by Outer
Banks for 2007. The shirt is short sleeve and
has a collar and three buttons. A pocket is on
the left. Appearing directly above the pocket
is the AMSAT logo and name in red and Radio
Amateur Satellite Corporation in blue. This
shirt comes in medium, large, extra large, 2X
large and 3X large. Quantities are limited.
More Symposium Notes:
SDX (Software Defined Transponder): There are 3 or 4 different versions of SDX being
independently developed. At least one will be demonstrated at Symposium. If you are
coming to Pittsburgh, bring your U/V capable rig!
ACP: A prototype phased array is being developed, based on the ideas Tom Clark, K3IO,
has been developing for the ACP. We hope to demonstrate it at Symposium; bring your
2m and 70cm handhelds. This is a significant effort for many reasons. The phased array
antenna system is mandatory for the digital communications package. The easiest way to
implement the phased array is unacceptable in terms of mass, power, and heat. Tom has
been applying basic mathematics and physics to develop simpler means of implementation,
and one is about to be tested. Tom will present a paper at Symposium on the mathematics
involved, the concepts, and the implementation. If you’ve never heard Tom speak, you’re in
for a real treat -- he has the rare ability to make apparently difficult mathematics intuitively
obvious to the casual observer!”
26
The AMSAT Journal  July/August 2007  www.amsat.org
This maroon 2007 Symposium Shirt with logo
embroidered in gold may be ordered either
in advance of Symposium for delivery at the
Symposium. Check the AMSAT 2007 Symposium
Web site for details.
DSP for Beginners
Learn DSP By Programming Your PC, Part 2
by Richard F. Crow, N2SPI, n2spi@amsat.org
Copyright 2007 by Richard F. Crow
Introduction
In the previous article in this series, Part
1, the series concept was introduced, a C
programming environment was established
and additional reading was suggested. In
this part, we’ll build on Part1 to write a
C program to generate sine wave signal
samples, from 20 Hz to 20 KHz in 1 Hz
steps and output them. If you have speakers
connected to your computer you’ll hear the
result. In other words, this exercise will show
you how to create signal samples and output
them through the DAC (Digital to Analog
Converter) in the PC’s sound card. Before
continuing, it’s strongly recommended
you read Part 1 in the July/August 2007
AMSAT Journal. Since program listings will
consume most of this part, leaving little room
for discussion, we’ll focus exclusively on
writing software.
Step1: Make New Folders
Right now, boot up your PC, open a DOS
window and navigate to c:\dm849c\dm\bin.
Then, use the DOS command “md” to make
a new subdirectory (folder) named “D2A”.
Now, navigate to this new subdirectory (c:\
dm849c\dm\bin\D2A) and make five more
subdirectories named “stage1”, “stage2”,
stage3”, “stage4”, and “stage5”. This will
allow you to create d2a.c in 5 stages, one
stage at a time. This way, you can type,
compile and run the code a little at a time
and, as a result, compiler errors, if any,
should be fewer at a time, and also contained
within a relatively small section of code.
Step 2: Prepare Make.bat, D2A.RC
Now, copy make.bat from c:\dm849c\dm\
Program1 to c:\dm849c\dm\D2A\stage1
and rename it make2.bat. Navigate to ...\
D2A\stage1 and edit the make2.bat there
by searching for the string “Pgm1” and
replacing it with “d2a”. When done, make2.
bat in ...\D2A\stage1 should look like Figure
1. Also, copy Pgm1.rc from c:\dm849c\dm\
bin\Program1 to c:\dm849c\dm\bin\D2A\
stage1. Then use the DOS command “ren”
or “copy”, your choice, to rename Pgm1.
rc to d2a.rc. Then search for “Pgm1” and
replace it with “d2a”. When done, d2a.rc in
...\D2A\stage1 should look like Figure 2.
Step 3: Create D2A.C, Stage 1
Create a new file named d2a.c and type in
the code in Figure 3. When done, save it in
c:\dm849c\dm\bin\D2A\stage1, run make.
bat, and execute d2a.exe. If everything is
OK, you should see a small dialog box
that includes a horizontal scrollbar with
the thumb (slider button) at its extreme
left position. At this point in d2a.c, we’ve
#defined all the constants we’ll need,
declared global variables, and established
WinMain(). WinMain() creates a dialog box
for this dialog box application, and in line
040, directs the WinOS’s attention to the
dialog box’s message handling function,
DlgProc(). Windows OS is a message-based
system. When it detects mouse clicks, etc.,
inside the dialog box, it calls DlgProc() with
its arguments “message”, “wParam”, and
“lParam” set to values that indicate what
was detected. Right now though, DlgProc()
is an empty shell. Once the code in Figure 3
is working, don’t add any more to d2a.c here
in stage1. If you run into trouble at a later
stage, instead of starting over from scratch,
you’ll want to copy the good files from here,
or from whatever stage was last working, as
a starting point for making another go at it.
Step 4: Update D2A.C, Stage 2
Copy make2.bat, d2a.rc, and d2a.c from c:\
dm849c\dm\bin\D2A\stage1 to c:\dm849c\
dm\bin\D2A\stage2. Navigate to ...\D2A\
stage2 and in d2a.c, delete everything
between lines 050 and 200. Be sure to keep
line 050 and line 200. Now go to Figure 4
and type in the code starting with line 051
and ending with line 085. In addition, enter
code starting with line 183 and ending with
line 199. (The ‘|’ character in lines 079 and
080 is the upper case of the ‘\’ character.)
When you execute d2a.exe now, you should
again see a small dialog box with a scrollbar.
But this time, the thumb will be at the 5000
Hz position. At this stage in d2a.c, we’ve
started to flesh out DlgProc(). In line 064,
a “switch” statement has been added. Now,
if a “case”, in the statement block from line
065 to line 199, matches the “message” in
the switch statement, the code following that
“case” will be executed. Most importantly,
“case WM_SYSCOMMAND:”, on line
184, now provides for proper program
termination.
Step 5: Update D2A.C, Stage 3
Copy make2.bat, d2a.rc, and d2a.c from
...\D2A\stage2 to ...\D2A\stage3. This time,
add code from Figure 5 to d2a.c starting with
line 086 and ending with line 133. Doing this
will complete the lengthy initialization going
on in “case WM_INITDIALOG:” (line 067).
Run d2a.exe this time and you should see the
same thing as in stage 2.
Step 6: Update D2A.C, Stage 4
Copy make2.bat, d2a.rc, and d2a.c from
...\D2A\stage3 to ...\D2A\stage4. This time
we need to add two functions below line
203 shown in Figure 3. To do this, add the
code from Figure 6 to d2a.c starting with
line 204 and ending with ine 230. Run d2a.
exe now, and again you’ll see the same as
REM Filename:
Make.bat, version 1.0
REM Description:
Compiles .res resource file from .rc resource script.
REM
Then, compiles .c source file and links it using the specified options
REM
NOTE: To make .exe file from source other than d2a.c, change “d2a” in
REM
lines 9 & 10 to new “name”. Name must match for both .rc and .c files.
REM Programmer:
Richard F. Crow, N2SPI, August 29, 2007
echo on
cls
C:\dm849c\dm\bin\rcc d2a.rc -32
C:\dm849c\dm\bin\dmc d2a.c winmm.lib comctl32.lib -mn -WA -L/RC -L/SUBSYSTEM:WINDOWS:4.0
pause
Figure 1: Make2.bat for d2a.rc and d2a.c
4
The AMSAT Journal  September/October 2007  www.amsat.org
//001) Filename:
//002) Description:
//003)
//004)
//005) Programmer:
d2a.rc, version 1.0
Resource script to make d2a.res resource file
that goes with d2a.c, a pgm that generates sine
wave samples xmitted thru sound card D/A output
Richard F. Crow, N2SPI, August 29, 2007
#define IDC_TEXT1 1000
#define IDC_TEXT2 1001
#define IDC_SCROLL 1002
//007)(this
//008)(must
//009)(must
//010)(must
#include<windows.h>
line reserved)
match same #define in d2a.c)
match same #define in d2a.c)
match same #define in d2a.c)
//012)(preprocr inserts text in”.h”files..
//013)...so pgm gets needed’C’definitions)
d2a_resources DIALOG DISCARDABLE 100, 100, 200, 50
STYLE WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION TEXT(“D2A,v1.0: Pgm to Output Signal Samples”)
FONT 8, TEXT(“MS Sans Serif”)
BEGIN
CTEXT TEXT(“Sine Wave Generator”),
CTEXT TEXT(“Frequency: 5000 Hz”),
SCROLLBAR
END
IDC_TEXT1, 60,
IDC_TEXT2, 60,
IDC_SCROLL, 10,
4,
14,
28,
80,
80,
180,
8
8
12
Figure 2: d2a.rc
in stage 2. At this point, FillBufferWithSi
neWaveData() now creates the sine wave
samples. In line 210, “iFrequency” (cycles/
second) is multiplied by TWO_PI to get
frequency (radians/second), which is divided
by SAMPLE_RATE (samples/second) to
get “wtIncrement” (radians/sample). In line
214, note that “wt” is an angle (radians)
which increases steadily in time at the rate of
TWO_PI radians per cycle of “iFrequency”.
Thus, “sin(wt)” produces sinusoidal values
from +1 to -1 representing a sinewave with
the frequency “iFrequency”. These values
are then multiplied by 32767 to produce
full range, signed 16 bit sine wave signal
samples. If this is still confusing to you,
see:
http://en.wikipedia.org/wiki/Radian
see:
http://en.wikipedia.org/wiki/Angular_
frequency
and see:
http://en.wikipedia.org/wiki/Trigonometry.
As for DigitalSignalProcessing(), it will be
replaced with the code for a FIR lowpass
filter in Part 3.
to hear what you have wrought. If nothing
is heard, click “Start > All Programs >
Accessories > Entertainment > Volume
Control”. Next, click “Options”, click
“Properties”, select “Playback”, be sure
“Volume Control” and “Wave” check boxes
are checked, and click “OK”. Then, drag
the sliders for the “Volume Control” and
“Wave” up to max, drag “Balance” controls
to their center, and make sure “Mute” is
not checked. As for d2a.c now, “case MM_
WOM_OPEN:” initializes both OutBuffer1
and OutBuffer2 with 5000 Hz sine wave data
to produce the initial burst of sound. After
that, when either OutBuffer empties, “case
MM_WOM_DONE:” refills the appropriate
OutBuffer with more sine wave samples.
Note that two OutBuffers are used for
continuity of output such that one can service
the D/A, while the other is being filled with
new data. If you have a slow PC, or one that
is running many programs simultaneously,
you may have to increase BUFFER_SIZE to,
say, 8192 or more. This sine wave generator
was developed from concepts presented in
chapter 22 of reference [3].
Step 7: Finish D2A.C, Stage 5
And finally, copy make2.bat, d2a.rc, and
d2a.c from ...\D2A\stage4 to ...\D2A\stage5.
Add the code from Figure 7 to d2a.c starting
with line 134 and ending with line 182. This
completes d2a.c. Compile and run d2a.exe
References
[1] Petzold, Charles (2000), Code,
Microsoft Press, Redmond, WA, ISBN
0-7356-0505-X, ISBN 0-7356-1131-9
(paperback).
[2] Zhang, Tony, and Southmayd, John
(2000), Sams Teach Yourself C In 24
Hours, Sams Publishing, Indianapolis,
IN, ISBN 0-672-31861-X.
[3] Petzold, Charles (1999), Programming
Windows, Fifth Edition, Microsoft
Press, Redmond, WA, ISBN 1-57231995-X.
[4] S mith, S teven W. ( 1997) , T he
Scientist and Engineer’s Guide To
Digital Signal Processing, California
Technical Publishing, P.O. Box 502407,
San Diego, CA 92150-2407, “www.
DSPguide.com”, ISBN 0-9660176-33.
amsat.org
Exciting new look!
Great new features!
Easy to maneuver!
Try it, you’ll love it !
The AMSAT Journal  September/October 2007  www.amsat.org
5
//001) Filename:
//002) Description:
//003)
//004)
//005) Programmer:
d2a.c, version 1.0
A DSP program for Windows XP that generates sine
wave signal samples and outputs them through the
sound card D/A output to speakers, if attached
Richard F. Crow, N2SPI, August 29, 2007
#define IDC_TEXT1 1000
#define IDC_TEXT2 1001
#define IDC_SCROLL 1002
//007)(this
//008)(must
//009)(must
//010)(must
#include<windows.h>
#include<math.h>
//012)(preprocr inserts text in”.h”files..
//013)...so pgm gets needed’C’definitions)
#define
#define
#define
#define
#define
#define
SAMPLE_RATE
TWO_PI
MIN_FREQ
INIT_FREQ
MAX_FREQ
BUFFER_SIZE
44100
6.28318
20
5000
20000
4096
line reserved)
match same #define in d2a.rc)
match same #define in d2a.rc)
match same #define in d2a.rc)
//015)(“#define” alias NAME for...
//016) ...text to replace it)
//017)
//018)
//019)(in units of Hz)
//020)(size in bytes is 2*BUFFER_SIZE )
TCHAR szTemplateName[] = TEXT( “d2a_resources” );
//declare globals
TCHAR szErrorMsg1[] = TEXT( “Allocation of memory failed!” );
TCHAR szErrorMsg2[] = TEXT( “Opening WaveOut device failed!” );
BOOL CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM); //func prototypes
void FillBufferWithSineWaveData(PSHORT, int );
void DigitalSignalProcessing(PSHORT, PSHORT );
//============================== Line 029 ===========================
int WINAPI WinMain(
//030)WinMain() starts this app:
HINSTANCE
hInstance,
//031)=a number that identifies
HINSTANCE
hPrevInstance,
//032).this occurance of this app
PSTR
szCmdLine,
//033)=ptr @ cmd line text,if any
int
iCmdShow )
//034)=init’l window(maxmzd?,etc)
{
DialogBox(
//036)create a Dialog Box:
hInstance,
//037)=”instance”handl 4 this pgm
szTemplateName,
//038)=ptr @ Dialog Box resources
NULL,
//039)=handl of ownr window(none)
DlgProc );
//040)=ptr @ Dialog Box procedure
return 0;
}
//042) ( end of “WinMain()” )
//============================== Line 043 ===========================
BOOL CALLBACK DlgProc(
//044)DlgProc handles WinOS msgs:
HWND
hwnd,
//045)=handl of window get’g msgs
UINT
message,
//046)=number that is the message
WPARAM
wParam,
//047)=1st numbr with msg details
LPARAM
lParam)
//048)=2nd numbr with msg details
{
//050)======== Line 050 =========
if( (message == WM_SYSCOMMAND) && (wParam == SC_CLOSE) ) // (temp)
EndDialog( hwnd, 0 );
// (temporary C statements)
//*** NOTE: other lines between 050 and 200 will be typed in later
//200)======== Line 200 =========
return FALSE;
//201)IfGetHere, msgs not handled
}
//202) ( end of “DlgProc()” )
//============================== Line 203 ===========================
Figure 3: d2a.c, stage1
6
The AMSAT Journal  September/October 2007  www.amsat.org
static int
static HWND
static PWAVEHDR
static PWAVEHDR
static PSHORT
static PSHORT
static PSHORT
static HWAVEOUT
static WAVEFORMATEX
MMRESULT
TCHAR
iScrollPos;
hwndScroll;
pWavOutHdr1;
pWavOutHdr2;
pOutBuffer1;
pOutBuffer2;
pDataBuffer;
hWaveOut;
waveformat;
mmResult;
szBuf[60];
//050)======== Line 050 =========
//051)declare local variables:
//052)=integer(for scrollbar pos)
//053)=a handle(ID # for scrlbar)
//054)=pointer to (address of)...
//055) ...a Wave Hdr structure
//056)=pointer @ 1st WaveOut buff
//057)=pointer @ 2nd WaveOut buff
//058)=pointer @ another buffer
//059)=handl(ID# for WaveOut dev)
//060)=a WavFormat data structure
//061)=numbr(return from mm_func)
//062)=buffr(for setting up text)
switch(message)
//064)(if match between”messag”..
{
//065)..& “case...”,execute case)
case WM_INITDIALOG:
//067)msg when dialog box created
iScrollPos = INIT_FREQ;
hwndScroll = GetDlgItem( hwnd, IDC_SCROLL );
SetScrollRange( hwndScroll, SB_CTL, MIN_FREQ, MAX_FREQ, FALSE);
SetScrollPos(
hwndScroll, SB_CTL, INIT_FREQ, TRUE );
pWavOutHdr1
pWavOutHdr2
pOutBuffer1
pOutBuffer2
pDataBuffer
=
=
=
=
=
(PWAVEHDR)malloc( sizeof(WAVEHDR) );
(PWAVEHDR)malloc( sizeof(WAVEHDR) );
(PSHORT)malloc( 2 * BUFFER_SIZE );
(PSHORT)malloc( 2 * BUFFER_SIZE );
(PSHORT)malloc( 2 * BUFFER_SIZE );
//allocate.
//...memory
//(InBytes)
if(!pWavOutHdr1 | !pWavOutHdr2 | !pOutBuffer1 | !pOutBuffer2
!pDataBuffer)
{
//081) if find errors...
MessageBox( NULL, szErrorMsg1, NULL, MB_OK );
return FALSE;
}
//085)======== Line 085 =========
// *** NOTE: lines between 085 and 183 will be typed in later ***
case WM_SYSCOMMAND:
if( wParam == SC_CLOSE )
//183)======== Line 183 =========
//184)msg when WinOS system event
//185)if get sub-message that...
//186)...dialog is closing, then
//187)stop WaveOut device output
waveOutReset( hWaveOut );
waveOutClose( hWaveOut );
waveOutUnprepareHeader(hWaveOut, pWavOutHdr1, sizeof(WAVEHDR));
waveOutUnprepareHeader(hWaveOut, pWavOutHdr2, sizeof(WAVEHDR));
free( pWavOutHdr1 );
free( pWavOutHdr2 );
//192)release allocated memory
free( pOutBuffer1 );
//193)release allocated memory
free( pOutBuffer2 );
//194)release allocated memory
free( pDataBuffer );
//195)release allocated memory
EndDialog( hwnd, 0 );
//196)end dialog
return TRUE;
//197)quit”DlgProc” & return TRUE
}
//199)(end of”switch(message){}”)
//200)======== Line 200 =========
Figure 4: d2a.c, stage2
The AMSAT Journal  September/October 2007  www.amsat.org
7
waveformat.wFormatTag = WAVE_FORMAT_PCM;
waveformat.nChannels = 1;
waveformat.nSamplesPerSec = SAMPLE_RATE;
waveformat.nAvgBytesPerSec = SAMPLE_RATE;
waveformat.nBlockAlign = 2;
waveformat.wBitsPerSample = 16;
waveformat.cbSize = 0;
mmResult = waveOutOpen(
&hWaveOut,
WAVE_MAPPER,
&waveformat,
(DWORD)hwnd,
0,
CALLBACK_WINDOW );
//085)======== Line 085 =========
// set wave audio preferences
//094)open waveOut audio device:
//095)=handl of dev actualy opend
//096)=select dev(type) to open
//097)=pointer to “waveformat”
//098)=inThisCase,dialg box handl
//099)=(function arg not used)
//100)=flags for opening the dev
if(mmResult != MMSYSERR_NOERROR) // if find errors...
{
MessageBox( NULL, szErrorMsg2, NULL, MB_OK );
return FALSE;
}
pWavOutHdr1->lpData = (PSTR)pOutBuffer1;
pWavOutHdr1->dwBufferLength = ( 2 * BUFFER_SIZE );
pWavOutHdr1->dwBytesRecorded = 0;
pWavOutHdr1->dwUser = 0;
pWavOutHdr1->dwFlags = 0;
pWavOutHdr1->dwLoops = 1;
pWavOutHdr1->lpNext = NULL;
pWavOutHdr1->reserved = 0;
// setup “WaveOutHdr1” to..
// ...describe “OutBuffer1”
waveOutPrepareHeader(
hWaveOut,
pWavOutHdr1,
sizeof(WAVEHDR) );
//117)prepare “pWavOutHdr1”:
//118)=handle of waveOut device
//119)=pointer to headr structure
//120)=size of header structure
pWavOutHdr2->lpData = (PSTR)pOutBuffer2;
pWavOutHdr2->dwBufferLength = ( 2 * BUFFER_SIZE );
pWavOutHdr2->dwBytesRecorded = 0;
pWavOutHdr2->dwUser = 0;
pWavOutHdr2->dwFlags = 0;
pWavOutHdr2->dwLoops = 1;
pWavOutHdr2->lpNext = NULL;
pWavOutHdr2->reserved = 0;
// setup “WaveOutHdr2” to..
// ...describe “OutBuffer2”
waveOutPrepareHeader(hWaveOut, pWavOutHdr2, sizeof(WAVEHDR) );
return FALSE;
//133)======== Line 133 =========
// *** NOTE: lines between 133 and 183 will be typed in later ***
//183)======== Line 183 =========
Figure 5: d2a.c, stage3
8
The AMSAT Journal  September/October 2007  www.amsat.org
//============================== Line 203 ===========================
void FillBufferWithSineWaveData( PSHORT pBuffer, int iFrequency )
{
static float wt;
//206)(note: “wt” = “Omega_t”...
float wtIncrement;
//207)..i.e.(radianFreq * time) )
int i;
//208)variable 4 indexing pBuffer
wtIncrement = ( (TWO_PI * (float)iFrequency) / SAMPLE_RATE );
for(i=0 ; i<BUFFER_SIZE ; i++)
//212)loop to fill buff w/samples
{
pBuffer[i] = (SHORT)( 32767.0 * sin( (double)wt ) );
wt = wt + wtIncrement;
//216)add radians/sampl increment
if( wt > TWO_PI )
//217)if wt exceeds TWO_PI...
wt = wt - TWO_PI;
//218)...subtract TWO_PI from it
}
}
//220( end of “FillBuffer...()” )
//============================== Line 221 ===========================
void DigitalSignalProcessing( PSHORT pIn, PSHORT pOut )
{
int i;
//224)(note: AtThisTime,this is..
//225)..a dummy “pass-thru” func)
for(i=0 ; i<BUFFER_SIZE ; i++)
pOut[i] = pIn[i];
//227)copy “InBuf” to “OutBuf”
}
//228)(end of “DigitalSig...()” )
//============================== Line 229 ===========================
// end of program
Figure 6: d2a.c, stage 4
Apogee View - continued from page 3.
AMSAT Institute
The AMSAT Institute is an opportunity
for teachers and others to spend some time
every summer at the AMSAT lab and the
nearby University of Maryland Eastern
Shore (UMES) campus. This project is in
its infancy but the university and the Hawk
Institute for Space Sciences (HISS), with
whom we share the lab space, are signed
up, in principal, to participate. The idea is to
teach teachers so they can carry back to their
students around the world the experiences
they gain from spending time with us. There
would be a balance of both classroom and lab
instruction provided by AMSAT, UMES and
HISS staff. I hope we can get accreditation
for Continuing Education Units (CEUs) and
funding to offset the costs for the teachers as
well as AMSAT.
Summary
AMSAT has spent the past few years
preparing itself to implement the missions
of the future. This year will focus on
measurable results in all areas of the
organization. AMSAT needs the support of
our membership, including your feedback,
your donations and your participation in
AMSAT activities.
73,
Richard M. Hambly, W2GPS
AMSAT President
VE4MX - SK
AMSAT notes with sadness the passing of Fred Zeltins, VE4MX, AMSAT member
number 32722. Fred was a long time member of AMSAT and generously bequeathed
$1000 to AMSAT from his estate. Fred will be missed and AMSAT offers our most sincere
condolences to Fred’s family and friends.
If you think you can assist with development
of this project, creation of the curriculum,
staffing the Institute, teaching courses or in
any way, please contact AMSAT’s Executive
VP Lee McLamb, KU4OS, or his team
member, Director of Education Paul Shuch,
N6TX. You can e-mail them at their call sign
at amsat.org.
The AMSAT Journal  September/October 2007  www.amsat.org
9
//133)======== Line 133 =========
case MM_WOM_OPEN:
//134)msg when WaveOut dev opened
FillBufferWithSineWaveData( pOutBuffer1, INIT_FREQ );
waveOutWrite( hWaveOut, pWavOutHdr1, sizeof(WAVEHDR) );
FillBufferWithSineWaveData( pOutBuffer2, INIT_FREQ );
waveOutWrite( hWaveOut, pWavOutHdr2, sizeof(WAVEHDR) );
return TRUE;
case MM_WOM_DONE:
//141)msg when an”OutBuf”is empty
if( ((PWAVEHDR)lParam)->lpData == (PSTR)pOutBuffer1 )
{
//if”OutBuf1”empty...
FillBufferWithSineWaveData( pDataBuffer, iScrollPos );
DigitalSignalProcessing( pDataBuffer, pOutBuffer1 );
waveOutWrite( hWaveOut, pWavOutHdr1, sizeof(WAVEHDR) );
}
else
//148)else,”OutBuf2” empty, so...
{
//149) ...refill it & send it out
FillBufferWithSineWaveData( pDataBuffer, iScrollPos );
DigitalSignalProcessing( pDataBuffer, pOutBuffer2 );
waveOutWrite( hWaveOut, pWavOutHdr2, sizeof(WAVEHDR) );
}
return TRUE;
case WM_HSCROLL:
switch(LOWORD(wParam))
//156)msg when scrollbar is moved
//157)(“TypeOfMovement”in wParam)
//158)(note:”Freq” = “ScrollPos”)
// 4 left scrlbar arrow button
// decrement “ScrollPos” by 1
case SB_LINELEFT:
iScrollPos = iScrollPos - 1;
break;
case SB_LINERIGHT:
// 4 right scrlbar arrowbutton
iScrollPos = iScrollPos + 1;
// increment “ScrollPos” by 1
break;
case SB_PAGELEFT:
// for paging scrollbar left
iScrollPos = iScrollPos - 20;
// decrement “ScrollPos” by 20
break;
case SB_PAGERIGHT:
// for paging scrollbar right
iScrollPos = iScrollPos + 20;
// increment “ScrollPos” by 20
break;
case SB_THUMBPOSITION:
// 4 dragging scrollbar”thumb”
iScrollPos = HIWORD(wParam);
// update when mouse released
break;
}
if( iScrollPos < MIN_FREQ )
//175)if “pos” below MIN_FREQ...
iScrollPos = MIN_FREQ;
//176) ...jam it to MIN_FREQ
if( iScrollPos > MAX_FREQ )
//177)if “pos” above MAX_FREQ...
iScrollPos = MAX_FREQ;
SetScrollPos( hwndScroll, SB_CTL, iScrollPos, TRUE );
wsprintf( szBuf, TEXT(“ Frequency: %5d Hz “), iScrollPos );
SetDlgItemText( hwnd, IDC_TEXT2, szBuf );
return TRUE;
//183)======== Line 183 =========
Figure 7: d2a.c, stage 5
10
The AMSAT Journal  September/October 2007  www.amsat.org
DSP for Beginners
Learn DSP By Programming Your PC, Part 3
by Richard F. Crow, N2SPI, n2spi@amsat.org
Copyright 2007 by Richard F. Crow
Introduction
In Part 1, a C programming environment
was established and additional reading
was suggested. In Part 2, a C program was
created which produced sine wave signal
samples outputted through the sound card.
In this part, we’ll build on Parts 1 and 2
to create the software for a convolutionbased, 3 kHz FIR low pass filter, which
we’ll insert between the sine wave samples
and the sound card output. Listening to
the sound card’s speakers, we’ll vary the
frequency of the sine wave samples to
test the filter. And finally, DSP in general
and convolution-based filters in particular,
will be discussed. Before continuing, it’s
strongly recommended you read Part 1 in
the July/August 2007 AMSAT Journal, and
then Part 2 in the September/October 2007
AMSAT Journal. I assume you’d like to see
the filter working right away, so I’ll focus on
writing software first.
Step 1: Make a New Folder, Copy
Files
Right now, boot up your PC, open a DOS
window, and navigate to “c:\dm849c\dm\
bin”. Next, use the DOS command “md”
to make a new subdirectory (folder) named
“LPF1”. Then, copy the files make2.bat, d2a.
rc, and d2a.c from c:\dm849c\dm\bin\D2A\
stage5 into c:\dm849c\dm\bin\LPF1 and
rename them make3.bat, lpf1.rc, and lpf1.c
respectively. Now, edit make3.bat to replace
“make2.bat” in line 001 with “make3.bat”,
“d2a.rc” in line 009 with “lpf1.rc”, and “d2a.
c” in line 010 with “lpf1.c”. Also, delete the
comments in line 004 and 005. When done,
make3.bat in ...\bin\LPF1 should look like
Figure 1.
Next, edit lpf1.rc to replace all 8 occurrences
of “d2a”, in lines 001, 002, 003, 008, 009,
010, 015, and 019, with “lpf1”. Also, edit the
caption text in line 019 as shown in Figure
2. When done, lpf1.rc in ...\bin\LPF1 should
look like Figure 2.
And finally, edit lpf1.c to replace all 5
occurrences of “d2a”, in lines 001, 008, 009,
010, and 022, with “lpf1”. Also, update the
“Description:” in lines 002, 003, and 004.
When done, the first 22 lines of lpf1.c should
look like Figure 3. Now run make3.bat just
to make sure everything still compiles OK.
Then run lpf1.exe to make sure it works just
like d2a.exe did before.
Step 2: Create Filter Software
Navigate to “c:\dm849c\dm\bin\LPF1” to
edit lpf1.c. First delete everything in lpf1.
c between lines 221 and the end of the
program. Then, create the filter software by
typing the code in Figure 4 (both pages of it)
into lpf1.c starting with line 222 and ending
with line 340. Note that I unrolled several
software loops into inline code to make
it easy to see how this convolution-based
algorithm works. What it’s doing is explained
in the section on demystifying convolutionbased filters below. Also, note that we’re
“multiplying” and “accumulating”, over and
over, starting in line 273. Thus, you can see
why a fast MAC (multiply and accumulate)
instruction is so important to DSP.
When done, save lpf1.c in “...\bin\LPF1”,
run make3.bat, and execute lpf1.exe. Again,
you should see the familiar dialog box and
hear the initial 5000 Hz burst. The initial
burst isn’t processed through the 3 KHz
low pass filter, so it should be just as loud as
before. After that, however, sine wave data
is passed through the filter, so you should
notice a drop in sound level. Experiment
with different frequencies to see which pass
unaltered, and which do not.
Step 3: Download a Filter Tool
Now that you have DSP filter software,
you’ll probably want to experiment with
it. Essentially, the array h[ ] is a sampled
version of the filter’s impulse response which
you “computer-convolve” with the sampled
input signal, pIn[ ], to produce the sampled,
filtered output, pOut[ ]. In DSP lingo, the
h[ ] array is called the “filter kernel”, its
number of elements is the number of “taps”,
and the elements, taken individually, are
“filter coefficients”. Generally speaking, the
more taps, the better the filter performance,
and the slower the software runs. To create
a different filter, say, a 3 kHz high pass
or 10 kHz low pass you don’t change the
filter algorithm, you change the kernel. If
you know how, you can generate your own
kernels. But until then, I recommend you
acquire a filter tool.
The filter tool I’ve selected is ScopeFIR.
To download a free, trial version, go to
“http://www.iowegian.com/download/
loadfir.htm” and download “ScopeFIR411.
zip”. After unzipping it, locate “ScopeFIR.
exe” and execute it. When you’re asked
to “Create or Open a ScopeFIR Project”,
select “Windowed Sinc” for now. Then, for
example, if you wanted to recreate the filter
kernel used here, enter 44100 Hz for the
“Sampling Frequency “, 31 for the “Number
of Taps”, and select Blackman-Harris-4
for the “Window Type”. Next, select Low
pass for the “Filter Type”, 3000 Hz for the
“First Corner (Frequency)”, and press the
“Design” button. After that, right click inside
the filter-coefficient-window (to the right),
click “Select Filter and Data Format...”, set
“View Coefficients:” to Inphase, and click
“Text:Hex” to get the filter coefficients
in the hexadecimal format used here. In
REM Filename:
Make3.bat, version 1.0
REM Description: Compiles .res resource file from .rc resource script.
REM
Then, compiles .c source file and links it using the specified options
REM Programmer:
Richard F. Crow, N2SPI, August 29, 2007
echo on
cls
C:\dm849c\dm\bin\rcc lpf1.rc -32
C:\dm849c\dm\bin\dmc lpf1.c winmm.lib comctl32.lib -mn -WA -L/RC -L/SUBSYSTEM:WINDOWS:4.0
pause
Figure 1: Make3.bat for lpf1.rc and lpf1.c
4
The AMSAT Journal  November/December 2007  www.amsat.org
//001) Filename:
//002) Description:
//003)
//004)
//005) Programmer:
lpf1.rc, version 1.0
Resource script to make lpf1.res resource file
that goes with lpf1.c, a Windows XP program that
implements a 3 KHz FIR lowpass filter (LPF)
Richard F. Crow, N2SPI, August 29, 2007
#define IDC_TEXT1
#define IDC_TEXT2
#define IDC_SCROLL 1002
1000
1001
//007)(this
//008)(must
//009)(must
//010)(must
#include<windows.h>
line reserved)
match same #define in lpf1.c)
match same #define in lpf1.c)
match same #define in lpf1.c)
//012)(preprocr inserts text in”.h”files..
//013)...so pgm gets needed’C’definitions)
lpf1_resources DIALOG DISCARDABLE 100, 100, 200, 50
STYLE WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION TEXT(“LPF1, v1.0: Implements a 3 KHz FIR LPF”)
FONT 8, TEXT(“MS Sans Serif”)
BEGIN
END
CTEXT TEXT(“Sine Wave Generator”),
CTEXT TEXT(“Frequency: 5000 Hz”),
SCROLLBAR
IDC_TEXT1, 60, 4, 80, 8
IDC_TEXT2, 60, 14, 80, 8
IDC_SCROLL, 10, 28, 180, 12
Figure 2: lpf1.rc
this trial version, you can’t copy or save
coefficients, so you’ll have to copy them by
hand. To do more, first read all the chapters
in reference [4] up to and including chapter
16, “Windowed-Sinc Filters”. Then, follow
the ScopeFIR tutorial and read the ScopeFIR
documentation.
DSP Demystified
So, what is DSP? Well, that’s too big a
question right now. Instead, let’s break it
into smaller pieces. Consider that at the
heart of a DSP system is some sort of digital
signal processor. So, what is a digital signal
processor? Well, most of you are familiar
with an ordinary four-function calculator
that adds, subtracts, multiplies and divides.
So, one colorful definition is: a digital signal
processor is a four-function calculator on
steroids. In other words, it’s a math engine.
A very, very fast math engine directed by a
stored program.
How can a math engine process analog
audio and radio signals? Well, for roughly
a hundred years now, while some were
developing analog circuits, such as amplifiers,
filters, modulators and demodulators, other
scientists and engineers were developing
mathematical models for this technology.
Eventually, there were not only obvious math
models for amplification and attenuation, but
more complex ones for filtering, modulation
and demodulation, to name a few. Now,
input signals could be represented as an
ordered sequence of numbers, which could
be mathematically manipulated by a model
of some process, so as to produce another
sequence of numbers, an output signal.
Not too long after digital computers were
invented, engineers could process signals
digitally. But those old computers were huge,
power-hungry and slow. It took hours, if
not days, to process a few minutes worth of
signal. It’s only recently, as digital computers
where reduced to the size of a chip and their
speed was increased by orders of magnitude,
that real-time digital signal processing in
everyday products was made possible.
So let’s try this out. Let’s connect an analog
signal to a digital signal processor’s I/O
(input/output) pin, programmed as an input,
and monitor another I/O pin, programmed as
an output, and see what happens. ...Nothing!
Maybe our signal is too weak. Let’s amplify
it and try again. ...Nothing. No matter how
much we amplify the signal, nothing (useful)
happens. What’s the problem??!!
Before we can answer that, we need to review
the nature of analog signals. Generally
speaking, all signals in this world start out
as analog, such as from a microphone or
antenna, and end up as analog, such as to
a speaker or antenna. One waveform that
embodies the analog concept is the sine
wave. Right now, picture a continuous,
analog sine wave voltage at some reasonable
frequency plotted on an XY graph. The Xaxis represents the time variable, in units such
as microseconds, and the Y-axis represents
the corresponding magnitude, in units such
as millivolts. This is the proverbial time
domain representation of a signal, which
is epitomized by an instrument known as
the oscilloscope. (If you have any difficulty
understanding the time domain concept,
beg, borrow or buy an oscilloscope and
learn how to use it. In a sentence, anything
in working order with “Tektronix” on it will
do. If nothing else, it’ll pay for itself when
it displays signals coming out of your sound
card beyond your range of hearing.)
The analog signal is a continuous signal, an
infinitely continuous signal. By infinitely
continuous, I mean that if you locate any
two time points on the X axis, say, points
t1 and t3 that correspond to time points in
the sine wave, I can show you another time
point, t2, that lies between these two points,
which is also a time point in the sine wave.
Furthermore, I can go on to show you two
more time points, one between t1 and t2,
and another between t2 and t3, and so forth,
and so forth, ad infinitum. Not only is the
time dimension infinitely continuous, the
The AMSAT Journal  November/December 2007  www.amsat.org
5
//001) Filename:
//002) Description:
//003)
//004)
//005) Programmer:
lpf1.c, version 1.0
A DSP program for Windows XP that generates sine
wave samples and outputs them through a 3 KHz
FIR lowpass filter (LPF) to sound card output
Richard F. Crow, N2SPI, August 29, 2007
#define
#define
#define
1000
1001
1002
IDC_TEXT1
IDC_TEXT2
IDC_SCROLL
//007)(this
//008)(must
//009)(must
//010)(must
line reserved)
match same #define in lpf1.rc)
match same #define in lpf1.rc)
match same #define in lpf1.rc)
#include<windows.h>
#include<math.h>
//012)(preprocr inserts text in”.h”files..
//013)...so pgm gets needed’C’definitions)
#define
#define
#define
#define
#define
#define
//015)(“#define” alias NAME for...
//016)
...text to replace it)
//017)
//018)
//019)(in units of Hz)
//020)(size in bytes is 2*BUFFER_SIZE )
SAMPLE_RATE
44100
TWO_PI
6.28318
MIN_FREQ
20
INIT_FREQ 5000
MAX_FREQ
20000
BUFFER_SIZE
4096
TCHAR szTemplateName[] = TEXT( “lpf1_resources” ); //declare globals
//** Lines 023 to 221 that follow are identical to those in d2a.c **
Figure 3: The first 22 lines of lpf1.c
magnitude dimension is infinitely continuous
as well.
So, back to the problem of the analog signal
connected to the digital signal processor. As
it turns out, there are several problems. The
first problem is the infinitely continuous
nature of the analog signal’s time dimension.
If we’re to process a signal time-point by
time-point, this implies that the digital signal
processor has to process infinite points in
finite time. Well, sorry, but there ain’t no
digital processor in the known universe that
can do that. The solution to this problem is
called “sampling”. First, we must sample
the analog signal to transform infinite timepoints into finite time-points. By sampling,
I mean we’ll take a snapshot of the signal’s
instantaneous magnitude at regular time
intervals, say, every 22.7 microseconds for
example. The device that does this sampling
is called an A/D (Analog to Digital) converter.
Note that sampling creates distortion as the
sampled version only approximates the
original analog signal. Specifically, sampling
of an arbitrary analog signal results in loss
of signal content above a certain frequency
limit. However, we can reduce this distortion
to insignificance if we can make the interval
between samples sufficiently short. In other
words, for a typical DSP system, the more
samples per second, the higher the DSP
system’s frequency response.
The second problem, similar to the first,
is due to the analog signal’s infinitely
6
continuous magnitude. Similarly, there
ain’t no digital computer that can represent
an infinite number of voltage or current
points with the finite number of binary bits
in the “word length” for a given number
representation scheme. The solution to this
is called “quantization”. We must quantize
the analog signal to transform an infinite
number of possible voltage or current
values into a finite number of values.
The device that performs quantization is,
again, the A/D converter. By quantization,
I mean an A/D will take a some range, say
0 to 1 volts, divide it uniformly into a finite
number of sub-ranges, and then at sampling
time, decide which sub-range most closely
matches the instantaneous magnitude of an
incoming signal. For example, a 3-bit A/D
would take a 0 to 1 volt range and divide it
up into 8 sub-ranges. In this case, sub-range
level_0 would cover from 0.000v to 0.124v,
sub-range level_1 from 0.125v to 0.249v,
and so forth until the last, sub-range level_7,
would cover 0.875v to 0.999v (virtually 1
volt). Then, if an incoming signal rose and
fell in a triangle waveform from 0 to 0.51
to 0 volts over nine sample periods, this 3
bit A/D would classify it as the sequence:
level_0, level_1, level_2, level_3, level_4,
level_3, level_2, level_1, and level_0. Note
that quantization introduces another type
of distortion as it further approximates the
original signal. This distortion shows up
mainly as additional background noise in
the quantized signal. Although we can’t
The AMSAT Journal  November/December 2007  www.amsat.org
eliminate this distortion, we can reduce it to
insignificance if we can use an A/D with a
sufficient number of bits of resolution.
The third, and final problem is that digital
processors, including DSP processors,
understand binary numbers, not multiple
voltage levels, quantized or not. To solve this
problem we need to encode the quantized
voltage levels into corresponding binary
numbers. In my view, this is not covered
well in the DSP literature. Some texts lump
this process in with quantization. Others
don’t mention it at all. Again, the device
that performs this encoding is the A/D
converter. By “encode”, I mean quantization
level level_0 is assigned the binary number
0, level_1 is assigned binary 1, level_2
binary 2, and so forth until all sequential
quantization levels are assigned sequential
binary numbers. If negative voltages are
quantized, level_-1 is assigned binary
number -1, level_-2 is assigned -2, and so
forth. In the end, we have a continuous range
of binary numbers that are proportional to
the magnitude of the input signal.
Now we’ve got a ball game! To summarize,
a DSP system needs an A/D converter to
sample, quantize, and encode an analog
signal at its input so as to create an ordered
sequence of binary numbers that the DSP
processor can deal with. These binary
numbers are known as input samples.
Generally, what is inputted to a DSP
algorithm are these input samples.
//============================== Line 221 ===========================
void DigitalSignalProcessing( PSHORT pIn, PSHORT pOut )
{
// Creates 31 tap, Windowed-Sinc, convolution-based 3 KHz LPF
// (This version uses fixed-point, integer arithmetic for speed)
//226)declare local variables:
int
i;
//227)=index var 4 pIn/pOut bufrs
LONG
Acc[ BUFFER_SIZE + 30 ];
//228)=accumulator, pIn responses
static LONG LeftOver[ 30 ];
//229)=buffr 4 left over Acc data
SHORT
h[31]=
//230)=array,3KHz LPF ImpulseResp
{
0x0000, 0x0000, 0xFFFE, 0xFFF7, 0xFFE5, 0xFFC8, 0xFFAE, 0xFFBE,
0x003F, 0x0184, 0x03CB, 0x0711, 0x0AF3, 0x0EB4, 0x1172,
0x1273,
0x1172, 0x0EB4, 0x0AF3, 0x0711, 0x03CB, 0x0184, 0x003F,
0xFFBE, 0xFFAE, 0xFFC8, 0xFFE5, 0xFFF7, 0xFFFE, 0x0000, 0x0000
};
Acc[ 0]
Acc[ 1]
Acc[ 2]
Acc[ 3]
Acc[ 4]
Acc[ 5]
Acc[ 6]
Acc[ 7]
Acc[ 8]
Acc[ 9]
Acc[10]
Acc[11]
Acc[12]
Acc[13]
Acc[14]
Acc[15]
Acc[16]
Acc[17]
Acc[18]
Acc[19]
Acc[20]
Acc[21]
Acc[22]
Acc[23]
Acc[24]
Acc[25]
Acc[26]
Acc[27]
Acc[28]
Acc[29]
Acc[30]
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
LeftOver[ 0];
LeftOver[ 1];
LeftOver[ 2];
LeftOver[ 3];
LeftOver[ 4];
LeftOver[ 5];
LeftOver[ 6];
LeftOver[ 7];
LeftOver[ 8];
LeftOver[ 9];
LeftOver[10];
LeftOver[11];
LeftOver[12];
LeftOver[13];
LeftOver[14];
LeftOver[15];
LeftOver[16];
LeftOver[17];
LeftOver[18];
LeftOver[19];
LeftOver[20];
LeftOver[21];
LeftOver[22];
LeftOver[23];
LeftOver[24];
LeftOver[25];
LeftOver[26];
LeftOver[27];
LeftOver[28];
LeftOver[29];
0;
//239)init Acc with Acc data...
//240)...left over from last time
//242)(Note: there may be a “pop”
//243)sound when start up because
//244)the LeftOver[] buffer is
//245)not initialized)
//269)will use this too, so init
for(i=0 ; i<BUFFER_SIZE ; i++)
{
Acc[i]
= Acc[i] + pIn[i]
Acc[i+ 1] = Acc[i+ 1] + pIn[i]
Acc[i+ 2] = Acc[i+ 2] + pIn[i]
Acc[i+ 3] = Acc[i+ 3] + pIn[i]
Acc[i+ 4] = Acc[i+ 4] + pIn[i]
Acc[i+ 5] = Acc[i+ 5] + pIn[i]
Acc[i+ 6] = Acc[i+ 6] + pIn[i]
*
*
*
*
*
*
*
h[
h[
h[
h[
h[
h[
h[
//271)loop thru pIn & calc scaled
//272)resp from each scaled pIn[]
0]; //scaled RespSampl@h[0]
1];
//scaled RespSampl@h[1]
2];
//scaled RespSampl@h[2]
3];
// etc. ...
4];
5];
6];
Figure 4: DigitalSignalProcessing()
The AMSAT Journal  November/December 2007  www.amsat.org
7
}
Acc[i+ 7]
Acc[i+ 8]
Acc[i+ 9]
Acc[i+10]
Acc[i+11]
Acc[i+12]
Acc[i+13]
Acc[i+14]
Acc[i+15]
Acc[i+16]
Acc[i+17]
Acc[i+18]
Acc[i+19]
Acc[i+20]
Acc[i+21]
Acc[i+22]
Acc[i+23]
Acc[i+24]
Acc[i+25]
Acc[i+26]
Acc[i+27]
Acc[i+28]
Acc[i+29]
Acc[i+30]
Acc[i+31]
pOut[i]
LeftOver[ 0]
LeftOver[ 1]
LeftOver[ 2]
LeftOver[ 3]
LeftOver[ 4]
LeftOver[ 5]
LeftOver[ 6]
LeftOver[ 7]
LeftOver[ 8]
LeftOver[ 9]
LeftOver[10]
LeftOver[11]
LeftOver[12]
LeftOver[13]
LeftOver[14]
LeftOver[15]
LeftOver[16]
LeftOver[17]
LeftOver[18]
LeftOver[19]
LeftOver[20]
LeftOver[21]
LeftOver[22]
LeftOver[23]
LeftOver[24]
LeftOver[25]
LeftOver[26]
LeftOver[27]
LeftOver[28]
LeftOver[29]
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
Acc[i+ 7] + pIn[i]
Acc[i+ 8] + pIn[i]
Acc[i+ 9] + pIn[i]
Acc[i+10] + pIn[i]
Acc[i+11] + pIn[i]
Acc[i+12] + pIn[i]
Acc[i+13] + pIn[i]
Acc[i+14] + pIn[i]
Acc[i+15] + pIn[i]
Acc[i+16] + pIn[i]
Acc[i+17] + pIn[i]
Acc[i+18] + pIn[i]
Acc[i+19] + pIn[i]
Acc[i+20] + pIn[i]
Acc[i+21] + pIn[i]
Acc[i+22] + pIn[i]
Acc[i+23] + pIn[i]
Acc[i+24] + pIn[i]
Acc[i+25] + pIn[i]
Acc[i+26] + pIn[i]
Acc[i+27] + pIn[i]
Acc[i+28] + pIn[i]
Acc[i+29] + pIn[i]
Acc[i+30] + pIn[i]
0;
Acc[i] / 32768;
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
Acc[BUFFER_SIZE
];
+ 1];
+ 2];
+ 3];
+ 4];
+ 5];
+ 6];
+ 7];
+ 8];
+ 9];
+10];
+11];
+12];
+13];
+14];
+15];
+16];
+17];
+18];
+19];
+20];
+21];
+22];
+23];
+24];
+25];
+26];
+27];
+28];
+29];
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
h[ 7];
h[ 8];
h[ 9];
h[10];
h[11];
h[12];
h[13];
h[14];
h[15];
h[16];
h[17];
h[18];
h[19];
h[20];
h[21];
h[22];
h[23];
h[24];
h[25];
h[26];
h[27];
h[28];
h[29];
h[30];
//304)will use next time, so init
//305)Acc[i], for current “i”,...
//306)..will not be referenced...
//307)...again, so harvest it
//data for these Accumulator
//locations, Acc[...], has
//been generated but are not
//a part of the current
//pOut[i], so save this data
//in the LeftOver[...] buffer
//and add to next set of
//input data (next pIn[])
}
//338)(end of “DigitalSig...()” )
//============================== Line 339 ===========================
// end of program
Figure 4, continued: DigitalSignalProcessing()
8
The AMSAT Journal  November/December 2007  www.amsat.org
selected. Then, a sampled version
of its impulse response, h[ ], was
obtained from ScopeFIR. And finally,
software was coded which computerconvolves input samples, whatever
they may be, with h[ ] to produce
the corresponding, low pass-filtered
output samples. What remains, is to
understand the key concepts “linear
system”, “time-invariant”, “impulse
response”, and “convolution”.
Figure 5: A 3 KHz RLC Lowpass Filter
Similarly, the output from a DSP algorithm
is generally another sequence, or train, of
binary numbers known as output samples.
Just as there is an A/D on the front end
of a DSP system, there is a corresponding
device at the back end called a D/A (Digital
to Analog) converter. After the digital signal
processor executes the desired algorithm
to transform input samples into output
samples, the D/A converter translates the
output samples back to an analog signal.
It’s this analog signal you hear on your PC’s
speakers. You can learn more about the A/D
converter (ADC) and the D/A converter
(DAC) in chapter 3, “ADC and DAC”, of
reference [4].
Convolution-Based Filters
Demystified
I can state the secret behind the algorithm
for convolution-based DSP filters in one
sentence. That is, for any time-invariant
linear system, if the system’s impulse
response is known, the system’s output
signal can be determined for any input signal
by convolving its input with the system’s
impulse response. For the low pass filter
software here, a linear system known as a
Windowed-Sinc 3 kHz low pass filter was
“Time invariant” means that the
system’s response to any particular
input signal will be the same no matter when
it occurs, at the present moment or at any
future moment.
A “linear system” is a system that’s
homogeneous and additive. A system is
homogeneous if a change in the amplitude
of its input signal results in a proportional
change in amplitude of its output signal. In
other words, if the amplitude of the input
doubles, the amplitude of the output must
double, and if the input is cut in half, the
output must be cut in half also. Among other
things, this means the system ceases to be
linear to the extent it “flat tops” its output
signal, creating harmonic distortion, such as
occurs when you turn up your stereo amplifier
too loud. Continuing, a system is additive if
its overall response to an input made from
two or more individual signals summed
together, is identical to, the summation of the
system’s separate responses to each of these
signals individually. Among other things,
this means the system ceases to be linear to
the extent components of its input signal can
interact to create entirely new signals and
responses somewhere between its input and
output, such as in the case of intermodulation
distortion. For another presentation of linear
Figure 6: Impulse Response of the 3 KHz RLC Lowpass Filter to a
10v, 10 us Virtual Impulse
Lower Trace, Channel 1:
Input Impulse, 10 volts/division
Upper Trace, Channel 2:
Impulse Response, 0.05 volts/division
Time base:
100 microseconds/division
systems, see chapter 5, “Linear Systems”, in
reference [4].
The “impulse response” concept is usually
presented in highly abstract terms. A more
down-to-earth approach will be used here.
Consider the following experiment. First,
construct a 3 kHz RLC low pass filter
circuit according to Figure 5. Then, rig
up a common 555 timer IC (Radio Shack
#276-1723) as an oscillator running at, say,
500 Hz. Next, rig up a second 555 timer to
output a 10 volt, 320 us (microsecond) wide
pulse. Use the first 555 to trigger the second
555 so it puts out a steady stream of pulses.
Now, think of the way a submarine sends out
sonar pings to probe its underwater world.
So, ping the input of the filter by connecting
it to the output of the second 555 timer and
observe the filter’s output response with an
oscilloscope. To determine when the second
555 timer is generating a suitable impulse
ping, cut its pulse width in half to 160 us,
then in half again to 80 us, and so forth.
The second 555 timer will be generating a
“virtual impulse” when its pulse width can
be cut in half, but the basic shape of the
filter’s response remains unchanged. Figure
6 shows the impulse response of the 3 kHz
low pass filter to a virtual impulse of 10v at
10 us wide.
Next, keeping the width at 10 us, crank up
the power supply powering the 555 timers to
increase the amplitude of the virtual impulse
to 15 volts. Now, the filter’s response is
the same as for the 10v pulse, except its
amplitude is 1.5 times higher. See Figure
7. Note how the amplitude of the response
varies in proportion to the amplitude of the
impulse, provided the pulse width is kept
constant. This strongly implies this circuit
is homogeneous.
Figure 7: Impulse Response of the 3 KHz RLC Lowpass Filter to a
15v, 10 us Virtual Impulse
Lower Trace, Channel 1:
Input Impulse, 20 volts/division
Upper Trace, Channel 2:
Impulse Response, 0.05 volts/division
Time base:
100 microseconds/division
The AMSAT Journal  November/December 2007  www.amsat.org
9
Then, use another 555 timer to add a second
10v, 10 us impulse to the original 10v, 10
us impulse. Make the added impulse occur
200 us after the original. Now, ping the filter
with this double impulse. See Figure 8. The
overall response appears to be same as the
the summation of the filter’s individual
responses to each impulse by itself. In
other words, the response appears to be
one impulse response, as before, added to
another impulse response occurring 200
us later. This strongly implies this circuit
is additive. Since the 3 KHz RLC low
pass filter appears to be homogeneous and
additive, it’s a linear system. What we’ve
done is to find, and investigate, the impulse
response of this linear system.
Generally, the aforementioned has useful
consequences for any signal processing
scheme that can be classified as a linear,
time-invariant system. First, it implies
we can decompose the input signal into a
series, or train, of scaled impulses. Next,
knowing the system’s impulse response,
we can individually calculate each scaled
impulse response for each of these scaled
impulses. And finally, we can align all the
scaled impulse responses in time and sum
them together to find the overall response
of the system.
This is the strategy of the DSP filter algorithm
implemented in DigitalSignalProcessing
(). First, by the systematic incrementing
of index variable “i” in line 271, each
successive input sample, pIn[i], is selected
one at a time. In other words, the input signal
is viewed as a train of scaled impulses. Next,
in lines 273 through 303, with the term ...
pIn[i] * h[...], the scaled impulse response
is calculated for the selected scaled impulse,
pIn[i]. Or more exactly, a sampled version of
this scaled impulse response is calculated, all
31 samples of it. Then, with the term Acc[i
+ ...] = Acc[i + ...] + ..., this scaled impulse
response is summed, sample by sample, with
previous impulse responses already summed
together in the accumulator, Acc[ ]. Now,
consider the following carefully:
The first input sample occurs at the timepoint denoted by i = 0. Its impulse response
starts at the same time (i = 0), continues
for 30 more time-points, and is summed
with the accumulator at locations Acc[0]
through Acc[30]. Then, the second input
sample occurs at i = 1. Its impulse response
starts at the same time (i = 1), continues for
30 more time-points, but it’s summed with
the accumulator at locations Acc[1] through
Acc[31]. First, note how the second impulse
response occurs one time unit, or sample
interval, later that the first, and in this manner,
how the first response overlaps the second.
Now, note how this is accounted for by first
accessing Acc[0] to Acc[30] for the first
impulse response, and then shifting by one
time unit within the accumulator to access
Acc[1] to Acc[31] for the second impulse
response. This one time unit, or sample
interval, time shift is repeated over and over
for each successive impulse response as it
is summed with previous responses. In this
way, the current impulse response is properly
time-aligned with previous responses as it is
being summed with them.
Finally, in line 305, note that Acc[i], for the
current i, will never be referenced again
as i will soon advance to i + 1, and what’s
more, the accumulation of impulse response
samples in Acc[i] is
complete, making Acc[i]
now an element in the
overall response of the
system. But before Acc[i]
is outputted, note that
Acc[i] may be too large
due to the accumulation
process. So first, Acc[i]
is normalized by
dividing it by 32768,
and then it’s outputted to
pOut[i], which assembles
the system’s overall
response.
Figure 8: Impulse Response of the 3 KHz RLC Low pass Filter to
Two 10v, 10 us Virtual Impulses Spaced 200 us Apart
Lower Trace, Channel 1:
Input Impulses, 10 volts/division
Upper Trace, Channel 2:
Impulse Response, 0.05 volts/division
Time base:
100 microseconds/division
10
“Convolution” is
implemented on digital
computers by process
described above. I call
The AMSAT Journal  November/December 2007  www.amsat.org
this process “computer-convolution” because
digital computers don’t do real convolution.
Real convolution is similar in concept,
however, except it deals with continuous,
analog signals. But, as we’ve seen, digital
computers choke on analog signals. So,
digital signal processors approximate
convolution. For another presentation of
what convolution is and how it works, read
Chapter 5, “Linear Systems”, then Chapter
6, “Convolution”, and then Chapter 7,
“Properties of Convolution”, in reference
[4].
And finally, because a digital signal
processor is a math engine, and mathematics
manipulates idealized concepts and
abstractions oblivious to the imperfections
and limitations of real-world circuit reality,
we can perform serious magic with DSP. In a
nutshell, this is why DSP is so superior to the
signal processing results of analog electronic
circuits. For example, note that DSP filter
kernels look symmetrical around their
largest peak amplitude, whereas real-world
impulse responses do not. That is, a realworld impulse response looks something
like the right side only of a symmetrical DSP
impulse response. This characteristic gives
DSP filters a desirable property known as
linear phase, something analog electronic
filters will never have.
References
[1] Petzold, Charles (2000), Code,
Microsoft Press, Redmond, WA, ISBN
0-7356-0505-X, ISBN 0-7356-1131-9
(paperback)
[2] Zhang, Tony, and Southmayd, John
(2000), Sams Teach Yourself C In 24
Hours, Sams Publishing, Indianapolis,
IN, ISBN 0-672-31861-X
[3] Petzold, Charles (1999), Programming
Windows, Fifth Edition, Microsoft
Press, Redmond, WA, ISBN 1-57231995-X
[4] S mith, S teven W. ( 1997) , T he
Scientist and Engineer’s Guide To
Digital Signal Processing, California
Technical Publishing, P.O. Box 502407,
San Diego, CA 92150-2407, “www.
DSPguide.com”, ISBN 0-9660176-33
Download