旋转编码器在智能硬件项目中经常用于参数调节,例如音量旋钮、屏幕亮度调节、菜单选项切换、设备档位控制和工业面板输入。它看起来只是一个可以左右旋转、可以按下的旋钮,但背后涉及 GPIO 输入读取、边沿检测、方向判断、按键消抖和数值范围限制等基础能力。
本实验使用 CanMV K210 开发板读取旋转编码器的 CLK、DT 和 SW 三个信号。程序通过fm.register()将物理引脚 6、7、8 分别映射为 GPIOHS0、GPIOHS1、GPIOHS2,再使用输入上拉模式读取旋转编码器状态。顺时针旋转时计数值增加,逆时针旋转时计数值减少,按下旋钮按键时计数值归零。为了让实验更接近真实项目,代码还加入了按键消抖、最大值最小值限制和方向反转配置。
| 学习目标 | 说明 |
|---|---|
| 理解旋转编码器输入 | 认识 CLK、DT 和 SW 三个信号在旋转检测中的作用 |
| 掌握 GPIOHS 输入读取 | 使用 GPIOHS0、GPIOHS1、GPIOHS2 读取编码器电平变化 |
| 理解方向判断逻辑 | 通过 CLK 边沿变化和 DT 电平关系判断顺时针或逆时针 |
| 掌握按键消抖 | 使用时间间隔过滤机械按键抖动,避免一次按下触发多次 |
| 建立参数调节思路 | 将旋转动作转换成程序中的计数值,为菜单和调参实验打基础 |
这个实验的价值不只是读取旋钮动作,而是把一个真实输入设备变成程序中的状态变量。对于硬件编程入门而言,旋转编码器比普通按键更能体现“输入信号如何被程序解析成业务动作”。当旋钮被转动时,GPIO 电平发生变化;程序捕获变化后判断方向,再把方向转换成数值增减,这就是很多智能硬件交互逻辑的基础。
文章目录
- 理论基础
- 硬件设施
- 软件代码
- 扩展应用
- 总结
理论基础
旋转编码器常见于各种需要“连续调节”的场景。普通按键通常只能表达按下和松开,旋转编码器则可以表达顺时针、逆时针和按下确认三类动作。它不像电位器那样输出连续模拟电压,而是通过 CLK 和 DT 两路数字信号的相位变化表示旋转方向,通过 SW 引脚表示中间按键状态。
本实验使用的是增量式旋转编码器。旋钮被转动时,CLK 和 DT 会产生有先后顺序的电平变化。程序选择在 CLK 发生下降沿时读取 DT 的电平,如果 DT 为高电平,就认为是一个方向;如果 DT 为低电平,就认为是另一个方向。不同编码器模块、接线方式或安装方向可能导致实际方向相反,因此代码中保留了REVERSE_DIRECTION参数,方便直接反转方向判断。
SW 按键部分和普通按键类似。编码器中间按键默认通过上拉保持高电平,按下后变成低电平。由于机械按键在按下瞬间可能出现短时间抖动,代码使用DEBOUNCE_MS控制消抖时间,只有两次有效触发间隔超过设定时间,才执行归零操作。这样可以避免一次按下被识别成多次归零。
这条链路可以从输入解析角度理解。旋转编码器先产生 CLK、DT、SW 三路信号,CanMV K210 通过 GPIOHS 读取这些信号,程序再把电平变化解析成顺时针、逆时针和按键按下三类事件。最终,旋转动作不再只是硬件电平变化,而是变成了程序中的encoder_value数值。
硬件设施
本实验只围绕代码中真实使用到的旋转编码器和 GPIO 输入功能展开。代码没有使用 LCD、蜂鸣器、摄像头、电机或传感器采集模块,因此硬件重点集中在旋转编码器的 CLK、DT、SW、VCC 和 GND 连接,以及 CanMV K210 的 IO6、IO7、IO8 输入读取。
接线关系可以先通过下面这张图建立整体印象。旋转编码器的 CLK 接入 IO6,DT 接入 IO7,SW 接入 IO8,模块还需要连接 VCC 和 GND。检查时应重点关注三根信号线是否和代码中的引脚配置一致。
| 硬件 / 软件 | 作用 | 说明 |
|---|---|---|
| CanMV K210 开发板 | 实验运行平台 | 负责执行 MicroPython 程序,并读取旋转编码器的电平变化 |
| 旋转编码器模块 | 输入控制器件 | 通过 CLK、DT 两路信号判断旋转方向,通过 SW 信号检测按键按下 |
| 物理引脚 6 | CLK 信号输入 | 连接旋转编码器 CLK 引脚 |
| 物理引脚 7 | DT 信号输入 | 连接旋转编码器 DT 引脚 |
| 物理引脚 8 | SW 按键信号输入 | 连接旋转编码器 SW 按键引脚 |
| VCC | 模块供电 | 根据模块标识连接对应供电端 |
| GND | 公共地线 | 与 CanMV K210 共地,保证输入电平稳定 |
maix.GPIO | GPIO 控制模块 | 创建 GPIO 输入对象,并通过value()读取当前电平 |
fpioa_manager.fm | 引脚功能映射模块 | 将物理引脚绑定为 GPIOHS 输入功能 |
time | 时间处理模块 | 提供毫秒时间和轮询延时,用于按键消抖和循环检测 |
实验中用到的旋转编码器模块如下。模块上的旋钮可以左右旋转,也可以向下按压;旋转动作由 CLK 和 DT 两路信号表达,按压动作由 SW 信号表达。实际接线时,不能只接 CLK 和 DT,如果需要按键归零功能,还必须接入 SW。
接线关系需要结合代码中的常量定义、fm.register()和GPIO()对象创建进行判断。当前代码明确给出了 CLK、DT、SW 对应的物理引脚编号,红黑电源线则需要按照模块标识连接 VCC 和 GND。
| 物理引脚 | GPIO 功能 | 代码变量 | 对应硬件 | 说明 |
|---|---|---|---|---|
| IO6 | GPIOHS0 | clk | 旋转编码器 CLK | 用于检测旋转编码器的时钟信号变化 |
| IO7 | GPIOHS1 | dt | 旋转编码器 DT | 配合 CLK 判断旋转方向 |
| IO8 | GPIOHS2 | sw | 旋转编码器 SW | 用于检测旋钮按键是否被按下 |
| VCC | 模块供电 | 无 | 旋转编码器 VCC | 根据模块标识连接对应供电端 |
| GND | 公共地线 | 无 | 旋转编码器 GND | 与 CanMV K210 共地,避免输入信号漂移 |
完成接线后的整体效果如下。程序运行后,顺时针旋转旋钮,串口中的计数值增加;逆时针旋转旋钮,串口中的计数值减少;按下旋钮中间按键,计数值归零。
| 实验现象 | 正常表现 | 异常提示 |
|---|---|---|
| 程序启动 | 串口输出初始化完成和当前数值 | 没有输出时检查程序运行和串口连接 |
| 顺时针旋转 | 计数值增加 | 如果方向相反,可修改REVERSE_DIRECTION |
| 逆时针旋转 | 计数值减少 | 如果无变化,检查 CLK 和 DT 接线 |
| 按下旋钮 | 计数值归零 | 如果按键无反应,检查 SW 接线和按键电平 |
| 达到最大值 | 计数值停留在MAX_VALUE | 属于范围限制逻辑,不是异常 |
| 达到最小值 | 计数值停留在MIN_VALUE | 属于范围限制逻辑,不是异常 |
| 快速旋转 | 可能出现少量丢计数 | 轮询方式适合教学演示,高速场景可改用中断方式 |
软件代码
本实验代码围绕旋转编码器的输入检测展开。程序先定义 CLK、DT、SW 三个引脚,再设置计数范围、步进值、消抖时间和轮询间隔。初始化阶段完成引脚映射和 GPIO 输入对象创建,主循环中持续调用旋转检测函数和按键检测函数,把硬件动作转换成计数值变化。
| 软件环境 | 作用 | 检查重点 |
|---|---|---|
| CanMV IDE | 编辑、运行和调试 K210 程序 | 能识别开发板串口,并能运行基础print()测试 |
| CanMV 固件 | 提供maix.GPIO、fpioa_manager等模块 | 固件环境需要支持当前 GPIOHS 与 FPIOA 写法 |
| USB 串口驱动 | 让电脑识别开发板串口 | 串口工具中能看到对应端口 |
| 串口终端 | 查看方向和计数值输出 | 旋转或按下时能看到对应提示 |
| 旋转编码器模块 | 输入设备 | CLK、DT、SW 三路信号应能随操作变化 |
time模块 | 时间处理 | 用于按键消抖和轮询延时 |
#!/usr/bin/env python3# -*- coding: utf-8 -*-""" CanMV K210 旋转编码器实验 Demo 实验功能: 1. 顺时针旋转,计数值增加 2. 逆时针旋转,计数值减少 3. 按下旋钮按键,计数值归零 4. 支持按键消抖,避免一次按下触发多次 5. 支持数值范围限制,便于模拟音量、亮度、菜单索引等场景 """frommaiximportGPIOfromfpioa_managerimportfmimporttime# =========================# 硬件引脚配置# =========================CLK_PIN=6# 旋转编码器 CLK 引脚DT_PIN=7# 旋转编码器 DT 引脚SW_PIN=8# 旋转编码器 SW 按键引脚CLK_GPIO=fm.fpioa.GPIOHS0 DT_GPIO=fm.fpioa.GPIOHS1 SW_GPIO=fm.fpioa.GPIOHS2# =========================# 参数配置# =========================MIN_VALUE=0# 最小值MAX_VALUE=20# 最大值STEP=1# 每旋转一格变化的数值DEBOUNCE_MS=200# 按键消抖时间READ_DELAY_MS=2# 轮询间隔# 如果实际旋转方向相反,把 False 改成 TrueREVERSE_DIRECTION=False# =========================# 全局变量# =========================encoder_value=0last_clk_status=1last_btn_status=1last_btn_time=0# =========================# 工具函数# =========================deflimit_value(value,min_value,max_value):"""限制数值范围,防止超过最大值或最小值"""ifvalue<min_value:returnmin_valueifvalue>max_value:returnmax_valuereturnvaluedefget_ms():"""获取当前毫秒时间"""returntime.ticks_ms()# =========================# 初始化函数# =========================defsetup():globalclkglobaldtglobalswgloballast_clk_statusgloballast_btn_statusgloballast_btn_time fm.register(CLK_PIN,CLK_GPIO,force=True)fm.register(DT_PIN,DT_GPIO,force=True)fm.register(SW_PIN,SW_GPIO,force=True)clk=GPIO(GPIO.GPIOHS0,GPIO.IN,GPIO.PULL_UP)dt=GPIO(GPIO.GPIOHS1,GPIO.IN,GPIO.PULL_UP)sw=GPIO(GPIO.GPIOHS2,GPIO.IN,GPIO.PULL_UP)last_clk_status=clk.value()last_btn_status=sw.value()last_btn_time=get_ms()print("旋转编码器初始化完成")print("顺时针增加,逆时针减少,按下旋钮归零")print("当前数值:%d"%encoder_value)# =========================# 旋转检测函数# =========================defcheck_rotation():""" 检测旋转方向 CLK 发生下降沿时读取 DT 状态: DT 为高电平,认为是顺时针 DT 为低电平,认为是逆时针 不同模块方向可能相反,可通过 REVERSE_DIRECTION 调整。 """globalencoder_valuegloballast_clk_status current_clk_status=clk.value()ifcurrent_clk_status!=last_clk_status:ifcurrent_clk_status==0:ifdt.value()==1:direction=1else:direction=-1ifREVERSE_DIRECTION:direction=-direction encoder_value+=direction*STEP encoder_value=limit_value(encoder_value,MIN_VALUE,MAX_VALUE)ifdirection>0:print("方向:顺时针,当前数值:%d"%encoder_value)else:print("方向:逆时针,当前数值:%d"%encoder_value)last_clk_status=current_clk_status# =========================# 按键检测函数# =========================defcheck_button():""" 检测旋钮按键 SW 默认被上拉为高电平。 按下时变成低电平。 """globalencoder_valuegloballast_btn_statusgloballast_btn_time current_btn_status=sw.value()current_time=get_ms()iflast_btn_status==1andcurrent_btn_status==0:ifcurrent_time-last_btn_time>DEBOUNCE_MS:encoder_value=0last_btn_time=current_timeprint("旋钮按键被按下,计数值已归零:%d"%encoder_value)last_btn_status=current_btn_status# =========================# 主循环# =========================defloop():whileTrue:check_rotation()check_button()time.sleep_ms(READ_DELAY_MS)# =========================# 程序入口# =========================if__name__=='__main__':setup()try:loop()exceptKeyboardInterrupt:print("程序已停止")这段程序可以分成输入读取、方向判断和状态输出三个层级。输入读取层由clk、dt、sw三个 GPIOHS 输入对象完成;方向判断层由check_rotation()完成,根据 CLK 的变化和 DT 的电平判断计数增加或减少;按键处理层由check_button()完成,检测 SW 的下降沿并执行归零操作。最终串口输出的是“方向”和“当前数值”,而不是原始电平。
参数配置区是后续调试的重点。MIN_VALUE和MAX_VALUE决定计数范围,适合模拟音量、亮度、菜单索引等有限范围参数;STEP决定每次旋转变化多少;DEBOUNCE_MS决定按键消抖时间;READ_DELAY_MS决定轮询间隔;REVERSE_DIRECTION用于修正实物方向和代码方向不一致的问题。
| 函数名 | 功能 | 对应现象 |
|---|---|---|
limit_value() | 限制计数值范围 | 计数值不会小于 0,也不会超过 20 |
get_ms() | 获取当前毫秒时间 | 为按键消抖提供时间判断依据 |
setup() | 初始化引脚和输入对象 | 串口输出初始化完成提示,编码器进入可检测状态 |
check_rotation() | 检测旋转方向并更新计数值 | 顺时针计数增加,逆时针计数减少 |
check_button() | 检测旋钮按键并执行归零 | 按下旋钮后计数值变为 0 |
loop() | 持续轮询旋转和按键状态 | 程序持续响应旋转和按下动作 |
主程序从setup()开始,完成硬件初始化和初始状态记录。loop()中不断调用check_rotation()和check_button(),并通过time.sleep_ms(READ_DELAY_MS)控制轮询间隔。当前轮询间隔为 2 毫秒,能够较及时地捕获旋转编码器状态变化。这个写法适合教学演示和低速手动旋转场景,如果后续用于高速编码器计数,可以继续扩展为中断方式检测。
扩展应用
旋转编码器实验容易遇到方向相反、计数跳动、按键多次触发、数值不变化等问题。排查时应从接线、输入模式、旋转方向配置、消抖时间和轮询间隔几个方向确认,不应直接把问题归结为编码器损坏。
| 问题现象 | 可能原因 | 处理思路 |
|---|---|---|
| 旋转没有任何输出 | 程序未运行、接线错误、GPIO 映射不匹配 | 检查串口是否打印初始化信息,确认 CLK、DT、SW 是否接到物理引脚 6、7、8 |
| 顺时针和逆时针方向相反 | 编码器模块方向定义与代码判断相反 | 将REVERSE_DIRECTION = False改成True |
| 数值变化不稳定 | CLK 或 DT 接线松动,轮询过程中读到抖动信号 | 检查杜邦线连接,适当增大READ_DELAY_MS观察是否改善 |
| 按下一次触发多次归零 | 按键机械抖动明显,消抖时间偏短 | 适当增大DEBOUNCE_MS,例如从 200 调整到 300 |
| 数值无法继续增加 | 已经达到MAX_VALUE | 修改MAX_VALUE,或确认当前限制范围是否符合实验目标 |
| 数值无法继续减少 | 已经达到MIN_VALUE | 修改MIN_VALUE,或确认当前场景是否需要允许负数 |
| 按键没有归零效果 | SW 引脚接线错误或按键电平逻辑不同 | 单独打印sw.value()观察按下前后的电平变化 |
| 旋转过快时丢计数 | 轮询方式无法捕获全部高速变化 | 降低旋转速度,或在后续课程中改成中断方式检测 |
| 一上电就触发按键 | SW 输入悬空、上拉配置失效或接线错误 | 检查 SW、VCC、GND 连接,并确认使用GPIO.PULL_UP |
旋转编码器的应用价值在于把连续旋转动作转化成可控的数值变化。相比普通按键,旋转编码器更适合调节类交互;相比触摸屏,它的操作反馈更直接,在嵌入式设备、调试面板和工业控制场景中都很常见。当前代码已经具备方向判断、范围限制、步进配置和按键确认能力,因此可以自然延伸到多种参数控制场景。
| 应用场景 | 实现思路 | 可扩展能力 |
|---|---|---|
| 音量调节旋钮 | 将encoder_value映射为音量等级 | 后续可扩展蜂鸣器或音频模块,实现真实音量反馈 |
| 屏幕亮度调节 | 将计数值映射为亮度百分比 | 后续可扩展 LCD 显示当前亮度 |
| 菜单选项切换 | 顺时针切换下一个菜单,逆时针切换上一个菜单 | 后续可结合按键确认当前菜单项 |
| 参数配置面板 | 使用旋转变化调整阈值、速度、模式编号 | 可扩展为传感器报警阈值或 LED 灯效速度配置 |
| 工业控制输入 | 用编码器模拟档位、速度或位置参数 | 可扩展电机控制、PWM 输出和状态显示 |
| 数据采集调试 | 用旋钮调整采样间隔或滤波参数 | 可与传感器采集实验组合,形成可调式实验平台 |
| 交互式教学实验 | 通过旋转动作观察 GPIO 输入、边沿检测和消抖逻辑 | 可把抽象输入信号转化成可观察的串口输出结果 |
从工程结构看,当前代码已经把硬件初始化、旋转检测、按键检测和数值限制拆成独立函数。后续扩展时,不需要重写输入读取逻辑,只要把encoder_value绑定到具体业务功能即可。例如控制 LED 灯效速度时,可以让计数值影响延时参数;控制菜单时,可以让计数值对应不同选项索引;控制阈值时,可以让计数值参与传感器判断条件。这样的结构让旋转编码器从单一实验模块变成通用输入组件。
总结
本实验通过 CanMV K210 开发板完成了旋转编码器输入检测,核心能力包括 FPIOA 引脚映射、GPIOHS 输入读取、上拉输入模式、边沿检测、方向判断、按键消抖、数值范围限制和循环轮询。代码通过 CLK 与 DT 的电平关系判断旋转方向,通过 SW 的下降沿判断按键按下,再把硬件动作转换成程序中的encoder_value状态变量。
旋转编码器实验非常适合放在 GPIO 输出实验之后学习。LED 实验强调程序如何控制硬件,旋转编码器实验强调硬件动作如何反过来影响程序。一个负责输出,一个负责输入,两者组合后就可以形成完整的人机交互基础。后续课程可以继续扩展到 LCD 显示当前数值、蜂鸣器提示按键确认、LED 灯效速度调节、传感器阈值设置、菜单系统控制和 AI 硬件交互面板等方向。理解了旋转编码器的输入逻辑,更多智能硬件项目中的参数调节和菜单控制都可以建立在这套思路之上。