2.1实验1:汇编程序实验1 2.1.1示例程序实验 (1)实验内容
在Keil环境下建立工程,并将以下程序加入工程,构造工程,并运行可执行程序,记录执行结果。分析程序功能。 (2)添加注释后的程序 ORG 0000H AJMP MAIN ORG 0030H MAIN:
MOV SP,#60H ;堆栈底设置在60H
MOV A,#0H ;A置零
MOV R1,#30H ;起始单元地址为30H MOV R7,#10H ;R7用于计数,初值为10H
LOOP1: ;将30H开始的16个内存单元置零 MOV @R1,A INC R1 DJNZ R7,LOOP1 NOP
MOV R1,#30H
MOV R7,#10H ;重新赋计数初值和起始单元地址 LOOP: ;将0到F赋给30H开始的16个单元中; MOV @R1,A
INC R1 ;地址自增 INC A ;A自增 DJNZ R7,LOOP SJMP $ END
(3)示例程序的功能
将30H开始的16个内存单元清0,然后将值0到F赋给30H开始的16个单元。 运行结果截图
2.1.2自我完成实验 (1)实验内容
将片内RAM 30H单元中的8位二进制数转换成10进制数。希望转换后的结果保存于31H和32H,31H低4位存放个位,高4位存放十位,32H低4位存放百位,高4位为0。 程序流程图:
(2)程序及其注释 org 0000h ajmp main org 0030h main:
mov 30h,#68h ;待转化的数存于30H单元 mov a,30h ;取数
mov b,#64h ;100赋给B
div ab ;待转化的数除以100 clr 32h ;清32H单元
mov 32h,a ;商为十进制百位,存于32H mov a,b mov b,#0ah
div ab ;余数赋给A,余数除以10
swap a ;商为个位,自交换后存于A高四位 add a,b
mov 31h,a ;A,B相加可以将十进制数个位十位存于31H单元 sjmp $ end
(3)实验过程
1、新建工程:二进制十进制转化.uV2;
2、设置工程:二进制十进制转化.uV2属性,将其晶振频率设置为12MHz,选择输出可执行文件,仿真方式为“Use Simulator”;
3、建立源程序,编写程序,程序编写思路:将待转化数存于30H单元,取出待转化的数给A,将待转化的数除以100,商位于A,余数在B。32H单元清零,前一步所得的商为十进制百位,将其存于32H单元,取余数除以10,A中的商为待转化十进制数的十位。A自交换,十位的值存于A高四位,将其和B中的值相加,和存于31H单元,即实现了十进制数个位十位存于31H单元。
4、将源程序保存为:二进制十进制转化.asm,并且添加到工程中;
5、编译源程序,如果输入有误进行修改,直至编译通过,生成可执行程序:二进制十进制转化.hex;
6、运行程序,并用存储器观察窗口,观察内部RAM 30H,31H,32H单元的值。 (4)程序运行结果截图
2.2实验2:汇编程序实验2 2.2.1示例程序实验 (1)实验内容
在Keil环境下建立工程,并将以下程序加入工程,构造工程,并运行可执行程序,记录执行结果。分析程序功能。 (2)示例程序 ORG 0000H AJMP MAIN ORG 0030H MAIN:
MOV 30H, #45H ;待操作数存于30H单元 MOV A, 30H ;30H单元中的数送A
ANL A,#0FH ;高四位清零,保留低四位 MOV 31H,A ;结果存于31H单元 MOV A,30H ;重新取待操作数至A ANL A, #0F0H ;低四位清零,保留高四位
SWAP A ;自交换实现待操作数的高四位表示的二进制数存于A MOV B, #10
MUL AB ;待操作数的高四位表示的二进制数乘以10
ADD A,31H ;待操作数的高四位表示的二进制数乘以10后和低四位表示的二进制数相加 MOV 31H,A ;存结果于31H单元 SJMP $ END
(3)示例程序的功能:
实现求得一个八位二进制数高四位表示的二进制数乘以10和低四位表示的二进制数的和。如为两位压缩BCD码,则实现求得相应十进制数值,并以二进制形式存于内存单元中。 (4)运行结果截图
2.2.2自我完成实验 (1)实验内容
将片内RAM 30H开始的32个单元中分布着随机的有符号8位二进制数,请按从小到大的顺序进行排序,排序后的数据仍然保存到30H开始的32个单元中(低地址存放小数据)。 编程思路:
首先,在程序存储器中构建一个表格,该表格具有32个随机产生的8位二进制数,如: TABLE: DB 1,3,9,2,17,4,11,6
DB 5,20,100,64,21,14,79,35 DB 92,7,91,23,65,16,13,18 DB 18,73,65,101,27,19,62,69
然后利用查表指令“MOVC A,@A+DPTR”将它们读取到30H-4FH单元中, 然后再利用“冒泡法排序法”将它们排序即可。 “冒泡法排序法”的基本原理是:
遍历所有32个数据找出其中的最大者,并记下最大数据所在的存储位置,然后将这个最大的数据放置在最后一个单元,同时,将最后一个单元原来的数据保存到这个最大值原来所处的位置,完成第一轮的排序。
然后,再遍历除了最后一个单元以外的前面31个单元的数据并找出其中最大者,并记下其所在位置,遍历完这一遍后将找到的最大数据保存在倒数第二个单元(对于所有数据来说它是次最大数据,所以保存在倒数第二个单元),并将倒数第二个单元原来的数据保存在刚刚找到的那个最大值原来所在的位置处,完成第二轮的排序。依次类推,用同样的方法把所有的数据排好序即可。
(2)程序及其注释 ORG 0000H AJMP MAIN ORG 0030H MAIN: MOV R0,#20H ;R0用于计数,20H=32 MOV R1,#30H ;R1为数据指针 MOV DPTR,#TABLE ;DPTR指向表首 MOV R2,#00H ;偏移量赋值 MOV A,R2
STO:MOVC A,@A+DPTR ;取出表中数据,存入30H开始的内存单元 MOV @R1,A ;取出数据存入R1中存储地址指向的内存单元 INC R1 ;指向下一个单元
INC R2 ;偏移量增一 MOV A,R2 DJNZ R0,STO ;判断表中数据是否全部取出并存入内存 MOV R3,#1FH ;内循环计数(31) MOV R4,#1FH ;外循环计数(31) MOV R1,#30H
LOOP1: CLR C ;C清0 MOV A,@R1 INC R1 MOV R5,A SUBB A,@R1 ;取出两个数相减置C来比较大小 MOV A,R5 JC LOOP2 ;第一个数小于第二个数,跳转LOOP3 XCH A,@R1 ;否则交换两单元中数的位置,小的在前大的在后 DEC R1
MOV @R1,A INC R1 ;指向下一单元
LOOP2:DJNZ R3,LOOP1 ;判断在一轮中是否比较完了所有的数 DEC R4 MOV A,R4 MOV R3,A ;内外循环计数值分别自减 MOV R1,#30H ADD A,#1 MOV R4,A DJNZ R4,LOOP1 ;判断外循环是否进行完毕,即排列完所有的数,否则跳回继续比较
ORG 3000H ;另外开辟空间,建立待排序数的表格 TABLE: DB 1,3,9,2,17,4,11,6
DB 5,20,100,64,21,14,79,35 DB 92,7,91,23,65,16,13,18 DB 18,73,65,101,27,19,62,69 HERE: SJMP HERE END (3)实验过程
1、新建工程:冒泡法排序.uV2;
2、设置工程:冒泡法排序.uV2属性,将其晶振频率设置为12MHz,选择输出可执行文件,仿真方式为“Use Simulator”;
3、建立源程序,编写程序,按照冒泡法排序的思路编写程序,大体思路可见源程序注释。 4、将源程序保存为:冒泡法排序.asm,并且添加到工程中;
5、编译源程序,如果输入有误进行修改,直至编译通过,生成可执行程序:冒泡法排序.hex; 6、运行程序,并用存储器观察窗口,观察内部RAM 30H-4FH单元排序前后的数值。
(4)程序运行结果截图
2.3 实验三:C语言程序实验 实验内容
将片内RAM 30H开始的32个单元中分布着随机的有符号8位二进制数,请按从小到大的顺序进行排序,排序后的数据仍然保存到30H开始的32个单元中(低地址存放小数据)。 实验程序
#include #define uchar unsigned char #define uint unsigned int uchar data a[32] _at_ 0x30; uint i _at_ 0x55; uint j _at_ 0x56; uint temp _at_ 0x57; uint n _at_ 0x58; void main() { char table[32]={1,3,9,2,17,4,11,6,5,20,100,64,21,14,79,35,92,7,91,23,65,16,13,18,18,73,65,101,27,19,62,69}; char i,j,temp; for(i=0;i<32;i++) {a[i]=table[i];} for(i=0;i<31;i++) { for(j=0;j<31-i;j++) { if(a[j]>a[j+1]) { temp=a[j]; a[j]=a[j+1]; a[j+1]=temp; } } } while(1); } 程序与运行结果截图 第三章Proteus系统仿真实验 3.1实验一:基本并行I/O口实验 (1)实验内容 在Proteus环境下搭建如下图2.5所示的电路图: U119XTAL1P0.0/AD0P0.1/AD1P0.2/AD2P0.3/AD3P0.4/AD4P0.5/AD5P0.6/AD6P0.7/AD7P2.0/A8P2.1/A9P2.2/A10P2.3/A11P2.4/A12P2.5/A13P2.6/A14P2.7/A15P3.0/RXDP3.1/TXDP3.2/INT0P3.3/INT1P3.4/T0P3.5/T1P3.6/WRP3.7/RD39383736353433322122232425262728101112131415161718XTAL29RSTD1D2D3D4D5D6D7D8R1R2R3R4R5R6R7R8100100100100100100100100R9100293031PSENALEEASW112345678P1.0P1.1P1.2P1.3P1.4P1.5P1.6P1.7AT89C51PROGRAM=basicIO.HEX 图3.5 basicIO原理图2 其中所用元器件如下表所示: 选件编号 U1 SW1 D1-D8 R1-R9 元件名称 AT89C51 BUTTON LED-RED RES 参数 100 所在元件库类名 Microprocessor ICs 微处理器 Switches & Relays 开关与继电器 Optoelectronics 光电子器件 Resistors 子类名 8051 Family 8051家族 Switches 开关 LEDs 发光二极管 Generic 生产厂家 ATMEL 电阻 一般的 实验功能为: 当按键SW1按下之后,D1-D8轮流点亮,点亮时间为100ms,当按键停下后,停止轮换,再次按下后继续轮换。 (2)编程思路 ①进行初始化工作,包括设置堆栈指针SP,将P2口所有位设置为1,使P2口所接发光二极管全部熄灭。将显示缓冲单元(设为20H单元)初始化为FEH。 ② 从P1口读数据,查看P1.0位,如果P1.0位为0,则执行如下循环:将显示缓冲单元的值送给P2口,调用100ms延时程序,将显示缓冲单元的值循环左移1位,再送回显示缓冲单元。如果P1.0位不为0则不执行上述循环。 ③重复上面的操作② (3)实验步骤 ① 根据上述实验内容,参考2.1.1,在Proteus环境下建立图2.5所示原理图,并将其保存为basicIO_self.DSN文件。 ② 根据(2)和(3)编写控制源程序,将其保存为basicIO_self.asm。 ③ 将源程序添加到U1中,并构造(build)该程序。 ④ 执行仿真过程观察D1-D8的指示,查看程序功能是否正确。 ⑤ 修改延时程序延时参数,重新执行③和④。 (4)实验程序 ORG 0000H AJMP MAIN ORG 0030H MAIN: MOV SP,#60H MOV P2,#0FEH ;显示缓冲单元初始化 LOOP1: JB P1.0,LOOP1 ;从P1读数据,查看P1.0位, P1.0位为0,则执行如下 循环 MOV A,P2 RL A ;左移 MOV P2,A ;将显示缓冲单元的值送给P2口 acall DL100ms ;调用100ms延时 SJMP LOOP1 DL100ms: MOV R7,#200 DL: MOV R6,#250 DJNZ R6,$ DJNZ R7,DL RET END (6)实验截图 实验二:扩展并行I/O口实验 (1)实验内容 在Proteus环境下搭建如下图3.7所示的电路图: U119XTAL1P0.0/AD0P0.1/AD1P0.2/AD2P0.3/AD3P0.4/AD4P0.5/AD5P0.6/AD6P0.7/AD7P2.0/A8P2.1/A9P2.2/A10P2.3/A11P2.4/A12P2.5/A13P2.6/A14P2.7/A15P3.0/RXDP3.1/TXDP3.2/INT0P3.3/INT1P3.4/T0P3.5/T1P3.6/WRP3.7/RD39383736353433322122232425262728101112131415161746574LS3213274LS32D[0...7]D0D1D2D3D4D5D6D7347813141718111474LS04D0D1D2D3D4D5D6D7D[0...7]D[0...7]D0D1D2D3D4D5D6D7347813141718111U4D0D1D2D3D4D5D6D7OELE74LS373Q0Q1Q2Q3Q4Q5Q6Q72RD_N5RD_S6RD_E9RD_W12YD_N15YD_S16YD_E19YD_W18R1100R2100R3100XTAL29RSTRD_NGD_NYD_NGD_NRD_NYD_N293031PSENALEEAU3:A1274LS04R4100R5100R6100R7100R8100R9100U2:A12345678P1.0P1.1P1.2P1.3P1.4P1.5P1.6P1.7AT89C51RD_WGD_EYD_EU5D0D1D2D3D4D5D6D7OELE74LS373RD_SRD_EGD_EYD_EU2:BQ0Q1Q2Q3Q4Q5Q6Q72GD_N5GD_S6GD_E9GD_W12151619GD_WRD_WYD_WGD_ERD_ERD_SGD_SYD_SU3:B3R10100GD_SR11100YD_SR12100YD_E 图3.7 ExpandIO2原理图 其中所用元器件如下表所示: 选件编号 U1 元件名称 AT89C51 参数 所在元件库类名 Microprocessor ICs 微处理器 TTL 74LS series TTL 74LS系列 TTL 74LS series TTL 74LS系列 TTL 74LS series TTL 74LS系列 Optoelectronics 光电子器件 Optoelectronics 光电子器件 Optoelectronics 光电子器件 子类名 8051 Family 8051家族 生产厂家 ATMEL U2 74LS32 Gates& Inverters 门与反相器 Gates& Inverters 门与反相器 Flip-Flops & Latches 触发与锁存 LEDs 发光二极管 LEDs 发光二极管 LEDs 发光二极管 U3 74LS04 U4,U5 74LS373 RD_N,RD_S,RD_E,RD_W YD_N,YD_S,YD_E,YD_W GD_N,GD_S,GD_E,GD_W LED-RED LED-YELLOW LED-GREEN R1-R12 RES 100 Resistors 电阻 Generic 一般的 实验功能为: 仿真实现交通信号灯控制功能。状态切换顺序为: 首先是南北绿灯亮10s,同时东西红灯亮;然后南北黄灯亮2s,同时东西红灯亮;再是南北红灯亮,东西绿灯亮10s;继续轮换。 (2)实验程序 ORG 0000H AJMP START ORG 0030H START: MOV SP,#60H LOOP: LCALL Start1 ;LCALL为子程序调用 LCALL DL10S;状态一延时10ss LCALL Start2 LCALL DL2S LCALL Start3 LCALL DL10S LCALL Start4 LCALL DL2S SJMP LOOP;相对转移P Start1: MOV A,#0F3H MOV DPTR,#U4 MOVX @DPTR,A;访问片外 MOV A,#0CH MOV DPTR,#U5 MOVX @DPTR,A RET;中断返回 Start2: MOV A,#0C3H MOV DPTR,#U4 MOVX @DPTR,A MOV A,#0FH MOV DPTR,#U5 MOVX @DPTR,A RET Start3: MOV A,#0FCH MOV DPTR,#U4 MOVX @DPTR,A MOV A,#03H MOV DPTR,#U5 MOVX @DPTR,A RET Start4: MOV A,#3CH MOV DPTR,#U4 MOVX @DPTR,A MOV A,#0FH MOV DPTR,#U5 MOVX @DPTR,A RET DL10S: MOV R1,#5 AA1:LCALL DL2S DJNZ R1,AA1 ;48 RET DL2S: MOV R7,#20 DL2: MOV R6,#200 DL1: MOV R5,#250 DJNZ R5,$ DJNZ R6,DL1 DJNZ R7,DL2 RET U4 EQU 0FE00H U5 EQU 0FD00H END (3)实验截图 3.3 实验 3:静态 LED 显示实验 (1)实验内容 在Proteus环境下搭建如下图3.9所示的电路图: 图3.9倒计时器原理图 其中所用元器件如下表: 选件编号 元件名称 参 数 所在元件库类名 子类名 生产厂 家 U1 AT89C51 Microprocessor ICs 微处理器 TTL 74LS series TTL 74LS 系列 TTL 74LS series TTL 74LS 系列 TTL 74LS series TTL 74LS 系列 Optoelectronics 光电子器件 8051 ATMEL Family 8051 家族 Flip-Flops & Latches 触发与锁存 Gates& Inverters 门与反相器 Gates& Inverters 门与反相器 7-Segment Displays U2,U3 74LS373 U4 74LS32 U5 74LS04 7SEG1,7SEG2 7SEG-COM-AN-G RN (2)控制要求 图 3.9 中 7SEG2 为十位显示数码管,7SEG1 为个位显示数码管,KEY_LOAD 为倒计时初 值按钮,KEY_START 为倒计时启动按钮。要求实现的功能是:当 KEY_LOAD 按钮按下时加载 倒计时初值(如:10s),当按下 KEY_START 按钮时,开始倒计时,每过 1s,计时器减 1, 直到减到“00”为止。减到“00”时使 P3.0 引脚上的 LED 按 10Hz 频率进行闪烁,直到再次 按下 KEY_LOAD 按钮才重新加载初值,并熄灭 LED。再次按下 KEY_START 按钮又一次开始倒 计时,如此反复。 (3)实验步骤 ① 根据上述实验内容,参考 1.2.2,在 Proteus 环境下建立图 3.9 所示原理图,并将其保 存为 staticLED_self.DSN 文件。 ② 根据(2)和(3)编写控制源程序,将其保存为 staticLED_self.asm。 ③ 将源程序添加到 U1 中,并构造(build)该程序。 ④ 执行仿真过程观察秒表程序功能是否正确。 (4)实验程序: ORG 0000H AJMP MAIN ORG 0030H MAIN: MOV SP,#60H;堆栈初始化 MOV R0,#0;各位 MOV R1,#1;十位 SETB P3.0;关掉LED1,终端撤消 CLR F0 LOOP: JB P1.1,LOOP2;如果P1.1=1,跳转到LOOP2 LOOP1: CLR F0 MOV 30H,R0 MOV 31H,R1;装载初值 SETB P3.0;关闭LED1 LCALL DISPLAY;显示 LOOP2: JB P1.0,LOOP;如果P1.0=1,跳回LOOP,否则继续执行 LOOP3: LCALL DISPLAY;刷新显示 LCALL DELAY1S;延时1s LCALL ADJUST2;调整计时器寄存器 JB F0,LOOP4 LJMP LOOP3 LOOP4: CLR P3.0 ;LED闪烁程序 LCALL DELAY100MS SETB P3.0 LCALL DELAY100MS JB P1.1,LOOP4 LJMP LOOP1 DISPLAY: ;显示子程序 MOV A,30H MOV DPTR,#TABLE MOVC A,@A+DPTR MOV DPTR,#D1ADD MOVX @DPTR,A MOV A,31H MOV DPTR,#TABLE MOVC A,@A+DPTR MOV DPTR,#D10ADD MOVX @DPTR,A RET ADJUST2: DEC 30H MOV A,30H CJNE A,#-1,GOTORET MOV 30H,#9 DEC 31H MOV A,31H CJNE A,#-1,GOTORET SETB F0 RET GOTORET: RET DELAY1S:MOV R7,#10 DL2:MOV R6,#200 DL1:MOV R5,#250 DJNZ R5,$ DJNZ R6,DL1 DJNZ R7,DL2 RET DELAY100MS:MOV R7,#200 DL:MOV R6,#250 DJNZ R6,$ DJNZ R7,DL RET TABLE: DB 0C0H,0f9H,0A4H,0B0H,99H,92H,82H,0F8H,80H,90H D1ADD EQU 0FE00H ;U3的锁存地址 D10ADD EQU 0FD00H END (6)实验截图 3.4 实验 4:矩阵键盘扫描实验 (1)实验内容 在 Proteus 环境下搭建如下图 3.11 所示的电路图: 图 3.11 旋转彩灯控制原理图 其中所用元器件如下表所示: 选件编号 U1 元件名称 AT89C51 参数 所在元件库类名 Microprocessor ICs 微处理器 Optoelectronics 光电子器件 Optoelectronics 光电子器件 Optoelectronics 光电子器件 Optoelectronics 光电子器件 子类名 生产厂家 8051 ATMEL Family 8051 家族 LEDs 发光二极 管 LEDs 发光二极 管 LEDs 发光二极 管 LEDs 发光二极 管 D1,D5,D9-D13 LED-RED D2,D6 LED-GREEN D3,D7 LED-BLUE D4,D8 LED-YELLOW R1-R13 KEY0-KEY3 RES BUTTON 100 Resistors 电阻 Switches & Relays 开关与继电器 Generic 一般的 Switches 开关 (2)控制要求 图 3.11 中 D1~D8 八个发光二极管构成彩色旋转灯,D9~D13 为档位指示灯,一档旋转 速度最慢(周期 1s,D13 亮),二档较快(周期 0.8s,D12 亮),三档更快(周期 0.6s, D11 亮),四档再快(周期 0.4s,D10 亮),五档最快(周期 0.2s,D10 亮)。四个按键 KEY0-KEY1 用于设定旋转方向为顺时针旋转或者逆时针旋转,KEY2-KEY3 用于增快或则减慢旋转速度。 (3)编程思路 按键扫描的方式可以采用前面示例程序中的方法:线反转法或行扫描法。可以用汇编语言 实现,也可以用 C 语言实现。建议如前面示例所示,汇编采用行扫描法,C 语言用线反转法。 程序控制流程是:首先初始化设置默认运行参数,然后读取按键,识别键码,并根据键码 的不同执行运行参数调整,最后根据当前的运行参数执行发光二极管 D1-D8 的轮流旋转。 按键的识别中的键码可以根据图 3.11 中的连接情况,总结出其键值表,即:正向按钮为: 0x22,反向按钮为:0x12,加速按钮为 0x21,减速按钮为 0x11,因此可以定义按键键码表(C 语言方式): uchar code KEY_TABLE[]={0x22,0x12,0x21,0x11}; //按键键值表 速度的控制通过控制调用延时程序的次数来决定,假设延时程序的延时长度为 5ms。延时 程序可以按如下方式实现(假设晶振频率为 12MHz): void delays() { uchar t,ms; ms=5; //延时 5ms while(ms--) for(t=0;t<120;t++); } 或者采用内嵌汇编来实现: void delays() { #pragma asm MOV R2,#50 //;5ms 延时程序 DL1:MOV R1,#48 DL2:DJNZ R1,DL2 // ;内循环 100us NOP DJNZ R2,DL1 //;中循环 10ms #pragma endasm } (4)实验步骤 ① 根据上述实验内容,参考 1.2.2,在 Proteus 环境下建立图 3.11 所示原理图,并将其 保存为 keyscan_self.DSN 文件。 ② 根据(2)和(3)编写控制源程序,将其保存为 keyscan_self.asm 或 keyscan_self.c。 ③ 将源程序添加到 U1 中,并构造(build)该程序。 ④ 执行仿真过程观察秒表程序功能是否正确。 (5)实验程序: #include #include sbit D5=P0^0; sbit D4=P0^1; sbit D3=P0^2; sbit D2=P0^3; sbit D1=P0^4; uchar data key _at_ 0x30; uchar code KEY_TABLE[]={0x22,0x12,0x21,0x11}; uchar code OUT_TABLE[]={0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe}; void delay(uchar n) { uchar t,i; uchar m=200; while(m--) for(i=0;i void main() { uchar DIR=0; uchar speed=3; char m,n; uchar i,num; uchar temp; P1=0x7f; while(1) { P3=0xf0; temp=P3; temp=temp&0x30; if(temp !=0x30) //L { delays(); P3=0xf0; temp=P3; temp=temp&0x30; if(temp != 0x30) { key=temp; temp=temp|0x03; P3=temp; temp=P3&0x03; key=temp|key; key=0x33-key; P3=0xf0; for(i=0;i<4;i++) { if(key==KEY_TABLE[i]) { num=i; break; } } switch (num) { case 0 : //speed_down { speed++; if(speed==6){speed=1;}break; } case 1 : //speed_up { speed--; if(speed==0){speed=5;}break; } case 2 : DIR=1;break; case 3 : DIR=0;break; } switch (speed) { case 1 : P0=0xFE;break; case 2 : P0=0xFD;break; case 3 : P0=0xFB;break; case 4 : P0=0xF7;break; case 5 : P0=0xEF;break; } if(DIR==1) //anticlk { for(;m<8;m++) {P1=OUT_TABLE[m]; delay(speed); if(m==7){m=-1;} temp=P3&0x30; if(temp !=0x30){break;} } } if(DIR==0) //clk { for(;m<8;m--) { P1=OUT_TABLE[m]; delay(speed); if(m==0){m=8;} temp=P3&0x30; if(temp !=0x30){break;} } } } } } } (6)实验结果截图: 3.5 实验 5:定时计数器实验 (1)实验内容 图 3.14 频率计原理图 图中左边是用 NE555 构成的脉冲信号发生器,右边是用定时/计数器 0 和定时/计数器 1 构成的频率计。 为了检验频率计量是否准确,用 Proteus 的虚拟频率计来进行测量脉冲信号频率进行比对。 图中所用元器件如下表所示: 选件编号 元件名称 参数 所在元件库类名 子类名 生产 厂家 ATMEL U1 U2-U7 AT89C51 74LS373 Microprocessor ICs 微处理器 TTL 74LS series TTL 74LS 系列 TTL 74LS series TTL 74LS 系列 8051 Family 8051 家族 Flip-Flops & Latches 触发与锁存 Gates& Inverters 门与反相器 U8,U10 74LS32 U9 74LS04 TTL 74LS series TTL 74LS 系列 Analog ICs 模拟集成电路 Optoelectronics 光电子器件 Optoelectronics 光电子器件 Resistors 电阻 Diodes 二极管 Resistors 电阻 Resistors 电阻 Resistors 电阻 Capacitors 电容 Gates& Inverters 门与反相器 Timers 定时器 U11 NE555 7SEG1-7SEG6 7SEG-COM-AN-G RN LED1 RV1,RV2 D1,D2 R1 R2 R3 C1 C2 LED-GREEN POT 1N4148 RES RES RES CAP CAP 100k 330 10 10k 0.1uF 7-Segment Displays 7 段显示 LEDs 发光二极管 Variable 可变的 Switching 开关 Generic 一般的 Generic 一般的 Generic 一般的 Generic 一般的 Generic 一般的 0.01uF Capacitors 电容 (2)控制要求 7SEG1-7SEG6 用于频率计百分位、十分位、个位、十位、百位、千位的显示, 单位为 Hz。要求单片机上电运行后作为频率计将一直运行,改变脉冲发生器所产生的脉冲 频率,则频率计的显示将跟随变化。 (3)编程思路 定时/计数器 0 工作在定时器模式方式 1(16 位),定时/计数器 1 工作在计数器模式方 式 2(8 位自动重装初值)。 定时/计数器 1 计数 200 个脉冲后(每计数 200 个脉冲产生一次中断)统计这 200 个脉 冲总的时间长度,计算出平均每个周期的时间长度。200 个脉冲所用时间长度的测量是靠定时/计数器 0 来实现的,定时/计数器 0 的初值为0。当定时/计数器 1 产生中断时,读出定时/计数器 0 当前计数器值,再加上在定时/计数器0 中断中累积的值即可得到。 为了便于大家编程,这里给出两个定时计数器的中断函数及有关变量的定义如下: #define uchar unsigned char #define uint unsigned int uchar code LED_Table[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82, 0xF8,0x80,0x90} ;//7 段 LED 共阳显示码 uchar xdata DA001 _at_ 0xfe00; //x0.01Hz 显示锁存地址 uchar xdata DA01 _at_ 0xfd00; //x0.1Hz 显示锁存地址 uchar xdata DA1 _at_ 0xfb00; //x1Hz 显示锁存地址 uchar xdata DA10 _at_ 0xf700; //x10Hz 显示锁存地址 uchar xdata DA100 _at_ 0xef00; //x100Hz 显示锁存地址 uchar xdata DA1k _at_ 0xdf00; //x1000Hz 显示锁存地址 uchar DV001,DV01,DV1,DV10,DV100,DV1k;// DV001 百分位、DV01 十分位、DV1 个位、 // DV10 十位、DV100 百位、DV1k 千位数值 long sumtime=0; //总时间 float frequency,ptime; // frequency 为频率,ptime 为单周期时间 void s_timer0() interrupt 1 { EA=0; sumtime=sumtime+65536; TH0=timer0H; TL0=timer0L; EA=1; } void s_timer1() interrupt 3 { EA=0; sumtime=sumtime+TH0*256+TL0; TH0=timer0H; TL0=timer0L; ptime=(float)sumtime/200; //计算每个周期的时间长度 sumtime=0; frequency=(float)1000000/ptime; //计算频率 DV1k=(uchar)(frequency/1000); //计算频率千位值 frequency=frequency-DV1k*1000; DV100=(uchar)(frequency/100); //计算频率百位值 frequency=frequency-DV100*100; DV10=(uchar)(frequency/10); //计算频率十位值 frequency=frequency-DV10*10; DV1=(uchar)frequency; //计算频率个位值 frequency=frequency-DV1; frequency=frequency*10; DV01=(uchar)frequency; //计算频率十分位值 frequency=frequency-DV01; frequency=frequency*10; DV001=(uchar)frequency; //计算频率百分位值 DA001=LED_Table[DV001]; DA01=LED_Table[DV01]; DA1=LED_Table[DV1]; DA10=LED_Table[DV10]; DA100=LED_Table[DV100]; DA1k=LED_Table[DV1k]; EA=1; } 主程序的编写由读者自己完成。 (4)实验步骤 ① 根据上述实验内容,参考 1.2.2,在 Proteus 环境下建立图 3.11 所示原理图,并将其 保存为 frequencycounter.DSN 文件。 ② 根据(2)和(3)编写控制源程序,将其保存为 frequencycounter.c。 ③运行 Keil uVision2 开发环境,按照 1.1.3 节介绍的方法建立工程 frequencycounter.uV2,CPU 为 AT89C51,包含启动文件 STARTUP.A51。 ④ 按照 1.2.2 第(6)节介绍的方法将 C 语言源程序 frequencycounter.c 加入工程frequencycounter.uV2,并设置工程 frequencycounter.uV2 属性,将其晶振频率设置为12MHz,选择输出可执行文件,仿真方式为选择硬仿真,并选择其中的“PROTEUS VSM MONITOR 51 DRIVER”仿真器。 ⑤ 构造(Build)工程 frequencycounter.uV2。如果输入有误进行修改,直至构造正确, 生成可执行程序 frequencycounter.hex 为止。 ⑥ 为 AT89C51 设置可执行程序 frequencycounter.hex。 ⑦ 运行程序,观察数码管的显示与虚拟频率计是否一致。 ⑧ 改变 RV2 的值,继续观察频率测量结果,观察数码管的显示与虚拟频率计是否一致。 (5)实验程序: #include\"reg51.h\" #define uchar unsigned char #define uint unsigned int uchar code led_table[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; uchar xdata da001 _at_ 0xfe00; uchar xdata da01 _at_ 0xfd00; uchar xdata da1 _at_ 0xfb00; uchar xdata da10 _at_ 0xf700; uchar xdata da100 _at_ 0xef00; uchar xdata da1k _at_ 0xdf00; uchar dv001,dv01,dv1,dv10,dv100,dv1k; long sumtime=0;//总时间 float frequency,ptime;//分别为 频率, 单周期时间 uint timer0H; uint timer0L; void s_timer0() interrupt 1 { EA=0; sumtime=sumtime+65536; TH0=timer0H; TL0=timer0L; EA=1; } void s_timer1() interrupt 3 { EA=0; sumtime=sumtime+TH0*256+TL0; TH0=timer0H; TL0=timer0L; ptime=(float)sumtime/200; sumtime=0; frequency=(float)1000000/ptime; dv1k=(uchar)(frequency/1000); frequency=frequency-dv100*100; dv10=(uchar)(frequency/10); frequency=frequency-dv10*10; dv1=(uchar)frequency; frequency=frequency-dv1; frequency=frequency*10; dv01=(uchar)frequency; frequency=frequency-dv01; frequency=frequency*10; dv001=(uchar)frequency; da001=led_table[dv001]; da01=led_table[dv01]; da1=led_table[dv1]; da10=led_table[dv10]; da100=led_table[dv100]; da1k=led_table[dv1k]; EA=1; } void main() { TMOD=0X61;//T1计数器,方式2,T0定时器,方式1 timer0H=0;//T0定时器初值 timer0L=0; TH1=TL1=56;//T1计数器初值 256-200=56 EA=1;//开总中断 ET0=1;//开定时器0中断 TR0=1;//启动定时器0 ET1=1;//开启T1中断 TR1=1;//开启T1 while(1); } (6)实验结果截图: frequency=frequency-dv1k*1000; dv100=(uchar)(frequency/100); 3.6 实验 6:串口通信实验 (1)实验内容 在 Proteus 环境下搭建如下图 3.22 所示 74LS164/165 串口扩展并口的电路图: 图 3.22 74LS164/165 串口扩展并口的电路图 其中所用元器件如下表所示: 选件编号 元件名称 参数 所在元件库类名 子类名 生产厂家 U1 U2,U3 U4 U5 U6 U7 AT89C51 74LS164 74LS165 74LS125 74LS04 7407 Microprocessor ICs 微处理器 TTL 74LS series TTL 74LS series TTL 74LS series TTL 74LS series TTL 74 series Resistors 电阻 Resistors 电阻 Switches&Relays 开关与继电器 Optoelectronics 光电子器件 Resistors 电阻 Resistors 电阻 Capacitors 电容 Switches & Relays 开关与继电器 8051 Family 8051 家族 Registers 寄存器 Registers 寄存器 Buffers&Drivers 缓冲器与驱动器 Gates&Inverters 门与反向器 Buffers&Drivers 缓冲器与驱动器 Resistor Packs 排阻 Resistor Packs 排阻 Switches 开关 7-Segment Displays 7 段显示 Generic 一般的 Generic 一般的 Generic 一般的 Switches 开关 ATMEL RN1,RN2 RX8 RP1 DSW1 7SEG1 7SEG2 R1,R2 R3 C1 KEY1 RESPACK-8 10k-8 DIPSW_8 7SEG-COM- AN-GRN RES RES 10k 100 CAP-ELEC 1uF BUTTON (2)控制要求 本实验利用 74LS165 实现串口转并行输入端口,用来采集 DSW1 的开关数据。采集来的数 据通过 74LS164 实现的串口转并行输出端口送给两个 7 段 LED 数码管 7SEG1 和 7SEG2。RN1 和 RN2 是两个排阻用于限流。图 3.22 中的 R2,R3,C1 和 KEY1 用于产生低电平脉冲,其中 的 R2,R3 和 C1 用于去除按键抖动。该电平脉冲一方面连接至 P1.1 用于 CPU 探测该脉冲的 产生时间,另一方面经过 U6A 的 74LS04 之后连接至 74LS165 的 1 号引脚(SH/LD),该信号 在按键 KEY1 未被按下时为低电平,74LS165 的输入一直被加载至器件内部的所存器,当 KEY1被按下时该信号为高电平,74LS165 加载的数据将在 2 号引脚 CLK 的同步下被串行移出并进 入单片机的 RXD 端口。 由于 74LS165 的 9 号引脚需要连接到单片机的 RXD 端口,而 RXD 又需要连接至 164 的 1、2 号管脚。同时,单片机的 TXD 引脚不论在读 74LS165 的数据还是向 74LS164 输出数据 时都提供时钟。为了防止 74LS165 的 9 号引脚输出影响 RXD 至 164 的 1、2 号引脚的输出, 而且防止 74LS165 的输出被相同的时钟直接移位进入 164(走近路),单片机的 TXD 和 RXD 需要分时接入 74LS165 和 74LS164。分时控制的方法就是利用 U5(74LS125)这一三态选通 缓冲器来实现。TXD 接入 U5A 和 U5D 的输入端,RXD 接入 U5C 的输入和 U5B 的输出。U5A 的 输出接 74LS165 的 CLK,U5B 的输入接 74LS165 的 9 号引脚,当 P1.0=0 时这两个三态缓冲器 是接通的,即 RXD 接至 74LS165 的 9 号引脚,TXD 接至 74LS165 的 CLK,可以实现单片机对 74LS165 的串行读取。反之,当 P1.0=1 时,经过反相器使得 U5C 和 U5D 接通,使得 TXD 接 至 74LS164 的 CLK,RXD 接至 74LS164 的 1,2 号引脚,可以实现单片机对 164 的串行输出。 最终要求实现的功能是:当按键按下时,DSW1 的开关数据能够被单片机通过 74LS165 串行读取,并通过 74LS164 串行输出至两个 7 段数码管 7SEG1 和 7SEG2 显示,要求显示的数 据和 DSW1 的开关数据一致,没有错误数据被读入或显示。 (3)编程思路 串行口工作在定时器模式方式 0 模式(即:SM1=0,SM0=0)。串行口的数据收发利用查 询模式实现。只有当查询到 P1.1 有低电平出现时才启动数据收发过程。启动的方法就是先 使 P1.0=0 打开接收通道,再使接收控制(REN=1)使能,这时 TXD 就会自动输出时钟,由于 这时 74LS165 正处于移位模式(SH/LD 引脚为高电平),数据会自动串行移入单片机的 RXD 端口。查询接收完成标志 RI,当 RI=1 时 1 个字节接收完成,清掉 RI(即使 RI=0)。然后, 使 P1.0=1,打开输出 74LS164 通道,把刚才接收的那个数据分低半字节和高半字节,分别 查询出它们共阳七段数码管的显示码,再将显示码(16 位)移位送出至 74LS164。两个 74LS164 是按级联方式连接的,先送出的会到达 U3 输出端,后送出的会到达 U2 输出端。发送时通过 TI 标志位查询发送一个字节是否完成。 (4)实验步骤 ① 根据上述实验内容,参考 1.2.2,在 Proteus 环境下建立图 3.22 所示原理图,并将其 保存为 uart_self.DSN 文件。 ② 根据(2)和(3)画出流程图,并编写源程序,将其保存为 uart_self.c。 ③ 运行 Keil uVision2 开发环境,按照 1.1.3 节介绍的方法建立工程 uart_self.uV2, CPU 为 AT89C51,包含启动文件 STARTUP.A51。 ④ 按照 1.2.2 第(6)节介绍的方法将 C 语言源程序 uart_self.c 加入工程 uart_self.uV2, 并设置工程 uart_self.uV2 属性,将其晶振频率设置为 12MHz,选择输出可执行文件, 仿真方式为选择硬仿真,并选择其中的“PROTEUS VSM MONITOR 51 DRIVER”仿真器。 ⑤ 构造(Build)工程 uart_self.uV2。如果输入有误进行修改,直至构造正确,生成可 执行程序 uart_self.hex 为止。 ⑥ 为 AT89C51 设置可执行程序 uart_self.hex。 ⑦ 运行程序,点击 KEY1 观察数码管的显示是否与 DSW1 的设置一致。 ⑧ 改变 DSW1 的设置,再次点击 KEY1 观察数码管的显示是否与 DSW1 的设置一致。重复本 条多次观察程序运行效果。 (5)实验程序: #include\"reg51.h\" #define uchar unsigned char #define uint unsigned int uchar code led_table[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; sbit P11=P1^1; sbit P10=P1^0; uchar tmp,tmp1,tmp2,tmp3; void main() { SCON=0X00;//设定定时器为移位寄存器模式 // SM1=0; // SM0=0;//方式0模式 while(1) { if(P11==0) { REN=1; while(!RI); tmp=SBUF; REN=0; RI=0; P10=1; tmp1=tmp&0x0f;//低半字节 tmp>>=4; tmp2=tmp&0x0f;//高半字节 SBUF=led_table[tmp1]; while(!TI); //等待发送是否完毕 TI=0; //置发送标志为零 SBUF=led_table[tmp2]; while(!TI);//等待发送是否完毕 TI=0;//置发送标志为零 while(!P11); } } } (6)实验结果截图: P10=0; 3.7 实验 7:LCD1602 显示实验 (1)实验内容 在 Proteus 环境下搭建如下图 3.29所示的电路图: 图 3.29 LCD1602 实现十进制计算器实验 其中所用元器件如下表所示: 选件编号 U1 LCD1 R1 元件名称 参数 AT89C51 LM016L RES 1.8k 所在元件库类名 Microprocessor ICs 微处理器 Optoelectronic 光电器件 Resistors 电阻 Switches & Relays 开关与继电器 子类名 8051 Family 8051 家族 Alpha Numeric LCDs 字符数字 LCD Generic 一般的 Switches 开关 生产厂家 ATMEL KEY0-KEY15 BUTTON (2)控制要求 本实验利用 LCD1602 和 16 个按键实现简单的十进制数加减乘除四则混合运算。其中按 键 KEY0-KEY9 分别代表了数字 0-9,按键 10-13 分别代表了运算符“+,-,*,/”,按键“15” 代表“=”,按键 14 代表清除命令,以便进行下一次的输入和计算。不管什么时候按下清除 按键计算过程停止,两个输入变量都将清 0,屏幕将清屏。 LCD1602 的第一行用于显示所输入的两个计算数以及计算符,第二行用于显示计算结果。 结果允许为负数,但输入的两个输入数都必须是双字节正整数范围内的数,即:0-32767 之 间。 除数必须保证不为 0,否则将报错。除数时必须能同时显示商与余数。输入与计算结果 的显示如下图 3.30 所示: (3)编程思路 按键的扫描与识别可以参考“3.4.1 示例实验”中的方法。LCD1602 的显示控制可以参 考“3.7.2 LCD1602 示例实验”。 编成时要有一个状态变量,该变量用于记录当前是输入的哪个变量。输入第一个变量, 遇到输入运算符时结束第一个变量的输入。输入第二个变量,遇到“=”号时结束第二个变 量的输入,并且开始计算结果。 计算结果由于是 16 进制的,要将其转换成十进制,并将该十进制的数转换成字符串后 逐位显示出来。减法时要注意结果是否为负,除法时要注意除数是否为 0,结果是否带有余 数。 另外,按键要注意去抖动处理。 (4)实验步骤 ① 根据上述实验内容,参考 1.2.2,在 Proteus 环境下建立图 3.22 所示原理图,并将其 保存为 LCD1602_self.DSN 文件。 ② 根据(2)和(3)画出流程图,并编写源程序,将其保存为 LCD1602_self.c。 ③ 运行 Keil uVision2 开发环境,按照 1.1.3 节介绍的方法建立工程 LCD1602_self.uV2, CPU 为 AT89C51,包含启动文件 STARTUP.A51。 ④ 按照 1.2.2 第(6)节介绍的方法将 C 语言源程序 LCD1602_self.c 加入工程 LCD1602_self.uV2,并设置工程 LCD1602_self.uV2 属性,将其晶振频率设置为 12MHz, 选择输出可执行文件,仿真方式为选择硬仿真,并选择其中的“PROTEUS VSM MONITOR 51 DRIVER”仿真器。 ⑤ 构造(Build)工程 LCD1602_self.uV2。如果输入有误进行修改,直至构造正确,生 成可执行程序 LCD1602_self.hex 为止。 ⑥ 为 AT89C51 设置可执行程序 LCD1602_self.hex。 ⑦ 运行程序,点击按键输入数据与运算符,计算,观察计算结果,并验证其是否正确。 ⑧ 输入过程中,按“清除按键”观察结果,重新输入数据计算并验证。 (5)实验程序: #include char i,j,temp,num,num_1; long a,b,c; float a_c,b_c; uchar flag,fuhao; uchar code table[]={ 0,1,2,3, 4,5,6,7, 8,9,0,0, 0,0,0,0}; uchar code table1[]={ 0,1,2,3, 4,5,6,7, 8,9,0x2b-0x30,0x2d-0x30, 0x2a-0x30,0x2f-0x30,0x01-0x30,0x3d-0x30} void delay(uchar z) { uchar y; for(z;z>0;z--) for(y=0;y<110;y++); } void check() { do{ P2=0xFF; rs=0; rw=1; lcden=0; delay(1); lcden=1; }while(busy==1); } void write_com(uchar com) { P2=com; rs=0; rw=0; lcden=0; check(); lcden=1; } void write_date(uchar date) { ; P2=date; rs=1; rw=0; lcden=0; check(); lcden=1; } void init() { num=-1; lcden=1; write_com(0x01); //清零 write_com(0x38); //设置16X2显示等 write_com(0x0c); //设置开显示,不显示光标 write_com(0x06);//写入新数据后,地址寄存器内容加1 //自动往下写 write_com(0x80); //第一行第一列开始 显示 write_com(0x01); num_1=0; i=0; j=0; a=0; b=0; c=0; flag=0; fuhao=0; } void keyscan() { P3=0xfe; if(P3!=0xfe) { delay(20); if(P3!=0xfe) { temp=P3&0xf0; switch(temp) { case 0xe0:num=0; break; case 0xd0:num=1; break; case 0xb0:num=2; break; case 0x70:num=3; break; } } while(P3!=0xfe); if(num==0||num==1||num==2||num==3) { if(j!=0)//j标志位:第二次计算时,把前面的清除 { write_com(0x01); j=0; } if(flag==0) { a=a*10+table[num]; } else { b=b*10+table[num]; } } i=table1[num]; write_date(0x30+i); } P3=0xfd; if(P3!=0xfd) { delay(5); if(P3!=0xfd) { temp=P3&0xf0; switch(temp) { case 0xe0:num=4; break; case 0xd0:num=5; break; case 0xb0:num=6; break; case 0x70:num=7; break; } } while(P3!=0xfd); if(num==4||num==5||num==6||num==7) { if(j!=0) { write_com(0x01); j=0; } if(flag==0) { a=a*10+table[num]; } else { b=b*10+table[num]; } } i=table1[num]; write_date(0x30+i); } P3=0xfb; if(P3!=0xfb) { delay(5); if(P3!=0xfb) { temp=P3&0xf0; switch(temp) { case 0xe0:num=8; break; case 0xd0:num=9; break; case 0xb0:num=10; break; case 0x70:num=11; break; } } while(P3!=0xfb); if(num==8||num==9) { if(j!=0) { write_com(0x01); j=0; } if(flag==0) { a=a*10+table[num]; } else { b=b*10+table[num]; } } if(num==11) //- { flag=1; fuhao=2; } if(num==10) //+ { flag=1; fuhao=1; } i=table1[num]; write_date(0x30+i); } P3=0xf7; if(P3!=0xf7) { delay(5); if(P3!=0xf7) { temp=P3&0xf0; switch(temp) { case 0xe0:num=12; break; case 0xd0:num=13; break; case 0xb0:num=14; break; case 0x70:num=15; break; } } while(P3!=0xf7); switch(num) { case 12://x {write_date(0x30+table1[num]); flag=1; fuhao=3; } break; case 13:// / { write_date(0x30+table1[num]); flag=1; fuhao=4; } break; case 15://计算结果 {j=1; if(fuhao==1){write_com(0x80+0x4f); //最后一个数的地址 write_com(0x04); //写入新数据后地址寄存器内容减1 c=a+b; while(c!=0) { write_date(0x30+c%10); c=c/10; } write_date(0x3d); //= a=0;b=0;flag=0;fuhao=0; } else if(fuhao==2){write_com(0x80+0x4f); write_com(0x04); if(a-b>0) c=a-b; else c=b-a; while(c!=0) { 相当于从后往前显示 write_date(0x30+c%10); c=c/10; } if(a-b<0) write_date(0x2d);//符号 45 write_date(0x3d); //= a=0;b=0;flag=0;fuhao=0; } else if(fuhao==3){write_com(0x80+0x4f); write_com(0x04); c=a*b; while(c!=0) { write_date(0x30+c%10); c=c/10; } write_date(0x3d); a=0;b=0;flag=0;fuhao=0; } else if(fuhao==4){write_com(0x80+0x4f); write_com(0x04); i=0; if(b==0)//如果除数是0,则显示error { write_date('r'); write_date('o'); write_date('r'); write_date('r'); write_date('e'); } else{ c=(long)(((float)a/b)*1000); while(c!=0) { write_date(0x30+c%10);//从个位开始写 最低位 c=c/10; i++; if(i==3) write_date(0x2e);//. 46 } if(a/b<=0) write_date(0x30);//0 } write_date(0x3d); a=0;b=0;flag=0;fuhao=0; } } break; case 14: //清除显示 {write_com(0x01);a=0;b=0;flag=0;fuhao=0;} break; } } } main() { init(); while(1) { keyscan(); } } (6)实验结果截图: 3.8 实验 8:ADC0808/9 信号采集实验 (1)实验内容 在 Proteus 环境下搭建如下图 3.32 所示的电路图: 图 3.32 交流信号频率与过零点检测 其中所用元器件如下表所示: 选件编号 U1 LCD1 元件名称 参数 AT89C51 LM016L 所在元件库类名 Microprocessor ICs 微处理器 Optoelectronic 光电器件 Data Converters 数据转换器 子类名 8051 Family 8051 家族 Alpha Numeric LCDs 字符数字 LCD A/D Converters A/D 转换器 生产厂家 ATMEL U2 ADC0808 U3 RP1 RV1 R1 LM324 Operational Amplifiers 运算放大器 Resistors 电阻 Resistors 电阻 Resistors 电阻 Quad 四 Resistor Packs 排阻 Variable 可变的 Generic 一般的 RESPACK- 10k- 8 8 POT-HG RES 10k 1.8k (2)控制要求 本实验利用 LCD1602 和 AD0808 实现简单的交流信号过零检测与频率分析。要求信号幅 度变化时(满量程的 5%-95%) ,不影响检测的结果。频率检测的结果通过 LCD1602 的第一行 显示出来,信号过零时,能够通过 P2.6 输出一个脉冲宽度为 5us 的脉冲信号。检测结果如 下图 3.33 所示: 图 3.32 中电位器 RV1 用于改变交流信号的幅值。图中的交流信号通过点击窗口左侧绘 图工具窗口中的虚拟信号发生器如下图 3.33 所示: 按钮,然后在器件选择窗口中选择正弦信号(SINE)。 图 3.33 交流信号发生器选择 将信号发生器连入图 3.32 中的电位器 RV1 的上端,RV1 的中间抽头连入 AD0808 的 0 通道输入 IN0。 交流信号发生器加入之后就要设置它的属性,方法是双击所加入的信号发生器 ,进 入其属性设置页面,如下图 3.34 所示: 其中信号的幅值和频率按图 3.34 所示设置。 图 3.32 中的虚拟示波器的加入是为了测量各种信号的波形。 (3)编程思路 LCD1602 的控制方法按 3.7 节所示方法进行 ADC0808 的控制方法按 3.8.1 所示方法进行。 这里主要是过零点的检测方法如何实现。不能采用判断所采集到的数据是否为 0 的方法来实 现,因为你的采集时刻不一定能够严格对准过零时刻。但是,我们注意到在 0 点的两边信号 的极性是发生变化的,我们可以利用这一特点来实现过零检测。正弦波每个周期有两个过零 点,因此,1s 内过零次数除以 2 就是信号的频率。 因此,在程序中可以这样实现 。当每次采集到一个新的数据之后都要看一下这个数据 是正数还是负数。当这个数大于 128 时是正数,当它小于 128 时是负数。判断当前数据的正 负极性和上一个数据的正负极性是否一致,如果不一致,则说明经过了一次过零点,将其记 录入次数计数器。 ADC0808 的 CLK 仍然用定时器 T1 来实现,可以将其设置为 50kHz(硬件实现时可以更高, 软件仿真再高将难以实现)。利用定时器 T0 实现 50ms 定时,并配合软件实现 1s 钟定时。 采用 12M 晶振时,T0 采用方式 1,则处置应为(TH0=0x3C,TL0=0xB0)。 但是,由于中断处理函数需要一定的响应时间,因此这个参数只是理论计算结果,要根 据实测情况稍作调整。 同样 T1 理论计算值和实际输出值可能也会有一定的差距,也要进行调整。 (4)实验步骤 ① 根据上述实验内容,参考 1.2.2,在 Proteus 环境下建立图 3.22 所示原理图,并将其 保存为 ADC0808_self.DSN 文件。 ② 根据(2)和(3)画出流程图,并编写源程序,将其保存为 ADC0808_self.c。 ③ 运行 Keil uVision2 开发环境,按照 1.1.3 节介绍的方法建立工程 ADC0808_self.uV2, CPU 为 AT89C51,包含启动文件 STARTUP.A51。 ④ 按照 1.2.2 第(6)节介绍的方法将 C 语言源程序 ADC0808_self.c 加入工程 ADC0808_self.uV2,并设置工程 ADC0808_self.uV2 属性,将其晶振频率设置为 12MHz, 选择输出可执行文件,仿真方式为选择硬仿真,并选择其中的“PROTEUS VSM MONITOR 51 DRIVER”仿真器。 ⑤ 构造(Build)工程 ADC0808_self.uV2。如果输入有误进行修改,直至构造正确,生 成可执行程序 ADC0808_self.hex 为止。 ⑥ 为 AT89C51 设置可执行程序 ADC0808_self.hex。 ⑦ 运行程序,观察计算结果,并验证其是否正确。 ⑧ 改变 RV1 的抽头位置,从而改变输入信号的幅值,观察计算结果是否正确。 ⑨ 更改信号发射器的频率,再次验证其功能是否正确。(注意:因为是软仿真,所以信 号采集的速度受到限制,因此所输入的交流信号频率也不能太高,可以在 200Hz 以内 尝试)。 (5)实验程序: #include sbit RS=P2^0; //寄存器选择位,将RS位定义为P2.0引脚 sbit RW=P2^1; //读写选择位,将RW位定义为P2.1引脚 sbit E=P2^2; //使能信号位,将E位定义为P2.2引脚 sbit BF=P1^7; //忙碌标志位,,将BF位定义为P0.7引脚 //AD端口定义 sbit CLK=P2^3; sbit start=P2^4; sbit oe=P2^5; sbit eoc=P2^7; sbit out_pulse=P2^6;//5us脉冲 sbit p30=P3^0; #define uchar unsigned char #define uint unsigned int uchar n=0; uchar flag=0;//1s标志位 /***************************************************** 函数功能:延时1ms ***************************************************/ void delay1ms() { unsigned char i,j; for(i=0;i<10;i++) for(j=0;j<33;j++) ; } /***************************************************** 函数功能:延时若干毫秒 入口参数:n ***************************************************/ void delay(unsigned int n) { unsigned int i; for(i=0;i 返回值:result。result=1,忙碌;result=0,不忙 ***************************************************/ bit BusyTest(void) { bit result; RS=0; //根据规定,RS为低电平,RW为高电平时,可以读状态 RW=1; E=1; //E=1,才允许读写 _nop_(); //空操作 _nop_(); _nop_(); _nop_(); //空操作四个机器周期,给硬件反应时间 result=BF; //将忙碌标志电平赋给result E=0; return result; } /***************************************************** 函数功能:将模式设置指令或显示地址写入液晶模块 入口参数:dictate ***************************************************/ void Write_com (unsigned char dictate) { while(BusyTest()==1); //如果忙就等待 RS=0; //根据规定,RS和R/W同时为低电平时,可以写入指令 RW=0; E=0; //E置低电平(写指令时, // 就是让E从0到1发生正跳变,所以应先置\"0\" _nop_(); _nop_(); //空操作两个机器周期,给硬件反应时间 P1=dictate; //将数据送入P0口,即写入指令或地址 _nop_(); _nop_(); _nop_(); _nop_(); //空操作四个机器周期,给硬件反应时间 E=1; //E置高电平 _nop_(); _nop_(); _nop_(); _nop_(); //空操作四个机器周期,给硬件反应时间 E=0; //当E由高电平跳变成低电平时,液晶模块开始执行命令 } /***************************************************** 函数功能:指定字符显示的实际地址 入口参数:x ***************************************************/ void WriteAddress(unsigned char x) { Write_com(x|0x80); //显示位置的确定方法规定为\"80H+地址码x\" } /***************************************************** 函数功能:将数据(字符的标准ASCII码)写入液晶模块 入口参数:y(为字符常量) ***************************************************/ void WriteData(unsigned char y) { while(BusyTest()==1); RS=1; //RS为高电平,RW为低电平时,可以写入数据 RW=0; E=0; //E置低电平(写指令时,, // 就是让E从0到1发生正跳变,所以应先置\"0\" P1=y; //将数据送入P0口,即将数据写入液晶模块 _nop_(); _nop_(); _nop_(); _nop_(); //空操作四个机器周期,给硬件反应时间 E=1; //E置高电平 _nop_(); _nop_(); _nop_(); _nop_(); //空操作四个机器周期,给硬件反应时间 E=0; //当E由高电平跳变成低电平时,液晶模块开始执行命令 } /***************************************************** 函数功能:对LCD的显示模式进行初始化设置 ***************************************************/ void LcdInt(void) { delay(15); //延时15ms,首次写指令时应给LCD一段较长的反应时间 Write_com(0x38); //显示模式设置:16×2显示,5×7点阵,8位数据接口 delay(5); //延时5ms Write_com(0x0c); //显示模式设置:显示开,有光标,光标闪烁 delay(5); Write_com(0x06); delay(5); Write_com(0x01); //清屏幕指令,将以前的显示内容清除 delay(5); } /*********初始化***********/ void sysinit() { TMOD = 0x21; //设定定时器T1工作方式 T0 工作方式1 TH1=0xfd; //利用T1中断产生CLK信号 TL1=0xfd;//50khz EA = 1; //开总中断 ET1=1; TR1=1; //启动定时器T1 TH0=0x45; TL0=0x00;//50MS定时 ET0=1; TR0=1; start=0; // START: A/D转换启动信号,输入,高电平有效。 oe=0; //数据输出允许信号,输入,高电平有效。当A/D转换结束时,此端输入一个高电平,才能打开输出三态门,输出数字量。 } /***********T0中断服务程序************/ void t0(void) interrupt 1 { ET0=0; TH0=0X45;//46 TL0=0X00;//50MS定时 n++; if(n==20) {flag=1;n=0;} ET0=1; } void t1(void) interrupt 3 { ET1=0; CLK=~CLK; ET1=1; } //0808数据采集 unsigned char adc() { unsigned char Temp; start=1; // delay(1);//do not delay start=0; //启动信号 while (!eoc);// A/D转换结束信号 P0=0xff; // delay(1); oe=1; Temp=P0;//读取采集数据 oe=0; // delay(4); return(Temp);//返回采集数据 } void display(uint a)//显示子函数 { uint bai,shi,ge; bai=a/100; } void main() { uint temp1,temp2; uchar f=0; LcdInt(); delay(5); sysinit(); CLK=0; WriteAddress(0);//从第0行开始显示 WriteData('f');//显示 f WriteData(':');//显示 : WriteAddress(5);//从第5行开始显示 WriteData('H'); //显示H WriteData('z');//显示z while(1) { temp2=temp1; temp1=adc();//ad采集 if(((temp1>=128)&&(temp2<=128))||((temp1<=128)&&(temp2>=128))) { f++;// out_pulse=1; _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); out_pulse=0;//产生5us的脉冲信号 } if(flag==1)//到达1s后计算频率 { flag=0; f=f/2; display(f);//显示频率 f=0; } } } shi=(a-bai*100)/10; ge=a%10; WriteAddress(2); WriteData(0x30+bai);//显示百位 WriteData(0x30+shi);//显示十位 WriteData(0x30+ge);//显示个位 (6)实验结果截图: 3.9 实验 9:DAC0832 应用实验 (1)实验内容 在 Proteus 环境下搭建如下图 3.36 所示的电路图: 其中所用元器件如下表所示: 选件编号 U1 U2 U3 U4 RP1 R1 元件名称 参数 AT89C51 所在元件库类名 Microprocessor ICs 微处理器 Data Converters 数据转换器 Data Converters 数据转换器 Operational Amplifiers 运算放大器 Resistors 电阻 Resistors 电阻 子类名 8051 Family 8051 家族 D/A Converters D/A 转换器 A/D Converters A/D 转换器 Quad 四 Resistor Packs 排阻 Generic 一般的 生产厂家 ATMEL ADC0832 ADC0808 LM324 RESPACK- 10k- 8 8 RES 5k R2-R5 RES 10k Resistors 电阻 Generic 一般的 (2)控制要求 本实验利用 ADC0808 和 DAC0832 实现简单的信号采集与输出。ADC0808 的 4 路输入分别 为: 正弦波 In0,三角波 In1,方波 In2,接地 In3。 KEY1,KEY2 用于切换通道,当开关打开时其所连接的位为 1,闭合时所连接位为 0。当 KEY2 KEY1 的组合为 00 时选择 In0 通道,为 01 时选择 In1 通道,为 10 时选择 In2 通道, 为 11 时选择 In3 通道。 (3)实验步骤 ① 根据上述实验内容,参考 1.2.2,在 Proteus 环境下建立图 3.22 所示原理图,并将其 保存为 DAC0832_self.DSN 文件。 ② 根据(2)和(3)画出流程图,并编写源程序,将其保存为 DAC0832_self.c。 ③ 运行 Keil uVision2 开发环境,按照 1.1.3 节介绍的方法建立工程 DAC0832_self.uV2, CPU 为 AT89C51,包含启动文件 STARTUP.A51。 ④ 按照 1.2.2 第(6)节介绍的方法将 C 语言源程序 DAC0832_self.c 加入工程 DAC0832_self.uV2,并设置工程 DAC0832_self.uV2 属性,将其晶振频率设置为 12MHz, 选择输出可执行文件,仿真方式为选择硬仿真,并选择其中的“PROTEUS VSM MONITOR 51 DRIVER”仿真器。 ⑤ 构造(Build)工程 DAC0832_self.uV2。如果输入有误进行修改,直至构造正确,生 成可执行程序 DAC0832_self.hex 为止。 ⑥ 为 AT89C51 设置可执行程序 DAC0832_self.hex。 ⑦ 运行程序,观察运行结果,并验证其是否正确。 ⑨ 更改信号发射器的频率,再次验证其功能是否正确。(注意:因为是软仿真,所以信 号采集的速度受到限制,因此所输入的交流信号频率也不能太高,可以在 200Hz 以内 尝试)。 (4)实验程序: #include //AD端口定义 sbit CLK=P3^4; sbit start=P3^0; sbit oe=P3^1; sbit eoc=P3^2; //DA端口定义 sbit dacs=P2^7; sbit dawr=P3^6; #define uchar unsigned char #define uint unsigned int /*********初始化***********/ void sysinit() { TMOD = 0x20; //设定定时器T1工作方式 T0 工作方式1 TH1=0xfe; //利用T1中断产生CLK信号 start=0; // START: A/D转换启动信号,输入,高电平有效。 oe=0; //数据输出允许信号,输入,高电平有效。当A/D转换结束时,此端输入一个高电平,才能打开输出三态门,输出数字量。 } void t1(void) interrupt 3 { ET1=0; CLK=~CLK; ET1=1; } //adc0808数据采集 unsigned char adc() { unsigned char Temp; start=1; start=0; //启动信号 while (!eoc);// A/D转换结束信号 P1=0xff; oe=1; Temp=P1;//读取采集数据 oe=0; return(Temp);//返回采集数据 } void main() { uchar a; sysinit(); dacs=0; dawr=0; while(1) { a=adc();//AD转换 P0=a;//DA转换 } TL1=0xfe;// EA = 1; //开总中断 ET1=1; TR1=1; //启动定时器T1 } (5)实验结果截图: 3.12 实验 12:LCD12864 显示实验 (1)实验内容 在 Proteus 环境下搭建万年历电路图如下图 3.63 所示的: 图 3.63 LCD12864 显示实验 其中所用元器件如下表所示: 选件编号 元件名称 参数 U1 LCD1 RP1 R1,R2 AT89C51 AMPIRE 128*64 所在元件库类名 Microprocessor ICs 微处理器 Optoelectronic 光电器件 Resistors Packs 电阻 Resistors 电阻 子类名 8051 Family 8051 家族 Graphical LCDs 图形 LCD Generic 一般的 生产厂家 ATMEL RESPACK- 8 RES 10k (2)控制要求 本实验控制程序运行时,能够用 LCD12864 显示“山大电工电子中心”几个汉字,并实 现从下向上滚屏移动所显示内容。 (3)实验程序 #include\"reg52.h\" #include\"intrins.h\" #define uint unsigned int #define uchar unsigned char #define DATA P0 sbit rs=P2^2; sbit rw=P2^1; sbit en=P2^0; sbit cs1=P2^3; sbit cs2=P2^4; uchar code hzk[]={ 0x00,0x00,0xF0,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0xF0,0x00,0x00,0x00, 0x00,0x00,0x3F,0x20,0x20,0x20,0x20,0x3F,0x20,0x20,0x20,0x20,0x7F,0x00,0x00,0x00,/*\"山\0x20,0x20,0x20,0x20,0x20,0x20,0x20,0xFF,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x00, 0x80,0x80,0x40,0x20,0x10,0x0C,0x03,0x00,0x03,0x0C,0x10,0x20,0x40,0x80,0x80,0x00,/*\"大\0x00,0x00,0xF8,0x88,0x88,0x88,0x88,0xFF,0x88,0x88,0x88,0x88,0xF8,0x00,0x00,0x00, 0x00,0x00,0x1F,0x08,0x08,0x08,0x08,0x7F,0x88,0x88,0x88,0x88,0x9F,0x80,0xF0,0x00,/*\"电\0x80,0x82,0x82,0x82,0x82,0x82,0x82,0xE2,0xA2,0x92,0x8A,0x86,0x82,0x80,0x80,0x00, 0x00,0x00,0x00,0x00,0x00,0x40,0x80,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*\"子\ 0x00,0x00,0xF8,0x88,0x88,0x88,0x88,0xFF,0x88,0x88,0x88,0x88,0xF8,0x00,0x00,0x00, 0x00,0x00,0x1F,0x08,0x08,0x08,0x08,0x7F,0x88,0x88,0x88,0x88,0x9F,0x80,0xF0,0x00,/*\"电\0x00,0x04,0x04,0x04,0x04,0x04,0x04,0xFC,0x04,0x04,0x04,0x04,0x04,0x04,0x00,0x00, 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x3F,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x00,/*\"工\0x00,0x00,0xF0,0x10,0x10,0x10,0x10,0xFF,0x10,0x10,0x10,0x10,0xF0,0x00,0x00,0x00, 0x00,0x00,0x0F,0x04,0x04,0x04,0x04,0xFF,0x04,0x04,0x04,0x04,0x0F,0x00,0x00,0x00,/*\"中\0x00,0x00,0x80,0x00,0x00,0xE0,0x02,0x04,0x18,0x00,0x00,0x00,0x40,0x80,0x00,0x00, 0x10,0x0C,0x03,0x00,0x00,0x3F,0x40,0x40,0x40,0x40,0x40,0x78,0x00,0x01,0x0E,0x00,/*\"心\}; void delay(uint x) { uint i; while(x--) for(i=0;i<117;i++); } void checkstate() { uchar dat; rs=0; rw=1; do{ DATA=0x00; en=1; _nop_(); dat=DATA; en=0; dat=0x80&dat; }while(!(dat==0)); } void send_com(uchar com) { checkstate(); rs=0; rw=0; DATA=com; en=1; _nop_(); _nop_(); en=0; } void setline(uchar page) { page=0xb8|page; send_com(page); } void setstartline(uchar startline) { startline=0xc0|startline; send_com(startline); } void setcolumn(uchar column) { column=column&0x3f; column=0x40|column; send_com(column); } void setonoff(uchar onoff) { onoff=0x3e|onoff; send_com(onoff); } void writebyte(uchar dat) { checkstate(); rs=1; rw=0; DATA=dat; en=1; _nop_(); _nop_(); en=0; } void selectscreen(uchar a) { switch(a) { case 0:cs1=0; _nop_(); _nop_(); _nop_(); cs2=0; _nop_(); _nop_(); _nop_(); break; case 1:cs1=0; _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); cs2=0; _nop_(); _nop_(); _nop_(); break; } void clearscreen(uchar a) { uchar i,j; selectscreen(a); for(i=0;i<8;i++) { setline(i); setcolumn(0); for(j=0;j<64;j++) { writebyte(0); } } } void initlcd() { checkstate(); selectscreen(0); setonoff(0); selectscreen(0); setonoff(1); selectscreen(0); clearscreen(0); setstartline(0); } void display(uchar ss,uchar page,uchar column,uchar number) { int i; selectscreen(ss); column=column&0x3f; setline(page); setcolumn(column); for(i=0;i<16;i++) { writebyte(hzk[i+32*number]); } case 2:cs1=1; cs2=1; _nop_(); _nop_(); _nop_(); break; } void main() { uint i; initlcd(); clearscreen(0); while(1) { for(i=0;i<128;i++) { setstartline(i); display(1,0,0*16,0); display(1,0,1*16,1); display(1,0,2*16,2); display(1,0,3*16,3); display(2,0,4*16,4); display(2,0,5*16,5); display(2,0,6*16,6); display(2,0,7*16,7); selectscreen(0); delay(50); } } } (4)实验步骤 ① 根据上述实验内容,参考 1.2.2,在 Proteus 环境下建立图 3.63 所示原理图,并将其 保存为 LCD12864.DSN 文件。 ② 根据(2)和(3)画出流程图,并编写源程序,将其保存为 LCD12864.c。 ③ 运行 Keil uVision2 开发环境,按照 1.1.3 节介绍的方法建立工程 LCD12864.uV2,CPU 为 AT89C51,包含启动文件 STARTUP.A51。 ④ 按照 1.2.2 第(6)节介绍的方法将 C 语言源程序 LCD12864.c 加入工程 LCD12864.uV2, 并设置工程 LCD12864.uV2 属性,将其晶振频率设置为 12MHz,选择输出可执行文件, 仿真方式为选择硬仿真,并选择其中的“PROTEUS VSM MONITOR 51 DRIVER”仿真器。 ⑤ 构造(Build)工程 LCD12864.uV2。如果输入有误进行修改,直至构造正确,生成可 执行程序 LCD12864.hex 为止。 ⑥ 为 AT89C51 设置可执行程序 LCD12864.hex。 ⑦ 运行程序,观察运行结果,并验证其是否正确。 实验结果截图 } setline(page+1); setcolumn(column); for(i=0;i<16;i++) { writebyte(hzk[i+32*number+16]); } 因篇幅问题不能全部显示,请点此查看更多更全内容