STC15F104八脚单片机入门例程拆解:从点灯到串口实战
简介本资源是面向嵌入式初学者与硬件开发入门者的STC15F104E系列八脚单片机实践套件聚焦基础外设驱动与典型应用场景解决学习者缺乏可运行、可调试、可理解的最小系统例程问题。压缩包共144个文件含10个核心C源文件.c、10个Keil工程文件.uvproj、10个编译输出文件.hex、20个汇编列表.lst及启动代码等完整覆盖从编写、编译、烧录到调试的全流程所需文件总大小仅236KB轻量易用。已有1063人下载学习印证其作为入门跳板的实用价值。10个例程由浅入深从LED闪烁、流水灯、按键识别等数字I/O基础延伸至串口通信、外部中断响应、EEPROM掉电存储、定时器精准延时、PWM信号生成并在最后一个综合例程中实现四功能协同运行所有代码均含中文逐行注释便于理解寄存器配置逻辑与STC增强型8051架构特性。 STC15F104这个八脚单片机的入门例程我翻来覆去看了很多遍源码注释写得很细对新手来说算是非常友好的那一类。如果你下载过不少51单片机的工程包应该有过这种体验代码能跑但注释等于没写寄存器全靠猜一个引脚配置错了能折腾一下午。而这套STC15F104的例程包把10个常见功能从点灯到串口全串起来了代码注释详细到几乎每一行关键操作都有说明拿来当八脚51单片机的入门教材完全够用。这篇博文我就围绕这套例程里最值得关注的核心代码、硬件连接、烧录流程以及实际调试中容易踩的坑做一个完整的拆解。无论是刚接触单片机、手里正好有一块STC15F104小板子的新手还是想用这颗八脚芯片快速做点小项目的老手都可以把这里面的内容当作一份可以直接“抄作业”的参考。1. 先搞清楚STC15F104这颗八脚单片机到底能干啥1.1 为什么八脚51芯片值得拿来入门STC15F104是STC公司推出的一款8脚封装的8051内核单片机。你别看它只有8个引脚属于51系列里最“精简”的一档但它的内核可不是传统的老51而是1T架构说白了就是绝大多数指令一个时钟周期就能完成比传统的12T老51快了将近12倍。默认工作频率可以靠内部IRC时钟搞定不需要外接晶振这在小板子设计上能省下两个引脚和一堆外围元件。在开始写代码之前你得先建立“引脚不够用”的预期。八脚封装在PCB上看起来很简单但它能用的GPIO其实非常有限。以常见的STC15F104W SOP8封装为例引脚排列大致是这样的P3.2和P3.3承担外部中断输入P3.4和P3.5可以做定时器外部计数输入P3.0和P3.1复用为串口RXD和TXD另外还有VCC和GND。整颗芯片真正能随意操作的IO口实际上只有6个左右。如果你拿它跟STC89C52那种40脚芯片比资源差得很远但正因为引脚少学习时不需要面对一堆用不上的外设注意力可以全部集中在定时器、中断、串口这些最核心的知识点上。另外需要说明的是STC15F104并不是只有一种后缀比如STC15F104E、STC15F104W在引脚定义和内部资源上可能有细微差别。你在看例程时先对着数据手册确认一下自己手里芯片的具体型号和引脚排列不要默认所有8脚芯片都一样。这个习惯很重要我第一次用的时候没核对引脚结果把按键接到了芯片根本不存在的引脚上代码看了两小时都没找到问题。1.2 八脚芯片和传统51在开发思维上的差异如果你之前学过标准8051比如AT89C51或者STC89C52再转到STC15F104上有几个思路要调整。首先是时钟。传统51板子上几乎都有一个12MHz或者11.0592MHz的晶振程序里延时函数都是照着这个频率算的。STC15F104完全靠内部IRC在烧录时可以选5MHz、11.0592MHz、22.1184MHz等频率。例程默认用11.0592MHz是为了方便串口波特率计算因为11.0592MHz除以波特率能得到整数误差接近零。你拿到例程后第一件事不是改代码而是确认烧录软件里选择的IRC频率跟代码注释里写的一致否则延时和定时全部对不上。其次是IO模式。传统51的IO口上电后大多是准双向口直接能读能写。STC15系列则支持四种IO模式准双向口、开漏输出、推挽输出、高阻输入。例程里如果用了P3M0和P3M1这两个寄存器来配置引脚模式你不要觉得陌生这是STC15系列非常有用的特性。比如驱动LED用推挽输出比准双向口更亮读按键设置成高阻输入再配合外部上拉逻辑更稳定。代码注释里会写清楚每个引脚配置成了什么模式但我建议你自己动手改一改把某个引脚从准双向改成开漏观察一下现象变化这比背寄存器定义有用得多。最后是下载方式。STC15F104通过串口下载程序所以你得有一个USB转TTL模块把TXD、RXD、GND三根线接好然后利用STC-ISP软件烧录。跟老51相比它不需要专门的编程器上手成本低很多。2. 10个入门例程的整体设计思路2.1 例程由浅入深的编排逻辑这套例程包里不是零散地放10个不相关的测试程序它有一个比较清晰的递进关系。我按自己的理解把例程重新排了个序你照着这个顺序学会比压缩包里的顺序更顺畅序号例程功能核心知识点建议学习重点1LED闪烁GPIO输出、软件延时搞懂IO配置和延时原理2流水灯数组操作、循环移位用循环简化重复代码3按键控制LEDGPIO输入、按键消抖理解机械抖动和滤波思路4外部中断INT0/INT1、中断服务函数学会用中断代替轮询5定时器精确定时定时器初值计算、中断掌握定时器的本质是计数器6定时器实现软件PWM定时中断、IO翻转理解PWM占空比概念7串口发送数据UART初始化、波特率设置学会用串口输出调试信息8串口接收控制接收中断、命令解析掌握串口接收状态机初稿9低功耗模式IDLE模式、掉电唤醒了解低功耗设计的入门操作10看门狗应用溢出复位、喂狗机制明白死机自动恢复的思路这个顺序的逻辑是先点亮一颗灯让你确认开发环境通了再做流水灯理解代码如何控制多个IO然后加入输入让芯片能和外部交互再引入中断和定时器摆脱傻等式的延时最后通过串口、低功耗、看门狗把这些知识综合起来做一个能稳定运行的小系统。如果你之前只是零散地抄过几个例程我强烈建议你按照这个表格的顺序重新刷一遍。每做完一个例程脑子里的知识拼图就完整一块比单独看代码要清晰很多。2.2 注释详细到什么程度才算“适合入门”很多例程的注释只写“初始化定时器”“延时函数”这种废话说了等于没说。这套例程的注释风格要好得多它的注释大概分成三层。第一层是文件头注释会写明这个例程的功能、硬件连接方式、对应的芯片型号和时钟频率。这一层主要帮你确认“这个例程能不能直接用到我的板子上”。第二层是寄存器配置注释比如“AUXR | 0x80; // 定时器0设置为1T模式”。这层注释的关键是告诉你“为什么是0x80”而不是让你死记这个值它把寄存器位的含义点出来了。我看过很多新手拿到代码就复制完全不知道0x80是哪一位最后换一个定时器就懵了。只要注释把位含义写清楚你自己查手册也能配出同样的效果。第三层是逻辑注释用大白话描述这段程序在干什么比如“循环发送字符串直到发送完成标志位置1”。这类注释在串口相关的例程里特别重要因为UART的发送和接收涉及到硬件标志位新手很容易被这些标志卡住。判断一个例程包注释好不好其实有个简单的标准你把所有代码删掉只看注释能不能大致写出整个程序的流程。如果能那这套例程的注释就是合格的。这套包里的10个例程基本达到了这个水平。3. 核心例程代码逐段拆解3.1 闪烁灯让单片机先跑起来第一个例程无论是什么单片机几乎都是LED闪烁。别小看这个例程它能跑通说明你的开发环境、烧录工具、IO配置、延时函数全都没问题。STC15F104的LED闪烁代码核心长这样#include STC15F104W.h sbit LED P3^4; // 假设LED接在P3.4 void delay_ms(unsigned int ms) { unsigned int i, j; for(i 0; i ms; i) for(j 0; j 600; j); } void main() { P3M1 ~0x10; P3M0 | 0x10; // 将P3.4配置为推挽输出提高LED驱动能力 while(1) { LED 0; // 点亮LED假设低电平点亮 delay_ms(500); LED 1; // 熄灭LED delay_ms(500); } }这里的核心不是while循环而是P3M0和P3M1这两个寄存器。在STC15系列里每个IO口有四模式配置通过PxM0和PxM1两位组合控制。P3M1 ~0x10; P3M0 | 0x10;这组操作把P3.4设成了推挽输出好处是灌电流能力比准双向口强LED会更亮。如果你用的是准双向口LED也能亮但亮度会有可见的差别。要注意的是不同板子上LED的接法可能不一样。有的板子是低电平点亮有的是高电平点亮如果你发现灯不亮第一件事就是检查LED是接到了VCC还是GND。这个信息在代码注释里一般会写清楚比如“LED一端接P3.4一端接GND”对应的代码就是LED 1灭、LED 0亮。另外delay_ms里那个600是经验值不是算出来的。因为STC15F104在11.0592MHz下执行一个空循环大约若干微秒这个数值在不同编译优化级别下都会变化。例程中直接写600是因为这个参数用来做演示足够了。如果做精确延时后面会给定时器方案。3.2 定时器实现精确延时初值到底怎么算软件延时的最大问题是占用CPU而且时间不精确。从第五个例程开始代码就切换到定时器延时了。定时器的本质是一个计数器它数的是机器周期数到溢出就触发中断。STC15F104有定时器0和定时器1例程里用定时器0做1ms定时中断代码核心如下void Timer0_Init(void) { AUXR ~0x80; // 定时器0为12T模式 TMOD 0xF0; // 清空定时器0的模式位 TMOD | 0x01; // 定时器0工作在16位定时模式 TH0 0xFC; // 高8位初值 TL0 0x66; // 低8位初值 ET0 1; // 使能定时器0中断 EA 1; // 打开总中断 TR0 1; // 启动定时器0 }这里最关键的是TH0 0xFC; TL0 0x66;这两个初值是怎么来的。以11.0592MHz主频、12T模式为例一个机器周期等于12个时钟周期所以定时器每计数一次需要的时间是12 / 11059200秒约等于1.085微秒。如果要定时1ms需要的计数次数就是0.001 / 0.000001085算出来约等于921.6次。16位定时器从0数到65535溢出所以初值应该是65536 - 922 64614转成十六进制就是0xFC66。我把这个计算过程拆成这样来看机器周期 12 ÷ 11059200 ≈ 1.085 微秒1ms需要的计数次数 1000 ÷ 1.085 ≈ 922次定时器初值 65536 - 922 64614 0xFC66注意这里算出来的是准确的“整数部分”但922次实际对应的定时时间是922 × 1.085 1000.37微秒有一点误差但完全够用。如果你非要做到零误差可以换成11.0592MHz下重新计算其他的分频组合或者直接改成1T模式后用其他主频但入门阶段没必要纠结这0.4%的误差。定时器初值计算是很多新手的第一道坎我的建议是你别直接抄0xFC66而是自己拿计算器算一遍然后改成一个不同的定时时间比如5ms重新算初值。算完之后再对照例程里的值你会发现规律一下子就通了。3.3 外部中断与按键消抖别小看那10ms延迟按键例程在入门套装里必不可少但真正写得好的按键例程会把消抖讲透。机械按键在按下和松开的瞬间由于弹性接触会产生一系列不稳定的脉冲如果不做处理一次按下可能被识别成多次触发。最简单的消抖办法是检测到电平变化后延时10到20ms再去读一次电平如果电平稳定了才认为按键真正按下。外部中断例程和按键例程一般是放在一起学的。STC15F104的P3.2对应INT0P3.3对应INT1配置好中断以后按键按下的下降沿会直接触发中断不用在主循环里反复查询。void INT0_Init(void) { IT0 1; // 设置INT0为下降沿触发 EX0 1; // 使能INT0中断 EA 1; // 开启总中断 } void INT0_ISR(void) interrupt 0 { delay_ms(10); // 消抖 if(INT0 0) // 确认按键确实被按下 { LED !LED; // 翻转LED状态 } }但这里有一个很多教程没那么强调的坑中断服务函数里尽量别做太多事尤其别放长延时。为什么因为中断服务函数执行期间主程序是停在那里的如果你在中断里延时20ms那这段时间里其他中断和主流程都无法响应。上面这个例程因为只处理一个按键和一个LED问题不大但在真实项目中中断里应该只做一个标志位把真正的逻辑放到主循环里处理。比如这样bit key_flag 0; void INT0_ISR(void) interrupt 0 { key_flag 1; // 中断里只置标志位 } void main() { while(1) { if(key_flag) { key_flag 0; LED !LED; } } }这种“中断置位、主循环处理”的思路贯穿整个嵌入式开发例程越往后越能体现这一点。你如果能在入门阶段就养成这个习惯后面做串口接收、多任务处理时会省很多麻烦。3.4 串口通信调试神器也是格式坑重灾区STC15F104的串口例程非常实用因为芯片引脚少没有仿真器的话串口几乎是唯一的调试输出通道。例程里用定时器2作为波特率发生器这是STC15系列常用的做法。直接上代码void UART_Init(void) { S2CON 0x50; // 串口2配置为8位可变波特率允许接收 T2L 0xE8; // 波特率初值低8位 T2H 0xFF; // 波特率初值高8位 AUXR | 0x04; // 定时器2开始运行 } void UART_SendByte(unsigned char dat) { S2BUF dat; // 把数据写入发送缓冲 while(!(S2CON 0x02)); // 等待发送完成标志 S2CON ~0x02; // 清除发送完成标志 }很多人在这个例程里遇到的第一个问题是为什么S2CON 0x500x50二进制是0101 0000其中bit4置1用来使能接收bit6为1表示串口模式18位UART。你只需要把S2CON当成一个开关组合每一位代表一个功能开关这样理解就比死记0x50轻松很多。第二个坑是波特率初值的计算。定时器2作为波特率发生器时初值计算公式在不同模式下不太一样。例程里给的0xFFE8对应的是11.0592MHz主频、9600波特率。我可以分享一个偷懒的办法STC-ISP软件自带一个波特率计算工具你输入主频和目标波特率它会把定时器初值直接算给你。不过我更建议你手动算一遍公式在数据手册里写得很清楚算过一次之后才能真正理解为什么11.0592MHz是“串口友好”的频率。收到串口发来的字符后接收中断会把数据存到缓冲区主循环再解析。这里有个特别常见的坑串口助手发一个字符串比如“hello”实际会一个字符一个字符地进入接收中断如果你在每个中断里只处理一个字符很容易丢失数据。例程里如果只演示了单字符接收那也不要急这是合理的入门编排。等你理解了单字符再去看多字节接收的状态机代码就顺理成章了。3.5 软件PWM和低功耗八脚芯片也能做呼吸灯严格说STC15F104没有硬件PWM模块所以例程里的PWM是用定时器中断模拟的。基本原理是用定时器产生一个固定频率的中断比如每100微秒中断一次在中断里控制一个变量这个变量决定了LED一个周期内高电平的时间占比也就是占空比。代码结构大致是unsigned char pwm_duty 50; // 占空比范围0~100 unsigned char pwm_cnt 0; void Timer0_ISR(void) interrupt 1 { pwm_cnt; if(pwm_cnt 100) pwm_cnt 0; if(pwm_cnt pwm_duty) LED 1; else LED 0; }这里每100次中断完成一个PWM周期占空比由pwm_duty控制。如果你把这个值从0逐渐变到100LED就会呈现呼吸灯的效果。软件PWM的优点是实现简单缺点是中断频率越高、周期数越大CPU占用就越高所以不适合做高精度控制。但作为入门理解PWM概念完全够了。低功耗例程讲的是PCON寄存器IDLE模式用PCON | 0x01;掉电模式用PCON | 0x02;。进入掉电模式后芯片的CPU和大部分外设都停止工作电流可以降到很低靠外部中断唤醒。这个例程适合做电池供电的小设备。调试时有个注意事项进入掉电模式前最好先把看门狗关掉否则看门狗会定时把芯片唤醒复位导致你误以为程序出了bug。4. 开发环境搭建与烧录实操4.1 八脚芯片最小系统怎么搭STC15F104的最小系统非常简单因为它内部已经有复位电路和时钟源。你只需要三样东西电源、下载接口、目标外设。常见接法是这样的VCC接3.3V或5VGND接GNDP3.0和P3.1分别接USB转TTL模块的RXD和TXD注意要交叉连接即芯片P3.0(TXD)接模块的RXD芯片P3.1(RXD)接模块的TXDGND要和USB转TTL模块共地这里有个基础但重要的点共地。如果不共地发送和接收就缺乏统一的电压参考点数据就会出现乱码或者完全收不到。我见过很多新手烧录失败不是接线错了而是忘了把两个GND连在一起。下载时STC单片机要求“冷启动”也就是先点击STC-ISP软件里的“下载/编程”按钮然后再给单片机重新上电。这个设计是为了让芯片在刚上电时检测到特定的下载命令才进入编程模式。所以你的板子最好装一个电源开关每次下载程序就断电、点下载、再上电。4.2 STC-ISP软件里最容易设置错的两个选项STC-ISP是STC官方提供的烧录软件界面虽然比较朴素但功能很全。第一次使用的时候有两个地方需要特别注意。第一个是芯片型号选择。一定不要只搜“STC15F104”就选了注意后缀比如STC15F104W和STC15F104E在软件里是分开的选错型号会导致代码下载进去后运行不正常或者干脆烧录失败。第二个是IRC频率选择。在“硬件选项”里你要选择与代码匹配的内部IRC频率比如代码注释里写了#define FOSC 11059200L烧录时就把IRC频率选成11.0592MHz。如果这里选的是5MHz代码里的定时器初值全部按11.0592MHz算的结果就会差两倍多最直观的表现就是LED闪烁变慢或者串口乱码。另外STC15系列有一种脱机烧录方式可以把程序先下载到专用的脱机烧录器里再通过脱机烧录器批量刷芯片。这个功能我建议暂时不用管等以后做小批量生产时再研究。5. 我踩过的坑常见问题速查与排查实录5.1 烧录失败、提示“仍在连接中请给MCU上电”这可能是入门STC15系列遇到最多的问题。出现这个提示说明USB转TTL模块没有正常和芯片握手。排查顺序我整理成了表格现象可能原因解决办法提示“仍在连接中”接线接反确认P3.0接模块RXDP3.1接模块TXD提示“仍在连接中”没有共地把模块GND和板子GND连在一起提示“仍在连接中”没有冷启动点下载后给板子重新上电提示“仍在连接中”模块供电能力不足板子用独立电源不要只靠USB模块供电已经进入下载流程但校验失败IO口被外部设备占用下载时把P3.0和P3.1外接的负载断开其中“P3.0和P3.1外接负载”是个很隐蔽的问题。如果板子上P3.0和P3.1接了其他外设比如一个对地电阻很小的LED会影响下载通信导致校验失败。解决办法是下载时断开这些外设或者把对应的跳线帽拔掉。5.2 IO配置错误导致LED不亮或按键失灵STC15系列四个IO模式这个问题在例程注释里已经写了但我还是想强调一下如果你发现LED该亮不亮或者亮度明显不对先检查P3M0和P3M1。推挽输出和准双向口的区别在实际电路里非常明显。另外按键输入那一路如果配置成了推挽输出你就根本读不到外部电平变化了因为引脚被内部强行拉高或拉低了。我习惯的配置方法是LED用推挽输出按键用高阻输入加外部上拉继电器等负载用开漏输出接外部上拉。这个组合在大多数八脚小项目里都能稳定工作。5.3 定时时间不准误差很大定时时间不准首先要排查时钟频率。如果烧录时选的IRC频率是5MHz代码里却按11.0592MHz算初值定时时间会差一倍多。其次排查定时器分频AUXR寄存器那个1T/12T位千万别设错同样的初值1T模式下时间会快12倍。最后才是初值计算本身的问题。我建议你在调试阶段用串口打印一个周期性的标识实际量一下时间间隔如果有万用表或示波器就更好。没有仪器的话用手机秒表掐一下LED整十次的翻转时间也能大致判断误差方向。5.4 芯片发烫或者引脚一摸就复位芯片发烫大概率是某个IO口推挽输出直接接地或者两个推挽输出脚互相短接了。STC15系列的推挽输出驱动能力比较强短路电流不小芯片能不烫吗。如果出现这个情况赶紧断电查一下电路不要长时间带电排查。引脚一摸就复位多数是复位引脚的问题。STC15F104的部分封装会把复位功能映射到特定引脚上如果你在例程里没用到的引脚悬空了或者手动接触时引入干扰芯片就可能复位。解决方法是检查代码是否关闭了外部复位功能或者把不用的引脚配置成IO模式并接地或接上拉避免悬空。5.5 串口收到乱码串口乱码按优先级排查三件事。一是波特率电脑端和芯片端要一致而且主频要对二是共地没有共地就会乱码三是电平如果你的USB转TTL模块是3.3V的芯片却跑在5V两侧逻辑电平不一致可能收不到或者收到错误数据需要通过电平转换模块对接。例程注释里一般会写清楚串口助手的参数比如“波特率9600数据位8停止位1无校验”你照着设就行。最后分享一点我的使用体会这套STC15F104八脚单片机的10个入门例程我前前后后带着不少新手跑过最大的感受是它把学习曲线切得很碎每一步都有反馈能让你建立起“写代码、烧录、观察现象”的正循环。八脚芯片虽然资源少但正因为少你才不会被复杂外设分散注意力能把中断、定时器、串口这些最核心的机制吃透。我个人在实际操作中比较建议的做法是每跑通一个例程就在原代码上改一个参数比如把定时时间从1ms改成3ms把波特率从9600改成115200把按键触发方式从下降沿改成低电平触发然后观察现象是否符合预期。这比单纯抄一遍例程收获大得多。等你把这10个例程全部改过一遍再回头看那些资源更多、功能更复杂的单片机就会发现基础的东西其实都是相通的。本文还有配套的精品资源点击获取