一句话: I2C 只能访问 256 个地址,但很多芯片的寄存器远不止 256 个。Page Select 用"下半区固定、上半区可切换"的窗口映射机制解决这个问题。这篇用文件柜做比喻,5 分钟看懂。
系列文章:《嵌入式外设实战》 — NTC测温、SPI通信、I2C从机、定点数运算——外设驱动从入门到精通
📄 附完整代码 ✅ 用文件柜抽屉讲透 📄 用分和元讲透(附代码) 📄 省引脚绝招 📄 一张表讲清四种模式 📄 多一根线差多远 📄 电压不同的区别 📄 用VTEC波动代替硬件信号 📄 连续N次满足才算数 📄 硬件脚悬空了也别慌 📄 pid-feedforward-lookup 📄 when-to-drop-pid-d-term 📄 pid-deadzone-silence
@[TOC]
适合谁读:适合嵌入式开发者、单片机初学者及遇到类似问题的工程师
问题:256 个地址不够用
I2C 通信协议里,一个设备地址对应 256 个寄存器位置(0x00 到 0xFF)。
但实际芯片——比如光模块的 CMIS 协议——动不动就好几百个寄存器。光 Page 00h 就有 256 字节,加上 Page 01h、Page 02h、Page 1Ah、Page 1Bh……总共有上千字节要管理。
256 个门牌号,怎么塞下上千个房间?
解法:把地址空间劈成两半
把 256 个地址切成两个区域:
| 区域 | 地址范围 | 特点 |
|---|---|---|
| 下半区 | 0x00 — 0x7F(128 字节) | 永远不变,放最常用的东西 |
| 上半区 | 0x80 — 0xFF(128 字节) | 可变,根据"当前页"显示不同内容 |
比喻:一个文件柜
想象一个文件柜:
┌─────────────────────┐ │ 0x00 - 0x7F │ ← 下半区:固定抽屉 │ 温度 | 电压 | 状态 │ 永远在这里,直接拉开就能看 │ 告警 | 版本 | 控制 │ ├─────────────────────┤ │ 0x80 - 0xFF │ ← 上半区:只有一个抽屉口 │ │ 但后面有多个"页"可以切换 │ [当前显示的页] │ └─────────────────────┘下半区像常用的工具挂在墙上——不用翻找,伸手就拿。
上半区像只有一个开口的文件柜抽屉——你只能看到当前选中的那一页。想看另一页?先拨一下选择开关,抽屉里的内容就换了。
实际操作:怎么切换页
第一步:写 Page Select 寄存器
地址0x7F(在下半区!所以永远可以直接访问)是 Page Select 寄存器。写入页码:
主机 → I2C 写 0x7F = 0x00 // 选择 Page 00h // 此时 0x80-0xFF 显示 Page 00h 的上半区 主机 → I2C 写 0x7F = 0x01 // 选择 Page 01h // 同一个 0x80-0xFF,内容全换了 主机 → I2C 写 0x7F = 0x1A // 选择 Page 1Ah // 又换了——ELSFP 通道控制信息第二步:读写上半区
页切换后,正常读写0x80-0xFF就行——硬件自动帮你映射到正确的物理寄存器。
// 读 Page 1Ah 的 0x90 寄存器 I2C_Write(0x7F, 0x1A); // 先切页 uint8_t data = I2C_Read(0x90); // 再读——拿到的就是 Page 1Ah 0x90主机读温度的例子
主机想知道模块温度(在 Page 00h 的0x0E-0x0F):
1. 主机 I2C 读地址 0x0E → 直接读,因为 0x0E < 0x80 在下半区 → 拿到温度高字节 2. 主机 I2C 读地址 0x0F → 同样在下半区 → 拿到温度低字节 全程不需要动 Page Select。主机想读 ELSFP 通道状态(在 Page 1Ah 的0xDC):
1. 主机 I2C 写 0x7F = 0x1A ← 先选 Page 1Ah 2. 主机 I2C 读 0xDC ← 此时 0xDC 位于上半区,显示 Page 1Ah 的内容 → 拿到通道状态固件侧怎么实现
从设备(MCU I2C 从机)内部维护一个g_u8CurrentPage变量:
展开查看完整代码(31行)
展开查看完整代码(31行)
展开查看完整代码(31行)
展开查看完整代码(31行)
展开查看完整代码(31行)
volatile uint8_t g_u8CurrentPage = 0x00; // 默认 Page 00h // I2C 写事件处理 void I2C_WriteHandler(uint8_t u8Addr, uint8_t u8Data) { if (u8Addr == 0x7F) { // 写 0x7F → 切换当前页 g_u8CurrentPage = u8Data; return; } if (u8Addr >= 0x80) { // 上半区 → 写入当前页对应的物理位置 WritePageRegister(g_u8CurrentPage, u8Addr, u8Data); } else { // 下半区 → 直接写 WriteLowerRegister(u8Addr, u8Data); } } // I2C 读事件处理 uint8_t I2C_ReadHandler(uint8_t u8Addr) { if (u8Addr >= 0x80) { // 上半区 → 从当前页对应的物理位置读 return ReadPageRegister(g_u8CurrentPage, u8Addr); } else { // 下半区 → 直接读 return ReadLowerRegister(u8Addr); } }核心逻辑就一个判断:地址 < 0x80 直接读写,地址 >= 0x80 加上"当前页"去查。
为什么下半区放这些
下半区的东西是"无论你在哪一页都需要访问的":
| 地址 | 内容 | 为什么放下半区 |
|---|---|---|
0x0E-0x0F | 模块温度 | 主机随时要看,不能换页 |
0x03 | 模块状态 | 一插电就要知道模块在什么状态 |
0x08-0x0B | 告警标志 | 出故障了立刻感知,不依赖页 |
0x7F | Page Select 本身 | 自举——开关必须放在固定的地方 |
上半区放"需要看但不是每时每刻都要的"——比如配置参数、广告信息、每个通道的详细数据。
总结
| 概念 | 一句说清 |
|---|---|
| Page Select 是什么 | 用0x7F寄存器选择上半区映射到哪一页 |
下半区0x00-0x7F | 永远不变,放紧急和常用数据 |
上半区0x80-0xFF | 根据 Page Select 切换内容 |
| 固件实现 | 一个g_u8CurrentPage变量 + 地址判断 |
| 类比 | 文件柜的共享抽屉口——转动选择开关,抽屉内容就换 |
这个技巧不限于 CMIS 光模块,任何"I2C 寄存器太多塞不下"的芯片都这么干。理解一次,终身受用。 点个收藏,下次调试直接用。有问题欢迎评论区交流,看到了都会回。
下一篇:《附完整代码》——NTC测温、SPI通信、I2C从机、定点数运算——外设驱动从入门到精通 点个收藏,下次调试直接用。有问题欢迎评论区交流,看到了都会回。
下一篇:《附完整代码》——NTC测温、SPI通信、I2C从机、定点数运算——外设驱动从入门到精通 点个收藏,下次调试直接用。有问题欢迎评论区交流,看到了都会回。
下一篇:《附完整代码》——NTC测温、SPI通信、I2C从机、定点数运算——外设驱动从入门到精通 点个收藏,下次调试直接用。有问题欢迎评论区交流,看到了都会回。
下一篇:《附完整代码》——NTC测温、SPI通信、I2C从机、定点数运算——外设驱动从入门到精通 点个收藏,下次调试直接用。有问题欢迎评论区交流,看到了都会回。
下一篇:《附完整代码》——NTC测温、SPI通信、I2C从机、定点数运算——外设驱动从入门到精通 点个收藏,下次调试直接用。有问题欢迎评论区交流,看到了都会回。
下一篇:《附完整代码》——NTC测温、SPI通信、I2C从机、定点数运算——外设驱动从入门到精通 点个收藏,下次调试直接用。有问题欢迎评论区交流,看到了都会回。
下一篇:《附完整代码》——NTC测温、SPI通信、I2C从机、定点数运算——外设驱动从入门到精通 点个收藏,下次调试直接用。有问题欢迎评论区交流,看到了都会回。
下一篇:《附完整代码》——NTC测温、SPI通信、I2C从机、定点数运算——外设驱动从入门到精通 点个收藏,下次调试直接用。有问题欢迎评论区交流,看到了都会回。
下一篇:《附完整代码》——NTC测温、SPI通信、I2C从机、定点数运算——外设驱动从入门到精通 点个收藏,下次调试直接用。有问题欢迎评论区交流,看到了都会回。
下一篇:附完整代码——NTC测温、SPI通信、I2C从机、定点数运算——外设驱动从入门到精通 点个收藏,下次调试直接用。有问题欢迎评论区交流,看到了都会回。
下一篇:附完整代码——NTC测温、SPI通信、I2C从机、定点数运算——外设驱动从入门到精通
有用的话点个收藏,下次调试直接用。有问题欢迎评论区交流,看到了都会回。
下一篇:附完整代码——NTC测温、SPI通信、I2C从机、定点数运算——外设驱动从入门到精通