1. 引脚复用之下的 GPIO:先搞清楚“通用”二字的边界
前阵子帮一位做固件的朋友复盘一次调试。他手上一块板子,按键怎么读都是低电平,示波器怼上去发现引脚一直是 0.3V 左右。他花了两个小时查代码,最后发现是复用功能号写错了——引脚被配成了某个外设的输出,根本不是 GPIO 输入。这个场景挺典型,也很能说明问题:很多人写 GPIO 的初始化代码都是复制粘贴,寄存器名字认识了,但“这个引脚此刻到底归谁管”这件事从来没想清楚。
GPIO 这三个字母是 General Purpose Input/Output 的缩写,通用输入输出。注意“通用”两个字,它不是说这个引脚什么都能干,恰恰相反,它是说这个引脚默认不属于任何专用外设,所以可以被人为指派。一旦你把它指派给某个串口、某个定时器通道、某个外部中断线,它就不再是“通用”的了。复习 GPIO,第一件要掰扯清楚的事就是这个归属权问题。
1.1 一个焊盘背后挂着的四五个外设
拆开任何一颗主流 MCU 的引脚框图,你会看到一个焊盘(pad)在芯片内部其实连着一大片多路选择器。以常见的 32 位 MCU 为例,一个引脚内部通常同时挂着:数字输入缓冲、数字输出驱动、上拉/下拉电阻、模拟开关(连到 ADC 或比较器)、以及若干条来自不同外设的复用信号线。这四五条通路全部并联在同一个焊盘上,靠一组配置寄存器决定谁被接通。
这就解释了一个新手经常踩的坑:把引脚配成 ADC 输入之后,发现数字读取一直是 0,以为是 ADC 坏了。实际上模拟通路接通时,数字输入缓冲往往被关掉了,你读到的就是个无效值。反过来,引脚配成数字输出,还硬要往 ADC 通道上采样,采出来的东西也没意义。一个焊盘在同一时刻只能有一个“主用途”,这是硬件层面的物理约束,不是软件能绕过去的。
我习惯在动手写代码之前先画一张小表:这个项目用到哪几个引脚,每个引脚在哪个阶段做什么,上电默认是什么状态。这张表只要三列——引脚号、功能、备注。看起来多余,但它能省掉后面大量的“为什么没反应”。
1.2 配置 GPIO 时必须同步完成的三件事
新手最容易漏的是顺序。一个引脚要能正常工作,至少需要三步全部到位,缺一步就是死寂:
- 开时钟。绝大多数 MCU 的 GPIO 端口挂在某个总线桥上,端口时钟默认是关的,为了省电。时钟不开,你往配置寄存器写什么都进不去,读回来全是 0。这是排查“写了没反应”的第一嫌疑对象。
- 选模式与复用号。输入、输出、复用、模拟,四种大模式选一个;如果选复用,还要指定具体是第几号复用功能。
- 配电气属性。输出类型(推挽/开漏)、速度档位、上下拉。这三项经常被当成“可有可无”,实际上决定了信号质量和抗干扰能力。
我见过不止一次,代码里时钟开了,模式也对了,就是输出摆幅不对、边沿软塌塌——最后发现速度档位留在了最低档,驱动一个几十 pF 的容性负载,上升沿拖到了一微秒以上。
1.3 复用功能号是怎么查出来的
复用号不是猜的,也不是背的,是从芯片的引脚定义表里查的。表里会写清楚:某个引脚的第 7 号复用功能是某个定时器的通道 2,第 2 号复用功能是某个串口的接收脚。写代码的时候,把这张表当成字典用就行。
这里有个实操经验:同一个外设的不同通道,未必在同一个端口上,也未必复用号相同。你不能因为“上次那个项目的串口 1 在 A 口第 2、3 脚”,就默认这次也一样。换一颗芯片、换一个封装,引脚映射可能全变。所以我的做法是每次新建工程,先把用到的引脚定义集中写在一个头文件里,用宏包起来:
/* 引脚集中定义,换芯片时只改这一处 */ #define LED_PORT GPIOA #define LED_PIN 5u #define LED_MODE 0x1u /* 通用输出 */ #define LED_OTYPE 0x0u /* 推挽 */ #define LED_SPEED 0x1u /* 中速 */ #define KEY_PORT GPIOC #define KEY_PIN 13u #define KEY_MODE 0x0u /* 输入 */ #define KEY_PUPD 0x1u /* 上拉 */这样后面所有代码都用宏,移植的时候不会出现“改了一半”的惨剧。
2. 推挽与开漏:输出级电路的两种脾气
如果说复用号是“谁能用这个脚”,那推挽和开漏就是“这个脚输出的时候是什么脾气”。这一块是 GPIO 复习里最容易被面试官抓的点,也是实际做电平转换、做总线共享时绕不开的知识。很多人能背出“推挽能输出高低,开漏只能输出低”,但问他为什么开漏必须配上拉、上拉该选多大,就答不上来了。
2.1 推挽输出的能力边界与短路风险
推挽(Push-Pull)的结构很好理解:输出级上下各有一个开关管,上面那个接着电源,下面那个接着地。输出高电平时上管导通、下管关断,引脚被拉到 VCC;输出低电平时反过来,引脚被拉到 GND。两个管子像两个人推拉一扇门,所以叫推挽。
这个结构的好处很直接:高电平和低电平都是“主动驱动”出来的,输出阻抗低,能给负载灌电流也能从负载抽电流,边沿陡、带载能力强。驱动一个普通的 LED、一个 CMOS 输入、一段短线,推挽是最省心的选择。
代价也很明确,两条:
- 不能并联。两个推挽输出接在一起,一个输出高一个输出低,那就等于电源和地之间通过两个导通电阻直接短路,瞬间电流可能上安培级,轻则发热重则烧管。
- 电平被电源钉死。推挽输出的高电平基本等于 VCC,你想让它输出一个 1.8V 的高电平去匹配低压器件,做不到,除非加电平转换。
我在实际项目里定过一条规矩:凡是可能被外部电路反向驱动的引脚,一律不配推挽。比如某个接口是双向的,或者调试阶段可能被外部信号源顶一下,用推挽就等于在赌运气。
2.2 开漏输出为什么几乎总要配一个上拉
开漏(Open-Drain)把上面那个管子拆了,只留下面的下拉管。输出低电平时下管导通,把引脚拉到地;输出高电平时下管关断,引脚变成高阻态——它不驱动任何电平,就那么悬着。
所以开漏输出想要得到高电平,必须有外部(或内部)上拉电阻把线拉上去。这也是为什么几乎所有讲开漏的资料都会补一句“需要外接上拉”。
那既然这么麻烦,为什么还要用开漏?因为它解决了推挽解决不了的两个问题:
第一,线与(Wired-AND)。多条开漏输出可以直接并联在一条线上,各自串一个上拉。只要有任何一条输出低,整条线就是低;全部释放,线才被上拉拉高。这就是 I²C 总线、某些中断汇总线、多主设备共享信号线的物理基础。换成推挽,并联就短路了。
第二,电平转换。开漏只负责“拉低”,高电平由外部上拉决定,那么上拉接到 1.8V,这条线的高电平就是 1.8V;接到 5V,就是 5V。同一根线,两边器件各跑各的电压,只要都容忍对方的低电平门限,就能通信。这是最便宜的双向电平转换方案,一个电阻搞定一个方向。
代价是边沿变慢。上拉电阻和线缆/引脚的寄生电容构成一个 RC 回路,上升沿的时间常数就是 R×C。10kΩ 配上 100pF,时间常数 1μs,从 0 到 0.7×VCC 大概要 1.2μs。跑到 400kHz 的总线上,这个上升时间已经吃掉不少裕量了。
2.3 上下拉电阻选多大:从电流和边沿两头算
选上拉阻值是个两头堵的活儿,得同时满足两个条件。
下限由灌电流决定。输出低的时候,外部上拉电阻上的电流会灌进下拉管:I = VCC / R。假设 VCC 是 3.3V,R 取 1kΩ,那就是 3.3mA。大部分 MCU 单个引脚能承受的灌电流在 8~20mA 量级,1kΩ 是安全的,但如果你一条总线上挂了 8 个器件,每个都有上拉,累积起来就要重新算总账。
上限由上升时间决定。上升时间 t_r ≈ 2.2 × R × C(10% 到 90%)。总线电容 C 包含引脚电容、PCB 走线、连接器,经验值可以按每米走线 50~100pF 估算,再加上每个器件引脚 5~10pF。一条 20cm 的板内总线,两端各挂两个器件,C 大概在 60~120pF。想让 t_r 控制在 300ns 以内,R 就要小于 300ns / (2.2 × 100pF) ≈ 1.36kΩ。
所以常见的取值区间是 2.2kΩ 到 10kΩ,速度越高、电容越大,阻值越小。我一般先用 4.7kΩ 起步,用示波器看上升沿,如果边沿圆得像馒头,就往 2.2kΩ 降;如果静态电流超标,就往 10kΩ 升。别迷信某个“标准值”,那是别人板子上的最优解。
提示:内部上拉电阻的阻值通常在几十 kΩ 量级,而且误差很大(可能正负 50%)。它只适合驱动按键这种慢速、高阻抗的场合,绝对不要拿内部上拉去跑 I²C。
2.4 四种常见组合的选型对照表
| 输出类型 | 外部上拉 | 典型用途 | 主要风险 |
|---|---|---|---|
| 推挽 | 不需要 | 驱动 LED、CMOS 输入、单向高速信号 | 不能并联,可能对地被短路 |
| 开漏 + 外部上拉 | 必须 | 总线共享、双向电平转换、多主中断线 | 边沿慢,静态功耗随上拉减小而增大 |
| 开漏 + 内部上拉 | 靠内部 | 低速按键、复位释放、极低成本场合 | 阻值大且误差大,边沿极慢 |
| 开漏不加上拉 | 无 | 只做“拉低”用的单向控制,比如拉低使能脚 | 高电平浮空,读回值不确定 |
3. 被忽略的电气参数:速度档位、驱动电流和输入阈值
寄存器配置界面里,模式、上下拉大家都会填,唯独“输出速度”这一栏经常被随手放过。这一项到底在调什么,值得单独拎出来说。
3.1 输出速度档位调的到底是什么
输出速度档位(有的手册叫 slew rate、有的叫 output speed)控制的不是“能跑多快的数据”,而是输出级的驱动强度,也就是上管/下管的导通电阻和开启速率。档位越高,管子开得越猛,边沿越陡;档位越低,边沿越缓。
它带来的影响有三层:
- 边沿陡 → 上升时间短 → 高速信号质量好。跑几 MHz 的 SPI 时钟,低档位会把方波削成三角波,采样点全乱。
- 边沿陡 → di/dt 大 → EMI 和过冲严重。几十 MHz 的边沿配上 PCB 走线的寄生电感,很容易在接收端看到振铃,严重时超过器件的绝对最大额定值。
- 边沿陡 → 动态功耗高。负载电容每次充放电的能量是 ½CV²,频率越高、边沿越快,单位时间翻转次数越多,功耗越大。
所以正确的做法是“够用就好”,而不是一律拉到最高。我自己的默认策略:LED、按键、使能脚这类慢速信号一律最低档;SPI、并口这类几 MHz 到几十 MHz 的信号用中高档;只有确实跑高速接口时才上最高档,并且配合串联端接电阻压振铃。
有个细节容易被忽略:有些芯片的低速档位驱动能力很弱,接一个 10kΩ 上拉都可能拉不下来。调试时会表现为“输出低电平只有 0.8V”,看着像坏了,其实是档位太低。
3.2 LED 限流电阻的计算与灌电流/拉电流的选择
点一个 LED 是最常见的 GPIO 练习,但接法有两种,很多人没想过区别。
拉电流接法:GPIO 输出高电平 → 限流电阻 → LED 阳极 → LED 阴极 → GND。电流从引脚流出,靠上管驱动。
灌电流接法:VCC → 限流电阻 → LED 阳极 → LED 阴极 → GPIO。GPIO 输出低电平,电流灌进引脚,靠下管吸收。
限流电阻的计算公式是一样的:
R = (V_supply − V_f) / I_target
假设供电 3.3V,红色 LED 正向压降 1.9V,目标电流 5mA,那么 R = (3.3 − 1.9) / 0.005 = 280Ω,取标准值 270Ω 或 300Ω。如果目标电流 10mA,R 就是 140Ω,取 150Ω。
选 5mA 还是 10mA,取决于两件事:LED 需要的亮度,以及引脚的驱动能力。查手册会发现很多 MCU 的单个引脚最大输出电流只有 8mA 左右,而整个端口的总电流上限(比如 25mA 或 40mA)往往比“单脚上限 × 脚数”小得多。也就是说八个脚各输出 8mA,总和就超了。这是新手设计多路 LED 时最常见的隐患。
我的习惯是默认 3~5mA,亮度不够再往上调,同时把所有 LED 的电流加总,确认不超过端口总限额。真要大电流,别硬扛 GPIO,挂个三极管或者专用驱动芯片。
3.3 输入高电平门限与 3.3V/5V 混接的真实风险
输入侧有个参数必须查:V_IH(输入高电平门限)和 V_IL(输入低电平门限)。以 3.3V 供电的器件为例,V_IH 通常标成 0.7×VDD ≈ 2.31V,V_IL 是 0.3×VDD ≈ 0.99V。落在这两个值之间的电压,属于“不确定区”,读出来是 0 还是 1 完全看运气、看温度。
所以一个 3.3V 的 GPIO 去读一个 5V 器件输出的信号,如果对方输出高电平是 4.5V,而你选的引脚不是 5V 容忍(5V tolerant)的,那就不是“读得准不准”的问题了,而是引脚可能被击穿。反过来,3.3V 输出高电平去驱动 5V 器件的输入,如果对方 V_IH 是 3.5V,那就永远识别不到高电平。
注意:手册里标的“5V 容忍”通常只对数字输入模式成立。一旦把引脚配成模拟输入,或者开了内部上拉,容忍能力往往就没了。混接之前一定逐条对一遍绝对最大额定值表。
4. 三类典型负载的接法与实测坑位
理论讲完,落到板子上,按键、LED、三极管驱动的继电器/蜂鸣器这三类负载占了 GPIO 应用的绝大多数。每一类都有几个不成文的规矩。
4.1 按键:硬件消抖与软件消抖的分工
按键接法最常见的是“一端接 GPIO,一端接 GND,GPIO 开内部上拉”。按下时读到 0,松开时读到 1。也有人接 VCC 配下拉,逻辑反过来,无所谓,看习惯。
真正的麻烦在抖动。机械触点闭合的瞬间,金属弹片会弹跳几毫秒到几十毫秒,中间产生一串随机的 0/1 脉冲。如果直接把 GPIO 接到外部中断上做计数,按一次能给你报十几次。
解决办法分两层:
硬件层加一个 RC 低通:GPIO 到 GND 并一个 100nF 电容,配上内部或外部 10kΩ 上拉,时间常数 1ms,能把大部分抖动吃掉。成本一个电容,效果立竿见影。
软件层做状态机消抖。我常用的写法不是简单的“延时 20ms 再读一次”(那会阻塞整个系统),而是丢进一个 1ms 的定时任务里采样,连续 N 次一致才认账:
#define DEBOUNCE_MS 8 static uint8_t s_cnt; static uint8_t s_stable; /* 消抖后的稳定电平 */ void key_scan_1ms(void) { uint8_t raw = (KEY_PORT->IDR >> KEY_PIN) & 0x1u; if (raw != s_stable) { if (++s_cnt >= DEBOUNCE_MS) { s_cnt = 0; s_stable = raw; if (s_stable == 0u) { /* 低电平有效 */ /* 这里发出一次“按下”事件 */ } } } else { s_cnt = 0; } }这个写法有个好处:它同时得到了“稳定状态”和“边沿事件”,长按判断、双击判断都可以在这上面继续叠。相比之下,“读一次延时再读一次”的写法在跑 RTOS 或者有看门狗的系统里基本不可用。
4.2 驱动三极管/MOS 时的电平逻辑倒置
GPIO 直接驱动继电器线圈、蜂鸣器、小电机是不现实的,电流和电压都不够,标准做法是串一个三极管或者 N 沟道 MOS 管。
用 NPN 三极管低边开关时,接法是:GPIO → 基极电阻 → 基极;集电极接负载负极;负载正极接 VCC;发射极接 GND。这个电路有个反直觉的地方——GPIO 输出高电平,负载才通电。也就是说逻辑没有倒置,是正逻辑。
但如果你用的是 PNP 三极管或者 P 沟道 MOS 做高边开关,情况就反过来了:GPIO 输出低电平,管子才导通。这时候就出现了逻辑倒置。很多人在软件里写LED_ON()结果灯灭,查半天,就是忘了这一茬。
基极电阻的算法:先定集电极电流 I_C(继电器线圈电流,比如 30mA),查三极管的 h_FE(比如 100),那么 I_B 至少要 0.3mA;为了让它进入深度饱和,一般取 3~5 倍,也就是 1~1.5mA。假设 GPIO 高电平 3.3V,V_BE 约 0.7V,那么 R_B = (3.3 − 0.7) / 0.0015 ≈ 1.7kΩ,取 1.5kΩ 或 2kΩ。
提示:驱动感性负载(继电器、电磁阀、小电机)时,负载两端必须反并一个续流二极管,否则断电瞬间线圈产生的反峰电压会把三极管击穿。这个二极管的方向不能接反——阴极接电源侧。
4.3 上电瞬间的毛刺与初始化顺序
还有一个很隐蔽的坑:从上电到你的初始化代码执行完,中间有几十毫秒到几百毫秒,这段时间引脚处于复位默认状态。复位默认状态通常是“浮空输入”或者“带上拉的输入”,而不是输出低电平。
如果这个引脚驱动的是一个使能信号、一个继电器,那么这几十毫秒里外部电路可能被误触发。我见过一个项目,上电瞬间蜂鸣器“嘀”一声,就是这个原因。
解决思路有两条:一是在硬件上给使能脚加一个下拉电阻,保证默认不导通;二是在软件里把初始化代码挪到启动文件最前面,越早越好。前者更可靠,因为软件永远有启动时间,硬件没有。
5. GPIO 不听话时的排查链路
写代码最耗时间的不是写,是“明明配了却没反应”。我总结了 a 一套从内往外的排查顺序,基本能覆盖九成以上的问题。
5.1 第一层:时钟与电源
先怀疑时钟。大部分 MCU 的 GPIO 端口时钟默认关闭,忘了开,写配置寄存器就是写进了空气。判断方法很简单:往配置寄存器写一个值,再读回来,如果读回值和写入值不一致(或者一直是 0),那八成是时钟没开。
再怀疑电源域。有些芯片的低功耗引脚、备份域引脚有独立的电源域和独立的配置寄存器,走错了域,写也没用。
5.2 第二层:复用号与调试口占用
复用号写错,是最常见的“配置了但功能不对”。现象往往是:引脚有电平变化,但变化规律跟你预期对不上——因为那个引脚此刻被另一个外设驱动着。这时候别急着改代码,先去看这个引脚的复用功能表,确认你写的号对应的是哪个外设。
比复用号更阴的是调试口占用。很多芯片的调试引脚(两根线的调试口)在复位后默认就是调试功能,你把它配成普通 GPIO 之后,下次就没法连上调试器了。解决办法是:改代码之前先在调试器里把“复位后 halt”打开,或者干脆用别的不冲突的引脚做实验。这个坑我踩过一次,芯片直接变砖,最后靠“上电瞬间抢连”才救回来。
5.3 第三层:外部电路把引脚“拉”住了
软件查完还没解决,就该上示波器了。这时候关注三个量:
- 静态电平:不驱动的时候是多少伏。如果是 1.6V 这种“中间值”,说明有外部电路在跟你抢。
- 边沿形状:是不是被 RC 拖慢了,是不是有振铃。
- 驱动时的电压跌落:输出高电平的时候只有 2.0V,说明负载太重,或者速度档位太低。
我遇到过一个案子:GPIO 配了推挽输出,实测高电平只有 1.9V。最后发现是板上有个 100Ω 的下拉电阻没删掉(上一版电路留下的),分压把高电平压下去了。这种问题看代码一辈子看不出来。
5.4 一张现象—原因对照表
| 现象 | 高概率原因 | 快速验证方法 |
|---|---|---|
| 配置寄存器读回一直是 0 | 端口时钟没开 | 查时钟使能寄存器对应位 |
| 输出电平正常,但另一个功能不工作 | 复用号写错 | 对照引脚复用表逐项核对 |
| 输出高电平偏低(如 1.9V) | 外部下拉/负载过重/速度档位过低 | 断开负载再测,或调高档位 |
| 输入读数在中间值附近跳变 | 引脚浮空,无上下拉 | 配内部上拉或加外部电阻 |
| 按键偶尔误触发 | 抖动未处理或上拉太弱 | 示波器看按下瞬间波形 |
| 改完代码后调试器连不上 | 调试引脚被复用 | 复位后 halt 模式下改回配置 |
| 上电瞬间外设误动作 | 复位默认电平与需求相反 | 加硬件下拉,或提前初始化 |
6. 低功耗与中断场景下的 GPIO 行为
最后一个容易被跳过、但在电池供电产品里致命的话题:GPIO 在睡眠模式下的表现。
6.1 悬空引脚在睡眠时的漏电
一个浮空的 CMOS 输入引脚,电压会停在不确 定的中间区域。这时候输入级的上下两个管子会同时微导通,形成一条从电源到地的直流通路,产生持续的漏电流。单看一个引脚可能只有几微安,但一颗芯片上几十个没用的引脚全悬空,加起来就是几百微安——对于一个目标待机电流 10μA 的产品,这直接就把指标干穿了。
处理办法很朴素:所有未使用的引脚都要明确定义状态。三种常见做法:
- 配成模拟输入。模拟模式下数字输入缓冲被断开,漏电最小,这是最省心的做法,我一般首选。
- 配成推挽输出并输出低电平。适合引脚外部可能有干扰的场合,但要确认外部没有其他器件往这个脚上灌电流。
- 配成输入并开内部上拉或下拉。简单,但上拉状态下如果外部环境有噪声,仍可能有微小翻转。
顺带说一句,很多人忽略了“已经接了外部器件但器件未上电”的情况。这时候引脚上可能有电压,但对方芯片没供电,电流会通过对方的保护二极管倒灌进去,形成暗电流。这种问题只能靠原理图层面解决,加隔离或者调整上电顺序。
6.2 外部中断的边沿配置与唤醒
用 GPIO 做唤醒源是低功耗产品的标配。这里有几个细节值得单独拎出来。
边沿还是电平?边沿触发只在跳变瞬间给一次事件,适合按键、脉冲计数;电平触发在电平保持期间会持续有效,适合“必须处理完才能继续睡”的场合。低功耗场景下绝大多数用边沿触发,因为电平触发容易在唤醒后反复进中断。
双边沿还是单边沿?如果只关心“按下”,配下降沿就够了;如果要同时检测按下和松开,就配双边沿。双边沿的代价是中断次数翻倍,唤醒更频繁,功耗更高。对于按键唤醒,我的做法是只配一个方向的边沿,唤醒后在任务里读电平再判断状态。
中断标志一定要清。有些芯片在进入中断服务函数时必须先清标志,否则退出后立刻又进一次,表现为“卡在中断里出不来”。还有的芯片在低电平触发模式下,如果电平一直保持,清标志也没用,必须等电平恢复。这类细节只能翻手册,没有通用答案。
唤醒后的引脚状态要做确认。从深睡眠醒来,某些芯片的 GPIO 配置会被复位成默认值。如果你的代码假设“睡醒后引脚还是睡前那个状态”,就可能出问题。稳妥的做法是唤醒后重新做一遍关键引脚的配置。
还有一个我在实际项目里吃过亏的点:唤醒源的引脚如果同时配了内部上拉,睡眠期间的静态电流会比预期大。上拉电阻几十 kΩ,引脚又被外部拉低,那就是几十微安的常态漏电。这种时候要么改成外部上拉(可以用更大的阻值),要么让外部电路在睡眠期间彻底断电。
7. 我个人在这块的经验小结
复习 GPIO 这件事,真正的门槛从来不在寄存器怎么填,而在于“这个引脚此刻归谁管、外部电路想让它变成什么样、我的配置和外部电路是不是在打架”这三件事。寄存器表可以查,电路打架只能靠一层一层剥。
我自己现在拿到一块新板子,第一步永远是拿万用表把每个用到的引脚对地、对电源量一遍,确认没有意外的电阻网络;第二步是写一个最小的点灯 + 读键程序,把所有引脚过一遍;第三步才动真正的业务代码。这三步看着慢,实际上比我以前直接上业务、出了问题再回来翻,要快得多。
另外提醒一句关于速度和功耗的取舍:不要因为“反正也跑不快”就默认最高档,也不要因为“省电”就全部最低档。档位选错了,要么信号质量差查不出原因,要么 EMI 过不了认证。这块没有捷径,老老实实按信号频率和负载电容估一遍,比事后改板子便宜太多了。