ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

NodeMCU Firmware rotary 模块指南:读取旋转编码器与按压开关事件

NodeMCU Firmware rotary 模块指南:读取旋转编码器与按压开关事件 物联网嵌入式【免费下载链接】nodemcu-firmwareLua based interactive firmware for ESP8266, ESP8285 and ESP32项目地址https://gitcode.com/gh_mirrors/no/nodemcu-firmware点击查看免费下载导读rotary是 NodeMCU Firmware 内置的 Lua 模块用于驱动廉价的旋转编码器rotary encoder / quadrature switch这类五引脚器件常见于车载音响、旋钮式音量控制等场景其中三只引脚以格雷码gray code输出旋转方向与步数另外两只引脚对应一个按压开关push switch。本指南将完整讲解该模块的事件常量、rotary.setup()、rotary.on()、rotary.getpos()、rotary.close()四个 API 的用法与参数细节并结合 app/modules/rotary.c 与 app/driver/rotary.c 的源码说明其事件队列、防抖与长按/双击判定等底层实现原理。读完本文你将能独立接线、启用模块并编写出可响应旋钮旋转、单击、双击、长按等事件的 NodeMCU 应用。认识旋转编码器与接线方式本模块面向的器件没有绝对位置编码只能输出顺时针 / 逆时针旋转的相对步数。这类开关通常为五引脚三引脚用于正交quadrature旋转检测两引脚用于按压开关。接线要求来自 docs/modules/rotary.md将正交编码器的公共引脚common接到GNDA 相、B 相分别接到 NodeMCU 的两个 GPIO按压开关的一个引脚接地另一个引脚接到 NodeMCU 的一个 GPIO。从源码看app/driver/rotary.c 在rotary_setup()中会把 A/B 相与按压引脚全部配置为PLATFORM_GPIO_INT模式并启用内部上拉PLATFORM_GPIO_PULLUP同时注册任意边沿中断GPIO_PIN_INTR_ANYEDGE。因此外部无需额外上拉电阻接线完成后即可直接使用。器件购买与常见型号仅供参考原文档列出了一些常见购买渠道Amazon、eBay、Adafruit、AliExpress 上搜索rotary encoder push button即可找到多种规格。另有焊在标准 0.1 间距板上的成品模块KY-040其引脚命名比较特殊且实践中通常需要连接 VCC 才能正常工作。需要提醒的是这些外链与器件规格属于原文档给出的经验信息实际选购时请以具体器件的数据手册为准。启用 rotary 模块编译期配置rotary模块并非默认编译进固件。在 app/include/user_modules.h 中该模块默认被注释掉//#define LUA_USE_MODULES_ROTARY如需使用应在构建固件前取消该行注释。同时app/modules/rotary.c 顶部有一段强制编译检查#if !defined(GPIO_INTERRUPT_ENABLE) || !defined(GPIO_INTERRUPT_HOOK_ENABLE) #error Must have GPIO_INTERRUPT and GPIO_INTERRUPT_HOOK if using ROTARY module #endif也就是说编译时必须同时开启GPIO_INTERRUPT_ENABLE与GPIO_INTERRUPT_HOOK_ENABLE否则直接编译失败。这与模块的实现方式一致旋钮和按键全部通过 GPIO 中断驱动并借助platform_gpio_register_intr_hook()实现于 app/platform/platform.c向平台 GPIO 中断分发器挂接钩子函数rotary_interrupt。事件类型常量模块将开关动作抽象为六类事件均以整数常量形式暴露给 Lua定义于 app/modules/rotary.c 的ROTARY_PRESS_INDEX~ROTARY_DBLCLICK_INDEX对应掩码如下常量值含义rotary.PRESS1按压开关被按下rotary.LONGPRESS2长按事件rotary.RELEASE4按压开关松开rotary.TURN8旋钮旋转rotary.CLICK16单击在松开之后判定rotary.DBLCLICK32双击在第二次松开之后判定rotary.ALL63以上全部事件类型的按位或这些值两两不重叠1、2、4、8、16、32因此可以像位掩码一样自由组合例如用rotary.TURN | rotary.CLICK只关心旋转和单击或直接使用rotary.ALL接收所有事件。若向rotary.on()传入非法的事件类型掩码模块会抛出错误。rotary.setup() —— 初始化通道rotary.setup(channel, pina, pinb[, pinpress[, longpress_time_ms[, dblclick_time_ms]]])参数说明channel模块最多同时支持3 个旋转开关取值只能是 0、1 或 2。该上限由 app/include/driver/rotary.h 中的#define ROTARY_CHANNEL_COUNT 3决定。pina连接开关 A 相的 GPIO 编号不能是 0。pinb连接开关 B 相的 GPIO 编号不能是 0。pinpress可选连接按压开关的 GPIO 编号不能是 0省略则只检测旋转。longpress_time_ms可选按多久才算长按默认500 毫秒。源码中默认值LONGPRESS_DELAY_US 500000微秒与之对应Lua 传入的毫秒值会乘以 1000 转换为微秒存储。dblclick_time_ms可选从“松开”到“再次按下”之间允许的最大间隔用于判定双击默认500 毫秒。对应源码中的CLICK_DELAY_US 500000。返回值无。参数非法或底层初始化失败如 GPIO 不存在、通道冲突等时直接抛出 Lua 错误。注意文档中pina/pinb/pinpress均要求“GPIO number excluding 0”即 GPIO0 不可用于本模块。另外三个通道共用一套 GPIO 中断钩子若某个引脚已被其它使用中断钩子的模块如 softuart、somfy、wiegand 等占用platform_gpio_register_intr_hook()会因位掩码冲突而拒绝注册。示例通道 0A 相 GPIO5B 相 GPIO6按压 GPIO7rotary.setup(0, 5, 6, 7)从 app/driver/rotary.c 的实现看rotary_setup()会对 A/B 相以及可选的按压引脚逐个配置中断并加入pin_mask最终通过set_gpio_bits()注册中断钩子。若对同一通道重复调用setup()底层会先自动rotary_close()清理旧资源再重新配置。rotary.on() —— 注册事件回调rotary.on(channel, eventtype[, callback])参数说明channel0、1 或 2。eventtype要监听的事件类型可以是上文任一常量的按位或组合。callback事件发生时被调用的函数如果传nil或省略该参数则取消对应事件的注册。回调函数被调用时会收到三个参数eventtype本次触发的事件类型对应PRESS/LONGPRESS/RELEASE/TURN/CLICK/DBLCLICK之一pos旋钮当前的位置是一个有符号 32 位整数数值增大表示顺时针旋转when事件发生的时刻单位为微秒同样以 32 位整数表示注意该值大约每 1 小时左右会回绕一次比较时间差时要注意符号运算。示例rotary.on(0, rotary.ALL, function (type, pos, when) print(Position .. pos .. event type .. type .. time .. when) end)说明原文档示例写作print Position .. ...这是 Lua 5.3 中print作为语句的写法在默认的 Lua 5.1 构建中请使用print(...)函数调用形式。当前仓库同时包含 app/luaLua 5.1与 app/lua53 两套解释器具体语法以你构建固件时选择的 Lua 版本为准。事件送达的语义与注意事项原文档对事件送达作了细致说明这些行为与 app/modules/rotary.c 的lrotary_dequeue_single()处理逻辑完全吻合事件按顺序送达但可能存在TURN 事件丢失。底层驱动使用容量为 8 的环形队列见 app/driver/rotary.c 的QUEUE_SIZE 8当队列写满时新的状态会覆盖队尾最近一次记录而非强制入队。如果事件积压严重PRESS和RELEASE事件也可能被丢弃。多个待处理的 TURN 事件通常合并为一次回调并以其最终位置作为参数回调函数拿到的pos是累计后的最新位置。位置刻度与旋转分辨率有些旋钮每个定位档位对应 4 步正交脉冲。此时应用层应把pos除以 4 来换算成实际的“格数”。底层驱动在每个 A/B 相沿变化时对位置执行1或-1参见rotary_interrupt中按 4 个微相位状态机的判定因此原始计数值为 1/4 步精度。由于位置是有符号 32 位整数理论上可表达 ±30 位的旋转量但实际机械旋钮寿命通常远低于此部分型号额定寿命不足 5 万转应用不必担心计数溢出问题。单击、双击与长按的判定时序CLICK与LONGPRESS事件都是基于超时定时器派发的按下后若持续按压超过longpress_time_ms则触发LONGPRESS松开后若在dblclick_time_ms内没有再次按下则在超时时刻补发CLICK。DBLCLICK的判定条件是完整的PRESS → RELEASE → PRESS → RELEASE序列且中间那次RELEASE与随后的PRESS之间的间隔必须小于dblclick_time_ms。一旦判定为双击模块会抑制随之而来的CLICK事件源码中通过清零last_recent_event_was_release实现。底层对应 app/modules/rotary.c 的lrotary_check_timer()它维护last_recent_event_was_press与last_recent_event_was_release两个状态位并借助ETSTimeros_timer_arm在需要时安排超时回调。rotary.getpos() —— 查询当前位置与按压状态pos, press rotary.getpos(channel)参数channel0、1 或 2。返回值pos旋钮当前累计位置有符号 32 位整数。press布尔值表示按压开关当前是否处于按下状态。示例print(rotary.getpos(0))从 app/modules/rotary.c 的lrotary_getpos()可以看到驱动层把“按下状态”编码在位置值的最高位0x80000000模块层取出该位后转换为布尔值返回因此在纯位置查询场景中它是无阻塞的即时读取。rotary.close() —— 释放通道资源rotary.close(channel)参数channel0、1 或 2。关闭后该通道的中断、钩子位掩码与内存都会被释放底层rotary_close()会逐一关闭 A/B 相与按压引脚的中断并恢复为上拉输入模式后续可以重新setup()复用该通道。关闭不存在的通道不会报错。示例rotary.close(0)底层实现要点中断驱动的解码与防抖为方便读者深入理解这里结合 app/driver/rotary.c 补充几个关键实现细节任意边沿中断 软件解码A/B 相均注册GPIO_PIN_INTR_ANYEDGE中断服务函数rotary_interrupt通过读取GPIO_STATUS判断哪些引脚变化再读取GPIO_IN实时采样电平将 A/B 相组合映射为 0~3 的微相位与上次相位求差差为 1 时位置1差为 3 时位置-1差为 2 说明漏了一次沿位置额外标记1000000以便上层察觉异常。该函数被标记为ICACHE_RAM_ATTR运行于中断上下文。按压开关 10ms 防抖rotary_interrupt中按压状态变化后 10ms 内的再次跳变会被忽略now - d-last_press_change_time 10 * 1000按下/松开状态以最高位0x80000000表示。中断 → 任务队列ISR 只负责入队和投递一个中优先级任务task_post_medium真正的 Lua 回调派发发生在任务上下文lrotary_task→lrotary_dequeue_single避免在中断中执行 Lua 代码也保证了多个通道事件能被依次、有序地派发。回调生命周期模块通过luaL_ref/luaL_unref在注册表中管理回调引用on()传nil即释放对应槽位setup()和close()都会清理旧回调防止悬空引用。综合示例一个可用的旋钮控制脚本将前面各 API 组合起来即可实现一个带单击、双击、长按与旋转计数的完整脚本可直接通过 ESPlorer 或串口工具上传运行-- 接线A→GPIO5B→GPIO6按键→GPIO7公共端接 GND rotary.setup(0, 5, 6, 7) rotary.on(0, rotary.ALL, function (type, pos, when) local kind if type rotary.PRESS then kind PRESS elseif type rotary.LONGPRESS then kind LONGPRESS elseif type rotary.RELEASE then kind RELEASE elseif type rotary.TURN then kind TURN elseif type rotary.CLICK then kind CLICK elseif type rotary.DBLCLICK then kind DBLCLICK end print(event .. kind .. pos .. pos) end) -- 如果旋钮是每档 4 步用 (pos / 4) 得到档位数 -- 查询当前状态示例 -- local p, pressed rotary.getpos(0)需要清理通道时调用rotary.close(0)小结rotary模块适合用最少的 GPIO 驱动常见的正交旋转编码器支持旋转计数与按压按键两类输入最多同时管理3 个通道。事件系统基于位掩码常量PRESS/LONGPRESS/RELEASE/TURN/CLICK/DBLCLICK/ALL一个回调即可覆盖全部事件类型。长按与双击阈值均默认 500ms可在setup()中按需调整TURN 事件可能合并或丢失、CLICK/DBLCLICK 依赖超时判定等行为特性需要在应用逻辑中予以考虑。使用前提构建固件时必须启用LUA_USE_MODULES_ROTARY并同时开启GPIO_INTERRUPT_ENABLE与GPIO_INTERRUPT_HOOK_ENABLE。相关参考文件docs/modules/rotary.md模块官方文档、app/modules/rotary.cLua API 实现、app/driver/rotary.c中断解码与事件队列驱动、app/include/driver/rotary.h通道数与事件结构定义、app/include/user_modules.h模块编译开关、app/platform/platform.cGPIO 中断钩子注册机制。赞分享物联网嵌入式【免费下载链接】nodemcu-firmwareLua based interactive firmware for ESP8266, ESP8285 and ESP32项目地址https://gitcode.com/gh_mirrors/no/nodemcu-firmware点击查看免费下载相关推荐LifeOS Interceptor 多页对比事实抽取MultiPageCompare 工作流实战指南LifeOS Interceptor 多页对比事实抽取MultiPageCompare 工作流实战指南 多页对比问题——Python 和 JavaScrip物联网嵌入式ManyMC启动器Apple Silicon Mac用户的终极Minecraft原生体验解决方案ManyMC启动器Apple Silicon Mac用户的终极Minecraft原生体验解决方案 ManyMC是一款专为Apple Silicon芯片M1/桌面应用游戏开发Juggl移动端优化指南在手机上使用交互式知识图谱的10个最佳实践Juggl移动端优化指南在手机上使用交互式知识图谱的10个最佳实践 Juggl是Obsidian中一款强大的交互式知识图谱插件它提供了完全可定制和可扩展的图前端上一篇RF-DETR 训练日志与实验追踪完全指南TensorBoard、WB、MLflow 的多 Logger 配置实战下一篇终极指南炉石传说自动化脚本如何让你的游戏时间效率提升7倍创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表