十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

51单片机汇编实战:从LED到定时器、中断与串口编程

51单片机汇编实战:从LED到定时器、中断与串口编程 简介51单片机汇编语言教程PDF文档是一份面向单片机初学者的入门级汇编语言学习资料以STC89C52等典型芯片为例系统讲解MCS51系列单片机的架构、引脚功能、时钟复位电路与汇编指令基础并配套演示点亮LED等基础实验适合电子类专业学生、嵌入式爱好者入门使用。压缩包共含1个PDF文件大小约15.71MB内容完整覆盖28课讲义便于打印或离线阅读。当前已有264人下载学习。读者通过该教程可快速建立单片机最小系统概念理解P0P3口及ALE、PSEN等控制信号的作用掌握汇编语言程序的基本书写与调试思路为后续C语言开发和实际项目打下基础。1. 汇编教程PDF不是过时书51汇编与C的边界在哪里《51单片机汇编语言教程.pdf》这类资料在GitHub和网盘里被反复转载但真正从头到尾读完的人不多。多数人翻到第二章看到MOV、ADD和立即数寻址觉得不如Keil里写C来得直接就合上了。可十年以上的嵌入式工程师大多有一个共同经验遇到定时精度不够、RAM紧张、或者某段逻辑怎么调都不对的时候最后能救场的反而是当年没耐心看的汇编。51单片机的汇编语言并不复杂全部指令只有111条常用的不过三四十条。它解决的是C语言难以表达的底层控制问题比如对寄存器位操作、精确到机器周期的延时、以及中断现场保护。这篇内容按“看PDF学汇编”的实际路径来写先讲怎么理解指令和内存模型再给能在Proteus或开发板上跑通的最小程序最后把定时器、串口和查表技巧全部用汇编实现一遍。2. 从空工程到点亮LED51汇编最小可执行程序2.1 Keil C51里建汇编工程的三个关键设置学汇编的第一步不是背指令而是把编译器环境调对。Keil C51虽然名字里带C但完全可以写纯汇编。新建工程后在Source Group里添加一个 .asm 文件然后在Options for Target对话框里勾选“Create HEX File”这样编译后才会生成烧录用HEX。这里最容易犯错的是芯片型号选择选AT89C51还是STC89C52会影响内部RAM大小和寄存器布局。STC89C52有256字节RAM、8K Flash而AT89C51只有128字节RAM和4K Flash如果PDF里的例程假设了较大的栈空间直接在AT89C51上跑会莫名复位。汇编文件的开头通常要写明晶振频率因为51的指令周期与晶振直接相关。12MHz晶振下一个机器周期是1微秒11.0592MHz晶振下一个机器周期约为1.085微秒这也是串口波特率常用11.0592MHz的原因。初次跑汇编用12MHz就行计算延时比较直观。; LED闪烁程序 - 12MHz晶振P1.0接LED ORG 0000H ; 程序入口 AJMP MAIN ; 跳转到主程序 ORG 0030H ; 主程序起始地址 MAIN: MOV P1, #0FEH ; P1.0输出低电平LED点亮 LCALL DELAY ; 调用延时子程序 MOV P1, #0FFH ; P1全部输出高电平LED熄灭 LCALL DELAY SJMP MAIN ; 死循环 DELAY: MOV R2, #200 ; 外层循环200次 DELAY1: MOV R3, #250 ; 内层循环250次 DELAY2: DJNZ R3, DELAY2 ; R3减1不为0则循环 DJNZ R2, DELAY1 RET END这段代码的逻辑很直接ORG 0000H告诉汇编器程序从地址0开始存放单片机复位后PC指向0000H。AJMP MAIN是2字节跳转指令跳到0030H之后的主程序区避开后面的中断向量区。MOV P1, #0FEH是立即数寻址把0xFE写入P1端口对应二进制11111110只有P1.0引脚为低电平。延时子程序用双层循环嵌套DJNZ是“减1不为0跳转”指令判断和跳转都在这一个指令里完成比C语言写for循环编译后生成的代码紧凑得多。2.2 在Proteus仿真51单片机的最小硬件连接纯软件环境里验证汇编程序Proteus比实体开发板更合适。从元件库拖出AT89C51加上一个LED、一个220欧姆限流电阻、一个10微法电解电容和12MHz晶振两个30pF负载电容。连接方式照抄最普通的最小系统电路复位引脚RST通过10微法电容接VCC晶振接XTAL1和XTAL2。这里有个常见问题很多人在Proteus里忘了给芯片接电源AT89C51默认不显示VCC和GND引脚必须双击芯片在属性里把Power属性设为VCCGround设为GND。加载HEX文件后点击运行如果LED不闪先检查Program Counter是否停在0000H再单步执行看P1寄存器的值是否变化。多数情况下问题出在两个地方一是HEX文件没生成编译器配置里没勾选Create HEX File二是晶振频率没设对Proteus里的晶振属性要和程序注释里的一致。利用Proteus仿真51单片机跑汇编程序还有一个好处是能观察SFR的变化比如打开Debug菜单里的Watch窗口加入P1和R2、R3运行后能看到循环计数器的变化过程这比拿着示波器量引脚直观得多。2.3 汇编源程序中三类伪指令的固定写法汇编器处理的不是机器码而是文本。除了真正的指令还有一些为汇编器服务的伪指令它们不生成机器码只影响编译过程。51汇编最常用的伪指令是ORG、EQU、DATA、DB和END。LED_PIN EQU P1.0 ; 用EQU定义符号等价于C语言的#define COUNT DATA 30H ; DATA把变量名与片内RAM地址绑定 ; 用DB定义查表数据常用于数码管段码 TABLE: DB 0C0H, 0F9H, 0A4H ; 共阳极数码管0、1、2的段码EQU和DATA的区别在于EQU定义的是纯符号替换编译时把出现的标志换成后面的值DATA定义的是可寻址的RAM变量汇编器知道这个地址属于内部RAM在后续指令中可以使用直接寻址访问。初学者总在MOV A, #TABLE和MOV A, TABLE之间犹豫加了#号是取TABLE这个标号的地址值不加#号是取TABLE这个地址里存的内容。查表操作中要用MOVC指令配合DPTR来读代码段普通MOV只能访问数据空间这是51的哈佛结构决定的——程序存储器和数据存储器物理分离。3. 寻址方式与指令分类表看懂任何一段51汇编的关键3.1 51单片机寄存器与七种寻址方式的对应关系翻开任何一份《51单片机汇编语言指令大全》开头都会列寻址方式。51有七种寻址方式立即数寻址、直接寻址、寄存器寻址、寄存器间接寻址、变址寻址、相对寻址、位寻址。背表格没有意义关键要抓住一个主线——每条指令的源操作数和目的操作数各能用什么寻址方式是由指令编码格式决定的。以最常用的MOV为例目的操作数可以是累加器A、RnR0-R7、direct直接地址、Ri间接地址但源操作数的组合方式更多错误的组合编译直接报错。寄存器间接寻址R0和R1的操作对象是片内RAM的低128字节MOVX DPTR和MOVX R0用于访问片外RAM或外部设备。变址寻址MOVC A, ADPTR是查表指令的核心它的原理是把累加器的值和DPTR的值相加得到一个新的地址然后从程序存储器中读出数据。这种寻址方式不是通用的只用于访问程序存储器的常数表不理解这点后面写数码管查表程序大概率会栽跟头。寻址方式示例指令操作数含义常用场景立即数寻址MOV A, #55H常数0x55直接参与运算初始化寄存器、端口赋值直接寻址MOV 90H, A访问地址0x90P1端口操作SFR和片内RAM寄存器寻址MOV R0, A访问R0-R7通用寄存器临时变量、循环计数寄存器间接寻址MOV A, R0R0中的值是地址取该地址内容访问片内RAM 30H-7FH区域变址寻址MOVC A, ADPTRA和DPTR相加得ROM地址查表、段码转换相对寻址SJMP LOOP以PC当前值为基址跳转短距离跳转范围-128到127位寻址SETB P1.0对可位寻址区域单独置1开关量控制、状态标志3.2 数据传送、算术、逻辑与位操作指令的最小集合实际写代码用不到111条指令。按出现频率排序至少有一半指令永远碰不到。整理一套能应付90%场合的最小指令集比硬背指令大全高效得多。数据传送类核心是MOV外加MOVC读ROM、MOVX读外部RAM、PUSH压栈、POP出栈。算术类核心是ADD、SUBB、INC、DEC乘除法MUL AB和DIV AB只有需要乘法时才用。逻辑类核心是ANL、ORL、XRL位操作为SETB、CLR、CPL、JB、JNB。控制跳转类是AJMP、SJMP、LCALL、RET、DJNZ、CJNE。CJNE是一类容易被忽略的指令它执行“比较且不等则跳转”比较的结果存到进位标志CY中。可以用它实现汇编版的if-else比如判断A是不是等于0x30CJNE A, #30H, NOTEQ ; A不等于30H则跳到NOTEQ SJMP EQ ; A等于30H则顺序走到这里 NOTEQ: JC LESS ; 如果A小于30HC1跳转 SJMP GREATER ; 否则A大于30H LESS: ; 处理A 30H的情况 SJMP DONE GREATER: ; 处理A 30H的情况 DONE:这条指令的独特之处在于一条指令同时完成了“判断”和“跳转”两件事而且比较结果还保留在CY标志里。C语言编译器在生成if (a ! 0x30)的代码时底层用的往往就是CJNE。理解它就能把高级语言的判断结构翻译回汇编视角。DJNZ则是循环结构的标配多数延时和扫描程序都用它做计数器注意它的跳转范围只有相对寻址的-128到127字节延时子程序如果循环体太长可以嵌套两层DJNZ解决。3.3 用Keil的Memory Window验证指令执行结果纸上谈兵分析指令不够直观不如让KEIL把每一步都给你演出来。汇编工程编译通过后选择Start Debug Session进入调试模式打开View菜单里的Watch Call Stack Window和Memory Window。在Watch窗口里添加A、R0、R1、P1等SFR在Memory窗口输入C:0x0000查看程序存储器的机器码。单步执行一条指令观察A寄存器数字变化这个过程能帮你把汇编指令和机器码、寄存器操作三者对应起来。当汇编程序里的逻辑太复杂导致跟踪不过来时推荐用Disassembly窗口直接在反汇编视图里单步。Keil的调试器会自动把汇编源码和机器码对照显示灰色代码是尚未执行的指令。如果发现程序跳飞也就是PC指向了非预期地址最可能是栈操作不平衡调用子程序的LCALL和RET不匹配或者PUSH和POP的数量不一致。用断点在RET处停下看栈指针SP的值正常RET前SP应该指向压栈前的地址多压一个就多弹一个。调试中最实用的技巧是观察程序运行行数Keil右下角的SEC和STATES会显示从程序启动至今执行的机器周期数这正好用来验证延时计算理论算出的机器周期数和调试器显示的一致说明延时算法没有错这比示波器测量更快。若想在Proteus仿真51单片机同样看到这些信息可以在Proteus的调试菜单打开Watch Window但寄存器列表不如Keil丰富一般只在Keil里调逻辑在Proteus里看电路时序。4. 定时器、中断与串口的汇编实操程序结构优先于指令记忆4.1 51单片机定时计数器的寄存器配置顺序51单片机最常用的是定时器T0和T1它们是16位定时计数器由TH0/TL0或TH1/TL1两个8位寄存器组成。要配置它们工作操作顺序不能乱第一步设置TMOD选工作方式第二步往THx/TLx写入初始值第三步设置IE寄存器里对应的中断使能位第四步用SETB TR0启动定时器。这里回答一个检索量很高的问题51单片机串口通讯使用那个定时器——标准答案是T1工作在方式2即8位自动重装模式因为方式2下TH1的值会自动重装到TL1不需要在中断里手动恢复初值波特率的误差最小。TMOD寄存器的高四位控制T1低四位控制T0。每一位的含意是M1和M0选择工作方式C/T选择定时还是计数GATE选择是否受INTx引脚控制。方式1是16位定时器方式2是8位自动重装。写TMOD的值最好用十六进制整体赋值但注释里要写清每一位的意义比如MOV TMOD, #01H表示T0工作方式1也就是16位定时器且为定时模式不受外部引脚门控。这样三个月后自己回头看代码也知道当时为什么这么配。; 用T0方式1产生50ms定时中断在中断里翻转P1.0 ORG 0000H AJMP START ORG 000BH ; T0中断向量地址 AJMP T0_ISR ORG 0030H START: MOV TMOD, #01H ; T0工作方式116位定时 MOV TH0, #4CH ; 50ms初值高8位12MHz晶振 MOV TL0, #00H ; 50ms初值低8位 MOV IE, #82H ; EA1ET01开总中断和T0中断 SETB TR0 ; 启动T0 MOV P1, #0FFH MAINLOOP: SJMP MAINLOOP ; 主循环不做实际工作等中断 T0_ISR: MOV TH0, #4CH ; 重装初值 MOV TL0, #00H CPL P1.0 ; 翻转P1.0 RETI ; 中断返回这段代码的初值计算方式值得展开说明。12MHz晶振下机器周期是1us定时50ms需要50000个机器周期。16位定时器从0计数到65535溢出所以初始值65536-5000015536十六进制就是0x3CB0。代码里写TH0, #4CHTL0, #00H4C00H即0x4C00等于19456明显偏小了。我故意保留这个“错误”的写法是为了演示一个常见坑——教科书上50ms的初值是按6MHz晶振或12分频后的1us机器周期算的直接抄网上代码而不算晶振是定时不准的第一大原因。12MHz下50ms初值应该是TH00x3C; TL00xB0。另外方式1定时器溢出后不会自动重装初值必须在中断服务程序里手动给TH0/TL0重新赋值这种方式叫软件重装。软件重装的问题是从溢出到重装之间有若干条指令的延迟定时精度受中断响应时间影响。4.2 中断现场保护用汇编时最容易忽略的PUSH和POP51的中断系统有5个中断源排优先级顺序是INT0、T0、INT1、T1、串口。写汇编中断服务程序时现场保护比业务逻辑更关键。主程序正在用的R0、A、PSW等寄存器进入中断后可能被中断里指令改写。C编译器会替使用者安排寄存器组切换或自动压栈但手写汇编必须自己做。从汇编角度讲保护现场不外乎两条路在中断入口把用到的寄存器压栈出口前弹出或者通过设置PSW中的RS0和RS1把中断服务程序切到另一组工作寄存器。UART_ISR: PUSH A ; 保护累加器 PUSH PSW ; 保护程序状态字 MOV A, SBUF ; 读取接收缓冲 MOV RECV_BUF, A POP PSW ; 恢复PSW POP A ; 恢复A RETI串口中断里收发数据的寄存器通常不压栈因为SBUF本来就是硬件缓冲区读走就清空了。但凡是中断里用到A、B、DPTR、PSW的都要压栈。压栈顺序和弹出顺序相反后压的先弹。如果压入2个字节弹出3个RETI回归时SP指向错误位置程序跳飞。这类问题在Keil调试器中很显眼——运行后PC跳到0xFFFF或者在反汇编窗口里看不到连续指令流。排查时只要单步执行到RETI前检查SP相对压栈前是否只差2个字节52单片机的SP初值一般是07H压2个字节后是09HRETI后恢复07H才算正常。4.3 串口通信波特率计算与SMOD位的设置公式51单片机串口工作在方式1时波特率由T1的溢出率决定公式是波特率 (2^SMOD / 32) × T1溢出率。T1溢出率 晶振频率 / (12 × (256 - TH1))。这个公式是所有串口波特率计算题的核心。11.0592MHz晶振、9600波特率、SMOD0时TH1的计算结果是0xFD这是网上无数例程中TH10xFD的来源。注意这里的单位换算晶振11.0592MHz除以12是921600Hz这个值除以32等于28800256-0xFD328800/39600正好整除所以11.0592MHz晶振被选为串口常用的晶振不是巧合是数学上整除带来的无误差波特率。; 串口初始化9600波特率T1方式2自动重装 MOV TMOD, #20H ; T1工作方式28位自动重装 MOV TH1, #0FDH ; 波特率重装值 MOV TL1, #0FDH ; 初值方式2下由TH1自动重装 MOV SCON, #50H ; 串口方式1REN1允许接收 MOV PCON, #00H ; SMOD0波特率不倍频 SETB TR1 ; 启动T1 SEND_CHAR: MOV SBUF, A ; 写入SBUF即开始发送 WAIT_TI: JNB TI, WAIT_TI ; 等待发送完成标志TI1 CLR TI ; 必须软件清零TI否则下次发送异常 RET这里有个再老手也会犯的错SCON寄存器中的TI和RI标志不会自动清零发送完成后TI置1接收完成后RI置1都必须由软件清零否则下一次发送或接收会因为标志还挂着而跳过等待。很多时候串口发出去第一帧数据后面的都不正常查了半天发现是没清TI。PCON寄存器的SMOD位如果被意外置1波特率会加倍同样是9600的配置发给电脑会变成乱码。初始化时给PCON整个赋零值是最省心的做法。5. 查表法与精确延时51汇编里最实用的两个技巧; 数码管显示查表程序DPTR指向段码表A中存放要显示的数字 ; 共阳极数码管0-F的段码依次存在TABLED中 TABLED: DB 0C0H, 0F9H, 0A4H, 0B0H, 99H, 92H, 82H, 0F8H DB 80H, 90H, 88H, 83H, 0C6H, 0A1H, 86H, 8EH ; 以上为0、1、2、3、4、5、6、7、8、9、A、b、C、d、E、F的段码 DISPLAY: MOV DPTR, #TABLED ; 把表格起始地址装入DPTR MOVC A, ADPTR ; 关键查表指令ADPTR得到表内地址 MOV P0, A ; 段码送到P0口驱动数码管 RET查表法是汇编语言特有的编程技巧C语言里用数组就能解决但编译后C语言代码效率不一定高查表则一次MOVC指令完成取数。MOVC A, ADPTR的执行过程是先把A中已有的值与DPTR相加得到程序存储器中的一个新地址然后读出该地址里的字节存入A。注意查表结束后DPTR的值也变了如果要再次查表需要重新给DPTR赋值更隐蔽的坑是查表范围不能跨页即表格所在地址的高8位地址在查表前后不能一致否则查到的数据错位这个问题在表格超过256字节时必须拆成多个表或在地址对齐上做设计。精确延时方面DJNZ搭配空操作NOP是微调精度的标准手段。1个机器周期是固定的NOP就是不干活的空指令正好消耗1个机器周期。比如要延时100个机器周期可以先算好循环体实际消耗的周期差几个周期用NOP补齐。如果追求极端的定时精度注意LCALL调用本身需要2个机器周期RET返回又需要2个机器周期这些都要算进总周期数里。这是51汇编能做的核心优化工作也值得用Keil调试器的states域去验证。最后在Keil调试里验证延时精度的方法进入调试在延时子程序的前后各打一个断点运行至第一个断点清除states计数直到停在第二个断点看states值是否为期望的机器周期数。如果偏大或偏小微调NOP数量即可不需要上示波器。这个习惯能让所有汇编延时的精度控制在单个机器周期级别也避开了仿真器的外部因素干扰。调试汇编多看看states、SP、PC三个寄存器处理复杂问题的思路比背指令更管用——指令是兵寄存器是阵阵摆对了兵不需要太多。 p a hrefhttps://download.csdn.net/download/metaboss/88032716 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
返回列表