Wiznet makers

ruilixin6

Published August 03, 2026 ©

61 UCC

0 VAR

0 Contests

0 Followers

0 Following

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.

COMPONENTS
PROJECT DESCRIPTION

【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

Here, we adopt the EC11 rotary encoder component with 20 pulses and 20 positioning points, whose structure is as follows:
The names and functions of the respective components are as follows:
Code wheel: A core component of an encoder that generates signals through rotational operation. Equally spaced conductive strips are distributed on the wheel; when the wheel rotates, these conductive strips sequentially come into contact with the output contacts, thereby generating pulse signals.
Detent piece: It locates each rotational position and provides mechanical feedback, giving the encoder a distinct rotational feel. The EC11 we use has 20 detents, meaning there is a pause for every 18 degrees of rotation;
Contact: works in coordination with the wave plate to detect the position change of the conductive strip and generate a signal;
Shaft sleeve and housing: They provide physical structural support for the encoder, and electronic components and pins are usually mounted on the housing.
EC11 usually has 5 pins, as shown in the following figure:
Pin A (Red) Signal Output 1: Outputs rotation pulse signals
Pin B (blue) Signal Output 2: Also outputs a pulse signal, but with a phase difference relative to Pin A
Pin C (Green) Common Terminal: Common ground terminal for signal channels A and B
Pins D and E: the two terminals of the push switch, which are short-circuited when the button is pressed
Its working principle is as follows: EC11 outputs incremental rotation signals through two signal channels (pins A and B). During rotation, the A and B signal channels generate interleaved pulses, and the rotation direction (clockwise or counterclockwise) can be determined by comparing the phase relationship between the two:
Simply put, it means:
When rotating clockwise, phase A leads phase B by 90 degrees, that is, when phase A is at the falling edge, phase B is at low level; when phase A is at the rising edge, phase B is at high level.
When rotating counterclockwise, phase B leads phase A by 90 degrees, that is, when phase A is at the falling edge, phase B is at a high level; when phase A is at the rising edge, phase B is at a low level.
It is not difficult to find that whenever a level transition occurs in phase A or phase B, the rotation direction can be determined only by detecting the state of the other phase:
Clockwise: both phases are at the rising edge simultaneously or at the falling edge simultaneously;
Counterclockwise: the two phases do not present rising edges at the same time, nor do they present falling edges at the same time;
Meanwhile, the number of rotation steps (increment) can be determined in combination with pulse counting.
Here, the EC11 rotary encoder also comes with a push switch (pins D and E): when the encoder is pressed, its internal mechanical structure triggers a key signal, which is typically used for operation control or confirmation.

2. Application Experiment

In the following code, we have customized an 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.
Here, we need to insert the 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:
The wiring conditions of the components are as follows:
The sample code is as follows, located in the folder of the supporting materials: 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)
Here, the core part that detects the rotation count and rotation direction of the rotary encoder is the _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
When the rising edge of signal A is detected, the system enters the _handle_rotation method to obtain the status of signal B and make a judgment:
When the B-phase signal is at a low level, it is considered as clockwise rotation, so the rotation counter self. rotation_count increments by 1
When the B-phase signal is at a high level, it is judged as counterclockwise rotation, so the rotation counter self. rotation_count decrements by 1
Meanwhile, we also set the key of the rotary encoder to be triggered on the falling edge, and when the key is pressed, the _handle_button method will be called, in which the key status is updated and the rotary counter is reset.
We use a custom 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='')
In this code, we ensure that 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.
In the code for building the Progress Bar section:
bar = '\033[92m' + '█' * block + '\033[91m' + '-' * (self.bar_length - block) + '\033[0m'
The meaning of each part is as follows:
'\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
Next, we use the Print method to print the Progress Bar, where we utilize the carriage return character '\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.
In the main program section, we continuously read the value of the rotary encoder and update the Progress Bar, and reset the Progress Bar when a key press is detected.
Burn the code, open the terminal, and the following content will be displayed:
When we rotate the EC11 encoder clockwise, we can see that the green rectangular box of the Progress Bar increases:
When we rotate the EC11 encoder counterclockwise, we can see that the green rectangular box of the Progress Bar decreases:
Here, we may find that when the rotary encoder rotates, the terminal Progress Bar sometimes jumps by several units. The reason is that the count value of the rotary encoder does not only change by one unit each time the rotary encoder rotates. We add the following code in the main loop to print the rotation count value of the rotary encoder:
print(f"Rotation count: {current_rotation}")
Burn the code, run it, and then open the terminal:
It can be observed that the count value of the rotary encoder sometimes jumps by several units when it rotates once. This is mainly because, as a mechanical device, when the user rotates the encoder, the contact points will repeatedly make and break contact in a short period of time, causing signal jitter and thus generating redundant count pulses.
To solve this problem, we can use a capacitor or an 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.
In the timer chapter, we will use a timer to scan the encoder status every 10ms for debouncing.
Here, you can refer to the following articles for knowledge related to Object Oriented programming and custom classes in Python:
 
Documents
Comments Write