教程 · 入门 · 阅读约 30 分钟

8051 架构:引脚、存储器映射与寄存器

8051 内部到底有什么:一个哈佛结构内核、四个脾性各异的端口、256 字节 RAM,以及一个可以用来精确计时的机器周期。

引言

大多数微控制器课程都从 8051 讲起,大多数学生初见它时都是一串要背的事实:四个端口、128 字节 RAM、两个定时器、六个中断源。靠死记硬背很快就会忘。如果把它理解为 1980 年在现实约束下做出的一组刻意权衡——当时 RAM 很贵,一颗芯片必须装进 40 个引脚——它几乎解释了今天小型微控制器的工作原理。

这是一次架构巡览:封装里有什么、存储器如何划分,以及为什么它的时序计算格外简单。后续两篇分别讲驱动引脚和用定时器产生 PWM。

“8051”是一个系列,不是一款芯片

Intel 在 1980 年推出 MCS-51,之后广泛授权该内核,所以今天没人会买 Intel 原产的 8051。你买到的是兼容型号——绝大多数是 Atmel(现 Microchip)的 AT89S52,它用可通过四个引脚反复烧写的闪存,取代了原版的紫外线擦除 ROM。

值得了解的差异:

8051(原版) AT89S52(你手上的)
程序存储器 4 KB ROM 8 KB 闪存
内部 RAM 128 字节 256 字节
定时器 2 个 3 个
中断源 5 个 6 个
烧写方式 出厂掩膜 ROM 在系统烧写,走 SPI 引脚

其余部分——指令集、端口、寄存器布局、时序——完全相同。为 8051 写的课程资料可以直接用在你桌上的芯片上。

封装里有什么

一个 40 脚 DIP 里装着一台完整的计算机:8 位 CPU、程序闪存、数据 RAM、三个定时器/计数器、一个全双工串口、一个中断控制器和四个 8 位 I/O 端口。没有操作系统,除了你的代码什么都不在运行。

40 脚 DIP 封装的 8051 引脚图,垂直绘制,顶部为 1 脚缺口。左侧为 1 至 20 脚:端口 1 引脚 P1.0 至 P1.7 及其 T2、T2EX、MOSI、MISO 和 SCK 复用功能,接着是 RST,然后是端口 3 引脚 P3.0 至 P3.7,分别标注 RXD、TXD、INT0、INT1、T0、T1、WR 和 RD,再往下是 XTAL2、XTAL1 和 GND。右侧为 40 至 21 脚:VCC,端口 0 引脚 P0.0 至 P0.7,同时复用为 AD0 至 AD7,然后是 EA、ALE 和 PSEN,再往下是端口 2 引脚 P2.7 至 P2.0,同时复用为地址线 A15 至 A8。颜色区分 I/O 端口、端口 3 复用功能、时钟与控制引脚以及电源。
40 个引脚中有 32 个是 I/O。端口 0 的编号与其他端口相反,端口 3 的复用标注正是串口、外部中断和定时器输入所在的位置。 下载 SVG

有四个引脚根本不是 I/O,而且这四个引脚都接对了芯片才能跑起来:XTAL1 和 XTAL2 接晶振,RST 接复位电路,EA 选择内部还是外部程序存储器。EA 接错了,芯片会静默地什么都不做。

8051 运行所需的最小电路。一个 AT89S52 芯片框标出六个引脚:40 脚 VCC 接 5 V 电源轨,20 脚 GND 接地;31 脚 EA 用红色线接 5 V 电源轨,标注 EA 必须拉高,否则芯片什么都不跑;18 脚 XTAL2 和 19 脚 XTAL1 跨接一颗 11.0592 MHz 晶振,晶振每个引脚各经一颗 33 皮法电容接地;9 脚 RST 经一颗 10 微法电容向上接 5 V 电源轨,再经一颗 10 千欧电阻向下接地,标注复位为高电平有效。
四个连接加一把分立元件。注意复位网络:电容接到电源而不是接地,因为 RST 是高电平有效。 下载 SVG

哈佛结构:代码和数据是两个世界

8051 把程序存储器和数据存储器放在相互独立的地址空间。地址 0x0030 对取指令和对数据访问意味着完全不同的东西——它们是不同的存储器,用不同的指令访问。

这就是为什么 8051 的 C 编译器逼你思考其他平台藏起来的存储类别。一个从不修改的字符串字面量属于 code 存储器,那里有几 KB 的余量。不小心把它放进 data,它会吃掉你本该留给其他一切的 256 字节:

// In code (flash) — costs nothing from your 256 bytes of RAM.
const char code banner[] = "Line follower v1";

// In RAM — 17 of your 256 bytes, gone, for a string that never changes.
const char banner_bad[] = "Line follower v1";

在 Arduino 上,这种区分就是 PROGMEM 存在的意义。在 8051 上它不是优化,而是程序装得下与装不下的区别。

内部 RAM 映射

256 字节内部 RAM 不是一块平坦的草稿纸,而是被分成能力各异的区域。知道谁是谁,就是存储器映射的大部分内容。

8051 内部数据存储器映射,从底部地址 00h 到顶部 FFh 堆叠绘制。自下而上:00h 至 07h、08h 至 0Fh、10h 至 17h 和 18h 至 1Fh 的四个 8 字节寄存器组;20h 至 2Fh 的 16 字节可位寻址区,可容纳 128 个独立寻址的位;30h 至 7Fh 的 80 字节通用 RAM 与堆栈;80h 至 FFh 的一块区域,同时容纳经直接寻址访问的特殊功能寄存器和只能经间接寻址访问的高 128 字节 RAM。一个箭头标注堆栈指针复位为 07h,因此堆栈从寄存器组 1 之上的 08h 开始,另一个箭头标注 80h 至 FFh 区域是共用一段地址范围的两块存储器。
四个区域,四种不同分工。两个标注的事实最容易踩坑:堆栈落在寄存器组 1 上,地址范围的上半部分是两块独立的存储器。 下载 SVG

**00h–1Fh——四个寄存器组。**R0 到 R7 不是固定位置,而是程序状态字中两位(RS0 和 RS1)选出的四个 8 字节寄存器组之一的窗口。切换寄存器组只是一次两位写操作,因此这是给中断一套专属寄存器、省掉八次压栈出栈的廉价办法。

**20h–2Fh——可位寻址。**这 16 个字节还可以作为 128 个独立的位来寻址,即 00h 到 7Fh。8051 有真正的单位操作指令——SETB、CLR、JB、JNB,所以这里的一个标志只花一位和一条指令,而不是一个字节加一个掩码。这就是 8051 C 编译器提供别处没有的 bit 类型的原因。

**30h–7Fh——通用与堆栈。**80 个字节留给你的变量和调用堆栈,这也是你真正会用完的资源。

80h–FFh——两块存储器,一段地址范围。直接寻址到这里访问的是特殊功能寄存器;间接寻址(经 R0 或 R1)访问的是高 128 字节 RAM。数字相同,存储器不同,用哪种指令形式来区分。在原版 128 字节 8051 上,高端 RAM 根本不存在。

特殊功能寄存器就是存储器

8051 的每个外设都是靠向 80h–FFh 直接寻址范围的地址写字节来控制的。这里没有 API——只有一个寄存器,置起其中一位,硅片的行为就变了。

寄存器 地址 控制对象
P0 P1 P2 P3 80h、90h、A0h、B0h 四个 I/O 端口
TMOD 89h 定时器 0 和 1 的模式选择
TCON 88h 定时器运行位和溢出标志
TH0 TL0 TH1 TL1 8Ch、8Ah、8Dh、8Bh 定时器计数值寄存器
SCON SBUF 98h、99h 串口控制和数据
IE IP A8h、B8h 中断使能和优先级
PSW D0h 标志位和寄存器组选择位
SP 81h 堆栈指针

在 C 里写 P1 = 0x0F; 就是向地址 90h 存一个数,八个引脚随之改变状态。这种直接就是这颗芯片作为教学工具的全部魅力。

可以数出来的时序

经典 8051 内核的一个机器周期等于十二个振荡周期,大多数指令占一到两个机器周期。这个固定比例意味着执行时间可以精确算出来,而不是靠测量:

machine cycle = 12 / f_osc

at 11.0592 MHz : 12 / 11 059 200 = 1.085 µs
at 12 MHz      : 12 / 12 000 000 = 1.000 µs

在 11.0592 MHz 下,三条单周期指令的循环每次耗时 3.26 µs,没有缓存、没有流水线,除了你自己使能的中断之外没有任何抖动。很少有现代处理器还能这样推理,这也是 8051 在时序关键的外设里活到今天的原因。

为什么晶振是 11.0592 MHz

这个数字看起来随意,其实不然。串口波特率是对机器周期时钟分频得到的,而 11.0592 MHz 可以精确分频出标准波特率:

baud = f_osc / (12 × 32 × (256 − TH1))

11.0592 MHz, TH1 = 253 :  11 059 200 / (12 × 32 × 3) = 9600.0   ✓ exact
12 MHz,      TH1 = 253 :  12 000 000 / (12 × 32 × 3) = 10416.7  ✗ 8.5% off

UART 能容忍几个百分点的误差;8.5% 会让每一帧都乱码。如果你的 8051 板子串口打印乱码,先看晶振再看代码。

8051 没有的东西

有两个缺席塑造了每一个 8051 项目,而且从功能列表上看不出来:

  • **没有模数转换器。**读电位器、热敏电阻或模拟巡线传感器,需要 ADC0804 这样的外部 ADC 芯片。这里没有 analogRead() 可用。
  • **没有 PWM 硬件。**芯片上没有任何东西能产生占空比可变的波形。调 LED 亮度或设定电机转速,意味着自己用定时器中断搭 PWM,并用 CPU 时间来换。

Arduino Uno 把这两样都内置了。这才是诚实的对比:8051 不是更差的 Arduino,它是 Arduino 下面那一层,把方便都拿掉,让你看清那些方便到底做了什么。

出问题时

  • **板子死了,哪里都不发热。**检查 EA(31 脚)是否在 +5 V。悬空或接地时,芯片会从根本不存在的外部存储器取代码,然后什么都不执行,没有任何症状。
  • 不断复位或根本起不来。8051 的复位是高电平有效。上电电路是 10 µF 电容从 +5 V 接到 RST,10 kΩ 电阻从 RST 接到地。如果按低电平有效复位那样接,芯片会被按在永久复位里。
  • **串口输出乱码。**几乎总是晶振的问题。12 MHz 的片子产生不了标准波特率。
  • **变量莫名其妙互相破坏。**多半是 30h–7Fh 的 80 字节用完了,或者堆栈长进了变量里。看编译器输出的存储器映射。
  • **中断里寄存器莫名其妙变了。**堆栈指针复位为 07h,所以堆栈从 08h 开始——而那正是寄存器组 1。要么在启动代码里挪走 SP,要么别碰寄存器组 1。

探索关联图谱

本教程所属的搭建

包含本教程的项目和学习路线。

延伸阅读

参考资料