本书的原著为:《Design Patterns for Embedded Systems in C ——An Embedded Software Engineering Toolkit 》,讲解的是嵌入式系统设计模式,是一本不可多得的好书。
本系列描述我对书中内容的理解。本文章描述访问硬件的设计模式之七:轮询模式。
从硬件获取传感器数据或信号时,另一种常见的模式是定期进行检查,这个过程被称为 轮询
(polling)。
轮询的基本思想是由软件主动去检查(即“询问”)硬件状态或数据是否已准备好,而不是等待硬件来通知软件(如通过 中断
、观察者模式
)。这种方法简单且易于实现,但效率可能较低,因为软件可能需要频繁地检查硬件,即使数据并没有更新。
摘要
轮询模式
(Polling Pattern)是检查硬件是否有新数据或信号的最简单方式。轮询可以是 周期性
(periodic) 的,也可以是 伺机性
(opportunistic) 的。周期性轮询使用定时器来指示何时应该对硬件进行采样,而伺机性轮询则在系统方便的时候进行,例如在主要系统函数之间或在重复执行周期的某个时间点。
伺机性轮询,顾名思义,不那么规律,但它对系统可能正在执行的其他活动的时间性影响较小。这意味着,如果系统在某些时候较为空闲,伺机性轮询可以作为一种低开销的方式来检查硬件状态,而不会显著干扰其他关键任务。
问题
轮询模式解决了在系统运行过程中获取新的传感器数据或硬件信号的问题,尤其适用于那些数据或事件并非高度紧急的情况。由于轮询操作可以在系统控制下进行,并且可以调整轮询的频率,因此它提供了一种灵活且可控的数据获取方式。
模式结构
该模式有两种类型:
-
伺机性轮询
-
周期性轮询
模式详情
应用层
应用层的 applicationFunc()
函数包含一个循环,在循环中调用 poll()
函数。例如:
while (processing)
{ action1(); action2(); action3(); OpportunityPoller_poll(me->itsOpportunisticPoller);
}
设备
该设备通过对外函数提供数据、设备状态等信息。这个元素可以是一个设备驱动程序,也可以简单地从内存或端口映射设备中读取信息。这个类提供了两个函数,一个用于检索数据(在图中定义为 deviceData 类型数据),另一个用于检索设备状态(图中定义为 int 类型整数)。
在图中,有 MAX_POLL_DEVICES
个设备关联到 伺机性轮询
上,因此 poll()
函数会扫描所有这些设备并通知它们各自的 客户端
。
伺机性轮询器
此元素具有 poll()
函数,该函数扫描所关联的设备以获取数据和设备状态,并将此信息传递给每个设备的相应客户端。伺机性轮询器
和 周期性轮询器
之间的区别在于,后者除了轮询数据外,还具有计时器初始化和关闭功能。
周期性轮询器
周期性轮询器
是一种软件组件,它定期扫描所关联的设备以获取数据和设备状态。与 伺机性轮询器
不同,周期性轮询器按照预定的时间间隔执行轮询操作。
主要功能和组件
-
poll() 函数:这是周期性轮询器的核心函数,负责扫描所关联的设备并获取数据和设备状态。一旦获取到信息,它会将这些数据传递给每个设备的相应客户端。
-
设置轮询时间的变量:周期性轮询器允许用户通过
setPollTime(t)
函数设置轮询的时间间隔。这个变量决定了轮询器多久执行一次poll()
函数。 -
启动和停止轮询的服务:周期性轮询器提供
startPolling()
和stopPolling()
函数来控制轮询操作的开始和停止。startPolling()
函数负责初始化定时器。stopPolling()
函数用于停止定时器,从而停止轮询操作。
轮询数据客户端
这是一个客户端,客户端使用 设备
的数据、状态等信息。通常每个设备都有自己的客户端,但也存在所有设备的数据只有一个客户端的情况。
轮询定时器
轮询定时器
元素表示一个定时器以及与该定时器相关的服务。startTimer()
方法负责初始化定时器,并将其与handleTimerInterrupt()
中断服务例程关联起来。当中断发生时,handleTimerInterrupt()
会被执行,进而触发轮询操作。handleTimerInterrupt()
的工作流程通常如下:
- 重置定时器计数:这是为了确保下一次定时器中断能够在正确的时间发生。
- 调用
poll()
函数:这是轮询操作的核心部分,它会扫描并读取设备的数据和状态信息。
轮询定时器还提供中断服务例程(ISR)的安装和移除:
installInterruptTimer()
函数负责将中断服务例程(ISR)的地址插入到中断向量表中。这是确保定时器中断能够正确触发并调用相应处理函数的关键步骤。removeInterruptHandler()
函数用于从中断向量表中移除之前安装的中断服务例程,恢复原始的中断向量设置。然而,请注意,在您的描述中并没有直接提到这个函数被周期性轮询器使用;它可能是作为更底层或更一般化的服务存在的。
效果
轮询的优点在于其简单性和能够同时检查多个设备的能力。它不需要复杂的中断设置和管理,因此更容易实现和维护。轮询的主要缺点是它可能无法及时处理快速变化的事件或数据,因为它只在轮询周期到达时才检查设备状态。
因此,必须注意,如果数据与信号有相关的截止时间(deadlines),那么轮询时间加上响应时间必须始终小于这些截止时间。如果数据到达的速度快于轮询时间,那么数据将会丢失。这在许多应用中可能不是问题,但在某些情况下可能是致命的。
相比之下,中断处理可以立即响应设备状态的变化,因为它是由设备本身触发的。但是,中断处理需要更复杂的设置和管理,包括中断向量的配置、中断服务例程的编写和中断优先级的设置等。此外,如果多个设备同时触发中断,还需要处理中断嵌套和优先级的问题。
因此,在选择使用轮询还是中断处理时,需要根据具体的应用场景和需求进行权衡。
实现策略
最简单的实现方式是在主循环中进行硬件检查,只要系统运行,该循环就会一直执行。这被称为 对称伺机性轮询
,因为它始终以相同的方式运行,即使处理循环所需的时间可能会有很大的变化。非对称伺机性轮询
指的是在处理过程中,在方便但无关的点上进行新数据检查。这种方法提供了更大的灵活,(如果需要更快的响应,可以增加一些检查),但增加了系统的复杂性和维护难度,因为需要在多个不相关的点上插入检查代码,并且需要确保这些检查不会干扰主要流程的执行。
周期性轮询
是以固定时间间隔(称为周期)进行的轮询。时间间隔不是严格不变的,而是有较小的抖动。实际轮询时间与预期轮询时间之间的差异很小。为了实现这种规律性,新数据的检查是由与中断绑定的定时器启动的。因此,轮询模式的周期性变体只是中断模式的一个特例。
实例
见原书。
读后有收获,资助博主养娃 - 千金难买知识,但可以买好多奶粉 (〃‘▽’〃)