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