打印

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

[复制链接]
5915|17
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
小弟看了本论坛teddeng的关于单片机纯软件使用IO口模拟DTMF信号的分析后,动手实践,用一款单片机搭了个电阻网络电路来实现DTMF信号,发现了个问题。
我用65US的采样频率,144的表格,12M晶振,8051的12T单片机,6个IO口。
问题点,中断程序执行实践超过65US,代码如下,我做测试,只实现输出某个特定周期的一个正旋波形
void Timer1_Interrupt(void) interrupt  3         //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[ucfreqH];
}
然后用示波器量波形,发现周期比表格理论的大了。查找问题,量某个IO口的波形图。发现最快的电压跳变实践是190US,远远超过65US的理论值。
网上有理论说,用普通的51能实现DTMF,不知道问题出在哪里了?求大神解惑

相关帖子

沙发
xuyongsos|  楼主 | 2013-6-17 11:08 | 只看该作者
自己顶顶~
我把问题简单化下,不谈自己的实践,网上有个 “双龙DTMF演示程序的两点疑问,做过DTMF(软件PWM方法)的请进”的帖子,我是看了这个帖子去动手实践的,实例代码如下
#include <io8515v.h>
#include <macros.h>
#define  Xtal       8000000          // 系统时钟频率
#define  prescaler  1                // T1预分频系数
#define  N_samples  128              // 在查找表中的样本数
#define  Fck        Xtal/prescaler   // T1工作频率
#define  delaycyc   10               // 读取port C口延时循环数
#pragma interrupt_handler ISR_T1_Overflow:7
/*************************** 正弦表 *****************************
       样本表: 一个周期分成128个点,每点按7位进行量化
****************************************************************/
flash unsigned char auc_SinParam [128] =
{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 [4] = {
107,96,
87,79};
const unsigned char auc_frequencyL [4] = {
61,56,
50,46};


//**************************  全局变量 ****************************
unsigned char x_SWa = 0x00;               // 高频信号脉冲宽度
unsigned char x_SWb = 0x00;               // 低频信号脉冲宽度
unsigned int  X_LUTaExt = 0;                 
unsigned int  X_LUTbExt = 0;              
unsigned int  X_LUTa;                  
unsigned int  X_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[X_LUTa] + (auc_SinParam[X_LUTb]-(auc_SinParam[X_LUTb]>>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];  
        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];//取高音脉冲宽度并结束循环
        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 | 只看该作者
我在实践中的疑问点是中断周期应该是多久?

使用特权

评论回复
5
xuyongsos|  楼主 | 2013-6-18 10:07 | 只看该作者
求大神帮忙解惑,我自己顶住先

使用特权

评论回复
6
gx_huang| | 2013-6-18 12:38 | 只看该作者
中断周期要固定,进入中断到输出数据的时间也要固定,中断执行时间要小于中断周期。
可以第一次进中断,先计算下一次中断的输出值。
第二次进中断,先输出上次中断的值,再计算下次的输出值。

使用特权

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

使用特权

评论回复
8
gx_huang| | 2013-6-18 17:24 | 只看该作者
你的理解是错的。
中断处理时间超过了中断周期,只能优化程序,或者加高主频。
每次处理数据的时间并不固定的,数据处理函数里会有条件判断,时间不是每次都一样的,会导致抖动。

使用特权

评论回复
9
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[X_LUTa] + (auc_SinParam[X_LUTb]-(auc_SinParam[X_LUTb]>>2)));
}
这个是一个算法的体现,每65US一次的中断里来计算步进值,然后查表得出2个不同频率的正玄波同一时间的电压值相叠加。
这个整套代码是网上的大神放出来的,我只是来实现。在实现的过程中,我发现上面这段放在中断里的代码运行时间超过了
65US,这样运算出来后的正玄波周期时间就超过了预定的周期时间,并不能得到我想要的频率的正玄波。
我不知道其中的问题出在哪里。我也相信我上面放出的代码是正确的,因为我查过不少资料,用IO口配合电阻网络来模拟输出
DTMF信号是绝对可行的。

使用特权

评论回复
10
gx_huang| | 2013-6-19 11:28 | 只看该作者
你说没有理解错,其实你还是没有理解。
我很清除你的问题点,就是2个:
1、来不及
2、输出的间隔有抖动

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

使用特权

评论回复
11
dengm| | 2013-6-19 11:47 | 只看该作者
编译器速度优化编译, 最好用汇编实现

使用特权

评论回复
12
xuyongsos|  楼主 | 2013-6-19 12:59 | 只看该作者

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


使用特权

评论回复
13
xuyongsos|  楼主 | 2013-6-19 13:17 | 只看该作者
www===**/mcu/2012/0707/article_9383_3.html(把===改为.)
这个链接是整套理论,有用DA实现和IO实现的对比效果。有兴趣的朋友可以看看。
早有人用低频率的单片机实现了,我只是如今做个DEMO再现一下。
void Timer1_Interrupt(void) interrupt  3         //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[ucfreqH]+(uc_SinParam[ucfreqL] - (uc_SinParam[ucfreqL]>>2 ) );
}       
我测试的时候,取P1=uc_SinParam[ucfreqH],只取一个高频波出来,然后示波器量输出发现波形周期大过理论周期,再去查IO口的反转电平最小周期,理论应该是中断时间65US,结果超过了。所以我就奇怪前辈们是怎么实现的了,我这样的情况问题又出现在哪里?

使用特权

评论回复
14
gx_huang| | 2013-6-19 14:07 | 只看该作者
人家是汇编,最多单字节加法,中断指令时间严格计算的。
你倒好,C语言,还有好多数**算,有双字节的除法?
65US太短,我以前用122US,8.196KHz采样。还用24MHz晶体,当然,记得12MHz也可以。

使用特权

评论回复
15
coody| | 2013-6-19 14:30 | 只看该作者
根据DDS原理来做双频率发生器,直接数字叠加再DAC输出,对正弦波表采样输出的最小采样频率是16KHZ,用32KHZ的话,就非常好了,DTMF最高频1633HZ,都可以接近20个采样点,外接至少2阶的低通。

使用特权

评论回复
16
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[X_LUTa] + (auc_SinParam[X_LUTb]-(auc_SinParam[X_LUTb]>>2)));
}
这个实例代码,根本看不出中断时间,我自己写的是65US的中断,算法跟这个一样,还稍微复杂点,带2字节的除法运算。问题就出在这里了。
上面的中断代码C写的,编译成汇编后,12T的51,用8MHZ晶振,我估计也不是65US内能跑完的。

使用特权

评论回复
17
autopccopy| | 2013-6-19 19:15 | 只看该作者
试试1T单片机并使用高主频?

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

stc_iap15f2k61s2_board1a.jpg (151.9 KB )

stc_iap15f2k61s2_board1a.jpg

使用特权

评论回复
18
fishszlkcy| | 2022-1-16 11:15 | 只看该作者
coody 发表于 2013-6-17 12:05
完全没有问题,至少要用6个IO做R-2R的DAC,用类似DDS的方法来合成DTMF,成功率很高的。
软件解码DTMF都可以 ...

你好,Coody;请问可以提供一个软件解码,编码DTMF信号的软件例子吗?谢谢

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

1

主题

9

帖子

0

粉丝