嵌入式MicroPython中如何写中断处理程序(一)
ztj100 2025-01-09 17:29 11 浏览 0 评论
MicroPython提供了在适当的硬件平台上用Python语言写中断处理函数的能力。中断处理函数,即中断服务例程(ISR),被定义为回调函数,其被作为类似于定时器触发或某引脚上电压改变等事件的响应函数而执行。这些事件在常规程序代码执行中的任何时间均可能发生,也就带来了一些值得注意的问题。其中,有些问题是特定于MicroPython而存在的,有些问题则对于所有实时系统是共性存在的。该篇文档首先关注MicroPython所特有的问题,接着针对新手简要介绍了对于实时系统编程时需要注意的问题。
该篇文档中使用了一些相对模糊的词语,比如"慢"或者"尽可能地快",这是有意为之,因为执行速度具体需依赖于实际应用程序设计而言。而ISR的可接受持续运行时间长短则依赖于中断发生频率,主程序本身和有无其它并发事件的产生而言。
建议及最佳实践
这里详细总结了写中断处理程序时所推荐遵守的原则如下:
MicroPython需要注意问题
1> 紧急异常缓冲区
如果在ISR中发生了错误,MicroPython并不能报告其错误信息,除非已经预先为其创建了特定的内存缓冲区。在带有中断处理的主程序中加入如下代码,可简化程序调试过程。
import micropython
micropython.alloc_emergency_exception_buf(100)
紧急异常缓冲区仅能保留一段异常追踪信息,这意味着在异常处理期间,堆空间被锁定时,如果有第二个异常被抛出,则其异常追踪信息会覆盖掉原来信息,即使第二个异常被干净地处理过了也会这样。这可能会导致在随后打印出缓冲区信息后,使得异常信息看似难以理解。
2> 简洁性
有许多原因可以说明保持ISR尽可能简洁的重要性。在某事件发生后的ISR中应该立即做且仅做那些必需要做的事情,而能够被延迟的操作应该交给主程序循环去做。典型场景下,ISR中会处理引发中断的硬件设备,使其准备就绪以处理下次中断,并更新某共享数据来指示中断已经发生,最后返回主程序。其以这种方式来和主程序完成通信。ISR应尽快返回主程序循环,该点也可以说并不特定于MicroPython,其会在下面章节进行更详细解说。
3> 在ISR和主程序之间通信
通常,在ISR和主程序之间确实需要进行适当通信。最简单的方式便是通过一个或者多个共享的数据对象,其可以为全局数据对象或者通过一个类进行共享。该方式有各种使用限制和需注意的危险,将在后面章节详细介绍。整数对象,字节串和字节数组对象常被用于该场景,还有来自于array模块的数组对象,其能够存储各种数据类型,也经常被使用。
4> 使用对象方法作为回调函数
MicroPython支持该项有力的技术特性,使得ISR能够使用底层代码共享实例变量,同时也使实现设备驱动的类能够同时支持多个设备实例。下面的示例代码使得两个LED以不同的频率闪烁。
import pyb, micropython
micropython.alloc_emergency_exception_buf(100)
class Foo(object):
def __init__(self, timer, led):
self.led = led
timer.callback(self.cb)
def cb(self, tim):
self.led.toggle()
red = Foo(pyb.Timer(4, freq=1), pyb.LED(1))
green = Foo(pyb.Timer(2, freq=0.8), pyb.LED(2))
该示例中,red实例将定时器4和LED1关联起来:定时器4中断发生时,red.cb()被调用,导致LED1改变状态。green实例以相似方式工作:定时器2的中断会导致green.cb()执行和LED2状态翻转。实例方法的使用有两个好处。第一,一个单独的类能够在多个硬件实例对象之间共享代码。第二,作为绑定方法,回调函数的第一个参数是self,这便使得回调函数能够访问实例数据并在连续调用之间保存状态。例如,如果上面的类在构造函数中将变量self.count置为零,cb()可以增加该计数器,然后,red和green实例将保持对各自LED状态翻转次数的独立计数。
5> 创建python对象
不能在ISR中创建python对象实例。这是因为对象实例的创建需要MicroPython从被称为堆的空闲空间中分配内存,但内存分配操作是不可重入的,所以这种操作在中断处理程序中是不被允许的。换句话说,当主程序正在执行内存分配时,可能产生中断,为了保持堆的完整性,python解释器不允许同时在ISR中进行内存分配。这样做的后果是,ISR中也不允许使用浮点运算,因为浮点数在python中均是数据对象。同样,ISR中也不能对列表追加元素。但实际使用过程中,往往很难准确地确定哪些代码执行了对象构造从而引起内存分配并最终引发错误消息:这也是倡导尽量保持ISR代码简短的另一个原因。
避免此问题的一种方法是在ISR中仅使用预分配的缓冲区。例如,在类构造函数中创建一个bytearray实例和一个布尔标志,然后ISR中仅将数据设置到缓冲区中适当位置,并置标志位。这样,当类对象被实例化时,内存分配就发生在主程序代码中,而不是在ISR中。
MicroPython库输入/输出函数通常提供使用预分配缓冲区的参数选项。例如,pyb.i2c.recv()可以接受一个可变缓冲区作为它的一个参数:这使得该函数可以在ISR中被使用。
不使用类或全局对象而创建新对象的另一种方法如下:
def set_volume(t, buf=bytearray(3)):
buf[0] = 0xa5
buf[1] = t >> 4
buf[2] = 0x5a
return buf
这样,在第一次加载函数时(通常是在导入其所在模块时),编译器会实例化默认的buf参数。
当创建对绑定方法的引用时,也会发生对象实例的创建,这意味着ISR中不能将绑定方法传递给函数。一种解决方案是在类的构造函数中创建对绑定方法的引用,并在ISR中传递该引用。比如:
class Foo():
def __init__(self):
self.bar_ref = self.bar # 在此分配内存
self.x = 0.1
tim = pyb.Timer(4)
tim.init(freq=2)
tim.callback(self.cb)
def bar(self, _):
self.x *= 1.2
print(self.x)
def cb(self, t):
# 直接传递self.bar会引起内存分配
micropython.schedule(self.bar_ref, 0)
其它的技术还包括在构造函数中定义和实例化方法等等。
6> python对象的使用
源于python本身的工作方式,其对数据对象的进一步限制也需要考虑在内。当执行import语句时,python代码被编译成字节码,并且一行代码通常可映射到多个字节码。当代码运行时,解释器读取每个字节码,并将其作为一系列机器码指令来执行。假设中断可以在机器码指令执行时的任何时间发生,python的原本代码行可能在中断发生时只被部分执行。因此,在主循环中被修改的python对象(如集合,列表或字典)可能会在中断发生时不能保证其内部一致性。
这样导致的典型后果如下:在极少情况下,ISR可能在某数据对象被部分修改过程中的某时刻开始运行,这样当ISR尝试读取数据对象时,可能导致崩溃发生。因为这种问题通常发生在罕见的随机情况下,所以很难被诊断出来。当然,也有一些方法可以避免该问题,将在下面“临界区”章节进行描述。
清楚地明白到底是什么原因造成了对数据对象的修改十分重要。对内置类型(如字典)数据的修改容易导致问题的发生,但对bytes或bytearray类型数据的修改则一般不会导致这种问题,这是因为字节或字类型数据在内部会被转化为单独的机器码,不可被中断执行:在实时编程的术语中,其被称为原子操作。这样,用户定义的类对象中便可以尽量实例化整数,数组或字节数组,其内容对于主循环和ISR来说均相对可靠有效。
MicroPython可支持任意精度的整数,2**30-1到-2**30之间的任何数值均可被存储在一个字存储空间中,但更大的数值则会以python对象形式被存储。因此,对长整数的修改不能被视作原子性的。在ISR中使用长整数是不安全的,因为当该变量值被修改时,可能会尝试内存分配动作。
7> 克服浮点数的限制
一般来说,最好避免在ISR中使用浮点数:硬件设备通常处理整数,然后在主循环中再进行浮点数转换。然而,有一些DSP算法确实需要进行浮点运算。在有硬件浮点支持的平台上(比如pyboard),其内嵌的ARM Thumb汇编器可以解决该限制。由于在这些平台上,处理器可将浮点值存储在一个字存储空间中,这样便可以通过浮点数数组在ISR和主程序代码之间共享数据。
8> 使用micropython.schedule
该函数使得ISR能够“很快”安排回调函数,使其得以执行。通过该调用该函数,回调函数会排队等待,在堆空间未被锁定的时候再开始执行,因此,其便又能够创建python对象并使用浮点值了。并且,这也会保证回调函数会在主程序完成对python对象的任何修改之后再开始运行,那么,回调函数执行时也就不会遇到被部分修改的数据对象了。
其典型用途是处理传感器原始数据:ISR从传感器硬件获取原始数据,并重新使能中断,之后使用该函数来安排回调函数具体处理数据。
使用micropython.schedule函数安排的回调函数在编写时应遵循下面章节所述通用中断处理程序设计原则。因为这些原则能够避免由于输入/输出活动和修改共享数据所引起的问题,这些问题可能出现在任何先于主程序循环的代码中。
ISR持续时间需要综合考虑中断发生频率来进行调整。如果在前一个回调执行过程中又发生了中断,则另一个回调函数实例将排队等待执行,其将在当前回调函数执行完毕之后再开始运行。因此,持续高频率的中断会带来队列无限增长的风险,最终导致产生RuntimeError异常。
如果要传递给micropython.schedule()的回调是绑定方法,可参考"创建python对象"章节中所述建议。
注:为避免太长,分两篇介绍,关注获取吧...
相关推荐
- Whoosh,纯python编写轻量级搜索工具
-
引言在许多应用程序中,搜索功能是至关重要的。Whoosh是一个纯Python编写的轻量级搜索引擎库,可以帮助我们快速构建搜索功能。无论是在网站、博客还是本地应用程序中,Whoosh都能提供高效的全文搜...
- 如何用Python实现二分搜索算法(python二分法查找代码)
-
如何用Python实现二分搜索算法二分搜索(BinarySearch)是一种高效的查找算法,适用于在有序数组中快速定位目标值。其核心思想是通过不断缩小搜索范围,每次将问题规模减半,时间复杂度为(O...
- 路径扫描 -- dirsearch(路径查找器怎么使用)
-
外表干净是尊重别人,内心干净是尊重自己,干净,在今天这个时代,应该是一种极高的赞美和珍贵。。。----网易云热评一、软件介绍Dirsearch是一种命令行工具,可以强制获取web服务器中的目录和文件...
- 78行Python代码帮你复现微信撤回消息!
-
来源:悟空智能科技本文约700字,建议阅读5分钟。本文基于python的微信开源库itchat,教你如何收集私聊撤回的信息。...
- 从零开始学习 Python!2《进阶知识》 Python进阶之路
-
欢迎来到Python学习的进阶篇章!如果你说已经掌握了基础语法,那么这篇就是你开启高手之路的大门。我们将一起探讨面向对象编程...
- 白帽黑客如何通过dirsearch脚本工具扫描和收集网站敏感文件
-
一、背景介绍...
- Python之txt数据预定替换word预定义定位标记生成word报告(四)
-
续接Python之txt数据预定替换word预定义定位标记生成word报告(一)https://mp.toutiao.com/profile_v4/graphic/preview?pgc_id=748...
- Python——字符串和正则表达式中的反斜杠('\')问题详解
-
在本篇文章里小编给大家整理的是关于Python字符串和正则表达式中的反斜杠('\')问题以及相关知识点,有需要的朋友们可以学习下。在Python普通字符串中在Python中,我们用'\'来转义某些普通...
- Python re模块:正则表达式综合指南
-
Python...
- python之re模块(python re模块sub)
-
re模块一.re模块的介绍1.什么是正则表达式"定义:正则表达式是一种对字符和特殊字符操作的一种逻辑公式,从特定的字符中,用正则表达字符来过滤的逻辑。(也是一种文本模式;)2、正则表达式可以帮助我们...
- MySQL、PostgreSQL、SQL Server 数据库导入导出实操全解
-
在数字化时代,数据是关键资产,数据库的导入导出操作则是连接数据与应用场景的桥梁。以下是常见数据库导入导出的实用方法及代码,包含更多细节和特殊情况处理,助你应对各种实际场景。一、MySQL数据库...
- Zabbix监控系统系列之六:监控 mysql
-
zabbix监控mysql1、监控规划在创建监控项之前要尽量考虑清楚要监控什么,怎么监控,监控数据如何存储,监控数据如何展现,如何处理报警等。要进行监控的系统规划需要对Zabbix很了解,这里只是...
- mysql系列之一文详解Navicat工具的使用(二)
-
本章内容是系列内容的第二部分,主要介绍Navicat工具的使用。若查看第一部分请见:...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- Whoosh,纯python编写轻量级搜索工具
- 如何用Python实现二分搜索算法(python二分法查找代码)
- 路径扫描 -- dirsearch(路径查找器怎么使用)
- 78行Python代码帮你复现微信撤回消息!
- 从零开始学习 Python!2《进阶知识》 Python进阶之路
- 白帽黑客如何通过dirsearch脚本工具扫描和收集网站敏感文件
- Python之txt数据预定替换word预定义定位标记生成word报告(四)
- 假期苦短,我用Python!这有个自动回复拜年信息的小程序
- Python——字符串和正则表达式中的反斜杠('\')问题详解
- Python re模块:正则表达式综合指南
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- node卸载 (33)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- exceptionininitializererror (33)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)