Pico + MicroPython: Build Rotary Encoder Progress Bar – Hardware, Principles & Full Code
This tutorial covers EC11 rotary encoder and its MicroPython interrupt experiment with a terminal progress bar.
【Preliminary Note】The original hardware example in this article was written based on the RP2040. The actual hardware used in this hands-on demonstration features the W55RP20 as the main controller chip. The circuit logic and UF2 flashing operation principles are universally applicable, with only the main controller model differing. The original chip model mentioned in the circuit descriptions below is provided for reference purposes only.
1. Basic Knowledge of Rotary Encoder
EC11 usually has 5 pins, as shown in the following figure:2. Application Experiment
EC11Encoder class, which reads the rotation steps and button status of the rotary encoder through interrupt processing, and updates the Progress Bar on the terminal in the main program loop to display the rotation degree of the rotary encoder.Fengya No. 1 Board - Universal Compatible Expansion Board into the Fengya No. 1 Board - LCD Screen Human-Machine Interaction Board, and turn on the RE-CLK, RE-DT, and RE-SW on the SWITCH2 DIP switch. The physical connection diagram is shown below:elegance-devkit v1\Demo\05 GPIO_RotaryEncoder:# Python env : MicroPython v1.23.0
# -*- coding: utf-8 -*-
# @Time : 2024/9/18 下午10:44
# @Author : 李清水
# @File : main.py
# @Description : GPIO类实验,读取旋转编码器的值
# ======================================== 导入相关模块 =========================================
# 导入硬件相关模块
from machine import Pin
# 导入时间相关模块
import time
# ======================================== 全局变量 ============================================
# ======================================== 功能函数 ============================================
# ======================================== 自定义类 ============================================
class EC11Encoder:
"""
EC11 旋转编码器类,用于通过 GPIO 引脚读取旋转编码器的信号,检测旋转方向和计数,并响应按键事件。
该类使用 GPIO 引脚读取旋转编码器的 A 相、B 相信号,并根据 A 相信号的上升沿和 B 相信号的状态
判断旋转方向。同时,监控按键引脚的按下和释放事件,用于重置旋转计数。每次按键按下都会清除计数器,
使得设备可以通过按键进行复位操作。
Attributes:
pin_a (Pin): A 相信号的 GPIO 引脚对象。用于读取旋转编码器的信号。
pin_b (Pin): B 相信号的 GPIO 引脚对象。用于与 A 相信号一起判断旋转方向。
pin_btn (Pin): 按键信号的 GPIO 引脚对象。用于检测按键的按下和释放。
rotation_count (int): 旋转计数器,记录旋转编码器的旋转次数。正值表示顺时针旋转,负值表示逆时针旋转。
last_state_a (int): 上一次读取的 A 相信号状态。用于判断旋转方向。
button_pressed (bool): 按键是否被按下。若按键被按下为 True,否则为 False。
Methods:
get_rotation_count(): 获取当前旋转计数。
reset_rotation_count(): 重置旋转计数器。
is_button_pressed(): 检测按键是否被按下。
_handle_rotation(pin: Pin): 内部中断处理函数,用于处理旋转信号并更新旋转计数。
_handle_button(pin: Pin): 内部中断处理函数,用于检测按键按下和释放事件。
"""
def __init__(self, pin_a: int, pin_b: int, pin_btn: int) -> None:
"""
初始化 EC11 旋转编码器。
Args:
pin_a (int): A 相信号的 GPIO 引脚编号。
pin_b (int): B 相信号的 GPIO 引脚编号。
pin_btn (int): 按键信号的 GPIO 引脚编号。
"""
self.pin_a = Pin(pin_a, Pin.IN)
self.pin_b = Pin(pin_b, Pin.IN)
self.pin_btn = Pin(pin_btn, Pin.IN, Pin.PULL_UP)
# 旋转计数器
self.rotation_count = 0
# 上一次 A 相的状态
self.last_state_a = self.pin_a.value()
# 按键是否被按下
self.button_pressed = False
# 设置中断处理
self.pin_a.irq(trigger=Pin.IRQ_RISING, handler=self._handle_rotation)
self.pin_btn.irq(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING, handler=self._handle_button)
def _handle_rotation(self, pin: Pin) -> None:
"""
中断回调函数,检测旋转编码器的旋转方向。
根据 A 相的上升沿触发,并读取 B 相状态来判断旋转方向。
Args:
pin (Pin): 触发中断的 A 相信号引脚。
"""
current_state_b = self.pin_b.value()
# 顺时针旋转
if current_state_b == 0:
self.rotation_count += 1
# 逆时针旋转
else:
self.rotation_count -= 1
def _handle_button(self, pin: Pin) -> None:
"""
中断回调函数,检测按键的按下和释放。
按键按下时更新状态,释放时重置旋转计数。
Args:
pin (Pin): 触发中断的按键信号引脚。
"""
# 按键按下
if self.pin_btn.value() == 0:
self.button_pressed = True
# 按键释放
else:
self.button_pressed = False
# 重置旋转计数
self.rotation_count = 0
def get_rotation_count(self) -> int:
"""
获取当前旋转计数。
返回旋转计数,正值表示顺时针,负值表示逆时针。
Returns:
int: 当前旋转计数。
"""
return self.rotation_count
def reset_rotation_count(self) -> None:
"""
重置旋转计数器。
将旋转计数器清零。
"""
self.rotation_count = 0
def is_button_pressed(self) -> bool:
"""
检测按键是否被按下。
返回按键状态,按键按下返回 True,未按下返回 False。
Returns:
bool: 按键是否被按下。
"""
return self.button_pressed
# 终端进度条类
class ProgressBar:
"""
终端进度条类,用于在终端显示一个可更新的进度条。
该类通过在终端中绘制进度条的方式来显示当前任务的进度,支持动态更新显示进度条,并且允许用户
自定义最大值和进度条的长度。进度条的显示形式使用绿色表示已完成部分,红色表示剩余部分。
Attributes:
max_value (int): 进度条的最大值,表示任务的总进度。当当前进度达到此值时,进度条显示 100%。
bar_length (int): 进度条的总长度,用于控制进度条的宽度,默认为 50。
Methods:
update(current_value: int) -> None: 更新进度条的显示,传入当前进度值。
reset() -> None: 重置进度条,将进度条重置为 0%,显示全红色的条形。
"""
def __init__(self, max_value: int, bar_length: int = 50) -> None:
"""
初始化进度条。
Args:
max_value (int): 进度条的最大值,达到此值时进度条显示100%。
bar_length (int): 进度条的总长度,默认为 50。
"""
self.max_value = max_value
self.bar_length = bar_length
def update(self, current_value: int) -> None:
"""
更新进度条。
根据当前值计算并更新进度条的显示,确保当前值不超过最大值。
Args:
current_value (int): 当前进度条的值。
"""
if current_value > self.max_value:
current_value = self.max_value
if current_value < 0:
current_value = 0
progress = current_value / self.max_value
block = int(self.bar_length * progress)
# 绿色表示进度,红色表示剩余
bar = '\033[92m' + '█' * block + '\033[91m' + '-' * (self.bar_length - block) + '\033[0m'
print(f"\r[{bar}]", end='')
def reset(self) -> None:
"""
重置进度条。
将进度条重置为 0%,即全为红色。
"""
print(f"\r\033[91m[{'-' * self.bar_length}]\033[0m", end='')
# ======================================== 初始化配置 ==========================================
# 上电延时3s
time.sleep(3)
# 打印调试信息
print("FreakStudio : GPIO read Rotary Encoder value")
# 创建EC11旋转编码器对象,使用GPIO26和GPIO27作为A相和B相,使用GPIO21作为按键
encoder = EC11Encoder(pin_a=26, pin_b=27, pin_btn=21)
# 创建终端进度条对象,用于显示进度,旋转20次达到100%
progress_bar = ProgressBar(max_value=20)
# ======================================== 主程序 ===========================================
# 主循环中获取旋转计数值和按键状态
while True:
# 获取旋转计数值
current_rotation = encoder.get_rotation_count()
# print(f"Rotation count: {current_rotation}")
# 更新进度条
progress_bar.update(current_rotation)
# 按键被按下时,重置进度条
if encoder.is_button_pressed():
progress_bar.reset()
# 每隔10ms秒更新一次
time.sleep_ms(10)_handle_rotation method; we configure the pin connected to phase A of the rotary encoder to input mode and enable rising-edge triggered interrupt, and set its interrupt callback function to the _handle_rotation method:def _handle_rotation(self, pin: Pin) -> None:
"""
中断回调函数,检测旋转编码器的旋转方向。
根据 A 相的上升沿触发,并读取 B 相状态来判断旋转方向。
Args:
pin (Pin): 触发中断的 A 相信号引脚。
"""
current_state_b = self.pin_b.value()
# 顺时针旋转
if current_state_b == 0:
self.rotation_count += 1
# 逆时针旋转
else:
self.rotation_count -= 1_handle_rotation method to obtain the status of signal B and make a judgment:self. rotation_count increments by 1self. rotation_count decrements by 1_handle_button method will be called, in which the key status is updated and the rotary counter is reset.ProgressBar class to dynamically display progress in the terminal. We can dynamically update the display of the Progress Bar via the update method, or call the reset method to reset the Progress Bar to zero for a fresh start of display. The core code snippet is as follows:def update(self, current_value: int) -> None:
"""
更新进度条。
根据当前值计算并更新进度条的显示,确保当前值不超过最大值。
Args:
current_value (int): 当前进度条的值。
"""
if current_value > self.max_value:
current_value = self.max_value
if current_value < 0:
current_value = 0
progress = current_value / self.max_value
block = int(self.bar_length * progress)
# 绿色表示进度,红色表示剩余
bar = '\033[92m' + '█' * block + '\033[91m' + '-' * (self.bar_length - block) + '\033[0m'
print(f"\r[{bar}]", end='')current_value stays within the valid range, i. e., no greater than the maximum value and no less than 0; then we calculate the proportion of the current value to the maximum value to obtain the number of blocks displayed in the Progress Bar.bar = '\033[92m' + '█' * block + '\033[91m' + '-' * (self.bar_length - block) + '\033[0m''\033[92m': ANSI escape code, sets the text color to green'█' * block: Green progress segment'\033[91m': ANSI escape code, sets the text color to red'-' *(self. bar_length - block): Remaining red portion'\033[0m': ANSI escape code, resets the text color to the default value'\r' to return to the start of the line so as to overwrite the previous Progress Bar output in the terminal; then we add the end parameter set to end='' to avoid line breaks after each print, ensuring that the Progress Bar only prints dynamically updated content within the same line.print(f"Rotation count: {current_rotation}")RC circuit to filter the signal, slow down its rapid changes and smooth out jitter; alternatively, at the software level, we can judge the signals collected multiple times in a short period of time, and only regard a signal as valid when the same signal is detected for several consecutive times, so as to address this issue.