MicroPython+Pico 接收空闲中断和发送空闲中断全实战
1.实验前的准备
在下面实验中,我们需要将通过 MiniUSB 数据线将 风雅一号板-通用兼容扩展板 上 UART0 的 USB 接口与电脑上的 USB 接口连接:


2.接收空闲中断(IRQ_RXIDLE):接收完整数据帧
串口接收空闲中断(IRQ_RXIDLE)是 MicroPython / 嵌入式开发中针对串口通信的核心中断机制,通俗来说:当串口接收端(RX)从 “有数据持续传输” 的忙碌状态,进入 “指定时间内无新字节接收” 的空闲状态时,就会触发该中断。它的核心价值是精准识别 “一帧完整的数据 / 指令” 接收完成 —— 不用依赖固定帧长、结束符(如 \r\n),只要数据传输暂停(空闲),就判定一帧数据接收完毕,完美解决了 “如何判断不定长指令传输结束” 的问题,是物联网、工业控制等场景中解析串口指令帧的常用方案。
这里,我们在下面的代码中周期性采集模拟温湿度数据,通过串口空闲中断响应特定指令并返回对应数据;串口接收到指令触发空闲中断后,解析指令并返回环境数据(或未知指令提示),随后重置缓冲区。
完整代码如下:
# 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)
在上面的代码中,我们进行如下的工作:
- 程序启动与初始化;
- 程序进入无限循环,以 1 秒为周期重复执行以下操作:
- 调用
collect_env_data函数,模拟采集环境数据:生成 18.0~35.0℃ 的温度、30.0~80.0% 的湿度数据(均保留 2 位小数),并更新全局env_data_cache缓存; - 打印当前采集的温湿度数据(调试用),控制状态 LED 点亮 0.1 秒后熄灭(直观指示数据采集动作);
- 延时 1 秒,模拟传感器的实际采集间隔,随后重复上述步骤。
- 调用
- 当串口接收到完整指令(触发 “接收空闲” 中断)时,暂停主循环,执行中断处理逻辑:
- 读取串口所有未处理的字符,存入预分配的 128 字节接收缓冲区(限制索引防止越界);
- 将缓冲区数据解码为 UTF-8 字符串并去除首尾空白,解析指令内容;
- 指令匹配:若为 “GET_ENV_DATA”,则通过串口返回缓存中最新的温湿度数据;若为其他指令,返回 “Unknown command” 提示;
- 重置缓冲区索引,中断处理完成,程序回到主循环继续执行数据采集任务。
下面是整体的流程图:

打开 XCOM 软件,选择 CH340K 芯片对应 COM 口:

串口通信参数和程序中一致:

其余发送和接收参数默认即可:


点击打开串口:

烧录程序,打开终端,显示如下:

可以看到,核心板上的 LED 灯定时闪烁,同时串口指令响应功能也符合预期(发送 GET_ENV_DATA 能返回对应温湿度、发送 OTHER_COMMAND 会提示未知指令),整体功能运行正常。
3.发送空闲中断(IRQ_TXIDLE):确认长数据发送完成
串口发送空闲中断(IRQ_TXIDLE)是串口通信中针对数据发送环节的核心中断机制,具体指当串口发送端(TX)将发送缓冲区中所有待发送字节全部发送完毕,发送缓冲区为空且 TX 引脚进入无数据输出的空闲状态时触发该中断;其核心价值在于精准确认 “一段长数据 / 一帧完整数据的发送流程彻底完成”,而非仅触发发送动作,尤其适配需要发送大尺寸数据(如批量传感器日志、长文本响应、二进制数据流)的场景,能有效避免因未确认发送完成就修改缓冲区、关闭串口或启动下一次发送导致的数据丢失 / 错乱问题。
它与接收空闲中断(IRQ_RXIDLE)形成对应:
IRQ_RXIDLE聚焦 “完整数据帧接收完成”;IRQ_TXIDLE聚焦 “完整数据帧发送完成”。
在下面代码中,我们利用 IRQ_TXIDLE 中断标记长日志发送接近完成,结合批量缓存机制实现模拟传感器日志的可靠串口发送。
完整代码如下:
# 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)
在上面的代码中,我们进行了如下工作:
- 程序启动与初始化;
- 程序进入无限循环,以 2 秒为周期重复执行以下操作:
- 日志生成:仅当发送状态(tx_status)为 IDLE(空闲)时,调用
generate_sensor_log生成固定格式的模拟温压传感器日志(含日志编号、时间戳、温度、气压),将日志添加到缓存列表sensor_logs并打印生成信息; - 批量发送:调用
send_log_batch函数,若缓存日志 ≥3 条且处于 IDLE 状态,执行批量发送逻辑:- 切换 tx_status 为 SENDING,重置 TXIDLE 中断标志
txidle_flag; - 拼接缓存中的 3 条日志为整段长文本(带 “LOG BATCH END” 结束标识),通过 UART0 一次性写入发送(保证硬件原子操作,避免数据截断);
- 调用
uart.flush()阻塞等待,直到所有数据发送完成(期间每 5ms 检测一次,替代不可靠的uart.txdone()); - 若 TXIDLE 中断触发(
txidle_flag为 True),打印 “发送接近完成” 提示并重置标志; - 清空日志缓存,将 tx_status 恢复为 IDLE;
- 切换 tx_status 为 SENDING,重置 TXIDLE 中断标志
- 状态指示:根据 tx_status 控制 LED(SENDING 点亮、IDLE 熄灭),打印当前发送状态;
- 延时 2 秒:降低循环频率,减少发送冲突,适配硬件缓冲区特性。
- 日志生成:仅当发送状态(tx_status)为 IDLE(空闲)时,调用
TXIDLE中断响应:当 UART0 发送缓冲区剩余 5 个字符待发送(RP2 硬件特性)触发 IRQ_TXIDLE 中断时,执行uart_txidle_handler函数:仅在 tx_status 为 SENDING 时,将txidle_flag标记为 True(仅标记状态,不执行写入操作,避免硬件抢占导致数据错乱),供主循环判定发送接近完成的状态。
完整流程图如下:

打开 XCOM 软件,选择 CH340K 芯片对应 COM 口:

串口通信参数和程序中一致:

其余发送和接收参数默认即可:


点击打开串口:

烧录程序,打开终端,显示如下:

我们可以看到,程序运行效果完全符合预期:
- 主循环周期性生成固定格式的模拟传感器日志(左侧终端持续输出 “Generated” 对应的日志信息),当日志缓存累积到 3 条时自动进入发送状态(终端显示 “Sending 3 logs”);
- 发送过程中触发 IRQ_TXIDLE 中断,终端同步打印 “Transmission nearly complete” 的回调提示( “串口发送中断触发回调函数”),同时右侧串口助手完整接收到带 “LOG BATCH END” 标识的批量日志;
- 发送完成后状态恢复为 IDLE。
日志生成 - 缓存 - 发送流程循环有序,既验证了 TXIDLE 中断对长数据发送状态的标记功能,也保障了批量日志的可靠串口传输。
在 RP2 平台使用 MicroPython 的 UART 发送空闲中断(IRQ_TXIDLE)时,核心注意事项是需严格控制单次批量发送的总字符数,使其适配 RP2 UART 硬件发送缓冲区(FIFO)的有限容量:
- 若总字符数远超该容量,数据会积压在软件缓冲区,而 RP2 的 TXIDLE 中断仅在剩余 5 个字符待发送时触发,若搭配不可靠的
uart.txdone()(仅判断单字符发送寄存器为空,不检测 FIFO)易误判发送完成,提前修改发送状态、清空缓存,干扰硬件最后的发送流程导致数据截断; - 因此需将单次发送字符量控制在 FIFO 安全承载区间,优先使用
uart.flush()(阻塞至所有数据含 FIFO 内的完全发送)替代uart.txdone(),同时关闭中断的hard=True改用软中断 ,避免硬件中断抢占加剧数据错乱,确保中断触发与发送状态管理的同步性。

在下面实验中,我们需要将通过 `MiniUSB` 数据线将 **`风雅一号板-通用兼容扩展板`** 上 `UART0` 的 `USB` 接口与电脑上的 `USB` 接口连接:
浙公网安备 33010602011771号