☰
51单片机花样流水灯工程:模块化编程与定时器中断实战
2026/10/21 3:21:13 网站建设 项目流程

简介:一份面向51单片机初学者的花样流水灯C语言工程,基于89C51芯片,演示了I/O口控制LED、定时器/计数器、延时函数、移位操作等核心编程技巧,适合嵌入式入门、课程设计及电子竞赛训练场景。资源包共5个文件,约21KB,含C源码、HEX烧录文件、Proteus仿真工程(DSN)、说明文档和工程调试文件,可对照实现从编写、编译到仿真的完整流程。已有7867人下载学习。通过阅读源码和仿真,学习者能够掌握Keil环境下的编译烧录方法,理解89C51的并行I/O端口和定时器中断在动态显示中的应用,并能在Proteus中直观验证多种流水灯花样;同时该工程提供可修改的C代码,便于二次开发或调整灯光节奏,能帮助读者快速积累单片机项目实战经验。

1. 花样流水灯代码包:把“点灯”从入门题做成可扩展的工程模板

51单片机的花样流水灯,听起来像是最基础的入门练习,但拿到这套代码之后我发现它并不只是“让LED轮流亮灭”的玩具。整个工程把数据驱动的花样表、定时器中断驱动、按键状态扫描和模式切换都串在了一起,跑起来之后你可以看到十几种完全不重样的流水灯效果,而且每种效果之间还能通过按键实时切换。对刚接触嵌入式开发的人来说,它是最好的“第一个完整工程”;对已经写过裸机代码的人来说,它又是一份可以拿去改造成智能灯控、呼吸灯、音乐频谱灯的基础模板。这套代码解决的不是怎么点亮一颗灯,而是怎么用工程化的方式去组织一个单片机应用,让花样流水灯从“能亮”变成“可控、可改、可扩展”。

2. 开发环境与硬件基础:目录结构、芯片选型和最小可运行系统

2.1 代码包目录结构与工程组成

先看整个代码包的目录组织。解压之后,里面是按照标准Keil工程结构拆分好的模块,源码和编译产物分开存放,方便直接打开编译:

51_led_demo/ ├── User/ │ ├── main.c // 主函数入口,模块初始化与主循环 │ ├── delay.c / delay.h // 软件延时函数(毫秒级) │ ├── timer.c / timer.h // 定时器初始化与中断服务函数 │ ├── pattern.c / pattern.h// 花样表数据定义与索引管理 │ └── key.c / key.h // 独立按键扫描与消抖状态机 ├── Obj/ // Keil编译输出目录,生成的hex文件在这里 └── LED.uvprojx // Keil工程文件,双击即可打开

main.c负责把各个模块串起来,它是整个程序的总指挥。上电后先调用timer0_init配置定时器0为50ms中断,然后把P1口复位成全部熄灭状态,最后进入一个死循环,循环里只做两件事:扫描按键、根据返回值决定是否切换当前花样。实际的LED刷新动作全部放在中断服务函数里完成,主循环不直接操作P1口。

这样的分层思路和做嵌入式产品是一样的:主循环越空越好,实时性要求高的动作放到中断里,实时性要求低的轮询放到主循环。新手写代码最容易犯的错就是把所有逻辑都塞进while循环里,导致CPU被delay占满,按键响应迟钝,这个工程的结构可以作为模块化裸机编程的一个标准参考。

2.2 芯片选型与最小硬件系统

这套代码的目标芯片是STC89C52RC,搭配12MHz外部晶振,是51单片机入门用的主流芯片,市面上绝大多数51开发板都用这颗芯片。硬件上整套系统只需要五部分:

  • STC89C52RC芯片本体,40引脚DIP封装或贴片封装均可
  • 12MHz晶振,配合两个22pF负载电容组成时钟电路,给CPU提供工作时钟
  • 复位电路,用10uF电解电容加10k电阻的标准RC复位,上电自动复位
  • 8个LED灯接P1口,每个LED串一个330Ω限流电阻到VCC,代码里P1输出低电平点亮LED,即共阳接法,这也符合大多数开发板板载LED的实际电路
  • 两个独立按键接P3.2和P3.3引脚,按下为低电平,上拉由开发板板载电阻完成

这是标准的最小系统加外设结构,任何51核心板都能直接套用。如果你用的是常见型号的开发板,直接对应板载LED和按键接口即可,不需要改动硬件。P1口是整个系统的输出核心,8个引脚正好对应8个LED,代码里所有花样数据最终都通过P1口输出。

2.3 工程配置与编译链路

用Keil C51打开LED.uvprojx工程文件之后,有几个关键配置项需要手动确认,否则编译出来的程序行为和预期会不一样:

  • Target标签页的晶振频率填12MHz,这影响软件延时函数的执行时间,填错会导致所有时序偏移
  • Output标签页勾选Create HEX File,这样编译后才生成可烧录的hex文件,否则只生成调试用的axf文件
  • Memory Model选Small模式,本工程代码量小,变量最多几十个字节,Small模式完全够用,不需要Large模式浪费空间

编译成功后,在Obj目录下会生成LED.hex文件。整个编译过程在Keil C51环境下只需要点一次编译按钮,如果代码没有改动,编译时间不会超过十秒。值得注意的是Keil的C51编译器和MDK的Arm编译器是两个独立产品,安装时要确认装的是C51版本,否则打开工程会直接报找不到头文件。

3. 核心模块拆解:从花样表数据到定时器中断的完整点灯链路

3.1 花样表的设计:数据驱动代替硬编码

这套代码最值得看的设计是花样的组织方式。它没有用if-else逐个枚举每种效果,而是维护了一张“数据表”。每一种花样就是一个长度为8的数组,数组的每个元素对应一个时间片上8个LED的亮灭状态,播放时按顺序读出每一帧刷新到P1口。

// pattern.h #ifndef __PATTERN_H__ #define __PATTERN_H__ #define PATTERN_COUNT 12 // 花样总数,新增花样时修改这里 #define FRAME_COUNT 8 // 每个花样的帧数,一个花样8个状态 extern unsigned char code pattern_table[PATTERN_COUNT][FRAME_COUNT]; extern unsigned char current_pattern; // 当前正在播放的花样编号 #endif
// pattern.c #include "pattern.h" // 花样表:每一行是一种花样,每个字节是某一时刻8个LED的亮灭状态 // 1表示LED亮,0表示灭,P1口输出低电平点亮(共阳接法) unsigned char code pattern_table[PATTERN_COUNT][FRAME_COUNT] = { // 花样0:从左到右依次点亮一轮 {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}, // 花样1:从右到左依次点亮一轮 {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}, // 花样2:两边向中间靠拢 {0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x81}, // 花样3:中间向两边扩散 {0x18, 0x24, 0x42, 0x81, 0x81, 0x42, 0x24, 0x18}, // 花样4:双灯右跑马 {0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x80}, // 花样5:二进制递增流水 {0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF}, // 花样6:二进制递减流水 {0xFF, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01}, // 花样7:单灯左右往返跑 {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}, // 花样8:全亮全灭交替闪烁 {0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF}, // 花样9:三灯亮起向右流动 {0x07, 0x0E, 0x1C, 0x38, 0x70, 0xE0, 0xC0, 0x80}, // 花样10:间隔亮灯交替 {0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55}, // 花样11:逐点累积亮起再逐点熄灭 {0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0x00}, };

参数说明:0x01等于二进制0000_0001,表示只有D1(P1.0)亮;0x81等于1000_0001,表示D1和D8(P1.0和P1.7)同时亮。code关键字表示数组存放在程序存储器Flash里而不是RAM里,这是51单片机很关键的优化手段——STC89C52的RAM总共只有128字节,而Flash有8K,把只读数据放Flash能省出大量RAM空间给运行时变量使用。

这段代码的巧妙之处在于,往花样表里加新花样完全不需要改任何逻辑代码。想加一种新效果,在二维数组里追加一行数据,然后把PATTERN_COUNT改为新总数,编译烧录,新花样就自动接入播放序列。数据与逻辑分离,在嵌入式C里虽然简单,但很多人写流水灯时习惯把十几段逻辑硬编码到switch-case里,加一种效果就要改一段逻辑,还容易把时序搞乱,这套代码的做法值得借鉴。

3.2 定时器中断:点灯节奏的“心跳”

流水灯的灵魂是“时序”。用delay实现时序简单但不可靠,delay期间CPU被完全占用,按键扫描会失灵,而且多个delay嵌套时很容易把逻辑绕晕。这套代码的处理方式是让定时器0产生50ms中断作为“心跳”,中断服务函数里根据当前计数决定是否推进到下一帧,这样LED刷新和按键扫描互不干扰。

// timer.c #include <reg52.h> #include "timer.h" #include "pattern.h" // 全局帧计数器:每个花样播放完会自动从头循环 volatile unsigned char frame_index = 0; void timer0_init(void) { TMOD &= 0xF0; // 清空定时器0的配置位,不影响定时器1 TMOD |= 0x01; // 定时器0设为方式1(16位手动重装) TH0 = 0x3C; // 12MHz晶振,50ms定时初值高8位 TL0 = 0xB0; // 50ms定时初值低8位 ET0 = 1; // 使能定时器0中断 EA = 1; // 打开总中断开关 TR0 = 1; // 启动定时器0 } void timer0_isr(void) interrupt 1 { static unsigned int tick = 0; TH0 = 0x3C; // 中断内重新装入初值,保证下次定时准确 TL0 = 0xB0; if (++tick >= 10) { // 10个中断周期 = 500ms,每个花样每帧停留500ms tick = 0; frame_index++; if (frame_index >= FRAME_COUNT) { frame_index = 0; // 一帧8个状态播完,回到开头循环 } P1 = pattern_table[current_pattern][frame_index]; // 刷新LED状态 } }

逻辑说明:定时器0工作在方式1,即16位定时器。12MHz晶振下,每个机器周期为1us,计数器每1us加1一次,要定时50ms需要计数50000次,所以初值是65536减50000等于15536,换算成十六进制就是0x3CB0,高8位0x3C装入TH0,低8位0xB0装入TL0。中断服务函数里每进入10次才刷新一次LED,正好500ms切换一帧,8个状态播完一轮刚好4秒,肉眼看起来节奏很舒服,不会太快也不会拖沓。

重装初值的代码放在中断开始位置而不是结束位置,这是为了尽量避免因中断内指令执行时间差异导致的定时漂移。如果把重装放在函数尾部,add、mov这些指令本身会消耗几个机器周期,长期累积会让定时偏差越来越大。另外frame_index被声明为volatile,因为它在中断里被修改、在主循环和中断里都被读取,编译器如果不加volatile可能把它优化进寄存器里,导致主循环读到脏数据,这是嵌入式新手最容易忽略的细节。

3.3 按键与模式切换:轮询扫描的状态机

主循环里按键扫描采用“按下检测、延时消抖、确认有效、等待释放”的常规流程,但代码里把它写成了三态状态机,避免按下一次却切换多次花样的问题。

// key.c #include <reg52.h> #include "key.h" #include "pattern.h" // 按键定义:KEY1接P3.2,KEY2接P3.3 sbit KEY1 = P3^2; sbit KEY2 = P3^3; void delay_ms(unsigned int ms) { unsigned int i, j; for (i = 0; i < ms; i++) for (j = 0; j < 114; j++); // 12MHz下内循环约1ms } unsigned char scan_key(void) { static unsigned char key_state = 0; // 0=释放态 1=消抖态 2=按下态 switch (key_state) { case 0: // 释放态:检测到低电平就进入消抖 if (KEY1 == 0) { key_state = 1; } break; case 1: // 消抖态:延时后再确认是否真的按下 delay_ms(20); if (KEY1 == 0) { key_state = 2; // 确认按下,进入等待释放态 return 1; // 返回有效按键事件 } else { key_state = 0; // 抖动信号,不是真正按下 } break; case 2: // 按下态:等待释放,避免长按期间反复触发 if (KEY1 == 1) { key_state = 0; } break; } return 0; }

主循环里调用扫描函数,返回1就切换到下一种花样,逻辑简洁清晰:

// main.c #include <reg52.h> #include "timer.h" #include "pattern.h" #include "key.h" void main(void) { unsigned char key_val; current_pattern = 0; // 默认从第一种花样开始 P1 = 0x00; // 上电全部熄灭 timer0_init(); // 启动定时器中断 while (1) { key_val = scan_key(); // 轮询按键 if (key_val == 1) { current_pattern++; if (current_pattern >= PATTERN_COUNT) { current_pattern = 0; // 播完最后一种回到第一种 } frame_index = 0; // 切换花样时从头开始播放 } } }

这里有个细节值得专门说:切换花样时把frame_index清零而不是保留原值,否则切过去的时候会从原来的半截帧开始放,观感上就是“跳了一下”。这个细节是我调了几次才注意到的,不处理的话每次按键切换花样都会感觉LED状态不连贯。

参数说明:P1 = 0x00在共阳接法下是8个LED全部熄灭。TMOD &= 0xF0保证只修改定时器0的配置位,不影响定时器1,如果以后要用定时器1做串口波特率发生器,就不会被这段初始化误伤。主循环只做按键扫描,其余事情全部交给中断,这是裸机编程里比较合理的任务分工,也是这套代码整体架构的精华所在。

4. 编译烧录与实物验证:从代码到板子的完整流程

4.1 创建Keil工程与配置路径

拿到代码后,第一步不是急着打开看代码,而是先把工程放到合适的位置。整个文件夹放到纯英文路径下,比如D:\51_led_demo或/home/user/51_led_demo,不要带中文,否则Keil C51的旧版编译链偶尔会报奇怪的路径错误。

打开Keil之后按以下几个步骤操作:

  1. Project菜单选择Open Project,找到LED.uvprojx双击打开
  2. 确认Device选的是STC89C52RC或兼容型号。如果器件列表里没有STC选项,选AT89C52也可以,两者寄存器定义完全兼容,烧录时按实际芯片型号操作
  3. 打开Option for Target对话框,在Target标签页把晶振频率改成12MHz,频率值只影响调试器的模拟时序,但养成填对的好习惯
  4. 切到Output标签页,勾选Create HEX File,这是烧录前最容易漏掉的一步
  5. 点击编译按钮(或按F7),编译信息栏输出compiling main.c...然后显示0 Error(s), 0 Warning(s)即表示编译成功

常见的编译报错有两个。提示ERROR L104: MULTIPLE PUBLIC DEFINITIONS说明某个函数在头文件里写了实现并且被多个.c文件包含,导致重复定义,解决方法是把实现挪回.c文件,头文件里只留extern声明。提示C138: 'code': space is insufficient说明程序存储器空间超了,检查花样表是否定义得过大,STC89C52的Flash是8K,这张12行乘8列的表只占不到100字节,正常不会触发这个错误。

4.2 烧录与实物验证步骤

编译通过后,在Obj目录下找到LED.hex文件,接下来接硬件烧录,整个流程只需要四步:

  1. 用USB转TTL模块连接单片机的串口引脚,TXD接RXD、RXD接TXD、GND接GND,三条线缺一不可
  2. 给开发板上电之前,先打开STC-ISP烧录软件,加载LED.hex文件,选择单片机型号STC89C52RC
  3. 点击Download按钮,软件提示“正在检测目标单片机”后,此时再给开发板上电
  4. 烧录完成后按一下复位键,LED就会从第一种花样开始流动

这里有个非常关键的时序问题:STC系列单片机是上电冷启动进ISP引导模式,必须先点下载、后上电。很多人第一次烧STC片子时习惯先上电再点下载,软件一直提示检测不到单片机,其实不是串口接错,是芯片已经运行在用户程序里了,ISP引导程序没有机会接管。反过来,先点下载(此时单片机没上电),再上电的瞬间,芯片内部的引导程序会先运行一小段时间,这时候就能正常握手通信,把hex文件写入Flash。

烧录完成后可以验证两个功能点。第一是花样自动循环:上电后LED顺序执行当前花样,8帧播完自动从头开始,无限循环。第二是按键切换:在LED跑花样的过程中按下KEY1,当前花样立即切到下一种,并且从第一帧重新开始播放。连续按键可以在12种花样之间循环切换。如果按下按键没反应,优先检查P3.2引脚是否有接触不良,用万用表测按下对地电压是否到0V。

5. 常见问题与避坑:点灯路上的五个坑

5.1 编译报错:找不到reg52.h头文件

现象:打开工程直接编译,控制台输出fatal error C129: missing 'reg52.h',整个工程无法编译。

原因:Keil安装目录下没有包含51系列头文件。大概率情况是电脑上装的是Keil MDK(Arm版),MDK只带ARM内核的芯片支持包,不带C51编译器,自然也没有reg52.h这个头文件。

解决:C51和MDK是两个独立产品,不能混用,需要安装Keil C51版本。安装完成后头文件位于C:\Keil_v5\C51\INC\目录,工程编译时会自动去这个目录找标准头文件。如果已经装了多个版本Keil,在Options菜单里检查Define和Include Paths是否正确指向C51的INC目录。

5.2 晶振频率和定时器初值不匹配导致时序错乱

现象:编译烧录全部成功,但LED切换帧的速度极快,眼睛完全跟不上,流水效果变成一片乱闪,根本看不出花样。

原因:代码里的定时器初值是按12MHz晶振计算的,但目标板实际用的可能是11.0592MHz晶振(很多开发板为了串口波特率精准用11.0592MHz)。频率变了,初值还是原来的,实际定时时间就完全对不上,11.0592MHz下同样初值的定时时间约54ms,和预期的50ms偏差不大,但如果Target配置里写了另外的值,或者宏定义里混用了两个频率,就会出现明显的时序混乱。

解决:确认开发板上的晶振型号,看丝印或者用频率计测一下。然后把初值计算改成宏定义方式适配不同晶振:

// timer.c 顶部加入宏定义,换晶振只改这一行 #define FOSC 12000000UL // 改成你的实际晶振频率,单位Hz #define TIMER_50MS_RELOAD (65536UL - ((FOSC / 1000UL) * 50UL / 12UL)) void timer0_init(void) { TMOD &= 0xF0; TMOD |= 0x01; TH0 = (unsigned char)(TIMER_50MS_RELOAD >> 8); TL0 = (unsigned char)(TIMER_50MS_RELOAD & 0xFF); ET0 = 1; EA = 1; TR0 = 1; }

这里宏计算原理是机器周期等于12除以晶振频率,50ms需要的计数次数等于频率除以1000乘50再除以12。换成11.0592MHz晶振时,宏会自动算出新的初值,不需要手工改TH0和TL0两行代码。

5.3 LED亮度不均,同时点亮多颗时整体变暗

现象:花样跑起来后,凡是两个LED同时亮的状态(比如0x81表示D1和D8同时亮),所有点亮的LED亮度都比单颗亮时暗,而且越靠近P1口高位的灯越明显。

原因:51单片机的I/O口输出驱动能力有限,P1口同时点亮多个LED时,每个LED需要约5mA到10mA电流,多个LED叠加后总电流被拉高,I/O口内部上拉结构的压降增大,输出高电平的实际电压被拉低,导致LED变暗。共阳接法下这个问题更明显,因为电流是从VCC经过LED流进I/O口,I/O口负责灌电流,灌电流能力通常比拉电流弱。

解决:每个LED必须单独串一个限流电阻,不能为了省电阻把多个LED接到同一个电阻上。如果板子上LED较多,把8个灯拆到P1和P2两个口,每个口驱动4个灯,分担电流压力。还可以把限流电阻从330Ω换成470Ω,降低单灯电流,代价是整体亮度会低一些,但均匀度会改善。更彻底的方案是加一片ULN2003达林顿管驱动阵列,用I/O口控制达林顿管再去驱动LED,和驱动电流分离,这是工业产品上常见的做法。

5.4 按键按一下切换了两种花样

现象:按下KEY1后,花样跳了两个,也就是连续切换了两次,看起来像按键灵敏度太高,实际上切换次数对不上按下的次数。

原因:按键状态机在消抖时用了20ms软件延时,但delay_ms函数本身精度一般。更关键的是,延时期间系统中断仍然在跑,中断会打断延时的for循环,导致实际消抖时间被拉长,抽样点落在按键抖动区域的边界处,某个随机抖动信号被误判成了一次有效按下。

解决:把消抖延时从20ms提到30ms,同时在等待释放状态也加一段短消抖。释放瞬间同样有抖动,不加处理的话松手时的抖动信号可能又触发一次消抖流程:

case 2: // 按下态:等待释放 if (KEY1 == 1) { delay_ms(10); // 释放也做一次消抖,过滤释放抖动 key_state = 0; } break;

如果问题仍然存在,换成更可靠的扫描式消抖:在20ms内连续采样8次,每次间隔约1ms,8次全部为低电平才确认按键按下。这种滤波方式对机械抖动有更强的抑制能力,代价是多几行代码,适合在按键信号比较脏的场合下使用。

5.5 烧录提示下载失败但串口连线确实无误

现象:STC-ISP软件提示“硬件不存在”或“下载失败”,但用串口助手能正常点亮USB转TTL模块上的收发指示灯,RX和TX也确认交换过了,接线没有明显错误。

原因:STC单片机进入ISP模式要求上电时序,先点下载后上电这个点前面提过。另一个常见原因是电平不匹配,USB转TTL模块如果输出3.3V电平,而STC89C52是5V器件,3.3V的高电平可能达不到芯片识别逻辑高电平的门限,导致通信不稳定或完全失败。还有可能是USB转TTL模块和单片机之间没有共地,GND没有接在一起,串口电平没有参考地,必然无法通信。

解决:确认USB转TTL模块标称支持5V电平,如果只有3.3V输出,在TXD脚加一个三极管做电平转换,或者换一块5V的模块。硬性要求是GND必须与单片机GND接在一起,这是串口通信的基础。部分STC型号下载时还需要把RST脚拉高再复位,具体操作看对应芯片的数据手册介绍的下载电路。

这些坑都是实际调试中踩过的,特别是晶振频率不匹配和烧录时序这两个,几乎每个新手都要栽一次。代码本身出问题的概率远小于环境配置和硬件理解出问题的概率,遇到异常先查硬件和配置,再查代码逻辑,能省很多排查时间。

6. 二次开发思路:把固定花样改成可配置的智能灯效

拿到这套代码之后,最有价值的改造方向不是继续往花样表里加行数,而是把整个“花样的播放”从静态数据升级成可动态配置的机制,让流水灯具备更接近产品的智能灯效能力。

第一个改造方向是引入花样结构体,把帧数据、帧数和帧间隔绑定在一起。当前这套代码所有花样共用一个帧间隔500ms,但实际效果里,跑马灯适合快节奏、呼吸效果适合慢节奏,不同花样的最佳帧间隔差异很大。可以定义结构体类型,每个花样带自己的帧间隔和循环次数:

typedef struct { unsigned char *frames; // 指向帧数据数组的指针 unsigned char frame_cnt; // 帧数 unsigned int frame_int; // 每帧间隔,单位ms unsigned char loop_cnt; // 循环次数,0表示无限循环 } PatternDef;

主循环根据loop_cnt字段决定当前花样播完是否自动进入下一种,做一个播放调度器,这样就实现了“先跑马三次再两边聚拢三秒再闪烁”这样的组合效果。这段改造代码量不大,但理解起来需要先熟悉指针和结构体,适合有一定C基础的开发者。

第二个改造方向是加PWM呼吸灯效果。呼吸灯的本质是LED亮度随时间渐变,裸机环境下用定时器中断做软件PWM,把中断频率提高到20kHz,在中断里维护一个占空比计数器和方向标志。占空比从0渐变到100再回0,渐变步长决定呼吸快慢,40个步进值从头到尾走一遍大约需要2秒,就有很自然的呼吸感。

第三个改造方向是按键改成外部中断。当前轮询扫描够用,但主循环每次都要跑一遍扫描函数,CPU的空闲时间被浪费。把P3.2配置成INT0下降沿触发的外部中断,按下时进中断置一个标记位,主循环只检测标记位再切换花样,主循环可以彻底释放出来,为后续加数码管显示或串口通信留下余量。

第四个改造方向是加串口控制。用定时器1做波特率发生器,初始化串口为9600波特率,接收上位机指令。比如收到0xA0切换到花样3,收到0xA1暂停播放,收到0xA2调整当前花样的帧间隔。这套工程的模块划分已经预留了串口接入的位置,定时器1完全没有被占用,直接初始化就行。

做这些改动时我养成了一个习惯:每次改完都重新编译一次,确认0错误后立刻烧录到板子上,观察20秒确认LED状态符合预期才继续下一步。如果观察到LED状态跳变异常,先查中断里的共享变量是不是在别处被改写,这个坑我踩过一次,扫了两天发现是主循环里某次调试顺手写了一句P1 = 0x55,和中断刷新P1冲突,两个地方同时写同一个寄存器,导致LED状态随机闪烁。从那以后我每次使用中断刷新外设的工程,都强制先排查“共享变量的访问边界”,再排查时序配置是否合理。这套代码本身已经把思路理得很顺,往里填自己的功能模块时,只要沿用它的模块化习惯,基本不会出大问题。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询