☰
51单片机Proteus仿真交通灯:定时器、状态机与数码管实战解析
2026/10/14 16:25:27 网站建设 项目流程

简介:面向51单片机初学者的交通灯控制系统仿真资料包,包含完整程序源码与Proteus仿真电路,适合电子工程、嵌入式系统课程设计或自学练手。资源基于Proteus搭建红黄绿三色LED灯组模型,配合51单片机C语言程序,演示利用定时器与中断实现红灯30秒、绿灯20秒、黄灯5秒的常见时序控制逻辑。包内共14个文件,核心内容为C源码、HEX烧录文件与DSN仿真工程,另有编译生成的OBJ、LST、M51等辅助文件及备份文件,便于读者对照分析程序编译和仿真加载过程,整体压缩包仅37KB,下载和传输都很轻量。已有887人学习下载,口碑与实用性经过验证。通过该资料,读者可直观理解51单片机I/O口控制、定时器中断编程以及Proteus虚拟仿真流程,同时为后续扩展倒计时显示、按键控制等复杂交通灯功能提供基础参考。

1. 51单片机Proteus仿真交通灯:这个项目到底在解决什么

很多人第一次接触51单片机Proteus仿真交通灯时,误以为它不过是把几个LED点亮、再让它们轮流亮灭的小儿科。实际上,这个看似简单的组合电路里,藏着定时器精确定时、状态机设计、动态数码管扫描、IO口驱动能力判断、中断处理这五道关卡,任何一个环节想当然都会让仿真直接翻车。它能帮你用最少的时间和硬件成本,把单片机从流水灯带进真实控制的场景,也就是处理有严格时序约束、有多路输出协同、还有外部突发事件打断控制逻辑的系统。

这个项目最适合两类人:一类是刚学完定时器和中断、需要一个小而完整的系统练手的学生,另一类是准备做课程设计或竞赛基础模块的开发者。它给你的不是眼花缭乱的功能清单,而是一条完整闭环:电路图长什么样、程序状态怎么切、HEX文件怎么烧进Proteus的芯片里、跑起来不对时从哪里查。把这些摸透,后面再做按键矩阵、LCD显示、PWM调速,都是同一套思维逻辑的延伸。

2. 仿真交通灯的硬件电路设计:在Proteus里搭出十字路口的骨架

2.1 元件选型与Proteus工程初始化

在Proteus里新建工程时,我习惯直接用AT89C51作为主控芯片,选它的原因不是性能强,而是Keil里编译出来的HEX文件可以直接加载,而且Proteus对它的外设模型支持得最稳定。如果你手头习惯用STC系列,芯片型号里带89C52的也行,只要记得在Proteus里把晶振频率改到和Keil工程一致,否则定时时间会整体跑偏。

需要从元件库调出的清单大致是:AT89C51、RESPACK-8排阻两个、LED-RED/LED-GREEN/LED-YELLOW若干、7SEG-MPX4-CA共阳数码管两到三只、BUTTON按键一个、CRYSTAL晶振(12MHz)、CAP电解电容和CAP陶瓷电容若干。LED的限流电阻不要漏,Proteus虽然不会因为过流烧管子,但仿真亮度会失真,严重时逻辑电平会被拉垮,出现信号冲突的诡异现象。

电路连接的核心规则是:单片机的P1口接LED指示灯,P0口加上拉排阻后接数码管的段码,P2口接数码管的位选,按键接P3.2(INT0外部中断引脚)。之所以把P0口加上拉电阻,是因为P0是开漏输出,不加上拉它根本拉不出高电平,这是新手最多踩的坑。晶振接在XTAL1和XTAL2之间,两个30pF陶瓷电容分别接地,复位电路用10uF电解电容和10k电阻组成上电复位。

初始化参数这里先给一个基准:晶振12MHz,机器周期就是1us,定时器0用方式1也就是16位定时器,初值设置为0x4C00,每50ms进入一次中断,20次中断后正好1秒。这个时基是整个交通灯计时的命根子,后面的状态切换、黄灯闪烁、数码管倒计时全都建立在它上面。Proteus里的虚拟晶振频率如果不改,AT89C51默认跟随工程设置,务必和代码注释保持一致。

2.2 定时器时基:让单片机知道一秒有多长

交通灯的所有时序都依赖秒这个基本单位,偏偏51单片机本身没有秒的概念,它只知道机器周期。12MHz晶振下每个机器周期1us,定时器每加1要1us,16位定时器从0计到65535需要65.535ms,直接拿它当1秒用显然不行。所以常见的做法是让定时器每50ms中断一次,软件里再维护一个计数器,累计20次就是1秒,这样既避开了16位溢出上限,又让计时误差可以控制在几个微秒内。

定时器0方式1的初始化代码只有几行,但初值计算容易栽跟头。50ms等于50000us,从计数最大值65536里减去50000得到15536,换算成十六进制是0x3CB0。每次中断里先重装初值,再累加变量,这点顺序不能反——如果先累加再重装,第一个中断周期的计时是错的。代码里我会把重装值做成宏定义,方便不同晶振频率下快速调整。

#define CRYSTAL_FREQ 12000000UL // 晶振频率12MHz,单位Hz #define TIMER0_RELOAD 65536UL - CRYSTAL_FREQ / 1000 / 20 // 50ms定时重装值 unsigned char timer_count = 0; unsigned int second_count = 0; void Timer0_Init(void) { TMOD &= 0xF0; // 清空定时器0的模式位 TMOD |= 0x01; // 定时器0工作在方式1:16位定时器 TH0 = (unsigned char)(TIMER0_RELOAD >> 8); TL0 = (unsigned char)(TIMER0_RELOAD); ET0 = 1; // 开启定时器0中断 EA = 1; // 开启总中断 TR0 = 1; // 启动定时器0 } void Timer0_ISR(void) interrupt 1 { TH0 = (unsigned char)(TIMER0_RELOAD >> 8); TL0 = (unsigned char)(TIMER0_RELOAD); timer_count++; if (timer_count >= 20) { // 20个50ms就是1秒 timer_count = 0; second_count++; } }

这段代码里,TMOD &= 0xF0再置0x01是保险写法,不会误伤定时器1的配置。重装初值用了宏展开,注意宏里减法运算要加括号,否则展开后运算优先级会出错。中断服务函数末尾没有清中断标志,因为方式1在进入中断时硬件会自动清TF0,手动去清反而可能丢掉下一次溢出。

把计时逻辑写得再稳一点,应该在主循环里用second_count的变化来触发状态机,而不是直接在中断里改led输出。中断函数里只做计数,主循环里做判断,这样可以避免程序正在切换状态时突然被中断打断,导致LED显示和内部计时不同步。很多仿真跑起来灯亮得乱七八糟,根源就是把这些职责混在了同一个函数里。

2.3 数码管动态扫描:倒计时显示的实现前提

交通灯上方通常要显示倒计时秒数,Proteus里最简单的方案是用7SEG-MPX4-CA这种四位共阳数码管,它的8个段码引脚全部并在一起接到P0口,四个位选引脚分别接P2.0到P2.3。单片机一次只能驱动一位数码管,靠快速轮流点亮各位来造成同时显示的错觉,扫描频率必须保证在50Hz以上,否则人眼会看到明显的闪烁。

段码表要按共阳数码管来编码,共阳意味着公共端接高电平,某一段接地才亮。数字0对应的段码是0xC0,表示除g段外其余段全亮,具体换算方式是把a到g和dp对应到P0.0到P0.7,按位取反。如果你买的是共阴数码管,段码表整体取反即可,但Proteus里选7SEG-MPX4-CA就是共阳,别搞混。

动态扫描虽然代码不长,但有个隐藏陷阱:位选切换的瞬间,如果先送段码再选位,会看到上一位的残影。正确的顺序是先把所有位选关掉(消隐),再送新段码,最后打开当前位的位选。三个操作缺一不可,顺序反了就出现所谓鬼影,在Proteus仿真里表现为数字边缘有淡淡的重影。

unsigned char code seg_table[] = { 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E }; void Display_Scan(void) { unsigned char i; for (i = 0; i < 4; i++) { P2 &= 0xF0; // 关闭所有位选,消隐 P0 = seg_table[digits[i]]; // 送当前位段码 P2 |= (1 << i); // 打开第i位 } }

消隐这一句P2 &= 0xF0是关键,它先把四个位选全部拉低,再送段码、开位选。如果漏了消隐,前一位的段码会残留到当前位,显示就会出现重影。扫描函数要放在主循环里持续调用,不能放在定时器中断里做全套显示,因为中断里做太多IO操作会把时基拉偏,秒计时就不准了。

3. 交通灯控制程序:状态机才是仿真项目真正的灵魂

3.1 主干道与支干道的相位切换逻辑

交通灯控制的本质是一个有限状态机,整个系统只有四种稳定状态:南北绿灯加东西红灯、南北黄灯加东西红灯、东西绿灯加南北红灯、东西黄灯加南北红灯。每次切换都伴随定时器秒数的清零和灯的重新赋值。如果把每个状态看成一段代码块,你会发现四个状态之间形成了闭环循环,这就是十字路口交通灯最常见的两相位控制方式。

我一般用结构体加函数指针来写状态机,但51单片机内存小,初学者直接用switch语句反而更直观。定义一个全局变量current_state保存当前状态,主循环里根据状态值执行对应的逻辑,每个状态都要判断当前秒数是否达到该状态的持续时间。持续时间的参数用宏定义列在最前面,后面想改成30秒还是20秒,改宏就行,不用动逻辑代码。

南北方向和东西方向的灯要分别控制,通常P1.0到P1.2控制南北的红黄绿,P1.3到P1.5控制东西的红黄绿。写灯的时候不要整个P1口直接赋值,因为那样会把无关位也改变。先用位操作或屏蔽方式更新对应位,保证每个方向的灯互不干扰。

#define NORTH_SOUTH 0 #define EAST_WEST 1 #define GREEN_TIME 30 #define YELLOW_TIME 3 unsigned char current_state = 0; unsigned char second_count = 0; void Traffic_StateMachine(void) { switch (current_state) { case 0: // 南北绿灯,东西红灯 P1 = (P1 & 0x0C) | 0x21; // 高两位清零保留东西灯,置位南北绿、东西红 if (second_count >= GREEN_TIME) { second_count = 0; current_state = 1; } break; case 1: // 南北黄灯,东西仍红 P1 = (P1 & 0x0C) | 0x11; // 南北黄灯亮,东西红保持 if (second_count >= YELLOW_TIME) { second_count = 0; current_state = 2; } break; case 2: // 东西绿灯,南北红灯 P1 = (P1 & 0x0C) | 0x0A; if (second_count >= GREEN_TIME) { second_count = 0; current_state = 3; } break; case 3: // 东西黄灯,南北仍红 P1 = (P1 & 0x0C) | 0x12; if (second_count >= YELLOW_TIME) { second_count = 0; current_state = 0; } break; } }

这里P1口的赋值看起来有点绕,原因是P1.0到P1.5被六个灯占用,而P1.6和P1.7可能接了别的外设,不能随便动。P1 & 0x0C的作用是先把高两位保存下来,再把低六位清空,然后用按位或的方式设置需要的灯位。这种写法比直接P1 = 0x21要严谨,尤其当你后面把P1口并给其他模块时,能少踩很多坑。

3.2 紧急模式:按键中断与长鸣时的交通灯切换

交通灯项目做到这里,已经能正常循环跑起来了。但真正让它像一个完整系统的,是紧急模式:按下按键,模拟救护车或消防车通过时,所有方向的红灯亮起,让社会车辆停止通行。这个需求用查询方式做也可以,但查询会占用主循环的时间,而且实时性差,所以我选择接到P3.2的INT0外部中断上。

外部中断0的初始化很简单,IT0置1设为下降沿触发,EX0置1使能中断,EA置1开总中断。下降沿触发比电平触发好在一次按键只触发一次,不需要在中断里等按键释放。不过机械按键按下瞬间会有抖动信号,理想做法是加10ms去抖,但在仿真环境里,按键模型本身不会产生真实的机械抖动,直接下降沿触发即可,这个差异在真实硬件上要重新考虑。

void INT0_Init(void) { IT0 = 1; // 下降沿触发 EX0 = 1; // 使能外部中断0 EA = 1; // 总中断使能 } void INT0_ISR(void) interrupt 0 { emergency_flag = 1; // 设置紧急状态标志 } void Emergency_Handler(void) { if (emergency_flag) { emergency_flag = 0; P1 = (P1 & 0x0C) | 0x15; // 南北红+东西红 current_state = 4; // 进入紧急状态 second_count = 0; } }

紧急模式下最好让整个路口全红,持续一段时间后自动恢复到正常轮转。恢复的方式有两种:一种是紧急状态持续固定秒数,比如10秒后自动回到正常状态;另一种是按下一次按键进入紧急模式,再按一次退出。我一般倾向于前一种,因为它更贴近真实交通控制逻辑,程序上也更好处理。注意紧急模式要单独做一个状态值,别直接覆盖正常的四个状态,否则退出时不知道回到哪个相位。

另外,中断服务函数里绝对不要写复杂逻辑,比如调用数码管扫描或延时函数。中断里只做标志位设置,所有实际处理放在主循环里调用Emergency_Handler函数。中断里做太多事情,轻则计时跑慢,重则嵌套中断导致系统卡死,这在Proteus仿真里很难排查,因为程序单步执行时一切正常,全速跑就出问题。

3.3 倒计时显示与黄灯闪烁的细节处理

前面提到用数码管显示倒计时,最直观的做法是让两边对称显示剩余秒数:南北绿灯剩GREEN_TIME,东西红灯剩GREEN_TIME,数字随着second_count递减。注意这里的倒计时是基于秒数的,而second_count是在中断里递增的,所以显示剩余值时不能直接改second_count,而是用一个临时变量计算剩余秒数,再拆成十进制送入显示缓冲区。

黄灯闪烁和倒计时有个典型冲突:黄灯阶段只有3秒,如果这3秒同时也在数码管上倒计时,人眼会同时注意灯的变化和数字跳动,反而降低辨识度。常见的折中方案是黄灯阶段数码管不做倒计时,只显示0或者保持上一个数字不动,让驾驶员专注在灯色切换上。

void Update_DisplayBuffer(void) { unsigned char remain; if (current_state == 0 || current_state == 2) { remain = GREEN_TIME - second_count; digits[0] = remain / 10; digits[1] = remain % 10; digits[2] = remain / 10; digits[3] = remain % 10; } else { // 黄灯阶段显示灭掉或显示00 digits[0] = 0; digits[1] = 0; digits[2] = 0; digits[3] = 0; } }

黄灯闪烁的实现反过来要借助second_count的奇偶:在黄灯状态下,读取second_count的最低位,为0时点亮黄灯,为1时熄灭黄灯,这样黄灯以每500ms切换一次的状态闪烁,符合真实交通灯的节奏。关键是在闪烁时不要直接改存储当前状态的变量,只改输出到P1口的灯位,这样即使黄灯暂时熄灭,状态机仍然认为它处于黄灯阶段。

数码管的显示缓冲数组digits是全局变量,扫描函数Display_Scan直接读取它,而Update_DisplayBuffer负责写入,两个函数职责分离。这样改显示逻辑时不用动扫描函数,扫描频率的调节也只影响显示效果,不影响状态机。

4. 程序与仿真联调:让Proteus里的灯按你的代码亮起来

4.1 生成HEX文件并加载到Proteus的完整步骤

Keil工程写好代码后,第一步是确认Target选项里选择的芯片型号和Proteus里一致。接着打开Options for Target,切到Output选项卡,勾选Create HEX File,这样编译后才会生成可以加载到Proteus的.hex文件。很多人编译时报0 Error但Proteus里芯片不跑,原因就是这一步没勾,Proteus的芯片里根本没有程序。

编译成功后,在Proteus里双击AT89C51芯片,弹开的属性对话框里有一项Program File,点击文件夹图标选择刚才生成的hex文件。这里要注意路径里不能有中文和空格,否则Proteus可能加载失败或加载了旧文件。加载完别忘了把晶振频率改到和Keil一致,12MHz就写12MHz,Proteus默认的晶振不一定匹配,这是定时不准的第一大来源。

设置完成后点击Proteus左下角的运行按钮,仿真就开始跑了。正常情况下LED按状态机依次亮灭,数码管同步倒计时。如果灯没反应,先检查左下角是不是显示Running,没有则说明仿真根本没跑起来,最常见原因是电路连接有悬空引脚或电源没接全。

建议在仿真这一步养成一个习惯:先跑最小功能验证,也就是只让主循环里的状态机跑起来,数码管和按键先注释掉。确认LED正常轮转后,再逐个加入数码管扫描、按键中断,每加一个模块就全速跑一次。这样做的好处是出问题时能明确知道是哪个模块引入的bug,而不是面对整个系统一起不工作。

4.2 用Proteus的虚拟仪器验证时序是否准确

Proteus里有一个说不上冷门但经常被忽略的功能——虚拟示波器和逻辑分析仪。很多人以为Proteus只能看LED亮灭,实际上它能把单片机引脚上的电平变化拉成波形,让你精确看到每个灯的翻转时刻。用它来验证交通灯的时序,比肉眼盯着LED亮灭靠谱得多。

把示波器探头接到P1.0引脚上,运行仿真,你会看到这个引脚每隔30秒左右跳变一次电平。如果两次跳变之间的间隔明显不是30秒,说明定时器初值或晶振配置有误。接两个探头分别看南北绿灯和东西绿灯的波形,会发现它们之间的相位差正好是半个周期,这正是两相位控制的标准特征。

还有一个容易被忽视的验证点是黄灯闪烁频率。把示波器接到黄灯对应引脚,正常情况应该是500ms高电平、500ms低电平的方波。如果你看到的波形高电平持续1秒、低电平也持续1秒,说明你是按照整秒来切换的,频率只有真实黄灯闪烁要求的一半。用示波器一测,这个玄学问题当场就能定位,不用靠肉眼猜。

# 示波器接线参考(Proteus操作) # 1. 从左侧工具栏选择虚拟仪器模式 # 2. 选择OSCILLOSCOPE,放置到原理图空白处 # 3. 示波器A通道接P1.0,B通道接P1.3 # 4. 运行仿真,观察A/B两通道的电平翻转时间差

4.3 仿真参数调节对照表

参数调节是联调阶段最频繁的操作。晶振频率、定时器初值、状态切换时间这三个变量互相纠缠,改任何一个都要同步考虑另外两个。下面这张表是我平时调整参数时的对照依据,按常见问题场景列出应该改哪里。

问题现象可能原因调整参数
绿灯持续时间过短GREEN_TIME宏太小修改宏定义中的秒数
倒计时每秒跳过多个数second_count累加异常检查timer_count是否被清零
整体计时变快晶振频率与Keil不一致核对两处晶振设置相同
黄灯不闪烁只常亮闪烁判断逻辑丢失检查second_count奇偶判断
数码管显示乱码段码表与共阳设置不匹配检查seg_table或数码管类型
按键进入紧急模式后无法退出紧急状态未自动恢复在紧急状态代码中增加计时退出

这些参数如果没有特别说明,我建议直接用前面代码里的默认值:绿灯30秒、黄灯3秒、晶振12MHz、定时器50ms中断。先把这套默认参数跑通,再按需调整,不要上来就随意改宏,否则后面排查时你根本不知道基线应该是什么样。

5. 避坑与常见问题排查:Proteus仿真交通灯最容易翻车的5个环节

5.1 LED完全不亮,芯片引脚却输出正常电平

现象:程序编译无误,Proteus正常运行,但LED就是不肯亮。用电压探针测量P1口引脚,高低电平切换都正常,可LED两端电压不对。

原因:这一类问题九成出在限流电阻和LED的接法上。Proteus仿真不是物理硬件,它严格按照电路模型的电气参数计算,如果限流电阻选得过大,比如10k,LED上的分压就远低于点亮阈值。反过来,如果忘了接地回路,电平再正常也是虚电压。

解决:把限流电阻换成330欧姆到470欧姆之间,确认LED阴极确实接地而不是接VCC。AT89C51的P1口内部有上拉,驱动LED时低电平点亮,也就是LED阳极接VCC、阴极接P1引脚这种方式,或者阳极接P1、阴极经电阻接地,取决于你用的方向控制逻辑。

5.2 数码管出现残影或数字重叠

现象:倒计时数字显示不清晰,数字边缘带淡影,尤其在数字快速变化时严重。改变扫描速度,症状有缓解但没根除。

原因:动态扫描的消隐处理没做到位。位选切换的瞬间,前一位的段码残留在IO口上,新位选打开时把这个残留段码也显示出来了。Proteus对信号的变化模拟得比较理想化,但扫描顺序不对时残影依旧可见。

解决:严格按消隐、送段码、开位选的顺序写扫描函数。每次切换前先把所有位选关闭,然后更新P0口的段码,最后打开当前位选。如果已经这么写了还残影,检查是否在扫描函数里意外调用了延时,导致某一位停留时间过长,破坏了均匀扫描。

5.3 计时不准,绿灯持续时间忽长忽短

现象:绿灯亮的时间不是固定的GREEN_TIME,有时多两三秒,有时少两三秒,看起来没有规律。

原因:定时器中断服务函数和主循环之间的second_count读写竞争,或者中断里做了耗时操作导致后续中断延迟。Proteus仿真是单核执行,中断处理时间过长会延迟下一次中断的触发时刻,积累下来就是秒数漂移。

解决:中断服务函数里只做重装初值、timer_count累加、second_count溢出判断三个动作,其余任何IO操作全部挪到主循环。这个看起来像玄学的问题,本质是中断服务函数写得太长,把定时精度拖垮了。把职责理顺,计时立刻恢复正常。

5.4 仿真运行极慢,LED亮灭像慢动作

现象:点击运行后,仿真确实在执行,但LED切换间隔明显比设定的秒数长得多,甚至十几秒才跳一次。

原因:Proteus仿真执行速度受电路规模影响,动态数码管扫描本身就在以毫秒级频率刷新IO口,再加上定时器中断、按键扫描,整个电路的事件量很大。电脑性能不足时,仿真速度会低于实时速度。

解决:在Proteus的Debug菜单里关闭不必要的仿真日志输出,特别是Pin State日志,它会在仿真时记录每个引脚的每一次变化,数据量巨大。同时把数码管扫描函数里不必要的延时去掉,扫描周期不要卡着极限。如果还是慢,优先考虑简化数码管位数,或者把显示刷新率从每一个主循环都刷改成每几个循环刷一次。

5.5 按下按键没反应,或者按键一次系统跳两个状态

现象:运行仿真时点击按键,紧急模式完全没有触发;或者只按了一下,状态机却像被按了多次。

原因:按键没有正确接到INT0引脚,或者中断触发方式配置不对。下降沿触发要求引脚平时为高电平,按下瞬间拉低产生下降沿。如果按键模型接反,平时引脚就是低电平,按下去反而变成上升沿,中断永远不会触发。

解决:确认按键一端接P3.2,另一端接GND,P3.2内部上拉保持高电平。中断初始化里IT0置1、EX0置1、EA置1,三者缺一不可。至于按一次跳多个状态,是因为主循环里紧急模式处理函数不断被调用,每次调用都恢复或切换状态。解决办法是使用一次性标志,处理完立即清零,并且在紧急状态期间屏蔽重复触发。

6. 从仿真到实物:让这个交通灯项目真正变成你的入门之作

Proteus仿真跑通只是第一步,我建议你做完仿真后,再做三件小事把项目彻底吃透。第一件事,用示波器把每个状态的翻转时间记录下来,做成一张表,和代码里设定的时间逐项核对。这个习惯能帮你建立时序敏感度,以后做任何带时间约束的项目,第一反应就是去测波形而不是瞎猜逻辑。

第二件事,在现有两相位基础上扩展一个左转相位。南北直行绿灯、南北左转绿灯、东西直行绿灯、东西左转绿灯,四相位切换比两相位复杂一个量级,状态机从4个状态变成8个状态,你会在设计过程中理解状态冲突、互锁防护这些问题。第三件事,把LED换成红黄绿三个颜色的交通灯模型,Proteus元件库里有TRAFFIC_LIGHTS模型,可以直接替换单个LED,看起来更接近真实的十字路口。

我自己做这个项目时栽过最大的跟头,是在一个版本里把消隐和位选顺序写反,仿真时数字残影严重,我以为是Proteus显示刷新问题,折腾了很久才发现是代码顺序错误。从那以后我养成了一个习惯:每次改完代码,先复位仿真并用示波器观察输出,再决定要不要继续。这个项目真正的价值不在于灯能不能亮,而在于你建立了从电路图、程序、时序到仿真验证这条完整的排查链路。

如果你现在正对着一个跑不起来或跑起来不对的仿真,先别急着删代码重写,按前面排查表的顺序一条条对照,九成问题出在晶振配置、定时器初值、IO口接法这三个地方。希望帮到你。

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

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

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

立即咨询