回调函数是开发人员创建驱动程序或自定义库所需的一个基本且通常很关键的概念。回调函数是对可执行代码的引用,它作为参数传递给其他代码,允许较低级别的软件层调用在较高级别层中定义的函数 (10)。回调允许驱动程序或库嵌入式开发人员在较低层指定行为,但将实现定义留给应用程序层。
最简单的回调函数只是一个函数指针,它作为参数传递给另一个函数。在大多数情况下,回调将包含三个部分:
• 回调函数
• 回调注册
• 回调执行
下图显示了这三个部分如何在典型的回调实现中协同工作。
首先,开发人员创建将具有由应用程序开发人员确定的实现元素的库或模块。例如,开发人员创建了一个 GPIO 驱动程序,该驱动程序具有一个中断服务例程,其代码由应用程序开发人员指定。中断可以处理按钮按下或其他一些功能。驱动程序不关心功能,只关心在运行时它知道在中断触发时应该调用什么函数。在模块中调用回调函数的代码通常称为信号处理程序。
接下来,需要有一些方法来告诉底层代码应该执行什么函数。有很多方法可以做到这一点,但对于驱动程序模块,推荐的做法是在模块内创建一个专门用于将函数注册为回调的函数。拥有一个单独的函数来注册回调函数使嵌入式开发人员非常清楚回调函数正在注册到特定的信号处理程序。当调用寄存器函数时,将被调用的所需函数作为参数传递给模块并存储函数地址。
最后,应用程序开发人员编写他们的应用程序,其中包括创建回调和初始化代码的实现,该代码将该函数注册到库或模块中。执行应用程序时,低级代码存储回调函数地址,当需要执行功能时,它会取消引用回调函数并执行它。
开发人员可以考虑使用回调的两个主要示例。首先,在驱动程序中,开发人员将不知道最终应用程序可能需要如何使用任何中断服务程序。如果开发人员正在为某些微控制器外设创建库,则可以使用回调来指定所有中断行为。使用回调将允许开发人员确保在应用程序开发人员没有注册自定义回调函数的情况下,每个中断都有一个默认的服务程序。当回调与中断一起使用时,开发人员需要记住,需要遵循中断的最佳实践。
其次,只要应用程序中存在可能具有特定于实现行为的常见行为,就可以使用回调。例如,初始化数组是一项非常常见的任务,需要在应用程序中执行。如果对于某些应用程序,嵌入式开发人员希望将数组元素初始化为全零,而在另一个应用程序中他们希望将数组元素初始化为随机数怎么办?在这种情况下,他们可以使用回调来初始化数组。
ArrayInit 函数接受一个指向具有元素大小的数组的指针,然后它还接受一个指向返回整数的函数的指针。此时的功能尚未定义,但可以由应用程序代码定义。当调用 ArrayInit 时,开发人员会传递他们选择的任何函数来初始化数组元素。
带回调的函数:
void ArrayInit(int * Array, size_t size, int (*Function)(void))
{
for(size_t i = 0; i < size; i )
{
Array[i] = Function();
}
}
将元素初始化为0:
int Zeros(void)
{
return 0;
}
将元素初始化为随机数:
int Random(void)
{
return rand();
}
函数Zeros或Random被传递给ArrayInit,这取决于应用程序嵌入式开发人员希望如何初始化数组。
,