MicroPython中断服务程序(ISR)编写规范与最佳实践
中断处理基础概念
在MicroPython中,中断服务程序(ISR)是响应硬件事件(如定时器触发或引脚电平变化)而执行的Python回调函数。这些事件可能发生在程序执行的任何时刻,因此编写ISR时需要特别注意一些MicroPython特有的限制以及实时编程的通用原则。
MicroPython特有的ISR限制
紧急异常缓冲区
在ISR中发生错误时,MicroPython需要专门的缓冲区来存储错误信息。建议在所有使用中断的程序中都包含以下代码:
import micropython
micropython.alloc_emergency_exception_buf(100)
注意该缓冲区只能保存一个异常的堆栈跟踪信息,如果处理异常时又发生第二个异常,原始异常信息会被覆盖。
保持代码简洁
ISR代码应尽可能简短,只执行必须立即完成的操作:
- 处理触发中断的硬件设备,使其准备好接收下一次中断
- 通过更新共享数据来通知主循环中断已发生
- 尽快返回控制权
与主程序通信
ISR通常需要通过共享数据对象与主程序通信,推荐使用:
- 整数、bytes和bytearray对象
- array模块中的数组(可存储多种数据类型)
- 全局变量或通过类共享的变量
使用对象方法作为回调
MicroPython支持将对象方法作为回调函数,这种方式非常强大:
class Foo(object):
def __init__(self, timer, led):
self.led = led
timer.callback(self.cb)
def cb(self, tim):
self.led.toggle()
这种方式可以实现:
- 单个类支持多个硬件实例
- 回调函数通过self访问实例数据
- 在多次调用间保持状态
内存分配限制
ISR中不能创建Python对象,因为:
- MicroPython需要从堆中分配内存
- 堆分配不可重入(中断可能发生在主程序正在分配内存时)
具体限制包括:
- 不能使用浮点运算(浮点数是Python对象)
- 不能向列表追加元素
- 不能插入字典项
解决方案是使用预分配缓冲区,例如在类构造函数中创建bytearray和标志位,ISR只修改这些预分配对象的内容。
使用micropython.schedule
这个函数允许ISR将回调排队执行,此时堆未被锁定:
- 可以创建Python对象和使用浮点数
- 保证在主程序完成Python对象更新后执行
典型用法是处理传感器数据:ISR从硬件获取数据并启用下一次中断,然后调度回调处理数据。
实时编程通用原则
中断处理程序设计要点
-
执行时间:ISR应能在短且可预测的时间内完成
- 避免循环结构
- 最小化I/O操作(除中断设备外的设备访问)
- 不要等待事件
-
中断优先级:
- 高优先级中断可以中断正在执行的ISR
- 低优先级中断会被延迟直到当前ISR完成
-
共享数据问题:
- 主程序和ISR共享数据时需考虑一致性问题
- 简单整数或字节数组通常安全
- 复杂数据结构(如字典)可能引发问题
临界区保护
当主程序访问与ISR共享的数据时,应考虑禁用中断:
import micropython
# 禁用中断
micropython.disable_irq()
# 访问共享数据
shared_data = ...
# 重新启用中断
micropython.enable_irq()
与asyncio交互
ISR不应直接执行asyncio操作,安全的方式是使用ThreadSafeFlag:
tsf = asyncio.ThreadSafeFlag()
def isr(_): # 中断处理程序
tsf.set()
async def foo():
while True:
await tsf.wait()
asyncio.create_task(bar())
最佳实践总结
- 保持ISR代码简短简单
- 避免内存分配(不使用列表追加、字典插入、浮点运算)
- 考虑使用micropython.schedule绕过限制
- 共享多字节数据时使用预分配bytearray
- 共享多个整数时考虑使用array.array
- 访问共享数据前禁用中断(临界区保护)
- 分配紧急异常缓冲区
- 避免在ISR中直接与asyncio交互
遵循这些原则可以编写出可靠、高效的中断处理程序,充分利用MicroPython的实时能力。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考