Practical Guide to Receive Idle Interrupt and Transmit Idle Interrupt with MicroPython + Pico
RP2040 MicroPython demos for UART RXIDLE and TXIDLE interrupts.
【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
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
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.# 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)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;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
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".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.# 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)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;send_log_batch function. If the number of cached logs is ≥3 and the system is in IDLE state, execute the batch sending logic:txidle_flag;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 ());txidle_flag is True), print the prompt "Transmission is nearly complete" and reset the flag;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.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):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;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.