数字示波器的仿真设计
丁 林
基于新型 51 单片机 STC12C5A60S2 硬件资源,想构思一个数字示波器的方案,不过一个稍微
复杂一些的想法是要时间来实现的,所幸现在正处于暑假期间,于是利用proteus 仿真完成了一个基
于 LCD12864 显示的数字示波器设计。在此声明,这个教程是写给初学者看的,我会从简单到复杂
一步一步详细介绍设计过程,甚至是调试的过程,还包括一些经验总结,特别是提供了完整的 keil
工程附件。希望读者立足示波器项目,学到更多关于软硬件开发的一些经验技巧。
1 简易数字示波器原理
数字示波器基本原理可以简单理解为:数据采集+图形显示,该过程循环进行,如图 1-1 所示。
图 1-1 简易数字示波器原理图
首先是数据采集,这里我们直接用 STC12C5A60S2 单片机上的 ADC 来进行数据的采集。(如果
你没有 ADC,也可能没有信号发生器,但后面会介绍一种正弦表调试方法。)
注:由于 keil C51 中没有 STC12C5A60S2,所以这里的参考程序均改自 STC89C52,两款单片机是兼容的,只
是资源和速度(大约是 STC89C52 的 8-12 倍)不同而已。本文档所有程序均位于灰色方框中
宏晶公司所给驱动函数 INT8U get_AD_result(INT8U channel)。
清 0 高 5 位
//INT8U 是 typedef unsigned char INT8U; 即已重定义的一种类型
INT8U get_AD_result(INT8U channel)
{
INT8U AD_finished=0; // 存储 A/D 转换标志
ADC_RES = 0;
ADC_RESL = 0;
channel &= 0x07; //0000,0111
ADC_CONTR = AD_SPEED;
_nop_();
ADC_CONTR |= channel; // 选择 A/D 当前通道
_nop_();
ADC_CONTR |= 0x80; // 启动 A/D 电源
delay(1); //
ADC_CONTR |= 0x08; //0000,1000 令 ADCS = 1, 启动 A/D 转换,
AD_finished = 0;
while (AD_finished ==0 ) // 等待 A/D 转换结束
{
AD_finished = (ADC_CONTR & 0x10); //0001,0000 测试 A/D 转换结束否
}
ADC_CONTR &= 0xE7; //1111,0111
使输入电压达到稳定
清 ADC_FLAG 位, 关闭 A/D 转换,
第 1 页,共 42 页
return (ADC_RES); //
}
返回 A/D 高 8 位转换结果
以上是宏晶公司所给的与 STC12C5A60S2 片上 ADC 配套的数据采集驱动函数,使用该函数需
要注意的是,两次调用该函数之间的间隔要超过 ADC 的采样时间,具体可参考 STC12C5A60S2 的
数据手册(datasheet),AD 转换是要一段时间的,在高速系统中时间控制尤其关键。STC12C5A60S2
单片机在 11.0592M 晶振时钟频率下运行,连续两次 AD 采集数据并将数据写入 RAM 变量缓冲区,
之间的时间间隔肯定小于 21us 的,需要适当延时。这在高速档数据采集时增加了一定延时等待就是
这个原因。
图形显示有很多种,LCD 显示稍难,ADC 得到的结果如何在 LCD 上描点,这确实是一个难点,
涉及 LCD 驱动问题,需要花费很大篇幅才能完成。
最初调试我们可以选用串口来做,借助他人现成的工具软件。下面介绍基于串口和上位机工具
软件的波形显示程序设计。
串口初始化函数 void initiate_RS232 (void)。
波特率
//---------------------------------------------------------------------
#define Fosc 11059200
#define BAUD 115200 //
#define RELOAD_115200 (256 - (Fosc/16*10/BAUD+5)/10 ) //1T 模式, 波特率加倍
#define BRTx12_enable() AUXR |= 0x04 //BRT 独立波特率发生器的溢出率快 12 倍
#define BRT_start() AUXR |= 0x10 // 启动独立波特率发生器 BRT 计数。
void initiate_RS232 (void) // 串口初始化
{
ES = 0; //
SCON = 0x50; //
AUXR |= 0x01; //
PCON |= 0x80; //
BRTx12_enable(); //BRT
BRT = RELOAD_115200; // 设置独立波特率发生器 BRT 的自动重装数
BRT_start(); //
ES = 1;
}
禁止串口中断
可变波特率. 8 位无奇偶校验
使用独立波特率发生器
波特率加倍
独立波特率发生器的溢出率快 12 倍
启动独立波特率发生器 BRT 计数。
往串口写 1 字节函数 void Send_Byte(INT8U one_byte)。
//---------------------------------------------------------------------
void Send_Byte(INT8U one_byte) // 发送一个字节
{
TI = 0; //
SBUF = one_byte;
while (TI == 0);
TI = 0; //
清零串口发送中断标志
清零串口发送中断标志
第 2 页,共 42 页
}
从串口读 1 字节函数 unsigned char uart_get_uchar()。
unsigned char uart_get_uchar() //从串口读 1 字节无符号数据
{
}
while(!RI);
RI = 0;
return SBUF;
以上这几个函数是学单片机的人一定要掌握的,能够随手拿来就用,通过串口调试程序,很方
便。有了以上 4 个函数,再建一个 keil 工程,添加一个主函数,就可以演示了。
initiate_RS232 (); // 串口初始化
for(i=0;i<128;i++){
#include
#include "STC_NEW_8051.H" /// 与 STC12C5A60S2 单片机有关的参数定义
void main() {
ADC_result = get_AD_result(7);//P1.7 为 A/D 当前通道, 测量并发送结果
Send_Byte(ADC_result);
}
}
}
在主程序循环中,读取 ADC 数据并发送一次,在串口调试助手(例如 SSCOM)里,设置相关
端口和波特率后,就可以看到 ADC 的结果,如图 1-2 所示。
第 3 页,共 42 页
图 1-2 串口调试 ADC
调试 ADC 还有一种更方便的方法,结果直接在数码管上显示出来,不管你对数码管硬件熟悉不
熟悉,只要使用模板程序提供的数码管驱动函数 led_disp(uint number)即可。
数码管驱动函数 led_disp(unsigned int)。
{
void led_disp(uint number) //Mini51板数码管显示函数,传入整数 0~9999
{
unsigned char code tab1[20]= {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,};
unsigned char temp,flag=0;
if(number < 10000)
SEG_B = 0xff;//数码管熄灭
flag = 0;
SEG_B = tab1[temp];
flag = 1;
SEG_Q = tab1[temp];
flag = 1;
SEG_Q = 0xff; //数码管熄灭
flag = 0;
temp = number/1000%10; //千位数码管
if (temp) {
}
else {
}
temp = number/100%10; //百位数码管
if(flag | temp)
}
else {
}
temp = number/10%10; //十位数码管
if(flag | temp) SEG_S = tab1[temp];
else SEG_S = 0xff; // 数码管熄灭
temp = number%10; // 个位数码管
{
第 4 页,共 42 页
SEG_G = tab1[temp];
SEG_Q = 0xbf;//"-"
SEG_B = 0xbf;
SEG_S = 0xbf;
SEG_G = 0xbf;
}
else {
}
}
1以上演示源程序 keil 工程请参考附件【串口调试 1】
这里再介绍两款串口绘图软件【MyOsc】和【ComCalWave】,可以直接把串口接收到的数据按
X-Y 轴绘图,显示结果更直观。
主程序这样改:
initiate_RS232 (); // 串口初始化
for(i=0;i<128;i++){
#include
#include "STC_NEW_8051.H" /// 与 STC12C5A60S2 单片机有关的参数定义
void main() {
ADC_result = get_AD_result(7);//P1.7 为 A/D 当前通道, 测量并发送结果
Send_Byte(ADC_result);
}
}
}
运行【MyOsc】,设置串口和波特率后“OPEN”,适当调节输入信号频率后可以看到如图 1-3 所
示的图案。
第 5 页,共 42 页
图 1-3 串口绘图软件示例
运行【ComCalWave】,选择正确的串口号和波特率,“Open COM”,再设置“Wave Show”,看
到了什么?图形!在图形窗口尝试用鼠标右键操作,还可选择特定范围显示,如图 1-4 所示。
图 1-4 串口绘图软件示例 2
要看到以上漂亮的波形,还有一些硬件连接要做,需要将信号发生器和最小系统板上面的 P1.7
口相连接,注意,本 ADC 只能进行 0 到 5V 之间的信号转换,你还需要调整信号发生器,产生满足
条件的信号才行。
1以上演示源程序 keil 工程请参考附件【串口调试 2】
第 6 页,共 42 页
实际在调试程序中,缺少必要硬件设备(如使用普通的 51 单片机且没有 ADC),还可以用正弦表
代替实际 ADC,这里再介绍一款正弦表生成器软件【正弦表发生器】,软件界面如图 1-5 所示。
图 1-5 正弦表发生器
【量化阶数】就是 ADC 位数,例如 STC12C5A60S2ADC 是 10 阶,ADC0809 是 8 阶;
【采样点数】就是在一个正弦周期内,均匀分布多少个采样点,例如在 128 点的 lcd 上显示 2
个以上周期的话,采样点数要小于 64 点,这里选用 30 点数来举例,源程序如下。
0x80,0x9a,0xb4,0xcb,0xdf,0xee,0xf9,0xff,0xff,0xf9,
0xee,0xdf,0xcb,0xb4,0x9a,0x80,0x65,0x4c,0x34,0x21,
0x11,0x6,0x0,0x0,0x6, 0x10,0x20,0x34,0x4b,0x65,
#include "mini51b.h" // 所有与硬件相关的接口函数定义
#include "uart.h"
unsigned char code dot[30]={ // 正弦表,注意数据类型是“code”,存放在 rom 当中
};
void main()
{
}
unsigned char i;
rs232_port_init();
delay_ms(1);
while(1) {
}
for(i=0;i<128;i++) {
}
uart_put_uchar(dot[i%30]);
delay_ms(1);//此处延时当于调节了采样率
用以上调试软件同样可以看到漂亮的正弦信号图形。
以上调试成功后,感觉应该还可以吧,如果你是第一次亲自完成 ADC 将数据采集,再用软件绘
图显示还原信号波形图,一定是一件特别令人激动的事情(Dare to Create and Enjoy)。
第 7 页,共 42 页
2 图形液晶 LCD12864 绘图驱动设计基础(以下均用仿真来代替)
下面我们学习如何在 LCD12864 上显示同样的正弦波形。
关于 LCD 的硬件接口电路,在前面的教程中有详细介绍,涉及单片机总线知识和 CPLD 内部电
路,需要专门学习,这里我们借助现成的驱动函数,重点讲解 LCD 绘图程序设计。
LCD12864 的电路接口在【STC51.h】头文件中定义。
#define LCD_LCW XBYTE[0xf4ea] // 左屏命令写入
#define LCD_LDW XBYTE[0xf5ea] //左屏数据写入
#define LCD_LCR XBYTE[0xf6ea] // 左屏命令读出
#define LCD_LDR XBYTE[0xf7ea] // 左屏数据读出
#define LCD_RCW XBYTE[0xf8ea] // 右屏命令写入
#define LCD_RDW XBYTE[0xf9ea] //右屏数据写入
#define LCD_RCR XBYTE[0xfaea] // 右屏命令读出
#define LCD_RDR XBYTE[0xfbea] //右屏数据读出
后面所有对 LCD 的编程操作都是基于以上接口定义(总线编址)进行的读写操作。
首先来看 LCD 点阵结构图,这里以不带字库的 LCD12864 来讲解,如图 2-1 所示。
图 2-1 LCD 点阵分布结构图
此 LCD 屏由水平 128 列,垂直 64 行组成。水平 128 列分左右各 64 列两个半屏构成。垂直 64
行又分 8 页,每页 8 行(1 列 8 点刚好 1 字节)。程序每次对 LCD 的绘图操作就是以最小单位 1 字
节进行操作的。理解这点至关重要。也就是每次只能针对 8 点进行操作,而不是 1 点进行操作。左
右屏由单独地址线控制(前面的接口定义就是分左右屏定义的)。实际打点只需往指定“位置”写入
数据,“1”亮,“0”暗(在前面的教程里我已经说过,对 LCD 编程实际上就是在对相应的 RAM 单元
进行写“1”操作)。
LCD 驱动函数:忙检测函数 void loop_lcd12864_is_busy(unsigned char right)。
void loop_lcd12864_is_busy(unsigned char right)
{
第 8 页,共 42 页