MSP430F1101AIDWR低功耗实战:比较器斜坡ADC与PID温控
2026/9/18 12:38:35 网站建设 项目流程

如果你的元器件架上还躺着一管 MSP430F1101AIDWR,你大概率是做过电池供电产品的老工程师:1KB Flash、128B RAM、SOIC-20 宽体、1.8V 就能跑,待机电流手册上标 0.7µA。这颗 TI 家 F1xx 系列里的“小不点”,参数放到今天看确实寒酸,可每年还是有相当数量的设计在用它——便宜、好焊、睡得死、醒得快。我最近又拿它做了一版低功耗温控节点,顺手把选型、画板、写寄存器、烧写、调 PID 这一整条链路重新踩了一遍。

这篇文章写给三类人:手上真有这颗料要落地的、看别人在用想抄作业的、以及被“Keil5 怎么绑定 TI 器件包”卡住的初学者。我会把 MSP430F1101AIDWR 这个型号一位一位拆开讲清楚,再从最小系统、两线调试口、低功耗模式、比较器当 ADC 用,一路讲到在这颗片子上跑 PB/Ti/Td 参数形式的 PID 控制,最后把踩过的坑和跟 TI FAE 打交道的心得整理成速查表。全文没有玄学,都是能直接抄的东西。

1. 先把型号拆开:MSP430F1101AIDWR 每一段在说什么

1.1 型号逐段解码与封装陷阱

很多人询价、画封装、下单的时候只写“MSP430F1101A”,结果拿到的料和板上丝印对不上,或者封装画错了。这种低级错误在打样阶段代价还小,量产就是几万块钱的事。完整型号 MSP430F1101AIDWR 里每一段都有具体含义:

型号段含义实操影响
MSP43016 位 RISC 超低功耗 MCU 家族内核和 STM32/8051 完全不兼容,工具链不能混用
F1101具体型号:1KB Flash + 128B RAM 的入门档选型时先看 Flash/RAM,再看有没有 USART
A版本后缀,宽压 1.8~3.6V、最高 8MHz、带掉电复位不带 A 的老版本是早期设计,参数差一截,别混用
I温度等级,一般指 -40~85℃ 工业级下单必须写全,否则可能拿到别的等级或包装
DWR封装 + 包装:DW 是 SOIC-20 宽体,R 表示编带这里最容易翻车,见下面

包装后缀这个坑我踩过。DW 和 DGV 都是 20 脚,但尺寸差着一倍多:DW(SOIC)是 300mil 宽体,引脚间距 1.27mm,手工焊毫无压力,缺点是占板面积大;DGV(TVSOP)引脚间距只有 0.4mm,体积小很多,手工焊基本别想,回流焊也得配钢网。我见过有人照着 DGV 的尺寸画封装,然后买了 DWR 的料回来,二十个脚全部对不上,一块板子直接报废。记住一句话:采购单上写全后缀,封装库里两个封装都建好,画板前再对一遍数据手册封面的尺寸图。

另外提醒一句,DW 的“宽体”指的是体宽 7.5mm 左右的 300mil SOIC,不是常见的 150mil 窄体 SOIC。很多 EDA 软件自带的 SOIC-20 库是窄体版本,直接拖过来也是错的。落地做法是:从数据手册的封装图自己量一遍焊盘中心距和体宽,或者用 EDA 工具自带的封装生成器按尺寸参数重算一遍。

1.2 20 个脚里到底装了些什么

这颗料的资源清单很短,短到你可以背下来。理解“没有什么是它的优势”,比记住“有什么”更重要。

资源规格说明
CPU16 位 RISC,27 条指令,7 种寻址方式8MHz 下指令周期 125ns
Flash1KB注意是 1024 字节,不是 1KB 压缩后的空间
RAM128B这是最大的设计约束,栈深度要算着用
通用 IO14 个:P1.0~P1.7 共 8 个,P2.0~P2.5 共 6 个每个脚都能单独配置方向、上下拉、中断
Timer_A16 位,3 个捕获/比较通道输出比较、PWM、捕获测时都靠它
Comparator_A模拟比较器,CA0/CA1 两路输入,CAOUT 输出没有 ADC 时,它就是你的模拟入口
Basic Timer1基础定时器,可驱动 LCD 或做时基走时和秒中断的备选
看门狗可配置为看门狗或间隔定时器上电默认开着,32ms 就复位
时钟LFXT1(32768Hz 或高频)+ DCODCO 可到 8MHz,但精度靠校准
掉电复位BORA 版相对老版本的重要改进
供电1.8V ~ 3.6V两节干电池、单节锂电都直接能用

请注意两处“没有”:没有 ADC,没有 USART。没有 ADC 意味着所有模拟量要么用比较器斜坡法软实现,要么外挂运放/ADC 芯片。没有串口意味着调参、上传数据得靠 IO 模拟时序(软件 UART),或者干脆用指示灯和按键反馈。这两个“没有”直接决定了你的系统架构,选型阶段就得想清楚,别画完板子才发现要升级。

P1 和 P2 的复用也不一样。P1 主要挂 Timer_A 的捕获比较输出(TA0/TA1/TA2 在 P1.1~P1.3 和 P1.5~P1.7 上各有一份,SMCLK 可从 P1.4 输出),还有 TACLK 输入;P2.0/P2.1 可以用作外部时钟输入(ACLK/INCLK),P2.2/P2.3/P2.4 则是比较器的 CAOUT/CA0/CA1,P2.5 有 ROSC。具体每一位的复用关系,务必按你手上那份数据手册的复用表核对一遍,不同 Revision 的表格会有微调。

2. 为什么今天还值得选它:功耗账与场景判断

2.1 典型落地场景,逐个对号入座

我见过的 MSP430F1101AIDWR 用得最舒服的几类场景,共同点都是“绝大部分时间在睡觉,偶尔醒来干一件事”:

  • 温控/恒温节点:用比较器 + 热敏电阻测温度,Timer_A 定时 1s 醒来一次跑 PID,输出过零触发或 PWM 控制加热丝/继电器。1KB 的 Flash 装增量式 PID 绰绰有余。
  • 电池供电传感器节点:定时醒来采一次数据,软件模拟时序发给无线模块,然后立刻回 LPM3。待机 0.7µA 这个数字,在这个场景里就是命。
  • 电子体温计/计时类小家电:只需要走时、按键、段码显示或蜂鸣提示,不需要任何通信接口。
  • 遥控/编码发射:Timer_A 产生 38kHz 载波,IO 打 IR 时序,代码量极小。
  • 电表水表气表的脉冲计量:两个 IO 做脉冲采集和防抖,Flash 存累积量,时钟用 32768Hz 走时。
  • 工业 4~20mA 变送器的低功耗辅助通道:主控另有其人,用它做看门狗、参数存储和告警逻辑,成本极低。

反过来说,如果你的项目需要 USB、需要跑 DSP、需要联网协议栈,就不要硬上。这颗料的定位是“默默无闻地睡很多年,偶尔准确地做一件小事”。

2.2 横向对比:它凭什么还能活

同价位的选择其实不少,我把关键维度拉平了看:

维度MSP430F1101AIDWR常见 8 位竞品低功耗 ARM Cortex-M0+
内核16 位 RISC8 位32 位
待机电流0.7µA(带 32k 晶振)通常 1µA 上下常见 1~2µA
唤醒时间小于 6µs较快较快
工作电压1.8V 起多数 2.0V 起1.8V 起
封装SOIC-20,好焊多种多数偏小
无 ADC 场景比较器 + 软件斜坡部分有 ADC多数有 12 位 ADC
工具链CCS / IAR / GCC厂商自有生态最丰富
开发难度寄存器级,门槛在“无库可依”中等有 HAL/LL 库

它的真正优势不在单点参数,而在“宽压 + 瞬时唤醒 + 寄存器级可预测性”。寄存器级编程听着费劲,好处是每一行代码耗多少电你心里有数,没有中间件在背后偷偷开外设。做功耗敏感产品的时候,这种可预测性比开发效率值钱。

2.3 把电池寿命算清楚,别被手册数字骗了

手册给的 0.7µA 是特定条件下的典型值,实际产品里根本用不到这个数,因为瓶颈往往不在 MCU。但账还是得算,先算最理想的情况。

假设一个节点每秒醒一次,每次醒来跑 2ms 的 1MHz 活动模式(200µA 典型值),其余时间待在 LPM3(0.7µA):

  • 活动部分平均电流:200µA × (2ms / 1000ms) = 0.4µA
  • 待机部分平均电流:0.7µA × (998/1000) ≈ 0.699µA
  • MCU 平均电流 ≈1.1µA

用一颗 220mAh 的 CR2032:220000µAh ÷ 1.1µA ≈ 200000 小时,理论 22 年。看起来荒谬对吧?现实里你绝对做不到,原因有三个,都是我实测过的:

  1. CR2032 的自放电。常温下一年 1%~3%,220mAh 一年就少 2~6mAh,换成平均电流就是 2~7µA。自放电比 MCU 本身大好几倍,MCU 根本不是瓶颈。
  2. 外部上拉和分压。一个 100kΩ 上拉到 3V 的电阻就是 30µA,一个 1MΩ 的分压网络也有 3µA。画板的时候每个电阻都要算一遍“它静态吃多少电”。
  3. LDO 或 DC/DC 的静态电流。很多 LDO 静态电流 1~50µA,比你 MCU 待机电流大两个数量级。低压差、低静态电流的器件是真的贵,但这里省不得。

所以我的经验是:先把所有静态负载列一张表算总电流,再把 MCU 加进去。只有当 MCU 不是主要消费者,你才真正榨出了这颗料的价值。另外,2ms 的运行时间能不能压到 200µs,对总电流的影响远不如把一个 100k 上拉换成 1M。

3. 从零到点灯:工具链、最小系统与寄存器实操

3.1 “Keil5 绑定 TI”这件事,先把话说清楚

这是搜索量很高的一个问题,我一次性讲透:Keil MDK(µVision5)编不了 MSP430。原因是 MDK 的器件支持靠 Device Family Pack,而 TI 官方提供的 DFP 覆盖的是 ARM 内核的产品线——SimpleLink 系列的无线 MCU、MSPM0 这类 Cortex-M0+ 器件、以及 Hercules、Sitara 等。MSP430 是 TI 自有的 16 位 RISC 内核,不在 MDK 的支持列表里,你翻遍 Pack Installer 也找不到它。

有人会问“那我装一个 TI 的 pack 不就行了”——不行,pack 里必须包含该内核的编译器后端(MDK 用的是 Arm Compiler,只输出 ARM/Thumb 代码),这不是装个包能解决的。绕路方案是用 GCC 配合 MDK 的“外部工具链”,但这样就等于完全放弃了 MDK 的调试体验,纯属自找麻烦。

实际可选的四条路,按我的推荐度排序:

工具链支持情况成本适合谁
TI CCS官方全支持,含寄存器视图、能量分析免费版够用主力推荐,新手首选
IAR EW430官方全支持,汇编和优化做得好有代码大小限制的免费版,1KB 的型号通常在限制内习惯 IAR 的人、需要看汇编
msp430-gccTI 发布的 GNU 工具链,配 MSP-FET 可用全免费喜欢 Makefile 和命令行的人
基于 GCC 的图形化 IDE视版本而定免费玩票性质,不建议量产项目用

还有一点要提醒:MSP430 没有像 SimpleLink SDK 那样统一的“TI SDK”。官方给你的是 MSP430Ware(文档和例程集合)、每个器件单独的 code examples 包,以及 Resource Explorer 这个网页版索引。DriverLib 那套外设驱动库只覆盖较新的 F5xx/F6xx,F1101A 这种老型号不在其中。指望下载一个“MSP430 SDK”然后调库,是走不通的,直接对着寄存器写才是正路。好消息是这颗料的外设极少,把几个寄存器名背下来就够了。

3.2 最小系统与电路细节

最小系统就四块:供电、复位、时钟、调试口。每一块都有具体的坑。

供电。1.8V 到 3.6V 都能跑,但电平决定了最高主频:2.2V 以下别指望 8MHz 稳稳跑满,做 1MHz 以下的低功耗应用完全没问题。去耦电容按 0.1µF 加 10µF 的组合放在 DVCC 附近,走线尽量短。如果你用电池直供,注意电池内阻和瞬时电流的关系——比较器翻转和 Flash 擦写时的电流尖峰会让电压瞬间掉一下,掉到 BOR 阈值以下就复位了,加个大电容垫一下能救命。

复位。RST/NMI 脚是复用引脚,上电默认是复位功能。典型接法是 47kΩ 上拉到 VCC,加一颗 1nF 电容到 GND。注意这颗电容不要贪大,100nF 会让复位沿变缓,调试器经常连不上,我一开始就是卡在这里,把电容降到 1nF 立刻就好。另外这个脚同时是调试口的一根线,所以外部复位电路要和调试器共存,别用复位芯片硬拉。

时钟。两颗晶振里,32768Hz 的 LFXT1 是低功耗走时的核心,XIN/XOUT 要尽量靠近芯片,负载电容按晶振规格选(常见 6pF 到 12.5pF),并且注意晶振的等效串联电阻要够小,劣质晶振在低温下起振很慢甚至停振。高频晶振一般不装,因为 DCO 就够用,而且省电。

调试口。20 脚的封装里没有把四线 JTAG 全引出来,用的是 TEST 加 RST/NMI 的两线方案。所以你的调试座只要四根线:TEST、RST/NMI、VCC、GND。布线要求很实在:线越短越好(15cm 以内),必须共地,TEST 脚别接 VCC,量产板上可以留测试点。我见过因为排线太长、还和 SPI 的数据线捆在一起,导致烧写成功率只有三成的情况,把排线缩短到 8cm 就稳定了。

3.3 寄存器级点灯:先关看门狗,再配时钟

新手最常遇到的现象是“烧进去不跑”。十有八九是看门狗。这颗料上电后看门狗默认开启,超时时间大约 32ms,你的程序还没跑到主循环就被复位了,表现出来就是反复重启、什么现象都没有。所以 main 函数的第一行永远是它。

下面这段代码可以直接跑,包含时钟配置和低功耗骨架:

#include <msp430.h> /* 老型号头文件,寄存器名是 TACTL/TACCRx 这种经典命名 */ #define LED1 BIT0 /* P1.0 */ volatile unsigned int tick = 0; int main(void) { /* 1. 关看门狗,这一行不能省 */ WDTCTL = WDTPW | WDTHOLD; /* 2. DCO 校准。地址按你的器件内存映射核对, 很多批次在信息区放了 1MHz 校准常数 */ BCSCTL1 = *(unsigned char *)0x10FF; /* CALBC1 */ DCOCTL = *(unsigned char *)0x10FE; /* CALDCO */ /* 3. 低频晶振配置:LFXT1 工作在 32768Hz 模式 */ BCSCTL1 &= ~XTS; /* 不选高频模式 */ /* 4. 等晶振稳定,真实项目里要加超时计数,别死等 */ { volatile unsigned int i; do { IFG1 &= ~OFIFG; for (i = 0xFF; i > 0; i--); } while (IFG1 & OFIFG); } /* 5. 主时钟切到 LFXT1,功耗优先。要跑得快就切 DCO */ BCSCTL2 |= SELM_3; /* 6. Timer_A:ACLK 时基,增计数模式,1 秒一次中断 */ TACCTL0 = CCIE; TACCR0 = 32767; TACTL = TASSEL_1 | MC_1 | TACLR; P1DIR |= LED1; P1OUT &= ~LED1; /* 7. 进 LPM3,等中断唤醒 */ __bis_SR_register(LPM3_bits | GIE); while (1) { /* 理论上到不了这里,真到了说明中断配置有问题 */ } } /* CCS / IAR 用这个写法 */ #pragma vector = TIMERA0_VECTOR __interrupt void TA0_ISR(void) { P1OUT ^= LED1; tick++; }

用 msp430-gcc 的话,中断向量写法不一样,要改成属性形式:

__attribute__((interrupt(TIMERA0_VECTOR))) void TA0_ISR(void) { P1OUT ^= LED1; tick++; }

这段代码里有几个值得说的细节。第一,SELM_3把 MCLK 切到 LFXT1,主频降到 32kHz,CPU 慢但极省电,对每秒醒一次的应用完全够。第二,Timer_A 的寄存器名在这颗料上是TACTL/TACCTL0/TACCR0的经典命名,而不是新器件的TA0CTL/TA0CCR0,复制新器件例程的时候要改。第三,__bis_SR_register(LPM3_bits | GIE)这一句是低功耗程序的骨架,进 LPM3 后 CPU 时钟关闭,靠中断唤醒,这条语句后面的代码在正常逻辑里是执行不到的。

3.4 烧写、保护与量产注意

烧写工具用 MSP-FET 或兼容的两线调试器都行。如果你手上有 LaunchPad 之类的开发板,板载仿真器引出的就是 RST/TEST/VCC/GND 这四根,飞线接到自己的板子上就能用,前提是协议兼容——先拿一块废板试通,再往正式板上接,别直接对量产板动手。

关于代码保护:F1xx 有可烧断的安全熔丝,烧断之后调试器读不出 Flash 内容。这个东西对老器件往往是一次性的,不可恢复,所以量产前一定在废弃样片上验证整个流程,包括烧熔丝、断电、重新上电、功能自检。我在一个项目里差点在调试阶段顺手烧了熔丝,后来发现那块样片再也读不出数据,幸好只是样片。

量产烧写我的做法是三步:先烧带测试标志的固件跑一遍自检,确认外设都能响应;再烧正式固件;最后重新上电跑一次最小功能验证。1KB 的 Flash 装不下复杂的自检代码,所以自检逻辑要精简到几百字节,比如点灯顺序、比较器翻转、定时器中断计数。

4. 低功耗设计的实操细节

4.1 LPM0 到 LPM4 怎么选

这颗料有五种低功耗模式,区别在于关掉了哪些时钟。选错了模式,功耗差几十倍。

模式CPUMCLKSMCLKACLKDCO典型电流唤醒源
活动运行200µA@1MHz
LPM0几十µA 量级任意中断
LPM1略低于 LPM0任意中断
LPM2数µA任意中断
LPM30.7µA任意中断
LPM40.1µA外部中断

判断方法很简单:你需要 32768Hz 走时或者定时唤醒,就用 LPM3;连低频晶振都要停,纯等按键,就用 LPM4。需要 SMCLK 驱动的外设(比如某个传感器要连续时钟)才用 LPM1。用了 LPM0/LPM1 却又不干活,是最常见的浪费。

一个容易忽略的点:进 LPM4 前要把所有 IO 处理干净。悬空的输入脚会随电压浮动导致内部输入级反复翻转,实测能多耗几十µA。做法是未使用的 IO 统一配成输出并驱动到低电平,或者配成带内部上拉的输入。这一步做完,待机电流经常能从几十µA 掉到一两µA,效果比换芯片明显得多。

4.2 没有 ADC 怎么测模拟量:比较器斜坡法

这是这颗料最实用的技巧。Comparator_A 有两个输入 CA0、CA1,输出是 CAOUT,可以通过寄存器读到。用它配合 Timer_A 和一颗 RC,就能做一个单斜率 ADC。

原理一句话:让电容通过待测电阻充电,同时启动定时器;比较器盯着电容电压,一旦超过阈值就翻转,把此刻的定时器计数值读出来,这个计数正比于充电时间,也就是正比于电阻值。

具体参数怎么定,我算一遍。用 100kΩ 的热敏电阻加 10nF 电容,充电到 VCC 的一半需要 0.693×R×C ≈ 0.7ms。用 1MHz 的 Timer_A 计数,能得到大约 690 个计数,理论上 9 到 10 位分辨率。考虑比较器失调和噪声,实测能稳定拿到 7 到 8 位,对温度这种慢变量足够了,大概 0.5℃ 的分辨率。想提高分辨率就提时钟或者拉长充电时间,但拉长时间会增加功耗,是个取舍。

比较器的固有失调在毫伏级,而且随温度漂,所以斜坡法测绝对精度有限,但测相对变化的重复性很好,做闭环控制完全够用。另外还要注意:

  • 阈值电压别用 VCC 直接分压,最好用低温度系数的参考源或者干脆用 VCC 的一半分压,但要知道它在电池电压下降时会跟着变——所以电池供电的产品要定期用软件修正这个阈值。
  • 比较器有内置迟滞,对抑制噪声有帮助,但如果你的信号本身就很小,迟滞会把信号吃掉,这时候要减小迟滞或者加放大。
  • 前端模拟调理可以用 TI 的滤波器设计工具(FilterPro 那套思路,现在官网主推的是 Filter Design Tool)算 Sallen-Key 低通参数,把高频噪声挡在比较器之前。生成的系数也能直接拿去写数字滤波,比如滑动平均或者一阶 IIR,几百字节代码就够。

4.3 实测踩到的三个坑

坑一:唤醒后立刻读外设,读到的是旧值。从 LPM3 唤醒有大约几微秒的稳定时间,晶振和比较器的状态需要恢复。实测做法是唤醒后先等一两个空指令周期或者一小段延时,再去读比较器输出,否则偶发误差。

坑二:时钟漂移。用 DCO 做时基,批次之间有百分之几的偏差,温度变化大的环境下更明显。做计时类应用必须用 32768Hz 晶振,或者定期用已知时间源校准 DCO。我有一个项目图省事用 DCO 计时,冬天室外和夏天室内差了将近 5%,客户直接投诉。

坑三:比较器噪声导致微分项抖动。温度信号上叠加几个 LSB 的噪声,经过 PID 的微分项放大后直接影响输出,表现是继电器频繁通断。解决办法是把微分项先行滤波,或者对测量值做滑动平均。这一点在下一节 PID 部分会详细说。

5. 在这颗片子上跑 PID:PB / Ti / Td 的定点实现

5.1 PB、Ti、Td 是什么,怎么换算成 Kp、Ki、Kd

工业温控仪表的参数面板上通常写的是PB(比例带)、Ti(积分时间)、Td(微分时间),而不是教材上的 Kp、Ki、Kd。这两套参数是一回事,只是表达方式不同,很多人第一次看到“比例带 5%”会懵。

换算关系记牢三个公式:

  • 比例增益 Kp = 100 / PB(%),PB 的单位是百分比
  • 积分系数 Ki = Kp / Ti,Ti 单位秒
  • 微分系数 Kd = Kp × Td,Td 单位秒

举个具体的例子。一台加热台,参数面板显示 PB=5、Ti=120、Td=30,意思是比例带 5%、积分时间 120 秒、微分时间 30 秒。换算:

  • Kp = 100 / 5 = 20
  • Ki = 20 / 120 ≈ 0.1667(每秒)
  • Kd = 20 × 30 = 600(秒)

比例带越小动作越猛,5% 是个比较激进的设定;积分时间越短消除静差越快,120 秒意味着误差持续两分钟才把输出积满;微分时间越长对变化越敏感,30 秒是偏大的值。

采样周期 Ts 我选 1 秒。对加热这种大惯性系统,1 秒足够快,也不至于让 CPU 一直醒着。采样周期一定,就可以把连续形式的 PID 离散化。我推荐用增量式,因为增量式不需要累积误差项,天然抗积分饱和,而且省 RAM——128 字节的 RAM,能省一点是一点。

增量式公式:Δu = Kp×(e0-e1) + Ki×Ts×e0 + (Kd/Ts)×(e0-2e1+e2)

代入上面的数:Δu = 20×(e0-e1) + 0.1667×e0 + 600×(e0-2e1+e2)

看一眼最后那一项,系数 600。这意味着测量值上 1 个单位的噪声抖动,会被放大成 600 个单位的输出变化。这就是为什么温度信号必须先滤波,否则微分项会把系统搞疯。也解释了为什么很多现场仪表会把 Td 设成 0——不是微分没用,是没滤波的微分太危险。

5.2 Q 定标的定点 PID 代码

这颗料没有硬件浮点,用 float 会拖进来一大堆库代码,1KB 的 Flash 装不下。必须用定点。我用 Q10 定标,也就是把小数乘以 1024 后取整。

/* 增量式 PID,定点实现 kp/ki/kd 分别用不同的 Q 定标: kp: Q10 (最大 32 左右,安全) ki: Q16 (Ki 通常小于 1,需要更高精度) kd: Q4 (Kd 可能到几百,Q10 会溢出 int16) */ typedef struct { signed int kp; /* Q10 */ signed int ki; /* Q16 */ signed int kd; /* Q4 */ signed int e1, e2; signed int out; /* 0 ~ 1000 对应 0 ~ 100% 输出 */ } pid_t; signed int pid_step(pid_t *p, signed int sp, signed int pv) { signed int e0 = sp - pv; signed long d; d = (signed long)p->kp * (e0 - p->e1); /* 比例项 */ d += ((signed long)p->ki * e0) >> 6; /* 积分项,Q16 缩成 Q10 */ d += (signed long)p->kd * (e0 - 2 * p->e1 + p->e2); /* 微分项 */ d >>= 10; /* 整体从 Q10 降回整数 */ p->out += (signed int)d; /* 输出限幅,这是防积分饱和的第一道闸门 */ if (p->out > 1000) p->out = 1000; if (p->out < 0) p->out = 0; p->e2 = p->e1; p->e1 = e0; return p->out; }

几个必须解释的地方。第一,三个系数用了不同的 Q 定标,原因看数值范围:Kp=20 在 Q10 下是 20480,接近 int16 上限 32767,勉强放得下;Kd=600 在 Q10 下是 614400,早就溢出了,只能用 Q4(600×16=9600)或者干脆用 long 存。用 long 存系数会让每次乘法都变成 32 位运算,速度慢一点但省心,我现在的做法是系数一律用 long,中间结果也 long。第二,>>10是算术右移,对负数会向下取整,会带来一点点偏差,累积起来问题不大,但要意识到。第三,输出限幅放在增量累加之后,这是防饱和最简单有效的办法。

采样周期、测量、输出的驱动部分,主循环大概长这样:

while (1) { __bis_SR_register(LPM3_bits | GIE); /* 睡到定时器唤醒 */ pv = read_temp(); /* 斜坡法测温度,带滑动平均 */ out = pid_step(&pid, setpoint, pv); pwm_set(out); /* 转成占空比或继电器通断 */ soft_uart_send(pv); /* 需要的话,软件模拟上报 */ }

5.3 定标、滤波与参数整定的实战经验

滤波放在 PID 之前,不要放在后面。我的做法是对测量值做 4 点滑动平均,代码只需要四个 int 和一个索引,RAM 占用很小,但能把随机噪声压掉一半以上。对于微分项,还可以单独再做一次一阶滤波,也就是“微分先行”的思路:把微分项建立在滤波后的信号上。

整定参数从 P 开始。先把 Ti 设成很大(相当于关掉积分),Td 设 0,只调 PB,看系统在设定点附近的响应。PB 从大到小试,直到出现小幅度的等幅振荡,然后把 PB 往大调一点作为工作值。接着加积分,Ti 从大到小试,直到静差在可接受时间内消除。最后加微分,Td 从小到大试,直到超调明显减小但不出现高频抖动。这个过程在真实加热台上大概要花两三个小时,别指望一次到位。

注意采样周期和积分时间的比值。如果 Ts=1 秒而 Ti=120 秒,那积分项每次只贡献总和的 1/120,需要 120 次采样才能积满,这在离散系统里是合理的。但如果 Ts 选得太大,比如 10 秒,离散化误差就会很明显,系统表现为响应迟钝、过冲偏大。经验法则是Ts 不超过系统主导时间常数的十分之一

RAM 要算着用。128 字节 RAM 里,栈通常占掉一半以上。这个过程变量(测量值、设定值、输出、四个滤波缓冲、PID 结构体)加起来已经接近 20 字节,再开数组就要小心了。绝对不要在中断里做复杂运算,也不要用递归,更不要开 printf。真有调参需求,用 LED 闪烁次数或者软件 UART 输出几个字节,别引入格式化库。

6. 常见问题与排查技巧实录

6.1 硬件与连接类问题速查

现象更可能的原因处理动作
调试器连不上器件目标板供电低于 1.8V;排线太长;RST 上电容过大先量 VCC,把排线缩到 10cm 内,把 RST 电容降到 1nF 再试
连上但读不出器件信息TEST 脚被拉高或接了别的负载;地和调试器没共检查 TEST 的接法,确保调试器和目标板可靠共地
晶振不起振,或低温不启负载电容不匹配;晶振等效串联电阻偏大;走线太长按晶振规格调电容,换低等效电阻的晶振,把晶振贴到 XIN/XOUT
待机电流是手册的十倍悬空 IO 漏电;外部上拉电阻;LDO 静态电流未用 IO 设成输出低或输入带拉,逐个断开外部负载测
上电反复重启电池内阻大,Flash 擦写时电压跌破掉电复位阈值DVCC 加大电容,缩短供电走线,避免在低压时擦写
通信/采样的模拟量偶尔跳变比较器输入没有滤波,噪声耦合前端加 RC 低通,软件滑动平均,走线远离时钟

6.2 软件与工具链类问题速查

现象更可能的原因处理动作
Keil5 里找不到这颗器件MDK 的器件包只覆盖 ARM 内核产品,MSP430 不在其中换 CCS 或 IAR,或者用 TI 发布的 GCC 工具链
编译报栈溢出、程序跑飞RAM 只有 128B,栈+全局变量+中断现场超了砍掉大数组和递归,常量加 const 放到 Flash,减少中断嵌套层数
烧写成功但程序不运行看门狗没关;时钟配置错误;一上来就进了 LPM4main 第一行关看门狗,先用最小点灯程序验证时钟
程序能跑,但时间不准用了未校准的 DCO 做时基用信息区校准常数,或者切到 32768Hz 晶振
中断进不去没有开总中断;中断标志没清;向量名写错检查 GIE,检查中断服务里清标志,核对向量名称和编译器语法
报 unexpected status 403 forbidden: only one codex conversation can run at a time这和这颗芯片没有任何关系,是某个代码助手类工具的并发会话限制关掉多余的会话再操作,别顺着这条报错去查硬件和固件

最后那一条特别说明一下。这个报错和 MSP430 没有半毛钱关系,只是搜索关键词撞车了。遇到它不用怀疑自己的板子,检查一下工具侧是不是开了多个会话就行。

6.3 跟 TI FAE 打交道,怎么问才能一次问到点上

TI 的现场应用工程师是很宝贵的资源,但他们的时间有限,问题问得好不好直接决定你拿到的是“请参考数据手册”还是“我帮你测一下”。我总结了一套模板,用了几次之后回复效率明显提升:

第一,型号写全。不要写“F1101”,要写 MSP430F1101AIDWR,并且附上芯片表面的完整丝印和批次/日期码。批次信息对于判断是不是工艺问题至关重要。

第二,附上最小可复现的工程。不是把整个项目打包发过去,而是把无关代码全删掉,只留能复现问题的几十行。这样对方能直接在评估板上跑起来,效率天差地别。

第三,波形和原理图。出问题的那一段电压波形、时钟波形,配上一页关键的原理图(供电、复位、时钟、有疑问的那个引脚),比文字描述强一百倍。

第四,说清你的测试条件。供电电压多少、温度多少、时钟源是什么、复现概率是必然还是偶发、换过几块板子。这些信息能排除掉一半的猜测。

第五,明确你想要什么。是要一个替代方案,还是要确认一个参数是否超规格,还是要一份参考设计。别让对方猜。

关于找谁的问题,我的一般做法是:先用官方论坛和文档自己查三天,查不到再提交案例。另外要有一个清醒的认识:FAE 不是免费的方案设计资源。如果你的设计本身超出了手册规定的范围(比如想在 1.5V 下跑 8MHz),谁来也只能告诉你不行。他们最大的价值在于判断批次性问题、工艺异常、以及给出替代型号和长期供货的建议。

6.4 长期供货与替代型号:量产前必须想的事

这颗料属于 TI 的经典 F1xx 系列,生命周期很长,但部分型号的状态会随时间变化。量产项目一定要提前做两件事:向代理问清楚当前的供货状态和预计周期;确认同封装的替代型号。

好消息是 F11x1A 这一档内部是引脚兼容的:同封装下,Flash 更大的兄弟型号(2KB、4KB 那两档)可以直接替换,代码不用改,只要不超出原型号的资源就能直接烧进去跑。所以我的习惯是:画板的时候按大容量型号留位,先用小容量型号量产。将来功能要加、Flash 不够了,换料就行,PCB 不用重新开。这个习惯救过我两个项目,加一个数据记录功能本来要改板,结果只是换了颗料重新编译。

如果你真的被 1KB Flash 卡住了,还有一招:把不常改的参数、校准系数、字符串常量全部塞进 Flash 的信息区,主程序只留逻辑。信息区在 F1xx 上是独立的小块 Flash,可以用来存这些不常写的数据,配合 CRC 校验做掉电保护。写之前记得先擦除,并且做好写入失败的兜底逻辑——低电压下写 Flash 失败是很常见的,掉电复位虽然能救大多数情况,但自己加一层校验更保险。

我个人在实际操作中的体会是,这颗料真正难的地方从来不是写代码,而是把每一微安和每一个字节都当回事。等你习惯了 128 字节 RAM 里精打细算的日子,再回去用那些带 HAL 库的芯片,你会发现自己在功耗和资源上的判断会比以前准很多。

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

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

立即咨询