xuyongsos 发表于 2013-6-17 10:13

单片机IO口模拟DTMF信号的实践问题

小弟看了本论坛teddeng的关于单片机纯软件使用IO口模拟DTMF信号的分析后,动手实践,用一款单片机搭了个电阻网络电路来实现DTMF信号,发现了个问题。
我用65US的采样频率,144的表格,12M晶振,8051的12T单片机,6个IO口。
问题点,中断程序执行实践超过65US,代码如下,我做测试,只实现输出某个特定周期的一个正旋波形
void Timer1_Interrupt(void) interrupt3       //Timer1 65US
{

        TH1 = 0xFf;               
            TL1 = 0x2e;
        ucfreqH_ext +=ucfreqH_step;
        ucfreqL_ext +=ucfreqL_step;
        ucfreqH = (char)(((ucfreqH_ext+4)>>3)%144);
        ucfreqL = (char)(((ucfreqL_ext+4)>>3)%144);
        P1=uc_SinParam;
}
然后用示波器量波形,发现周期比表格理论的大了。查找问题,量某个IO口的波形图。发现最快的电压跳变实践是190US,远远超过65US的理论值。
网上有理论说,用普通的51能实现DTMF,不知道问题出在哪里了?求大神解惑

xuyongsos 发表于 2013-6-17 11:08

自己顶顶~
我把问题简单化下,不谈自己的实践,网上有个 “双龙DTMF演示程序的两点疑问,做过DTMF(软件PWM方法)的请进”的帖子,我是看了这个帖子去动手实践的,实例代码如下
#include <io8515v.h>
#include <macros.h>
#defineXtal       8000000          // 系统时钟频率
#defineprescaler1                // T1预分频系数
#defineN_samples128            // 在查找表中的样本数
#defineFck      Xtal/prescaler   // T1工作频率
#definedelaycyc   10               // 读取port C口延时循环数
#pragma interrupt_handler ISR_T1_Overflow:7
/*************************** 正弦表 *****************************
       样本表: 一个周期分成128个点,每点按7位进行量化
****************************************************************/
flash unsigned char auc_SinParam =
{64,67,70,73,76,79,82,85,88,91,94,96,99,102,104,106,109,111,113,115,117,
118,120,121,123,124,125,126,126,127,127,127,127,127,127,127,126,126,125,
124,123,121,120,118,117,115,113,111,109,106,104,102,99,96,94,91,88,85,82,
79,76,73,70,67,64,60,57,54,51,48,45,42,39,36,33,31,28,25,23,21,18,16,14,
12,10,9,7,6,4,3,2,1,1,0,0,0,0,0,0,0,1,1,2,3,4,6,7,9,10,12,14,16,18,21,23,
25,28,31,33,36,39,42,45,48,51,54,57,60};
//***************************x_SW*************************
//   x_SW 表(8倍): x_SW = ROUND(8*N_samples*f*510/Fck)
//************************************************************
const unsigned char auc_frequencyH = {
107,96,
87,79};
const unsigned char auc_frequencyL = {
61,56,
50,46};


//**************************全局变量 ****************************
unsigned char x_SWa = 0x00;               // 高频信号脉冲宽度
unsigned char x_SWb = 0x00;               // 低频信号脉冲宽度
unsigned intX_LUTaExt = 0;               
unsigned intX_LUTbExt = 0;            
unsigned intX_LUTa;                  
unsigned intX_LUTb;                     

/*****************************************************************
                  定时器溢出中断服务程序
******************************************************************/
void ISR_T1_Overflow (void)
{
X_LUTaExt += x_SWa;      
X_LUTbExt += x_SWb;
X_LUTa=(char)(((X_LUTaExt+4) >> 3)&(0x007F));
X_LUTb=(char)(((X_LUTbExt+4) >> 3)&(0x007F));
         // 计算 PWM 值: 高频值 + 3/4 低频值
OCR1A = (auc_SinParam + (auc_SinParam-(auc_SinParam>>2)));
}

/***********************************************************
                        初始化
***********************************************************/
void init (void)
{
MCUCR=0x00;
TIMSK= 0x80;                     // T1 溢出中断使能
TCCR1A = (1<<COM1A1)+(1<<PWM10);   // 不翻转、8位PWM
TCCR1B = (1<<CS10);                // 预分频系数为1、即CLK/1
DDRD   = (1 <<PD5);               // PD5 (OC1A)用作输出
_SEI();                              // 全局中断使能
}
/*********************************************************************
      为从PORT C口读取稳定的按键数据,所必须的延时程序(消抖延时)
*********************************************************************/
void Delay (void)
{
int i;
for (i = 0; i < delaycyc; i++) _NOP();
}

/********************************************************************
                            主程序
      从PORT C口读取按键数据(如:SL+ AVR实验板) ,来确定产生哪个
      高频(列)和低频(行)信号的混合信号,并且修正 x_SWa 和 x_SWb。
                   行-> PINC 高四位
                   列-> PINC 低四位
*********************************************************************/

void main (void)
{
unsigned char uc_Input;
unsigned char uc_Counter = 0;
init();
for(;;){
   // 高四位 - 行
    DDRC= 0x0F;         // 高四位输入、低四位输出
    PORTC = 0xF0;         // 高四位打开上位、低四位输出低电平
    uc_Counter = 0;
    Delay();                        // 延时等待 Port C 电平稳定
    uc_Input = PINC;                  // 读取 Port C
    do
    {
      if(!(uc_Input & 0x80))          // 检查MSB是否为低
      {
                                    // 取低音脉冲宽度并结束循环
      x_SWb = auc_frequencyL;
      uc_Counter = 4;
      }
      else
      {
      x_SWb = 0;                  // 没有频率调制要求
      }
      uc_Counter++;
      uc_Input = uc_Input << 1;       // 左移一位
    }
    while ((uc_Counter < 4));
    // 低四位 - 列
    DDRC= 0xF0;          // 高四位输出、低四位输入
    PORTC = 0x0F;          // 高四位输出低电平、低四位打开上拉
    uc_Counter = 0;
    Delay();               // 延时等待 Port C 电平稳定
    uc_Input = PINC;
    uc_Input = uc_Input << 4;   
    do
    {
      if(!(uc_Input & 0x80))    // 检查 MSB 是否为低
      {
                              
      x_SWa = auc_frequencyH;//取高音脉冲宽度并结束循环
      uc_Counter = 4;
      }
      else
      {
      x_SWa = 0;               
      }
      uc_Counter++;
      uc_Input = uc_Input<< 1;
    }
   while (uc_Counter < 4);
}
}
----------------------------------------------------------------------
我实践中的疑问点就一个,定时器溢出中断的时间是多长??????

coody 发表于 2013-6-17 12:05

完全没有问题,至少要用6个IO做R-2R的DAC,用类似DDS的方法来合成DTMF,成功率很高的。
软件解码DTMF都可以的。

xuyongsos 发表于 2013-6-17 12:22

我在实践中的疑问点是中断周期应该是多久?

xuyongsos 发表于 2013-6-18 10:07

求大神帮忙解惑,我自己顶住先

gx_huang 发表于 2013-6-18 12:38

中断周期要固定,进入中断到输出数据的时间也要固定,中断执行时间要小于中断周期。
可以第一次进中断,先计算下一次中断的输出值。
第二次进中断,先输出上次中断的值,再计算下次的输出值。

xuyongsos 发表于 2013-6-18 12:49

感谢gx_huang的回复。“可以第一次进中断,先计算下一次中断的输出值。
第二次进中断,先输出上次中断的值,再计算下次的输出值”无论如何,中断里都要计算输出的值和并且输出。这个时间是固定的,我根据看到的事例整出来,上面2个动作的时间超过了中断的定时,不懂得地方也就在这里了。

gx_huang 发表于 2013-6-18 17:24

你的理解是错的。
中断处理时间超过了中断周期,只能优化程序,或者加高主频。
每次处理数据的时间并不固定的,数据处理函数里会有条件判断,时间不是每次都一样的,会导致抖动。

xuyongsos 发表于 2013-6-19 10:31

不是我理解错了,是你还没搞清楚我的问题点。
/*****************************************************************
                  定时器溢出中断服务程序
******************************************************************/
void ISR_T1_Overflow (void)
{
X_LUTaExt += x_SWa;      
X_LUTbExt += x_SWb;
X_LUTa=(char)(((X_LUTaExt+4) >> 3)&(0x007F));
X_LUTb=(char)(((X_LUTbExt+4) >> 3)&(0x007F));
         // 计算 PWM 值: 高频值 + 3/4 低频值
OCR1A = (auc_SinParam + (auc_SinParam-(auc_SinParam>>2)));
}
这个是一个算法的体现,每65US一次的中断里来计算步进值,然后查表得出2个不同频率的正玄波同一时间的电压值相叠加。
这个整套代码是网上的大神放出来的,我只是来实现。在实现的过程中,我发现上面这段放在中断里的代码运行时间超过了
65US,这样运算出来后的正玄波周期时间就超过了预定的周期时间,并不能得到我想要的频率的正玄波。
我不知道其中的问题出在哪里。我也相信我上面放出的代码是正确的,因为我查过不少资料,用IO口配合电阻网络来模拟输出
DTMF信号是绝对可行的。

gx_huang 发表于 2013-6-19 11:28

你说没有理解错,其实你还是没有理解。
我很清除你的问题点,就是2个:
1、来不及
2、输出的间隔有抖动

我不是说的很清楚吗?
中断处理时间超过了中断周期,只能优化程序,或者加高主频。
每次处理数据的时间并不固定的,数据处理函数里会有条件判断,时间不是每次都一样的,会导致抖动。
你无法用低速的MCU实现软件DTMF输出的。

dengm 发表于 2013-6-19 11:47

编译器速度优化编译, 最好用汇编实现

xuyongsos 发表于 2013-6-19 12:59


这是我网上找到的资料,就是通过低速单片机,12T的51,实现了的,我代码里也能看出来,有个是用8MHZ的晶振#defineXtal       8000000          // 系统时钟频率。


xuyongsos 发表于 2013-6-19 13:17

www===**/mcu/2012/0707/article_9383_3.html(把===改为.)
这个链接是整套理论,有用DA实现和IO实现的对比效果。有兴趣的朋友可以看看。
早有人用低频率的单片机实现了,我只是如今做个DEMO再现一下。
void Timer1_Interrupt(void) interrupt3       //Timer1 65US
{

        TH1 = 0xFf;               
            TL1 = 0x2e;
        ucfreqH_ext +=ucfreqH_step;
        ucfreqL_ext +=ucfreqL_step;
        ucfreqH = (char)(((ucfreqH_ext+4)>>3)%144);
        ucfreqL = (char)(((ucfreqL_ext+4)>>3)%144);
        P1=uc_SinParam+(uc_SinParam - (uc_SinParam>>2 ) );
}       
我测试的时候,取P1=uc_SinParam,只取一个高频波出来,然后示波器量输出发现波形周期大过理论周期,再去查IO口的反转电平最小周期,理论应该是中断时间65US,结果超过了。所以我就奇怪前辈们是怎么实现的了,我这样的情况问题又出现在哪里?

gx_huang 发表于 2013-6-19 14:07

人家是汇编,最多单字节加法,中断指令时间严格计算的。
你倒好,C语言,还有好多数**算,有双字节的除法?
65US太短,我以前用122US,8.196KHz采样。还用24MHz晶体,当然,记得12MHz也可以。

coody 发表于 2013-6-19 14:30

根据DDS原理来做双频率发生器,直接数字叠加再DAC输出,对正弦波表采样输出的最小采样频率是16KHZ,用32KHZ的话,就非常好了,DTMF最高频1633HZ,都可以接近20个采样点,外接至少2阶的低通。

xuyongsos 发表于 2013-6-19 16:14

感谢gx_huang,我明白问题出在哪了。我看的理论分析,是不会错的。但是我找到的实例代码,有没有问题,就不知道了。我验证的是是根据实例代码来写的。
void ISR_T1_Overflow (void)
{
X_LUTaExt += x_SWa;      
X_LUTbExt += x_SWb;
X_LUTa=(char)(((X_LUTaExt+4) >> 3)&(0x007F));
X_LUTb=(char)(((X_LUTbExt+4) >> 3)&(0x007F));
         // 计算 PWM 值: 高频值 + 3/4 低频值
OCR1A = (auc_SinParam + (auc_SinParam-(auc_SinParam>>2)));
}
这个实例代码,根本看不出中断时间,我自己写的是65US的中断,算法跟这个一样,还稍微复杂点,带2字节的除法运算。问题就出在这里了。
上面的中断代码C写的,编译成汇编后,12T的51,用8MHZ晶振,我估计也不是65US内能跑完的。

autopccopy 发表于 2013-6-19 19:15

试试1T单片机并使用高主频?

例如:STC IAP15F2K61S2-I35-PDIP40, 主频可调到35M,1T的速度。;P

fishszlkcy 发表于 2022-1-16 11:15

coody 发表于 2013-6-17 12:05
完全没有问题,至少要用6个IO做R-2R的DAC,用类似DDS的方法来合成DTMF,成功率很高的。
软件解码DTMF都可以 ...

你好,Coody;请问可以提供一个软件解码,编码DTMF信号的软件例子吗?谢谢
页: [1]
查看完整版本: 单片机IO口模拟DTMF信号的实践问题