在EdSim51模拟器的左边我们可以看到LED的对应端口为P1,8个LED分别对应P1.0-P1.7。

这8个LED位于下面外设区以下位置:

在这篇文章中我们来看看如何点亮和闪烁这些LED。
下面是模拟器的电路图:

放大与LED相关的电路,可以看到LED的左边接的是高电平,所以如果要点亮LED,右边的端口必须设为电平(0)。

让我们进一步简化为下面的布局:

每个LED对应P1中的一位,假设我们要点亮最右边的LED,只需要将P1的值设为1111 1110即可,对应的十六位进制数为FE。

让我们写个简单的程序点亮最右边的LED:
MOV P1,#0FEH
可以看到最右边的LED被成功点亮:

然后从0我们再切换回1,这将关闭LED。如果我们不断重复这个过程,LED将会不断地开关,并且会闪烁。意味着我们只需要将P1的值在FE和FF之间来回切换即可。

编写以下程序:
ORG 00H
BACK: MOV A,#0FEH
MOV P1,A
MOV A,#0FFH
MOV P1,A
SJMP BACK
END
现在让我们看看编码,这相当简单,所以我们从0H开始,标签是BACK,稍后我会解释为什么我使用了这个标签。FE表示打开最低有效位的LED。我们把FE值记住,任何以字母开头的值都需要在前面加上一个前导零,所以这是0FE十六进制,将其移动到A寄存器,然后从A寄存器移动到P1端口,这将关闭LED。然后用SJMP,代表短跳转,跳回BACK,程序进入一个循环。
运行模拟器,期望中的闪烁并没有发生。
原因是微控制器以12兆赫兹运行,大约是每秒1200万次循环。现在LED实际上正在闪烁,但它以每秒1200万次的速度闪烁,这太快了,人眼无法检测到这种速度的变化。那么我们如何解决这个问题呢?
通常,我们可以在这里插入一个延迟,这里的延迟被称为子程序,这个子程序的功能是将LED保持在其状态一段时间,然后当它完成后,它将关闭LED。为了保持LED关闭状态,你插入另一个延迟函数或子程序,保持它关闭一段时间,然后它将跳转回并重复整个过程。
ORG 00H
BACK: MOV A,#0FEH
MOV P1,A
ACall Delay
MOV A,#0FFH
MOV P1,A
ACall Delay
SJMP BACK
Delay:Mov R0, #0FFH
Again:Mov R1,#0FFH
Here: Djnz R1, Here
Djnz R0,Again
Ret
END
延迟函数的工作原理:
外循环 (R0):
MOV R0,#0FFH 将 R0 加载 255
这是外循环计数器。
内循环 (R1):
MOV R1,#0FFH 将 R1 加载 255
这是内循环计数器。
内循环操作:
DJNZ R1,Here
表示:将 R1 减 1,如果 R1 ≠ 0,则跳转回“Here”,此循环运行 255 次
外循环操作:
DJNZ R0,Again
表示:R1 达到 0 后,将 R0 减 1,如果 R0 ≠ 0,则跳转回“Again”并重新加载 R1,此过程重复 255 次。
总延迟计算:
内循环 (R1) 运行 255 次,外循环 (R0) 运行255 次,每次内循环迭代需要 2 个机器周期,总周期 ≈ 255 × 255 × 2 = 130,050 个周期。
这会产生延迟,因为每条 DJNZ 指令需要 2 个机器周期,每条 MOV 指令需要 1 个机器周期。使用典型的 12MHz 晶振:
1 个机器周期 = 1μs
总延迟 ≈ 130ms
运行程序后LED灯可以正常闪烁。

另一个控制LED的方法是单独设置LED灯的值,使用CLR和SETB命令,CLR将值清为0,SETB将值设为1。
ORG 00H
BACK: CLR P1.0
ACall Delay
SETB P1.0
ACall Delay
SJMP BACK
Delay:Mov R0, #0FFH
Again:Mov R1,#0FFH
Here: Djnz R1, Here
Djnz R0,Again
Ret
END
以上代码可以同样实现LED闪烁。