PIC18 查表七步:22 位表指针、2 TCY 读表与 2 字节块写一次讲透

八路 NTC 标定表搬进程序存储器后读数总偏几个码,问题多出在表指针与块写规则。按 PIC18CXXX 参考手册第 7、8 章拆解 TBLPTR 三字节结构、四种自增方式与写表落盘条件,给出七步配置与排查表。

2026-11-06
PIC18读表/写表TBLPTRTABLAT块写程序存储器

一台八路 NTC 采集板用 PIC18 做温度标定,原先把分段折线写成 if-else 链,每加一个探头就多一层判断,Flash 越吃越紧,改一次标定值还要重新编译。把折线表搬进程序存储器后,读数却对不上:有的点偏两三个码,有的点直接跳到表尾。结论先给:PIC18 的查表不是普通数组访问,TBLPTR 是 22 位字节指针,TABLAT 是 8 位锁存器,每次 TBLRD 固定两个指令周期;真正容易踩的是指针三字节的装载顺序、奇偶字节的配对,以及写表时“写到块的最高字节才落盘”的块写规则。

本文按 Microchip《PICmicro 高档单片机系列参考手册》DS39500A_CN 第 7 章“存储器构成”与第 8 章“读表/写表”整理,寄存器位定义、块写顺序与例程均取自该手册。文中器件为 PIC18CXXX 增强型 MCU,Flash 型号的块大小与擦除流程以对应器件数据手册为准。

一、先把边界划清:表操作、EEPROM 与 OTA 不是一回事

程序存储器宽 16 位,数据存储器宽 8 位,两个空间各有独立总线,可以在同一个指令周期里分别访问。读表、写表就是把 8 位数据在两者之间搬运:TBLRD 从程序存储器取一个字节送进 TABLAT,TBLWT 把 TABLAT 里的字节送进表保持寄存器。

表操作以字节为单位,表块不需要字对齐,可以从任意字节地址开始、到任意字节地址结束;但如果要写进程序存储器的是可执行指令,这些指令必须按字对齐。这一点决定了常量表和指令表要分开存放。

它和 EEPROM 读写也不同:EEPROM 挂在数据空间,按字节擦写,适合频繁改的参数;程序存储器里的表适合“编译时定死、运行时只读”的标定曲线、字模、字符串和校准系数。至于 OTA 升级关心的双 Bank 切换与跳转,则属于另一个维度,本文不展开。

还有一条前提:读表操作与内部存储器技术无关,EPROM、Flash、ROM 读法一样;写表则要看技术类型——ROM 根本不能改,EPROM 只能把 1 写成 0,Flash 可以在字节或块的基础上修改,但每次写都是整块写。

二、22 位表指针与四种自增方式

表指针由三个 SFR 拼成:TBLPTRU:TBLPTRH:TBLPTRL,合计 22 位。低 21 位用于寻址程序存储器,最大 2 MB,也就是 1M 个字;第 22 位用来访问器件 ID、用户 ID 和配置位。注意它是按字节寻址的,不是按字——同一个程序字占两个连续字节地址。

TABLAT 是一个映射在 SFR 空间的 8 位寄存器,所有表数据都要经过它。TBLRD 与 TBLWT 各有四种写法,区别只在指针怎么动,四种操作都只影响 TBLPTR 的低 21 位。

指令写法指针动作典型用法
TBLRD* / TBLWT*不变反复读同一个字节,或改写后写回原地址
TBLRD*+ / TBLWT*+操作后加 1顺序搬移整张表,最常用
TBLRD*- / TBLWT*-操作后减 1倒序查表
TBLRD+* / TBLWT+*操作前加 1表头先占一个位置、从第二项开始读

读表指令固定要两个指令周期,这比普通数据空间访问慢一拍。如果表查得很频繁,把这几个周期算进控制环预算,比事后优化算法更有效。另一个常被忽略的点是:TBLPTR 是全局状态,中断服务程序里如果也要查表,进出中断前后必须自己保存和恢复三个字节,否则主循环的表指针会被改掉,表现出来就是“读到一半跳到了表尾”。

三、读表:一个字正好装两个字节

每个程序字 16 位,可以存两个字节的表数据。TBLPTR 指向哪个字节,TBLRD 就读哪个字节;读完一个字节后指针停在同一个字的另一半,再执行一次 TBLRD* 就能取到高字节或低字节。手册例 8-1 给出的初始化顺序是先把 FSR0 指向 RAM 缓冲区,再依次写 TBLPTRU、TBLPTRH、TBLPTRL,然后 TBLRD*+ 配合 MOVFF TABLAT, POSTINC0 循环搬运。

三字节指针的装载顺序值得单独强调:一定要先写高位再写低位。因为写入 TBLPTRL 不会触发任何动作,真正读表的是后面的 TBLRD;但如果先写低位、中间被中断打断,指针就处在“高低字节不匹配”的中间态。工程上更稳妥的做法是关掉会查表的中断,或者把指针备份在 RAM 里,用完再恢复。

分段线性标定是最典型的用法:把 ADC 码值和对应温度按升序排成表项,查到落在哪一段后做一次插值。手册还提醒,有些器件把校准信息放在程序存储器末尾,这些字节同样只能用读表指令访问;窗口器件在擦除前要先把标定值记下来,否则重新编程后就找不回来了。

四、写表:为什么写最后一个字节才落盘

Flash 器件的写操作针对整块执行,块大小由具体器件决定,小到 2 字节,大到多字节。对块内最高有效字节(MSB)执行 TBLWT 才会触发整块写入程序存储器;对块内其他地址的写,只是把数据装进对应的保持寄存器,并不会立刻改变存储器内容。

EPROM 器件的规则更直观:对偶地址(TBLPTR<0>=0)写表是短写,只把 TABLAT 装进内部锁存器;对奇地址写表是长写,TABLAT 被编程为该程序字的高字节,锁存器内容被编程为低字节,两个字一起落盘。

这个机制带来一个好处:写周期结束后,块内保持寄存器会被硬件强制置 1,所以只改一个字节时,不必读出整块再整体回写。手册给的标准流程是——先用 TBLRD* 读目标字的偶字节,按需修改后 TBLWT*+ 写回同一地址;再用 TBLRD* 读奇字节,修改后 TBLWT 写回同一地址,由写奇地址启动长写。这样两个字节里的原有数据都不会被误改。

写入地址动作存储器是否改变
块内非 MSB 字节装入保持寄存器不变,等待触发
块的 MSB 字节启动整块写入整块更新
EPROM 偶地址短写,装锁存器不变
EPROM 奇地址长写,编程两个字该程序字更新

如果写入过程被意外事件打断——掉电、意外复位、不期望的中断——被编程的单元可能只完成了一部分,必须重新校验,必要时重写。手册建议在可能掉电的场合加欠压复位电路,让写操作被彻底终止,降低单元再也写不回目标值的概率。

五、自编程的三道闸:LWRT、VPP 与中断

第一道闸是 RCON 寄存器里的 LWRT 位。只有把它置 1,用户模式下的长写表操作才被允许;它只能通过上电复位或 MCLR 复位清零,软件写 0 清不掉。这也意味着一旦使能,就要靠一次完整复位才能收回权限。

第二道闸是 MCLR/VPP 引脚上的编程电压。手册表 8-3 把模式列得很清楚:VPP 加 LWRT=1 时是正常执行加长写使能;VDD 加 LWRT=1 时只有短写,程序存储器不会改变;VPP 且 RB6、RB7 都为低时进入 ICSP 编程模式。所以“代码里写了 TBLWT 却没反应”,先量一下 MCLR 上的电压。

第三道闸是中断。长写操作必须由一次中断或复位来终止,这个中断源要事先使能、标志要事先清零;编程期间控制器会中断指令执行,等中断唤醒后继续。可选中断源包括 WDT、A/D、INT0~INT2、RB 端口电平变化、地址检测 USART、异步模式下的 Timer1/Timer3、捕捉和 SPI。中断标志不会自动清零,必须软件处理,否则下一次写表会被上一次的标志立刻打断。

MCLR/VPPLWRTRB6/RB7工作模式
VPP1无关正常执行,长写表使能
VDD1无关正常执行,仅短写表
VPP0均为低ICSP 编程模式
VSS0无关器件复位

现场最常见的错误,是在写表前把全局中断全关了,想“安静地写完”。结果没有任何唤醒源,长写永远等不到终止条件。正确做法是只使能一个确定的终止源,把其余中断关掉,写完再恢复。

六、七步落地:从算地址到回读校验

  1. 算地址:按字节算出目标表项在程序存储器中的 21 位地址,确认没有越过器件实际容量。
  2. 装指针:先写 TBLPTRU,再写 TBLPTRH,最后写 TBLPTRL,避免中间态被中断看到。
  3. 读原值:偶字节用 TBLRD* 读入 TABLAT,暂存到 RAM;奇字节同样处理。
  4. 改数据:只修改需要变的字节,其余位保持原样,避免把整字擦掉。
  5. 开权限:置 LWRT=1,把 MCLR/VPP 拉到编程电压,清掉将要使用的终止中断标志。
  6. 写两块:先写偶地址(短写装锁存器),再写奇地址(长写触发整字编程),等中断唤醒。
  7. 回读校验:用 TBLRD 读回刚写的两个字节逐一比较,不一致就重写;全部通过后再拉低 VPP、恢复中断。
; 读表:TBLPTR 指向常量表首字节,结果搬进 RAM 缓冲区
    LFSR   FSR0, RAMBUFADDR
    MOVLW  UPPER(LUT_START)
    MOVWF  TBLPTRU
    MOVLW  HIGH(LUT_START)
    MOVWF  TBLPTRH
    MOVLW  LOW(LUT_START)
    MOVWF  TBLPTRL
    TBLRD*+                 ; 读一个字节,指针加 1,固定 2 TCY
    MOVFF  TABLAT, POSTINC0 ; 锁存器内容存入 RAM 并自增

; 写表:偶字节进锁存器,奇字节启动长写
; 前置条件:LWRT=1,MCLR/VPP 已加编程电压,终止中断已使能
    MOVFF  POSTINC0, TABLAT
    TBLWT*+                 ; 短写:装保持寄存器
    MOVFF  POSTINC0, TABLAT
    TBLWT*+                 ; 长写:写块 MSB,整块落盘

这段代码把“读”和“写”放在一起,是为了说明两者共用同一套指针和锁存器。实际工程里建议把读表封装成带长度参数的函数,把写表封装成“读-改-写-校验”的原子操作,中间不要插入其他查表动作。

七、六个现场排查

现象根因改法
读出的值整体错位一个字节把字地址当字节地址用,指针少乘 2按字节算地址,表项首地址与 TBLPTR 对齐
只有前半张表正确TBLPTRU 没写,越过 64 KB 边界后回绕先写 TBLPTRU,再写 TBLPTRH/L
查表结果偶尔跳到表尾中断里也用了 TBLPTR,没有保存恢复ISR 进出保护三个字节,或改用 RAM 缓存
写进去的数据没变化MCLR 仍是 VDD,只有短写;或 LWRT=0加 VPP,置 LWRT=1,再写奇地址
写一半器件复位后数据乱长写被意外中断,单元只编了一部分加 BOR,上电后先校验再重写
写完第一次能读,第二次写失败终止中断标志没清,长写被立刻打断每次写前软件清零对应中断标志

一句话总结:查表的关键不是记住指令名,而是记住三件事——指针 22 位按字节走、数据必须过 8 位锁存器、写表要等块 MSB 那一笔才真正落盘;把这三件事和 LWRT、VPP、终止中断三道闸对齐,标定表才能稳定读、安全写。

还想接着看:PIC16C74A 体选与 PCLATH 中断现场保护、PIC18F2480 ECAN 验收滤波与发送缓冲、STM32 OTA 双 Bank 升级与 Bootloader。

本文基于 Microchip《PICmicro 高档单片机系列参考手册》DS39500A_CN 技术资料整理优化。

需要定制开发?

沧州艾诺威电子 — 国家高新技术企业,20+项国家专利
嵌入式系统开发 · 物联网方案 · AI智能硬件 · 一站式交付

立即微信咨询

电话:13930711029 | 邮箱:tech@czinv.com | 24小时内响应