Discussion
Forum
Home
Company Information
Information Request
Linux How-to Guides
ADSP 21xx Digital Signal
Processing Tutorials
SW Utilities
On-line Order Form
Aerospace Projects
Commercial Projects
Circuit Boards
Server Support
Have you found this site useful? Did we save you time? Did we cure your head-ache? Is your hair growing back now?
Please make a donation to help with maintenance.
|
Objective Real-Time Software on the ADSP21XX
Tone Generation
General
A common use for DSP is modem functions. There are of course two
aspects to this, namely modulation and demodulation. This article
deals with the former, being rather simpler as this isn't meant to be a
complex book, but rather a hands-on tutorial for a beginner or simply a
place where an experienced DSPer can pick up some examples, to avoid re-inventing
the wheel.
Continuous tones of varying frequency are commonly used in frequency
shift keying systems. Another common use is the ubiquitous telephone,
which uses a Dual Tone Multi Frequency (DTMF) modem to transmit dial keypad
information to the exchange. This chapter presents a simple way to
generate DTMF tones, but which can also be used to generate tones of arbitrary
frequency, using a sine wave lookup table.
FSK
A dual tone FSK modulator, only needs to generate a single tone at a time.
Usually the frequency of the tone is shifted in a phase continuous manner,
meaning simply that the new tone starts at the exact level and phase where
the previous one left off. Each tone is a data bit of information
and the bits need to be recovered in a synchronous fashion, usually with
a pair of matched filters, a threshold algorithm and a Phase Locked Loop
(PLL) for bit recovery.
DTMF
DTMF tones are not like FSK at all. DTMF consists of discrete pulses,
separated by a defined gap of no tone. Each pulse consists of a mix
of two tones, played simultaneously. This way, intersymbol interference
is a non-issue being suppressed in the time domain and since there are
a number of tones, each pulse can convey a distinct symbol/character, not
just a single bit.
Using 4 standardized tones, the DTMF system can generate 16 characters,
denoted as 1..9, *, 0, #, A..D. The A to D codes can be used as shift
codes to enlarge the code set to 60 characters if so desired, which yields
the same size code set as Baudot encoded Radio Teletype (RTTY).
Sizing and Timing
The DTMF specification requires tones and gaps to be a minimum of 35ms
in length. In practice, it is a good idea to use longer tones of
50ms, for added reliability. This does of course slow things down,
but DTMF is never to be used for high speed transmission, or to convey
large amounts of data, so speed doesn't matter much.
The level of the signal is also important. In North America, transmissions
are nominally at -10dBm, while it is 0dBm in the rest of the world, but
since we are only looking at the digital side of things, the line level
is not at issue here. All we need to do is have the loudest possible
signal, without causing numeric overflows. There are two PCM companding
laws in use. Mu law can handle 14 bits and A law 16 bits, so we'll
scale the mixed signal to 12 bits before compressing it. To avoid
losing precision when mixing, we'll shift the whole table left by 4 bits,
for simple 12.4 fixed point integer mathematics.
Sine Table Construction
The easiest way to generate a sine wave is to use a PC and a C compiler,
include the math.h header file and use sin(x), with x in radians.
This is not the worlds best sine wave and you are in effect at the mercy
of whoever wrote the library function, but it is usually pretty darn good.
You will however need to define the value of the constant Pi.
So for your convenience, here it is in table 1:
Table 1. The value of Pi to 1000 digits.
Pi = 3.
1415926535 8979323846 2643383279 5028841971 6939937510
5820974944 5923078164 0628620899 8628034825 3421170679
8214808651 3282306647 0938446095 5058223172 5359408128
4811174502 8410270193 8521105559 6446229489 5493038196
4428810975 6659334461 2847564823 3786783165 2712019091
4564856692 3460348610 4543266482 1339360726 0249141273
7245870066 0631558817 4881520920 9628292540 9171536436
7892590360 0113305305 4882046652 1384146951 9415116094
3305727036 5759591953 0921861173 8193261179 3105118548
0744623799 6274956735 1885752724 8912279381 8301194912
9833673362 4406566430 8602139494 6395224737 1907021798
6094370277 0539217176 2931767523 8467481846 7669405132
0005681271 4526356082 7785771342 7577896091 7363717872
1468440901 2249534301 4654958537 1050792279 6892589235
4201995611 2129021960 8640344181 5981362977 4771309960
5187072113 4999999837 2978049951 0597317328 1609631859
5024459455 3469083026 4252230825 3344685035 2619311881
7101000313 7838752886 5875332083 8142061717 7669147303
5982534904 2875546873 1159562863 8823537875 9375195778
1857780532 1712268066 1300192787 6611195909 2164201989
...
|
Hmmm, that should be enough for most normal purposes...
A Sine Table Generator
Example 1 is a simple C program to generate a sine lookup table in ANSI
C. I compiled it with the free compiler LCC-Win32.
A full sine cycle, sampled at 256 intervals, will provide a modulated
tone with very low distortion (about 0.03%), perfect for our DTMF modulator.
It is possible to save some memory by storing only a half or quarter cycle,
but that makes the code more complicated, so it is pretty much six of one
and half a dozen of the other. It is far easier to debug a table
than to debug code, so I therefore prefer the simple approach and usually
store a full cycle in ROM.
Example 1. Sine wave table generator.
/*********************************************************************
* Filename: sin.c
* Description: Sine wave table generator
*
* Copyright Aerospace Software Ltd., 1996
*
* Version Author
* -------------------------------------------------------------------
* Feb 96 Herman Oosthuysen
*********************************************************************/
#include "stdio.h"
#include "string.h"
#include "stdlib.h"
#include "conio.h"
#include "math.h"
#include "sin.h"
/*********************************************************************
* Name: main
* Description: sin(x) lookup table
* Constraints: none
*********************************************************************/
int main(void)
{
double PI = 3.14159265358979323846;
double sine[256];
double scale[256];
double tsine[64];
double x;
int i, j;
unsigned int s;
FILE *sinfile;
/*
* Math library sine table
*/
for (i = 0; i < 256; i++)
{
x = i * 2 * PI / 256;
sine[i] = sin(x);
}
/*
* scale for 12 bit signed PCM
* and shift left by 4
*/
for (i = 0; i < 256; i++)
{
scale[i] = sine[i] * 2047 * 16;
}
/*
* print the output to a file
*/
sinfile = fopen("sin.txt", "wb");
if (sinfile != NULL)
{
fprintf(sinfile, "Library sine table:\n");
for (i = 0; i < 256; i++)
{
fprintf(sinfile, "%1.10f\n", sine[i]);
}
fprintf(sinfile, "\n\nScaled sine table:\n");
fprintf(sinfile, "sine * 2047 * 16, for ADSP PM initializer\n");
i = 0;
while (i < 256)
{
fprintf(sinfile, " ");
for (j = 0; j < 8; j++)
{
s = (unsigned int)scale[i];
s &= 0x0000FFFF;
fprintf(sinfile, "0x%04X, ", s);
i++;
}
fprintf(sinfile, "\n");
}
fclose(sinfile);
}
return 1;
}
|
Tone Generation
Well, so how exactly does one do it? Look at it this way: We want
standard PCM out, sampled at 8kHz. We have 256 samples in our wave
table. So, if we would read one sample every PCM interrupt, then
the output wave will be 8kHz / 256 samples = 31.25Hz.
Somewhere else in the book I promissed not to do any complicated mathematics
and really, you can see from the above that to generate any audio frequency
you want above 31.25Hz, all you need to do is skip a few samples while
stepping through the table. For instance, if you take every other
sample, then you get a sine wave of 62.5Hz.
It is clear that a simple sample counter is OK for frequencies that
are exact multiples of 31.25Hz, but not so good for other frequencies.
The trick here, is to keep track of the sample count or more correctly
the phase angle, in a fixed point variable. For simplicity, I scale
the phase angle by 16 (4 bits) and then do a right shift of 4 bits before
doing the table lookup. This provides an accuracy of 1/16 sample
point for the cumulative phase angle, or 16 * 256 = 4096ths of a cycle,
which is pretty darn accurate and far more than good enough for most purposes.
This is simple 12.4 fixed point mathematics which any little processor
can handle efficiently.
Some people prefer to use a smaller table of 128 samples and then do
a linear interpolation, but it is far easier to use a larger table and
forget about interpolation. So that is what we'll do here.
As always, there is more than one way to skin a cat.
To generate a tone, take the step size and add it to a cumulative phase
angle, then perform a table lookup to get a PCM sample. The cumulative
phase angle has to be wrapped around when one reaches the end of the table,
so that it will start again at the beginning of the table.
Step Size
Using the above formula, I calculated step sizes for the four DTMF tones
and then put them in a pair of simple lookup tables (plus a few extra tones
for debugging or whatever you want). This allows one to easily find
the required step sizes, given the desired DTMF character. Function
dtmf_dial(), finds the step sizes and then set the play timer for the DTMF
generator.
DTMF Generation
The tone generator is suitable for multi channel operation as defined by
DTMF_CHAN_MAX. This allows us to use the same code to generate DTMF
tones for a whole bunch of phone lines.
The actual tone is generated by a routine called dtmf_play(), which
could be called at regular intervals from somewhere in your application
to fill up a buffer with a block of DTMF samples. Every time this
routine runs, it decrements the play timer, until it eventually becomes
zero.
Typically, one will either send telephone voice, or DTMF, so you need
to add a call to dtmf_play() somewhere in your voice processing handler
to generate one or more DTMF samples. Bear in mind that you also
need to zero the gaps between the tones, with a constant value (the last
one generated by dtmf_play()), so there is some work for you to do to use
this generator.
Sample Code
Example 2 is a multi channel DTMF modulator. For debugging, it is
best to play a single tone, e.g. 1kHz continuously, then look at it on
an oscilloscope. The tone should be clean, without discontinuities
or sudden phase changes. As soon as one tries to play two tones simultaneously,
the 'scope picture becomes muddled.
Example 2. Multi Channel DTMF Modulator
/*********************************************************************
* Filename: dtmf.dsp
* Description: Multi channel DTMF modulator
*
* Copyright Aerospace Software Ltd., 1996
*
* Version Author
* -------------------------------------------------------------------
* Feb 96 Herman Oosthuysen
*********************************************************************/
.MODULE DTMF;
#include "macro.h"
#define DTMF_CHAN_MAX 4
#define DTMF_CHAN_MASK (DTMF_CHAN_MAX - 1)
#define DTMF_TONE_SIZE 20
#if 1
#define DTMF_PLAY_TIME 400 /* 400 = 50ms, 600 = 75ms */
#else
#define DTMF_PLAY_TIME 4000 /* 4000 = 500ms for test and measuring */
#endif
#define DTMF_SYMBOL_TIME 800 /* 100ms symbol time */
#define DTMF_SIN_MASK 0x0FFF
#define DTMF_NUMSTR_LENGTH 32
#define DTMF_NUMSTR_MAX (DTMF_NUMSTR_LENGTH * DTMF_CHAN_MAX)
/*
* DTMF global variables
*/
.VAR/DM
dtmf_playtmr[DTMF_CHAN_MAX],
dtmf_step1[DTMF_CHAN_MAX],
dtmf_step2[DTMF_CHAN_MAX],
dtmf_phase1[DTMF_CHAN_MAX],
dtmf_phase2[DTMF_CHAN_MAX],
dtmf_strtmr[DTMF_CHAN_MAX],
dtmf_numstr[DTMF_NUMSTR_MAX],
dtmf_numptr[DTMF_CHAN_MAX];
/*
* DTMF lookup tables in PM
*/
.VAR/PM DTMF_STEP1_TBL[DTMF_TONE_SIZE];
.INIT DTMF_STEP1_TBL:
0x01e100, /* 0 = 941Hz */
0x016500, /* 1 = 697Hz */
0x016500, /* 2 = 697Hz */
0x016500, /* 3 = 697Hz */
0x018a00, /* 4 = 770Hz */
0x018a00, /* 5 = 770Hz */
0x018a00, /* 6 = 770Hz */
0x01b400, /* 7 = 852Hz */
0x01b400, /* 8 = 852Hz */
0x01b400, /* 9 = 852Hz */
0x01e100, /* * = 941Hz */
0x01e100, /* # = 941Hz */
0x016500, /* A = 697Hz */
0x018a00, /* B = 770Hz */
0x01b400, /* C = 852Hz */
0x01e100, /* D = 941Hz */
0x00b300, /* dial tone = 350Hz */
0x000000, /* toot = 0Hz */
0x000000, /* beep = 0Hz */
0x000000; /* tweet = 0Hz */
.VAR/PM DTMF_STEP2_TBL[DTMF_TONE_SIZE];
.INIT DTMF_STEP2_TBL:
0x02ac00, /* 0 = 1336Hz */
0x026b00, /* 1 = 1209Hz */
0x02ac00, /* 2 = 1336Hz */
0x02f400, /* 3 = 1477Hz */
0x026b00, /* 4 = 1209Hz */
0x02ac00, /* 5 = 1336Hz */
0x02f400, /* 6 = 1447Hz */
0x026b00, /* 7 = 1209Hz */
0x02ac00, /* 8 = 1336Hz */
0x02f400, /* 9 = 1447Hz */
0x026b00, /* * = 1209Hz */
0x02f400, /* # = 1477Hz */
0x034400, /* A = 1633Hz */
0x034400, /* B = 1633Hz */
0x034400, /* C = 1633Hz */
0x034400, /* D = 1633Hz */
0x00e100, /* dial tone = 440Hz */
0x010000, /* toot = 500Hz */
0x020000, /* beep = 1000Hz */
0x040000; /* tweet = 2000Hz */
.VAR/PM DTMF_SIN_TBL[256];
.INIT DTMF_SIN_TBL:
0x000000, 0x032300, 0x064700, 0x096900, 0x0C8A00, 0x0FA900, 0x12C500, 0x15DF00,
0x18F500, 0x1C0800, 0x1F1600, 0x221F00, 0x252300, 0x282100, 0x2B1900, 0x2E0B00,
0x30F500, 0x33D800, 0x36B300, 0x398500, 0x3C4F00, 0x3F0F00, 0x41C500, 0x447200,
0x471400, 0x49AA00, 0x4C3600, 0x4EB600, 0x512900, 0x539000, 0x55EA00, 0x583700,
0x5A7700, 0x5CA800, 0x5ECB00, 0x60E000, 0x62E500, 0x64DB00, 0x66C200, 0x689900,
0x6A6000, 0x6C1600, 0x6DBC00, 0x6F5100, 0x70D400, 0x724600, 0x73A700, 0x74F600,
0x763200, 0x775D00, 0x787500, 0x797A00, 0x7A6D00, 0x7B4D00, 0x7C1A00, 0x7CD400,
0x7D7A00, 0x7E0D00, 0x7E8D00, 0x7EF900, 0x7F5200, 0x7F9700, 0x7FC800, 0x7FE600,
0x7FF000, 0x7FE600, 0x7FC800, 0x7F9700, 0x7F5200, 0x7EF900, 0x7E8D00, 0x7E0D00,
0x7D7A00, 0x7CD400, 0x7C1A00, 0x7B4D00, 0x7A6D00, 0x797A00, 0x787500, 0x775D00,
0x763200, 0x74F600, 0x73A700, 0x724600, 0x70D400, 0x6F5100, 0x6DBC00, 0x6C1600,
0x6A6000, 0x689900, 0x66C200, 0x64DB00, 0x62E500, 0x60E000, 0x5ECB00, 0x5CA800,
0x5A7700, 0x583700, 0x55EA00, 0x539000, 0x512900, 0x4EB600, 0x4C3600, 0x49AA00,
0x471400, 0x447200, 0x41C500, 0x3F0F00, 0x3C4F00, 0x398500, 0x36B300, 0x33D800,
0x30F500, 0x2E0B00, 0x2B1900, 0x282100, 0x252300, 0x221F00, 0x1F1600, 0x1C0800,
0x18F500, 0x15DF00, 0x12C500, 0x0FA900, 0x0C8A00, 0x096900, 0x064700, 0x032300,
0x000000, 0xFCDD00, 0xF9B900, 0xF69700, 0xF37600, 0xF05700, 0xED3B00, 0xEA2100,
0xE70B00, 0xE3F800, 0xE0EA00, 0xDDE100, 0xDADD00, 0xD7DF00, 0xD4E700, 0xD1F500,
0xCF0B00, 0xCC2800, 0xC94D00, 0xC67B00, 0xC3B100, 0xC0F100, 0xBE3B00, 0xBB8E00,
0xB8EC00, 0xB65600, 0xB3CA00, 0xB14A00, 0xAED700, 0xAC7000, 0xAA1600, 0xA7C900,
0xA58900, 0xA35800, 0xA13500, 0x9F2000, 0x9D1B00, 0x9B2500, 0x993E00, 0x976700,
0x95A000, 0x93EA00, 0x924400, 0x90AF00, 0x8F2C00, 0x8DBA00, 0x8C5900, 0x8B0A00,
0x89CE00, 0x88A300, 0x878B00, 0x868600, 0x859300, 0x84B300, 0x83E600, 0x832C00,
0x828600, 0x81F300, 0x817300, 0x810700, 0x80AE00, 0x806900, 0x803800, 0x801A00,
0x801000, 0x801A00, 0x803800, 0x806900, 0x80AE00, 0x810700, 0x817300, 0x81F300,
0x828600, 0x832C00, 0x83E600, 0x84B300, 0x859300, 0x868600, 0x878B00, 0x88A300,
0x89CE00, 0x8B0A00, 0x8C5900, 0x8DBA00, 0x8F2C00, 0x90AF00, 0x924400, 0x93EA00,
0x95A000, 0x976700, 0x993E00, 0x9B2500, 0x9D1B00, 0x9F2000, 0xA13500, 0xA35800,
0xA58900, 0xA7C900, 0xAA1600, 0xAC7000, 0xAED700, 0xB14A00, 0xB3CA00, 0xB65600,
0xB8EC00, 0xBB8E00, 0xBE3B00, 0xC0F100, 0xC3B100, 0xC67B00, 0xC94D00, 0xCC2800,
0xCF0B00, 0xD1F500, 0xD4E700, 0xD7DF00, 0xDADD00, 0xDDE100, 0xE0EA00, 0xE3F800,
0xE70B00, 0xEA2100, 0xED3B00, 0xF05700, 0xF37600, 0xF69700, 0xF9B900, 0xFCDD00;
/*********************************************************************
* Name: dtmf_init
* Description: initialize the DTMF modulator
* Constraints: none
*********************************************************************/
dtmf_init:
MAC_ENTER
ar = 0;
cntr = DTMF_CHAN_MAX;
do dtmf_init_loop until ce;
MAC_WR_DM(0, ^dtmf_step1, ar)
MAC_WR_DM(0, ^dtmf_step2, ar)
MAC_WR_DM(0, ^dtmf_phase1, ar)
MAC_WR_DM(0, ^dtmf_phase2, ar)
MAC_WR_DM(0, ^dtmf_playtmr, ar)
ar = ar + 1;
dtmf_init_loop: nop;
MAC_EXIT
rts;
/*********************************************************************
* Name: dtmf_dial
* Description: lookup the step sizes for the two DTMF tones
* Constraints: ar = channel, ay1 = digit
*********************************************************************/
dtmf_dial:
MAC_ENTER
dis ints;
ay0 = DTMF_CHAN_MAX;
af = ar - ay0;
if gt jump dtmf_dial_exit;
ax0 = ar;
ax1 = DTMF_TONE_SIZE;
af = ax1 - ay1;
if lt jump dtmf_dial_exit;
/*
* if a tone is already playing then
* it will be disrupted
*/
MAC_RD_PM(ar, ^DTMF_STEP1_TBL, ay1)
MAC_WR_DM(ar, ^dtmf_step1, ax0)
MAC_RD_PM(ar, ^DTMF_STEP2_TBL, ay1)
MAC_WR_DM(ar, ^dtmf_step2, ax0)
MAC_WR_DM(0, ^dtmf_phase1, ax0)
MAC_WR_DM(0, ^dtmf_phase2, ax0)
ar = DTMF_PLAY_TIME;
MAC_WR_DM(ar, ^dtmf_playtmr, ax0)
dtmf_dial_exit:
ena ints;
MAC_EXIT
rts;
/*********************************************************************
* Name: dtmf_play
* Description: play the tones until the timer expires
* Constraints: ar = channel
*********************************************************************/
dtmf_play:
MAC_ENTER
ay0 = ar;
/*
* if playtimer > 0 then
* decrement playtimer
*/
MAC_RD_DM(ar, ^dtmf_playtmr, ay0)
ar = pass ar;
if eq jump dtmf_play_exit;
ar = ar - 1;
MAC_WR_DM(ar, ^dtmf_playtmr, ay0)
/*
* lookup two tone samples
* mix tones
* convert to 12 bit value
*/
MAC_RD_DM(ay1, ^dtmf_step1, ay0)
MAC_RD_DM(ar, ^dtmf_phase1, ay0)
call dtmf_tone;
ax1 = ar;
ar = pass af;
MAC_WR_DM(ar, ^dtmf_phase1, ay0)
MAC_RD_DM(ay1, ^dtmf_step2, ay0)
MAC_RD_DM(ar, ^dtmf_phase2, ay0)
call dtmf_tone;
ay1 = ar;
ar = pass af;
MAC_WR_DM(ar, ^dtmf_phase2, ay0)
ar = ax1 + ay1;
sr = ashift ar by -4 (lo);
ar = sr0;
/*
* Compress Output
* convert signed value to log value
*/
tx1 = ar;
nop; /* one clock cycle needed for conversion */
ar = tx1;
dtmf_play_exit:
MAC_EXIT
rts;
/*********************************************************************
* Name: dtmf_tone
* Description: DTMF tone generator
* Constraints: ar = step, ay1 = phase
* returns ar = tone, af = phase
*********************************************************************/
dtmf_tone:
MAC_QUICK_ENTER
/*
* calc new phase
* lookup tone sample in sine table
* drop level by 3dB
* (Alternatively one can change the table for a lower level)
*/
ar = ar + ay1;
ay1 = DTMF_SIN_MASK;
ar = ar AND ay1;
af = pass ar;
sr = lshift ar by -4 (lo);
MAC_RD_PM(ar, ^DTMF_SIN_TBL, sr0)
sr = ashift ar by -1 (lo);
ar = sr0;
MAC_QUICK_EXIT
rts;
.ENDMOD;
/********************************************************************/
|
Easy!
Demodulation is of course a different kettle of fish altogether, not
unlike unscrambling an egg...
Have fun,
Herman
|
|