第一部分:51单片机C语言开发基础
什么是51单片机?
51单片机是基于Intel 8051架构的一种经典8位微控制器,虽然历史悠久,但其结构简单、成本低廉、学习资料丰富,至今仍是电子入门和工业控制领域的常青树。

- 核心特点:
- 8位CPU:一次能处理8位数据。
- 哈佛结构:程序存储器和数据存储器物理分开,访问效率高。
- 特殊功能寄存器:用于控制单片机所有的外设(如I/O、定时器、串口等)。
- 经典的引脚:40个引脚,包括4个8位I/O口(P0, P1, P2, P3)、电源、晶振等。
开发环境搭建
要进行51单片机开发,你需要两个核心软件:
-
集成开发环境:
- Keil C51 (μVision):工业标准,功能强大,调试方便,支持C语言和汇编,有免费版(代码量限制)和商业版。强烈推荐初学者使用。
- SDCC:开源的C51编译器,跨平台,适合喜欢开源工具的开发者。
- IAR Embedded Workbench:另一款强大的商业IDE,性能优异,但价格昂贵。
-
编程/下载工具:
- STC-ISP:用于将编译好的程序代码下载到STC品牌的51单片机中,STC是目前市面上最流行的51内核单片机厂商,其官方下载软件功能齐全,集成了烧录、串口助手等功能。
- 其他:如USB-TTL转换模块、编程器等。
基本流程:

- 在Keil中创建新工程,选择对应的单片机型号。
- 编写C语言代码。
- 编译代码,生成
.hex文件。 - 使用STC-ISP软件打开
.hex文件,连接硬件,将程序下载到单片机中。
51单片机C语言核心要点
标准的C语言在51单片机上运行,需要特别注意以下几点,这也是与PC端C语言最大的区别。
(1) sfr 和 sbit - 访问特殊功能寄存器
51单片机的I/O口、定时器、串口等外设都通过特殊的寄存器来控制,51 C语言扩展了两个关键字来访问它们。
-
sfr(Special Function Register):用于定义一个8位的SFR。- 语法:
sfr SFR名称 = 地址; - 定义P1口的寄存器(地址为0x90)。
sfr P1 = 0x90;
- 语法:
-
sbit(Special Bit):用于定义一个SFR中的某一位。
(图片来源网络,侵删)- 语法:
sbit 位名称 = SFR名称 ^ 位序号;或sbit 位名称 = 地址; - 定义P1口的第0位(P1.0)。
sbit P1_0 = P1 ^ 0; // 方法一 // sbit P1_0 = 0x90; // 方法二,直接使用P1的地址
- 语法:
现代替代方案:
许多51单片机厂商(如STC)提供了自己的头文件(如 STC89C52RC.H),你只需要包含这个头文件,就可以直接使用像 P0, P1, P2, P3, TMOD, TCON 等预定义好的名称,无需手动定义 sfr 和 sbit。
(2) absacc.h - 绝对地址访问
这个头文件提供了一些宏,用于直接访问内存的绝对地址,相当于汇编中的 MOV 指令。
#include <absacc.h>#define PA XBYTE[0x1000]// 定义一个指向外部数据存储器0x1000地址的指针
(3) interrupt 关键字 - 中断服务函数
51单片机有多个中断源(如外部中断0、定时器0中断、串口中断等),C语言使用 interrupt 关键字来声明一个中断服务函数。
- 语法:
void 函数名() interrupt 中断编号 using 寄存器组 - 中断编号:
- 0: 外部中断0
- 1: 定时器0中断
- 2: 外部中断1
- 3: 定时器1中断
- 4: 串口中断
using:指定中断服务函数使用哪个寄存器组(0-3),可以节省现场保护和恢复的时间,提高效率。- 注意:
- 中断服务函数应尽量简短,耗时操作放在主循环中。
- 函数没有返回值。
- 函数不能传递参数。
第二部分:51单片机核心外设C编程实例
实例1:GPIO控制 - LED闪烁
这是最经典的“Hello, World!”程序。
硬件:一个LED灯,一端接单片机P1.0引脚,另一端通过一个限流电阻(如220Ω-1kΩ)接地。
代码:
#include <reg52.h> // 包含STC89C52的头文件,已定义好P1等
// sbit P1_0 = P1 ^ 0; // 如果头文件里没有,可以自己定义,但通常不需要
void main() {
while (1) { // 无限循环
P1_0 = 0; // P1.0输出低电平,LED点亮 (LED是低电平点亮)
Delay(100); // 调用延时函数
P1_0 = 1; // P1.0输出高电平,LED熄灭
Delay(100);
}
}
// 简单的延时函数 (不精确,仅作演示)
void Delay(unsigned int t) {
unsigned int i, j;
for (i = 0; i < t; i++)
for (j = 0; j < 120; j++);
}
实例2:定时器 - 精确延时与PWM
硬件:同上。
目标:使用定时器T0,实现一个1秒的精确闪烁。
代码:
#include <reg52.h>
sbit LED = P1^0;
// 定义定时器0的初值,用于1ms定时
#define TIMER0_RELOAD (65536 - 921) // 12MHz晶振,1ms定时
unsigned int timer0_count = 0;
// 定时器0中断服务函数
void Timer0_ISR() interrupt 1 {
// 重新装载初值
TH0 = TIMER0_RELOAD >> 8;
TL0 = TIMER0_RELOAD & 0xFF;
timer0_count++;
if (timer0_count >= 1000) { // 1000 * 1ms = 1000ms = 1s
timer0_count = 0;
LED = ~LED; // LED状态翻转
}
}
void main() {
// 1. 设置定时器0为16位定时模式
TMOD &= 0xF0; // 清空T0的设置位
TMOD |= 0x01; // 设置T0为模式1 (16位定时器)
// 2. 装载初值
TH0 = TIMER0_RELOAD >> 8;
TL0 = TIMER0_RELOAD & 0xFF;
// 3. 开启定时器0中断和总中断
ET0 = 1; // 允许T0中断
EA = 1; // 开启总中断
// 4. 启动定时器T0
TR0 = 1;
while (1) {
// 主循环可以执行其他任务,或者什么都不做
// LED的状态由中断服务函数控制
}
}
实例3:串口通信 - PC与单片机互发数据
硬件:USB-TTL模块,单片机P3.0(RXD) -> TTL模块的TXD;单片机P3.1(TXD) -> TTL模块的RXD。
目标:单片机每隔1秒向PC发送 "Hello, World!",并能接收PC发来的数据并回传。
代码:
#include <reg52.h>
#define uchar unsigned char
#define uint unsigned int
// 串口初始化函数,设置波特率
void UART_Init() {
// 设置定时器1为模式2 (8位自动重装) 作为波特率发生器
TMOD &= 0x0F; // 清空T1的设置位
TMOD |= 0x20; // 设置T1为模式2
// 设置波特率9600 (12MHz晶振)
TH1 = 0xFD; // 波特率重装值
TL1 = 0xFD;
// 设置串口模式1 (8位UART), 允许接收
SCON = 0x50;
// 启动定时器1
TR1 = 1;
}
// 串口发送一个字节
void UART_SendByte(uchar dat) {
SBUF = dat; // 将数据写入发送缓冲区
while (!TI); // 等待发送完成 (TI由硬件置1)
TI = 0; // 软件清零TI标志
}
// 串口发送一个字符串
void UART_SendString(uchar *str) {
while (*str != '\0') {
UART_SendByte(*str++);
}
}
// 串口中断服务函数
void UART_ISR() interrupt 4 {
if (RI) { // 接收中断标志
RI = 0; // 软件清零RI标志
// 接收到的数据在SBUF中,可以在这里处理
UART_SendByte(SBUF); // 将收到的数据回传给PC
}
}
void main() {
UART_Init(); // 初始化串口
while (1) {
UART_SendString("Hello, World!\r\n");
Delay(1000); // 延时1秒
}
}
// 延时函数 (同实例1)
void Delay(uint t) {
uint i, j;
for (i = 0; i < t; i++)
for (j = 0; j < 120; j++);
}
第三部分:综合项目实例
项目:智能温控风扇
功能描述:
- 使用DS18B20数字温度传感器实时检测环境温度。
- 当温度低于26°C时,风扇(用直流电机模拟)停止。
- 当温度在26°C ~ 30°C之间时,风扇低速运转。
- 当温度高于30°C时,风扇高速运转。
- 通过串口将当前温度和风扇状态打印到PC上。
硬件组件:
- 51单片机 (如STC89C52)
- DS18B20温度传感器
- 直流电机 + L298N电机驱动模块
- USB-TTL模块
- 按键、LED、电阻、杜邦线等
软件设计思路:
- 模块化编程:将不同功能写成独立的函数,如
DS18B20_ReadTemp(),Motor_Control(speed),UART_Init()等。 - 主循环:
main()函数中,不断循环执行:读取温度 -> 根据温度设置电机速度 -> 通过串口发送信息。 - 状态显示:可以用LED指示风扇档位(0档:灭,1档:慢闪,2档:快闪)。
核心代码片段:
// ... (包含头文件和宏定义)
// 温度值和风扇状态
float temperature = 0.0;
uchar fan_state = 0; // 0: off, 1: low, 2: high
// 主函数
void main() {
UART_Init(); // 初始化串口
// ... 其他初始化, 如电机引脚
while (1) {
temperature = DS18B20_ReadTemp(); // 读取温度
// 根据温度控制风扇
if (temperature < 26.0) {
fan_state = 0;
Motor_Control(0);
} else if (temperature >= 26.0 && temperature <= 30.0) {
fan_state = 1;
Motor_Control(1); // 低速
} else {
fan_state = 2;
Motor_Control(2); // 高速
}
// 通过串口发送信息
UART_SendString("Temp: ");
UART_SendInt((int)temperature); // 需要自己实现发送整数的函数
UART_SendString(" C, Fan State: ");
UART_SendByte(fan_state + '0');
UART_SendString("\r\n");
Delay(500); // 每500ms更新一次
}
}
// DS18B20读取温度函数 (伪代码,需根据具体时序实现)
float DS18B20_ReadTemp() {
// ... DS18B20的初始化、跳过ROM、启动温度转换、等待转换完成...
// ... 然后读取暂存器中的温度数据...
// ... 将原始数据转换为实际的温度值
return temp_value;
}
// 电机控制函数
void Motor_Control(uchar speed) {
switch(speed) {
case 0: // 停止
// 设置电机驱动引脚为低电平
break;
case 1: // 低速
// 设置PWM占空比或电压
break;
case 2: // 高速
// 设置PWM占空比或电压
break;
}
}
第四部分:学习资源与建议
-
书籍:
- 《单片机C语言程序设计实训100例》 - 经典实例书籍,覆盖面广。
- 《手把手教你学51单片C语言》 - 入门友好,讲解细致。
- 《C51单片机应用与C语言程序设计》 - 理论与实例结合。
-
在线资源:
- B站/YouTube:搜索“51单片机 C语言”,有大量视频教程,从点亮LED到复杂项目,非常直观。
- 博客/论坛:CSDN、博客园、电子发烧友等平台有海量的技术文章和项目分享。
- 开源项目:在GitHub上搜索 "51 C project",可以找到很多优秀的开源代码供学习参考。
-
学习建议:
- 动手实践:单片机是“玩”出来的,不要只看书,一定要亲手焊接电路、编写代码、调试。
- 由简到难:从LED、按键开始,逐步掌握定时器、串口、ADC、I2C/SPI等。
- 善用工具:学会使用万用表、示波器(如果条件允许)可以帮助你更好地理解硬件信号和调试问题。
- 模块化思维:将复杂功能分解成小模块,逐个击破,最后再组合起来。
希望这份详细的指南能帮助你顺利开启51单片机的C语言开发之旅!
