- AT89C2051 简介:为什么选择它?
- 开发环境搭建:你需要什么软件和硬件?
- C 语言核心要点:针对 8051 内核的特殊性。
- 一个完整的 LED 闪烁示例:从零开始点亮一个 LED。
- 常用外设驱动示例:如按键、定时器。
- 总结与进阶。
AT89C2051 简介
AT89C2051 是 Atmel(现已被 Microchip 收购)生产的一款 8 位单片机,基于经典的 Intel 8051 内核。

主要特点:
- 内核:标准的 8051 内核。
- Flash 存储器:2KB,可重复擦写 1000 次。
- RAM:128 字节。
- 工作频率:最高 24MHz。
- I/O 端口:15 个可编程 I/O 口(P1.0-P1.7, P3.0-P3.7, P3.6 作为外部中断 INT1,P3.7 作为 RXD)。注意:它没有 P0 口和 P2 口,这是它与标准 8052 的一个重要区别。
- 定时器/计数器:2 个 16 位定时器(Timer 0 和 Timer 1)。
- 中断系统:5 个中断源(2 个外部中断,2 个定时器中断,1 个串口中断)。
- 串行通信:1 个全双工 UART。
- 特殊功能:内置精密模拟比较器。
- 封装:20 脚 PDIP 或 SOIC,体积小巧。
为什么适合学习?
- 经典内核:学习 8051 是嵌入式开发的基石,理解了它,再学其他 MCU(如 STM32, PIC)会事半功倍。
- 资源简单:2KB Flash 和 128B RAM 的限制,迫使你写出精简高效的代码,有助于理解底层资源管理。
- 资料丰富:作为一款非常古老的芯片,网上有海量的教程、数据手册和应用笔记。
- 成本低廉:芯片本身和开发板都非常便宜。
开发环境搭建
要在 AT89C2051 上运行 C 语言,你需要一个 交叉编译器,因为你的 PC 是 x86 架构,而 AT89C2051 是 8051 架构,编译器需要生成能在 8051 上运行的机器码。
软件环境
-
C51 编译器:
(图片来源网络,侵删)- Keil C51 (µVision):这是最经典、最广泛使用的 8051 开发环境,它集成了编辑器、编译器、链接器和调试器,虽然有商业版,但有一个功能受限的评估版(代码大小限制在 2KB 以内),对于 AT89C2051 评估版已经完全够用!
- SDCC (Small Device C Compiler):一个开源的、跨平台的 C 编译器,支持 8051 及其他多种 8 位 MCU,完全免费,没有代码大小限制,是 Keil 的一个优秀替代品。
- 推荐:初学者强烈推荐 Keil C51,因为它非常成熟,教程和示例最多,熟悉后再可以尝试 SDCC。
-
烧录软件:
- 编译后生成的
.hex文件需要通过编程器(烧录器)写入到 AT89C2051 的 Flash 中。 - 常用的烧录软件和硬件有:
- ProGIS:配合并口或 USB 转并口的编程器。
- STC-ISP:虽然主要用于 STC 单片机,但很多兼容 8051 的编程器也支持其格式。
- USBasp:一个开源的 USB 编程器,配合
avrdude等软件可以烧录 8051。 - 最简单的方式:购买一块集成了 USB 转串口和烧录电路的 AT89C2051 开发板,通常会提供配套的烧录软件。
- 编译后生成的
硬件环境
- AT89C2051 芯片或开发板:推荐直接购买开发板,上面通常已经集成了晶振、复位电路、USB 转串口、LED 和按键,省去了焊接和搭建最小系统的麻烦。
- USB 数据线:用于供电和程序下载。
C 语言核心要点 (与标准 C 的区别)
在 8051 上写 C,需要了解其特殊的内存结构和寄存器访问方式,Keil C51 提供了扩展关键字来访问这些硬件资源。
A. 特殊功能寄存器 访问
8051 的 I/O 端口、定时器、中断等都是通过 SFR 来控制的,Keil C51 允许你直接用 C 语言读写它们。
-
sfr:定义一个 8 位的 SFR。sfr P1 = 0x90; // 定义 P1 端口地址为 0x90
-
sbit:定义一个 SFR 中的 1 位(如 I/O 口的某一位)。sbit LED = P1^0; // 定义 P1.0 引脚为 LED sbit KEY = P3^2; // 定义 P3.2 引脚为按键 (INT0)
B. 内存模型
8051 有 4 个主要的内存空间,Keil C51 使用关键字来指定变量存储的位置。
-
data:直接寻址区 (128字节,地址 0x00-0x7F),访问速度最快。unsigned char data i; // 变量 i 存在最快访问的 data 区
-
bdata:可位寻址区 (16字节,地址 0x20-0x2F),可以像 SFR 一样用sbit操作其中的每一位。unsigned char bdata flags; // 变量 flags 存在可位寻址区 sflag flag_busy = flags^0; // 定义 flag_busy 为 flags 的第 0 位
-
idata:间接寻址区 (256字节,地址 0x00-0xFF),速度比data慢。unsigned char idata j;
-
xdata:外部数据区 (最多 64KB),用于访问外部 RAM。unsigned int xdata sensor_value;
-
code:代码区 (最多 64KB),用于存储常量、查找表等。unsigned char code seg_table[] = {0x3F, 0x06, 0x5B, ...}; // 数码管段码表
现代 Keil 默认会自动优化变量位置,通常你不需要显式指定,但在对性能要求极高的地方,手动指定 data 仍然有用。
C. 中断处理函数
使用 interrupt 关键字来定义中断服务函数。
interrupt 0-> 外部中断 0 (INT0)interrupt 1-> 定时器 0 溢出中断interrupt 2-> 外部中断 1 (INT1)interrupt 3-> 定时器 1 溢出中断interrupt 4-> 串口中断
void Timer0_ISR() interrupt 1 // 定时器0中断服务函数
{
// 中断处理代码
// ... ...
TF0 = 0; // 手动清除定时器0溢出标志位 (某些模式下自动清除)
}
中断函数中要注意:
- 尽量保持简短。
- 如果需要修改共享变量,要关闭中断或使用
volatile关键字。 - 如果使用寄存器组切换(
using n),要小心。
完整示例:LED 闪烁
这是嵌入式开发的 "Hello, World!",假设我们将 LED 连接到 P1.0 引脚。
步骤 1:硬件连接
- AT89C2051 的 P1.0 引脚连接一个 LED 的正极。
- LED 的负极通过一个限流电阻(如 330Ω)连接到 GND。
步骤 2:Keil C51 代码
#include <reg2051.h> // 包含 AT89C2051 的特殊功能寄存器定义头文件
// sbit 定义
sbit LED = P1^0; // 将 P1.0 引脚命名为 LED
void main(void)
{
// 1. 初始化
// P1 口作为准双向口,默认为输入模式,输出前最好先写 1
P1 = 0xFF; // 将 P1 的所有引脚设置为高电平
// 2. 主循环
while(1) // 无限循环
{
LED = 0; // P1.0 输出低电平,LED 点亮 (共阴极接法)
// 软件延时
unsigned int i, j;
for(i=0; i<20000; i++)
{
for(j=0; j<100; j++);
}
LED = 1; // P1.0 输出高电平,LED 熄灭
// 软件延时
for(i=0; i<20000; i++)
{
for(j=0; j<100; j++);
}
}
}
步骤 3:编译与烧录
- 在 Keil µVision 中新建一个 "C51 Project"。
- 将上面的代码保存为
main.c并添加到项目中。 - 设置目标选项:
- Target -> Device:选择
Atmel -> AT89C2051。 - Output -> Create HEX File:勾选,以便生成烧录文件。
- Target -> Device:选择
- 编译项目,会生成
main.hex文件。 - 使用烧录软件(如 ProGIS 或开发板自带软件)将
main.hex文件下载到 AT89C2051 芯片中。 - 给芯片上电,你就能看到 LED 开始闪烁了。
常用外设驱动示例
A. 按键检测
假设按键连接在 P3.2 (INT1),按键另一端接地,采用 查询方式。
#include <reg2051.h>
sbit KEY = P3^2;
void main(void)
{
P3 = 0xFF; // 初始化 P3 口
while(1)
{
if(KEY == 0) // 检测按键是否被按下 (低电平有效)
{
// 按键消抖
unsigned int i;
for(i=0; i<20000; i++);
if(KEY == 0) // 再次确认按键是否仍然按下
{
// 执行按键按下后的操作,例如点亮 LED
P1^0 = 0; // LED 亮
// 等待按键释放
while(KEY == 0);
// 再次消抖
for(i=0; i<20000; i++);
}
}
}
}
B. 定时器应用 (精确延时)
软件延时不够精确,使用定时器可以实现更精确的延时。
目标:使用定时器 0,实现一个 50ms 的定时,并每 20 次定时(即 1 秒)翻转一次 LED。
#include <reg2051.h>
sbit LED = P1^0;
unsigned int time_count = 0; // 用于记录定时次数
void Timer0_Init(void)
{
// 1. 设置定时器模式
TMOD &= 0xF0; // 清空 T0 的设置位
TMOD |= 0x01; // 设置 T0 为 16 位定时器模式
// 2. 计算初值 (假设晶振为 12MHz)
// 12MHz -> 1us/机器周期
// 50ms = 50000us
// 定时器初值 = 65536 - 50000 = 15536
// 15536 的十六进制是 0x3CB8
TH0 = 0x3C; // 高位
TL0 = 0xB8; // 低位
// 3. 开启定时器
TR0 = 1; // 启动 Timer0
}
void main(void)
{
P1 = 0xFF;
LED = 1;
Timer0_Init();
// 4. 开启总中断和定时器0中断
EA = 1; // 全局中断允许
ET0 = 1; // 允许 Timer0 中断
while(1); // 主循环等待中断
}
// Timer0 中断服务函数
void Timer0_ISR() interrupt 1
{
// 1. 重新装载初值
TH0 = 0x3C;
TL0 = 0xB8;
// 2. 执行定时任务
time_count++;
if(time_count >= 20)
{
time_count = 0;
LED = ~LED; // 翻转 LED 状态
}
}
总结与进阶
AT89C2051 虽然古老,但它是一个完美的嵌入式系统入门平台,通过它,你可以学到:
- 单片机最小系统:电源、晶振、复位电路。
- GPIO 操作:输入、输出、上拉电阻的概念。
- 中断系统:理解中断的机制和重要性。
- 定时器/计数器:实现精确定时和事件计数。
- UART 通信:实现单片机与 PC 之间的串口通信。
- 底层思维:理解硬件资源(RAM, Flash, I/O)的有限性,学会编写高效代码。
进阶方向:
- 掌握 Keil 调试器:使用 Keil 自带的仿真器进行在线调试,设置断点、观察变量,极大提高开发效率。
- 学习更复杂的外设:如利用内部模拟比较器、驱动数码管、使用 74HC595 扩展 I/O 等。
- 尝试 SDCC:体验开源工具链,了解不同编译器的差异。
- 转向现代 MCU:在掌握了 8051 的基础后,可以轻松过渡到功能更强大的 ARM Cortex-M 系列(如 STM32),你会发现很多概念是相通的。
希望这份指南能帮助你顺利开启 AT89C2051 的 C 语言开发之旅!
