Aerospace



Citadel Forums

Home

Company Information

Information Request

Linux How-to Guides

ADSP 21xx
Digital Signal Processing
Tutorials

SW Utilities

On-line Order Form

Server Support


bonk

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



Copyright © 1996-2008, Aerospace Software Ltd., GPL.