物联网嵌入式【免费下载链接】nodemcu-firmwareLua based interactive firmware for ESP8266, ESP8285 and ESP32项目地址https://gitcode.com/gh_mirrors/no/nodemcu-firmware点击查看免费下载本篇指南系统讲解 NodeMCU Firmwarenodemcu-firmware面向 ESP8266/ESP8285/ESP32 的 Lua 交互式固件中i2c模块的完整用法从硬件接线、固件编译开关到i2c.setup/i2c.address/i2c.write/i2c.read等全部 API 的语法、参数与真实示例并结合仓库源码app/modules/i2c.c、app/driver/i2c_master.c剖析软件 I²C 的时钟生成、时钟拉伸clock stretching、多总线与 GPIO16 支持等底层原理。读完本文你可以在任意 GPIO 引脚上搭建最高 1MHz 的软件 I²C 总线连接多颗同地址传感器并能读懂驱动源码定位通信故障。一、模块概述为什么 ESP8266 需要软件 I²CI²CI2C / IIC是一种两线制串行总线用于与各类外设通信也被称为 SMBus 或 TWISMBus 在 I²C 协议基础上增加了一些扩展。ESP8266 芯片本身没有硬件 I²C 外设因此本固件使用软件 I²C 驱动通过 GPIO 引脚上的电平翻转时序模拟 I²C 协议。得益于软件实现总线可以挂在任意 GPIO 引脚上包括 GPIO16D0见下文约束。该模块新驱动版本源码见 app/driver/i2c_master.c 第 41~394 行的NEW driver分支支持主机Master模式多总线最多 10 条总线NUM_I2C 10定义于 app/platform/cpu_esp8266.h且每条总线可独立配置速度标准Slow100kHz、快速Fast400kHz、快速增强FastPlus1MHz三种预置速率也支持 25kHz~1MHz 范围内的任意时钟频率时钟拉伸Clock stretching慢速从设备可以拉低 SCL 让主机等待其就绪多总线共享 SDA 线多条总线可共用同一根 SDA节省可用引脚GPIO16 可作为 SCL但使用 GPIO16 的总线速率被限制为不超过 FAST400kHz。不支持的特性HIGH-speed 模式3.5MHz 时钟与 10 位地址方案。二、使用前须知初始化、上拉电阻与接线2.1 必须先调用i2c.setup在通过某条总线与任何设备通信之前必须至少对该总线调用一次i2c.setup否则会报错。从源码看i2c.start会先检查总线是否已配置if (platform_i2c_configured( id ) ) platform_i2c_send_start( id ); else luaL_error( L, i2c %d is not configured, id );见 app/modules/i2c.c底层i2c_master_configured则检查该总线状态结构指针是否非空app/driver/i2c_master.c。2.2 上拉电阻I²C 总线按设计工作在开漏open-drain模式因此 SDA 与 SCL 两条线都需要1kΩ~10kΩ 的上拉电阻。不过许多外设模块板上已自带无需外加电阻。2.3 GPIO16D0的特殊限制GPIO16不支持开漏模式工作于推挽push-pull模式。这可能导致从设备尝试拉伸 SCL 时钟时无法把 SCL 拉低从而产生通信错误如果遇到这种情况可尝试降低 I²C 速率。硬件风险警告如果你的模块在使用 GPIO16 时发生重启说明该引脚被接到了 RESET 引脚用于支持 deep sleep 模式此时不能把 GPIO16 用作 I²C 总线或其他用途。2.4 多总线共享 SDA当需要接入多颗地址相同的设备时例如两颗同为0x76的气压计 BME280/BMP280其地址选择见 app/modules/bme280.c 的BME280_I2C_ADDRESS1/2定义可以优先利用设备自身的地址选择引脚很多器件可通过引脚或焊接 0Ω 电阻在 2 个地址间切换若地址无法更改或设备超过 2 颗则使用多条不同的 I²C 总线对每条总线分别用不同的总线号和引脚调用一次i2c.setup之后按「总线 id 设备地址」区分设备。此时SCL 引脚必须不同SDA 可以共用一根。2.5 总线 id 的兼容性提醒历史上很多 NodeMCU 驱动与模块只假设存在 id 为 0 的单条 I²C 总线因此代码中总是以 id 0 作为第一条总线更稳妥。如果你的设备驱动函数没有把 I²C 总线 id 作为输入参数、且不是按 Lua 面向对象OOP原则编写那么该设备大概率只能通过总线 0 访问必须把设备接在总线 0 的引脚上。三、固件编译开关新旧驱动与 GPIO16 支持i2c模块的驱动行为由 app/include/user_config.h 中的两个宏控制启用新驱动注释掉#define I2C_MASTER_OLD_VERSION这一行。新驱动支持多总线、可变速率和 GPIO16见 app/driver/i2c_master.c。默认配置中该行处于已定义状态即编译的是旧驱动旧驱动固定只有总线 0、速率固定 100kHzI2C_MASTER_SPEED 100000、不支持 GPIO16见 app/driver/i2c_master.c。启用 GPIO16 作 SCL取消注释//#define I2C_MASTER_GPIO16_ENABLE注意源码中的宏名为I2C_MASTER_GPIO16_ENABLE文档中写作I2C_MASTER_GPIO16_ENABLED以 app/include/user_config.h 源码为准。该宏只对新驱动生效且启用后使用 GPIO16 的总线速率被限制为不超过 400kHz。从 app/platform/cpu_esp8266.h 可见NUM_I2C也随宏联动新驱动为 10旧驱动为 1。四、API 详解附完整示例模块的 Lua 函数映射表定义于 app/modules/i2c.c共 6 个函数与 5 个常量Lua 常量值见 app/platform/platform.h含义i2c.SLOW100000标准模式 100kHzi2c.FAST400000快速模式 400kHzi2c.FASTPLUS1000000快速增强 1MHz实际速率取决于 CPU 频率i2c.TRANSMITTER0写模式发送方向i2c.RECEIVER1读模式接收方向4.1i2c.setup(id, pinSDA, pinSCL, speed)初始化指定总线号、引脚与速率的总线。参数id0~9总线号pinSDA1~12IO 索引注意 SDA 不能使用 0即 GPIO16源码中sda 0直接报错见 app/modules/i2c.cpinSCL0~12IO 索引0 即 GPIO16需编译期开启I2C_MASTER_GPIO16_ENABLEspeedi2c.SLOW100kHz、i2c.FAST400kHz、i2c.FASTPLUS1MHz或 25000~1000000 Hz 范围内的任意时钟频率。返回值返回实际生效的speed若总线初始化失败返回0。速率与 CPU 频率的关系FASTPLUS 在默认 80MHz CPU 频率下实际为600kHzI²C 时钟要获得真正的 1MHz需先通过node.setcpufreq(node.CPU160MHZ)把 CPU 提升到 160MHz。这一点与驱动源码一致——底层按 CPU 频率计算每半周期延时cycles_delay system_get_cpu_freq() * 500000 / speed见 app/driver/i2c_master.c因此同一速度常量在不同 CPU 频率下对应不同时钟。失败检查源码在i2c_master_setup中会检查 SCL/SDA 是否被外部拉低若总线被卡死例如缺少上拉电阻导致线被拉低则返回 0此时i2c.setup抛出failed to initialize i2capp/modules/i2c.c。双总线初始化示例来自原文档第二条总线与第一条共用 SDA1i2c0 { id 0, sda 1, scl 0, speed i2c.FAST } i2c1 { id 1, sda 1, scl 2, speed i2c.FASTPLUS } -- initialize i2c bus 0 i2c0.speed i2c.setup(i2c0.id, i2c0.sda, i2c0.scl, i2c0.speed) -- initialize i2c bus 1 with shared SDA on pin 1 node.setcpufreq(node.CPU160MHZ) -- to support FASTPLUS speed i2c1.speed i2c.setup(i2c1.id, i2c1.sda, i2c1.scl, i2c1.speed) print(i2c bus 0 speed: , i2c0.speed, i2c bus 1 speed: , i2c1.speed)4.2i2c.start(id)/i2c.stop(id)发送 I²C起始条件start与停止条件stop。参数为总线号id返回nil。在 I²C 时序中start 是 SCL 为高时 SDA 由高拉低见i2c_master_startapp/driver/i2c_master.cstop 则相反i2c_master_stopapp/driver/i2c_master.c。每个完整的读写事务都要以 start 开头、以 stop 结尾。4.3i2c.address(id, device_addr, direction)为下一次传输设置 I²C 设备地址与读写方向。参数id总线号device_addr7 位设备地址。注意在 I²C 协议中device_addr代表总线字节的高 7 位其后紧跟 1 个方向位。若器件手册把地址宣传为 8 位值则需要除以 2得到 7 位值directioni2c.TRANSMITTER表示写模式i2c.RECEIVER表示读模式。返回值收到 ACK 返回true无 ACK 返回false。源码视角Lua 层把地址限制在 0~127app/modules/i2c.c平台层在发送时把 7 位地址左移 1 位再拼接方向位(address 1) direction见 app/platform/platform.c。4.4i2c.write(id, data1[, data2[, ..., datan]])向总线写入数据可一次传入多个数据项。参数id总线号data可以是数值0~255、字符串或 Lua 表数组。返回值实际写入的字节数number。源码视角i2c_write逐参数处理数值要求 0~255表按1..n取元素字符串按字节逐位发送任一台从设备未 ACK 即中断后续发送并返回已写字节数见 app/modules/i2c.c。完整写入示例来自原文档id 0 sda 1 scl 2 -- initialize i2c, set pin 1 as sda, set pin 2 as scl i2c.setup(id, sda, scl, i2c.FAST) -- user defined function: write some data to device -- with address dev_addr starting from reg_addr function write_reg(id, dev_addr, reg_addr, data) i2c.start(id) i2c.address(id, dev_addr, i2c.TRANSMITTER) i2c.write(id, reg_addr) c i2c.write(id, data) i2c.stop(id) return c end -- set register with address 0x45 of device 0x77 with value 1 count write_reg(id, 0x77, 0x45, 1) print(count, bytes written) -- write text into i2c EEPROM starting with memory address 0 count write_reg(id, 0x50, 0, Sample) print(count, bytes written)4.5i2c.read(id, len)从总线读取指定字节数的数据。参数id总线号len要读取的数据字节数。返回值string包含读到的数据len为 0 时返回空。源码视角i2c_read使用 Lua 缓冲区逐字节接收除最后一字节外均发送 ACKplatform_i2c_recv_byte(id, i size - 1)见 app/modules/i2c.c符合 I²C 协议「最后 1 字节应回 NACK」的规范。读取示例来自原文档实现「读寄存器」的完整流程id 0 sda 1 scl 2 -- initialize i2c, set pin 1 as sda, set pin 2 as scl i2c.setup(id, sda, scl, i2c.FAST) -- user defined function: read 1 byte of data from device function read_reg(id, dev_addr, reg_addr) i2c.start(id) i2c.address(id, dev_addr, i2c.TRANSMITTER) i2c.write(id, reg_addr) i2c.stop(id) i2c.start(id) i2c.address(id, dev_addr, i2c.RECEIVER) c i2c.read(id, 1) i2c.stop(id) return c end -- get content of register 0xAA of device 0x77 reg read_reg(id, 0x77, 0xAA) print(string.byte(reg))五、底层原理软件 I²C 是如何实现的5.1 三层调用链Lua 脚本 → 模块层 → 平台层 → 驱动层其完整链路为模块层app/modules/i2c.cLua 参数校验与返回值封装平台层app/platform/platform.cplatform_i2c_setup先把 SDA/SCL 配置为输入带上拉再调用驱动platform_i2c_send_address负责把 7 位地址与方向位拼装驱动层app/driver/i2c_master.c真正的位级时序接口声明见 app/include/driver/i2c_master.h。5.2 时钟频率的精确计算新驱动为每条总线保存一个cycles_delay值表示每个 SCL 半周期需要等待的 CPU 周期数i2c[id]-cycles_delay system_get_cpu_freq() * 500000 / i2c[id]-speed - CPU_CYCLES_BETWEEN_DELAYS;app/driver/i2c_master.c。等待通过读取 CPU 的ccount寄存器实现精确定时i2c_master_wait_cpu_cyclesapp/driver/i2c_master.c。这也解释了为何 FASTPLUS 在 80MHz 下只有 600kHz——500000系数对应 160MHz 下的 1MHz 设计值。5.3 时钟拉伸的实现新驱动在把 SCL 置高后会循环读取 GPIO 输入寄存器直到 SCL 真正变高普通 GPIOi2c_master_wait_gpio_SCL_high用内联汇编轮询app/driver/i2c_master.cGPIO16直接轮询RTC_GPIO_IN_DATA位app/driver/i2c_master.c。由于 GPIO16 处于推挽模式、无法被从设备拉低这一机制在 GPIO16 上可能失效这正是文档建议降低速率的根因。5.4 GPIO16 的寄存器操作差异GPIO16 属于 RTC 域不走普通 GPIO 寄存器。驱动在 setup 阶段对PAD_XPD_DCDC_CONF、RTC_GPIO_CONF、RTC_GPIO_ENABLE、RTC_GPIO_OUT做专门配置app/driver/i2c_master.c且由于 RTC 相关 IO 耗时更长计算延时需额外减去CPU_CYCLES_GPIO16app/driver/i2c_master.c。这些常量在未启用 GPIO16 的构建中会被编译器移除以保持普通引脚的最优时序。5.5 总线自检与复位i2c_master_setup在配置完成后会检查 SCL/SDA 是否处于空闲高电平若被拉低则判定总线失败并返回 0app/driver/i2c_master.ci2c_master_init则会在 SCL 为低时翻转 SDA 并发出多组时钟脉冲用于让挂在总线上的所有从设备复位app/driver/i2c_master.c。六、与内置传感器模块的协同仓库中的多个传感器模块底层直接复用这套 I²C 平台接口可作为你在 Lua 中手工读写寄存器时的参考实现温湿度传感器 BME280/BMP280app/modules/bme280.c 展示了「先写寄存器地址、再切 RECEIVER 读数据」的标准时序其地址常量0x76/0x77与文档中的示例一致温度传感器 tsl2561app/tsl2561/tsl2561.c 通过platform_i2c_setup(tsl2561_i2c_id, sda, scl, PLATFORM_I2C_SPEED_SLOW)以 SLOW 速率初始化说明不同器件可选用不同速率。这些模块源码均通过platform_i2c_send_address、platform_i2c_send_byte、platform_i2c_recv_byte访问总线与i2cLua 模块走同一套底层驱动进一步印证了「多设备、多总线、多速率」的通用性。七、排错速查现象可能原因与对策i2c.setup返回 0 或报failed to initialize i2cSDA/SCL 无上拉或线被拉低确认接线与 1k~10kΩ 上拉SCL/SDA 空闲时必须为高电平源码在 setup 末尾做此自检使用 GPIO16 时通信偶发错误GPIO16 为推挽模式从设备无法拉伸时钟降低速率或改用普通 GPIO 作 SCL使用 GPIO16 时模块重启GPIO16 已被硬件接到 RESET 引脚不能用作 I²C需换引脚报i2c %d is not configured通信前未对对应总线号调用i2c.setupi2c.address总是返回 false7 位地址换算错误8 位地址需除以 2设备不在该总线上方向位参数写错只有 100kHz 且只有总线 0编译的是旧驱动需在 app/include/user_config.h 中注释#define I2C_MASTER_OLD_VERSION启用新驱动FASTPLUS 只有 600kHz默认 80MHz CPU 频率所致需先node.setcpufreq(node.CPU160MHZ)此外若怀疑总线时序异常可参照 app/modules/bme280.c 这类驱动源码核对每一步 start/address/write/stop 的顺序是否正确——这也是 I²C 调试中最常见的错误来源。赞分享物联网嵌入式【免费下载链接】nodemcu-firmwareLua based interactive firmware for ESP8266, ESP8285 and ESP32项目地址https://gitcode.com/gh_mirrors/no/nodemcu-firmware点击查看免费下载相关推荐NodeMCU Firmware 的 MCP4725 模块12 位 I²C DAC 的读取、写入与多设备扩展实战NodeMCU Firmware 的 MCP4725 模块12 位 I²C DAC 的读取、写入与多设备扩展实战 本指南以 NodeMCU Firmware物联网嵌入式F´ 文件包Fw::FilePacket协议详解数据结构、序列化实现与文件上下行应用F´ 文件包Fw::FilePacket协议详解数据结构、序列化实现与文件上下行应用 F´F Prime的 Fw::FilePacket 是飞行软件与物联网嵌入式QMK Firmware 中 IS31FL3736 I²C LED 驱动完整指南配置、去鬼影与 API 详解QMK Firmware 中 IS31FL3736 I²C LED 驱动完整指南配置、去鬼影与 API 详解 IS31FL3736 是 Lumissil 推出嵌入式固件驱动开发硬件开发上一篇如何快速部署Leantime开源项目管理工具完整指南下一篇tufw高级技巧自定义颜色主题与日志配置全攻略创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?