目 录
1 绪论 1
1.1 示波器概述 1
1.2 数字示波器的特点及原理 1
1.3 数字示波器国内外研究状况 2
1.4 Android概述 3
1.5 题目研究背景及意义 4
2 数字示波器原理分析与技术指标 5
2.1 数字示波器的组成原理 5
2.2 数字示波器与模拟示波器相比较 5
2.3 数字示波器技术指标 7
2.3.1 最大采样速率 7
2.3.2 存储宽度 7
2.3.3 分辨率 8
2.3.4 记录长度 8
2.3.5 存储带宽 9
2.3.6 垂直灵敏度及误差 9
2.3.7 扫描速度及误差 10
2.4 采样原理分析 10
2.4.1 实时采样方式的原理 10
2.4.2 等效取样方式的原理 12
3 系统方案论证 13
3.1 系统总体方案设计 13
3.2 数据采集系统 14
3.2.1 波形整形电路方案选择 14
3.2.2 程控放大电路方案选择 15
3.3 数据处理系统 15
3.3.1 触发方案选择 15
3.3.2 峰值测量方案选择 16
3.3.3 测量频率方案选择 16
3.3.4 采样方案选择 17
4 系统硬件电路设计 19
4.1 微控制器电路 19
4.2 电源电路设计 19
4.3 信号采集电路设计 20
4.3.1 A/D转换及FIFO缓存器 20
4.3.2 程控放大电路 21
4.3.3 电平移位电路设计 22
4.3.4 频率测量电路 23
4.4 无线数据传输 24
5 系统软件设计 26
5.1 单片机软件开发环境 26
5.2 主程序设计及流程图 26
5.3 数据处理子程序 27
5.3.1 同步触发的软件实现 27
5.3.2 频率计算原理及流程图 28
5.3.3 测量信号峰峰值 29
6 Android显示平台 31
6.1 Android应用开发环境 31
6.1.1 Android简介 31
6.1.2 开发所需的软件包 31
6.1.3 搭建Android开发环境 31
6.1.4 测试所配的开发环境 32
6.2 Android蓝牙通信设计 33
6.2.1 Android设备中蓝牙模块的使用 34
6.2.2 蓝牙数据通信 35
6.3 Android上绘制波形 36
6.3.1 SurfaceView的使用 36
6.3.2 绘制波形子程序 37
结论 39
参考文献 40
致谢 42
附录 A 实物图 43
附录 B 系统主程序设计源码 44
附录 C Android蓝牙服务程序设计源码 48
3 系统方案论证
3.1 系统总体方案设计
数字示波器很大的一个特点,就是能对采集到的波形数据进行处理,通过特定的算法即可测量出信号的频率、幅值和相位等电气参数。并且示波器能将运算处理后的数据直接显示在LCD液晶屏上,通过调整合适的扫描速度就能方便的观察到信号的细节。由于示波器需要处理的数据量比较大,且对于高频信号需要很高的采样率和刷新速度,因此一般的微处理器难于满足[19]。目前高性能的数字示波器都采用FPGA来控制数据的采集和处理数据。由于FPGA的灵活性极高,采用FPGA的系统能高效的处理数据及显示波形。但是由于FPGA的成本较高,并不适用于低端便携式产品上。
本文的设计理念是便携式、低成本,因此需要寻找一款性能高、价格低的微处理器。考虑到成本和实际应用,本文选择了意法半导体公司的STM32微处理器作为主控制器。STM32系列是基于专为要求高性能、低成本、低功耗的嵌入式应用专门设计的ARM Cortex-M3内核,能完全满足本设计对成本和性能的要求。
本文的另外一个亮点就是使用了Android作为波形显示平台。在这个基本上是人手一部Android手机的时代,怎样充分挖掘Android软硬件的使用价值就显得尤为重要。本设计用到Android的屏幕来显示波形,而且还使用了触摸屏来对示波器进行控制,这样将大大缩小硬件的体积和降低系统成本。同时示波器硬件电路和Android之间通过蓝牙实现无线数据传输,这使得系统的应用将是非常的方便。
数字示波器的结构框图如图3.1所示。
本论文设计的数字示波器的工作方式是,被测信号经STM32控制的采样电路进行采样并对数据进行处理,然后通过蓝牙把处理过后的波形数据发送到Android端,最后通过Android端的应用程序把波形显示出来。在Android端的应用程序上可以操作示波器的所有功能,比如调节垂直灵敏度、水平扫描速度、触发电平、垂直基线等。同时本设计非常小巧,且使用锂电池进行供电,非常方便携带。本设计的理念是,只要你拥有一台Android手机或者平板电脑,你就可以拥有一个低成本的、便于携带的数字示波器。
图3.1 结构框图
模拟信号处理是示波器的重要组成部分,也是信号采集系统中的一个核心部分。图3.1清晰的介绍了数字示波器的结构框图,从图中可以看出,模拟通道的输入信号被分成两路,一路送至程控放大器,用来实现垂直灵敏度的调节;另一路送至触发电路,用于测量被测信号的频率。其中程控放大器是示波器模拟部分设计的关键,也是示波器带宽最重要的限制因素。
3.2 数据采集系统
3.2.1 波形整形电路方案选择
波形整形电路的选择主要是选择合适的比较器芯片,为了防止波形在过零点出现抖动,比较电路最好是接成滞回比较结构,以免影响比较器的输出波形。
方案一:采用Maxim公司的高速比较芯片Max912构成滞回比较器。Max912的最高转换速度可达ns级别,对频率很高的信号都能进行有效的整形。但缺点是Max912的成本太高。
方案二:采用比较器LM311构成滞回比较器。在对5M以下的信号进行整形时,能收到比较好的效果。最主要的还是其价格相对比较低,能满足本设计对成本的控制。但当信号频率高于5M时,本文转载自http://www.biyezuopin.vip/onews.asp?id=14838由于收到LM311转换速度的限制,其整形效果比较差。
基于本设计对测试范围的要求以及成本的控制,本设计选择方案二。
3.2.2 程控放大电路方案选择
由于A/D转换器的输入范围一般都比较小(低于2Vpp),不可能直接测量几十伏甚至是几百伏的信号。而且由于A/D转换器的分辨率有限,对于幅值很低的信号测量误差将会很大甚至是无法测量。所以在输入级必须要设计一个程控放大电路,以现实对信号进行不失真的处理,而后再送至A/D数模转换器,以达到A/D数模转换器的输入要求。通常情况下所说的“示波器的模拟带宽”就是输入级的模拟信号放大电路的带宽。而数字示波器的带宽主要是用采样率来定义,主要受数模转换器的最高采样率以及控制器的处理速度的限制。
方案一:利用高速运放LM6172构成放大电路,并设置多种不同放大倍数的反馈电阻网络,然后通过控制继电器选择不同的反馈电阻,从而实现不同放大倍数的切换。其优点是电路和控制都比较简单,放大倍数稳定。但缺点是继电器的工作电流大,且需要多个继电器配合才能实现多种不同的放大倍数,很明显不符合本设计便携小巧的宗旨。同时继电器在开和关的瞬间都会产生很大的电磁干扰,有可能会影响到系统的正常运行。
方案二:使用模拟开关HCF4051BE代替继电器,来实现不同放大倍数的切换。优点是克服了继电器工作电流大、体积大等缺点,且电路结构简单,成本低廉。但缺点是模拟开关无法实现零阻抗,且其带宽有限,通过高频信号将会产生失真。
方案三:使用专用PGA芯片AD603,可以通过MCU来控制AD603的基准,进而实现不同放大倍数的调节。该电路优点是控制比较简单,且增益调节范围大,外围电路简单。缺点是成本稍微偏高。
基于本设计便携小巧的宗旨,和电路复杂度的考虑,本设计选择方案三。
package com.test.BTClient;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
import com.test.BTClient.R;
import com.test.BTClient.DeviceListActivity;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
public class BTClient extends Activity {
private final static int REQUEST_CONNECT_DEVICE = 1; //宏定义查询设备句柄
private final static String MY_UUID = "00001101-0000-1000-8000-00805F9B34FB"; //SPP服务UUID号
private InputStream is; //输入流,用来接收蓝牙数据
private TextView Title; //提示栏解句柄
private TextView TextX;
private TextView TextY;
Button X1,X2,Y1,Y2;
//绘图部分定义
SurfaceView sfv;
SurfaceHolder sfh;
//蓝牙接收数据缓存
int sdflag=1;//开始先发送初始数据
int sent_num=0;
int num = 0;
int data_num=0;
int data_byte;
byte[] buffer = new byte[1024];
byte[] buffer_new = new byte[1024];
int[] data_osc = new int[1024];
int flag_draw=0;
int Y_axis[],//保存正弦波的Y 轴上的点
centerY,//中心线
oldX,oldY,//上一个XY 点
currentX;//当前绘制到的X 轴上的点
int rateX=5;
int rateY=5;
int baseY=255;
public String filename=""; //用来保存存储的文件名
BluetoothDevice _device = null; //蓝牙设备
BluetoothSocket _socket = null; //蓝牙通信socket
boolean _discoveryFinished = false;
boolean bRun = true;
boolean bThread = false;
private OutputStream outStream = null;
private BluetoothAdapter _bluetooth = BluetoothAdapter.getDefaultAdapter(); //获取本地蓝牙适配器,即蓝牙设备
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main); //设置画面为主画面 main.xml
/* 获得TextView对象 标题 “数字示波器” */
Title = (TextView) findViewById(R.id.Text0);
/* 设置文本的颜色 */
Title.setTextColor(Color.RED);
/* 设置字体大小 */
Title.setTextSize(35);
/* 设置文本框大小 */
//Title.setWidth(150);
/* 设置文字背景 */
Title.setBackgroundColor(Color.TRANSPARENT);
TextX = (TextView) findViewById(R.id.TextX); //得到数据显示句柄
/* 设置文本的颜色 */
TextX.setTextColor(Color.CYAN);
/* 设置字体大小 */
TextX.setTextSize(20);
/* 设置文本框大小 */
//Title.setWidth(150);
/* 设置文字背景 */
TextX.setBackgroundColor(Color.TRANSPARENT);
TextY = (TextView) findViewById(R.id.TextY); //得到数据显示句柄
/* 设置文本的颜色 */
TextY.setTextColor(Color.CYAN);
/* 设置字体大小 */
TextY.setTextSize(20);
/* 设置文本框大小 */
//Title.setWidth(150);
/* 设置文字背景 */
TextY.setBackgroundColor(Color.TRANSPARENT);
X1 = (Button) this.findViewById(R.id.X1);//X-
/* 设置字体大小 */
X1.setTextSize(25);
X2 = (Button) this.findViewById(R.id.X2);//X+
/* 设置字体大小 */
X2.setTextSize(25);
Y1 = (Button) this.findViewById(R.id.Y1);//Y-
/* 设置字体大小 */
Y1.setTextSize(25);
Y2 = (Button) this.findViewById(R.id.Y2);//Y+
/* 设置字体大小 */
Y2.setTextSize(25);
sfv = (SurfaceView) this.findViewById(R.id.SurfaceView01);
sfh = sfv.getHolder();
// 初始化y 轴数据
centerY = (getWindowManager().getDefaultDisplay().getHeight()-sfv.getTop()) / 2;
Y_axis = new int[getWindowManager().getDefaultDisplay().getWidth()];
//for (int i = 1; i < 170; i++) {// 计算正弦波
// data_osc[i -1] = 128- (int) (100 * Math.sin(i * 2 * Math.PI / 180));
//}
//如果打开本地蓝牙设备不成功,提示信息,结束程序
if (_bluetooth == null){
Toast.makeText(this, "无法打开手机蓝牙,请确认手机是否有蓝牙功能!", Toast.LENGTH_LONG).show();
finish();
return;
}
// 设置设备可以被搜索
new Thread(){
public void run(){
if(_bluetooth.isEnabled()==false){
_bluetooth.enable();
}
}
}.start();
}
//发送设置数据
void SentData(){
//数据头
sentying(222);
sentying(0);
sentying(88);
sentying(111);
//
sentying(255-baseY/2);//垂直基线
sentying(rateX);//水平灵敏度
sentying(rateY);
sentying(88);
sentying(250);
sentying(55);
switch(rateX)
{
case 1: TextX.setText("10uS/div");break;
case 2: TextX.setText("20uS/div");break;
case 3: TextX.setText("40uS/div");break;
case 4: TextX.setText("100uS/div");break;
case 5: TextX.setText("200uS/div");break;
case 6: TextX.setText("400uS/div");break;
case 7: TextX.setText("1mS/div");break;
case 8: TextX.setText("2mS/div");break;
case 9: TextX.setText("4mS/div");break;
case 10: TextX.setText("10mS/div");break;
case 11: TextX.setText("20mS/div");break;
case 12: TextX.setText("40mS/div");break;
default: break;
}
switch(rateY)
{
case 1: TextY.setText("200mV/div");break;
case 2: TextY.setText("500mV/div");break;
case 3: TextY.setText("1V/div");break;
default: break;
}
}
/*
* 绘制指定区域
*/
void SimpleDraw(int length) {
Canvas canvas = sfh.lockCanvas(new Rect(0, 0, 480,511));// 关键:获取画布
canvas.drawColor(Color.BLACK);//清除画布
int x=0,y=0;
//画网格8*8
Paint mPaint = new Paint();
mPaint.setColor(Color.GRAY);// 网格为黄色
mPaint.setStrokeWidth(1);// 设置画笔粗细
oldY=0;
for (int i = 0; i <= 8; i++) {// 绘画横线
canvas.drawLine(0, oldY, 511, oldY, mPaint);
oldY = oldY+64;
}
oldX=0;
for (int i = 0; i <= 8; i++) {// 绘画纵线
canvas.drawLine(oldX, 0, oldX, 511, mPaint);
oldX = oldX+60;
}
//画Y轴基线
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(3);// 设置画笔粗细
// 绘画横线
canvas.drawLine(0, baseY, 479, baseY, mPaint);
if( length != 500)
{
//画波形
mPaint.setColor(Color.YELLOW);// 画笔为绿色
mPaint.setStrokeWidth(3);// 设置画笔粗细
oldX = 0;
oldY = (255-data_osc[0])*2;
for (int i = 0; i < length; i++) {
y = (255 - data_osc[i])*2;//Y轴32点/格
x=i*3;//X轴20点/格
canvas.drawLine(oldX, oldY, x, y, mPaint);
oldX = x;
oldY = y;
}
}
//oldX = 0;
sfh.unlockCanvasAndPost(canvas);// 解锁画布,提交画好的图像
}
/* 触笔事件 触摸屏幕动态设置波形图基线 */
public boolean onTouchEvent(MotionEvent event)
{
int iAction = event.getAction();
if (iAction == MotionEvent.ACTION_CANCEL ||
iAction == MotionEvent.ACTION_DOWN ||
iAction == MotionEvent.ACTION_MOVE)
{
return false;
}
//得到触笔点击的位置
int y = (int) event.getY();
if((y >= sfv.getTop()) && (y <= sfv.getTop() + 511))
baseY = y - sfv.getTop();
else if(y < sfv.getTop())
baseY = 0;
else if(y > sfv.getTop() + 511)
baseY = 511;
SimpleDraw(500);
SentData();//发送设置数据
return super.onTouchEvent(event);
}
//水平灵敏度 -
public void onClickedX1(View v){
if(rateX > 1)
rateX --;
else
rateX = 1;
SentData();//发送设置数据
}
//水平灵敏度 +
public void onClickedX2(View v){
if(rateX < 12)
rateX ++;
else
rateX = 12;
SentData();//发送设置数据
}
//垂直灵敏度 -
public void onClickedY1(View v){
if(rateY > 1)
rateY --;
else
rateY = 1;
SentData();//发送设置数据
}
//垂直灵敏度 +
public void onClickedY2(View v){
if(rateY < 3)
rateY ++;
else
rateY = 3;
//SimpleDraw(160);//直接绘制正弦波
SentData();//发送设置数据
}
//蓝牙发送一位数据
public void sentying(int ying)
{
try {
outStream = _socket.getOutputStream();
} catch (IOException e1) {
}
try {
outStream.write(ying);
} catch (IOException e1) {
}
}
//接收活动结果,响应startActivityForResult()
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch(requestCode){
case REQUEST_CONNECT_DEVICE: //连接结果,由DeviceListActivity设置返回
// 响应返回结果
if (resultCode == Activity.RESULT_OK) { //连接成功,由DeviceListActivity设置返回
// MAC地址,由DeviceListActivity设置返回
String address = data.getExtras()
.getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
// 得到蓝牙设备句柄
_device = _bluetooth.getRemoteDevice(address);
// 用服务号得到socket
try{
_socket = _device.createRfcommSocketToServiceRecord(UUID.fromString(MY_UUID));
}catch(IOException e){
Toast.makeText(this, "连接失败!", Toast.LENGTH_SHORT).show();
}
//连接socket
Button btn = (Button) findViewById(R.id.Button03);
try{
_socket.connect();
Toast.makeText(this, "连接"+_device.getName()+"成功!", Toast.LENGTH_SHORT).show();
btn.setText("断开");
}catch(IOException e){
try{
Toast.makeText(this, "连接失败!", Toast.LENGTH_SHORT).show();
_socket.close();
_socket = null;
}catch(IOException ee){
Toast.makeText(this, "连接失败!", Toast.LENGTH_SHORT).show();
}
return;
}
//打开接收线程
try{
is = _socket.getInputStream(); //得到蓝牙数据输入流
}catch(IOException e){
Toast.makeText(this, "接收数据失败!", Toast.LENGTH_SHORT).show();
return;
}
if(bThread==false){
ReadThread.start();
bThread=true;
}else{
bRun = true;
}
}
break;
default:break;
}
}
//接收数据线程
Thread ReadThread=new Thread(){
public void run(){
int i;
//bRun = true;
//接收线程
while(true){
try{
while(true){
num = is.read(buffer); //读入数据
for(i=0;i<num;i++){
if(buffer[i] < 0)
data_osc[data_num+i]=buffer[i] + 255;
else
data_osc[data_num+i]=buffer[i];
}
data_num = data_num + num;
if(data_num>=159)
{
data_num=0;//
SimpleDraw(160);//直接绘制正弦波
//发送显示消息,进行显示刷新
//handler.sendMessage(handler.obtainMessage());
}
if(is.available()==0)break; //短时间没有数据才跳出进行显示
}
}catch(IOException e){
}
}
}
};
//消息处理队列
Handler handler= new Handler(){
public void handleMessage(Message msg){
super.handleMessage(msg);
//SentData();//发送设置数据
}
};
//关闭程序掉用处理部分
public void onDestroy(){
super.onDestroy();
if(_socket!=null) //关闭连接socket
try{
_socket.close();
}catch(IOException e){}
// _bluetooth.disable(); //关闭蓝牙服务
}
//连接按键响应函数
public void onConnectButtonClicked(View v){
if(_bluetooth.isEnabled()==false){ //如果蓝牙服务不可用则提示
Toast.makeText(this, " 打开蓝牙中...", Toast.LENGTH_LONG).show();
return;
}
//如未连接设备则打开DeviceListActivity进行设备搜索
Button btn = (Button) findViewById(R.id.Button03);
if(_socket==null){
Intent serverIntent = new Intent(this, DeviceListActivity.class); //跳转程序设置
startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE); //设置返回宏定义
}
else{
//关闭连接socket
try{
is.close();
_socket.close();
_socket = null;
bRun = false;
btn.setText("连接");
}catch(IOException e){}
}
return;
}
//退出按键响应函数
public void onQuitButtonClicked(View v){
finish();
}
}