Wiznet makers

ruilixin6

Published August 04, 2026 ©

74 UCC

0 VAR

0 Contests

0 Followers

0 Following

Practical Guide to Receive Idle Interrupt and Transmit Idle Interrupt with MicroPython + Pico

RP2040 MicroPython demos for UART RXIDLE and TXIDLE interrupts.

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. Preparations before the experiment

In the following experiment, we need to use a MiniUSB data cable to connect the Fengya No. 1 Board - Universal Compatible Expansion Board 's UART0 USB port to the USB port on the computer:

2. Receive idle interrupt (IRQ_RXIDLE): Receive a complete data frame

The UART receive idle interrupt (IRQ_RXIDLE) is a core interrupt mechanism for UART communication in MicroPython / embedded development. Put simply: when the UART receive pin (RX) transitions from the busy state of "sustained data transmission" to the idle state of "no new bytes received within the specified duration", this interrupt will be triggered.Its core value lies in accurately identifying the completion of receiving "a complete frame of data/instruction" — it does not rely on fixed frame lengths or end markers (such as \r\n), and as long as data transmission pauses (idle), it determines that a frame of data has been received, perfectly solving the problem of "how to judge the end of variable-length instruction transmission", making it a common solution for parsing serial port instruction frames in scenarios such as the Internet of Things and industrial control.
Here, we periodically collect simulated temperature and humidity data in the code below, and respond to specific commands and return corresponding data through the UART idle interrupt; after the UART receives a command and triggers the idle interrupt, it parses the command and returns environmental data (or a prompt for unknown commands), then resets the buffer.
The complete code is as follows:
# Python env   : MicroPython v1.27.0
# -*- coding: utf-8 -*-        
# @Time    : 2025/12/28 上午10:37   
# @Author  : 李清水            
# @File    : main.py       
# @Description : 串口接收空闲中断(IRQ_RXIDLE),接收完整指令并返回环境数据

# ======================================== 导入相关模块 =========================================

# 导入MicroPython标准库模块
from machine import Pin, UART
# 导入时间模块
import time
# 用于模拟环境传感器数据
import random

# ======================================== 全局变量 ============================================

# 环境数据缓存(温度+湿度)
env_data_cache = {"temp": 0.0, "humi": 0.0}
# 串口接收缓冲区(预分配128字节)
rx_buffer = bytearray(128)
# 缓冲区索引
buf_index = 0

# ======================================== 功能函数 ============================================

def uart_rxidle_handler(uart: UART) -> None:
    """
    串口接收空闲中断处理函数:读取完整指令并返回环境数据。

    Args:
        uart (UART): 触发中断的UART实例(machine.UART类型)。

    Notes:
        - 中断触发时读取所有未处理字符至缓冲区,避免数据丢失。
        - 缓冲区最大长度限制为64字节,防止索引越界导致程序异常。
        - 仅支持UTF-8编码指令解析,非UTF-8字符可能导致解码失败。
        - 识别"GET_ENV_DATA"指令并返回温湿度数据,其他指令返回未知提示。
        - 指令解析完成后立即重置缓冲区索引,确保下一次接收不受影响。
    """
    global buf_index, env_data_cache

    # 读取所有未处理的字符到缓冲区
    while uart.any() and buf_index < len(rx_buffer):
        rx_buffer[buf_index] = uart.read(1)[0]
        buf_index += 1

    # 解析完整指令
    if buf_index > 0:
        cmd = bytes(rx_buffer[:buf_index]).decode('utf-8').strip()
        # 响应环境数据查询指令
        if cmd == "GET_ENV_DATA":
            resp = f"Env Data - Temp: {env_data_cache['temp']}C, Humidity: {env_data_cache['humi']}%\r\n"
            uart.write(resp)
        else:
            uart.write(f"Unknown command: {cmd}\r\n")
        # 重置缓冲区
        buf_index = 0

def collect_env_data() -> tuple[float, float]:
    """
    模拟环境传感器(温湿度)数据采集函数(主任务核心业务)。

    Args:

    Notes:
        - 温度模拟范围为18.0~35.0℃,湿度模拟范围为30.0~80.0%,保留2位小数。
        - 采集数据后更新全局环境数据缓存,确保中断响应指令时获取最新数据。
        - 返回值为元组格式,第一个元素为温度(℃),第二个为湿度(%)。
        - 数据采集无硬件依赖,仅用于业务逻辑模拟。
    """
    global env_data_cache

    temp = round(random.uniform(18.0, 35.0), 2)
    humi = round(random.uniform(30.0, 80.0), 2)
    # 更新全局缓存
    env_data_cache["temp"] = temp
    env_data_cache["humi"] = humi
    return temp, humi

# ======================================== 初始化配置 ==========================================

# 上电延时3s
time.sleep(3)
# 打印调试消息
print("FreakStudio: Testing UART Recv IRQ_RX")

# 初始化状态LED(板载Pin25)- 指示数据采集状态
status_led = Pin(25, Pin.OUT)
status_led.off()

# 初始化UART0:TX=Pin0,RX=Pin1,波特率115200
uart0 = UART(0, baudrate=115200, tx=Pin(0), rx=Pin(1))
# 配置串口中断:接收空闲触发(IRQ_RXIDLE),软中断
uart0.irq(handler=uart_rxidle_handler, trigger=UART.IRQ_RXIDLE, hard=False)

# ========================================  主程序  ============================================

# 主循环:核心业务(环境数据采集)+ 等待中断响应
while True:
    # 真实业务任务1:采集环境数据
    current_temp, current_humi = collect_env_data()
    # 真实业务任务2:打印采集数据(调试)
    print(f"Collected - Temp: {current_temp}°C, Humidity: {current_humi}%")
    # LED闪烁指示采集动作
    status_led.on()
    time.sleep(0.1)
    status_led.off()
    # 模拟传感器采集间隔
    time.sleep(1)
In the code above, we perform the following operations:
Program startup and initialization;
The program enters an infinite loop and repeatedly performs the following operations at a cycle of 1 second:
Call collect_env_data function to simulate environmental data collection: generate temperature data ranging from 18.0 to 35.0°C and humidity data ranging from 30.0 to 80.0%(both retaining 2 decimal places), and update the global env_data_cache cache;
Print the currently collected temperature and humidity data (for debugging use), and control the status LED to turn on for 0.1 second before turning off (to visually indicate the Data Acquisition action);
Delay for 1 second to simulate the actual sampling interval of the sensor, then repeat the above steps.
When the serial port receives a complete command (triggering the "receive idle" interrupt), the main loop is suspended and the interrupt processing logic is executed:
Read all unprocessed characters from the serial port and store them in the pre-allocated 128-byte receive buffer (index restriction is applied to prevent out-of-bounds access);
Decode the buffer data into a UTF-8 string, strip leading and trailing whitespace, and parse the instruction content;
Command Matching: If the command is "GET_ENV_DATA", the latest temperature and humidity data in the cache will be returned via the serial port; for other commands, the prompt "Unknown command" will be returned;
Reset the buffer index, the interrupt handling is completed, and the program returns to the main loop to continue executing the Data Acquisition task.
The overall flow chart is shown below:
Launch the XCOM software and select the COM port corresponding to the CH340K chip:
The serial communication parameters are consistent with those in the program:
The remaining transmission and reception parameters can be kept as default:
Click to open the serial port:
Burn the program, open the terminal, and the following content will be displayed:
As can be seen, the LED on the core board flashes periodically, and the serial port command response function also works as expected (sending GET_ENV_DATA returns the corresponding temperature and humidity data, while sending OTHER_COMMAND will prompt an unknown command), indicating that the overall function operates normally.

3. Transmit idle interrupt (IRQ_TXIDLE): Confirm that the transmission of long data is completed

The UART transmission idle interrupt (IRQ_TXIDLE) is a core interrupt mechanism for the data transmission link in UART communication. Specifically, this interrupt is triggered when the UART transmitter (TX) has completely sent all bytes to be transmitted in the transmit buffer, the transmit buffer is empty, and the TX pin enters an idle state with no data output.Its core value lies in accurately confirming that "the transmission process of a long segment of data / a complete frame of data has been thoroughly completed", rather than merely triggering the transmission action. It is particularly suitable for scenarios requiring the transmission of large-sized data (such as batch sensor logs, long text responses, and binary data streams), and can effectively prevent data loss or disorder caused by modifying the buffer, closing the serial port, or initiating the next transmission before the completion of transmission is confirmed.
It corresponds to the receive idle interrupt (IRQ_RXIDLE):
IRQ_RXIDLE Focus on the event of "complete data frame reception finished";
IRQ_TXIDLE Focus on the event that "the complete data frame has been fully transmitted".
In the code below, we use the IRQ_TXIDLE interrupt to indicate that the long log transmission is nearly complete, and implement reliable serial transmission of simulated sensor logs in combination with the batch caching mechanism.
The complete code is as follows:
# Python env   : MicroPython v1.27.0
# -*- coding: utf-8 -*-        
# @Time    : 2025/12/28 上午11:12   
# @Author  : 李清水            
# @File    : main.py       
# @Description : 串口发送空闲中断(IRQ_TXIDLE)- 长日志传输状态提示

# ======================================== 导入相关模块 =========================================

# 导入MicroPython标准库模块
from machine import Pin, UART
# 导入时间模块
import time
# 用于模拟传感器数据
import random

# ======================================== 全局变量 ============================================

# 传感器日志缓存(积累6条后发送,确保长度>5触发TXIDLE)
sensor_logs = []
# 日志编号
log_seq = 1
# 发送状态:IDLE(空闲)/SENDING(发送中)
tx_status = "IDLE"
# TXIDLE中断触发标志
txidle_flag = False

# ======================================== 功能函数 ============================================

def uart_txidle_handler(uart: UART) -> None:
    """
    RP2串口TXIDLE中断处理函数(软中断),仅标记中断触发状态避免硬件抢占。

    Args:
        uart (UART): 触发中断的UART实例(machine.UART类型)。

    Notes:
        - RP2的TXIDLE中断在剩余5个字符待发送时触发,非发送完成状态。
        - 仅在发送中(SENDING)状态下标记,避免空触发干扰状态机。
        - 不执行UART写入操作,防止硬件抢占导致数据错乱。
    """
    global txidle_flag
    # 仅在发送中标记
    if tx_status == "SENDING":
        txidle_flag = True

def generate_sensor_log() -> str:
    """
    生成固定格式的模拟传感器日志,适配RP2 UART缓冲区特性。

    Args:
        None

    Notes:
        - 固定字段格式确保单条日志字符数一致,避免长度波动导致数据截断。
        - 日志编号4位补零,防止编号循环过快导致视觉混淆。
        - 编号超过9999时重置,避免数值溢出影响日志可读性。

    Return:
        (str): 拟传感器日志文件,格式固定。
    """
    global log_seq

    temp = round(random.uniform(20.0, 40.0), 2)
    pressure = round(random.uniform(950.0, 1050.0), 2)
    # 强制固定格式,确保每条日志长度一致(避免截断)
    log = f"LOG_{log_seq:02d} | Time: {int(time.time())}s | Temp: {temp:5.2f} degC | Pressure: {pressure:6.2f} hPa"
    log_seq += 1
    return log

def send_log_batch(uart: UART) -> None:
    """
    RP2串口3条日志批量发送函数,适配硬件FIFO缓冲区容量(防截断)。

    Args:
        uart (UART): 用于发送日志的UART实例(machine.UART类型)。

    Notes:
        - 批量阈值设为3条,总字符数适配RP2 UART FIFO缓冲区,避免数据积压。
        - 使用uart.flush()阻塞等待,替代不可靠的uart.txdone()确保发送完成。
        - 仅在IDLE状态下执行发送,避免多线程式状态竞态导致数据错乱。
        - 一次性写入整批日志,保证硬件层面的原子发送操作。

    Return:
        None
    """
    global sensor_logs, tx_status, txidle_flag

    if len(sensor_logs) >= 3 and tx_status == "IDLE":
        tx_status = "SENDING"

        # 重置中断标志
        txidle_flag = False

        # 拼接整批日志(原子块,一次性发送)
        long_log = "\r\n".join(sensor_logs) + "\r\n===== LOG BATCH END =====\r\n"
        print(f"Sending {len(sensor_logs)} logs...")

        # 一次性写入整批日志(硬件原子操作)
        uart.write(long_log)

        # 阻塞直到整批日志发送完成
        while uart.flush():
            time.sleep_ms(5)

        # 发送完成后,终端输出TXIDLE提示
        if txidle_flag:
            print("[STATUS] Transmission nearly complete (TXIDLE triggered)")
            txidle_flag = False

        # 清空缓存+恢复状态
        sensor_logs.clear()
        tx_status = "IDLE"

# ======================================== 初始化配置 ==========================================

# 上电延时3s
time.sleep(3)
# 打印调试消息
print("FreakStudio: Testing UART Transmit IRQ_TXIDLE")

# 初始化发送状态LED(板载Pin25)
tx_led = Pin(25, Pin.OUT)
tx_led.off()

# 初始化UART0:TX=Pin0,RX=Pin1,波特率115200
uart0 = UART(0, baudrate=115200, tx=Pin(0), rx=Pin(1))
# 配置串口中断:发送空闲触发(IRQ_TXIDLE),软中断
uart0.irq(handler=uart_txidle_handler, trigger=UART.IRQ_TXIDLE, hard=False)

# ========================================  主程序  ============================================

while True:
    # 仅空闲时生成日志(避免缓冲区过载)
    if tx_status == "IDLE":
        log = generate_sensor_log()
        sensor_logs.append(log)
        print(f"Generated: {log}")

    # 批量发送(阻塞直到完成)
    send_log_batch(uart0)

    # 更新LED和状态打印
    tx_led.value(1 if tx_status != "IDLE" else 0)
    print(f"Transmit Status: {tx_status}")

    # 固定间隔(降低循环频率,减少冲突)
    time.sleep(2)
In the code above, we have completed the following tasks:
Program startup and initialization;
The program enters an infinite loop and repeatedly performs the following operations at a cycle of 2 seconds:
Log generation: This function is called only when the transmission status (tx_status) is IDLE generate_sensor_log to generate a simulated temperature and pressure sensor log in a fixed format (including log ID, timestamp, temperature, and barometric pressure), add the log to the cache list sensor_logs and print the generation information;
Batch sending: call the send_log_batch function. If the number of cached logs is ≥3 and the system is in IDLE state, execute the batch sending logic:
Switch tx_status to SENDING and reset the TXIDLE interrupt flag txidle_flag;
Concatenate the 3 logs in the cache into a complete long text (with the "LOG BATCH END" end marker), and write and send them in one go via UART0 to ensure hardware atomic operations and avoid data truncation;
Call uart. flush () to block and wait until all data is sent (during which it is detected every 5ms, as a replacement for the unreliable uart. txdone ());
If the TXIDLE interrupt is triggered (txidle_flag is True), print the prompt "Transmission is nearly complete" and reset the flag;
Clear the log cache and restore tx_status to IDLE;
Status indication: Control the LED according to tx_status (lit when SENDING, off when IDLE), and print the current transmission status;
Delay for 2 seconds: reduce the loop frequency, cut down transmission conflicts, and adapt to the features of the hardware buffer.
TXIDLE interrupt response: When the remaining 5 characters in the UART0 send buffer are to be sent (RP2 hardware characteristics), the IRQ_TXIDLE interrupt is triggered, and the uart_txidle_handler function is executed: only when the tx_status is SENDING, the txidle_flag is marked as True (only the status is marked, and the write operation is not performed to avoid hardware preemption leading to data disorder), for the main loop to determine the status of the transmission approaching completion.
The complete flow chart is as follows:
Open the XCOM software and select the COM port corresponding to the CH340K chip:
The serial communication parameters are consistent with those in the program:
Keep the rest of the sending and receiving parameters as default:
Click to open the serial port:
Burn the program, open the terminal, and the following content will be displayed:
We can see that the running effect of the program fully meets the expectations:
The main loop periodically generates simulated sensor logs in a fixed format (the terminal on the left continuously outputs the log information corresponding to "Generated"), and automatically enters the sending state when the log cache accumulates to 3 entries (the terminal displays "Sending 3 logs");
During the transmission process, the IRQ_TXIDLE interrupt is triggered, and the terminal synchronously prints the callback prompt "Transmission nearly complete"("serial port transmission interrupt triggers callback function"); meanwhile, the serial port assistant on the right side completely receives the batch log marked with "LOG BATCH END".
The status will be restored to IDLE after the transmission is completed.
The loop of the log generation - caching - transmission process is orderly, which not only verifies the marking function of the TXIDLE interrupt for the long data transmission status, but also ensures the reliable serial port transmission of batch logs.
When using the MicroPython UART transmit idle interrupt (IRQ_TXIDLE) on the RP2 platform, the core precaution is to strictly control the total number of characters sent in a single batch to adapt to the limited capacity of the RP2 UART hardware transmit buffer (FIFO):
If the total number of characters far exceeds this capacity, data will accumulate in the software buffer. Meanwhile, the TXIDLE interrupt of RP2 is only triggered when there are 5 remaining characters to be sent. When used with the unreliable uart. txdone ()(which only checks whether the single-character transmit register is empty without detecting the FIFO), it is easy to misjudge that the transmission is completed, modify the transmission status in advance and clear the cache, which will interfere with the final transmission process of the hardware and result in data truncation;
Therefore, the number of characters sent in a single transmission shall be controlled within the safe load range of the FIFO, and priority shall be given to using uart. flush ()(which blocks until all data, including that in the FIFO, is completely sent) instead of uart. txdone (). Meanwhile, the hard= True for disabling interrupts shall be replaced with software interrupts to avoid data disorder aggravated by hardware interrupt preemption, and ensure the synchronization between interrupt triggering and transmission status management.
Documents
Comments Write