- 物联网
- 嵌入式
【免费下载链接】nodemcu-firmware
Lua based interactive firmware for ESP8266, ESP8285 and ESP32
NodeMCU Firmware 内置的bit模块为 Lua 开发者提供了完整的 32 位整数位操作能力,其接口设计沿袭 LuaDist 的 bitlib 规范,功能等价于 C 语言中的&、|、^、~、<<、>>等位运算符。阅读本文后,你将掌握bit模块全部 12 个函数的语法、参数语义与底层实现,能够直接用它在 ESP8266/ESP8285/ESP32 固件上完成状态标志位读写、寄存器位域解析、GPIO 引脚状态提取与 I2C 设备控制等嵌入式开发任务。
一、模块概览:来源、启用与适用范围
bit模块于 2014-12-24 加入 NodeMCU Firmware,代码源自 LuaDist/bitlib 中。该模块专门用于在32 位整数上进行位级操作,这意味着所有运算都基于 32 位有符号/无符号整数语义展开。
编译启用:在 NodeMCU 中,模块是否被编译进固件由 app/include/user_modules.h 控制。默认配置中#define LUA_USE_MODULES_BIT已被启用(第 15 行),因此使用官方默认配置编译的固件自带bit模块,无需额外配置。若自行裁剪固件,只需确保该宏处于启用状态即可。
使用方式:bit模块与 NodeMCU 的多数 C 模块一样,编译后自动注册到全局命名空间,Lua 代码中直接以bit.xxx()调用,无需require。
二、位级算数运算:逻辑与移位
bit.band() —— 按位与
对两个或多个参数执行按位与,等价于 C 语言的val1 & val2 & ... & valn。
- 语法:
bit.band(val1, val2 [, ... valn]) - 参数:
val1第一个 AND 参数;val2第二个 AND 参数;...valn第 n 个 AND 参数(变参,至少两个) - 返回:所有参数的按位与结果(number)
bit.band(3, 2) -- returns 2 -- 4 位表示:0011 & 0010 == 0010源码印证:bit.band由VARIADIC(band, &=)宏展开实现(app/modules/bit.c),逻辑是取第一个参数为初值,随后对栈上所有剩余参数逐一执行&=,最终将结果压栈返回。这也是所有变参位运算(band/bor/bxor)的统一实现模式。
bit.bor() —— 按位或
对两个或多个参数执行按位或,等价于 C 语言的val1 | val2 | ... | valn。
- 语法:
bit.bor(val1, val2 [, ... valn]) - 参数:
val1第一个 OR 参数;val2第二个 OR 参数;...valn第 n 个 OR 参数 - 返回:所有参数的按位或结果(number)
bit.bor(3, 2) -- returns 3 -- 4 位表示:0011 | 0010 == 0011典型用法是组合多个标志位生成配置字。例如 lua_examples/dcc/dcc.lua 中用bit.bor(dcc.FLAGS_AUTO_FACTORY_DEFAULT)组装 DCC 解码器初始化标志;lua_modules/hdc1000/HDC1000.lua 中用bit.bor(resolution, heater)组合温湿度传感器的分辨率与加热器配置位。
bit.bxor() —— 按位异或
对两个或多个参数执行按位异或,等价于 C 语言的val1 ^ val2 ^ ... ^ valn。
- 语法:
bit.bxor(val1, val2 [, ... valn]) - 参数:
val1第一个 XOR 参数;val2第二个 XOR 参数;...valn第 n 个 XOR 参数 - 返回:所有参数的按位异或结果(number)
bit.bxor(3, 2) -- returns 1 -- 4 位表示:0011 ^ 0010 == 0001bit.lshift() —— 左移
将数值左移指定位数,等价于 C 语言的value << shift。
- 语法:
bit.lshift(value, shift) - 参数:
value待移位数值;shift移动位数 - 返回:左移后的数值
bit.lshift(2, 2) -- returns 8 -- 4 位表示:0010 << 2 == 1000源码印证:左移通过ARITHMETIC_SHIFT(lshift, <<)宏实现(app/modules/bit.c),操作数以lua_Integer处理,移动位数用luaL_checkunsigned校验为非负整数。左移常用于将两个 8 位字节拼合成 16 位数据,例如 lua_modules/hdc1000/HDC1000.lua 中bit.lshift(string.byte(data_temp, 1, 1), 8) + string.byte(data_temp, 2, 2)将高字节左移 8 位后与低字节相加,还原 16 位温度原始值。
bit.rshift() —— 逻辑右移
将数值右移指定位数,等价于 C 语言的(unsigned)value >> shift。注意这是逻辑右移:最高位补 0,即使原数值为负数也会按无符号数处理,因此不会保留符号位。
- 语法:
bit.rshift(value, shift) - 参数:
value待移位数值;shift移动位数 - 返回:逻辑右移后的数值
bit.rshift(2, 1) -- returns 1 -- 4 位表示:0010 >> 1 == 0001源码印证:逻辑右移由LOGICAL_SHIFT(rshift, >>)宏实现(app/modules/bit.c),关键点在于移位前先把操作数强制转换为lua_UInteger(无符号类型)。源码注释明确说明:LOGICAL_SHIFT 会在移位前截断左操作数,确保最高有效端多余的高位不会被移入结果。因此bit.rshift(-1, 1)将得到0x7FFFFFFF而非-1,这是它与arshift的本质区别。
常用模式:bit.rshift与bit.band组合可提取数值中的任意位段。DCC 示例 lua_examples/dcc/dcc.lua 中bit.band(bit.rshift(addr, 6), 0x7)先从地址中取出第 6~8 位,再与掩码0x7相与得到 3 位的高位地址;docs/modules/si7021.md 中bit.rshift(snb, 24)用于提取设备序列号的高 8 位。
bit.arshift() —— 算术右移
将数值右移指定位数,等价于 C 语言的value >> shift。这是算术右移:保留符号位,负数右移后最高位仍补 1。
- 语法:
bit.arshift(value, shift) - 参数:
value待移位数值;shift移动位数 - 返回:算术右移后的数值
bit.arshift(3, 1) -- returns 1 -- 4 位表示:0011 >> 1 == 0001源码印证:算术右移同样由ARITHMETIC_SHIFT(arshift, >>)宏实现(app/modules/bit.c)。与逻辑右移的差异在于:算术移位不截断左操作数的符号位,操作数以lua_Integer处理。源码注释指出,正因如此算术右移才能正确工作于有符号数。例如bit.arshift(-8, 2)的结果是-2,而bit.rshift(-8, 2)的结果则是0x3FFFFFFE。
三、位级逻辑运算:取反与掩码生成
bit.bnot() —— 按位取反
对单个数值执行按位取反,等价于 C 语言的~value。
- 语法:
bit.bnot(value) - 参数:
value待取反的数值 - 返回:按位取反后的数值
bit.bnot(0) -- returns -1(32 位全 1) bit.bnot(5) -- returns -6(0xFFFFFFFA)源码印证:bit.bnot由MONADIC(bnot, ~)宏实现(app/modules/bit.c),是模块中唯一的单目位运算。由于 Lua 数值以有符号整数表示,取反结果的最高位被解释为符号位,因此正数取反通常得到负数——这与 C 语言中int的行为一致。
bit.bit() —— 生成单一位掩码
生成一个只有指定位置为 1 的数值,等价于 C 语言的1 << position,常用来构造位掩码。
- 语法:
bit.bit(position) - 参数:
position需要置 1 的位位置(0 起始) - 返回:仅在该位置为 1、其余位为 0 的数值
bit.bit(4) -- returns 16(二进制 10000)源码印证:bit_bit直接实现1 << luaL_checkinteger(L, 1)(app/modules/bit.c)。由于 ESP8266 平台为 32 位架构,position的有效范围是 0~31;超出此范围(如1 << 32)会触发未定义行为,实际使用时应当限制在 0~31 内。lua_modules/liquidcrystal/lc-i2c4bit.lua 中即用bit.bit(rs)构造 RS 控制位掩码。
四、位的读取与修改:set / clear / isset / isclear
bit.set() —— 置位
将指定位置的位设置为 1,可一次设置多个位置。
- 语法:
bit.set(value, pos1 [, ... posn]) - 参数:
value基础数值;pos1第一个要置 1 的位位置;...posn第 n 个要置 1 的位位置 - 返回:在指定位置置 1 后的数值
bit.set(2, 0) -- returns 3(二进制 10 → 11)源码印证:bit_set以无符号整型保存初值,随后对每个位置执行val |= 1 << pos(app/modules/bit.c)。这意味着set是对原值做加法式的或操作——如果某位原本就是 1,则保持不变。例如 lua_modules/ds3231/ds3231.lua 中bit.bor(val, 64)与bit.set同效,用于设置星期寄存器的对应位。
bit.clear() —— 清位
将指定位置的位清除为 0,可一次清除多个位置。
- 语法:
bit.clear(value, pos1 [, ... posn]) - 参数:
value基础数值;pos1第一个要清除的位位置;...posn第 n 个要清除的位位置 - 返回:在指定位置清零后的数值
bit.clear(3, 0) -- returns 2(二进制 11 → 10)源码印证:bit_clear对每个位置执行val &= ~(1 << pos)(app/modules/bit.c),即先取反生成清零掩码再做与运算。该函数常用于从配置寄存器中关闭某个功能位,例如 lua_modules/ds3231/ds3231.lua 中bit.band(d, 252)清除低两位、lua_modules/liquidcrystal/lc-gpio8bit.lua 中bit.clear(..., 7)清除忙碌标志位。
bit.isset() —— 测试位是否置 1
判断数值在指定位置是否为 1。
- 语法:
bit.isset(value, position) - 参数:
value待测试的数值;position待测试的位位置 - 返回:该位置为 1 返回
true,否则返回false
bit.isset(2, 0) -- returns false(二进制 10 的第 0 位为 0) bit.isset(2, 1) -- returns true源码印证:bit_isset以无符号整型读取value,通过val & (1 << pos)判真后以布尔值返回(app/modules/bit.c)。典型场景是读取外设状态寄存器判断某位标志,例如 lua_modules/hdc1000/HDC1000.lua 用bit.isset(read16(), 11)检查传感器配置位。
bit.isclear() —— 测试位是否清零
判断数值在指定位置是否为 0,是isset的互补函数。
- 语法:
bit.isclear(value, position) - 参数:
value待测试的数值;position待测试的位位置 - 返回:该位置为 0 返回
true,否则返回false
bit.isclear(2, 0) -- returns true(二进制 10 的第 0 位为 0)源码印证:bit_isclear与isset实现对称,判定逻辑为val & (1 << pos)为真时返回 0(false),否则返回 1(true)(app/modules/bit.c)。
五、函数速查表
| 函数 | C 等价操作 | 参数形式 | 返回 |
|---|---|---|---|
bit.band(val1, val2 [, ...]) | val1 & val2 & ... | 变参(≥2) | 按位与结果 |
bit.bor(val1, val2 [, ...]) | val1 \| val2 \| ... | 变参(≥2) | 按位或结果 |
bit.bxor(val1, val2 [, ...]) | val1 ^ val2 ^ ... | 变参(≥2) | 按位异或结果 |
bit.bnot(value) | ~value | 单参数 | 按位取反结果 |
bit.lshift(value, shift) | value << shift | 双参数 | 左移结果 |
bit.rshift(value, shift) | (unsigned)value >> shift | 双参数 | 逻辑右移结果(高位补 0) |
bit.arshift(value, shift) | value >> shift | 双参数 | 算术右移结果(保留符号位) |
bit.bit(position) | 1 << position | 单参数 | 单一位掩码 |
bit.set(value, pos1 [, ...]) | value \|= (1<<pos) | 变参 | 置位后的数值 |
bit.clear(value, pos1 [, ...]) | value &= ~(1<<pos) | 变参 | 清位后的数值 |
bit.isset(value, position) | value & (1<<pos) | 双参数 | true/false |
bit.isclear(value, position) | !(value & (1<<pos)) | 双参数 | true/false |
六、实战应用:从源码与示例看典型模式
bit模块在 NodeMCU 生态中被大量使用,以下模式均可在仓库中找到真实案例:
1. 提取 GPIO / 外设引脚状态
lua_examples/mcp23008/mcp23008_buttons.lua 中,每 2 秒读取一次 MCP23008 扩展器全部 8 个引脚状态,再通过bit.band(bit.rshift(gpio, pin), 0x1)逐个提取每个引脚的电平——右移把目标位挪到最低位,再与0x1相与剔除其余位:
local pinState = bit.band(bit.rshift(gpio, pin), 0x1) -- extract one pin state2. 位段拆分与重组
DCC 解码地址拆分为 LSB/MSB(lua_examples/dcc/dcc.lua):
[1]=bit.band(addr, 0x3f), -- CV_ACCESSORY_DECODER_ADDRESS_LSB (6 bits) [9]=bit.band(bit.rshift(addr,6), 0x7) -- CV_ACCESSORY_DECODER_ADDRESS_MSB (3 bits)16 位传感器数据重组(lua_modules/hdc1000/HDC1000.lua):
local data = bit.lshift(string.byte(data_temp, 1, 1), 8) + string.byte(data_temp, 2, 2)3. 状态标志读取
NTest_gpio_env.lua 是仓库内 NTest 测试框架中的真实用例,通过bit.band(v, 0x40)判断 I2C 读回的寄存器第 6 位状态:
return (bit.band(v,0x40) ~= 0)4. 4 位并行数据总线拼合
lua_modules/liquidcrystal/lc-gpio4bit.lua 演示了如何将 4 位高低半字节拆合:
local hi = bit.rshift(bit.band(value, 0xf0), 4) local lo = bit.band(value, 0xf) return bit.bor(bit.lshift(hi, 4), lo)七、注意事项与使用建议
- 32 位整数边界:模块按 32 位整数运算,位位置有效范围是 0~31。
bit.bit(32)之类的调用会触发 C 语言的未定义移位行为,应避免。 rshift与arshift的选择:处理无符号数值(寄存器原始值、I2C 读取的字节)时用rshift保证高位补 0;处理带符号数值、需要保持符号语义时用arshift。bnot的符号表现:取反结果在 Lua 中以有符号整数呈现,bit.bnot(0)为-1,在需要无符号视图时可配合rshift或与0xFFFFFFFF掩码使用。- 模块注册:
bit模块在固件源码末尾通过NODEMCU_MODULE(BIT, "bit", bit, NULL)注册(app/modules/bit.c),并以LROT_*只读表导出全部 12 个函数(app/modules/bit.c),因此 Lua 侧不能覆盖或删除该模块的成员。
八、扩展阅读
- 模块完整实现:app/modules/bit.c
- 模块编译开关:app/include/user_modules.h
- 官方模块文档:docs/modules/bit.md
- 相关实战示例:lua_examples/mcp23008/mcp23008_buttons.lua、lua_examples/dcc/dcc.lua
- 仓库内 Lua 库应用:lua_modules/ds3231/ds3231.lua、lua_modules/hdc1000/HDC1000.lua、lua_modules/liquidcrystal/lc-gpio4bit.lua
- 物联网
- 嵌入式
【免费下载链接】nodemcu-firmware
Lua based interactive firmware for ESP8266, ESP8285 and ESP32
相关推荐
NodeMCU SoftUART 模块实战指南:在 ESP8266 上使用 GPIO 位拆串口(softuart 模块详解)
NodeMCU SoftUART 模块实战指南:在 ESP8266 上使用 GPIO 位拆串口(softuart 模块详解) SoftUART 是 NodeMC
物联网嵌入式NodeMCU ucg 模块实战:用 Lua 驱动彩色 TFT 屏(ESP8266 / ESP32)
NodeMCU ucg 模块实战:用 Lua 驱动彩色 TFT 屏(ESP8266 / ESP32) ucg 是 NodeMCU 固件中面向彩色 TFT 显示屏
物联网嵌入式NodeMCU 固件的 ESP8266 FTP 服务器 Lua 模块实战指南(LFS 加载)
NodeMCU 固件的 ESP8266 FTP 服务器 Lua 模块实战指南(LFS 加载) 导读 ftpserver.lua 是 NodeMCU 固件仓库中一
物联网嵌入式
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考