C51单片机设计C语言实例合集:从LED到超声波测距的完整开发指南
简介本资源为C51单片机设计C语言实例合集配套400个由浅入深的源码案例覆盖IO输出、LED花样控制、数码管与按键交互、LCD1602/12864液晶显示、DS1302时钟、DS18B20测温、24C02存储、串口通信、红外收发、点阵显示、步进电机及直流电机驱动等常用外设与典型应用适合51单片机初学者入门学习也可作为课程设计、毕业设计及项目开发的参考代码库。资源压缩包约28.52MB内含以.c源文件为主的数百个独立实例平均每个案例文件精简可按编号逐例阅读、烧录验证或二次修改循序渐进理解C语言编程思路与寄存器配置方法。资源目前已有3900余人学习下载经过较多学习者验证涵盖从点亮第一个LED到综合项目如密码锁、电子钟、温度报警等的完整训练路径案例分类清晰、目录直观便于快速定位所需功能模块对提升单片机编程与调试能力有较大帮助。1. 从点亮LED到超声波测距C51单片机设计C语言实例合集的能力图谱翻一遍这套C51单片机设计C语言实例合集的目录从“IO输出点亮1个LED灯方法1”起步到“LED循环左移”“数码管动态显示”再到“DS18B20温度采集”“DS1302时钟”“1602/12864液晶显示”最后落到“超声波测距LCD1286”和“彩屏图片显示”。这个排列顺序不是随机的它实际上对应了从GPIO电平控制、片上外设配置到多模块系统集成的完整进阶路径和大学单片机课程以及蓝桥杯等竞赛的考察范围高度重合。所有例程都是可直接编译的C语言源码按外设类型编号每个例程都对应一个能观察到的实验现象。新手可以用它建立LED、数码管、按键、液晶、传感器这些基础模块的驱动意识做过几个项目的开发者也能直接抽取其中的I2C、单总线、串口时序代码快速移植到自己的工程里。下文按显示控制、片上外设、综合应用和工程配置几个层面拆解这套源码的实际用法。2. GPIO控制与显示基础流水灯、数码管与按键扫描的实现思路2.1 IO输出控制LED的两种方式与延时函数精度分析合集前两个实例是点亮LED的两种方法。第一个用sbit位定义单独控制引脚第二个对整个端口做字节赋值。两者在指令层面完全不同位操作锁定的是可位寻址寄存器字节赋值直接把数据写入P0寄存器。实际开发中位操作适合控制使能端、片选端这类单引脚信号字节赋值适合一次并行输出8位数据。// 方法1位寻址方式点亮LED #include reg51.h sbit LED1 P1^0; // 将P1.0位映射为LED1变量 void main(void) { LED1 0; // 共阳接法输出低电平点亮 while (1) { // 主循环保持LED持续点亮 } }sbit关键字把P1.0引脚映射为可操作的位变量后续代码可读性更好。方法2用P1 0xFE做整端口赋值0xFE的二进制是1111 1110只有P1.0输出低电平其余引脚输出高电平。两种方式在Keil C51中通用reg51.h头文件里已经定义好全部特殊功能寄存器和位地址不需要额外配置。与点亮LED配套的是延时函数。合集里大量例程使用软件延时核心是循环体的指令周期void delay_ms(unsigned int ms) { unsigned int i, j; for (i 0; i ms; i) { for (j 0; j 120; j) { // 空操作消耗指令周期 } } }这个函数在12MHz晶振、Keil默认优化级别下实测每轮约1ms。换成STC11系列1T内核后同一条语句执行速度是传统8051的12倍内层循环次数必须按比例调小。软件延时不需要硬件资源但会被中断程序打扰时序要求严格的场合应改用硬件定时器。合集里从“点亮LED”到“定时器使用”的编排顺序正好是这条思路的自然延续。2.2 循环移位与查表法实现LED流水灯和花样灯实例10和11分别实现LED循环左移和循环右移逻辑互为镜像。左移的核心是组合移位和按位或#include reg51.h void delay_ms(unsigned int ms); void main(void) { unsigned char led 0xFE; // 初始状态P0.0为低电平点亮 while (1) { P0 led; delay_ms(200); led (led 1) | 0x01; // 左移一位最低位补1 if (led 0xFF) led 0xFE; // 全部熄灭后回到初始位 } }led (led 1) | 0x01中左移一位把当前亮灯位置向高位推| 0x01补上最低位的灭灯电平。判断led 0xFF是因为所有位都变成1时说明灯已全部熄灭此时重新赋0xFE形成循环。如果换成右移把移位方向反过来0x01换成0x80即可。实例12 “查表显示LED灯”换了一种思路把运动序列预存到数组unsigned char code led_table[] { 0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF, 0x7F }; void main(void) { unsigned char i; while (1) { for (i 0; i 8; i) { P0 led_table[i]; delay_ms(150); } } }查表法的优势是序列可以任意指定不限于线性位移。实例14 “花样灯”在表中写入非线性的亮灭组合实现“两灯交替、向中间聚拢再分开”这类视觉效果。数组前的code关键字把数据放到程序存储区不占用RAM。C51单片机的内部RAM通常只有128字节对全局数组来说这个关键字是必须的。2.3 共阳数码管段码表与静态显示程序设计从实例16 “共阳数码管静态显示”开始例程引入段码概念。数码管的7个段加小数点共8个引脚点亮某段需要在对应引脚输出电平。共阳接法下0~9的段码是一个固定查找表// 共阳数码管段码表数字0~9 unsigned char code seg_code[] { 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90 };下表列出常用数字在共阳和共阴两种接法下的段码值两者的关系是按位取反显示字符共阳段码共阴段码00xC00x3F10xF90x0620xA40x5B30xB00x4F40x990x6650x920x6D60x820x7D70xF80x0780x800x7F90x900x6F段码的推导方法共阴接法下按a0x01、b0x02、c0x04、d0x08、e0x10、f0x20、g0x40、dp0x80的位权重累加。显示数字1时b和c两个段亮共阴段码是0x020x040x06共阳取反得0xF9。实例17 “1个共阳数码管显示变化数字”在查表基础上加定时器刷新演示了表驱动加状态更新的基本结构。2.4 动态扫描与独立按键扫描的实现思路多位显示时每一位单独占用一组IO不现实C51的做法是位选加段选两级结构。实例159 “数码管显示锁存器”和实例160 “数码管动态显示”展示了两种实现前者用锁存器隔离位选与段选后者直接用P2口位选、P0口段选逐位扫描。unsigned char code bit_sel[] {0xFE, 0xFD, 0xFB, 0xF7}; unsigned char display_buf[4] {1, 2, 3, 4}; void display_scan(void) { unsigned char i; for (i 0; i 4; i) { P2 bit_sel[i]; // 选中第i位数码管 P0 seg_code[display_buf[i]]; // 输出对应段码 delay_ms(2); // 保持2ms } }动态扫描一轮4位的周期是8ms刷新率约125Hz人眼不会察觉闪烁。位与位的切换空隙要尽量短否则显示有拖影。扫描函数放在定时器中断里比主循环稳定因为主循环里的其他耗时操作会拉长刷新周期。按键扫描经常和数码管显示搭配实例19 “按键控制单个数码管显示”的流程是经典的三段式判断void key_scan(void) { if (KEY1 0) // 第一次检测到按下 { delay_ms(10); // 跳过抖动区间 if (KEY1 0) // 再次确认电平稳定 { while (KEY1 0); // 等待按键释放 num; if (num 9) num 0; P0 seg_code[num]; } } }去抖时间取10~20ms是机械按键的通用值太短滤不掉抖动毛刺太长影响响应手感。按键接在P3口且电路为高电平上拉时检测到低电平表示按下。STC系列默认的准双向口带弱上拉可以直接用这种电路。3. 片上外设与通信接口定时器、串口、I2C与单总线实战3.1 定时器工作方式选择与方式1中断定时实现流水灯和数码管扫描的延时对精度要求不高软件延时够用一旦涉及串口波特率、DS18B20时序、DS1302读写就必须用硬件定时器。C51的T0和T1支持四种工作方式选择依据如下表工作方式计数器位数最大定时12MHz晶振典型应用方式013位约8.192ms兼容早期产品新设计很少用方式116位约65.536ms周期定时、方波发生方式28位自动重装载约0.256ms串口波特率发生器方式3T0分裂为两个独立8位定时器约0.256ms同时需要两路定时中断实例167和170分别演示方式1和方式2。方式1的初始化代码void Timer0_Init(void) { TMOD 0xF0; // 清零T0控制位保留T1原有配置 TMOD | 0x01; // T0设为方式116位定时器 TH0 0x3C; // 初值高8位对应50ms TL0 0xB0; // 初值低8位 ET0 1; // 使能T0中断 EA 1; // 打开总中断 TR0 1; // 启动定时器 }初值计算逻辑12MHz晶振下机器周期1us定时50ms需要计数50000次初值65536-5000015536十六进制是0x3CB0。TMOD 0xF0和TMOD | 0x01的组合是惯用写法只修改T0相关位不影响T1的配置。中断服务函数必须重新装载初值否则定时器从0重新计数周期变成65.536msunsigned char cnt 0; void Timer0_ISR(void) interrupt 1 { TH0 0x3C; // 重装初值 TL0 0xB0; cnt; if (cnt 20) // 累计20个50ms为1s { cnt 0; P0 ~P0; // 翻转LED状态 } }interrupt 1是T0的中断号Keil C51规定0对应外部中断0、1对应T0、2对应外部中断1、3对应T1、4对应串口。中断函数里不要做耗时操作尤其不要调用软件延时否则会拖慢主循环并影响其他中断响应。3.2 串口通信与中断收发波特率配置与帧处理实例113到115覆盖串口基本通讯、串口中断应用和RS485通信。串口方式1是8位异步收发波特率由T1的溢出率决定经典配置void UART_Init(void) { SCON 0x50; // 方式110位一帧REN1允许接收 TMOD 0x0F; // 保留T0配置 TMOD | 0x20; // T1设为方式28位自动重装载 TH1 0xFD; // 波特率9600 TL1 0xFD; ES 1; // 使能串口中断 EA 1; TR1 1; // 启动T1 }TH10xFD对应9600波特率前提是晶振频率11.0592MHz。这个频率是专门为串口选的因为它能精确分频出标准波特率。换成12MHz晶振时误差约2%短帧通信可用长帧或接Modbus总线时容易出错。波特率(bps)晶振频率(MHz)TH1初值误差960011.05920xFD0%480011.05920xFA0%240011.05920xF40%9600120xE6约2%串口接收用中断方式避免丢字节数据到达时硬件将SBUF中的数据交给程序并置位RI标志unsigned char rx_buf[16]; unsigned char rx_cnt 0; void UART_ISR(void) interrupt 4 { if (RI) { RI 0; // 清接收标志 rx_buf[rx_cnt] SBUF; // 读取串口数据 if (rx_cnt 16) rx_cnt 0; // 环形缓冲处理 } if (TI) { TI 0; // 清发送完成标志 } }SBUF是收发共用的特殊功能寄存器读它得到接收数据写它发送数据。实际项目中帧接收不能只靠“收满16字节”判断还需要帧头、帧尾和校验值约束。参考Modbus RTU的帧格式常见做法是收到帧头后启动一个定时器两个字节间隔超过3.5个字符时间就认为帧结束再做CRC校验。合集里的“串口测温电脑显示”实例146就是“接收命令-采集温度-回传数据”的完整闭环。3.3 AT24C02的I2C读写开机次数与掉电保存的实现实例100 “24c02记忆开机次数”和实例101 “24c02存储上次使用中状态”用AT24C02实现掉电保存。AT24C02是2Kb的EEPROMI2C总线访问两根线SCL和SDA在空闲时被上拉电阻拉高。void I2C_Start(void) { SDA 1; SCL 1; delay_us(5); SDA 0; // SCL高电平期间SDA出现下降沿起始条件 delay_us(5); SCL 0; } void I2C_Stop(void) { SDA 0; SCL 1; delay_us(5); SDA 1; // SCL高电平期间SDA出现上升沿停止条件 delay_us(5); }起始和停止条件是I2C协议所有事务的边界。AT24C02的设备地址是0xA0写和0xA1读内部有256字节存储空间。写周期最长约5ms写完数据后如果立即读硬件ACK会一直保持高电平需要软件轮询或强制延时。记录开机次数的核心逻辑count AT24C02_ReadByte(0x00); // 上电读取保存次数 count; AT24C02_WriteByte(0x00, count); // 写回地址0这类应用要注意EEPROM的擦写寿命约100万次同一地址频繁写入会提前磨损工业场景通常加磨损均衡策略比如轮流使用多个存储地址避免每次都写同一个位置。3.4 DS18B20单总线时序温度采集与超温报警实例122到126覆盖单路和多个DS18B20的读取以及超温报警逻辑。DS18B20使用单总线协议一根数据线既要供电又要传数据所有时序以微秒级延时为基准操作时序要求说明复位脉冲拉低480us以上再释放释放后器件拉低60~240us应答写0拉低整个60us时隙数据线上保持低电平写1拉低1~15us后释放由上拉电阻恢复高电平读时序拉低1~15us后采样必须在时隙开始后15us内读读取温度的流程分四步复位、写命令跳过ROM匹配0xCC、写转换命令0x44、等转换结束然后再次复位、写读暂存器命令0xBE最后读回两字节温度数据。unsigned int DS18B20_GetTemperature(void) { unsigned char low, high; unsigned int temp; DS18B20_Reset(); DS18B20_WriteByte(0xCC); // 跳过ROM序列号匹配 DS18B20_WriteByte(0x44); // 启动温度转换 while (DQ 0); // 转换期间DS18B20拉低总线完成后释放 DS18B20_Reset(); DS18B20_WriteByte(0xCC); // 跳过ROM DS18B20_WriteByte(0xBE); // 读取暂存器 low DS18B20_ReadByte(); // 温度低字节 high DS18B20_ReadByte(); // 温度高字节 temp (high 8) | low; // 组合成16位温度值 return temp; }温度值格式是高字节的高5位为符号位低11位为数据位。12位分辨率下每LSB对应0.0625℃实际温度等于原始值乘以0.0625。显示时先右移4位得到整数部分小数部分单独处理。实例125 “超温报警测试”读取温度后与阈值比较超过则驱动蜂鸣器这就是“传感器数据阈值判断执行机构”的控制系统最小闭环。多个DS18B20挂同一条总线时实例124需要先读每颗芯片的64位序列号再用0x55命令匹配目标设备否则读到的数据会混乱。4. 显示系统与时钟应用1602/12864液晶、DS1302与综合项目设计4.1 1602液晶的读写时序与字符显示流程合集多处实例用到1602液晶它的驱动本质是对两个寄存器的操作写命令和写数据。RS引脚区分访问对象RW区分读写方向EN是使能脉冲。void LCD1602_WriteCmd(unsigned char cmd) { RS 0; // 选择命令寄存器 RW 0; // 写方向 P0 cmd; // 命令字送到数据口 EN 1; delay_ms(2); // 使能高电平保持时间 EN 0; } void LCD1602_WriteData(unsigned char dat) { RS 1; // 选择数据寄存器 RW 0; P0 dat; EN 1; delay_ms(2); EN 0; }1602初始化序列一般是延时15ms、写0x388位总线两行显示、写0x0C开显示关光标、写0x06写入后光标右移、写0x01清屏。实例175 “液晶1602显示”的参考工程里有完整的初始化序列。1602内部DDRAM地址第一行从0x00开始第二行从0x40开始定位时用0x80 | 列地址作为命令写入。写命令和写数据之间要留出足够的执行时间用查忙标志比固定延时更可靠但查忙需要把P0设为输入模式很多工程师图省事直接用延时。提示1602模块如果只有16个引脚而没有PSB等模式选择位默认就是8位并口模式。部分模块带背光控制引脚BLK接法看模块丝印确认。4.2 12864液晶的并行/串行驱动与中文字库显示实例106、107和176到180覆盖ST7920主控12864液晶的多种驱动方式。ST7920内置中文字库显示中文时写入GB2312编码的两个字节即可不需要自己取模。并行模式下DB0~DB7接P0口RS、RW、EN控制寄存器选通PSB接高电平串行模式PSB接低电平只要CS、SID、SCLK三根线。void LCD12864_WriteByte(unsigned char dat) { unsigned char i; for (i 0; i 8; i) { SCLK 0; if (dat 0x80) SID 1; else SID 0; dat 1; SCLK 1; // 上升沿锁存数据 } }串行模式在 ST7920 上每一位的传输顺序是固定的先写5个连续的1作为同步头再跟两个字节的命令和数据。上述循环只演示了单字节移位发送完整驱动里需要按照“同步头高字节低字节”的帧格式组包。串行模式的优点是省IO缺点是速度慢刷全屏图形时有明显的逐行绘制过程。实例131和168在刷图时先清屏再写新数据直接在原图上叠加会产生残影。4.3 DS1302时钟芯片驱动与可调日历实现DS1302是串行时钟芯片通过CE、SCLK、I/O三线接口访问。实例102到105从“时钟原理”到“串口自动更新时间”难度递增。读写DS1302的一个寄存器需要先写地址字节最低位表示方向unsigned char DS1302_ReadByte(unsigned char addr) { unsigned char i, dat 0; CE 1; // 启动通信 DS1302_WriteByte(addr | 0x01); // 地址字节最低位置1表示读 for (i 0; i 8; i) { dat 1; SCLK 0; if (DS1302_IO) dat | 0x80; // 上升沿读入1位 SCLK 1; } CE 0; // 结束通信 return dat; }DS1302内部时间寄存器全部使用BCD码存储读出来要转十进制才能显示写时间时反过来。实例166 “用DS1302与12864LCD设计的可调式中文电子日历”把DS1302时间和12864汉字显示组合按键改BCD值后写回芯片。这里最容易踩的坑是DS1302默认写保护是开启的往时间寄存器写入数据前必须先向地址0x8E写入0x00关闭写保护写完后写0x80重新打开否则时间写不进去读出来一直是初始值。DS1302用外部32.768kHz晶振焊接时晶振两脚对地电容的容值要匹配通常选6pF不匹配时走时误差会明显增大。4.4 综合项目实例电子钟、密码锁与交通灯的模块组合合集后半部分大量综合项目最典型的是“电子钟类”“密码锁类”和“交通灯类”。以实例142 “1602液晶显示的密码锁”为例功能拆分是矩阵键盘输入、EEPROM存储密码、1602显示状态、继电器控制开锁。模块间通过全局变量交换数据输入缓存放数组密码比对用循环逐位bit check_password(void) { unsigned char i; for (i 0; i 4; i) { if (input_buf[i] ! saved_pwd[i]) return 0; // 有一位不匹配即验证失败 } return 1; }这种“输入采集-状态判断-输出控制”的结构是所有综合项目的主干。实例147 “交通灯测试”把定时器中断的时基用到极致主循环只做状态判断定时器中断每秒更新倒计时四个方向用状态枚举切换。分析这类项目的源码时建议先画模块间的数据流图再逐模块对照硬件接线比从头按行读代码高效得多。实例138 “电子琴”则是按键扫描加蜂鸣器输出不同频率的组合频率表放在code区按键索引直接映射表下标这也是查表法在音频场景中的典型应用。5. Keil C51工程搭建与代码复用芯片包安装、编译配置与排错技巧5.1 Keil5添加C51芯片包与工程模板建立Keil MDK5默认只支持ARM内核要编译C51源码必须先安装C51芯片包。安装完成后新建工程Device栏会出现Atmel、STC等厂家的8051型号。如果列表里没有STC89C52可以先选Atmel的AT89C52建工程头文件用reg51.h不影响编译烧录时再选STC对应型号。工程必须修改两个配置Output标签勾选Create HEX File否则没有HEX文件可下载Debug标签按烧录器选型配置常见的是STC-Link或USB转串口下载。5.2 常见编译错误与硬件调试排查思路C51编译报错最高频的是C231和C141。C231表示位地址重复定义通常因为两个sbit变量映射到同一个可位寻址引脚检查头文件和源文件的位定义C141表示数组越界或指针访问非法地址域重点检查数组下标是否被外部修改。硬件排查按固定顺序先电源电压和复位电路再晶振波形最后下载器连接。程序能下载但现象不对时在main函数第一行翻转一个IO口用示波器或万用表确认芯片是否真正运行。使用STC内部IRC时钟可以省外部晶振但内部时钟精度约1%左右串口高波特率下还是建议外接11.0592MHz晶振。5.3 基于外设驱动的代码复用方法把合集里的每个外设驱动拆成“头文件源文件”的文件对比如ds18b20.h和ds18b20.c头文件只暴露初始化函数和读取函数源文件封装底层时序// ds18b20.h #ifndef DS18B20_H #define DS18B20_H extern bit DS18B20_Reset(void); extern void DS18B20_WriteByte(unsigned char dat); extern unsigned int DS18B20_GetTemperature(void); #endif这样做的好处是工程从AT89C52换到STC15系列时只需修改源文件里的引脚定义主程序调用接口完全不变。合集的目录本身就是检索表做测温项目查“18B20”相关实例做时钟项目查“DS1302”和“12864”做通信项目查“串口”和“RS485”几分钟就能找到对应工程的驱动文件再按自己的引脚分配改头文件。合并多个实例时注意两点reg51.h只包含一次所有sbit引脚定义集中在同一小文件管理。合并前把每个实例原有的main.c改名成功能模块文件删掉各自的main函数保留一个总入口调用各模块初始化函数。这套工作完成后你就拥有了一份属于自己的C51驱动库后续做蓝桥杯习题、课程设计或实际项目时都可以直接调用而不必每次从头写底层时序。本文还有配套的精品资源点击获取