PWM技术实现灯光的无极调节(代码+详细注释)

课程设计,我只放代码,代码借鉴网上某位师兄的,其中有些错误,做了修改,并自己写了很多注释,适合小白阅读。不熟悉汇编,可能有纰漏,不过代码亲测可用,在学校机房试过了。注释只供借鉴,请禁止抄袭,代码请随便用。为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

你可能感兴趣的:(平时上课)