简介:面向辉芒微FT60F02X、FT61F023等8位单片机初学者与嵌入式工程师的C语言编程笔记,围绕寄存器级开发中头文件缺失、引脚未定义、寄存器地址不明等常见问题给出可直接参照的写法。文档依次梳理头文件包含、LED引脚定义与volatile bit位寻址、变量与标志位声明、系统时钟8MHz配置、PWM3内部快时钟与10位占空比设置、Timer0每250微秒中断、ADC通道选择及右对齐查询式转换等模块,并附寄存器地址自行补定义、I/O方向与上下拉配置的示例,便于对照规格书完成底层驱动搭建与调试。文档以代码片段配合注释呈现,可减少查阅手册时的试错成本。压缩包仅含1个docx文件,约821KB,轻量易读,适合作为查表式速查资料。该笔记已有1433人学习,对刚接触辉芒微工具链、需要快速理清初始化顺序和外设配置思路的读者较有参考价值,也可作为项目代码模板的来源。
1. 一份 8 位单片机 C 语言笔记值不值得留着:从 PA1 报未定义说起
手头这份单片机 C 语言笔记,本质不是教程,而是一份工程现场的速查表:头文件怎么包、引脚位怎么定、寄存器缺了怎么按手册地址补、PWM 和定时器怎么初始化、ADC 怎么取值,全是一条条能直接粘进工程的代码片段。它面向的是写小家电控制板、LED 驱动、小功率电源这类 8 位 MCU 项目的人——Flash 只有几个 K,没有 HAL 库,没有图形化配置工具,寄存器名字得自己一个一个敲。真正让它有价值的地方不是代码写得多漂亮,而是它把「编译器报 PA1 未定义」「换个型号寄存器地址对不上」这些真实卡点明明白白写在了纸面上,还给了绕过去的办法。下面按上手顺序拆一遍,从编译能过,到波形对得上,再到采回来的数不跳。
2. 头文件与位定义:PA1 报未定义时先看哪三处
这颗芯片的开发环境基于 HI-TECH 风格的编译器,语法上和标准 C 有出入,最典型的就是@绝对地址定位和bit类型。刚上手的人十个里有八个会卡在第一行#include上,剩下两个卡在「我明明定义了 PA1,它说不认识」。
2.1 #include 后面的分号与头文件顺序
原笔记给的开头是这样:
#include "SYSCFG.h"; #include "FT60F02X.h"; /* 注意需要分号结尾 */逻辑说明:SYSCFG.h一般承载的是配置字相关的宏定义(看门狗、复位电压门限、振荡模式选择这一类),它必须排在所有头文件最前面,因为配置字是烧录进芯片特定区域的,编译器在处理第一个源文件时就要拿到这些值,后面再包含就来不及了。FT60F02X.h提供的是该型号的 SFR(特殊功能寄存器)定义,也就是PORTA、TRISA、OSCCON这些名字到地址的映射。
参数说明:末尾那个分号不是笔误。HI-TECH 系的预处理器对#include "x.h";这种写法是宽容的,多余的分号会被吞掉,所以作者写习惯了就一直带着。但如果你把同一份代码丢进标准的 GCC 预处理器或者某些代码检查工具,它会报「extra tokens at end of #include directive」一类告警。我一般会把这个分号去掉,除非确认整个工程链都吃这一套。
头文件找不到的排查顺序,我踩过几次,基本是这三步:
- 看工程设置里的 include 搜索路径有没有把
SYSCFG.h所在目录加进去,相对路径和绝对路径的行为不一样。 - 看文件名大小写。同一套代码在 Windows 上能编,换到区分大小写的主机上就找不到,
syscfg.h和SYSCFG.h是两个文件。 - 看头文件是不是跟芯片型号绑定的。
FT60F02X.h对应的是一个系列,换成同厂另一个系列的片子,这个文件名就得换,硬套会编过但寄存器地址全错,这种错误最难查,因为编译期一声不吭。
2.2 用 volatile bit 把引脚钉到绝对地址
引脚定义是最常见的翻车点:
#define LED1 PA1 #define LED2 PC2如果编译报PA1、PC2未定义,说明当前头文件版本比芯片手册旧,或者这个头文件压根没给位定义。原笔记给的兜底写法是:
volatile bit PA1 @ ((unsigned)&PORTA * 8) + 1; volatile bit PC2 @ ((unsigned)&PORTC * 8) + 2;逻辑说明:@是 HI-TECH 编译器的绝对地址定位语法,告诉链接器「别给我分配空间,这个变量就住在那个地址上」。&PORTA取的是 PORTA 这个 SFR 在数据空间的字节地址,乘 8 换算成位地址空间的基址,+1就是 bit1。所以整句话的意思是:把PA1这个名字绑定到 PORTA 的 bit1 上,读写它等同于读写那个物理引脚对应的锁存位。
参数说明几个关键点:
volatile不能省。省掉之后编译器看到你在循环里反复读PA1却没改过它,会把它优化成一个寄存器缓存,按键按下去程序也感知不到——这是新手最常遇到的「程序跑飞」假象,其实是优化器干的。unsigned强转是为了避免&PORTA被当成有符号数做乘法时溢出或产生符号扩展。* 8里的 8 是固定值,因为一个字节 8 位,位地址空间是字节地址空间的一维展开。
坑在这里:如果头文件里已经有PA1的定义,你再写一遍就是重复定义,编译器会直接报错。所以正确做法是把它当后悔药,只在报未定义的时候加,加完编过了就别留在工程里当常驻代码。我一般的习惯是在自己的board.h里用条件判断包一层,或者干脆全部用手写的位定义,把厂商头文件里那部分屏蔽掉,统一管理,省得哪天换了头文件版本两边打架。
2.3 变量与标志位的类型选择
原笔记给的类型清单很朴素:
char i = 0; unsigned char num = 0; int time = 0; unsigned int second = 0; bit time_flag = 0; bit key_flag = 0;在 8 位机上这几个类型的实际开销差别很大,值得单独说清楚,因为 RAM 往往只有一百多个字节,选错类型后面就得为几个字节发愁。
| 类型 | 位宽 | 取值范围 | 典型用途 |
|---|---|---|---|
char | 8 | -128 ~ 127 | 有符号中间量,做差值比较时用得上 |
unsigned char | 8 | 0 ~ 255 | 计数、查表下标、状态机 |
int | 16 | -32768 ~ 32767 | 定时计数、需要负数的差值 |
unsigned int | 16 | 0 ~ 65535 | 秒计数、ADC 累加 |
bit | 1 | 0 / 1 | 单标志位,省 RAM |
逻辑说明:8 位机的int固定是 16 位,不是桌面上的 32 位,所以int time和unsigned char num在 RAM 占用上是 2 比 1。bit类型是这颗芯片的架构特性,位寻址空间里的一个位,读写效率高,但数量有限,而且跨 bank 访问要多花一条指令。
参数说明:bit变量用来做「超时标志」「按键有效标志」这类只会取 0 或 1 的量非常合适,比unsigned char省 7 个位。但如果标志位的读写分散在中断和主循环两边,要注意原子性——8 位机写一个unsigned char是一条指令,写bit也是一条,两者都不会被打断,这一点倒是省心。
3. 寄存器补全与系统时钟:型号对不上手册时怎么兜底
编过了第一关,第二个关卡是「部分寄存器未定义」。这个问题的本质是头文件滞后于芯片,厂商新出的型号加了外设,头文件没跟上,或者你用的是同系列另一个型号的头文件,恰好缺了你要用的那几个。
3.1 @ 0x081 这种绝对地址定义在做什么
原笔记给的做法是自己在主文件里补定义:
#include "SYSCFG.h"; #include "FT60F02X.h"; /* 头文件里缺失的 SFR,按规格书地址补 */ volatile unsigned char OPTION @ 0x081; volatile unsigned char EPWMCR0 @ 0x11E; volatile unsigned char TMR3L @ 0x10C; volatile unsigned char TMR3H @ 0x118; volatile unsigned char PR3H @ 0x10D; volatile unsigned char PR3L @ 0x10E; volatile unsigned char PWM3CR0 @ 0x10F; volatile unsigned char PWM3CR1 @ 0x110; volatile unsigned char T3CKDIV @ 0x111;逻辑说明:这里的@ 0x081是数据空间的线性绝对地址,不是程序空间。编译器看到这个声明后,不会给变量分配 RAM,而是把所有对它的访问直接翻译成对该地址的读写,必要时自动插入 bank 切换指令。
参数说明:为什么OPTION是0x081而不是0x01?因为数据空间按 bank 分页,每页 128 字节,0x081落在 bank1 的第一个字节。写地址的时候一定要区分「手册上的 bank 内偏移」和「编译器要的线性地址」,两者差了一个 bank 基址。我见过最冤的一次是照着手册写了@ 0x01,编过了,运行起来配置全乱——因为它把 bank0 的某个变量给覆盖了。
还有一点:这类定义必须带volatile。SFR 的值会被硬件改变,编译器不知道,不加volatile就会把连续两次读同一个 SFR 优化成一次,读出来的状态位永远是旧的。
补定义的纪律性要求很高。地址抄错一个十六进制位,编译器不会报错,程序行为却是玄学级的:可能 PWM 没输出,可能定时器跑飞,可能某个引脚莫名其妙一直输出高。我一般的做法是补完之后立刻写一段自检,往寄存器写一个特征值再读回来,读不回来就说明地址错了。这种黑匣子式的排查,比盯着代码看半小时强。
3.2 system_init 里 OSCCON 与 PCON 的取值
系统时钟是整个工程的地基,时钟错了,后面所有定时、波特率、PWM 频率全错:
void system_init(void) { OSCCON = 0x61; /* 设置系统时钟为 8MHz */ PCON = 0x2b; PORTA = 0x00; /* PORTA 初值清零,避免上电毛刺 */ TRISA = 0xff; /* PORTA 全部设为输入 */ }逻辑说明:OSCCON = 0x61是选择内部 RC 振荡器并设定频率档位,具体哪几位控制频率分频,必须对着这颗芯片的规格书看,照抄到别的型号上大概率出问题。PCON = 0x2b涉及复位源标志和欠压复位配置,这一句的风险最大,因为PCON里通常混着只读位和「写 1 清零」位。
参数说明:我一般不直接整字节覆写PCON,而是先读出来判断复位源,再按需改。原因很实在——PCON里如果有看门狗复位标志,你一句PCON = 0x2b就把它清了,等于把「上次为什么重启」这条线索抹掉了。上板调试阶段这条线索比什么都金贵,我吃过亏,后来固定改成读-改-写。
TRISA = 0xff把 PORTA 全设成输入,这个做法在板子刚上电时是安全的,但要记得:如果某个引脚外接了下拉电阻或者接了 LED 到地,设成输入后是高阻态,引脚电压不确定,可能有微弱漏电或者误触发。比较稳的做法是先设成输出并写一个确定电平,等外设初始化完了再切成需要的方向。原笔记里后面 PWM 那段就是这么干的——先TRISC4 = 1置输入,配置完再TRISC4 = 0切输出。
3.3 用编译告警反查缺失的 SFR
补寄存器这件事有一套固定的动作,我把它固化成四步:
- 全量编译,把所有
undeclared identifier的报错收集起来,一条条对应到手册的 SFR 表。 - 打开规格书的数据手册章节,找到 SFR 映射总表,记下每个寄存器的 bank 和偏移,换算成线性地址。
- 按
volatile unsigned char 名字 @ 地址;的格式补进一个独立的sfr_ext.h,不要散落在主文件里。 - 补完后写一段读写自检,把能写的位写进去读回来比对。
提示:补定义一定要集中在一个文件里,别东一个西一个。换型号的时候只需要改一个文件,不用满工程搜索。
这套流程跑下来,一个中型工程补十来个寄存器大概二十分钟。真正耗时的不是补,是核实地址——手册上的表格经常有勘误,同一个寄存器在不同版本的文档里地址不一致,这时候以最新版数据手册为准,同时用读写自检兜底。
4. PWM 与定时器:16 位寄存器分两次写的顺序问题
时钟配好之后,最常用的两个外设就是 PWM 和定时器。PWM 驱动 LED 调光、驱动蜂鸣器发声、驱动小功率 MOS 管,定时器做系统节拍和按键扫描,这两个东西写不对,板子基本等于砖。
4.1 PWM3 初始化的三件事:时钟源、位宽、引脚方向
原笔记给的初始化代码如下:
void Pwm3_Init(void) { TRISC4 = 1; /* 先把引脚设为输入,禁止输出,避免配置过程中出毛刺 */ EPWMCR0 = 0x10; /* PWM 时钟源选内部快时钟 32MHz */ PWM3CR0 = 0xd2; /* 中断方式 + PWM 阶数为 10 位 */ PWM3CR1 = 0x80; T3CKDIV = 0x00; /* 计数频率 = 32MHz / (1 + T3CKDIV) */ T3CKDIV = 0x00; /* ... 周期与占空比见下一节 ... */ TMR3ON = 1; /* 启动定时器 3 */ TRISC4 = 0; /* 配置完成,引脚切为输出 */ }逻辑说明:这段代码的顺序是有讲究的。第一句TRISC4 = 1把引脚切成高阻输入,这样在配置 PWM 模块的过程中,即使比较寄存器里是随机值,引脚上也不会输出一个宽度不可控的脉冲去干扰后级电路。等周期、占空比、时钟源全部就位,最后一句再切成输出,波形从第一个周期就是干净的。
参数说明:EPWMCR0 = 0x10选的是内部快时钟。这里有个型号差异必须提醒——原笔记自己就标注了,这段代码针对的是带 32MHz 时钟源的型号,同厂另一个型号根本没有 32MHz 时钟源,同样的值写进去要么无效要么跑出完全不对的频率。换型号第一件事是翻时钟树章节,确认最高时钟是多少,别照抄。
PWM3CR0 = 0xd2里同时编码了中断方式和 PWM 位宽。10 位分辨率意味着占空比可以分 1024 档,档位越多调光越细腻,但同样的时钟下 PWM 频率会越低。这是个典型的取舍:调光要细腻就牺牲频率,要频率高就牺牲分辨率。驱动 LED 一般 1kHz 到 4kHz 够用,10 位分辨率配 1MHz 左右的计数时钟比较合适;驱动 MOS 管做电源就得往上提到几十 kHz,那时候只能降到 8 位。
T3CKDIV = 0x00是分频系数,实际计数频率等于时钟源除以(1 + T3CKDIV)。分频系数越大,同样周期值对应的 PWM 频率越低。原笔记里这句写了两遍,属于笔误,但无害——同一个值写两次结果一样。
4.2 周期与占空比的写入次序
16 位寄存器在 8 位机上是拆成两个 8 位寄存器访问的,这里最容易出错。原笔记的写法是这样的:
PR3H = 0x30; TMR3H = 0x30; TMR3L = 0xe8; /* 设置 PWM 周期,先高 4 位再低 8 位,此处设为 1000 */ PR3L = 0xc8; /* 设置 PWM 占空比,先高 4 位再低 8 位,此处设为 200 */逻辑说明:PR3H/PR3L是周期寄存器,TMR3H/TMR3L是计数/比较寄存器。原笔记的注释和实际写的数值对不上——注释说周期是 1000、占空比是 200,但写进去的0x30C8是 12488,0x30E8是 12520。这是抄代码时最危险的一类问题:注释是给人看的,寄存器只认数值。
我把这段重写一遍,并且给一个能自己算的公式:
/* 目标:PWM 频率 f_pwm,计数时钟 f_clk,10 位模式 */ /* 周期寄存器值 = f_clk / f_pwm - 1,且必须 <= 1023(10 位上限) */ #define PWM_CLK_HZ 1000000UL /* 计数时钟 1MHz */ #define PWM_FREQ_HZ 1000UL /* 目标 1kHz */ void Pwm3_SetPeriod(unsigned int period) { unsigned char hi = (unsigned char)((period >> 8) & 0x03); /* 10 位模式只保留高 2 位 */ unsigned char lo = (unsigned char)(period & 0xFF); PR3H = hi; /* 先写高字节 */ PR3L = lo; /* 再写低字节 */ } void Pwm3_SetDuty(unsigned int duty) { TMR3H = (unsigned char)((duty >> 8) & 0x03); TMR3L = (unsigned char)(duty & 0xFF); } void Pwm3_Init(void) { unsigned int period = (unsigned int)(PWM_CLK_HZ / PWM_FREQ_HZ) - 1; /* 999 */ TRISC4 = 1; EPWMCR0 = 0x10; PWM3CR0 = 0xd2; PWM3CR1 = 0x80; T3CKDIV = 0x00; Pwm3_SetPeriod(period); Pwm3_SetDuty(period / 2); /* 上电给 50% 占空比,方便示波器抓 */ TMR3ON = 1; TRISC4 = 0; }参数说明:为什么写高字节再写低字节?大多数这类芯片的高字节寄存器带缓冲,写低字节的瞬间 16 位值才整体生效。反过来写的话,中间会短暂出现一个「新低字节 + 旧高字节」的拼接值,PWM 会出一个毛刺。原笔记注释里那句「先设置高 4 位,再设置低 8 位」说的就是这件事,只是把「高字节」写成了「高 4 位」,容易让人误会成 4 位。
还有一点:10 位模式下周期寄存器只有低 10 位有效,高 6 位写进去可能被忽略也可能被当成别的配置位。上面代码里用& 0x03强制只保留高 2 位,虽然保守,但避免了写到保留位上引发意外。宁可按最保守的位域写,也不要赌手册没写清楚的那几位。
4.3 Timer0 250us 中断的重装值计算
定时器 0 用来做系统节拍,原笔记的目标是 250 微秒中断一次:
void interrupt ISR(void) { if (T0IF) /* 定时器 0 中断标志 */ { T0IF = 0; /* 清标志,必须手动清 */ TMR0 = 0x06; /* 重装初值 */ if (Test) Test = 0; else Test = 1; /* 每 250us 翻转一次,用示波器看周期是否 500us */ } } void TimeCnt0_Init(void) { TRISA4 = 0; /* 测试脚设为输出 */ OPTION = 0x01; /* 预分频 1:4,分配给 TMR0,时钟源为指令周期 */ TMR0 = 0x06; T0IF = 0; T0IE = 1; /* 开定时器 0 中断 */ GIE = 1; /* 开总中断,原笔记里漏了这句 */ }逻辑说明:算重装值是这套东西的核心。系统时钟 8MHz,指令周期按 2T 算就是 4MHz,每个指令周期 0.25 微秒。250 微秒需要 1000 个指令周期。TMR0 是 8 位,最多数 256 个,所以要靠预分频先降 4 倍,得到 250 个计数,重装值就是256 - 250 = 6,即0x06。
参数说明:OPTION = 0x01这一个字节同时决定了四件事——预分频比、预分频分配给谁、定时器时钟源、以及所有 PORTA 引脚的弱上拉使能。0x01拆开看是「预分频 1:4、分配给 TMR0、时钟源为内部指令周期、弱上拉关闭」。最后那条最容易被忽略:如果工程别的地方依赖 PORTA 的内部上拉来接按键,这一句下去上拉全没了,按键就再也读不到。
原笔记的ISR里没有GIE = 1,这在完整工程里是必须补的,否则中断开了也进不来。另外中断服务函数的名字和修饰符必须跟编译器约定一致,HI-TECH 系是void interrupt ISR(void),写成void ISR(void)编过但永远不进。
验证方法很土但有效:把Test引脚接示波器,看翻转周期。250 微秒中断一次、每次翻转,引脚上应该是 500 微秒的方波。如果测出来是 2 毫秒,说明指令周期算错了 4 倍,回头查芯片是 1T、2T 还是 4T 架构。这个参数在手册的「指令周期」章节,不在定时器章节,别找错地方。
5. 常见问题排查:五条上板翻车记录与定位路径
前面几章讲的是「应该怎么写」,这一章讲「写完了不对怎么办」。下面五条都是我或者身边人真实遇到的,按现象、原因、解决三段式记,方便直接对照。
5.1 编译阶段:未定义、重复定义、头文件找不到
现象一:报PA1未定义。原因:头文件版本不包含该引脚的位定义,或者这个头文件根本不是给你手上这颗型号用的。 解决:先用volatile bit PA1 @ ((unsigned)&PORTA * 8) + 1;临时顶上,确认能编过、能点灯之后,再去找匹配的头文件版本。临时定义不要长期留在代码里,换头文件那天会撞车。
现象二:加了位定义之后报重复定义。原因:新换的头文件里已经有PA1了,你手写的那份和它冲突。 解决:把手写的那几行注释掉,重新编译;如果又变成未定义,说明头文件没生效,检查 include 顺序和路径。我一般的做法是把所有手写位定义集中到board.h,用宏开关包起来,切换编译选项,而不是每次手工注释。
现象三:SYSCFG.h找不到。原因:工程 include 搜索路径没配,或者文件名大小写不匹配。 解决:优先看工程设置里的包含目录;其次把文件重命名成全大写试一次;最后确认这个头文件是不是必须放在工程根目录而不是子目录里。有些老编译器只认工程文件同级的目录。
5.2 时钟与定时器:中断不进、周期差几倍
现象四:定时器中断周期差 4 倍。原因:把系统时钟当成了指令周期。8MHz 是振荡频率,指令周期要看架构是几分频,2T 就是 4MHz,4T 就是 2MHz。 解决:翻手册确认指令周期,重新算重装值。算的时候把中间量写下来:f_osc = 8MHz、T_cyc = 0.25us、需要计数 = 250us / 0.25us = 1000、预分频 4、重装 = 256 - 250 = 6。中间量写在注释里,下次换芯片直接改数。
现象五:改完OPTION之后按键读不到,或者别的引脚行为变了。原因:OPTION是共享寄存器,整字节覆写会把弱上拉使能、边沿选择这些位一起改掉。 解决:改成读-改-写。OPTION = (OPTION & 0xF8) | 0x01;这样只动低 3 位的分频比,其他位保持原样。第一次配置时可以整字节写,但要在注释里写清楚「此句会关闭所有 PORTA 上拉」,给后面接手的人留个提醒。
5.3 模拟外设:PWM 没波形、ADC 值乱跳
现象六:PWM 引脚示波器上一条直线。原因:三种可能——引脚方向还停在输入、时钟源选的档位这颗型号没有、比较值大于周期值。 解决:按顺序查。先用万用表量引脚对地阻抗,确认是不是高阻;再翻时钟树确认EPWMCR0那个值在这颗型号上有效;最后把周期和占空比打印出来比对,占空比必须小于周期。
现象七:ADC 采回来的值跳得厉害,同一路电压每次差几十个码。原因:切通道后没等稳定就启动转换、模拟引脚没设ANSEL、参考电压选了 VDD 而 VDD 本身纹波大。 解决:切通道后加 1 毫秒以上延时;确认ANSEL对应位已置 1;如果精度要求高,改用内部基准而不是 VDD 做参考。软件上再做一次中值或滑动平均,能压掉大部分随机跳变。
6. 把零散笔记变成工程骨架:一份模板与上电自检顺序
台账式的笔记看懂了,接下来要做的是把它变成每次开新项目都能直接复用的东西。我的做法是把散落在主文件里的定义抽成两个头文件:board.h管引脚映射,sfr_ext.h管寄存器补全,主文件只留业务逻辑。引脚映射用一张表固定下来,换板子只改这张表。
| 引脚 | 功能 | 方向 | 备注 |
|---|---|---|---|
| PA1 | LED1 | 输出 | 高电平点亮,串 1k 限流 |
| PC2 | LED2 | 输出 | 同上 |
| PA4 | 调试脚 | 输出 | 定时器翻转,接示波器 |
| PC4 | PWM3 输出 | 输出 | 初始化时先设输入 |
| PA0 | ADC 通道 0 | 模拟输入 | ANSEL必须置位 |
抽头文件的收益在换型号那天体现得最明显。上次从带 32MHz 时钟源的型号换到没有 32MHz 的型号,我只改了board.h里的时钟宏和Pwm3_Init里的一行时钟源配置,其余业务代码一行没动。要是当初把EPWMCR0 = 0x10散在十个源文件里,那天就得通宵。
上电自检的顺序我固定成三步,从不跳步:
void board_selftest(void) { /* 第一步:时钟。翻转一个引脚,示波器量周期确认主频 */ system_init(); TimeCnt0_Init(); /* 250us 中断,PA4 应输出 500us 方波 */ /* 第二步:GPIO。两个 LED 各闪三次,确认方向和电平定义没反 */ led_blink(LED1, 3); led_blink(LED2, 3); /* 第三步:模拟外设。PWM 给 50%,ADC 读一次通道 0 */ Pwm3_Init(); Pwm3_SetDuty(499); /* 周期 999,50% */ (void)ADC_Convert(0); }先时钟、再 GPIO、最后模拟外设,这个顺序的道理是:前一步错了,后一步的测量结果一定不可信。定时器周期不对,PWM 频率就不可能对;GPIO 方向配反,PWM 引脚量出来就是直线。倒过来查会浪费大量时间在「到底哪一层错了」上。
从那以后我每次开新工程,第一件事不是写业务代码,而是把这三个自检函数跑通,示波器上看到 500 微秒方波、两个灯正常闪、PWM 有 50% 占空比,才动手写真正要做的东西。这套习惯救过我很多次——有回板子一上电就复位,查了两小时电源,最后发现是配置字里看门狗没关,而配置字就在第一个头文件里,自检第一步就该暴露的问题,被我跳过去了。希望帮到你。
本文还有配套的精品资源,点击获取