我们研究了如何编写阻塞的模数转换器(ADC)驱动程序和使用轮询技术不阻塞应用程序流的驱动程序。轮询外围设备的驱动程序效率很低,如果系统可能处于低功耗状态,它们可能会浪费宝贵的时钟周期,而这些时钟周期本来会被使用或浪费能量。开发人员可以实施ADC驱动程序的一种有效方法是使用中断来通知应用程序转换周期已经完成。在本文中,我们将研究如何做到这一点。
更新ADC驱动器采样功能
可以编写几种不同的方式来编写嵌入式ADC驱动程序以使用中断。在本文中,我们将研究如何修改上一篇文章中介绍的无阻塞嵌入式ADC驱动器。就像我们在那篇文章中所做的那样,应用程序可以通过调用函数Adc_Sample进行调用以启动ADC转换。
这是一个很好的示例,为什么拥有一个好的硬件抽象层(HAL)可以派上用场。无论是阻止,不阻止,轮询还是中断,我都会调用完全相同的函数,并且行为会根据驱动程序的配置设置而发生简单的变化,或者它可能基于以下原因链接到不同版本的Adc_Sample函数中:应用需求。
非阻塞系统的Adc_Sample函数如下所示:
在我们的新版本中,可能会实现类似于以下内容的Adc_Sample函数:
哇!所有代码都发生了什么?我们的非阻塞驱动程序具有各种检查和对缓冲区的访问等。对于基于中断的驱动程序,我们要做的只是启动在初始化期间配置的基于外设的ADC通道转换。因此,例如,如果我们要对通道0、1和3进行采样,则这些通道将是初始化期间启用的通道。该驱动程序旨在一次采样所有指定的通道,而不仅仅是采样一两个。正如我已经提到的,有很多方法可以做到这一点并帮助阐明概念,我们正在使用最简单的解决方案。
在这一点上,如果我们调用Adc_StartConversion,我们期望ADC外设对通道进行采样,但是当中断触发时,此时将不会发生任何事情。我们需要填充ADC中断处理程序,但是在驱动程序中这样做是有问题的。相反,如果可以的话,我们想尝试抽象中断处理程序代码。
抽象中断
开发人员在编写驱动程序时经常遇到的一个问题是,在开发中断驱动的解决方案时,他们通常会将中断紧密地耦合到应用程序代码。最佳地,中断将驻留在驱动程序代码中,该代码位于驱动程序层中,而不是位于体系结构中最高层的应用程序代码中。将中断与应用程序代码紧密耦合可能会导致难以移植代码,甚至在某些情况下很难扩展代码。
开发人员可用于将中断保留在驱动程序层中并仍为应用程序自定义中断的一种解决方案是使用回调。回调函数是对可执行代码的引用,该可执行代码作为参数传递给其他代码,这些代码允许较低级别的软件层调用较高级别的层中定义的函数[1]。最简单的回调函数只是作为参数传递给另一个函数的函数指针。在大多数情况下,回调将包含三部分:
回调函数
回调注册
回调执行
下图显示了这三个部分在典型的回调实现中如何协同工作:
图:典型的回调体系结构
如果您回想起上一个博客,则ADC驱动程序HAL包含以下功能:
如果您仔细看一下,该函数旨在通过嵌入式ADC驱动程序注册应用程序代码中的回调函数。第一个参数指定将向其分配回调的中断,而第二个参数通过将函数指针传递给函数来分配要调用的函数。
然后,低层驱动程序此时会将功能指针分配给指定的中断。这非常灵活,因为开发人员可以轻松更新和更改中断执行的功能,而不必回头修改和重新编译嵌入式ADC驱动程序。这有助于将应用程序代码与驱动程序代码分开,从而创建可扩展且灵活的解决方案。
有了这些知识,我们就可以实现ADC中断处理程序,使其类似于以下内容:
该中断仅是取消引用通过Adc_CallbackRegister()函数分配的指针。(请注意,在生产代码中,我还将添加一些检查以确保已分配了函数指针,但我想您知道了)。
编写中断处理程序
对于使用此方法的开发人员,中断处理程序将写在其应用程序层中,并且几乎可以具有他们喜欢的任何函数名。就我个人而言,我总是将其命名为Adc_InterruptCallback之类的名称,这样我就很容易知道它到底是什么。该回调的实现可能因应用程序而异。例如,在一个应用程序中,回调可能如下所示:
在此示例中,回调只是放置一个信号量以通知任务ADC数据可用。另一个示例可能如下所示:
如您所见,由开发人员决定他们要如何在中断处理程序中处理模拟数据,并且它会根据应用程序及其需求而有很大不同。
重要的是要注意,对于这些实际上是中断处理程序的回调函数,遵循中断处理程序最佳实践很重要。这意味着将代码最小化,并使它们尽可能快,以最大程度地减少对其余系统性能的影响。
结论
正如我们在本文中看到的,使用中断驱动的驱动器设计模式可以大大提高驱动器的效率。使用回调可以将中断实现保留在应用程序代码中,并通过驱动程序的回调机制分配给中断。这使解决方案和代码具有高度可重用性,灵活性和可扩展性。