课程设计,我只放代码,代码借鉴网上某位师兄的,其中有些错误,做了修改,并自己写了很多注释,适合小白阅读。不熟悉汇编,可能有纰漏,不过代码亲测可用,在学校机房试过了。注释只供借鉴,请禁止抄袭,代码请随便用。为51单片机的
DIRLOP EQU 38H //显示字节数
DISP0 EQU 39H //串行显示寄存器0
DISP1 EQU 3AH //串行显示寄存器1
DISP2 EQU 3BH //串行显示寄存器2
LDAN EQU 58H //电灯亮度档值
T2H EQU 59H //T1高8位定时值存储器
T2D EQU 5AH //T1低8位定时值存储器
LAMP EQU P1.2 //电灯控制位
ORG 0000H
; 复位
; ORG 是汇编的伪指令 告诉编译器我的代码放在什么位置
; 在单片机复位的时候 在你没有做任何改变的时候 程序指针会是指向00000H的地址
; 所以我们把 START 卸载地址00H 让单片机复位后直接跳转到 START的程序段去执行
AJMP START
ORG 000BH
; 000BH是定时器T0中断源入口地址
LJMP TIME1 //T0中断程序(产生PWM基频)
ORG 001BH
; 001BH是定时器T1中断源入口地址
LJMP TIME2 //T1中断程序(控制脉冲宽度)
ORG 0030H
; ORG 30H 是你程序的代码开始地址,由于30H以前有中断向量入口和寄存器的地址,所以一般用户程序都是从30H开始的。
START:SETB LAMP //程序初始化
MOV A,#0
MOV LDAN,A
MOV DISP0,A
MOV DISP1,A
MOV DISP2,A
; 将电灯亮度档值、串行显示寄存器0、1、2的初始值都设置为0
MOV TMOD,#11H //设定两定时器为16位定时器
; 11H --> 0001 0001 TMOD,设置两个定时器,
; GATE为0,表示“或门”输出恒为1,“与门”的输出信号K由TRx决定(即此时K=TRx),定时器不受INTx输入电平的影响,由TRx直接控制定时器的启动和停止。
; C/T为0,为定时功能
; M1 M0 --> 0 1 ,为方式1,16位定时器/计数器
MOV IE,#10001010B //开启两定时器的中断请求
; 51单片机中断允许寄存器IE
; 设置为1,为开,设置为0,为关;
; D7:全局中断位,1,开; 1
; D6:无效位; 0
; D5:定时/计数2(52单片机) 0
; D4:串行口中断,0,关 0
; D3:定时/计数1,1,开 1
; D2:外部中断1,0,关 0
; D1:定时/计数0,1,开 1
; D0:外部中断0,0,关 0
MOV TH0,#0FCH //设定PWM基频(500Hz)
MOV TL0,#18H
; 0FC18H --> 1111 1100 0001 1000 = 64536 周期是1/500*2 = 0.001s = 1ms = 1000us
; 1个机器周期为1us
; 初始值 X = 65536 - 1000 = 64536
SETB TR0 //启动计数器T0
; 将寄存器TR0的值置一
; 主程序:MAIN
MAIN: ACALL KEY //调动键扫描子程序
ACALL TRANS //调动显示拆字子程序
ACALL DISP //调动串行显示子程序
ACALL DLYK //调动20ms延时程序
NOP
NOP
; NOP:空操作
SJMP MAIN
; 自己跳自己
; 子程序1:KEY,键扫描,根据按下不同的按钮执行,改变LADN的值并调用执行亮度值到PWM定时值转换程序。
KEY: MOV P2,#0FFH //向P2口写“1”,准备读取数据
; 0FFH = 1111 1111; 全部为高电位。灯泡不亮
NOP
NOP
; JNB 无符号,不小于则跳转,判断是否按下,按下电位为0,则跳。
KEY1: JNB P2.0,K1 //判断电灯变亮或变暗
; 按下K1,跳往K1程序,亮度增强
JNB P2.1,K2
; 按下K2,跳往K2程序,亮度减小
JNB P2.2,K3
; 按下K3,跳往K3程序,开启、关闭灯光
BACK: ACALL DONE //调用亮度值到PWM定时值转换程序
; 若K1、K2、K3都没有按下,或者按下之后处理完,执行亮度值到PWM定时值转换程序
RET
; RET:子程序返回指令,跳转到 “call 标号” 的下一句去运行。若有调用BACK则返回。让调用的可以继续执行下面的程序。
K1: ACALL DLYK //按键防抖动延时
; 延时20ms,确定是人为按下的,不是机器轻微的抖动引起的挤压。
JB P2.0,BACK
; JB: 无符号数的跳转判定,小于则跳。当抬起的时候跳转到BACK执行亮度值到PWM定时值转换程序。
J1: JNB P2.0,J1 //亮度档值加1
; 当K1一直被按住的时候自跳,不执行后续程序。
INC LDAN
; LDAN的值加一
MOV A,LDAN
; 将LDAN的值赋给A
CJNE A,#0,BACK //限定最高档值为255
; 255 == FFH , 256 = 100H ,A溢出,所以为0
; CJNE是Compare Jump Not Equal 比较不相等转移指令。
; 当A在256以下时,执行BACK程序,亮度值到PWM定时值转换程序。
DEC LDAN
; 若A为256超出最大限度,则将其设置为255。
SJMP BACK
; 依旧按照最大255的亮度调BACK,执行亮度值到PWM定时值转换程序。
K2: ACALL DLYK
; 防抖动
JB P2.1,BACK
; JB: 无符号数的跳转判定,小于则跳。当抬起的时候跳转到BACK执行亮度值到PWM定时值转换程序。
J2: JNB P2.1,J2 //亮度档值减1
; 当K2一直被按住的时候自跳,不执行后续程序。
MOV A,LDAN
; 将LDAN的值赋给A
CJNE A,#0,J22
; 当A不为0的时候执行J22程序
SJMP BACK
; 当A为0的时候,直接跳转BACK,按照0的亮度值显示。
J22: DEC LDAN
; LDAN的值减一
SJMP BACK
; 跳转BACK,执行亮度值到PWM定时值转换程序。
K3: ACALL DLYK
; 防抖动
JB P2.2,BACK
; JB: 无符号数的跳转判定,小于则跳。当抬起的时候跳转到BACK执行亮度值到PWM定时值转换程序。
J3: JNB P2.2,J3 //开关电灯
; 当K3一直被按住的时候自跳,不执行后续程序。
CPL TR0
; 对TR0取反,表示若定时器是开的就关掉,是关的就开启。
NOP
NOP
; 空操作
SETB LAMP
; 对电灯置1.按照原本的亮度显示。
SJMP BACK
; SJMP: 短跳。执行亮度值到PWM定时值转换程序。
; 子程序4:DLYK:延时程序
DLYK: MOV R4,#4AH //20ms定时程序
DLYK1:MOV R5,#62H
DLYK2:DJNZ R5,DLYK2
; DJNZ RN,REL 是一条件转移指令,先将工作寄存器Rn中的数减“1”,判断结果是否为“0”,
; 不为“0”程序就跳转到行标为REL的地方执行,
; 否则,为“0”就不转移,继续执行下一条指令。
DJNZ R4,DLYK1
RET
; 4AH = 72 , 62H = 98
; 精确延时时间为:1+(1*72)+(2*98*72)+(2*72)+ 2 =(2*72+3)*98+3 = 14409us 约等于20ms
; 子程序5:DONE ,亮度值到PWM定时值转换程序。
DONE:MOV A,LDAN //亮度值到PWM定时值转换程序
; 将亮度值LDAN赋给A寄存器
CJNE A,#0,DJ0
; 判断A是否为0,不为0跳DJ0执行后续程序
RET
; 若A的值为0,不做改变,直接返回
DJ0: MOV B,#2H //亮度值转化为定时器初始值
; 给寄存器B赋值为2H = 2 = 0010
MUL AB
; 累加器A与B寄存器相乘。将A和B中两个无符号8位二进制数相乘,所得的16位积的低8位存于A中,高8位存于B中。
CPL A
; CPL A :将累加器A的内容按位逻辑取反,不影响相关标志。
MOV R1,A
; 将A中的值赋给R1
MOV A, B
; 将B中的值赋给A
CPL A
; CPL A :将累加器A的内容按位逻辑取反,不影响相关标志。
MOV R2, A
; 将A中的值赋给R2
MOV T2H, R2
; 将R2中的值赋给T1高8位定时值存储器
MOV T2D, R1
; 将R1中的值赋给T1低8位定时值存储器
RET
; 返回
; 假设A的值为10H = 16 = 0001 0000
; 则MUL AB --> 20H = 0010 0000 A = 0001 0000 ,B = 0000 0000
; T2D = R1 --> 1101 1111 T2H = R2 --> 1111 1111
; 定时中断0:主频
TIME1: MOV TH0,#0FCH //定时中断0
MOV TL0,#18H
; FC18H = 64536 设置主频,重置定时周期。
PUSH ACC
PUSH PSW
; 保护现场
MOV A,LDAN
; 将LDAN的值赋给A
CLR P1.2 //开灯
; CLR: 置0,电灯亮
CJNE A,#0,TM1 //0档时立刻关掉电灯
; 判断A是否为0,若不为0则跳TM1,为零执行后续代码
SETB P1.2
; 对电灯置1,电灯不亮。
SJMP TBACK
; 跳结束程序
TM1: CJNE A,#0,TM2 //255档时亮度调到最大
; 判断A是否为0,不为0跳TM2,为0则执行下面代码
CLR TR1 //关闭定时器1
; 对TR1置1,即使关闭定时器1
SJMP TBACK
; 结束
TM2: MOV TH1,T2H //输入不同的T2定时值控制脉冲宽度
; 将T2H中的值赋给TH1,定时器1的高4位
MOV TL1,T2D
; 将T2D的内容赋给TL1,定时器1的低4位
; 若前面的LDAN为10H,T2D --> 1111 T2H --> 1101,则改变了TH1和TL1,改变了PWM的占空比,改变了亮度。
SETB TR1 //启动定时器1
; 开启定时器1
TBACK: POP PSW
POP ACC
RETI
; 恢复现场
TIME2: SETB P1.2 //关掉电灯
CLR TR1 //关闭定时器1
RETI
; 子程序2:TRANS:转十进制的代码,若LDAN = 10H = 0001 0000
TRANS: MOV A, LDAN //将LDAN转化为十进制
; 将LDAN的值赋给A ,A = LDAN = 10H = 0001 0000
MOV B, #64H
; B = 64H = 100 = 0110 0100
DIV AB
; DIV (unsigned divide) 无符号数除法
; 商存在 A寄存器
; 余数存在 B寄存器
; B = 10H , A = 00H
MOV DISP2,A
; DISP2 = 00H
MOV A, #0AH
; A = 0AH = 10 = 0000 1010
XCH A,B
; XCH A,B ; 数据交换指令 A 与 B 内的数据交换
; A = 10H , B = 0AH
DIV AB
; B = 06H, A = 01H
MOV DISP1,A
; DISP1 = 01H
MOV DISP0,B
; DISP0 = 06H
MOV 3CH, #0
; 3CH 中保存0
RET
; 返回
; TRANS:
; MOV 3CH,LDAN //亮度档值显示拆字
; MOV A,DISP2
; 最开始,DISP2 = 0, A = 0
; ANL A,#0F0H
; 做逻辑与运算,将累加器A与寄存器的值做与运算,结果存回累加器A。
; 0F0H = 240 = 1111 0000
; A = 0000 1111
; SWAP A
; SWAP是半字节交换指令。
; SWAP A 这条指令,将累加器A的高、低4位数据交换,
; 也就是低4位数据进入高4位,高4位数据进入低4位。
; A = 1111 0000
; MOV 3EH,A
; MOV A,DISP2
; ANL A,#0FH
; MOV 3FH,A
; RET
; 返回
; 子程序3:DISP:显示程序
DISP: MOV DIRLOP,#4 //串行显示程序(4位数码管)
; DTRLOP = 4
MOV R0,#3CH //3CH到39H是显示缓冲区
; 第一次 R0 = 3CH
DL0: MOV A,@R0
; 以LADN = 10H为例
; A = DISP2 = 06H
MOV DPTR,#TAB
; DPTR = TAB的地址,查表程序
MOVC A,@A+DPTR //查得相应的七段代码
; 将TAB中查表得到的数据赋给A
MOV SBUF,A //串行发送
DL1: JNB TI,DL1
; 当没有接收完毕的时候等待
CLR TI
; 清空状态值,继续接收后续数据
DEC R0
; R0 = 3BH
DJNZ DIRLOP,DL0
; 是一条件转移指令,先将DIRLOP中的数减“1”,
; 判断结果是否为“0”,不为“0”程序就跳转到行标为DL0的地方执行,
; 否则,为“0”就不转移,继续执行下一条指令。
RET
; 程序返回
TAB: DB 0FCH,60H,0DAH,0F2H,66H,0B6H //共阴七段数码管代码表
DB 0BEH,0E0H,0FEH,0E6H,0EEH,03EH,9CH
DB 7AH,9EH,8EH
END