频率计设计-8086&Proteus仿真&C语言汇编混编

频率计设计

  • 一、设计任务
    • 1.1 课程设计任务
    • 1.2 具体要求
  • 二、系统电路设计
    • 2.1 方案设计与分析
      • 2.2.1 方案设计
      • 2.2.2 方案分析
    • 2.2 8086控制电路设计
    • 2.3 地址总线电路设计
    • 2.4 8253定时/计数电路设计
      • 2.4.1 定时部分
      • 2.4.2 计数部分
    • 2.5 8259中断控制电路设计
    • 2.6 8255并行控制电路设计
    • 2.7 数码管显示电路设计
  • 三、 系统程序设计
    • 3.1 程序整体框架
    • 3.2 8253程序设计
      • 3.2.1 8253 初始化
      • 3.2.2 8253读取计数值
    • 3.3 8259 及中断程序设计
      • 3.3.1 8259初始化编程
      • 3.3.2 8259工作方式编程
      • 3.3.3 中断初始化
      • 3.3.4 中断服务函数
    • 3.4 数码管显示程序设计
      • 3.4.1 模式切换与频率转换
      • 3.4.2 数码管显示程序
  • 四、结果分析
    • 4.1 不同模式下频率显示
    • 4.2 频率测量
  • 五、设计总结

一、设计任务

1.1 课程设计任务

利用所学8086系统的知识设计简易频率计,综合利用所学知识点,通过8253、8259、8255等芯片完成硬件设计,并采用C语言嵌汇编语言编制系统功能程序。最后基于Protues仿真平台进行仿真分析,给出设计的优缺点分析。

1.2 具体要求

  1. 根据给定的题目,进行方案的分析;
  2. 方案设计;
  3. 学习8086及相关芯片手册;
  4. 设计硬件系统电路图;
  5. 编制软件系统,完成基本功能如下:
    测量一定范围(5Hz~50kHz)的信号频率,并将测得的频率显示在LED数码管显示器上。
  6. 在Proteus中仿真运行;
  7. 撰写规范的项目设计报告。

二、系统电路设计

2.1 方案设计与分析

2.2.1 方案设计

本次课程设计采用8086最小系统作为控制系统,74273作为地址锁存器,74154作为地址译码器产生芯片片选信号。利用8253芯片的CT1定时/计数器的OUT1端和CT0定时/计数器的CLK0端级联计时,OUT0端口将计时信号输出到8259芯片的IR7口产生中断。在计时期间,CT2定时/计数器的CLK2端接收待测信号进行计数,直到计时结束后中断产生,从CT2中读出计数值转换为频率值。利用8255并行接口驱动LED数码管,以扫描的方式将转换得到的频率值显示出来。
具体分为高频信号和低频信号两种情况。
对于高频信号:利用8253CT0和CT1级联作为1s定时器——OUT1生成10ms方波,连接到CLK0,从OUT0输出1s的方波。当计时结束即到达1s时,通过OUT0的上升沿触发IR7口产生中断,从而读取CT2计数值M。则被测频率为f = M/1 = M。将频率值转换为十进制显示在数码管上即可。
对于低频信号:利用8253CT0和CT1级联作为10s定时器——OUT1生成10ms方波,连接到CLK0,从OUT0输出10s的方波。当计时结束即到达10s时,通过OUT0的上升沿触发IR7口产生中断,从而读取CT2计数值M。则被测频率为f = M/10。将频率值转换为十进制显示在数码管上即可。

2.2.2 方案分析

  1. 频率测量的方法
    频率测量的典型方法有M法、T法等。
    T法指测量一个完整的脉冲的周期T,则此周期T的倒数即为待测频率。如测得T = 0.1 ms,则信号频率为1/0.0001 = 10000Hz。这种方式对时间的精度要求较高,且适用于较低频信号的测量。
    M法指计数在一定时间Tc内的信号脉冲数M并转换为频率值,如Tc = 1s,计数值M = 1200,则信号频率为1200Hz,适用于一般频率的测量,可操作性高,能满足一定的精度,且对硬件要求较低,因此我们选择M法进行频率的测量,即通过8253的CT1与CT0级联产生计时信号,并通过CT2输入待测信号并对其脉冲进行计数。
  2. 8253芯片门控信号的连接
    8253芯片的三个定时/计数器在工作时都需要将其GATE端口接高电平,对于CT0与CT1可以直接连接至+5V电源处,而CT2由于需要完成测试信号的输入与信号脉冲计数,需要读取当前计数值,其中读取方式有一种需要通过将门控GATE信号置为低电平,因此不宜将其直接连接至电源处,而采用通过8255并行接口的PC端口的某一位进行置位和复位,这样通过软件编程即可控制门控信号。
  3. 10ms方波的产生
    方案中10ms方波的产生也可以采用直接输入激励源的方式,但在查阅文献时发现8253芯片中CT1的CLK1自带内部晶振1.8432MHz,所以为了与实际情况结合,减少激励源个数的使用,我选择通过CT1与CT0级联的方式产生10ms方波,然而由于在Proteus仿真平台中并没有内部晶振的仿真,所以在CLK1处手动输入1.8432MHz的方波模拟内部晶振。
  4. 中断触发计时信号的选择
    而IR7中断触发计时信号也可以采用直接输入1s或10s方波激励源至IR7口的方式,但考虑到通过8253可编程芯片级联产生计时信号方便通过软件配置计数初值实现计时信号周期的随时切换,也更符合实际需求,因此采取以上方案。

根据上述设计思路,系统硬件连接图如图2-1所示。
频率计设计-8086&Proteus仿真&C语言汇编混编_第1张图片
图2-1 硬件连接图

2.2 8086控制电路设计

8086 CPU采用双列直插式封装,具有40条引脚,按其功能分可分为地址总线、数据总线、控制总线以及其它(时钟与电源)。
AD15 ~ AD0为分时复用地址/数据总线。数据总线 D15 ~ D0与地址总线的低16 位A15 ~ A0复用。RD为读信号,低电平有效;WR为写信号,低电平有效,表明CPU向I/O端口的读操作与写操作。READY为准备就绪信号,高电平有效,这里直接连接+5V电源表示I/O时刻准备就绪。INTR为可屏蔽中断请求信号,INTA为中断响应信号,分别与8259的INT和INTA接口相连以传输和响应中断请求。NMI为非屏蔽中断请求信号,这里不需要用到,故可以不接。ALE输出地址锁存允许信号,利用其下降沿把地址信息和BHE信号锁存于地址锁存器中,故要与地址锁存器相连。M/IO为存储器/输入、输出控制信号,可利用此信号结合译码器地址线进行外设片选。由于当前设计8086CPU工作于最小模式,故其引脚需接+5V决定其工作模式。
频率计设计-8086&Proteus仿真&C语言汇编混编_第2张图片
图2-2 8086控制电路硬件连接图

2.3 地址总线电路设计

由于8086CPU的地址/数据总线是分时复用的,在每个总线周期的的第一个时钟周期中作为地址总线的低16位使CPU送出地址信号,在总线周期的其余时间作为数据总线使CPU送出数据信号,故需要使用74273芯片将CPU送出的地址信号进行锁存,这样在总线周期的后半部分地址和数据才能同时出现在系统的地址总线和数据总线上。
另外,由于在所设计系统中由8086CPU产生全部控制信号控制所有芯片,故需要利用一个译码器74154结合或非门等元件将8086CPU送出的地址数据转换成对应外设芯片的片选信号,确保控制对象的准确性。
频率计设计-8086&Proteus仿真&C语言汇编混编_第3张图片
图2-3 地址总线电路硬件连接图

则IOYx片选起始地址分别为:

#define IO0  0x0000
#define IO1  0x0200
#define IO2  0x0400
#define IO3  0x0600
#define IO4  0x0800
#define IO5  0x0A00
#define IO6  0x0C00
……

2.4 8253定时/计数电路设计

2.4.1 定时部分

以利用8253的CT0和CT1级联作为1s定时器为例。
CT0和CT1都工作于方式3模式,即方波发生器。
其工作过程为:

  1. 向CTx(x=0,1)写入方式3控制字后,OUTx输出为高电平。
  2. GATEx保持为高电平时,向CTx写入计数初值后就开始计数(向下递减),OUT输出保持为高。当计数到一半计数初值时,输出变为低,直到计数为0,输出又变为高,重新开始计数。从而产生先高后低的方波。

由此可见,CTx的CLK端输入信号为基准时钟信号,其信号频率即为计数频率;而计数初值决定了输出方波的频率。如当CLK端输入信号频率为f0 Hz,计数初值为N,则方波频率为f0/N Hz,其周期为N/f0 s。
因此,使OUT1端产生10ms方波,可通过激励源模拟CLK1内部晶振,输入1.8432MHz的时钟信号,即f0 = 1.8432M Hz;并写入计数初值为18432,则OUT1输出方波周期为 18432/1.8432M = 0.01s = 10ms。而OUT0端产生1s方波,则将OUT1端输出的10ms方波作为CLK0的输入信号,即f0 = 1/0.01 = 100Hz;并写入计数初值100,则OUT0输出方波周期为 100/100 = 1s。
对于10s定时器,只需要将CT0的计数初值设为1000,则OUT0输出方波周期即为1000/100 = 10s。

2.4.2 计数部分

用8253的CT2实现计数。令其工作于工作方式0,即计数结束后输出由低变高模式。CLK2是待测信号脉冲的输入端,OUT2悬空,初值定为65535,即从65535开始,每一个脉冲周期自减1。
其工作过程为:

  1. 向CT2写入方式0控制字后,计数器输出端OUT2立即变成低电平。
  2. 写入计数初值N后,若GATE为高电平,则计数器开始计数。在计数过程中,OUT端一直维持为低,直到计数为0时,OUT端变为高。

但在本设计过程中,一般不会使CT2计数到0,而是当1s或10s中断到来时读取当前计数值CNT,转换为频率(65535-CNT)/1(对应高频信号)或(65535-CNT)/10(对应低频信号)。
至于CT2读取当前计数值有两种方式可选择。
第一种方法是利用门控GATE信号为低电平或关闭CLK脉冲,使计数操作暂停,以读出确定的计数值。
第二种方法是在计数过程中通过锁存读命令读出计数器值,而不影响计数器的工作。为了使两种方法都可以使用,这里通过8255的PC0的置位与复位控制CT2的门控信号,即当PC0置为1时,GATE为高电平;反之为低电平。
同时,为了在调试时方便对输出波形进行观察,这里还在各输入或输出端口连接示波器。
因此,8253定时/计数电路硬件连接图如下图所示。
频率计设计-8086&Proteus仿真&C语言汇编混编_第4张图片
图2-4 8253定时/计数电路硬件连接图

由此可见,通过译码器的输出IO2进行片选,并通过地址线A2A1选择对应定时/计数器或控制器(注意这里地址线选取至少要从A2A1开始,即不能从A1A0开始,因为要保证地址为偶地址)。则8253中各计数器与控制寄存器的地址分别为:

#define M8253_CT0	IO2+00H*2 //计数器0
#define M8253_CT1	IO2+01H*2 //计数器1
#define M8253_CT2	IO2+02H*2 //计数器2
#define M8253_CTR	IO2+03H*2 //计数器3

2.5 8259中断控制电路设计

8259可编程中断控制器可用于管理8086系列微机系统的外部中断请求,实现优先权的排队、提供终端类型码、屏蔽中断输入等功能,单片8259可以管理8级中断。
在本次设计中,将8253的OUT0的计时信号输入至8259的IR7口,以通过计时信号的上升沿触发IR7产生中断,从而进入中断服务函数读取当前计数值,并将相关的标志位置1。
因此8259中断控制电路硬件连接图如图所示。通过译码器输出IO1进行片选,并通过地址线A1区分8259内部奇地址或偶地址。
频率计设计-8086&Proteus仿真&C语言汇编混编_第5张图片
图2-5 8259中断控制电路硬件连接图

奇地址与偶地址以及各个命令字对应地址分别为:

//8259端口地址
#define	M8259_P0	IO1+00H*8//A0=0,偶地址
#define	M8259_P1	IO1+01H*8//A0=1,奇地址
#define	ICW1		M8259_P0
#define	ICW2		M8259_P1
#define	ICW3		M8259_P1
#define	ICW4		M8259_P1
#define	OCW1		M8259_P1
#define	OCW2		M8259_P0
#define	OCW3		M8259_P0

2.6 8255并行控制电路设计

8255是可编程并行I/O接口芯片,内部有3个8位I/O数据端口,即A口、B口和C口,以及一个8位的控制端口。
令端口A、端口B和端口C均工作于方式0基本输入/输出方式,其中端口A工作于输出状态,PA0-PA7分别控制数码管的a-g以及dp,以配置数码管显示数字;端口B工作于输出状态,PB0-PB5分别对应数码管LED1-LED6,以选择对应数码管使之点亮;端口C的PC0用于控制8253CT2的门控信号,可用C口置位/复位控制字单独配置。
因此,8255并行控制电路硬件连接图如图所示。通过IO6作为片选信号,并通过与地址线A2A1以及读写信号的配合进行各端口或控制寄存器的访问。
频率计设计-8086&Proteus仿真&C语言汇编混编_第6张图片
图2-6 8255并行控制电路硬件连接图

8255各端口地址分别为:

#define M8255_A		IO6+00H*2//A(字型位)端口地址
#define M8255_B		IO6+01H*2//B(字位位)端口地址
#define M8255_C		IO6+02H*2//C端口地址
#define M8255_MODE	IO6+03H*2//控制器地址

2.7 数码管显示电路设计

7SEG-MPX6-CC为8段六位共阴极数码管,其中ABCDEFG和DP为段选引脚,为阳极,可控制数码管显示字型;123456为片选引脚,为阴极,可选择显示哪一位数码管。因此,如果要实现动态显示数字必须通过扫描的方式,即以片选引脚1-6轮流通低电位,并以段选引脚给各段通高或低电平来使当前片选数码管显示对应数字。
数码管显示电路硬件连接图如图,其中段选引脚连接8255的PA端口,片选引脚连接8255的PB端口。
频率计设计-8086&Proteus仿真&C语言汇编混编_第7张图片
图2-7 数码管显示电路硬件连接图

三、 系统程序设计

3.1 程序整体框架

程序采用C语言嵌汇编的形式编程。主要包括对片选地址、外设端口地址的定义、对各芯片与中断的初始化、中断服务函数处理以及while死循环中的频率处理与显示等。程序流程图如下图所示:
频率计设计-8086&Proteus仿真&C语言汇编混编_第8张图片
图3-1 整体程序流程图

3.2 8253程序设计

3.2.1 8253 初始化

8253工作之前,CPU需要对其进行初始化编程。8253有3个计数器通道,需逐个对各计数器分别进行初始化。8253的方式控制字和读/写操作如图所示。
频率计设计-8086&Proteus仿真&C语言汇编混编_第9张图片
图3-2 8253的方式控制字格式

由于CT0和CT1均工作于方式3,CT2工作于方式0,且读/写方式均为先读/写低8位,再读/写高8位,数制均选择二进制,因此各定时/计数器的控制字分别为00110110(0x36),01110110(0x76),10110000(0xb0)。

CPU向 8253的控制器地址依次写入方式控制字之后,接着按方式控制字约定的数据读/写格式及顺序要求写入计数初值
由于CT1要求输出10ms方波,而其输入时钟频率为1.8432M,因此写入计数初值为 1.8432M0.01 = 18432;而CT0要求输出1s方波或10s方波,而其输入时钟信号为10ms方波,因此定义变量sec表示输出方波周期,写入计数初值为sec100。对于CT2,作为计数器,设置其初值为65535,使其从65535开始每接收一个脉冲自减1。
因此8253的初始化程序如下:

void Init_8253(char sec)
{
	//控制字——CT0,从低到高读写,方式3,二进制
	outp(M8253_CTR,0x36);
	//控制字——CT1,从低到高读写,方式3,二进制
	outp(M8253_CTR,0x76);
	//控制字——CT2,从低到高读写,方式0,二进制
	outp(M8253_CTR,0xb0);
	//设置CT1初值
	outpx(M8253_CT1,18432);
	//设置CT0初值
	outpx(M8253_CT0,100*sec);
	//设置CT2初值
	outpx(M8253_CT2,FREQ_MAX);//FREQ_MAX为测量频率范围
}

3.2.2 8253读取计数值

在中断服务函数中,需要读取CT2当前计数值。
读取计数器当前值有两种方法。第一种方法是利用门控GATE信号为低电平或关闭CLK脉冲,使计数操作暂停,以读出确定的计数值。第二种方法是在计数过程中通过锁存命令锁存计数值后再读出,而不影响计数器的工作。
这里以第二种方法为例。首先给CT2端口地址输出控制字10000000(0x80),从而将计数值锁存到计数锁存器中。接着通过两条连续的IN制令先读取低位再读取高位。将读取到的计数值存于一个变量后,再向CT2端口地址重新写入读写控制字10110000(0xb0),并重置设置CT2计数初值,使其重新开始计数。
则读取计数值的程序如下:

//控制字——计数器2,计数器锁存命令,方式0,二进制
	outp(M8253_CT2, 0x80);
//读取当前计数值
	_asm
	{	
	       MOV DX,M8253_CT2 
	       IN AL,DX
	       MOV BL,AL
          IN AL,DX
	       MOV BH,AL
	       MOV result,BX  
	}
//重新写回读写制字
	outp(M8253_CT2, 0xB0);
//重新设置CT2计数初值,使CT2重新开始计数
	outpx(M8253_CT2,FREQ_MAX);

这里注意一点,按我自己的理解来说,控制字命令是一定要写到M8253_CTR控制寄存器才可以的,但是在读取计数值这里,无论是写锁存命令还是重新写回读写控制字,一旦写到控制寄存器地址都会出错,而只有像上面那样写到M8232_CT2端口地址才可以(只有在初始化的时候才可以写到控制器地址上)。这是与教材上所介绍的不同的,也许是Proteus的Bug?我并没有完全理解。在查阅资料与询问他人时也有人提到8253与8254的区别,前者可以直接对各计数器地址操作,只有后者才要对控制器地址操作。对此我并没有去证实过,希望读者留意。

3.3 8259 及中断程序设计

3.3.1 8259初始化编程

在8259工作前,首先要进行初始化编程,其共有4个初始化命令字——ICW1,ICW2,ICW3和ICW4。
初始化命令字 ICW1。ICW1用于规定 8259的连接方式(单片或级联)和中断源请求信号的有效形式(电平触发或边沿触发)。当CS’=0、A0=0、D4=1时,表示当前写入8259的是ICW1命令字,其格式如图:
频率计设计-8086&Proteus仿真&C语言汇编混编_第10张图片
图3-3 ICW1命令字格式

在本程序设计中,所选CPU为8086,采用上升沿边沿触发,只需单片8259,因此ICW1的控制字为00010011(0x13)。
初始化命令字ICW2。ICW2命令字用于设置中断类型码基值。所谓中断类型码基值,是指0级中断源IR0所对应的中断类型码,它是一个可被8整除的正整数。ICW2的格式如图:
频率计设计-8086&Proteus仿真&C语言汇编混编_第11张图片
图3-4 ICW2命令字格式

其中,低3位必须为0,高5位是由用户编程设定的中断类型码基值。在8259接收到CPU发回的中断响应信号INTA’后,便通过数据总线向CPU送出中断类型码字节。该字节的高5位即为ICW2的高5位,低3位根据当前CPU响应的中断是IR0~IR7中的哪一个而自动确定,分别对应000~111。
在本程序设计中,我们设定中断类型码(中断向量号)基值20H,则8259的IR0-IR7对应的中断向量号分别为20H-27H。使用IR7作为触发中断,则当CPU响应中断请求时,8259把IR7的编码111作为低3位构成一个完整的8位中断类型码27H,经数据总线发送给CPU。则ICW2控制字为00100000(0x20)。
初始化命令字ICW3。ICW3命令字仅用于 8259级联方式。这里不需要用到。
初始化命令字ICW4。对于8086系统,ICW4是必须设置的初始化命令字,该命令字规定 8259的工作方式、中断优先顺序和中断结束方式等,其格式和各位意义如图:
频率计设计-8086&Proteus仿真&C语言汇编混编_第12张图片
图3-5 ICW4命令字格式

在本程序设计中,采用一般全嵌套方式,设置为非缓冲方式和非自动EOI结束方式,CPU为8086,则ICW4控制字为00000001(0x01)。

3.3.2 8259工作方式编程

对8259进行初始化编程后,8259进入工作模式。可以通过向8259写入工作命令字OCW1~OCW3来定义和修改8259的工作方式,工作命令字OCW1~OCW3的写入是可以根据需要随时和重复进行的。
工作命令字OCW1。该命令字用来设置中断源的屏蔽状态并写入 IMR 中,其格式如图:
在这里插入图片描述
图3-6 OCW1命令字格式

当 Mi=1时,表明相应中断源IRi的中断请求被屏蔽,8259不会产生发向 CPU 的INT信号;当Mi=0时,表明相应中断源IRi的中断请求未被屏蔽,8259 可以产生发向CPU 的INT信号,请求CPU服务。在本程序设计中,我们设置IR7为中断源(只有D7=0),并且屏蔽其它的中断源,则OCW1控制字为01111111(0x7f)。
由于OCW1一般只需要在初始化的时候进行配置,因此将ICW1,ICW2,ICW4和OCW1依次进行设置作为8259的初始化程序,即:

void Init_8259(void)
{
	//ICW1的设置——A0=0
	outp(ICW1,0x13);
	//ICW2的设置——A0=1
	outp(ICW2,0x20);
	//ICW4的设置——A0=1
	outp(ICW4,0x01);
	//OCW1的设置——A0=1
	outp(OCW1,0x7f);
}

工作命令字 OCW2。OCW2工作命令字用于控制中断结束方式及修改优先权管理方式。其格式如图:
频率计设计-8086&Proteus仿真&C语言汇编混编_第13张图片
图3-7 OCW2命令字格式

在每一次中断服务函数处理完即将结束时,都需要通过OCW2命令配置为正常EOI中断结束方式,即一旦中断服务程序结束,将给8259送出EOI结束命令,8259将ISR寄存器中当前最高优先级的中断请求位由1清为0。则OCW2控制字为00100000(0x20)。具体程序为:

//OCW2的设置——A0=0(Port0),D4D3=00
outp(OCW2,0x20);

3.3.3 中断初始化

中断初始化包括中断向量表的配置和8259初始化。
配置中断向量表需要在所配置中断向量号对应的内存里填入中断服务函数的入口地址。在8259初始化中配置了中断向量号的基值为20H,而所用的中断为IR7中断,则其中断向量号(27H)在中断向量表中的物理地址为27H*4=9CH。因此需要在ES:SI = 0000:009C处填入中断服务函数的偏移地址IP,使触发IR7中断时可以根据其中断号获取中断服务函数的入口地址。至于中断服务函数的段地址,由于CS的地址为IP+2,则填写在ES:[SI+2]处。
中断初始化函数如下:

void Init_Interrupt(void)
{
	//中断向量表配置
	__asm
	{
		MOV AX,0
		MOV ES,AX
		MOV SI,0x9C	
		MOV AX,OFFSET Interrupt7	//获取中断服务函数偏移地址
		MOV ES:[SI],AX	//填偏移量矢量IP
		MOV AX,SEG Interrupt7//获取中断服务函数段地址
		MOV ES:[SI+2],AX	//填段地址矢量CS		
	}
	//初始化8259
	Init_8259();
}

3.3.4 中断服务函数

在中断服务函数中需要完成的操作有:
(1)关闭中断并进行现场保护,即将各通用寄存器入栈;
(2)读取CT2当前计数值——包括给CT2置锁存控制字、读取低高位、回写读写控制字以及重新配置CT2计数初值;
(3)将当前计数值与计数初值的差值作为频率计数值;
(4)将标志位置1以在main函数中进行频率转换;
(5)配置OCW2命令字,以正常EOI结束方式结束中断;
(6)进行现场恢复并重新开启中断。
则中断服务函数如下:

void Interrupt7(void)
{
	int j;
	//现场保护
	_asm
	{
		CLI	//关闭中断
		PUSH AX
		PUSH DX
		PUSH BX
		PUSH CX
	} 
	outp(M8253_CT2, 0x80);//给CT2置控制字(锁存)
	_asm
	{	
			//读取计数值
	       MOV DX,M8253_CT2 
	       IN AL,DX
	       MOV BL,AL
           IN AL,DX
	       MOV BH,AL
	       MOV result,BX  
	}
	outp(M8253_CT2, 0xB0);//重新写回控制字
	outpx(M8253_CT2,FREQ_MAX);//重新设置CT2计数初值
	Freq_Count = (unsigned int)FREQ_MAX - result;	//获取频率计数值
	flag = 1;
	
	//OCW2的设置
	outp(OCW2,0x20);//0010 0000——正常EOI中断结束命令

	//现场恢复
	_asm
	{
		POP CX
		POP BX	
		POP DX
		POP AX
		STI	//重新开启中断
	}
}

3.4 数码管显示程序设计

3.4.1 模式切换与频率转换

模式切换
程序默认为高频模式,即以1s中断测量频率。因此需要通过对中断服务函数中获取的频率计数值Freq_Count的大小进行判断以决定是否切换为低频模式,即当在高频模式下但Freq_Count小于一定值时,通过重新初始化CT0、CT1计数初值以切换为低频模式(即10s中断)。但注意切换的模式只能在下一个周期中生效。对于正常高频信号,则直接进行频率转换。
注意:由于在仿真时Proteus一直有bug,在程序运行时频繁对8253初始化以及过于复杂的程序结构(即使是用C语言汇编混编也是如此)似乎容易使中断不稳定或变量值异常,所以后面在调试时频率切换没能实现,而是对于可测频率范围内都是以高频模式处理。
频率转换
所谓频率转换,是指频率计数值转换为各位显示数组,即将二进制数对应的十进制数转换为六位单独的数字,分别存于Dis_Freq[]数组的6个位中。Dis_Freq[]作为表项序号,对应着LED数码管编码表LED[]中的各个编码,通过Dis_Freq即可获取六位数的对应编码,以在对应数码管显示。
LED数码管编码表如下图所示:
在这里插入图片描述
图3-8 LED数码管编码表

高频信号,为无小数点显示模式:利用除10与余的运算,每一次除以10得到的余数即为十进制对应位的数。如十进制12345/10=1234,12345%10=5,则BUF[5]=5;再1234/10=123,%10=4,BUF[[4]]=4…最后BUF={0,1,2,3,4,5}。
低频信号,为小数点后1位显示模式:只有在倒数第二位才需要显示小数点,其余与高频处理类似。如测得计数值1234(十进制),低频信号要除以10s,即频率为123.4。则有:
1234/10=123,1234%10=4,使BUF[5]=4;123/10=12,123%10=3 -> ‘3.’,使BUF[[4]] = 3+10 (对应LED表编码即为’3.’ )…最终BUF={0,0,1,2,3+10,4},对应数码管显示’00123.4’。
具体程序设计如下:

void Freq_Convert(void)
{
	if(Mode == MODE_HF)//若为高频模式
	{
		if(Freq_Count<2000)
		{
			Mode_Change();//切换为低频模式(即10s中断)
		}
		Freq_To_Disp(Freq_Count,MODE_HF);//频率计数值转换为各位显示数组
	}
	else	//若为低频模式
	{
		if(Freq_Count>20000)
		{
			Mode_Change();//切换为高频模式(即1s中断)
		}
		Freq_To_Disp(Freq_Count,MODE_LF);//频率计数值转换为各位显示数组(低频,此时有小数点)
	}
}
void Freq_To_Disp(unsigned int freq, char mode)
{
	int i,j;
	Init_Dis();
	switch(mode)
	{
		case MODE_HF:
		{
			for(i = 5; i >= 0; i--)
			{
				Dis_Freq[i] = freq % 10;	//除以10所得余数存放低位
				freq = freq / 10;	//更新被除数
				if(freq == 0)
					return;
			}
		}
		break;
		case MODE_LF:
		{
			for(i = 5; i >= 0; i--)
			{
				if(i = 4)//倒数第二位,要将数字映射为对应的带小数点的数(如 8 -> 8.)
					Dis_Freq[i] = freq % 10 + 10;//对应于Led[]由'x' -> 'x.'
				else	//其余与高频处理一致
					Dis_Freq[i] = freq % 10;	//除以10所得余数存放低位
				freq = freq / 10;	//更新被除数
				if(freq == 0)
					return;
			}
		}
		break;
		default:
		break;
	}
}

3.4.2 数码管显示程序

数码管显示要采用扫描的形式,依次点亮L1-L6。对于每一个数码管,首先输出片选信号,选择要点亮的数码管;再输出段选信号(LED编码值),以显示对应数字。注意最好要先定位,再定型——否则在下一次循环来时会先在上一位置显示一下再点亮下一位置。
当6个数码管依次点亮一次(扫描一遍),使所有数码管为暗,直到下一次扫描。
具体程序设计如下:

void LED_Display(void)
{
	int i;//数码管扫描,L1-L6依次点亮
	for(i = 0; i < 6; i++)
	{
		//输出字位位——要点亮的数码管	
		outp(M8255_B,Led_CH[i]);//依次点亮L1,L2,...,L6
		//输出字型位——要显示的数字
		outp(M8255_A,Led[Dis_Freq[i]]);
		//delay_asm(0x0100);//延时,使当前数码管显示一段时间
		delay(1);
	}
	//六个数码管显示完后(速度很快,只扫一遍),使所有数码管为暗
	outp(M8255_B,0xff);
}

四、结果分析

4.1 不同模式下频率显示

对于高频信号,为无小数点显示模式。如对f = 12345Hz的高频信号,数码管上显示为:
频率计设计-8086&Proteus仿真&C语言汇编混编_第14张图片
图4-1 高频模式下显示

对于低频信号,为小数点后1位显示模式。如对f = 1232.5Hz的低频信号,数码管上显示为:
频率计设计-8086&Proteus仿真&C语言汇编混编_第15张图片
图4-2 低频模式下显示

4.2 频率测量

如图4-3所示,通过CLK2输入待测信号CLK2_TEST。点击激励源即可配置待测频率的频率大小。
频率计设计-8086&Proteus仿真&C语言汇编混编_第16张图片
图4-3 待测信号输入

在实际调试中发现,Proteus在运行仿真时会有bug,即中断进入若干次后便不能进入了,而对于10s中断而言很难保持稳定,对于低频信号频率测量有较大影响。因此最终在程序中,对高低频信号都采用1s中断。由于本频率计频率范围有限(10Hz~60000Hz),因此经测试发现1s中断基本上能满足低频和高频信号的要求。

当输入50Hz待测信号时,数码管上显示值稳定于50、51之间(如图4-4),误差约为1%~2%;
频率计设计-8086&Proteus仿真&C语言汇编混编_第17张图片
图4-4 50Hz待测信号输入

当输入500Hz待测信号时,数码管上显示值基本上稳定于495-595之间(如图4-5),误差约为1%;
频率计设计-8086&Proteus仿真&C语言汇编混编_第18张图片
图4-5 500Hz待测信号输入
当输入5000Hz待测信号,数码管上显示值稳定于4995-5050之间(如图4-6),误差约为1%。
频率计设计-8086&Proteus仿真&C语言汇编混编_第19张图片
图4-6 5000Hz待测信号输入

当输入50000Hz待测信号,数码管上显示值稳定于49980-50500之间(如图4-7),误差约为0.5%-1%。
频率计设计-8086&Proteus仿真&C语言汇编混编_第20张图片
图4-7 50000Hz待测信号输入

由此可见,在一定的频率范围内,本频率计所测得的频率与设置频率基本一致,测量误差约为1%。因此,系统设计满足要求。
上述待测信号均为方波信号
经仿真试验得知,当输入为较低频率的正弦波时(如图4-8所示),也能准确的测量。然而当频率大于1000Hz时,则中断会变得很不稳定导致无法测量。
频率计设计-8086&Proteus仿真&C语言汇编混编_第21张图片
图4-8 待测信号为正弦波

如以下为当输入为500Hz,振幅为5V的正弦波时的测量结果,其显示值在495-505间波动。
频率计设计-8086&Proteus仿真&C语言汇编混编_第22张图片
图4-9 500Hz待测正弦波输入

不足:由于选择测量频率方式的限制,当方波信号频率低于10Hz时或大于60000Hz时,会有较大误差。且频率太低会使得测量频率与计时信号频率接近,使中断不稳定,而计数器的计数范围则限制了最大频率不能超过65535。这导致了本频率计的频率测量范围有限。对于非方波信号如正弦信号,其测量范围则更小。这是其局限性。

五、设计总结

略。

注:由于Proteus仿真软件隐含着千千万万的坑,代码逻辑完全正确、没有任何语法错误不代表仿真能够通过,会有很多奇怪的错误类型和仿真错误,这给整个调试过程增添了极大的难度,容易陷入“是代码哪里出错了还是仿真模型有bug”的泥潭中。因此,调试的过程是很关键也很重要的。通过单步调试,断点设置和对寄存器值或变量值的检测,找到错误,改善代码,从而实现基本功能。

附:
(1) 本设计思路参考《微机原理课程设计频率计》。如若侵权,请联系本人修订删除。
(2) 完整工程与源代码请访问以下链接获取。
微机原理频率计设计-Proteus仿真&C语言汇编混编.zip
(3)本文含作者原创内容。转载请注明来源。

你可能感兴趣的:(c语言,proteus,单片机,asm)