双通道相位可调 PWM 脉冲发生小板(HC32F003,16 KB Flash / 2 KB RAM)
做这块板子的起因很实际:加注计量设备在台面上调试时,需要一路稳定的脉冲信号去代替真实的流量计, 而且最好能给出两路有相位差的信号,用来观察下游计数逻辑在正交/双路输入下的表现。 我用一片 HC32F003 做了这个小信号源:两路 PWM 占空比固定 50%、相位差 90°, 频率以“每分钟脉冲数”表达并由电位器调节,两个按键负责“连续输出 / 定量输出 100 个 / 停止”。 结论是功能全部跑通了,示波器上看波形也干净;真正的难点不在“怎么发出 PWM”, 而在“定量输出时一个都不能多、软停时最后一个周期不能被截断”——这两条我各自返工过一次。 另外,产品化那一版把电位器调节直接写死成固定频率与固定相位,这个决定好用但难看,我在文里如实写了。
一、这块小板要做什么
需求一句话能说完:输出两路方波脉冲,频率可调、相位差可调,能连续发也能只发固定个数。 它不驱动任何功率器件,输出就是两个 GPIO 上的逻辑电平,拿来给下游设备的脉冲输入端当信号源用。
最后定下来的规格是这样几条:
- 两路输出:PWM1 在 P35(复用功能 AF2)、PWM2 在 P36(AF2),都用高级定时器的两路比较输出;
- 占空比固定 50%:脉冲计数类输入只关心边沿,占空比不是重点,固定 50% 最省事;
- 相位差固定成产品设定值:给下游做双路/正交输入用的,调相位的能力保留在代码里(整圈全角度范围),但实际出厂版本只用一个固定角度;
- 频率用“每分钟脉冲数”表达:因为下游设备是按脉冲个数计量的,用 Hz 反而要在脑子里多换算一次;
- 两种输出模式:连续输出,或者定量输出固定个数脉冲后自动停;
- 两个按键:KEY1(P26)长按切换“连续输出 / 停止”,KEY2(P25)短按触发“定量输出固定个数”,KEY1+KEY2 同时按下立即停止;
- 一个指示灯:P03 上的 LED,连续输出时每 100 ms 翻转一次,停止时改成 500 ms 翻转——不看示波器也能一眼看出板子在不在跑;
- 一个调试串口:UART0 只发不收,115200 8N1,每 500 ms 打印一次当前的“每分钟脉冲数”和相位。
我特意没有做屏幕、没有做参数存储、没有做通信协议。它的定位就是一次性调好、插上就用的“信号发生器替身”, 功能越少,现场越不容易出问题。
二、在 16 KB Flash / 2 KB RAM 上做实时波形输出
先说资源账,因为它直接决定了后面所有的取舍。工程里配置的存储区是
IRAM(0x20000000, 0x800) 和 IROM(0x00000000, 0x4000),
也就是 2 KB SRAM、16 KB Flash,内核 Cortex-M0+,编译优化等级 -O1。
2 KB RAM 是什么概念:两个 ADC 通道各留 10 点的采样缓冲(每点 2 字节)就吃掉 40 字节,
再算上栈和若干全局状态,已经没有空间去做动态分配或者大缓冲了。
所以我干脆全程静态分配,不用 malloc,不做任何“先申请再判断”的写法。
16 KB Flash 也逼着我把遗留代码清干净——这个工程里恰好有一组反面教材:
目录里躺着差压传感器和 SPI Flash 的驱动文件,但从 main.c 的调用链看它们一次都没被调用。
我现在拿到陌生工程的第一步,就是对着工程文件的编译列表把“真的在编译、真的被调用”的代码切出来。
板子上用到的外设一共这几样,没有一个是多余的:
| 外设 | 配置要点 | 在这块板上的用途 |
|---|---|---|
| 高级定时器 ADTIM6 | 锯齿波向上计数,时钟 PCLK0/1024,两路比较输出 A/B,比较匹配时电平翻转 |
产生 PWM1(P35)与 PWM2(P36),相位差靠两个比较值的差实现 |
| 基本定时器 BT/TIM1 | 1 ms 基准(自动重装,重载值按定时器时基算得,本文不列具体值) | 按键计时、LED 闪烁计时、串口打印节拍 |
| ADC 通道 CH2 / CH3 | 对应 P32 / P33,内部 2.5 V 基准,每通道 10 点缓冲 | CH2 接电位器调频率,CH3 接电位器调相位(产品版已写死) |
| GPIO 按键 ×2 | P26 / P25,无内部上下拉 | 连续输出 / 定量输出 / 停止 |
| GPIO 指示灯 | P03 | 运行状态心跳 |
| UART0 | 115200 8N1,只用 TX(P14,AF6),接收中断全部注释掉 | 打印当前频率与相位,纯粹给人看 |
没有 RTOS。main() 里就是一个 while(1),按顺序调
get_adc_data()、GetKeyValue()、pwm_adjust(),
波形输出完全靠定时器中断自己跑,主循环慢一点、卡一下都不影响波形——这一点很重要,
因为主循环里要跑冒泡排序(中值滤波),那是个不确定耗时操作,绝不能让它出现在波形路径上。
HC32F003C4PA,但工程实际引用的启动文件与系统文件是同族的另一档型号。
两者封装与资源同族,功能上跑得通,但“器件字段与启动文件不一致”本身就是个隐患:
它意味着有人换过型号而没换全。我至今没有把最终型号核对清楚,所以本文标题里的型号只按工程配置写,
实际批次的器件以硬件实物为准。这件事教给我的是:型号要从“实物丝印 + 链接文件 + 器件配置”三头核对,
只看一处迟早要吃亏。
三、频率与相位怎么算:从公式到寄存器
这块板子的核心只有一条换算链:输入三个量——目标频率(用“每分钟脉冲数”表达)、 相位角、占空比,输出“一个周期值 + 四个翻转点比较值”,再把它们写进定时器寄存器。 出于对个人项目实现细节的保护,这条换算链属于核心实现, 所以这里不再给代码,只把量纲怎么一步步落到寄存器上讲清楚。
换算的顺序是固定的,而顺序本身比公式重要:
- 先统一时间量纲:把“每分钟多少个脉冲”折算成“每个脉冲占多长时间”,得到脉冲周期的时间量纲(秒)。这一步只是分钟与秒之间的换算,不涉及任何标定。
- 再把时间量纲换成计数量纲:定时器数的是“计数个数”,不是秒。这一步要用一个“时间 → 计数”的系数,它由定时器时基决定;我的工程里这个系数是实测标定出来的,写在代码里,但不在本文公开。
- 由周期值算出两个翻转点:占空比决定这两个翻转点在周期内的相对位置——一个是靠近周期末尾的翻转点,一个是靠近周期起点的翻转点。
- 把相位角折算成计数偏移:整圈对应一个完整周期,所以相位角就是“周期值的一个比例”;把第二路的两个翻转点整体平移这么多计数,两路之间就有了相位差。
- 做回绕与边界保护:平移之后的翻转点越过周期值时要按一个周期回绕;恰好等于周期值时按下面说的通用边界规则处理。
- 最后写寄存器:周期值写进周期寄存器,四个翻转点分别写进两路的比较寄存器,输出模式配成“比较匹配时电平翻转”。
把这套顺序整理成表,就是一条“输入量 → 中间量 → 寄存器”的推导链。 表里只写量纲、动作与落点,不写任何系数与数值:
| 步骤 | 输入量(量纲) | 中间量(量纲) | 换算动作(只讲顺序) | 落到哪里 |
|---|---|---|---|---|
| 1 | 目标频率(每分钟脉冲数) | 脉冲周期(时间量纲) | 把“每分钟多少个”折算成“每个占多长时间” | —(仍是中间量) |
| 2 | 脉冲周期(时间量纲) | 一周期的计数个数(计数量纲) | 乘一个“时间 → 计数”的系数;该系数由定时器时基决定,本文不给具体值 | 周期寄存器 |
| 3 | 占空比(百分比) | 两个翻转点的计数值 | 按比例在周期内取两个点,并做上下限钳位 | 第一路比较寄存器 |
| 4 | 相位角(角度) | 计数偏移量(计数量纲) | 按“整圈对应一个周期”把角度折算成计数 | —(作为第二路的平移量) |
| 5 | 翻转点 + 偏移量(计数) | 回绕后的翻转点(计数) | 越过周期值就减掉一个周期;等于周期值时按边界规则让开 | 第二路比较寄存器 |
几处值得单独说的:
占空比为什么要钳位
因为这一路输出不是硬件 PWM 模块自动生成的,而是每个比较点中断一次、在中断里手动切换比较值。 如果占空比贴到 0% 或 100%,两个比较点就重合了,“翻转”这件事在数学上还在、在时序上已经不成立, 输出会停在某个电平不动。所以我在换算的最后一步做钳位,把占空比限制在“两端各留一点余量”的区间里; 具体上下限是产品设定值,不在本文给出。反正脉冲计数只认边沿,留余量不影响用途。
那个“时间 → 计数”的系数是怎么来的
按道理它是可以推出来的:定时器的计数时钟是确定的,先算出“一个计数代表多长时间”, 再用它去除脉冲周期,就能得到计数个数。但我在这块板子上没有把它推到底—— 工程里写的是一个实测标定出来的系数,而不是从时钟树推导出来的值。 两者当时对不上,说明要么时钟配置与我以为的不一样,要么这个常数就是标定值; 我没有核完,就照原样保留,并在注释里写清“这是标定值,不是推导值”。 这一段换算链恰好属于本文不公开的部分,所以这里只留结论: 系数来自实测标定,本文不给具体数值。 这里我宁可留一个诚实的疑问,也不想编一个看起来漂亮但错的推导。
相位为什么就是两个比较值之差
这是“比较中断翻转”方案最舒服的地方:相位差和占空比在同一个坐标系里。 周期值对应整圈角度,所以相位角就是“周期值的一个比例”; 相移多少度,就是把第二路的翻转点整体往后挪这么多个计数。 想改相位,改一个比较值就够了,不需要重算波形表,也不需要动硬件。
ARR == CCR 时,这个周期内不会产生比较匹配,也就没有翻转,波形直接少一拍,
下游计数跟着少一个。改法是三重判断——比较值等于周期就减 1,大于周期就减掉一个周期,否则用原值。
这类“只在某个特定频率/相位组合下才出现”的问题,靠试是试不出来的,
只能把边界条件一条条写死在代码里。
四、用比较中断翻转电平:为什么不用 PWM 外设
这块芯片的高级定时器是有硬件 PWM 模式的,配好周期和比较值就能自动输出,CPU 一次都不用管。 我没有用它,而是用了更笨的办法:把通道配成“比较匹配时电平翻转、周期匹配时保持不变”, 然后在比较中断里交替把比较值换成“下一个沿”的位置。 中断服务函数本身很短,但它是这块板子的核心实现,所以这里只讲三个设计点,不给代码。
- 为什么要在中断里“交替切换”比较值。 硬件 PWM 模式只在一个固定的比较点上动一次电平;我要的是“一个周期内翻转两次、 而且两次的位置我都能改”。做法是把两个翻转点都先算好存起来,中断里先看当前电平是高还是低: 当前是高,说明刚越过的是靠近周期末尾的那个翻转点,于是把比较值换成靠近起点的那一个; 当前是低,就换回末尾那一个。两个状态来回切,波形就接上了。 切换的依据是“当前电平状态”,不是时间—— 这样即使某一次中断被延迟,也不会因为“少翻一次”而错位。
- 回绕时怎么保护。 第二路的翻转点里含了相位偏移,平移之后可能越过周期值。 如果不处理,比较值大于周期值时这个周期就不会产生比较匹配,波形直接少一拍。 我的做法是在计算阶段就把回绕做完:越过周期值的减掉一个周期, 恰好等于周期值的按通用边界规则再让开一个计数, 保证写进寄存器的比较值始终落在一个周期之内。 保护放在计算阶段、而不是放在中断里, 中断里就只剩“清标志 + 换比较值”两件事,耗时是确定的。
- 为什么先关输出、再停定时器。 停止时如果直接停定时器,最后半个周期里可能还留着一次没有执行的翻转, 于是多出一个边沿;而下游只数边沿,多一个就是多计一份。 所以顺序固定是:先把通道的输出使能位清掉,让“那一次多余的翻转”没有引脚可翻, 然后再停定时器。顺序反了,问题就回来—— 这两行的先后关系比它们本身重要得多。
为什么绕这一圈?因为我要的不是“一路稳定的 PWM”,而是“两路之间有一个随时可改、且我能精确控制落点的相位差”。 硬件 PWM 模式擅长的是“占空比自动维持”,而相位这件事在硬件里要么靠主从定时器触发(配置复杂, 改相位不直观),要么根本没法在小芯片上做。把翻转点攥在自己手里之后, 相位差就是两个比较值之差,改一个数就生效,示波器上立刻能看到。
代价也是明摆着的,我认了:
- 中断次数是输出频率的两倍。每个周期两次比较匹配,就是两次中断;频率越高,CPU 越忙。
- 中断里不能做耗时的事。所以中断里只允许“清标志 + 换比较值”,ADC 滤波、按键扫描全部赶到主循环去。
- 异常情况下会留下“半口气”的输出,这正是后面两个真实约束的来源。
在这个应用里,输出频率本身不高、板子也只需要干这一件事,所以这个代价完全付得起。 但如果哪天要输出几十 kHz 以上的脉冲,我会毫不犹豫换成硬件 PWM 模式,相位改用定时器主从触发来做。 方案没有优劣,只有“这个场景下值不值”。
五、两个真实约束:定量输出不能多打、软停必须停在整周期
这两个约束都不是我自己想出来的,是下游逼出来的:脉冲计数设备只数边沿,多一个就是多计一份、少一个就是少计一份。 所以“大致差不多”在这里等于不合格。
约束一:定量输出 N 个,一个都不能多
实现思路很直白:进入定量模式时把剩余个数记在计数变量里,通道中断里递减,减到 0 就停。 真正难的是在哪里停。我第一版是数到 0 直接停定时器,结果做定量输出时, 下游有时候会多计一个脉冲。原因是:比较中断里刚换完比较值,如果这时候直接把定时器停掉, 最后半个周期里可能还留着一次翻转没执行完,于是多出一个边沿。
改法是先关输出使能,再停定时器——把通道的输出使能位清掉,让“多出来的那一次翻转”没有引脚可以翻, 然后再停。这一条在源码注释里我写得很直白:“固定波形个数结束(先关闭通道,以防止波形再次多翻转)”。 顺序反了,问题就回来。
约束二:停止时最后一个脉冲必须是完整的
定量输出解决了,手动按停止又出了新问题:按下去的时机是随机的,如果立刻关输出, 最后一个脉冲可能只发了一半——高电平发出去、低电平还没回来就断了。 对只认边沿的计数器来说,这就是一个“半个脉冲”,有的设备会把它算作一个,有的不算, 于是同一个动作在两台设备上表现不一致。
我的做法是软件同步停,按这个顺序走:
- 用捕获寄存器死等,直到当前计数值等于第一路的翻转点,再关掉第一路输出——保证第一路的最后一个周期是完整的;
- 继续死等,直到计数值等于第二路的翻转点,此时第二路的最后一个周期也完整了;
- 再执行软件同步停止,先延时几个计数刻度让状态落定,最后清计数器和中断标志。
说“死等”听起来很不优雅,但这几微秒到几十微秒的等待,换来的是“不多不少”这个确定性。 在这种场景下,确定性比优雅重要。真正要忌讳的是在主循环里死等, 而这里的等待发生在“用户刚按下停止”这一刻,最多等一个脉冲周期,人完全感觉不到。
六、频率调节与中点滤波
两个电位器分别接在 ADC 的 CH2(P32)和 CH3(P33)上,用内部 2.5 V 基准, 一个调频率、一个调相位。采集方式很朴素:主循环里每轮各采一次, 分别存进两个 10 点数组;某个通道采满 10 次之后,做一次冒泡排序,取中间那个值作为本次结果, 然后清掉缓冲重新开始。
为什么取中值而不是取平均?因为电位器这个信号源最典型的干扰是脉冲型尖峰—— 手碰一下、电源纹波抖一下、旁边继电器吸合一下,都会在采样序列里留下一个孤零零的坏点。 平均值会被这个坏点整体带偏,而排序取中值会直接把它扔掉。 这也是我在别的采集项目里一贯的做法:排序 + 去极值,再取中值或者对剩下的点求平均。 纯滑动平均在这种干扰环境下反而是最差的选择。
中值出来之后还要做一次线性整定,把 ADC 码值映射成人能理解的两组量: 频率通道映射成“每分钟脉冲数”,相位通道映射成角度。 这一段属于标定换算链,所以下面只讲结构,不给任何除数、门限与写死的常量。
整定的结构是三段,顺序不能换:
- 死区段:电位器拧到行程一端附近时,ADC 码值几乎不再变化。 这一段如果也参与映射,端点附近就会输出一个乱跳的小值, 看起来像“设备坏了”。所以先划一段死区:码值落在死区里,直接按最小值处理。 死区的边界是我一边拧电位器、一边看串口打印定下来的,具体码值不在本文给出。
- 线性映射段:把死区之外剩下的行程线性摊到目标量程上—— 频率通道摊到“每分钟脉冲数”的量程,相位通道摊到角度量程。 这一步要用一个除数(剩余行程 ÷ 目标量程),它同样是实测整定出来的,不是算出来的; 本文只说明它的作用,不给数值。
- 下限(上限)钳位:映射结果再做一次钳位。 频率通道要设一个下限,不允许输出低到“停摆”, 否则看起来就像这块板子没在工作;相位通道则把超出量程的结果钳到量程端点上。 钳位值同样是产品设定值,不在本文公开。
需要如实说明的是:这三段里的死区边界、除数与上下限都不是算出来的, 是我一边拧电位器、一边看串口打印和示波器凑出来的整定值。 我在代码注释里统一标了“整定值”三个字,以免以后自己以为它们有什么理论依据。 这类产品整定值不再出现在文章里—— 这一段我保留下来的是“死区 + 线性映射 + 钳位”这个三段结构, 因为结构是可以复用的,数值不行。
七、产品化时把 ADC 结果写死:一个实用但难看的做法
上一节那三段整定算完之后,最后一步本来是“把结果赋给频率变量”。 产品版里,这一行被我换成了一个写死的产品设定值: 前面辛辛苦苦滤波、整定出来的结果,被这一行直接覆盖掉了。相位那边也一样, 主程序里把相位写死成另一个产品设定值。也就是说, 实际出厂的那一版,电位器调节这条路径等于被旁路了, ADC 只剩下“采集”和“打印”两个作用。
/* 错误示范(反模式,仅用于说明问题,不是我的产品代码):
滤波与整定都算完了,最后两行又把结果覆盖成固定值——
后果是"看代码以为能调,实际调不动",整定那一段还变成了死代码。 */
static void pwm_adjust(void)
{
f_adc = median_filter(adc_buf); /* 滤波 + 整定:算了,但白算 */
x_adc = phase_map(adc_buf);
f_adc = FREQ_FIXED; /* ← 写死的产品设定值,数值不公开 */
x_adc = PHASE_FIXED; /* ← 同上 */
}为什么这么干?因为现场标定时发现电位器方案的一致性不够好:同一批板子、同样的刻度位置, 输出频率落点有可见差异;换一批电位器差异更明显。这个信号源是要拿去当“标准”用的, 如果每台都要单独标一次、还要在机壳上做刻度,成本和不确认性都不划算。 于是改成:频率固定成产品设定值(注释里只写了这个值对应的工作点),相位也固定成产品设定值。
好用是好用,但这个做法有三个后遗症,我后来吃到了:
- 代码里出现了两套真相。看代码,它能调;实际用,它调不动。下一个读这段代码的人(很可能就是几个月后的我)会先被误导一次。
- 那一整段滤波与整定代码变成了“死代码”,但它还占着 Flash、还每轮照样跑一遍排序。在 16 KB 的小芯片上这不是小事。
- 没有任何注释说明为什么。要不是我还记得现场标定那件事,这段代码就是纯粹的谜。
如果让我重做一次,我会用一个编译开关把两条路径显式分开, 在注释里写清日期、原因和“哪一版开始改成固定值”,而不是留一行赋值在那里当暗雷。 这也是我后来给自己定的一条规矩:凡是“看起来还能用、其实已经不用”的代码,要么删掉,要么用开关围起来并写清原因。
八、问题与解决
把这块板子上真实发生过的问题按“现象 → 原因 → 办法”列成一张表,比按时间顺序回忆有用得多:
| 现象 | 原因 | 办法 |
|---|---|---|
| 定量输出固定个数,下游却多计了一个 | 比较中断里改比较值的方式,直接停定时器时最后半个周期可能再翻转一次 | 结束前先把通道输出使能关掉,再停定时器,让多出来的那次翻转没有引脚可翻 |
| 手动按停时最后一个脉冲只有半个 | 停止是异步的,最后周期被截断 | 软件同步停:死等到计数值等于翻转点再关输出;停后延时几个计数刻度再清计数与中断标志 |
| 某些频率/相位组合下偶尔少一整个周期 | 比较值等于周期值(ARR == CCR)时该周期没有比较匹配 |
三重判断:等于周期减 1、大于周期减一个周期、否则用原值 |
| 长按与短按会重复触发同一个动作 | 一次按下同时满足“按下一次”和“长按”两个条件,处理分支先后执行 | 用四个按键状态锁标志做互斥;双键同时按的分支单独处理“停止”和“解除长按锁” |
| 不同板子输出的频率落点不一致 | 电位器批次差异 + 人手调节重复性差 | 产品版把频率与相位写死(见上一节),ADC 只保留采集与打印 |
| 看代码以为能调频,实际调不动 | 写死的赋值留在调节路径的最后一步 | 必须显式写清原因与日期;更好的做法是用编译开关把两条路径分开 |
另外还有一条不算 bug、但值得记的经验:按键扫描里的“按键卡住”判定。 我留了一个极大的阈值(按 1 ms 时基算,要很久以后才触发),一旦超过就认为按键被异物压住了, 直接把状态机拉回停止。这是被现场教出来的——按键卡住不处理的话, 板子会一直以为有人在操作,你按什么都没反应。
九、局限与还没做完的部分
作为个人学习项目,它跑通了;作为一件“作品”,它离完整还差得远。我把知道的缺口全部列在这里:
- 器件型号没有最终确认。工程器件字段与实际引用的启动/系统文件不是同一档型号,功能上跑得通,但这是一处真实的隐患。
- 固件层面完全没有参数存储。频率、相位、定量个数断电就丢,每次上电回到默认值。目录里其实有 SPI Flash 的驱动,但没有接进主流程。
- 授权设计起了个头就停了。头文件里声明了三个基于芯片唯一 ID 的授权函数原型(读取、写入密钥、校验),实现文件里一个都没有实现。当时想的是“以后要防抄”,后来发现这个项目根本不需要,就搁下了。
- 没有显式状态机,也没有错误码。当前状态靠“输出模式标志 + 定时器运行标志”两个变量表达,读代码的人得自己在脑子里拼出状态图。现在我会至少写一个枚举,把“停止 / 连续 / 定量”三态明确下来。
- 串口只能发不能收。接收中断全部被注释掉了,所以这块板无法通过串口被配置,想改参数只能重新烧程序。
- 定量个数写死成一个固定值,没有做成可设置项。当时觉得够用,但“少发几个看看”这种需求一来就得改代码。
- 相位差只能固定成产品设定值。代码里整个角度范围的换算路径是完整的,但产品版把它写死了。要恢复可调,得先把上一节说的那套“双真相”清理掉。
这些缺口里,我认为最该先补的是“参数存储”和“显式状态机”这两条: 前者决定它能不能脱离电脑独立工作,后者决定我半年后还能不能读懂它。 至于相位可调,那是它的名字里写着的功能,早晚要还回来。
如果你也在做类似的脉冲信号源,我更推荐先看我另外两篇笔记: 《采样数据的处理:环形缓冲、中值滤波与迟滞控制》 讲的是本文第六节那套滤波与阈值处理的完整思路; 《PCB 布局检查清单:从原理图到打样前的自查》 则是我在打样这类小板之前一定会过一遍的清单, 里面关于时钟、复位和上电时序的几条,都是被这种小项目教出来的。 如果你更关心本文第六节那条“采集—滤波—标定”的线,可以接着看 《工业压力变送器:标定链路与双总线升级》, 那块板子把这条路走得更完整,也把标定这件事的复杂度暴露得更彻底。
十、参考资料与说明
- 主控芯片的参考手册与应用笔记中关于高级定时器(比较输出、比较匹配翻转、周期匹配行为、软件同步停止)的章节,属公开技术文档。
- 定时器/PWM 应用笔记中关于“周期值与比较值相等时的边界行为”的说明——本文第三节那条回绕保护就是照着这类说明补的。
- 通用电子工程中关于电位器作为设定元件的重复性与批次一致性问题的公开讨论,是我决定在产品版写死参数的主要依据。
- 文中出现的芯片型号仅用于说明技术方案,与相关厂商无隶属或授权关系。
- 示例代码为按个人理解重写的最小片段,用于说明思路,不代表任何产品或交付代码,也未经过完整的量产验证。
- 出于对个人项目实现细节的保护,本文只公开方法与思路;涉及核心实现的部分以步骤与字段说明代替代码,示例代码为按个人理解重写的通用片段。
- 出于脱敏考虑,本文不涉及任何单位、客户、项目代号与商务信息;涉及行业应用处均使用中性表述。