PIC18C 的 BSF/BCF 对 PORT 执行读-改-写,读回的是引脚而不是锁存器,输入脚和模拟脚都会把错误电平写回去。按 DS39511A_CN 第 11 章拆开 PORT/LAT/TRIS 三条路径、0.25TCY−TPD 稳定时间与 5 条安全写法。
一块 12 路 IO 采集板用 PIC18C:RB3~RB0 驱动四只继电器,RB7~RB4 接 NPN 接近开关。调试时发现一个怪现象——只要上位机改一次继电器状态,已经吸合的另一路会跟着释放;单独测每一路又都正常。结论先给:BSF、BCF 这类指令对 PORT 是读-改-写,读回的是引脚电平而不是输出锁存器;只要同端口里有输入脚或模拟脚,它们的电平就会被一起读进来、再写回锁存器,把别的位改掉。PIC18C 的解法是把所有输出操作换成 LAT 寄存器,并遵守“先写 LAT、后配 TRIS”的初始化顺序。
本文按 Microchip《PIC18C 参考手册》DS39511A_CN 第 11 章“I/O 端口”整理。PORT/LAT/TRIS 的读写路径、例 11-13 的锁存结果、0.25TCY−TPD 稳定时间、模拟脚读 0、RA6 与 RB3 复用条件均取自该章。
手册 11.14.1 给了一组很短的例子:PORTB<7:4> 做输入,PORTB<3:0> 做输出,RB7、RB6 外接上拉且不接其他电路。执行 BCF PORTB,7 再执行 BCF PORTB,6,本来希望两位都变低,结果第二次操作把 RB7 又锁存成了高电平。
原因是一次 BCF 把 PORTB 的 8 位全部读进 CPU。RB7 此时是输入,读回的是引脚电平——外部上拉把它拉成 1;CPU 只把 bit6 清掉,然后把整个字节写回锁存器,RB7 的 1 也跟着写回去了。手册给出的结果很直观:第一次 BCF 后锁存器是 01pp pppp、引脚是 11pp pppp;第二次 BCF 后锁存器变成 10pp pppp,RB7 的锁存值已经被“翻转”。
| 执行指令 | PORT 锁存器 | 实际引脚 | 说明 |
|---|---|---|---|
| 初始 | 01pp pppp | 11pp pppp | RB7、RB6 为输入,被外部上拉 |
| BCF PORTB,7 | 01pp pppp | 11pp pppp | 读引脚得 1,清 bit7 后再写回 |
| BCF PORTB,6 | 10pp pppp | 11pp pppp | RB7 的引脚值 1 被写回锁存器 |
| BCF TRISB,7 | 10pp pppp | 11pp pppp | 切为输出,锁存器的 1 立即输出 |
只要 RB7 还是输入,锁存器被改成什么都不影响引脚;一旦后面把它切成输出,锁存器里的错误值就直接出现在引脚上。这就是“单独测都正常、联调就互相干扰”的来源。
手册 11.1.2 把结构讲得很清楚:PORT 读操作返回引脚电平,写操作写的是输出数据锁存器;LAT 读操作返回锁存器内容,写操作也写锁存器;TRIS 里 1 表示输入、0 表示输出,复位后所有引脚都是输入。也就是说,读 PORT 和读 LAT 是两条完全不同的路径,写 PORT 和写 LAT 却落到同一个地方。
| 寄存器 | 读操作 | 写操作 | 典型用途 |
|---|---|---|---|
| PORTx | 读引脚真实电平 | 写输出锁存器 | 读按键、读外部器件状态 |
| LATx | 读输出锁存器 | 写输出锁存器 | 输出、位操作、读改写安全 |
| TRISx | 读方向配置 | 配置输入/输出 | 初始化、切换方向 |
手册例 11-3 把差异量化了:LATB=1100 0011,TRISB=1111 0000,用 MOVWF PORTB 写入 0010 1110 后,LATB 整个变成 0010 1110,低 4 位引脚变成 1110,高 4 位因为还是输入,引脚不受影响——但它们的锁存值已经被改成 0010。如果之后把高 4 位切成输出,引脚就会输出 0010,而不是初始化时想要的 1100。
即使全部用 LAT,连续操作同一端口也有时间账。I/O 的写发生在指令周期结束前,读要求数据在指令周期一开始就有效,手册给出的数据稳定时间是 0.25TCY − TPD。20 MHz 时 TCY 为 200 ns,0.25TCY 只有 50 ns,再减掉引脚传播延时,留给外部电容充电的窗口非常窄。
| FOSC | 指令周期 TCY | 0.25TCY | 连续读写的实际余量 |
|---|---|---|---|
| 4 MHz | 1 µs | 250 ns | 低速板基本无感 |
| 10 MHz | 400 ns | 100 ns | 长走线需评估 |
| 20 MHz | 200 ns | 50 ns | 容性负载下易读回旧电平 |
手册用 LED 举例:每个引脚并 100 µF 电容,20 MHz 下连续 BSF PORTC,0 到 BSF PORTC,2,只有最后一个 LED 会亮。因为前一个引脚还没充到高电平,下一次读-改-写读到的是 0,又把它写回锁存器清掉了。工程上的处理有三种:在连续操作之间插 NOP(数量取决于电容和频率,不好定量)、引脚串小电阻限制充电电流、或者干脆把写操作改成 LAT 位操作,让引脚电平不再参与运算。
更隐蔽的是模拟输入。ADCON1 把某引脚配成模拟输入后,读该引脚固定返回 0,与真实电压无关;比较器输入也有同样行为。复位时与 A/D 复用的引脚默认就是模拟输入,所以上电后第一次读 PORT 就可能读到一片 0。此时执行 BSF PORTB,5,会把同端口所有模拟位的 0 写回锁存器;等这些脚后来切回数字输出,输出电平已经错了。
手册还给了两条相关约束:即使配置为模拟输入,引脚方向仍由 TRIS 控制,必须保证对应 TRIS 位为 1;而一旦某个外设使能并接管引脚,TRIS 的设置可能被外设覆盖,所以不要用 BSF、BCF、XORWF 去改 TRIS。复用脚的具体条件也写在第 11 章:RA6 只有在 RCIO/ECIO 振荡器模式下才能当通用 I/O,其他模式读 TRIS、PORT、LAT 都得 0;RB3 与 CCP2 复用要配置位 CCP2MX=0;弱上拉要同时满足 TRIS 位为 1 和 RBPU=0。
还有一个“读得到 1 却不进中断”的坑。PORTB 的数据输入是 TTL 缓冲,VDD=5 V 时 3 V 就能读成 1;但 RB0 外部中断走的是施密特触发器输入,阈值更高,同一个 3 V 电平可能读得到 1、却触发不了中断。遇到“查询正常、中断不响应”,先量电平余量,不要先怀疑中断配置。
把上面几节收成五条:①所有输出位操作统一写 LATx,不写 PORTx;②读端口状态才用 PORTx,且要知道那是引脚真实电平;③初始化先写 LAT 定电平、再写 TRIS 定方向,避免上电毛刺;④同端口混用输入输出时,禁止对 PORTx 做读-改-写;⑤模拟脚、复用脚先查配置寄存器,再谈电平。
// PIC18C:RB3~RB0 输出驱动继电器,RB7~RB4 输入接接近开关
void io_init(void)
{
LATB = 0x00; // 先定锁存值:四路继电器默认释放
TRISB = 0xF0; // 后定方向:高 4 位输入,低 4 位输出
ADCON1 = 0x0F; // 全部数字 I/O,避免模拟脚读回 0
}
void relay_set(unsigned char ch, unsigned char on)
{
if (ch > 3) return;
if (on) LATB |= (unsigned char)(1 << ch); // 只动锁存器
else LATB &= (unsigned char)~(1 << ch);
}
unsigned char sensor_read(void)
{
return (unsigned char)((PORTB >> 4) & 0x0F); // 读引脚用 PORTB
}
对照一下错误写法:BSF PORTB,3 会先把 8 位引脚读进来,RB7~RB4 的状态、模拟脚的 0 都可能被写回锁存器;而 BSF LATB,3 读的是锁存器,写回也是锁存器,引脚电平完全不参与。C 语言里用 LATBbits.LATB3 或对 LATB 做影子变量再整体写回,效果一样,关键是别让 PORT 出现在写操作的左边。
| 现象 | 根因 | 改法 |
|---|---|---|
| 改一路输出,另一路跟着变 | 对 PORT 做读-改-写,输入脚电平被写回 | 输出操作改用 LATx |
| 上电瞬间继电器抖动 | 先配 TRIS 后写 LAT,锁存值随机 | 先写 LAT,再写 TRIS |
| 模拟通道旁边的输出异常 | 模拟脚读 0,被读改写写回锁存器 | ADCON1 先配数字口,或分端口操作 |
| 高速下连续置位只剩最后一位有效 | 0.25TCY−TPD 内电容未充满 | 插 NOP、串电阻或改用 LAT |
| 查询读到高电平但中断不触发 | RB0 中断用施密特输入,阈值高于 TTL | 提高电平余量或改查询方式 |
一句话总结:PORT 是“引脚窗口”,LAT 是“锁存器窗口”。凡是写输出,都走 LAT;凡是读外部状态,才走 PORT。把这条分清楚,再守住“先 LAT 后 TRIS”的初始化顺序,同端口混用输入输出、模拟脚读 0、连续读写抢时间这三类问题就不会再混在一起。
还想接着看:PIC18 查表七步:表指针与块写、PIC18 中断优先级与双向量、Cortex-M3 异常模型与 NVIC 低延迟中断。
本文基于 Microchip《PIC18C 参考手册》DS39511A_CN 第 11 章“I/O 端口”技术资料整理优化。
沧州艾诺威电子 — 国家高新技术企业,20+项国家专利
嵌入式系统开发 · 物联网方案 · AI智能硬件 · 一站式交付
电话:13930711029 | 邮箱:tech@czinv.com | 24小时内响应