Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

DTMF generation and decoding on a PIC 12F

Status
Not open for further replies.

Mr RB

Well-Known Member
Hi, I noticed recently that there had been some discussion of using DTMF and people have said the specialised DTMF chips are hard to obtain etc.

I recently created a system to generate very precise dual sinewave DTMF on any PIC that has a PWM module;

**broken link removed**

https://www.romanblack.com/SG/SG_tutorial.htm#DTMF

---------------------------------------------------------

Also over the last couple of days I put together a new algorithm for doing DTMF decoding even on the small slow PICs (I have included a working example on a PIC12F675).

This uses a combination of some software filtering to extract reliable periods, comparing periods and analysing the period patterns generated.

I made a simple spectrum (period) analyser to decipher the DTMF data;

**broken link removed**

And a new system to very easily detect period patterns in the DTMF data;

**broken link removed**

These 2 systems are quite open ended and can be used for other frequencies too and my PIC DTMF decoder can also decode the other telephone call signals and dialtones etc.

There's lots of data on my web page incuding C source code for the PIC 12F675 DTMF decoder;
https://www.romanblack.com/DTMF/DTMF_alg.htm

Please feel free to use these systems for very cheap DTMF generation and decoding. :)
 
Last edited:
That is some impressive work; and very clean & creative solutions!

Although I won't claim to fully understand the encoder, the mention of 'variable overloading' piqued my curiosity. The normal 'C' way of doing that is either using a union, or a pointer e.g. ((char*)&longVar)[2], which compiles to the same code and means you don't have to manually allocate variables.
 
Roman,

Your DTMF encoder appears to an example of DDS (direct digital synthesis), much like some DDS-PWM examples I've shared on this forum.

May I ask if you're claiming invention of this "zero error BDA (Binary Divided Accumulator) algorithm"? I ask because you've really presented nothing more than a 22-bit DDS phase accumulator and standard algorithms for determining DDS frequency, DDS resolution, and DDS output frequency tuning word (phase offset). The DDS-PWM method and algorithms have been around and in use quite some time now.

Kind regards, Mike
 
Last edited:
Thanks dougy83. I was aware of the pointer way of referencing the overloaded variable. :) But for PIC C-code with all its particular RAM issues like bank switching and limited procesor speed etc etc I think absolute variable overloading for this task is still the best option for best speed in fixed frequency generation tasks.

Mike- I did actually create that DTMF encoder from scratch, out of a dual separate bresenham system and then I refined it down to the very fastest simplest process I could come up with. By the time I had written it up and went to put it on the net I found a couple of similar DDS systems, but their code always used a 32 bit divide to derive the output which is significantly slower than my version as compilers usually default to a Div32/32 library for that task.

When I put it up on the next I added a comment then in my text that "a similar system had previously been used as one form of DDS..." etc. I thought my use of the overloaded varibale in the PIC application to give instantaneous access the to sine table (rather than use the slow 32bit division as all the DDS examples I could find) made it remarkable enough to warrant the use of the text as I had written it when I created the system (and save me the effort of re-writing it becuase writing the text always takes me longer than devising algorithms). ;)

However I greatly respect your input on this, if you think the PIC variable overloading optimisation I added had been done before or is simply without significance I can re-write it to state "Here is a dual sinewave generator using a standard DDS system I have optimised a bit for PICs" or similar title.

Please advise.
 
Last edited:
very cool, but my suppliers carry lots of the CM8870 for my dtmf decode needs , if thats what you were after....
 
Mike- I did actually create that DTMF encoder from scratch, out of a dual separate bresenham system and then I refined it down to the very fastest simplest process I could come up with. By the time I had written it up and went to put it on the net I found a couple of similar DDS systems, but their code always used a 32 bit divide to derive the output which is significantly slower than my version as compilers usually default to a Div32/32 library for that task.
Roman, you did a very nice job with a very nice write-up, as usual. We all benefit greatly by your creations and your experiments and by the time you spend writing up and sharing that knowledge. Your DDS (direct digital synthesis) or NCO (numerically controlled oscillator) algorithm is very similar to others which have been around a long time. I even posted one example last November. But your write-ups are much better and much more comprehensive compared to mere examples.

When I put it up on the net I added a comment then in my text that "a similar system had previously been used as one form of DDS..." etc. I thought my use of the overloaded varibale in the PIC application to give instantaneous access the to sine table (rather than use the slow 32bit division as all the DDS examples I could find) made it remarkable enough to warrant the use of the text as I had written it when I created the system (and save me the effort of re-writing it becuase writing the text always takes me longer than devising algorithms). ;)
There are plenty of ways to get a compiler to collect the b16..b23 byte directly from a "long" without using costly 32bit division. Even a ccpr1l = sine[accum>>16] instruction will generate a single word assembly instruction that extracts the b16..b23 byte directly from the phase accumulator variable in my BoostC compiler. The "zero error BDA (Binary Divided Accumulator) algorithm" claim just seems a little bloated or pumped up to me since it's really just your variation of existing works.

However I greatly respect your input on this, if you think the PIC variable overloading optimisation I added had been done before or is simply without significance I can re-write it to state "Here is a dual sinewave generator using a standard DDS system I have optimised a bit for PICs" or similar title.
I guess I'm surprised that you think it's significant enough to brag about. Using the ul(x) portion of a #define ul(x) (char)((x)>>16) macro in your pwm assignment statement would provide the same level of optimization without having to force an absolute address for the phase accumulator variable. The example I posted last November has the same level of optimization.

Anyway, thank you for providing a very nice DTMF example program and I'm delighted that you're excited about your recent discoveries and that you decided to share them in your own humble manner (grin).

Cheerful regards, Mike
 
Last edited:
Smack accepted! :D I have re-written that DTMF generator part of my page in a much more "humble" fashion and modified the source code commenting etc.

As a penance I improved the generator code by adding the correct amount of "twist" so it now generates the high DTMF tone 28% more amplitude than the low tone, to match proper specs for telephony use.

I hadn't seen your CTCSS generating DDS code Mike, in fact I just had to go look up what CTCSS was having no clue. Nice neat code. :)

There are plenty of ways to get a compiler to collect the b16..b23 byte directly from a "long" without using costly 32bit division. Even a ccpr1l = sine[accum>>16] instruction will generate a single word assembly instruction that extracts the b16..b23 byte directly from the phase accumulator variable in my BoostC compiler. ...

Interesting. Did you actually check your compilers assember output? In my compiler (with the higher optimisations on) using >>16 referencing normally results in the 32bit variable being placed in the compiler stack and indirectly addressed to get the 2nd byte. That's still more code than a direct absolute addressing of a RAM location. With the compiler optimisations turned off the >>16 results in, well lots of right shifts. It does depend a lot on which PIC the compiler is generating code for and whether it prefers the extra overhead of stack use with 32bit vars for that PIC or not. Generally it does handle 32bit var opertions with the compiler stack. But using the absolute addressing forces the compiler to directly access PIC RAM and forces the variable to be in the prime RAM on bank0 to avoid compiler bank switching.

I guess I'm surprised that you think it's significant enough to brag about.

It was much more a matter of lazyness than bragging. I had originally searched for PIC sine DTMF genrators with no luck and then came up with that system in about 20 mins work and wrote up some guff to put it on my page to help others since there appeared to be no PIC sine DTMF genrators out there. Especially ones with very high freq accuracy. It was late in ym day and when I saw there were some similar sine generators I put the disclaimer on instead of bothering to re-write. My mistake, lesson learnt. ;)

Anyway we have focussed entirely on the DTMF generator which was a pretty trivial piece of work and very much the minor one of the two DTMF projects. Did you check out my DTMF decoder Mike? Decoding is a *much* harder process and took me a couple of solid evenings theorising, building spectrum analysers and special test equipment and actual testing in hardware. It allows DTMF decoding on extremely low power micros and the process of period filtering and pattern recognition offers much higher protection against false activation than the standard system of detecting significant presence of two frequencies.

Doggy-
very cool, but my suppliers carry lots of the CM8870 for my dtmf decode needs , if thats what you were after....

I have 8870 chips in my parts stock Doggy. But there's a big difference between building a project with a specialty 18pin 8870 and specialty 3.58MHz xtal and then need 5 i/o lines to a PIC which will probably require an 18 pin PIC.

My Decoder allows the use of a single low cost 8pin PIC and any xtal you have, or adding DTMF to an already existing PIC circuit without having to add extra ICs. And it requires significantly less input hardware than a 8870. Actually if it only needed to decode a couple of tones I'm pretty sure it could fit in a tiny 6pin 10F series PIC and no xtal.
 
... Did you actually check your compilers assember output? In my compiler (with the higher optimisations on) using >>16 referencing normally results in the 32bit variable being placed in the compiler stack and indirectly addressed to get the 2nd byte. That's still more code than a direct absolute addressing of a RAM location. ...
The BoostC compiler generates a movf _accum+2,W instruction.

... Did you check out my DTMF decoder Mike? Decoding is a *much* harder process and took me a couple of solid evenings theorising, building spectrum analysers and special test equipment and actual testing in hardware. It allows DTMF decoding on extremely low power micros and the process of period filtering and pattern recognition offers much higher protection against false activation than the standard system of detecting significant presence of two frequencies.
I just gave it a quick once-over. A very interesting read about a very impressive brain exercise. I wish I had the time to give it the attention it deserves.

On another subject, may I propose an alternative or supplemental Zero Error 1 Second Timer method for your Zero Error web page? I believe it's smaller and faster than the current C examples on that page (it eliminates one 32 bit comparison each update interval and one 32 bit subtraction at the end of each period). Anyway, I'll post it here for you and other Forum members who may want to test it.

BoostC generates a btfss instruction followed by a goto instruction for the if(accum.31) statement and produces a bcf instruction for the accum.31 = 0 statement. I'm not sure how to duplicate that level of optimization on other compilers. Overload variables perhaps (grin)? Of course this is a non-issue for assembly language programs.

Cheerful regards, Mike

Code:
/********************************************************************
 *                                                                  *
 *  Project: McLaren Zero Error                                     *
 *   Source: McLaren_Zero_Error.c                                   *
 *   Author: Mike McLaren, K8LH                                     *
 *  (C)2010: Micro Application Consultants                          *
 *     Date: 15-Oct-10                                              *
 *  Revised: 06-Mar-11                                              *
 *                                                                  *
 *  Mike McLaren's Super Optimized 1 Second Zero Cumulative Error   *
 *  One Second Timebase Algorithm And Method Demo (for 12F683)      *
 *                                                                  *
 *      IDE: MPLAB 8.56 (tabs = 4)                                  *
 *     Lang: SourceBoost BoostC v7.01, Lite/Free version            *
 *                                                                  *
 ********************************************************************/

 #include <system.h>

 #pragma DATA _CONFIG, _MCLRE_OFF, _INTOSCIO, _WDT_OFF

 #pragma CLOCK_FREQ 8000000     // INTOSC

 //--< function prototypes >------------------------------------------
 //--< type definitions >---------------------------------------------
 //--< variables >----------------------------------------------------

  unsigned long accum = 0;    // 32-bit phase accumulator

/*********************************************************************
 *  Super Optimized Zero Error 1-Second Timer  (Mike McLaren, K8LH)  *
 *********************************************************************
 *  using 8.0 MHz INTOSC for this demo'
 *
 *     Tcy = 1 / 8000000 * 4 = 500.0 nsecs
 *    Fdds = 1 / (256 * Tcy) = 7812.5 Hz
 *
 *  frequency resolution using a 31 bit phase accumulator is
 *
 *    Fres = Fdds / 2^31
 *    Fres = 7812.5 / 2^31 = 0.00000363797880709171295166015625 Hz
 *
 *  phase offset (DDS tuning word) for zero error 1 second period
 *
 *   phase = Fout * (2^31 / Fdds)
 *         = 1-Hz * (2^31 / 7812.5)
 *         = 1-Hz * (274877.906944)
 *         = 274877.906944
 */
[COLOR=Blue][B]
 void interrupt()               // 256-cycles (7812.5-Hz)
 { intcon.T0IF = 0;             // clear TMR0 interrupt flag
   accum += 274878;             // add 1-Hz phase offset
   if(accum.31)                 // if bit-32 period overflow
   { accum.31 = 0;              // reset it for next period and
     nop();                     // do 1 second stuff here
   }
 }
[/B][/COLOR] 
 //--< main >---------------------------------------------------------

 void main()
 {
   cmcon0 = 7;                  // comparator off, digital I/O
   ansel = 0;                   // a2d module off, digital I/O
   trisio = 0b00111011;         // GP2 output, all others input

   option_reg = 0b10001000;     //
   intcon = 0b10100000;         // TMR0 interrupts (pre 1:1)

   while(1)                     // loop forever
   {
   }
 }
 
Last edited:
Nice one Mike. The code execution is estremely fast (as would be expected from removing the 32bit:32bit comparison).

I had never put a DDS 1 second generator up on the page although I know we discussed it previously, i think it was in the ZEZJ thread where we did some sinewave inverter stuff together?

There's a couple of small issues; 1. The math for deriving the constant is a little unpleasant and off putting for a beginner. With the other 1 second examples they can just input their xtal freq as a neat constant #define and it's quite intiutive.

May I suggest adding a #define calc at the top so the compiler itself can derive the constant to be added to the accumulator? I think that would be a valuable addition as the user never needs to know (or even understand) how the constant is derived.

Something like;
#define XTAL 8000000 // your xtal freq
#define INT_TMR_PRESCALE 1 // your TMR0 prescale, ie 1:1 (or use int period in instructions, ie; #define INT_PERIOD 256 ?)
#define ADD_VALUE (calc) // this is the auto-calculated value they add each int

The second issue is that *most* compilers won't support direct bit access on 32bit vars. That's very non-ANSI C. And of course without the direct bit access it's back to being a bresenham 32bit add + 32:32 compare but without the benefit of clean numbers. It would be good to have a simple pointer version or something beginners would understand that keeps the speed but doesn't require a compiler that can handle direct bit access.

Another minor issue is that it can't generate a perfect accumulated period like the bresenham can, although with 3.6 PPm error that is not really an issue, most xtals are 30 PPM error range or greater.

If you wanted to add the auto calc for the period and maybe consider a second pointer version, I would be proud to add the two versions to the 1 second page for people to use. :)
 
Status
Not open for further replies.

Latest threads

New Articles From Microcontroller Tips

Back
Top