八路 NTC 标定表搬进程序存储器后读数总偏几个码,问题多出在表指针与块写规则。按 PIC18CXXX 参考手册第 7、8 章拆解 TBLPTR 三字节结构、四种自增方式与写表落盘条件,给出七步配置与排查表。
一台八路 NTC 采集板用 PIC18 做温度标定,原先把分段折线写成 if-else 链,每加一个探头就多一层判断,Flash 越吃越紧,改一次标定值还要重新编译。把折线表搬进程序存储器后,读数却对不上:有的点偏两三个码,有的点直接跳到表尾。结论先给:PIC18 的查表不是普通数组访问,TBLPTR 是 22 位字节指针,TABLAT 是 8 位锁存器,每次 TBLRD 固定两个指令周期;真正容易踩的是指针三字节的装载顺序、奇偶字节的配对,以及写表时“写到块的最高字节才落盘”的块写规则。
本文按 Microchip《PICmicro 高档单片机系列参考手册》DS39500A_CN 第 7 章“存储器构成”与第 8 章“读表/写表”整理,寄存器位定义、块写顺序与例程均取自该手册。文中器件为 PIC18CXXX 增强型 MCU,Flash 型号的块大小与擦除流程以对应器件数据手册为准。
程序存储器宽 16 位,数据存储器宽 8 位,两个空间各有独立总线,可以在同一个指令周期里分别访问。读表、写表就是把 8 位数据在两者之间搬运:TBLRD 从程序存储器取一个字节送进 TABLAT,TBLWT 把 TABLAT 里的字节送进表保持寄存器。
表操作以字节为单位,表块不需要字对齐,可以从任意字节地址开始、到任意字节地址结束;但如果要写进程序存储器的是可执行指令,这些指令必须按字对齐。这一点决定了常量表和指令表要分开存放。
它和 EEPROM 读写也不同:EEPROM 挂在数据空间,按字节擦写,适合频繁改的参数;程序存储器里的表适合“编译时定死、运行时只读”的标定曲线、字模、字符串和校准系数。至于 OTA 升级关心的双 Bank 切换与跳转,则属于另一个维度,本文不展开。
还有一条前提:读表操作与内部存储器技术无关,EPROM、Flash、ROM 读法一样;写表则要看技术类型——ROM 根本不能改,EPROM 只能把 1 写成 0,Flash 可以在字节或块的基础上修改,但每次写都是整块写。
表指针由三个 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 奇地址 | 长写,编程两个字 | 该程序字更新 |
如果写入过程被意外事件打断——掉电、意外复位、不期望的中断——被编程的单元可能只完成了一部分,必须重新校验,必要时重写。手册建议在可能掉电的场合加欠压复位电路,让写操作被彻底终止,降低单元再也写不回目标值的概率。
第一道闸是 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/VPP | LWRT | RB6/RB7 | 工作模式 |
|---|---|---|---|
| VPP | 1 | 无关 | 正常执行,长写表使能 |
| VDD | 1 | 无关 | 正常执行,仅短写表 |
| VPP | 0 | 均为低 | ICSP 编程模式 |
| VSS | 0 | 无关 | 器件复位 |
现场最常见的错误,是在写表前把全局中断全关了,想“安静地写完”。结果没有任何唤醒源,长写永远等不到终止条件。正确做法是只使能一个确定的终止源,把其余中断关掉,写完再恢复。
; 读表: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小时内响应