手里刚拿到一块 LAUNCHXL-F28379D,第一件事几乎所有人都是点灯。但真把这颗 200MHz 双核 C28x 的 LED 点亮,和在八位单片机上写GPIOA->ODR ^= 1完全是两回事——这块板子默认时钟跑在内部 10MHz 振荡器上、看门狗默认是开的、绝大多数引脚上电后并不是 GPIO 功能、Flash 取指还带着等待周期,再加上双核启动顺序和板上那组启动模式拨码,任何一个环节没对上,表现都是“灯不亮”。所以这篇东西不打算写成一份例程说明,而是把 LAUNCHXL-F28379D 点灯这件事拆成一条完整的信号链路:从 LED 焊在哪两个引脚、GPIO 寄存器怎么配、时钟和看门狗什么时候处理,一直讲到下载成功却不跑、灯闪一下就熄这几种典型现场怎么排查。不管你是第一次碰 C2000 系列,还是从别的 MCU 转过来想快速确认工具链是否正常,下面这些内容都能直接照着复现。
1. 为什么在 F28379D 上点灯比在普通单片机上多绕三道弯
1.1 一颗 LED 背后其实压着整条启动链路
先把话说透:点灯这个动作本身没有任何技术含量,它的价值在于它是一个最小可观测闭环。你写进去的代码要经过编译、链接定位、通过板载仿真器烧进 Flash、芯片复位后从正确的启动地址取指、初始化系统时钟、打开 GPIO 输出驱动、最终让引脚电平变化——这一整条链路上任何一环断了,肉眼看到的都是同一个现象:灯不亮。所以点灯成功,等价于这条链路全部打通;点灯失败,等价于链路里有一处断了,但你还不知道是哪一处。
普通八位机上这条链路很短,复位后直接跑,引脚默认就是 GPIO,时钟就那么点频率,不需要等待周期。而 F28379D 是面向电机控制、数字电源、光伏逆变这类场景设计的器件,它的默认状态是为安全和控制预留的:上电后先跑内部振荡器,主频很低;看门狗是使能的,防止程序跑飞后功率级继续输出;绝大多数引脚默认挂在某个外设功能上,防止上电瞬间产生误动作;Flash 需要按频率配置等待周期。这些设计在工业场景里都是优点,但对一个只想点灯的初学者来说,就是三道必须自己跨过去的坎。
我个人的判断标准很简单:如果一块新板子到手,三十分钟内点不亮灯,那就不要急着往下学外设,先把点灯这条链路彻底摸清。因为后面调 ADC、调 ePWM、调 CAN,出问题时的排查思路和现在是一模一样的:先确认是不是根本层面的问题(供电、时钟、启动),再确认是不是配置层面的问题(复用、方向、保护锁),最后才怀疑代码逻辑。
1.2 双核、看门狗、时钟:三个最容易让人白折腾半天的默认状态
第一是时钟。F28379D 复位后默认使用片内振荡器作为系统时钟源,PLL 处于旁路状态,SYSCLK 大约只有 10MHz 量级。这意味着你写的软件延时循环会比预期慢二十倍,你算出来“闪 1 秒”的循环实际可能闪 20 秒,很多人第一次点灯就是盯着板子等半天以为没跑起来。正确做法是在 main 的最前面调用官方的系统初始化函数,把 PLL 配到 200MHz,同时把 Flash 等待周期一起配好。
第二是看门狗。这块芯片的看门狗在上电复位后默认是使能状态,超时时间在百毫秒到秒级这个量级。如果你手写了一个while(1)里只有翻转引脚和延时的循环,而没有调用系统初始化函数去关看门狗,那结果就是:灯闪几下,芯片复位,再闪几下,再复位。用示波器看就是一段规律的、突然中断又重新开始的波形。这个现象极其容易被误判成“电源不稳”或者“仿真器干扰”,我第一次遇到时也走了弯路。
第三是双核。这颗芯片有 CPU1 和 CPU2 两个 C28x 核,还有两个 CLA 协处理器。上电后 CPU1 是主核,负责启动流程;CPU2 默认停在复位状态,需要 CPU1 显式放行才会开始运行。对于点灯这种任务,所有代码放在 CPU1 里就够了,CPU2 保持复位或者跑一个空循环都行。但如果你拿到的工程模板是双核模板,里面带 IPC 通信和 CPU2 的工程,那么 CPU2 那边的启动代码如果卡在等待某个 IPC 标志上,整个系统看起来就像死机。所以初学阶段,优先选单核工程模板,把变量降到最少。
顺带提一个 GPIO 的细节:在双核架构下,GPIO 控制寄存器两个核都能访问。如果 CPU1 和 CPU2 都在操作同一个引脚,而且没有约定好谁负责,那你会看到引脚状态像是在随机跳变。点灯阶段不会遇到,但一旦你开始做双核分工,这就是第一个要立规矩的地方。
2. 先搞清楚 LED 到底挂在哪两个脚上,别照着别人的帖子抄
2.1 从原理图确认极性:高电平亮还是低电平亮
这块板子上有若干颗贴片 LED,其中两颗是留给用户自己控制的,丝印通常标为 LED4 和 LED5,网络名分别对应 GPIO31 和 GPIO34。不同批次的板子丝印编号可能会有出入,但网络名在原理图里是固定的,所以永远以你手上那块板子对应的原理图为准,不要直接抄网上帖子里的引脚号。
比引脚号更容易出错的是极性。LED 的接法有两种:一种是 GPIO 经过限流电阻接到 LED 阳极,LED 阴极接地,此时输出高电平点亮;另一种是 3.3V 经过限流电阻接 LED 阳极,阴极接 GPIO,此时输出低电平点亮。这两种接法在代码上就是SET和CLEAR的区别,抄错了就是“程序没错但灯不亮”。
手上没有原理图怎么办?两个办法。第一,用万用表的二极管档,红表笔搭在 GPIO 焊盘上、黑表笔搭地,如果 LED 微亮,说明是高电平点亮;反过来黑表笔搭 3.3V、红表笔搭 GPIO 微亮,说明是低电平点亮。第二,直接写一段“闪烁”代码,把两个引脚一个拉高一个拉低,观察哪颗灯亮——反正只有两种组合,试一次就知道了。
还有一个非常好用但很少有人提的技巧:F28379D 的每个 GPIO 都有一个极性反转寄存器(GPxINV)。把对应位置 1,引脚的实际输出电平就和寄存器里写的值反过来了。这意味着你可以在初始化时用一个宏把“点亮有效电平”抽象出来,换板子的时候只改这一处,业务代码完全不用动。做多块板子适配的时候,这个寄存器能省掉大量条件编译。
2.2 引脚复用与外设占用的冲突排查
F28379D 的引脚复用密度很高,绝大多数 GPIO 上电后默认并不是 GPIO 功能,而是挂着某个外设的信号线。GPIO31 和 GPIO34 这两个脚同样挂着别的复用功能,比如串行口和 SPI 相关的信号线,具体编号必须查器件数据手册里的引脚复用表。这就是为什么配置 GPIO 输出时,复用寄存器(GPxMUX)必须先写成 0——写 0 表示选择通用 IO 功能,写其他值表示交给某个外设。这一步漏了,你把方向寄存器设成输出、把数据寄存器设成高电平,引脚依然纹丝不动,因为它压根没连到 GPIO 模块上。
下面这张表是我排查“引脚没反应”时的固定顺序,按这个顺序走,基本不会漏:
| 检查项 | 寄存器 | 常见错误 | 现象 |
|---|---|---|---|
| 复用功能 | GPxMUX1/GPxMUX2 | 没写 0,停留在外设功能 | 引脚完全不受控 |
| 方向 | GPxDIR | 写成输入 | 电平由外部决定,写数据无效 |
| 上拉配置 | GPxPUD | 输出时开了上拉 | 一般不影响,但低电平驱动能力受影响 |
| 保护锁 | EALLOW/EDIS | 没开保护就写寄存器 | 写入被忽略,配置不生效 |
| 量化采样 | GPxQSEL | 用作输入时没配 | 输入读到抖动值 |
| 外部电路 | 跳线帽/隔离 | 板上跳线没插 | 芯片引脚有波形,LED 不亮 |
其中“保护锁”这一项最坑。F28379D 把 GPIO 配置寄存器划进了受保护区域,写入之前必须先执行EALLOW指令解除保护,写完再EDIS恢复。很多从别的平台转过来的人不习惯这个机制,代码看起来完全正确,编译也没报错,但配置就是不生效,因为写入被硬件静默丢弃了。判断方法很直接:在调试器里看寄存器的值,如果和你写进去的不一致,八成就是保护没解。
3. 手写寄存器版点灯:把该配的四个寄存器一个个走通
3.1 时钟与看门狗,必须在碰 GPIO 之前处理
我建议的顺序是:先处理系统级的东西,再处理引脚级的东西。系统级包括三件事——关看门狗、配置 PLL、配置 Flash 等待周期。这三件事官方提供了一个打包好的初始化函数,直接调用即可,不需要自己一位一位去填。
如果想搞清楚它到底做了什么,可以拆开看。PLL 的配置逻辑是:选定参考时钟源,设定倍频系数和分频系数,等待 PLL 锁定,然后切换系统时钟源到 PLL 输出。以 10MHz 参考时钟、20 倍频、1 分频为例,得到的就是 200MHz 的系统时钟。Flash 等待周期的设置原则是频率越高等待越多,200MHz 这个档位通常配置为 3 个等待周期,具体数值要对照器件手册里的表,不要凭感觉填。
如果跳过这一步直接配 GPIO,会怎样?灯其实能亮,因为 GPIO 模块本身在低速时钟下也能工作。但你会遇到两个隐性问题:一是软件延时的实际时长和你算的差二十倍;二是当你在后续代码里手动把时钟提到 200MHz 却忘了改 Flash 等待周期,程序会在取指阶段出现莫名其妙的异常,表现可能是跑飞、可能是进入错误处理、也可能是看起来“随机死机”。这类问题极难定位,因为它和代码逻辑没有关系。
3.2 GPIO 输出配置的四个动作,一个都不能省
配置一个 GPIO 做推挽输出,本质上就四个动作:选功能、定方向、配上拉、给初值。用寄存器写出来大概是这样:
#include "F28x_Project.h" void main(void) { InitSysCtrl(); /* 关看门狗、配 PLL 到 200MHz、配 Flash 等待 */ EALLOW; /* 打开 GPIO 配置寄存器的写保护 */ /* GPIO31:A 口,MUX2 里选功能,DIR 定方向 */ GpioCtrlRegs.GPAMUX2.bit.GPIO31 = 0; GpioCtrlRegs.GPADIR.bit.GPIO31 = 1; GpioCtrlRegs.GPAPUD.bit.GPIO31 = 1; /* 输出不需要内部上拉 */ /* GPIO34:B 口,MUX1 里选功能 */ GpioCtrlRegs.GPBMUX1.bit.GPIO34 = 0; GpioCtrlRegs.GPBDIR.bit.GPIO34 = 1; GpioCtrlRegs.GPBPUD.bit.GPIO34 = 1; EDIS; /* 恢复写保护 */ for(;;) { GpioDataRegs.GPASET.bit.GPIO31 = 1; GpioDataRegs.GPBSET.bit.GPIO34 = 1; DELAY_US(500000); GpioDataRegs.GPACLEAR.bit.GPIO31 = 1; GpioDataRegs.GPBCLEAR.bit.GPIO34 = 1; DELAY_US(500000); } }这里有个容易记混的地方:GPIO 是按 32 个一组分口的。0 到 31 属于 A 口,32 到 63 属于 B 口,64 到 95 属于 C 口。所以 GPIO31 在 A 口、GPIO34 在 B 口,两个灯的寄存器根本不在同一个组里。写代码时如果搞错了组,比如对着 B 口寄存器去配 GPIO31,编译器不会报错,因为位域名字是合法的,但那个位在硬件上对应的就是另一个引脚,结果就是“配了但没反应”。
关于上拉配置还有一点值得说:输出模式下内部上拉其实意义不大,但建议显式关掉,尤其是在做低功耗或者需要精确控制静态电流的场景。默认状态下很多引脚内部上拉是开着的,一个上拉电阻加上外部负载,会产生持续的漏电流,单看一个引脚微不足道,几十个引脚加起来就很可观了。
3.3 写 1 置位机制:为什么不能用数据寄存器做读改写
上面代码里用的是GPxSET和GPxCLEAR,而不是直接写GPxDAT。这不是风格问题,是必须遵守的规则。
GPxDAT是一个可读可写的寄存器,你读它得到当前引脚状态,写它直接改变引脚状态。听起来很方便,但问题在于:对 GPxDAT 的写操作是对整个端口的 32 个位一起生效的。也就是说,如果你写GpioDataRegs.GPADAT.all = 0x00000001,你实际上把 A 口其余 31 个引脚全部拉低了。如果你的程序里其他地方正在控制 A 口的别的引脚,比如某个片选信号或者使能信号,这一下就被你冲掉了。
GPxSET和GPxCLEAR的设计就是为了解决这个问题:它们是写 1 有效的寄存器。往GPxSET的某一位写 1,对应的引脚拉高,写 0 则该引脚保持不变;GPxCLEAR同理。这样你操作一个引脚时,完全不会影响同口的其他引脚,也就不存在读改写竞态的问题。
还有一个GPxTOGGLE,往某一位写 1,对应引脚电平翻转。如果只是想做个闪烁,用它最省事——一次写操作就完成翻转,不需要判断当前电平。我在做“心跳灯”的时候基本都用这个寄存器,代码短、执行快、不会出错。
4. 用官方驱动库重写一遍,顺便把工程骨架理顺
4.1 链接文件与运行位置:Flash 还是 RAM
寄存器写法能让你看清底层,但实际项目里我更推荐用官方提供的外设驱动库,代码可读性和可移植性都更好。不过在写代码之前,有个比代码更重要的东西要确认:链接文件。
F28379D 的工程里通常会有多个链接文件,分别对应“程序烧到 Flash 运行”和“程序放到 RAM 里运行”两种模式。这两个文件的区别在于代码段的定位地址,选错了会出现很诡异的现象——比如你明明下载成功了,但一按复位程序就不动了,或者调试器连着的时候一切正常、断开调试器重新上电就失效。
我的习惯是:第一次点灯用 Flash 版本,因为这才是最终产品的真实运行方式,能一次性把烧写链路和启动链路都验证掉。等确认 Flash 版本没问题,再去做 RAM 调试版本的优化,那时候你知道问题不在链路上,排查范围就小得多。
还有一个和链接相关的小细节:如果工程里带了memcpy把时间关键代码从 Flash 搬到 RAM 运行的段,你需要确保搬运代码在main里被调用到了。点灯这种应用不涉及,但如果你拿的是别人的复杂模板,搬运没做会导致某些函数执行异常。初学阶段直接选最简单的模板,能避开一大半这类问题。
4.2 三行代码点灯,但每一行的前提你得知道
用驱动库写出来是这样:
#include "driverlib.h" #include "device.h" void main(void) { Device_init(); /* 关看门狗、配 PLL、配 Flash 等待 */ Device_initGPIO(); /* 初始化 GPIO 默认状态 */ GPIO_setPinConfig(GPIO_31_GPIO31); GPIO_setDirectionMode(31, GPIO_DIR_MODE_OUT); GPIO_setPadConfig(31, GPIO_PIN_TYPE_STD); GPIO_setPinConfig(GPIO_34_GPIO34); GPIO_setDirectionMode(34, GPIO_DIR_MODE_OUT); GPIO_setPadConfig(34, GPIO_PIN_TYPE_STD); for(;;) { GPIO_togglePin(31); GPIO_togglePin(34); DEVICE_DELAY_US(500000); } }看起来只有几行,但每一行背后都有前提。Device_init()之所以能一行搞定时钟和看门狗,是因为库函数内部已经按 200MHz 的目标做了完整配置,包括 PLL 倍频、分频、Flash 等待周期、外设时钟使能。Device_initGPIO()会把所有 GPIO 恢复到一个已知的默认状态,这一步在多外设工程里很重要,能避免上一个工程残留的配置影响当前运行。
GPIO_setPadConfig里的GPIO_PIN_TYPE_STD表示标准推挽、关闭内部上拉。这个参数如果写成带拉上的类型,输出功能虽然也能工作,但静态电流会上去。至于DEVICE_DELAY_US,它是基于当前 CPU 频率计算的忙等延时,前提是系统时钟真的配到了它以为的那个频率。如果时钟配错了,延时就会等比例偏差,这也是为什么我前面强调时钟要先处理好。
如果你手头没有完整的驱动库环境,只有芯片头文件,那也没关系,寄存器版本一样能跑,只是代码长一点。两条路都走一遍其实很有价值:先用寄存器版理解硬件,再用库函数版提高效率,两边的现象能对上,说明你真的搞懂了。
5. 跑不起来的六种现场,按这个顺序排查
5.1 下载成功但断电重启不跑:启动模式采样脚
这是新手遇到的第一个大坑,也是最容易怀疑人生的一个。现象是:在集成开发环境里点调试,程序能跑,灯能闪;一旦断开调试器、拔掉 USB 再重新上电,灯就彻底不动了。
原因在于 F28379D 的启动方式是上电时采样两个特定引脚的电平来决定从哪里启动。如果采样结果指向的是串口等待模式或者某种引导加载模式,芯片就不会去执行你烧在 Flash 里的程序,而是停在那里等外部命令。调试器连着的时候,调试器会主动控制复位和启动流程,所以看起来一切正常;独立上电时就露馅了。
解决办法是找到板子上那组启动模式拨码开关,把它拨到 Flash 启动对应的位置。板上一般会有丝印标注,如果没有,就查用户指南里的启动模式表。我建议把这个拨码位置当成板子的“固定资产”,调好之后就不要随便动,否则每次换工程都要重新怀疑一遍。
5.2 灯闪几下就熄:看门狗的典型波形
前面提过看门狗,这里说具体现象。程序如果是“翻转、延时半秒、翻转、延时半秒”,而看门狗超时时间比延时短,那么芯片会在延时过程中被复位,复位后重新开始,于是你看到的是一段规律闪烁然后突然中断、停顿、再重新开始。
用示波器看这个波形特别有辨识度:正常闪烁是均匀的方波,看门狗复位造成的是“方波—长低电平—方波”的周期性图案,那个长低电平就是复位和重新初始化花掉的时间。如果没有示波器,就观察 LED 的闪烁节奏,如果节奏里有明显的“卡顿”,基本可以确定。
修复方式有两个:一是在初始化阶段直接关掉看门狗(调试阶段常用),二是在主循环里定期喂狗(产品阶段必须)。我个人的做法是调试期关掉、出产品前打开并加上喂狗,这样两头都不耽误。但要提醒一句:关看门狗这件事一定要在代码里显式写出来并且加注释,不能依赖“默认是关的”这种假设,否则换个模板、换个人接手,问题又会回来。
5.3 完全没反应:从供电到跳线的物理层检查
如果连调试器都连不上,那就不是代码问题,往下走物理层。
先看板子上的电源指示灯亮不亮。这块板支持多种供电方式,USB 供电和外部供电之间的跳线设置不一样,跳线位置不对可能导致某个电源域没上电。再看仿真器指示灯,板载仿真器的状态灯如果不亮或者颜色异常,说明 USB 通信本身有问题,先换线、换口排除。
然后是隔离跳线。这块板为了支持外部仿真器,板上有一组隔离跳线,用来断开板载仿真器和芯片之间的连接。如果这些跳线被拔掉了,板载仿真器就连不上芯片,表现就是“设备识别到了但无法连接目标”。这个细节在快速上手文档里往往一笔带过,但确实是实际会踩的坑。
最后确认目标芯片的供电电压。用万用表量一下 IO 电源引脚,正常应该在 3.3V 附近。如果明显偏低,先别怀疑芯片坏了,查查是不是某个外设板或者杜邦线把电源拉塌了。
5.4 引脚有波形但灯不亮:外部电路的锅
还有一种情况特别迷惑人:用示波器或者逻辑分析仪量芯片引脚,方波漂漂亮亮,频率也对,但 LED 就是不亮。
这时候问题基本在引脚之后。可能是限流电阻虚焊、LED 本身损坏、或者板上串联的跳线帽没插。有些开发板为了让你能独立测量引脚,会在 GPIO 和 LED 之间放一个可插拔的跳线,出厂时是插着的,但如果板子被人动过,跳线丢了就会出现这个现象。
判断方法很简单:直接在引脚和地之间串一个几百欧的电阻加一颗 LED 试一下,如果外接的亮、板载的不亮,那就是板载电路的问题。
5.5 两个灯只有一个亮:分组寄存器搞混了
前面说过 GPIO31 在 A 口、GPIO34 在 B 口。如果有人照着“两个脚挨着写”的思路,把两个脚都配到了同一个口的寄存器里,那必然有一个不生效。这种错误的隐蔽性在于编译完全通过,因为位域名在头文件里是存在的,只是那个位物理上对应的是另一个引脚。
排查方式是在调试器里逐个查看GPAMUX2、GPADIR、GPBMUX1、GPBDIR这四个寄存器的值,确认每一位是不是你期望的。养成“配完就看寄存器”的习惯,能省掉大量猜测时间。
5.6 灯亮但亮度不对:驱动能力与限流电阻
如果灯亮了但明显偏暗,或者用万用表量出来引脚电压不是接近 0 就是接近 3.3V 而是卡在中间某个值,那多半是驱动能力的问题。GPIO 的输出电流是有上限的,直接驱动大电流负载会把电平拉塌。板载 LED 一般都配了合适的限流电阻,所以正常不会出现这个问题;但如果你外接了 LED 又忘了串电阻,或者串的阻值太小,就会出现这种现象,严重时还会损伤引脚。
6. 让点灯真正有点用:用它验证时钟、量化延时、切入中断
6.1 用翻转周期反推系统时钟,别信注释
代码注释里写“延时 1 秒”不代表真的是一秒。我的习惯是:点灯成功之后,第一件事就是用逻辑分析仪或者示波器量一下翻转周期,然后用这个周期反推实际系统时钟。
计算方法很直接:如果代码里翻转间隔是 N 次循环,每次循环消耗若干个时钟周期,那么测量到的半周期时间除以循环次数,就能反推出单周期时间,进而得到频率。如果算出来是 10MHz 而不是 200MHz,那说明 PLL 根本没配上,只是恰好 GPIO 在低速下也能工作而已。
这个验证动作花不了五分钟,但它给你的是对整块板子时间基准的信任。后面做 ADC 采样率计算、ePWM 死区时间设定、通信波特率配置,全都建立在这个基准上。基准错了,后面所有参数都是错的,而且错得很隐蔽。
6.2 从软件延时切到定时器中断
软件忙等延时的缺点很明显:占着 CPU 什么都干不了,而且延时精度受编译优化影响。你在延时循环里加一句打印,整个时序就变了。编译器如果把空循环优化掉,延时会直接消失——这时候要么加volatile,要么用官方的延时函数(内部用了不会被优化掉的方式实现)。
真正做项目肯定要用硬件定时器。F28379D 上有多个 CPU 定时器,配置思路是:设定周期值、设定分频、使能中断、在中断服务函数里翻转引脚。以 200MHz 主频、要产生 1 秒中断为例,周期值设为 200000000 就够,但这个数超过了 32 位定时器的计数范围(最大约 42.9 亿,其实够用),如果不够就靠分频解决。中断服务函数里做两件事:翻转 GPIO,清除中断标志。
这里有个必须注意的点:中断服务函数要短。翻转一个引脚是几纳秒的事,如果你在里面加了串口打印或者浮点运算,中断响应时间就会被拉长,主循环的时序也会受影响。定时器中断点灯这个练习的价值,就是让你建立“中断里只做最少的事”这个习惯。
6.3 再往前一步:PWM 调光与双核分工
灯能闪了,下一步可以让它呼吸。F28379D 的 ePWM 模块非常强,但 LED 引脚不一定连到 ePWM 输出上,所以做呼吸灯有两条路:一是用定时器中断做软件 PWM,在中断里按正弦表改变高低电平的占空比;二是把 LED 换到某个 ePWM 输出引脚上,直接用硬件 PWM 加死区配置。
第一条路适合练手,能让你理解占空比和视觉亮度的关系,也能顺便验证中断频率够不够。第二条路更接近实际工程,因为真实项目里 PWM 都是硬件产生的,软件只负责改占空比寄存器。我建议两条都试一遍,尤其是第二条,你会直观感受到硬件外设和软件模拟之间的差距。
再往后就是双核和 CLA 的参与了。比如可以让 CPU1 负责通信和状态机,CPU2 专门跑控制环路,CLA 处理 ADC 采样后的数学运算。这时候 GPIO 的归属就要提前规划:哪个核负责初始化、哪个核负责翻转、共享的引脚用什么机制同步。我见过一个项目因为两个核都在改同一个引脚的输出,导致输出波形出现毛刺,最后查了两天才发现是竞争问题。所以“点灯”这件事,在小系统里是入门,在大系统里是资源分配的缩影。
最后分享一个我自己一直在用的做法:每拿到一块新板子,我都会写一个“自检固件”,里面包含时钟验证、GPIO 全口翻转、串口回环、定时器中断四个部分,点灯只是其中最基础的一项。这套固件跑通了,后面无论接什么外设,我都知道底子是干净的。省下来的排查时间,远比写这套固件花的时间多。