找回密码
 立即注册
查看: 10|回复: 0

从零开始构建你的第一个8051汇编程序:掌握A51汇编语言核心知识

[复制链接]
  • 打卡等级:常住居民II
  • 打卡总天数:90
  • 最近打卡:2026-05-14 11:24:47

167

主题

1330

回帖

5101

积分

荣誉版主

积分
5101
发表于 6 小时前 | 显示全部楼层 |阅读模式
​本文介绍了8051单片机汇编语言编程的核心概念与项目框架。主要内容包括:1. 模块化编程基础:使用NAME伪指令命名模块,通过PUBLIC/EXTRN实现模块间函数调用,采用#include包含头文件定义特殊功能寄存器地址。
2.  程序段管理:使用SEGMENT声明可重定位段,CSEG AT处理绝对地址段,ORG伪指令避开中断矢量区,构建从复位入口到用户代码的完整执行路径。
3.  关键编程技巧:堆栈指针初始化、宏定义处理16位整数限制、延时函数实现与寄存器保护规范。
4.  完整项目示例:包含主程序模块和驱动模块,实现LED流水灯效果,展示了模块化汇编程序的标准结构。
该框架为8051汇编开发提供了规范化模板,涵盖了从基础语法到项目组织的核心知识。

前言
在嵌入式系统开发领域,8051单片机堪称一代经典。从最早Intel的MCS-51系列,到如今国产芯片厂商不断推出的增强型8051,如本文示例中的AI8051U,其核心指令集和基本架构始终保持着高度的兼容性。这也就意味着,掌握8051的汇编语言,不仅是理解单片机底层工作原理的最佳路径,更是你跨越不同芯片型号、深入嵌入式开发的“基石”。
目前主流的8051开发环境,当属Keil的μVision IDE,它集成了C51编译器、A51汇编器、BL51连接器等一系列工具。对于一个C语言编写的51程序来说,C51编译器在幕后做的第一件事,就是将其“翻译”成标准的A51汇编语言文件,然后再交由A51汇编器生成可重定位的目标代码,最后通过连接定位器生成最终的机器码,也就是我们烧录到片内的HEX文件。可以说,A51汇编语言是C51编译器的基础,也是我们与8051硬件直接对话的语言
本文使用的“金水32051编译器”,是一款全面兼容Keil A51汇编规范的开发工具。我们将围绕一个完整的、基于AI8051U单片机8BIT模式的A51汇编项目框架,详细介绍一系列核心伪指令和编程规范。这个项目的具体效果是:通过编写两个汇编模块,让连接在P17、P16和P15引脚上的LED依次闪烁,形成流水灯效果。
无论你是完全零基础的初学者,还是已有一定C语言经验、希望向下扎深根基的开发者,相信这篇文章都能帮你建立起对8051汇编程序结构的清晰认知。

一、 程序框架概览:模块化与伪指令
在正式开始之前,我们先看一眼这个项目的代码结构。它由三个文件组成:
  • C351_XSFR_AI8051U_8BIT.INC:头文件,定义了AI8051U在8位模式下所有特殊功能寄存器(SFR)的地址。
  • A51_Demo_main.A51:主程序模块,包含程序的启动入口、主循环和LED控制逻辑。
  • DRV_PortX_250MS.S51:驱动程序模块,负责各端口的初始化和精确的毫秒级延时。
将程序分为主程序和驱动程序两个模块,不仅仅是为了代码整洁,更是为了向你展示如何在实际项目中进行模块化的汇编程序设计。这里涉及到的第一个关键知识点,就是模块命名
1. NAME 伪指令:给模块起个名字
在A51汇编中,每个源文件开头都应该使用NAME伪指令来为当前模块指定一个名字。
NAME    A51_DEMO
NAME伪指令的作用是为后续生成的目标文件模块提供一个标识。当你在一个大型项目中包含多个源文件时,连接器(Linker)就是通过这些名字来区分和管理不同的模块的。如果某个源文件没有使用NAME指令,汇编器通常会默认以源文件名(去掉扩展名)作为模块名。但显式地使用NAME指令是一个非常好的编程习惯,它让模块的意图更加明确,不受文件名更改的影响。
2. #include 宏指令:代码复用与头文件
在C语言中你已熟悉的#include,在A51汇编中同样存在。它是一个宏指令,意味着它由汇编器的预处理器来处理。
#include    "..\\C351_XSFR_AI8051U_8BIT.INC"
这条指令的作用,就是让预处理器在编译之前,将
C351_XSFR_AI8051U_8BIT.INC文件的所有内容原封不动地复制到当前文件的这条指令所在位置。头文件.INC中通常包含了像P0、P1、SP、PSW等所有特殊功能寄存器的地址定义,比如:
P0  DATA    80HSP  DATA    81H; ... 等等
有了它,我们就无需在每个程序文件里重复书写这些枯燥的SFR地址了,大大提升了代码的可维护性和复用性。引入头文件,是任何一个规范性项目的第一步。
3. 模块间对话:PUBLIC 与 EXTRN既然项目被拆分成了A51_Demo_main.A51和DRV_PortX_250MS.S51两个模块,那么主程序要调用驱动程序里的PortX_Init_STC(端口初始化)和DRV_Delay_250MS(延时函数),它们之间就必须有一种对话机制。
这就是PUBLIC和EXTRN(通常也拼写为EXTERN)的作用。
  • 在定义模块(驱动程序)中:你需要将对外公开的函数名或变量名声明为PUBLIC。
PUBLIC  PortX_Init_STCPUBLIC  DRV_Delay_250MS
这就相当于告诉汇编器和连接器:“这两个符号(函数)是我提供的,其他模块可以合法地使用它们。”
  • 在调用模块(主程序)中:你必须用EXTRN声明那些来自外部的符号。
EXTRN   CODE    (PortX_Init_STC, DRV_Delay_250MS)
这里的CODE是一个段类型关键字,它告诉汇编器,这两个符号(具体来说是地址标签)位于代码空间(ROM)中,而不是数据空间(RAM)中。EXTRN声明让汇编器在编译本模块时,遇到LCALL PortX_Init_STC这样的指令,知道这是一个外部引用,暂时不把它解析为具体地址,而是留一个“待定”的记号;最终,由连接器在定位阶段,根据PUBLIC声明找到函数真实的入口地址,再把这个“待定”的记号填上正确值。
PUBLIC和EXTRN这一对伪指令,是实现模块化编程的基石。

二、 程序的骨架:段(SEGMENT)与定位
如果说PUBLIC/EXTRN解决了模块间“横向”的符号引用问题,那么“段”(SEGMENT)的机制则解决了程序在芯片地址空间中“纵向”的布局问题。这是初学者理解上的第一个难点,请务必跟上。
1. SEGMENT 伪指令与可重定位段
在汇编程序里,代码和数据最终都要被放置到具体的存储器地址上。传统的方式是直接在程序里写死地址,比如一些简单的汇编器用ORG 1000H。但在模块化的项目中,你无法预知你的模块会被连接器排布在哪个确切位置。于是,可重定位段的概念就诞生了。
MYPROG  SEGMENT CODE
这条语句做了一件事:声明一个类型为CODE(程序存储器)的、名为MYPROG的可重定位段。它仅仅是声明,并没有实际分配空间。
你可以把“段”想象成一个逻辑容器。所有放入MYPROG这个容器里的代码,在编译时会生成带有浮动地址的目标代码。等到连接时,连接器会把项目中所有相同或不同名字的CODE段,按照你设定的规则,井然有序地排列在一个连续的物理ROM空间中,并最终为它们填上正确的绝对地址。这就是“重定位”的含义。
2. 重定位段的类型
A51汇编支持多种类型的段,用于映射到8051单片机不同的存储空间。通过SEGMENT关键字后的类型指定:
  • CODE段:用于存放程序代码和常量数据,映射到8051的64KB程序存储空间(ROM)。这是最常用的段,我们用MYPROG SEGMENT CODE来声明。
  • DATA段:用于存放可直接寻址的内部RAM变量(地址00H~7FH)。声明方式如 MYVAR SEGMENT DATA。访问速度最快,但空间极小。
  • XDATA段:用于存放外部RAM变量,通过MOVX指令访问,地址范围可达64KB,声明方式如 MYBIGDATA SEGMENT XDATA。传统的51需外扩RAM,而像AI8051U这种增强型芯片已在片内集成了大容量的XDATA空间。
  • IDATA段:用于存放可间接寻址的内部RAM,覆盖全部00H~FFH。
  • BIT段:用于存放位变量,对应内部RAM的位寻址区(20H~2FH)。
上述段都是“可重定位”的,它们是构建大型、结构化程序的基础。
3. CSEG AT:绝对段与复位地址
尽管可重定位段很灵活,但有一个神圣不可侵犯的位置,必须使用绝对地址:复位入口地址0x0000
8051单片机上电或手动复位后,程序计数器(PC)会被硬件强制置为0000H。CPU总是从0000H这个地址开始取指执行第一条指令。这个行为是硬件决定的,无法更改。
因此,我们必须在ROM的物理地址0x0000处,放置一条指令。如何做到在高级的段定义机制下,也能精确控制某个代码的物理地址呢?这就要求助于绝对段和AT指令。
在主程序模块中,我们看到了这样一段代码:
// ==== 绝对代码段 =======================    CSEG    AT   0// 复位地址 0x0000    LJMP    BOOT
让我们逐行解读:
  • CSEG:这是一个伪指令,表示当前接下来的内容,将是“代码绝对段”(Code Segment)。与前面SEGMENT声明的可重定位段不同,CSEG后面的内容地址将由程序员直接指定。
  • AT 0:这是对绝对段地址的精确设置。它告诉汇编器:“把我下面生成的代码,从程序存储器的绝对物理地址0开始安放。” 这一招,让我们在模块化的可重定位世界里,获得了一个可以精确瞄准的“定点武器”。
  • LJMP BOOT:这是一条8051的长跳转指令(Long Jump),它是一个3字节的指令。在地址0x0000处,必须也只能放一条跳转指令。这是所有8051程序框架的铁律。为什么?请看下一节。
4. LJMP 与 ORG:让开中断矢量区
LJMP是一条可以跳转到64KB程序空间内任何地址的无条件转移指令。我们让它在0x0000处,越过一段区域,跳到我们自己的引导代码BOOT处。
这段被“越过”的区域,就是中断矢量区
8051单片机的每一个中断源,都有一个固定的入口地址(中断矢量)。比如:
  • 外部中断0:地址 0003H
  • 定时器0:地址 000BH
  • 外部中断1:地址 0013H
  • 定时器1:地址 001BH
  • 串口中断:地址 0023H
  • ……
从中断矢量表可以看出,每个中断矢量的间隔仅为8个字节。这个空间通常不够放一个完整的中断服务程序。所以,标准做法是,在每个中断的入口地址处,也放一条跳转指令,跳转到该中断的真正处理函数处。但若我们的程序压根没用到某些中断,就必须确保没有随机代码占据这些位置,否则一旦中断被误触发,程序就会“跑飞”。
因此,一个健壮的框架,主程序必须主动“让开”整个中断矢量区。本项目中,作者是通过ORG伪指令配合跳转来实现的。仔细看主程序代码:
// 复位地址 0x0000    LJMP    BOOT// ---- 让开 中断矢量 -------------// 63个中断  63x8+3=507字节    ORG     200H
  • ORG伪指令:ORG(Origin)用于设置当前段内的地址计数器(PC值)。注意,它操作的是逻辑地址。在这里,我们已经在CSEG AT 0的绝对段环境下了,ORG 200H的意思就是“将当前的汇编地址,向后推移到相对于段起始地址(0)偏移200H的位置”。
  • 为什么是200H?作者注释说明:AI8051U芯片非常强大,其支持的中断数远超传统8051的5个。Keil的C51编译器最大支持32个中断(中断号0~31),而金水32051编译器支持最大64个中断(中断号0~63)。每个中断矢量地址依旧占用8个字节空间,那么63个中断(0~62)加上复位,总占用空间如何计算?
            传统8051:复位0x0000,中断矢量从0x0003开始,每隔8字节一个,最后一个中断矢量地址 = 0x0003 + 中断号 * 8。 若最大中断号是62,则最后一个矢量地址 = 3 + 62*8 = 3 + 496 = 499 = 0x01F3。 该矢量占8字节,到0x01FA。那么下一个可用地址大约是0x01FB。这里直接跳转到200H(512),是一个对齐良好的、绝对够用的避让位置。63x8+3=507字节,从0开始算到200H正好512字节,留足了余量,非常清晰。用户程序从地址0x200开始,根本目的就是为了避开所有可能的中断矢量地址,确保程序的坚固性。
ORG在这里的作用,不是移动代码,而是调整地址计数器的值,相当于在LJMP BOOT这条指令之后,生成了大量的“空洞”。LJMP在0x0000,然后地址计数器直接跳到0x200,BOOT标号代表的代码就实际烧录在ROM的0x200位置了。这样,我们就完美地构建了一个“越过”中断矢量区的启动结构。

三、 引导与环境初始化
跳转指令的目的地BOOT,是我们用户程序真正开始执行的起点。
BOOT:    MOV     SP,  # 80H   ; 堆栈顶初始化    MOV     PSW, # 00H   ; 程序状态字初始化    LJMP    A51_MAIN
1. 堆栈指针SP的初始化:为什么是80H?
这是8051硬件决定的。系统复位后,堆栈指针SP的默认值是07H。这意味着堆栈将从内部RAM的08H单元开始向上生长。然而,8051的内部RAM中,00H到1FH这32个字节被分配给了四组工作寄存器(R0~R7),20H到2FH这16个字节是位寻址区,这些都是程序运行中非常宝贵的资源。如果堆栈从08H开始,只要堆栈深度稍大,就会与工作寄存器区和位寻址区发生灾难性的重叠,导致数据被意外篡改。
因此,任何正规的8051程序,在BOOT代码里要做的第一件头等大事,就是把SP重新指向一个安全的区域。通常的做法是指向内部RAM的高端,例如80H。这样,堆栈就从81H开始向上增长(因为8051是满递增堆栈,即SP先加1,再存数据),一直可以安全地用到内部RAM的顶部(标准8051为0FFH),为工作寄存器和位变量留出了完整的低128字节空间,互不干扰。
2. 调用可重定位段的用户主程序
LJMP A51_MAIN引导我们进入了真正的用户代码。A51_MAIN位于哪里呢?就在前面声明的可重定位段MYPROG中。
    RSEG    MYPROGA51_MAIN:
RSEG(Relocate Segment)伪指令表示“选择并激活一个之前已声明的可重定位段作为当前段”。这里它激活了MYPROG SEGMENT CODE,所以其后的代码将被放入这个可重定位的CODE段中。最终,A51_MAIN这个标签的具体物理地址,将由连接器在排布所有CODE段时决定,但它一定在ROM的某个位置,并且被BOOT中的LJMP正确引用。
你看,程序的骨架就这样被清晰地构建起来了:绝对段负责硬性的入口和跳转,可重定位段负责承载灵活的用户程序模块

四、 驱动程序探秘:宏、常数与精密延时
现在我们切换到驱动程序模块DRV_PortX_250MS.S51。这个模块除了展示PUBLIC的使用,更展示了A51汇编中宏处理的用法及其局限性。
1. 频率常数的定义与16位整数限制
为了让延时函数具有通用性和可移植性,作者没有硬编码循环次数,而是用一个“宏定义”来表示系统主频。
#define  Fosc_KHZ   33177
你可能会问,为什么系统运行主频是33.1776MHz,却定义成33177,要以KHz为单位呢?这牵扯到一个非常重要的知识:Keil及兼容的A51汇编器,其宏语言中的表达式计算器,只支持16位无符号整数。
16位整数的最大值是65535。33.1776MHz换算成Hz就是33177600,这个数值远远超出了16位表达范围。如果用这个值去参与宏表达式的计算,会发生溢出,导致错误的计算结果。而使用以KHz为单位的33177,它小于65535,是一个安全、合法的16位整数。所以,在A51宏处理中,涉及主频的计算,通常以KHz为单位进行处理,这是汇编编程中一个非常务实的技巧。
2. MACRO 与 ENDM:定义你自己的指令
宏(Macro)是高级汇编器的标志性功能,它允许你把一组重复的指令序列定义成一个“宏函数”,然后在程序中像使用新指令一样调用它。
Set_Times MACRO  WH, WL, Times    MOV     WH, #HIGH (Times)    MOV     WL, #LOW (Times)ENDM
  • MACRO关键字:标志着宏定义的开始。Set_Times是我们给这个宏起的“指令名”。后面的WH, WL, Times是形式参数。
  • 宏体:在MACRO和ENDM之间的就是宏体。这里它用到了HIGH和LOW两个运算符,它们同样是在宏处理阶段计算的,可以把Times这个数值的高8位和低8位立即数分别传给工作寄存器。
  • ENDM关键字:标志着宏定义的结束。
定义了宏之后,在程序中就可以这样调用它:
Set_Times R3, R4, MS_Times
在编译时,汇编器会把这条语句“展开”为:
    MOV     R3, #HIGH (MS_Times)    MOV     R4, #LOW (MS_Times)
对于延时循环中需要加载一个16位计数值到R3、R4的重复操作来说,这个宏让它变得既简洁又不容易出错。这是一种用汇编语言实现“高级抽象”的巧妙方法。
3. 延时函数的实现与现场保护
延时函数DRV_Delay_250MS是一个非常精确的软件延时实现。它通过多重循环来消耗CPU时间。注意函数开头和结尾:
DRV_Delay_250MS:    PUSH    02H   ; 入栈R2    PUSH    03H   ; 入栈R3    PUSH    04H   ; 入栈R4    ; ... 延时循环体使用了R2,R3,R4 ...    POP     04H    POP     03H    POP     02H    RET
这是一个重要的子程序编程规范:现场保护。一个函数如果内部要使用某些寄存器(这里是R2、R3、R4,寄存器地址分别为02H、03H、04H),而在调用者那里这些寄存器可能也存有重要数据,那么函数就有责任在执行前把它们备份到堆栈中(PUSH),执行完毕后再恢复(POP)。PUSH和POP的顺序遵循“后进先出”原则,这里成对出现,保证了数据的完整性。RET指令则从堆栈中弹出返回地址,使程序回到主循环中。

五、 融会贯通:程序的整体运行逻辑图最后,让我们把所有的“骨架”和“知识点”串联起来,看看这个程序从加电到流水灯闪烁的完整旅程:
  • 上电复位:硬件强制PC = 0x0000。
  • 绝对段执行:CPU执行在CSEG AT 0处摆好的LJMP BOOT指令。
  • 跨越中断矢量区:LJMP指令将程序计数器直接带到ROM的0x200处(BOOT标签所在物理地址),越过了中断矢量表。
  • 环境初始化:BOOT处的代码初始化SP到0x80,建立安全堆栈;清零PSW。
  • 跳入主程序:LJMP A51_MAIN将控制权交给布局在MYPROG可重定位段中的用户代码。
  • 主程序初始化:调用外部模块PortX_Init_STC(其入口地址由连接器在定位时通过PUBLIC/EXTRN解决),将P0~P7所有端口配置为准双向口并输出高电平(熄灭所有LED)。
  • 主循环:进入Main_Loop,依次通过CLR P1x(置低电平点亮LED),调用DRV_Delay_250MS(一个利用宏精确计算的循环来消耗约250毫秒),再改变点亮下一个LED的位,循环往复,形成流水灯效果。
  • 模块间的无缝协作:整个过程中,主模块通过EXTRN声明来调用驱动模块的PUBLIC函数,两个模块的CODE段被连接器合理排布,绝对段和可重定位段各司其职。

总结
通过拆解这个金水32051编译器下的A51汇编项目框架,我们触及了8051单片机汇编编程最核心、最通用的那些概念。
我们从最基础的模块命名NAME、头文件包含#include开始,理解了模块化协作的“信使”——PUBLIC与EXTRN声明。接着我们深入了程序的“骨架”结构:用SEGMENT声明可重定位的CODE段来承载商业逻辑,又用CSEG AT 0的绝对段来精准守住0x0000这个天字第一号入口,并用一条LJMP指令开启了程序的征程。ORG伪指令则像一把尺子,精确地量出了从0x0000到0x200的空洞,为64个中断矢量让出了绝对安全的空间。
在初始化环节,我们将堆栈SP指向0x80,为程序运行划清了工作区和堆栈区的楚河汉界。而在驱动模块中,我们见识了宏MACRO与ENDM的威力,也明白了汇编器表达式只支持16位整数这一限制所带来的精妙处理——将频率以KHz为单位进行计算。
这篇文章的范例,麻雀虽小,五脏俱全。它不仅仅是一份跑马灯程序,更是一个严格遵守51汇编规范的、坚固的、可扩展的项目“模板”。当你要在这个基础上增加定时器中断、串口通信、外部中断时,你只需要在中断矢量区0003H、000BH等位置,放上相应的跳转指令,并在你的驱动模块中编写中断服务函数,将其声明为PUBLIC,一个复杂项目的框架就自然形成了。
掌握这些知识后,你看到的将不再是一条条的指令,而是一座规划有序、运转高效的数字逻辑大厦。希望这篇文章能成为你踏入8051汇编世界、探索计算机体系结构底层奥秘的一把钥匙。勤动手,多实践,勇于修改范例中的每一处细节,你终会感受到与硬件亲密对话的掌控之美。

下面是完整的汇编语言程序:
一、 A51汇编语言主程序模块
汇编语言主程序模块“A51_Demo_main.A51”。
/* ---------------------------------------------

金水32051编译器
A51编程 项目框架 主程序

单片机 AI8051U-8BIT
系统运行主频 33.1776MHz

作者: 杨为民 2026-05
QQ: 86547995

--------------------------------------------- */
NAME A51_DEMO

// ==== SFR 定义 ===========================
#include "..\C351_XSFR_AI8051U_8BIT.INC"

// ==== 模块接口 定义 ===========================
EXTRN CODE (PortX_Init_STC, DRV_Delay_250MS)

// ==== 重定位段 定义 ===========================
MYPROG SEGMENT CODE

// ==== 绝对代码段 =======================
CSEG AT 0

// 复位地址 0x0000
LJMP BOOT

// ---- 让开 中断矢量 -------------
// 63个中断 63x8+3=507字节
ORG 200H

// ---- 引导程序 -------------
BOOT:
MOV SP, # 80H; 堆栈顶初始化
MOV PSW, # 00H; 程序状态字初始化
LJMP A51_MAIN;

// ==== 用户程序段 入口 ===================
RSEG MYPROG
A51_MAIN:
// ---- 初始化 ------------------------
LCALL PortX_Init_STC;
MOV P1, # 0FFH;
MOV P4, # 0FFH;

// ---- 主循环 开始 ----------
Main_Loop:
CLR P17;
SETB P16;
SETB P15;
CLR P07;
SETB P06;
SETB P05;
LCALL DRV_Delay_250MS;

SETB P17;
CLR P16;
SETB P15;
SETB P07;
CLR P06;
SETB P05;
LCALL DRV_Delay_250MS;

SETB P17;
SETB P16;
CLR P15;
SETB P07;
SETB P06;
CLR P05;
LCALL DRV_Delay_250MS;

// ---- 主循环 结束 ----------
LJMP Main_Loop;

END


二、驱动程序模块
汇编语言驱动程序模块“DRV_PortX_250MS.S51””。
/* ---------------------------------------------

金水32051编译器
A51编程 项目框架 驱动程序

单片机 AI8051U-8BIT
系统运行主频 33.1776MHz

作者: 杨为民 2026-05
QQ: 86547995

--------------------------------------------- */
NAME A51_Driver

// ---- A51 汇编语言只支持16位整数 ---------
#define Fosc_KHZ 33177

// ==== 头文件 ====================
#include "..\C351_XSFR_AI8051U_8BIT.INC"

// ---- 涉及宏,必须用宏定义 -------
Set_Times MACRO WH, WL, Times
MOV WH, #HIGH (Times)
MOV WL, #LOW (Times)
ENDM

// ==== 接口声明 ====================
PUBLIC PortX_Init_STC
PUBLIC DRV_Delay_250MS

// ==== 段声明 ====================
DRV_PortX_250MS_CODE SEGMENT CODE
RSEG DRV_PortX_250MS_CODE

// ==== 端口X 初始化函数 =============================
PortX_Init_STC:
CLR A
MOV P0M1, A ;设置为准双向口
MOV P0M0, A
MOV P0, # 0FFH;
MOV P1M1, A ;设置为准双向口
MOV P1M0, A
MOV P1, # 0FFH;
MOV P2M1, A ;设置为准双向口
MOV P2M0, A
MOV P2, # 0FFH;
MOV P3M1, A ;设置为准双向口
MOV P3M0, A
MOV P3, # 0FFH;
MOV P4M1, A ;设置为准双向口
MOV P4M0, A
MOV P4, # 0FFH;
MOV P5M1, A ;设置为准双向口
MOV P5M0, A
MOV P5, # 0FFH;
MOV P6M1, A ;设置为准双向口
MOV P6M0, A
MOV P6, # 0FFH;
MOV P7M1, A ;设置为准双向口
MOV P7M0, A
MOV P7, # 0FFH;
RET

// ==== 延时 250 毫秒 ====================
DRV_Delay_250MS:
PUSH 02H ;入栈R2
PUSH 03H ;入栈R3
PUSH 04H ;入栈R4

MOV R2, # 250 ; 250 MS

DRV_Delay_250MS_1:

#define MS_Times Fosc_KHZ / 10
// ----调用宏函数 --------------------
Set_Times R3, R4, MS_Times

DRV_Delay_250MS_2:
MOV A, R4 ; Total 10T/loop
DEC R4 ;
JNZ DRV_Delay_250MS_3 ;
DEC R3
DRV_Delay_250MS_3:
DEC A ;
ORL A, R3 ;
JNZ DRV_Delay_250MS_2 ;

DJNZ R2, DRV_Delay_250MS_1

POP 04H ;出栈R2
POP 03H ;出栈R3
POP 02H ;出栈R4
RET

END




回复

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2026-5-14 17:48 , Processed in 0.111698 second(s), 42 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表