C51 语言参考手册
适用单片机:STC15W4K32S4 及标准 8051/8052 系列
开发环境:Keil C51 (μVision)、SDCC、IAR Embedded Workbench
Proteus 仿真常用元器件:
RES(电阻)、CAP(电容)、7SEG-MPX4-CC(四位七段数码管)、BUTTON(按键)、LED(LED灯)、SW-SPST(单刀单掷开关)、TRAFFIC LIGHTS(红绿灯)、POT-HG(电位器)、COMPIM(虚拟串口)
### 文章目录
- [C51 语言参考手册](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [@toc](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [基础知识](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [计算机数制](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [理解补码的独特之处](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [如何求一个负数的补码](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [C 语言相关的基础知识](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [C 语言之数据类型](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [一、C51 基本语法与扩展](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [1. 头文件包含](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [2. 特殊功能寄存器(SFR)访问](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [3. 存储器类型关键字](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [4. 中断系统](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [5. 位操作](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [6. 绝对地址定位](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [二、与标准 C 的主要区别](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [1. 内存结构](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [2. 扩展关键字](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [3. 指针类型](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [4. 函数修饰符](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [5. 标准库差异](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [6. 编译与运行限制](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [三、编程注意事项](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [1. 数据类型与内存优化](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [2. 端口(I/O)操作](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [3. 定时器/计数器编程](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [4. 串口通信编程](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [5. 中断编程最佳实践](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [四、示例代码](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [1. 延时函数](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [2. 数码管驱动(4 位共阴动态扫描)](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [3. 综合示例:智能 LED 控制系统](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [五、开发工具与调试技巧](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [1. 常用开发工具](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [2. 调试技巧](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [3. 常见问题与解决方案](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [六、高级主题与扩展](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [1. 内存分页(Bank Switching,ROM > 64KB)](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [2. 简单协作式任务调度器](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [3. C 与汇编混合编程](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [4. I²C 软件模拟(STC15 无硬件 I²C 时)](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [总结](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
- [关键编程原则](#文章目录 C51 语言参考手册 @[toc] 基础知识 计算机数制 理解补码的独特之处 如何求一个负数的补码 C 语言相关的基础知识 C 语言之数据类型 一、C51 基本语法与扩展 1. 头文件包含 2. 特殊功能寄存器(SFR)访问 3. 存储器类型关键字 4. 中断系统 5. 位操作 6. 绝对地址定位 二、与标准 C 的主要区别 1. 内存结构 2. 扩展关键字 3. 指针类型 4. 函数修饰符 5. 标准库差异 6. 编译与运行限制 三、编程注意事项 1. 数据类型与内存优化 2. 端口(I/O)操作 3. 定时器/计数器编程 4. 串口通信编程 5. 中断编程最佳实践 四、示例代码 1. 延时函数 2. 数码管驱动(4 位共阴动态扫描) 3. 综合示例:智能 LED 控制系统 五、开发工具与调试技巧 1. 常用开发工具 2. 调试技巧 3. 常见问题与解决方案 六、高级主题与扩展 1. 内存分页(Bank Switching,ROM > 64KB) 2. 简单协作式任务调度器 3. C 与汇编混合编程 4. I²C 软件模拟(STC15 无硬件 I²C 时) 总结 关键编程原则)
基础知识
计算机数制
为了让你快速掌握核心区别,我先用一个表格总结原码、反码和补码的关键特征,然后再详细解释补码的独特之处。
| 表示法 | 符号位规则 (最高位) | 数值位规则 (正数/负数) | 数值 0 的表示 | 8位二进制整数范围 |
|---|---|---|---|---|
| 原码 | 0 正 1 负 | 正数 :二进制绝对值 负数:二进制绝对值 | 不唯一 [+0]原 = 00000000 [-0]原 = 10000000 |
-127 到 +127 |
| 反码 | 0 正 1 负 | 正数 :二进制绝对值 负数 :原码数值位取反 | 不唯一 [+0]反 = 00000000 [-0]反 = 11111111 |
-127 到 +127 |
| 补码 | 0 正 1 负 | 正数 :二进制绝对值 负数 :反码末位加1 | 唯一 [0]补 = 00000000 |
-128 到 +127 |
有一个关键细节需要明确:在求负数的补码时,符号位是保持不变的。
下面的表格能帮助您快速把握核心转换规则:
| 数值类型 | 原码 → 反码 | 反码 → 补码 | 核心规则 |
|---|---|---|---|
| 正数 | 完全相同(符号位为0,数值位不变) | 完全相同(符号位为0,数值位不变) | 正数的原码、反码、补码三码合一 |
| 负数 | 符号位不变(仍为1),数值位按位取反 | 符号位不变(仍为1),末位加1 | 符号位始终保持不变 |
理解补码的独特之处
补码之所以成为现代计算机数值表示的标准,关键在于它巧妙地解决了原码和反码的固有问题。
- 解决"零"的歧义 如表格所示,原码和反码中的"零"都有两种表示方法(
+0和-0)。这在进行比较判断时会带来麻烦。而补码中的零有唯一的编码(00000000),这简化了电路设计,避免了二义性。 - 实现加减法统一 这是补码最重要的优势。在补码表示下,减法运算可以转换为加法运算 。计算机的CPU内部无需设计复杂的减法器,只需一个加法器,就可以同时处理加法和减法。 例如计算 1 - 1 : 这可以转化为 1 + (-1)。
[1]补 = 00000001[-1]补 = 11111111两者相加:00000001 + 11111111 = (1)00000000。由于位数限制,最高位的1自然丢失,结果正是00000000(即0)。 - 扩展表示范围 请注意表格最右列的"数值范围"。对于8位二进制数,补码能表示 -128 ,而原码和反码的下限是-127。这是因为补码中
10000000这个编码被专门定义为了-128,使得表示范围比原码和反码多一个数。
如何求一个负数的补码
转换方法非常直接,以求 -5 的8位补码为例:
- 求原码 :先写出其绝对值(+5)的原码
00000101,然后将符号位(最高位)改为1,得到-5的原码10000101。 - 求反码 :保持符号位
1不变,将原码的数值部分(0000101)按位取反(0变1,1变0),得到反码11111010。 - 求补码 :在反码的末位加1,
11111010 + 1 = 11111011。所以,-5的补码就是11111011。
一个更快捷的技巧是:从原码的二进制位最低位开始,自右向左找到第一个1,这个1及其右边的所有0保持不变,左边的所有位(除符号位)按位取反 。例如,对于 10000101,从右开始第一个1是最低位本身,故只保留这个1,左边全部取反(符号位1不变),结果同样是 11111011。
C 语言相关的基础知识
1. 二进制、十进制和十六进制
- 8 位二进制 = 1 个字节,范围 0b00000000~0b11111111(即 0x00~0xFF)
- 二进制转十六进制:4 位一组,遵循 8421 规律
- 带 0x 前缀的是十六进制,不带的是十进制
2. for 循环语句
c
for(表达式1; 表达式2; 表达式3)
(需要执行的语句);
执行过程:先求解表达式 1,判断表达式 2,若为真则执行语句,再执行表达式 3,循环直到表达式 2 不成立。
注意事项:
while(1)等价于for(;;),都是死循环- 建议使用"半开半闭区间"写法
- 不能在循环体内修改循环变量
3. while 循环语句
c
While (表达式)
{
循环体语句;
}
表达式为真时执行循环体,为假时不执行。常数非 0 为真,0 为假。
4. do while 循环语句
c
do
{
语句;
} while(条件表达式);
先执行一次循环体,再判断条件,至少执行一次。
5. 函数的简单介绍
c
函数值类型 函数名 (形式参数列表)
{
函数体
}
- 一个工程文件必须有且仅能有一个 main 函数
- 函数值类型为 void 时表示无返回值
- 声明语句必须在执行语句之前
C 语言之数据类型
C51 数据类型分类和数值范围:
| 数据类型 | 长度 | 范围 |
|---|---|---|
| unsigned char | 1字节 | 0~255 |
| signed char | 1字节 | -128~127 |
| unsigned int | 2字节 | 0~65535 |
| signed int | 2字节 | -32768~32767 |
| unsigned long | 4字节 | 0~4294967295 |
| signed long | 4字节 | -2147483648~2147483647 |
| float | 4字节 | ±1.175494E-38~±3.402823E+38 |
| bit | 1位 | 0或1 |
原则:编写程序时,对于变量只用小不用大。
变量的作用域:
- 局部变量:在函数内部声明,只在本函数内有效,函数结束后自动释放
- 全局变量:在函数外声明,作用范围从声明处到程序结束,所有函数可访问
建议尽量减少全局变量的使用,过多使用会降低函数的独立性和程序的可读性。
变量的存储类别:
- auto(自动变量):默认类型,函数调用时分配,结束后释放
- static(静态变量):生命周期贯穿整个程序,只初始化一次
- register(寄存器变量):提高效率,变量值放在 CPU 寄存器中
- extern(外部变量):扩展全局变量的作用域,让其他文件也可引用
变量命名规则:
- 驼峰命名法:myFirstName(小驼峰)、MyLastName(大驼峰)
- 匈牙利命名法:g_lFailCount(g_ 表示全局,l 表示长整型)
一、C51 基本语法与扩展
C51 是针对 8051 系列单片机的 C 语言编译器(以 Keil C51 为代表),在标准 C 的基础上针对嵌入式硬件进行了深度扩展。
1. 头文件包含
c
#include <reg51.h> // 8051 标准 SFR 定义(P0~P3、TMOD、SCON 等)
#include <intrins.h> // 内部函数:_nop_、_crol_、_cror_ 等
#include <absacc.h> // 绝对地址访问宏:CBYTE、XBYTE、DBYTE、PBYTE
#include <stdio.h> // 标准 I/O(通过串口实现,需先初始化串口)
#include <stdlib.h> // 标准库(功能有限,不支持动态内存)
#include <string.h> // 字符串操作(memcpy、strlen 等,占用较多 ROM)
注意 :使用
STC15系列时,建议改用厂商提供的stc15.h,其中包含了片上 ADC、PWM、增强串口等 SFR 定义。
2. 特殊功能寄存器(SFR)访问
c
// ---- 端口定义(reg51.h 已包含,此处展示原理)----
sfr P0 = 0x80; // P0 口(需外接上拉电阻)
sfr P1 = 0x90; // P1 口
sfr P2 = 0xA0; // P2 口
sfr P3 = 0xB0; // P3 口(兼具串口/外中断/定时器引脚)
// ---- 程序状态字 PSW ----
sfr PSW = 0xD0;
sbit CY = PSW^7; // 进位标志(Carry)
sbit AC = PSW^6; // 辅助进位(BCD 运算用)
sbit F0 = PSW^5; // 用户自定义标志
sbit RS1 = PSW^4; // 寄存器组选择位 1
sbit RS0 = PSW^3; // 寄存器组选择位 0
sbit OV = PSW^2; // 溢出标志
sbit P = PSW^0; // 累加器奇偶标志
// ---- 中断控制 ----
sfr IE = 0xA8; // 中断允许寄存器
sbit EA = IE^7; // 总中断开关
sbit ES = IE^4; // 串口中断允许
sbit ET1 = IE^3; // 定时器 1 中断允许
sbit EX1 = IE^2; // 外部中断 1 允许
sbit ET0 = IE^1; // 定时器 0 中断允许
sbit EX0 = IE^0; // 外部中断 0 允许
sfr IP = 0xB8; // 中断优先级寄存器(0=低优先级,1=高优先级)
// ---- 定时器 ----
sfr TMOD = 0x89; // 定时器模式寄存器(不可位寻址)
sfr TCON = 0x88; // 定时器控制寄存器(可位寻址)
sbit TR0 = TCON^4; // 定时器 0 运行控制
sbit TR1 = TCON^6; // 定时器 1 运行控制
sbit TF0 = TCON^5; // 定时器 0 溢出标志
sbit TF1 = TCON^7; // 定时器 1 溢出标志
sbit IT0 = TCON^0; // 外部中断 0 触发方式(1=下降沿,0=低电平)
sbit IT1 = TCON^2; // 外部中断 1 触发方式
sbit IE0 = TCON^1; // 外部中断 0 标志
sbit IE1 = TCON^3; // 外部中断 1 标志
sfr TH0 = 0x8C; sfr TL0 = 0x8A; // 定时器 0 高/低字节
sfr TH1 = 0x8D; sfr TL1 = 0x8B; // 定时器 1 高/低字节
// ---- 串口 ----
sfr SCON = 0x98; // 串口控制寄存器
sfr SBUF = 0x99; // 串口数据缓冲(发送/接收共用地址,物理上分开)
sbit RI = SCON^0; // 接收中断标志(软件清零)
sbit TI = SCON^1; // 发送中断标志(软件清零)
sbit REN = SCON^4; // 允许接收
// ---- 电源控制 ----
sfr PCON = 0x87; // 电源控制(SMOD 位影响波特率)
3. 存储器类型关键字
8051 采用哈佛架构,程序存储器(ROM)与数据存储器(RAM)独立编址,C51 用以下关键字明确指定存储区域:
| 关键字 | 存储区 | 大小 | 寻址方式 | 说明 |
|---|---|---|---|---|
data |
内部 RAM 低 128B | 0x00--0x7F | 直接寻址 | 速度最快,优先存放常用变量 |
idata |
内部 RAM 全部 | 0x00--0xFF | 间接寻址 | 含高 128B(SFR 区同地址) |
bdata |
位寻址区 | 0x20--0x2F | 位/字节均可 | 可用 sbit 定义其中的位 |
pdata |
外部 RAM 一页 | 0x00--0xFF | @R0/@R1 |
用 P2 口选页,速度次于 data |
xdata |
外部 RAM | 0x0000--FFFF | DPTR 寻址 | 最大 64KB,速度最慢 |
code |
程序存储器 | 0x0000--FFFF | DPTR/PC | 只读,存常量表和字符串 |
c
// 内部 RAM
unsigned char data hot_var; // 频繁访问的变量
unsigned char idata stack_area[64]; // 间接寻址区
// 位寻址区
unsigned char bdata status_byte; // 可位/字节双重访问
sbit status_bit0 = status_byte^0; // 定义第 0 位
sbit status_bit7 = status_byte^7; // 定义第 7 位
// 外部 RAM
unsigned char xdata large_buf[1000]; // 大缓冲区
unsigned char pdata page_buf[256]; // 页面缓冲区
// 程序存储器(常量,只读)
code unsigned char seg_table[] = { // 共阴七段码表(0--F)
0x3F, 0x06, 0x5B, 0x4F, 0x66, // 0 1 2 3 4
0x6D, 0x7D, 0x07, 0x7F, 0x6F, // 5 6 7 8 9
0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71 // A B C D E F
};
4. 中断系统
8051 标准中断向量表:
| 中断号 | 中断源 | 向量地址 | 默认优先级 |
|---|---|---|---|
| 0 | 外部中断 0 | 0x0003 | 最高 |
| 1 | 定时器 0 | 0x000B | |
| 2 | 外部中断 1 | 0x0013 | |
| 3 | 定时器 1 | 0x001B | |
| 4 | 串口 | 0x0023 | 最低 |
| 5 | 定时器 2(8052) | 0x002B |
c
// 定时器 0 中断(50ms @12MHz,模式 1)
void Timer0_ISR() interrupt 1
{
TH0 = 0x3C; // 重装初值(65536 - 50000 = 15536 = 0x3CB0)
TL0 = 0xB0;
// 处理代码尽量简短,设置标志位由主循环处理
flag_50ms = 1;
}
// 外部中断 0(下降沿触发)
void INT0_ISR() interrupt 0
{
// 编译器自动保存/恢复:ACC、B、DPH、DPL、PSW
// 无需手动压栈
}
// 串口中断(发送/接收共用)
void Serial_ISR() interrupt 4
{
if (RI) { // 接收完成
RI = 0; // 必须软件清零
rx_buf = SBUF;
rx_flag = 1;
}
if (TI) { // 发送完成
TI = 0; // 必须软件清零
// 继续发送下一字节或清除忙标志
}
}
// 外部中断 1,使用寄存器组 1(快速响应,无需保存 R0-R7)
void Fast_ISR() interrupt 2 using 1
{
// 寄存器组 1:地址 0x08--0x0F
// 主程序使用寄存器组 0,两组不冲突
// 适合对响应时间要求极高的场合
}
中断初始化模板:
c
void interrupt_init(void)
{
// 外部中断 0:下降沿,高优先级
IT0 = 1; // 下降沿触发(0 = 低电平触发)
EX0 = 1; // 允许外部中断 0
IP |= 0x01; // 设为高优先级
// 定时器 0 中断
ET0 = 1;
// 串口中断
ES = 1;
EA = 1; // 最后开总中断
}
5. 位操作
c
// 位变量声明(存储在内部 RAM 的位寻址区 0x20--0x2F)
bit flag_tx = 0; // 发送完成标志
bit flag_rx = 0; // 接收完成标志
// I/O 位定义
sbit LED = P1^0; // P1.0 连接 LED
sbit KEY = P3^2; // P3.2 连接按键(INT0)
sbit SCL = P2^0; // I²C 时钟线
sbit SDA = P2^1; // I²C 数据线
// 常用位操作
LED = 1; // 置 1(点亮或熄灭,取决于电路)
LED = 0; // 清 0
LED = !LED; // 取反(翻转)
LED ^= 1; // 等效取反(针对端口字节操作时推荐此方式)
P1 |= 0x01; // 仅置位 P1.0,不影响其他位(读-修改-写)
P1 &= ~0x01; // 仅清除 P1.0
P2 ^= 0x0F; // 翻转 P2 低四位
// 注意:bit 类型不能定义数组,不能取地址
// 模拟 8 位标志位数组的方法:
unsigned char bdata flags;
sbit f0 = flags^0;
sbit f1 = flags^1;
// ... 以此类推到 f7
6. 绝对地址定位
c
// 变量绝对定位(_at_ 关键字,Keil C51 专用)
unsigned char xdata uart_buf[64] _at_ 0x2000; // 外部 RAM 0x2000 起
unsigned char code font_lib[128] _at_ 0x7F00; // ROM 0x7F00 起(需确保不与代码重叠)
// 使用 absacc.h 宏直接访问绝对地址
#include <absacc.h>
#define ADC_DATA XBYTE[0x8000] // 访问外部 I/O 地址 0x8000(MOVX @DPTR)
#define KEY_REG DBYTE[0x30] // 访问内部 RAM 0x30(MOV 直接地址)
#define CONST_ROM CBYTE[0x1000] // 读取 ROM 0x1000 处字节(MOVC)
#define PAGE_DATA PBYTE[0x40] // 访问 pdata 区 0x40(MOVX @R0)
// 使用示例
ADC_DATA = 0x00; // 启动 ADC 转换(写 0x8000)
unsigned char result = ADC_DATA; // 读 ADC 结果
// 函数绝对定位(通过链接器配置文件,不常用)
// 在 .Lnp 文件中:CODE (MyFunc, 0x1000)
二、与标准 C 的主要区别
1. 内存结构
| 特性 | 标准 C | C51 |
|---|---|---|
| 架构 | 冯·诺依曼(统一地址空间) | 哈佛(代码/数据物理分离) |
| 存储器类型 | 无区分 | data / idata / bdata / pdata / xdata / code |
| 指针大小 | 统一(通常 4 或 8 字节) | 1~3 字节,取决于目标存储区 |
| 堆栈方向 | 向下增长(多数平台) | 固定向上增长(内部 RAM 有限,默认约 64B) |
| 动态内存 | malloc/free 完整支持 | 不支持(或极受限) |
| 变量初始化 | 运行时从 ROM 复制到 RAM | 启动代码初始化 data 区;xdata 需手动初始化 |
2. 扩展关键字
c
// 存储类型说明符
data idata bdata pdata xdata code
// 硬件访问
sfr sbit bit
// 函数修饰符
interrupt using reentrant _task_
// 地址定位
_at_
// 内存模型(影响默认存储类型)
small compact large
// SDCC/IAR 兼容前缀(Keil 一般不需要)
__xdata __code __idata __sfr __sbit
3. 指针类型
c
// ① 通用指针(3 字节):可指向任意存储区,但效率最低
// 字节结构:[存储类型(1B)] [地址高字节(1B)] [地址低字节(1B)]
// 类型编码:0x00=idata, 0x01=xdata, 0xFE=pdata, 0xFF=code
char *gp; // 3 字节,生成类型判断+转换代码
// ② 存储器特定指针(1~2 字节):效率最高
char data *dp; // 1 字节(data/idata 区,0~255)
char idata *ip; // 1 字节
char pdata *pp; // 1 字节
char xdata *xp; // 2 字节(DPTR,0~65535)
char code *cp; // 2 字节(MOVC 指令)
// ③ 指针大小(sizeof)
// data/idata/pdata 指针:1 字节
// xdata/code 指针: 2 字节
// 通用指针: 3 字节
// ④ 性能建议
xdata char *buf_ptr; // 推荐:直接用 DPTR,高效
char *gen_ptr; // 不推荐:需判断类型,代码膨胀
// ⑤ 函数指针(通用指针,存放在 xdata/data 中)
void (*fp)(void); // 函数指针(指向 code 区)
4. 函数修饰符
c
// reentrant:支持递归调用(使用模拟堆栈,存储在 xdata)
// 注意:reentrant 函数速度慢,尽量只在必要时使用
int factorial(int n) reentrant
{
if (n <= 1) return 1;
return n * factorial(n - 1);
}
// interrupt + using:中断函数指定寄存器组,省去 R0-R7 保存开销
void fast_isr() interrupt 2 using 1
{
// 直接使用组 1 寄存器(0x08--0x0F)
// 主程序默认用组 0(0x00--0x07),互不干扰
}
// large:函数局部变量默认放 xdata(用于大内存需求函数)
void big_func() large
{
unsigned char buf[200]; // 自动分配在 xdata
}
// small(默认):局部变量放 data
// compact:局部变量放 pdata
5. 标准库差异
c
// 支持(有限)
#include <string.h> // memcpy、memset、strlen 等(代码较大)
#include <math.h> // 浮点运算(软件模拟,极慢,避免使用)
#include <stdio.h> // printf/scanf(通过串口,需初始化)
// 不支持
// fopen、fclose、fread、fwrite(无文件系统)
// malloc、free(无动态堆)
// system、exit(无操作系统)
// 多线程相关函数
// C51 特有的内部函数(intrins.h)
_nop_() // 精确延时 1 个机器周期(插入 NOP 指令)
_testbit_(bit_var) // 原子测试并清除位(JBC 指令,适合中断标志)
_crol_(val, n) // 无符号字符循环左移 n 位(RLA 指令)
_cror_(val, n) // 无符号字符循环右移 n 位
_irol_(val, n) // 无符号整数循环左移 n 位
_iror_(val, n) // 无符号整数循环右移 n 位
_lrol_(val, n) // 无符号长整数循环左移 n 位
_lror_(val, n) // 无符号长整数循环右移 n 位
_chkfloat_(val) // 检查浮点数状态(0=正常,1=NaN,2=正无穷,3=负无穷)
6. 编译与运行限制
| 限制项 | 说明 |
|---|---|
| 堆栈深度 | 内部 RAM 仅 256B,实际可用堆栈约 64~128B,深层递归会溢出 |
| 浮点运算 | 纯软件模拟,每次运算约 100+ 机器周期,应尽量避免 |
bit 类型限制 |
不能定义 bit 数组,不能对 bit 变量取地址(&) |
| 函数参数传递 | 中断函数不能有参数和返回值,不能被普通代码直接调用 |
| 结构体对齐 | 字节对齐,不支持 #pragma pack |
long/float |
32 位运算需多条指令,速度极慢,应改用查表或定点数代替 |
| 数组越界 | 编译器不检查,运行时越界会破坏相邻变量 |
三、编程注意事项
1. 数据类型与内存优化
c
// ---- 数据类型选择原则(从快到慢)----
unsigned char i; // ★★★★★ 首选:8 位,单指令,零扩展无开销
signed char c; // ★★★★ 有符号 8 位,有符号扩展略慢
unsigned int j; // ★★★ 16 位,2 字节,多指令
signed int k; // ★★★
unsigned long l; // ★★ 32 位,4 字节,非常慢
float f; // ★ 软件浮点,极慢(避免!)
// ---- 变量位置策略 ----
unsigned char data counter; // 频繁访问 → data 区(最快)
bit data flag_done; // 布尔标志 → bit 类型(节省 RAM)
unsigned char xdata uart_rx_buf[128]; // 大缓冲 → xdata 区
code unsigned char crc_table[256]; // 常量表 → code 区(不占 RAM)
// ---- 定点数替代浮点数 ----
// 不推荐:float temperature = 36.5f;
// 推荐:以 0.1°C 为单位存储
unsigned int temperature_x10 = 365; // 代表 36.5°C
// 显示时:temp_int = temperature_x10 / 10; temp_dec = temperature_x10 % 10;
// ---- 查表替代复杂计算 ----
// 不推荐:y = (unsigned char)(sin(x * 3.14159 / 180) * 127 + 128);
code unsigned char sin_table[256] = { /* 预计算值 */ };
unsigned char y = sin_table[x]; // 一条 MOVC 指令
// ---- 循环优化(向下计数到零)----
// 不推荐(16 位比较):
// for (unsigned int i = 0; i < 1000; i++) { ... }
// 推荐(8 位,DJNZ 指令):
unsigned char j;
for (j = 200; j != 0; j--) { ... }
2. 端口(I/O)操作
c
// ---- 整体赋值 ----
P0 = 0x55; // 直接写全部 8 位(MOV 指令)
P2 = ~P2; // 取反所有位
// ---- 读-修改-写(RMW)操作 ----
// 8051 对端口的位操作是"读端口锁存器,修改,写回"
// 不是读引脚状态(引脚有外部驱动时需注意)
P1 |= 0x04; // 置位 P1.2(ANL/ORL 指令)
P1 &= ~0x04; // 清除 P1.2
P1 ^= 0x04; // 翻转 P1.2
// ---- 输入模式(准双向口)----
P1 = 0xFF; // 先写全 1,使内部上拉生效
delay_us(5); // 等待引脚稳定
val = P1; // 再读引脚状态
// ---- 单引脚操作(sbit)----
sbit LED = P2^0;
sbit KEY = P3^3;
LED = 0; // 点亮(低有效)
if (KEY == 0) { ... } // 检测按键按下
// ---- P0 口注意事项 ----
// P0 口是开漏输出,作通用 I/O 时必须外接上拉电阻(通常 10kΩ)
// 总线模式下无需上拉(内部自动切换)
// ---- 外部中断配置 ----
IT0 = 1; // INT0 下降沿触发(更常用,避免电平触发的持续中断)
IT1 = 1; // INT1 下降沿触发
EX0 = 1; // 允许外部中断 0
EX1 = 1; // 允许外部中断 1
EA = 1; // 开总中断
3. 定时器/计数器编程
定时器初值计算公式(12MHz,12 分频):
- 机器周期 = 1μs(12MHz 晶振,12 分频)
- 模式 1(16 位):初值 = 65536 − 定时时间(μs)
- 模式 2(8 位自动重装):初值 = 256 − 定时时间(μs)
c
// ---- 定时器 0:模式 1,16 位定时,50ms @12MHz ----
void timer0_init(void)
{
TMOD &= 0xF0; // 清除 T0 的模式位
TMOD |= 0x01; // T0 模式 1(16 位)
TH0 = (65536 - 50000) / 256; // = 0x3C(高字节)
TL0 = (65536 - 50000) % 256; // = 0xB0(低字节)
ET0 = 1; // 允许 T0 中断
TR0 = 1; // 启动 T0
}
// ---- 定时器 1:模式 2,8 位自动重装,用作波特率发生器 ----
// 9600bps @11.0592MHz:TH1 = TL1 = 256 - 11059200/(12×32×9600) = 0xFD
// 9600bps @12MHz(PCON.SMOD=1):TH1 = TL1 = 0xF3(误差约 0.16%)
void timer1_baudrate_init(void)
{
TMOD &= 0x0F;
TMOD |= 0x20; // T1 模式 2(8 位自动重装)
TH1 = 0xFD; // @11.0592MHz,9600bps(无误差)
TL1 = 0xFD;
TR1 = 1; // 启动 T1(不开 ET1 中断)
}
// ---- 定时器 2(8052 增强型):16 位自动重装,50ms @12MHz ----
// 注意:T2CON/RCAP2H/RCAP2L 仅在 8052 及部分增强型 8051 中存在
#ifdef HAVE_T2
sfr T2CON = 0xC8;
sfr RCAP2H = 0xCB;
sfr RCAP2L = 0xCA;
sfr TH2 = 0xCD;
sfr TL2 = 0xCC;
sbit TR2 = T2CON^2;
sbit ET2 = IE^5;
void timer2_init(void)
{
T2CON = 0x04; // 16 位自动重装模式,允许重装
RCAP2H = (65536 - 50000) / 256;
RCAP2L = (65536 - 50000) % 256;
TH2 = RCAP2H;
TL2 = RCAP2L;
ET2 = 1;
TR2 = 1;
}
#endif
// ---- 定时器中断服务(模式 1 需手动重装)----
volatile unsigned char tick_count = 0;
void Timer0_ISR() interrupt 1
{
TH0 = 0x3C; // 模式 1 必须手动重装(模式 2 自动重装)
TL0 = 0xB0;
if (++tick_count >= 20) { // 20 × 50ms = 1s
tick_count = 0;
flag_1s = 1;
}
}
4. 串口通信编程
c
#include <reg51.h>
// ---- 串口初始化(模式 1,8 位,9600bps @11.0592MHz)----
void uart_init(void)
{
SCON = 0x50; // 模式 1:8 位可变波特率;REN=1 允许接收
TMOD |= 0x20; // T1 模式 2(8 位自动重装)
TH1 = 0xFD; // 9600bps @11.0592MHz
TL1 = 0xFD;
TR1 = 1; // 启动 T1
ES = 1; // 允许串口中断
EA = 1; // 开总中断
TI = 1; // 置位 TI(使 printf 可直接使用)
}
// ---- 查询方式发送单个字节 ----
void uart_send_byte(unsigned char dat)
{
SBUF = dat;
while (!TI); // 等待发送完成(约 1ms @9600bps)
TI = 0;
}
// ---- 查询方式发送字符串 ----
void uart_send_str(unsigned char *str)
{
while (*str) {
uart_send_byte(*str++);
}
}
// ---- 查询方式发送十六进制数(调试用)----
void uart_send_hex(unsigned char val)
{
code unsigned char hex_table[] = "0123456789ABCDEF";
uart_send_byte(hex_table[val >> 4]); // 高 4 位
uart_send_byte(hex_table[val & 0x0F]); // 低 4 位
uart_send_byte(' ');
}
// ---- 中断方式接收(环形缓冲)----
#define RX_BUF_SIZE 32
unsigned char xdata rx_buf[RX_BUF_SIZE];
unsigned char rx_head = 0, rx_tail = 0;
bit rx_overflow = 0;
void uart_isr() interrupt 4
{
if (RI) {
RI = 0;
unsigned char next = (rx_head + 1) % RX_BUF_SIZE;
if (next != rx_tail) { // 缓冲未满
rx_buf[rx_head] = SBUF;
rx_head = next;
} else {
rx_overflow = 1; // 溢出标志
}
}
if (TI) {
TI = 0;
// 可在此处实现中断发送队列
}
}
// 主循环中读取接收数据
unsigned char uart_read_byte(void)
{
unsigned char dat;
while (rx_head == rx_tail); // 等待数据(生产环境建议加超时)
dat = rx_buf[rx_tail];
rx_tail = (rx_tail + 1) % RX_BUF_SIZE;
return dat;
}
5. 中断编程最佳实践
c
// ---- 原则 1:中断函数要短,使用标志位与主循环通信 ----
volatile bit uart_rx_ready = 0;
volatile unsigned char uart_rx_data;
void uart_isr() interrupt 4
{
if (RI) {
RI = 0;
uart_rx_data = SBUF;
uart_rx_ready = 1; // 通知主循环处理
}
if (TI) { TI = 0; }
}
void main(void)
{
uart_init();
while (1) {
if (uart_rx_ready) {
uart_rx_ready = 0;
process_data(uart_rx_data); // 在主循环中处理
}
}
}
// ---- 原则 2:volatile 修饰中断与主程序共享变量 ----
volatile unsigned int adc_result; // 防止编译器优化掉读取操作
volatile bit conv_done = 0;
// ---- 原则 3:临界区保护(原子操作)----
// 方法一:关/开总中断(简单,会延迟其他中断)
EA = 0;
shared_var++; // 临界区
EA = 1;
// 方法二:保存/恢复中断状态(更安全)
unsigned char saved_ea;
saved_ea = EA;
EA = 0;
shared_var++;
EA = saved_ea;
// ---- 原则 4:中断嵌套与优先级 ----
// IP 寄存器各位:PS(串口), PT1(T1), PX1(INT1), PT0(T0), PX0(INT0)
IP = 0x01; // INT0 高优先级,其余低优先级
// 高优先级中断可打断低优先级中断(两级嵌套)
// ---- 原则 5:中断函数不能被主程序调用,不能有参数/返回值 ----
// 错误:Timer0_ISR(); ← 编译可能通过但行为未定义
// 正确:通过全局变量或 bit 标志传递数据
四、示例代码
1. 延时函数
c
#include <intrins.h>
// ---- 微秒延时(@12MHz,每循环约 1μs)----
void delay_us(unsigned int us)
{
while (us--) {
_nop_(); _nop_(); _nop_(); _nop_();
_nop_(); _nop_(); _nop_(); _nop_();
}
}
// ---- 毫秒延时(@12MHz)----
void delay_ms(unsigned int ms)
{
unsigned char i;
while (ms--) {
for (i = 120; i != 0; i--); // 约 1ms(可用示波器校准)
}
}
// ---- 精确 N × 10μs 延时(@12MHz,可用于 PWM)----
void delay_10us(unsigned char n)
{
while (n--) {
_nop_(); _nop_(); _nop_(); _nop_();
_nop_(); _nop_(); _nop_(); _nop_();
_nop_(); _nop_();
}
}
2. 数码管驱动(4 位共阴动态扫描)
c
#include <reg51.h>
#define SEG_PORT P0 // 段码输出口
#define DIG_PORT P2 // 位选输出口(低有效)
// 共阴七段码表(0--9,A--F,-,空)
code unsigned char seg_code[] = {
0x3F, 0x06, 0x5B, 0x4F, 0x66, // 0 1 2 3 4
0x6D, 0x7D, 0x07, 0x7F, 0x6F, // 5 6 7 8 9
0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, // A B C D E F
0x40, 0x00 // - (熄灭)
};
unsigned char disp_buf[4] = {0, 1, 2, 3}; // 显示缓冲区(0--16)
unsigned char disp_point = 0x00; // 小数点位掩码(位 n 对应第 n 位)
// 在定时器中断中每 2ms 调用一次
void seg_scan(void)
{
static unsigned char pos = 0;
SEG_PORT = 0x00; // 消隐(防止鬼影)
DIG_PORT = ~(0x01 << pos); // 选通当前位(低有效)
SEG_PORT = seg_code[disp_buf[pos]];
if (disp_point & (0x01 << pos)) // 小数点控制
SEG_PORT |= 0x80;
pos = (pos + 1) & 0x03; // 0→1→2→3→0
}
// 显示 4 位十进制数(0--9999)
void disp_number(unsigned int num)
{
disp_buf[3] = num / 1000;
disp_buf[2] = num % 1000 / 100;
disp_buf[1] = num % 100 / 10;
disp_buf[0] = num % 10;
// 消除高位的前导零(可选)
if (disp_buf[3] == 0) { disp_buf[3] = 17; } // 17 = 空白
if (disp_buf[3] == 17 && disp_buf[2] == 0) { disp_buf[2] = 17; }
if (disp_buf[2] == 17 && disp_buf[1] == 0) { disp_buf[1] = 17; }
}
3. 综合示例:智能 LED 控制系统
本示例演示多模式 LED 控制,涵盖按键消抖、定时器中断、PWM 调光、流水灯等技术。
c
#include <reg51.h>
#include <intrins.h>
// ---- 硬件引脚定义 ----
#define LED_PORT P2 // LED 阵列(低电平有效)
sbit LED0 = P2^0;
sbit LED1 = P2^7;
sbit KEY_MODE = P3^2; // 模式切换键(INT0)
sbit KEY_UP = P3^3; // 亮度增加键
sbit KEY_DOWN = P3^4; // 亮度减小键
// ---- 全局变量 ----
unsigned char data mode = 0; // 工作模式(0--3)
unsigned char data brightness = 50; // 亮度百分比(0--100)
volatile bit flag_mode_chg = 0; // 模式变化标志
volatile bit flag_50ms = 0; // 50ms 定时标志
// ---- 正弦亮度表(32 级,0--255)----
code unsigned char sin_table[32] = {
128, 152, 176, 198, 218, 234, 246, 253,
255, 253, 246, 234, 218, 198, 176, 152,
128, 103, 79, 57, 37, 21, 9, 2,
0, 2, 9, 21, 37, 57, 79, 103
};
// ---- 延时函数 ----
void delay_ms(unsigned int ms)
{
unsigned char i;
while (ms--) for (i = 120; i != 0; i--);
}
void delay_us(unsigned int us)
{
while (us--) { _nop_(); _nop_(); _nop_(); _nop_(); }
}
// ---- 软件 PWM(duty:0--100)----
void pwm_output(unsigned char duty)
{
if (duty == 0) { LED0 = 0; delay_ms(1); return; }
if (duty >= 100) { LED0 = 1; delay_ms(1); return; }
LED0 = 1;
delay_us((unsigned int)duty * 10);
LED0 = 0;
delay_us((unsigned int)(100 - duty) * 10);
}
// ---- 外部中断 0:模式切换(带消抖)----
void mode_isr() interrupt 0
{
delay_ms(10); // 消抖延时
if (KEY_MODE == 0) { // 确认按下
mode = (mode + 1) & 0x03;
flag_mode_chg = 1;
while (KEY_MODE == 0); // 等待释放
delay_ms(10); // 释放消抖
}
}
// ---- 定时器 0 中断(50ms,呼吸灯逻辑)----
void timer0_isr() interrupt 1
{
static unsigned char pwm_idx = 0;
TH0 = 0x3C; // 重装初值
TL0 = 0xB0;
flag_50ms = 1;
// 模式 1:呼吸灯(按正弦表调制 PWM)
if (mode == 1) {
LED0 = (sin_table[pwm_idx] > (brightness * 255 / 100));
pwm_idx = (pwm_idx + 1) & 0x1F;
}
}
// ---- 系统初始化 ----
void system_init(void)
{
LED_PORT = 0xFF; // 关闭所有 LED(低有效,输出高)
P3 |= 0x1C; // P3.2/3/4 置 1,配置为输入
// 定时器 0:模式 1,50ms
TMOD &= 0xF0;
TMOD |= 0x01;
TH0 = 0x3C;
TL0 = 0xB0;
ET0 = 1;
TR0 = 1;
// 外部中断 0:下降沿
IT0 = 1;
EX0 = 1;
EA = 1; // 开总中断
}
// ---- 按键扫描(含消抖,在主循环调用)----
void key_scan(void)
{
if (mode == 3) { // 仅调光模式处理亮度键
if (KEY_UP == 0) {
delay_ms(10);
if (KEY_UP == 0) {
if (brightness <= 95) brightness += 5;
while (KEY_UP == 0);
}
}
if (KEY_DOWN == 0) {
delay_ms(10);
if (KEY_DOWN == 0) {
if (brightness >= 5) brightness -= 5;
while (KEY_DOWN == 0);
}
}
}
}
// ---- 主函数 ----
void main(void)
{
unsigned char i;
system_init();
while (1)
{
key_scan();
switch (mode)
{
case 0: // 全亮
LED_PORT = 0x00;
break;
case 1: // 呼吸灯(在定时器中断中处理)
break;
case 2: // 流水灯
if (flag_50ms) {
flag_50ms = 0;
static unsigned char pos = 0;
LED_PORT = ~(0x01 << pos);
pos = (pos + 1) & 0x07;
}
break;
case 3: // PWM 调光
pwm_output(brightness);
break;
}
// 模式切换指示:LED1 闪烁一次
if (flag_mode_chg) {
flag_mode_chg = 0;
LED1 = 1;
delay_ms(200);
LED1 = 0;
}
}
}
五、开发工具与调试技巧
1. 常用开发工具
| 工具 | 类型 | 说明 |
|---|---|---|
| Keil C51 μVision | IDE + 编译器 | 最主流,内置仿真器,支持性能分析 |
| SDCC | 开源编译器 | 跨平台,免费,语法略有差异(__sfr 等) |
| IAR EW 8051 | 商业 IDE | 代码优化最强,适合对体积要求苛刻的项目 |
| Proteus ISIS | 仿真工具 | 电路 + 单片机联合仿真,调试阶段必备 |
| STC-ISP | 烧录软件 | STC 系列官方工具,USB 转串口一键下载 |
| CH341A | 编程器 | 通用 SPI/I²C EEPROM 烧录器 |
| 串口调试助手 | 调试工具 | 如 SSCOM、MobaXterm,查看 printf 输出 |
| Saleae Logic | 逻辑分析仪 | 8/16 通道,分析 UART/I²C/SPI 时序 |
2. 调试技巧
c
// ---- 技巧 1:I/O 口调试(示波器观测时序)----
sbit DBG = P1^7;
#define DBG_HIGH() (DBG = 1)
#define DBG_LOW() (DBG = 0)
#define DBG_TOGGLE() (DBG = ~DBG)
// 在关键位置插入 DBG_HIGH()/DBG_LOW(),用示波器测量执行时间
// ---- 技巧 2:软件断点(仅限仿真器)----
#define SW_BREAK() do { _nop_(); _nop_(); _nop_(); } while(0)
// 在 Keil 仿真模式下,将 PC 停在 NOP 序列上
// ---- 技巧 3:串口 printf 调试 ----
#ifdef DEBUG_UART
#define DBG_PRINT(str) uart_send_str((unsigned char *)(str))
#define DBG_HEX(val) uart_send_hex(val)
#else
#define DBG_PRINT(str) /* 空宏,发布版本零开销 */
#define DBG_HEX(val)
#endif
// 使用示例
DBG_PRINT("ADC=");
DBG_HEX(adc_result);
// ---- 技巧 4:堆栈溢出检测 ----
// 在 data 区末尾设置已知标记,主循环定期检查
idata unsigned char stack_guard[2]; // 放置在堆栈增长方向
void stack_guard_init(void)
{
stack_guard[0] = 0xAA;
stack_guard[1] = 0x55;
}
void stack_guard_check(void)
{
if (stack_guard[0] != 0xAA || stack_guard[1] != 0x55) {
// 堆栈溢出!执行安全处理
EA = 0;
while (1) { DBG_PIN = ~DBG_PIN; delay_ms(100); } // 报警
}
}
// ---- 技巧 5:看门狗(STC 系列)----
// STC15W4K32S4 看门狗寄存器
sfr WDT_CONTR = 0xC1;
// WDT_CONTR 各位:WDT_FLAG | - | EN_WDT | CLR_WDT | IDLE_WDT | PS2 | PS1 | PS0
// PS[2:0] 分频:000=2^5, 001=2^6, ..., 111=2^12(@12MHz)
// 喂狗周期 = 12 × 分频系数 / 晶振频率
#define WDT_ENABLE() (WDT_CONTR = 0x35) // 使能,PS=101,约 1s @12MHz
#define FEED_WDT() (WDT_CONTR = 0x35) // 喂狗(重写即可)
// 在主循环最底部调用 FEED_WDT()
3. 常见问题与解决方案
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 程序跑飞/死机 | 堆栈溢出、指针越界、EMI干扰 | 加看门狗,检查递归深度,中断中避免大局部变量 |
| 中断不响应 | EA 未开、中断标志未清、优先级 | 检查 IE/IP 寄存器;软件清除 RI/TI/TF0 等标志 |
| 串口乱码 | 波特率不匹配、晶振误差 | 改用 11.0592MHz;检查 SMOD 位;用逻辑分析仪测波形 |
| 变量值意外改变 | 中断与主程序竞争、优化器问题 | 加 volatile;用临界区保护(EA=0/1) |
| 数码管显示鬼影 | 切换位选前未消隐 | 切换前先关段码(SEG_PORT=0x00)再切位选 |
| 浮点数计算结果偏差大 | 软件浮点精度有限 | 改用定点数(×10、×100 等比例因子) |
| 外部 RAM 读写异常 | MOVX 时序不匹配、总线冲突 | 检查 P0 上拉、ALE 信号;调整外部 RAM 等待状态 |
| 代码超出 ROM 限制 | 代码量过大 | 启用编译器优化(Level 8+);将常量移至 code 区 |
六、高级主题与扩展
1. 内存分页(Bank Switching,ROM > 64KB)
c
// 适用于代码量超过 64KB 的项目
// 原理:用额外 I/O 口控制 ROM 页面选择电路(如 74HC138)
sfr PAGE_REG = 0x90; // 假设页面寄存器映射到 P1 口
// 读取超过 64KB 的 ROM 数据
unsigned char read_ext_rom(unsigned long addr)
{
unsigned char page = (unsigned char)(addr >> 16);
unsigned int offset = (unsigned int)(addr & 0xFFFF);
PAGE_REG = page;
return *((code unsigned char *)offset);
}
// 切换代码页面(需配合连接器脚本)
#define SELECT_PAGE(n) (PAGE_REG = (n))
2. 简单协作式任务调度器
c
// 适用于需要并发执行多个任务的场景
// 原理:时间片轮转,利用定时器中断触发任务切换
#define MAX_TASKS 4
typedef struct {
void (*func)(void); // 任务函数指针
unsigned int period_ms; // 执行周期(ms)
unsigned int counter; // 计数器
unsigned char state; // 0=停止, 1=就绪, 2=运行
} Task;
Task task_list[MAX_TASKS];
volatile unsigned int sys_tick = 0; // 系统毫秒计数
// 定时器中断(1ms)
void timer_isr() interrupt 1
{
unsigned char i;
TH0 = (65536 - 1000) / 256;
TL0 = (65536 - 1000) % 256;
sys_tick++;
for (i = 0; i < MAX_TASKS; i++) {
if (task_list[i].state == 1) {
if (++task_list[i].counter >= task_list[i].period_ms) {
task_list[i].counter = 0;
task_list[i].state = 2; // 标记为就绪
}
}
}
}
// 注册任务
void task_register(unsigned char id, void (*func)(void), unsigned int period)
{
task_list[id].func = func;
task_list[id].period_ms = period;
task_list[id].counter = 0;
task_list[id].state = 1;
}
// 主调度循环
void scheduler_run(void)
{
unsigned char i;
while (1) {
for (i = 0; i < MAX_TASKS; i++) {
if (task_list[i].state == 2) {
task_list[i].state = 1;
task_list[i].func(); // 执行任务
}
}
// 可在此处加 PCON |= 0x01 进入空闲模式节省功耗
}
}
3. C 与汇编混合编程
c
// ---- 方法一:在 C 文件中内嵌汇编(Keil C51)----
void precise_delay_100us(void)
{
#pragma asm
MOV R7, #50 ; 50 次循环
DELAY_LOOP:
NOP
NOP
DJNZ R7, DELAY_LOOP
#pragma endasm
}
// ---- 方法二:C 声明,汇编文件实现 ----
// C 头文件中声明:
extern unsigned char fast_crc8(unsigned char *data, unsigned char len);
// 汇编文件 (crc8.asm) 中实现:
// ; 参数:data 指针在 R6:R7(通用指针低2字节),len 在 R5
// ; 返回值:ACC(R7)
// PUBLIC _fast_crc8
// _fast_crc8:
// ; ... CRC 计算汇编代码 ...
// RET
// ---- 方法三:生成汇编列表文件(用于优化关键循环)----
// 在 Keil Options → C51 → Generate Assembler SRC File
// 编译后生成 .src 文件,可直接查看编译器生成的汇编代码
4. I²C 软件模拟(STC15 无硬件 I²C 时)
c
#include <intrins.h>
sbit SCL = P2^0;
sbit SDA = P2^1;
#define I2C_DELAY() { _nop_(); _nop_(); _nop_(); _nop_(); }
void i2c_start(void)
{
SDA = 1; SCL = 1; I2C_DELAY();
SDA = 0; I2C_DELAY(); // SDA 下降沿(SCL 高时)= 起始信号
SCL = 0; I2C_DELAY();
}
void i2c_stop(void)
{
SDA = 0; SCL = 1; I2C_DELAY();
SDA = 1; I2C_DELAY(); // SDA 上升沿(SCL 高时)= 停止信号
}
unsigned char i2c_send_byte(unsigned char dat)
{
unsigned char i, ack;
for (i = 0; i < 8; i++) {
SDA = (dat & 0x80) ? 1 : 0;
dat <<= 1;
SCL = 1; I2C_DELAY();
SCL = 0; I2C_DELAY();
}
SDA = 1; // 释放 SDA,等待 ACK
SCL = 1; I2C_DELAY();
ack = (SDA == 0) ? 1 : 0; // ACK=0 表示从机应答
SCL = 0; I2C_DELAY();
return ack;
}
unsigned char i2c_recv_byte(unsigned char send_ack)
{
unsigned char i, dat = 0;
SDA = 1; // 释放 SDA
for (i = 0; i < 8; i++) {
SCL = 1; I2C_DELAY();
dat = (dat << 1) | SDA;
SCL = 0; I2C_DELAY();
}
SDA = send_ack ? 0 : 1; // 0=ACK(继续),1=NACK(结束)
SCL = 1; I2C_DELAY();
SCL = 0; I2C_DELAY();
SDA = 1;
return dat;
}
// 向 AT24C02 EEPROM 写入一个字节(设备地址 0xA0)
void eeprom_write(unsigned char addr, unsigned char dat)
{
i2c_start();
i2c_send_byte(0xA0); // 设备地址 + 写
i2c_send_byte(addr); // 存储地址
i2c_send_byte(dat); // 数据
i2c_stop();
delay_ms(5); // EEPROM 写入等待(典型值 5ms)
}
// 从 AT24C02 EEPROM 读取一个字节
unsigned char eeprom_read(unsigned char addr)
{
unsigned char dat;
i2c_start();
i2c_send_byte(0xA0); // 写地址
i2c_send_byte(addr);
i2c_start(); // 重复起始
i2c_send_byte(0xA1); // 设备地址 + 读
dat = i2c_recv_byte(0); // 读取,发 NACK
i2c_stop();
return dat;
}
总结
C51 在保持标准 C 语法的基础上,针对 8051 单片机的硬件特点进行了深度扩展,核心差异体现在以下几点:
- 明确的内存分区管理 :哈佛架构要求 ROM/RAM 分离,用
data/xdata/code等关键字精确控制变量位置,是提升性能和节约 RAM 的关键。 - 直接硬件寄存器访问 :
sfr/sbit以 C 语法直接映射 SFR,比汇编更易维护,比通用指针更高效。 - 位操作原生支持 :
bit类型和bdata位寻址区,1 位即 1 个布尔标志,远比用字节模拟高效。 - 中断处理简化 :
interrupt关键字自动处理保存/恢复现场,using指定寄存器组可进一步提升响应速度。 - 针对资源限制的优化意识 :始终优先选择
unsigned char,避免浮点,用查表替代复杂运算,是 8051 编程的基本原则。
关键编程原则
- 资源意识优先:256B 内部 RAM、64KB ROM 是硬约束,每个变量的类型和存储位置都应经过考量。
- 效率驱动设计 :
unsigned char>unsigned int>long>float;data 区 > xdata 区。 - 中断保持简短:中断函数只设标志,主循环做处理,避免在中断中调用复杂函数。
- 防御性编程 :加看门狗、检查堆栈、用
volatile保护共享变量、临界区关中断。 - 逐模块验证:先在 Proteus 中仿真验证单个模块,再逐步集成到完整系统。
掌握 C51 既需要扎实的 C 语言基础,也需要深入理解 8051 的硬件架构。随着对片内外设(ADC、PWM、SPI、I²C 等)的熟悉,可以在极有限的资源上构建功能完善的嵌入式系统。