🎀 文章作者:二土电子
🐸 期待大家一起学习交流!
1 摘要
双音多频(DTMF)信号具有抗干扰能力强、传输速率高的优点,其首先应用于电话的拨号系统。随着时代的发展,DTMF 信号的应用也更加广泛,现今已被应用在诸如语言菜单、语音邮件、电话银行和ATM 终端中。
本系统分别基于MATLAB 和单片机完成了DTMF 通信系统的设计,可实现基于DTMF信号的MATLAB 双机通信、单片机双机通信以及MATLAB 与单片机的相互通信等功能,并且能输出相关波形及声音。为了操作更加简便,结果观察更加直观,系统设计有相应的GUI 界面。此外,为了保护系统的隐私性,本系统还增加了登录功能,可实现用户注册、修改密码。
关键词:双音多频;MATLAB;单片机;双机通信
2 设计任务
- 利用MATLAB 设计一个DTMF 通信系统;
- 输出DTMF 波形及声音;
- 利用单片机设计一个DTMF 通信系统
3 课程设计主要解决的问题
六位电话号码输出到MATLAB GUI 的文本框中时,显示不完全。
错误原因:起初获取输入号码时利用str2num()函数,将字符串转换成了数字,号码检测完成后,输出结果仍为数字变量,由于数值较大,显示时自动采用了科学计数法来表示,导致输出结果出现错误。
解决办法:在输出检测结果前,先利用num2str()函数将数字转换成字符串,然后再输出。利用单片机根据时序图进行DTMF 编码时出现编码错误,0~9 只有两种DTMF 信号。
错误原因:控制DTMF 编码数据位的单片机引脚被复用。
解决办法:选择其他没被复用的GPIO 口。利用录音进行六位号码检测时,检测出的结果出现错误。
错误原因:利用audiowrite()函数将录音存储为.wav 文件时,audiowrite 要求信号幅度位于[-1,1]区间,但是产生的DTMF 信号幅度位于[-2,2]区间,所以在存储时信号被裁剪。而译码时信号幅度又是重要参量,所以导致译码错误。
解决办法:在利用audiowrite()函数将录音存储为.wav 文件前先将DTMF 信号幅度缩小两倍再进行存储,读取出来后再放大两倍,恢复成原来的幅度。实现MATLAB 双机通信时,误码率较高。
错误原因:DTMF 信号的幅度偏低,而噪声较大,导致信噪比较低,接收时误码率偏高。
解决办法:提高DTMF 信号的幅度。
4 设计内容
4.1 整体设计方案
- MATLAB部分
由一个高频信号和一个低频信号叠加生成一个DTMF 信号,发送端利用sound()函数将数字信号转换成模拟语音信号,接收端利用audiorecorder()函数对模拟语音信号进行采样将其转换为数字信号,做出译码并显示结果,二者通过音频线连接,也可直接将声音外放,译码利采用戈泽尔算法。
这个图容易引发歧义,图中“产生DTMF信号”实际指的是MATLAB配置好频率,实际发出声音产生还是要DAC。
单片机部分
主要流程与MATLAB 实现相同,以STM32F103ZET6 为主控核心,利用AE11A04模块产生DTMF 信号,MT8870 芯片及其外部电路实现DTMF 信号的接收,声音信号通过音频线传输。输出DTMF 波形及声音
MATLAB 中利用plot()函数绘出DTMF 时域波形,利用stem()函数绘出频谱图,利用sound()函数输出DTMF 拨号音;单片机部分利用扬声器输出DTMF 拨号音,将声音传输给MATLAB 并利用MATLAB 显示出波形;
4.2 详细设计内容
基于MATLAB 的DTMF 通信系统
实现了利用MATLAB 对DTMF 信号的产生和检测,并且能够实现两台PC 机间的任意位电话号码传输和接收。基于单片机的DTMF 通信系统
以STM32F103ZET6 为主控核心,实现了DTMF 信号的产生以及接收,可实现两台单片机间的六位电话号码传输和接收。也可以由一台单片机自发自收。MATLAB 与单片机间的DTMF 通信
实现了由MATLAB 产生六位电话号码,通过音频线发送给单片机,单片机进行接收并正确显示接收结果。输出DTMF 波形及声音
在MATLAB 中实现了输出DTMF 时域波形、频谱以及声音。单片机也可输出DTMF声音,波形显示需借助于示波器或者MATLAB。MATLAB 登录系统设计
设计了一个MATLAB 登陆系统,可实现用户注册、修改密码等功能。MATLAB 和单片机均设计了相应的GUI 界面,操作简便,便于观察检测结果和相关
波形。
5 结果与分析
5.1 基于MATLAB 的DTMF 通信系统
- DTMF 信号波形显示声音输出以及六位电话号码检测
上图右侧可以设置信噪比,选择是否引入高斯白噪声。每按下一个按键就会检测按下键值。左侧可以进行6位电话号码检测。可以显示检测结果,查看每一位DTMF信号的波形和频谱。当不引入噪声时结果均正确;引入噪声且信噪比较低时会出现接收错误。
5.2 双机通信模拟
此时需要两台电脑和一根音频线,一台电脑做发送端,另一台电脑做接收端。
首先在主页选择双机通信
进入双机通信仿真页面,发送端发送125869。
接收端首先点击开始检测,会弹出一个窗,提示准备开始录音。
接收端按下确定后,发送端点击拨号。此时接收端开始录音,录音完成后会显示检测结果,点击声音波形或者短时能量可以看到接收信号的声音波形和短时能量波形。
5.2 基于单片机的DTMF 通信系统
由于资源有限,所以这里只用一台单片机自发自收,模拟双机通信,二者原理相同。仿真结果可以看出发送接收,误码率较低。
5.3 MATLAB 与单片机间的DTMF 通信
由MATLAB 随机发送六位电话号码,单片机进行接收,能正确判断发送的号码以及号
码位数,二者通过音频线相互连接。
电脑端选择单片机通信,输入号码,通过音频线发送给单片机,单片机解析并显示。
可以看出,MATLAB 发送的六位号码为“106337”,单片机检测的号码位数为六位,检测结果为“006337”,第一位出现错误。但是在大量的仿真实验中发现,单片机接收的误码率还是很低的。
6 总结与展望
本次课程设计基于MATLAB 和单片机设计了一个DTMF 通信系统,系统操作简单,误码率较低,不仅满足了题目要求,还做出了相应的拓展,总体来看较为满意。通过本次课程设计强化了我的MATLA 编程、C 语言编程和GUI 界面设计的能力,加深了我对DTMF 通信系统的了解,学习到了一些用于DTMF 编、译码的模块和芯片,了解了戈泽尔算法的原理。由于单片机部分资料较少,所有程序都只能根据芯片资料编写,甚至有些芯片的资料为英文。这不仅锻炼了我的英文水平,还让我学习了如何根据芯片时序图独立编写程序。
当然,本次设计也有不足之处,由单片机产生DTMF 信号时,两个音频输出口同时只能一个具有有效输出,所以无法在发送DTMF 信号时产生拨号音。此外,在由于课程设计时间有限,所以系统还有许多可以改进的地方。比如利用DMA 使得编码、译码分别独立进行,以提高系统运行效率,甚至利用红外遥控或者蓝牙实现远程控制DTMF 编码等功能。
至此课程设计报告的内容已经介绍完成,接下来针对程序设计时的核心部分做一个简单介绍,后续博主也会将程序代码上传到资源,感兴趣的友友可以关注一下哟。
7 关键程序设计
7.1 MATLAB程序设计
7.1.1 产生DTMF信号
MATLAB产生DTMF信号较为简单,生成两个固定频率的正弦信号,将二者叠加(相加)起来即可。
以“1”为例,介绍一下MATLAB生成DTMF信号的程序设计
%按键1
A=10;%振幅
fs=44100; %采样频率
N=8820; % 信号样点数,每个音频播放时长
f = [697,1209;697,1336;697,1477;770,1209;770,1336;770,1477;852 1209;852 1336;852 1477;
global y;
y = [y A*sin(2*pi*f(1,1)*(0:N-1)/fs)+A*sin(2*pi*f(1,2)*(0:N-1)/fs) zeros(1,4410) ];
n=strcat(get(handles.edit1,'String'),'1');
set(handles.edit1,'String',n); % 显示按下的按键
值得注意的是,如果信号幅值太小会导致译码时错误概率升高。
7.1.2 DTMF信号译码
以单个DTMF信号解析为例,介绍一下译码过程。
%单个按键检测程序
%DAC生成DTMF信号音
filename = ('chen.wav'); %给文件取名
audiowrite(filename,z1,8000) %存储.wav音频文件,在这里文件名为dtmf.wav
%ADC解析录音文件
[r1,~]=audioread('chen.wav');
r1=r1.*2; %程序里audiowrite要求sum_x位于[-1,1]区间,译码时信号幅度是重要参量,所以读取完之后要把幅度变为原来的幅度
X=goertzel(r1(1:N),K+1); %利用戈泽尔算法进行译码
val=abs(X);
limit=80;
for s=5:8
if val(s)>limit,break,end %查找列号
end
for r=1:4
if val(r)>limit,break,end %查找行号
end
TNr=tm(r,s-4); %译码结果
7.2 单片机程序设计
7.2.1 产生DTMA信号
单片机利用AE11A04芯片产生DTMF信号,具体的资料找不到了,有兴趣的友友可以到某宝搜索一下。这里凭借之前的程序,简单介绍一下产生DTMF信号的原理。
实际原理比较简单,当时买的一个集成的硬件模块,一共五根线,分别是STD,AD0,AD1,AD2,AD3。需要产生DTMA信号时先拉高STD引脚,配置AD0~AD3,组成一个二进制数,实际DTMF信号就是由二进制数控制,4位二进制数,16种组合,每一种代表一个DTMF信号。延时500ms后再将STD引脚拉低,就输出了一个DTMF信号。具体的对应表格有些找不到了,大家也可以根据下面每一个DTMF信号的程序看一下对应关系。
void AE11A04_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能PB,PE端口时钟
GPIO_InitStructure.GPIO_Pin = STD | AD0 | AD1 | AD2 | AD3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始化GPIOB.5
GPIO_ResetBits(GPIOB,STD | AD0 | AD1 | AD2 | AD3);
}
// 接下来是每一个DTMF信号对应的IO配置,这里只用到了数字
// 数字1
GPIO_SetBits(GPIOB,STD | AD0);
GPIO_ResetBits(GPIOB,AD1 | AD2 | AD3); //对DTMF信号做二进制编码
delay_ms(500);
GPIO_ResetBits(GPIOB,STD);
// 数字2
GPIO_SetBits(GPIOB,STD | AD1);
GPIO_ResetBits(GPIOB,AD0 | AD2 | AD3);
delay_ms(500);
GPIO_ResetBits(GPIOB,STD);
// 数字3
GPIO_SetBits(GPIOB,STD | AD0 | AD1);
GPIO_ResetBits(GPIOB,AD2 | AD3);
delay_ms(500);
GPIO_ResetBits(GPIOB,STD);
// 数字4
GPIO_SetBits(GPIOB,STD | AD2);
GPIO_ResetBits(GPIOB,AD0 | AD1 | AD3);
delay_ms(500);
GPIO_ResetBits(GPIOB,STD);
// 数字5
GPIO_SetBits(GPIOB,STD | AD0 | AD2);
GPIO_ResetBits(GPIOB,AD1 | AD3);
delay_ms(500);
GPIO_ResetBits(GPIOB,STD);
// 数字6
GPIO_SetBits(GPIOB,STD | AD1 | AD2);
GPIO_ResetBits(GPIOB,AD0 | AD3);
delay_ms(500);
GPIO_ResetBits(GPIOB,STD);
// 数字7
GPIO_SetBits(GPIOB,STD | AD0 | AD1 | AD2);
GPIO_ResetBits(GPIOB,AD3);
delay_ms(500);
GPIO_ResetBits(GPIOB,STD);
// 数字8
GPIO_SetBits(GPIOB,STD | AD3);
GPIO_ResetBits(GPIOB,AD0 | AD1 | AD2);
delay_ms(500);
GPIO_ResetBits(GPIOB,STD);
// 数字9
GPIO_SetBits(GPIOB,STD | AD0 | AD3);
GPIO_ResetBits(GPIOB,AD1 | AD2);
delay_ms(500);
GPIO_ResetBits(GPIOB,STD);
// 数字0
GPIO_SetBits(GPIOB,STD | AD1 | AD3);
GPIO_ResetBits(GPIOB,AD0 | AD2);
delay_ms(500);
GPIO_ResetBits(GPIOB,STD);
7.2.2 单片机接收
单片机接收使用的是MT8870芯片,也是直接买的集成好的模块。跟生成DTMF信号的原理相同,他也是通过四根信号线接收到的4位二进制数来判断是哪个DTMF信号。这里只贴一下MT8870的初始化函数,具体解析时信号的对应关系可以看一下7.2.1小节。
void MT8870_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE); // 使能PC端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 |GPIO_Pin_4 | GPIO_Pin_5; //选择对应的引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//配置GPIO模式,输入上拉
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PC端口
}