文章目录
- 一、前言
- 二、仿真
-
- 1.016-定时计数器T0作定时应用技术(二)
-
- [1.1 电路仿真](#1.1 电路仿真)
- [1.2 仿真程序](#1.2 仿真程序)
- 2.017-99秒马表设计
-
- [2.1 电路仿真](#2.1 电路仿真)
- [2.2 仿真程序](#2.2 仿真程序)
- 3.018-数字钟﹝★﹞
-
- [3.1 电路仿真](#3.1 电路仿真)
- [3.2 仿真程序](#3.2 仿真程序)
- 4.019-点阵式LED"0-9"数字显示技术
-
- [4.1 电路仿真](#4.1 电路仿真)
- [4.2 仿真程序](#4.2 仿真程序)
- 三、总结
一、前言
`汇编语言是二进制指令的文本形式,与指令是一一对应的关系。比如,加法指令00000011写成汇编语言就是 ADD。只要还原成二进制,汇编语言就可以被 CPU 直接执行,所以它是最底层的低级语言。
最近对汇编的仿真产生兴趣,虽然尚未系统学习,但搜集了一些资料尝试实践。学习汇编有助于更深入理解硬件工作原理,顺带记录一下学习过程,也欢迎交流指正。
二、仿真
1.016-定时计数器T0作定时应用技术(二)
/*
016-定时计数器T0作定时应用技术(二)
一个典型的定时器中断控制多任务程序
4个LED会轮流闪烁,每个LED闪烁约0.4秒,闪烁5次,然后切换到下一个LED,形成轮流闪烁的效果。
*/
T0中断服务程序框图

主程序框图

1.1 电路仿真

1.2 仿真程序
c
/*
016-定时计数器T0作定时应用技术(二)
一个典型的定时器中断控制多任务程序
4个LED会轮流闪烁,每个LED闪烁约0.4秒,闪烁5次,然后切换到下一个LED,形成轮流闪烁的效果。
*/
; ===== 定义变量 =====
TCOUNT2S EQU 30H ; 2秒计时计数器
TCNT02S EQU 31H ; 0.2秒计时计数器
ID EQU 32H ; 状态ID(0-3,对应4个LED)
FLASH_COUNT EQU 33H ; 闪烁次数计数器
ORG 00H ; 程序从地址00H开始
LJMP START ; 跳转到主程序
ORG 0BH ; 定时器0中断向量地址
LJMP INT_T0 ; 跳转到定时器0中断服务程序
; ===== 主程序 =====
START:
MOV TCOUNT2S, #00H ; 初始化2秒计数器为0
MOV TCNT02S, #00H ; 初始化0.2秒计数器为0
MOV ID, #00H ; 初始化状态ID为0
MOV FLASH_COUNT, #00H ; 初始化闪烁次数计数器为0
; ===== 初始化所有LED为熄灭状态 =====
SETB P1.0 ; 熄灭LED1(共阳极接法,1=熄灭)
SETB P1.1 ; 熄灭LED2
SETB P1.2 ; 熄灭LED3
SETB P1.3 ; 熄灭LED4
; ===== 定时器0初始化 =====
MOV TMOD, #01H ; 设置定时器0为模式1(16位定时器)
; 计算并设置定时器初值(50ms定时,晶振12MHz)
; 65536 - 50000 = 15536 = 3CB0H
MOV TH0, #(65536-50000) / 256 ; 高8位:3CH
MOV TL0, #(65536-50000) MOD 256 ; 低8位:0B0H
SETB TR0 ; 启动定时器0
SETB ET0 ; 允许定时器0中断
SETB EA ; 开启总中断允许
SJMP $ ; 原地循环,等待中断
; ===== 定时器0中断服务程序 =====
INT_T0:
; 重装定时器初值(50ms)
MOV TH0, #(65536-50000) / 256
MOV TL0, #(65536-50000) MOD 256
; ===== 2秒计时 =====
INC TCOUNT2S ; 2秒计数器加1
MOV A, TCOUNT2S
CJNE A, #40, NEXT ; 比较是否达到40次(40×50ms=2000ms=2秒)
; 达到2秒,重置2秒计数器
MOV TCOUNT2S, #00H
; ===== 状态切换 =====
INC ID ; 状态ID加1(切换到下一个LED)
MOV A, ID
CJNE A, #04H, RESET_FLASH_COUNT ; 检查ID是否达到4
MOV ID, #00H ; ID达到4,重置为0(实现0-3循环)
; ===== 重置闪烁计数器 =====
RESET_FLASH_COUNT:
MOV FLASH_COUNT, #00H ; 新状态开始,闪烁次数清零
; 熄灭所有LED
SETB P1.0
SETB P1.1
SETB P1.2
SETB P1.3
SJMP NEXT ; 跳转到下一步
; ===== 0.2秒计时 =====
NEXT:
INC TCNT02S ; 0.2秒计数器加1
MOV A, TCNT02S
CJNE A, #4, DONE ; 比较是否达到4次(4×50ms=200ms=0.2秒)
; 达到0.2秒,重置0.2秒计数器
MOV TCNT02S, #00H
; ===== 检查闪烁次数 =====
MOV A, FLASH_COUNT
CJNE A, #10, DO_FLASH ; 检查是否达到10次(5次闪烁,每次闪烁包含亮和灭)
; ===== 闪烁5次完成,熄灭当前LED =====
; 根据当前ID熄灭对应的LED
MOV A, ID
CJNE A, #00H, CHECK1
; ID=0:熄灭LED1
SETB P1.0
SJMP DONE
CHECK1:
CJNE A, #01H, CHECK2
; ID=1:熄灭LED2
SETB P1.1
SJMP DONE
CHECK2:
CJNE A, #02H, CHECK3
; ID=2:熄灭LED3
SETB P1.2
SJMP DONE
CHECK3:
; ID=3:熄灭LED4
SETB P1.3
SJMP DONE
; ===== 执行闪烁 =====
DO_FLASH:
; 闪烁次数加1
INC FLASH_COUNT
; 根据当前ID闪烁对应的LED
MOV A, ID
CJNE A, #00H, FLASH1
; ID=0:LED1闪烁
CPL P1.0 ; 取反P1.0引脚
SJMP DONE
FLASH1:
CJNE A, #01H, FLASH2
; ID=1:LED2闪烁
CPL P1.1 ; 取反P1.1引脚
SJMP DONE
FLASH2:
CJNE A, #02H, FLASH3
; ID=2:LED3闪烁
CPL P1.2 ; 取反P1.2引脚
SJMP DONE
FLASH3:
; ID=3:LED4闪烁
CPL P1.3 ; 取反P1.3引脚
DONE:
RETI ; 中断返回
END ; 程序结束
2.017-99秒马表设计
/*
017-99秒马表设计
开始时,显示"00",第1次按下按键后就开始计时。
第2次按按键后,计时停止。
第3次按按键后,计时归零
*/

2.1 电路仿真

2.2 仿真程序
c
/*
017-99秒马表设计
开始时,显示"00",第1次按下按键后就开始计时。
第2次按按键后,计时停止。
第3次按按键后,计时归零
*/
; 符号定义
TCNTA EQU 30H ; 定时器中断计数器A(用于0.01秒计数)
TCNTB EQU 31H ; 定时器中断计数器B(用于秒计数)
SEC EQU 32H ; 秒计数变量(0-99)
KEYCNT EQU 33H ; 按键计数器
SP1 BIT P3.5 ; 按键连接到P3.5
; 程序入口
ORG 00H ; 复位向量
LJMP START ; 跳转到主程序
; 中断向量
ORG 0BH ; 定时器0中断向量
LJMP INT_T0 ; 跳转到定时器0中断服务程序
; 主程序
START:
MOV KEYCNT, #00H ; 初始化按键计数器为0
MOV SEC, #00H ; 初始化秒计数为0
; 显示初始值00
MOV A, SEC ; 将秒数送入累加器A
MOV B, #10 ; 除数10
DIV AB ; 将秒数分解为十位(A)和个位(B)
MOV DPTR, #TABLE ; 加载段码表地址
MOVC A, @A+DPTR ; 获取十位的7段码
MOV P0, A ; 输出到P0(十位数码管)
MOV A, B ; 取个位
MOV DPTR, #TABLE ; 重新加载段码表地址
MOVC A, @A+DPTR ; 获取个位的7段码
MOV P2, A ; 输出到P2(个位数码管)
; 定时器初始化
MOV TMOD, #02H ; 设置定时器0为模式2(8位自动重装)
SETB ET0 ; 允许定时器0中断
SETB EA ; 允许总中断
; 主循环
WT:
JB SP1, WT ; 等待按键按下(SP1为0表示按下)
; 按键消抖
LCALL DELY10MS ; 延时10ms
JB SP1, WT ; 再次检测,如果按键松开则为抖动
; 有效按键
INC KEYCNT ; 按键计数器加1
MOV A, KEYCNT ; 获取按键计数值
; 根据按键次数执行不同功能
CJNE A, #01H, KN1 ; 第一次按键:启动计时
SETB TR0 ; 启动定时器0
MOV TH0, #06H ; 设置定时器初值
MOV TL0, #06H
MOV TCNTA, #00H ; 清空中断计数变量
MOV TCNTB, #00H
LJMP DKN
KN1:
CJNE A, #02H, KN2 ; 第二次按键:停止计时
CLR TR0 ; 停止定时器0
LJMP DKN
KN2:
CJNE A, #03H, DKN ; 第三次按键:复位
MOV SEC, #00H ; 秒计数清零
; 显示复位后的00
MOV A, SEC
MOV B, #10
DIV AB
MOV DPTR, #TABLE
MOVC A, @A+DPTR
MOV P0, A
MOV A, B
MOV DPTR, #TABLE
MOVC A, @A+DPTR
MOV P2, A
MOV KEYCNT, #00H ; 按键计数器清零
; 等待按键释放
DKN:
JNB SP1, $ ; 等待按键释放
LJMP WT ; 返回主循环
; 10ms延时子程序
DELY10MS:
MOV R6, #20 ; 外循环20次
D1:
MOV R7, #248 ; 内循环248次
DJNZ R7, $ ; 内循环延时
DJNZ R6, D1 ; 外循环控制
RET
; 定时器0中断服务程序
INT_T0:
INC TCNTA ; 中断计数器A加1
MOV A, TCNTA
CJNE A, #100, NEXT ; 判断是否达到100次中断(1秒?实际是4秒)
MOV TCNTA, #00H ; 清空中断计数器A
INC TCNTB ; 中断计数器B加1
MOV A, TCNTB
CJNE A, #30, NEXT ; 判断是否达到30次
MOV TCNTB, #00H ; 清空中断计数器B
INC SEC ; 秒数加1
; 判断秒数是否达到100
MOV A, SEC
CJNE A, #100, DONE
MOV SEC, #00H ; 达到100秒则清零
; 更新显示
DONE:
MOV A, SEC
MOV B, #10
DIV AB
MOV DPTR, #TABLE
MOVC A, @A+DPTR
MOV P0, A
MOV A, B
MOV DPTR, #TABLE
MOVC A, @A+DPTR
MOV P2, A
NEXT:
RETI ; 中断返回
; 7段数码管段码表(共阴极)
; 0-9的7段码
TABLE:
DB 3FH, 06H, 5BH, 4FH, 66H, 6DH, 7DH, 07H, 7FH, 6FH
; 对应数字:0, 1, 2, 3, 4, 5, 6, 7, 8, 9
END
3.018-数字钟﹝★﹞
/*
018-数字钟
(1. 开机时,显示12:00:00的时间开始计时;
(2. P0.0/AD0控制"秒"的调整,每按一次加1秒;
(3. P0.1/AD1控制"分"的调整,每按一次加1分;
(4. P0.2/AD2控制"时"的调整,每按一次加1个小时;
*/

3.1 电路仿真

3.2 仿真程序
c
/*
018-数字钟
(1. 开机时,显示12:00:00的时间开始计时;
(2. P0.0/AD0控制"秒"的调整,每按一次加1秒;
(3. P0.1/AD1控制"分"的调整,每按一次加1分;
(4. P0.2/AD2控制"时"的调整,每按一次加1个小时;
*/
; 寄存器定义
SECOND EQU 30H ; 秒
MINUTE EQU 31H ; 分
HOUR EQU 32H ; 时
COUNT EQU 33H ; 定时中断计数
TEMP EQU 34H ; 临时寄存器
DISP_BUF EQU 40H ; 显示缓冲区(8字节)
; 位定义
KEY_SEC BIT P0.0 ; 调秒
KEY_MIN BIT P0.1 ; 调分
KEY_HOUR BIT P0.2 ; 调时
ORG 0000H
LJMP MAIN
ORG 000BH ; 定时器0中断入口
LJMP TIMER0_ISR
ORG 0100H
MAIN:
MOV SP, #60H ; 堆栈初始化
MOV SECOND, #00H
MOV MINUTE, #00H
MOV HOUR, #0CH ; 12点
MOV COUNT, #00H
LCALL UPDATE_DISP_BUF ; 初始化显示缓冲区
; 定时器0设置(方式1,50ms)
MOV TMOD, #01H
MOV TH0, #3CH ; 12MHz下,定时50ms
MOV TL0, #0B0H
SETB ET0
SETB EA
SETB TR0
MAIN_LOOP:
LCALL KEY_SCAN ; 按键扫描
LCALL DISPLAY ; 数码管显示
SJMP MAIN_LOOP
; 定时器0中断服务程序
TIMER0_ISR:
PUSH ACC
PUSH PSW
CLR TR0
MOV TH0, #3CH
MOV TL0, #0B0H
SETB TR0
INC COUNT
MOV A, COUNT
CJNE A, #20, TIMER_EXIT ; 50ms*20=1秒
MOV COUNT, #00H
INC SECOND
MOV A, SECOND
CJNE A, #60, UPDATE_TIME
MOV SECOND, #00H
INC MINUTE
MOV A, MINUTE
CJNE A, #60, UPDATE_TIME
MOV MINUTE, #00H
INC HOUR
MOV A, HOUR
CJNE A, #24, UPDATE_TIME
MOV HOUR, #00H
UPDATE_TIME:
LCALL UPDATE_DISP_BUF
TIMER_EXIT:
POP PSW
POP ACC
RETI
; 按键扫描程序
KEY_SCAN:
; 检查秒按键
JNB KEY_SEC, KEY_SEC_PRESS
SJMP CHECK_MIN_KEY
KEY_SEC_PRESS:
LCALL DELAY_20MS
JNB KEY_SEC, KEY_SEC_HANDLE
SJMP CHECK_MIN_KEY
KEY_SEC_HANDLE:
INC SECOND
MOV A, SECOND
CJNE A, #60, KEY_SEC_UPDATE
MOV SECOND, #00H
KEY_SEC_UPDATE:
LCALL UPDATE_DISP_BUF
JNB KEY_SEC, $ ; 等待按键释放
LCALL DELAY_20MS
RET
CHECK_MIN_KEY:
; 检查分按键
JNB KEY_MIN, KEY_MIN_PRESS
SJMP CHECK_HOUR_KEY
KEY_MIN_PRESS:
LCALL DELAY_20MS
JNB KEY_MIN, KEY_MIN_HANDLE
SJMP CHECK_HOUR_KEY
KEY_MIN_HANDLE:
INC MINUTE
MOV A, MINUTE
CJNE A, #60, KEY_MIN_UPDATE
MOV MINUTE, #00H
KEY_MIN_UPDATE:
LCALL UPDATE_DISP_BUF
JNB KEY_MIN, $ ; 等待按键释放
LCALL DELAY_20MS
RET
CHECK_HOUR_KEY:
; 检查时按键
JNB KEY_HOUR, KEY_HOUR_PRESS
RET
KEY_HOUR_PRESS:
LCALL DELAY_20MS
JNB KEY_HOUR, KEY_HOUR_HANDLE
RET
KEY_HOUR_HANDLE:
INC HOUR
MOV A, HOUR
CJNE A, #24, KEY_HOUR_UPDATE
MOV HOUR, #00H
KEY_HOUR_UPDATE:
LCALL UPDATE_DISP_BUF
JNB KEY_HOUR, $ ; 等待按键释放
LCALL DELAY_20MS
RET
; 更新时间到显示缓冲区
UPDATE_DISP_BUF:
; 时
MOV A, HOUR
MOV B, #10
DIV AB
MOV DISP_BUF, A ; 时十位
MOV DISP_BUF+1, B ; 时个位
; 横线
MOV DISP_BUF+2, #10
; 分
MOV A, MINUTE
MOV B, #10
DIV AB
MOV DISP_BUF+3, A ; 分十位
MOV DISP_BUF+4, B ; 分个位
; 横线
MOV DISP_BUF+5, #10
; 秒
MOV A, SECOND
MOV B, #10
DIV AB
MOV DISP_BUF+6, A ; 秒十位
MOV DISP_BUF+7, B ; 秒个位
RET
; 数码管显示
DISPLAY:
MOV R0, #DISP_BUF
MOV R2, #0FEH ; 位选初始值(P3.0=0,选中第一位)
MOV R3, #8
DISPLAY_LOOP:
MOV A, @R0
LCALL SEG_TABLE ; 查表获取段码
MOV P1, A ; 输出段码
MOV P3, R2 ; 输出位选
LCALL DELAY_2MS ; 延时2ms
MOV P3, #0FFH ; 关闭显示,消隐
INC R0 ; 指向下一个显示数据
MOV A, R2
RL A ; 左移一位,选中下一位
MOV R2, A
DJNZ R3, DISPLAY_LOOP
RET
; 共阴数码管段码表(0~9,横线)
; 顺序:P1.0~P1.7 -> a,b,c,d,e,f,g,dp
; 0: 3FH, 1: 06H, 2: 5BH, 3: 4FH, 4: 66H
; 5: 6DH, 6: 7DH, 7: 07H, 8: 7FH, 9: 6FH, -: 40H
SEG_TABLE:
INC A
MOVC A, @A+PC
RET
DB 3FH, 06H, 5BH, 4FH, 66H, 6DH, 7DH, 07H, 7FH, 6FH, 40H
; 延时子程序
DELAY_2MS:
MOV R6, #4
DL1: MOV R7, #250
DJNZ R7, $
DJNZ R6, DL1
RET
DELAY_20MS:
PUSH 00H
MOV R0, #100
DL2: LCALL DELAY_2MS
DJNZ R0, DL2
POP 00H
RET
DELAY_1MS:
MOV R6, #2
DL3: MOV R7, #250
DJNZ R7, $
DJNZ R6, DL3
RET
END
4.019-点阵式LED"0-9"数字显示技术
/*
019-点阵式LED"0-9"数字显示技术
利用8X8点阵显示数字0到9的数字。
*/
4.1 电路仿真

显示数字0
; 数字0
DB 00H,00H,3EH,41H,41H,41H,3EH,00H

显示数字1


显示数字2


显示数字3


显示数字4


显示数字5


显示数字6


显示数字7


显示数字8


显示数字9


4.2 仿真程序
c
/*
019-点阵式LED"0-9"数字显示技术
利用8X8点阵显示数字0到9的数字。
*/
TIM EQU 30H ; 中断计数器,用于计时
CNTA EQU 31H ; 位扫描指针(控制哪个数码管亮)
CNTB EQU 32H ; 显示数字索引(0-9)
ORG 00H ; 程序从地址00H开始
LJMP START ; 跳转到主程序
ORG 0BH ; 定时器0中断入口
LJMP T0X ; 跳转到中断服务程序
ORG 30H ; 主程序起始地址
START:
MOV TIM, #00H ; 初始化中断计数器
MOV CNTA, #00H ; 初始化位扫描指针
MOV CNTB, #00H ; 初始化数字索引
MOV TMOD, #01H ; 设置定时器0为模式1(16位定时)
; 计算并设置定时初值(4000次计数,假设12MHz晶振,约4ms中断一次)
MOV TH0, #(65536-4000)/256 ; 高8位
MOV TL0, #(65536-4000) MOD 256 ; 低8位
SETB TR0 ; 启动定时器0
SETB ET0 ; 允许定时器0中断
SETB EA ; 开启总中断
SJMP $ ; 原地循环,等待中断
T0X:
; 重装定时初值
MOV TH0, #(65536-4000)/256
MOV TL0, #(65536-4000) MOD 256
; --- 位选控制 ---
MOV DPTR, #TAB ; 指向位选码表
MOV A, CNTA ; 取当前位选索引
MOVC A, @A+DPTR ; 查表获取位选码
MOV P3, A ; 输出到P3口(控制哪个数码管亮)
; --- 段选控制 ---
MOV DPTR, #DIGIT ; 指向段码表
MOV A, CNTB ; 取当前显示数字索引
MOV B, #8 ; 每个数字占8个字节(8位数码管)
MUL AB ; 计算数字在表中的偏移地址
ADD A, CNTA ; 加上当前位选偏移
MOVC A, @A+DPTR ; 查表获取段码
MOV P1, A ; 输出到P1口(控制显示内容)
; 更新位选指针
INC CNTA
MOV A, CNTA
CJNE A, #8, NEXT ; 如果未到8,继续
MOV CNTA, #00H ; 到8则归零(8位数码管循环)
NEXT:
; 更新中断计数
INC TIM
MOV A, TIM
CJNE A, #250, NEX ; 计数250次(4ms×250=1秒)
MOV TIM, #00H ; 计时到1秒,重置计数器
; 更新显示数字
INC CNTB
MOV A, CNTB
CJNE A, #10, NEX ; 如果未到10,继续
MOV CNTB, #00H ; 到10则归零(0-9循环)
NEX:
RETI ; 中断返回
; 位选码表(共阴数码管,低电平选中)
; 依次选中第1~8位数码管
TAB: DB 0FEH,0FDH,0FBH,0F7H,0EFH,0DFH,0BFH,07FH
; 对应二进制:11111110, 11111101, 11111011, 11110111,
; 11101111, 11011111, 10111111, 01111111
; 段码表(共阴数码管,低电平点亮段)
; 每个数字8个字节,对应8位数码管的段码
DIGIT:
; 数字0
DB 00H,00H,3EH,41H,41H,41H,3EH,00H
; 数字1
DB 00H,00H,00H,00H,21H,7FH,01H,00H
; 数字2
DB 00H,00H,27H,45H,45H,45H,39H,00H
; 数字3
DB 00H,00H,22H,49H,49H,49H,36H,00H
; 数字4
DB 00H,00H,0CH,14H,24H,7FH,04H,00H
; 数字5
DB 00H,00H,72H,51H,51H,51H,4EH,00H
; 数字6
DB 00H,00H,3EH,49H,49H,49H,26H,00H
; 数字7
DB 00H,00H,40H,40H,40H,4FH,70H,00H
; 数字8
DB 00H,00H,36H,49H,49H,49H,36H,00H
; 数字9
DB 00H,00H,32H,49H,49H,49H,3EH,00H
END; 结束
三、总结
今天通过几个简单的51汇编仿真,我们亲眼看到了代码如何驱动硬件。算是为"更深层次了解单片机工作原理"这个目标迈出了一小步。感谢观看,后续继续分享!
感谢你的观看!
