教程 · 入门 · 阅读约 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 端口。没有操作系统,除了你的代码什么都不在运行。
有四个引脚根本不是 I/O,而且这四个引脚都接对了芯片才能跑起来:XTAL1 和 XTAL2 接晶振,RST 接复位电路,EA 选择内部还是外部程序存储器。EA 接错了,芯片会静默地什么都不做。
哈佛结构:代码和数据是两个世界
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 不是一块平坦的草稿纸,而是被分成能力各异的区域。知道谁是谁,就是存储器映射的大部分内容。
**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。
探索关联图谱
本教程所属的搭建
包含本教程的项目和学习路线。
延伸阅读