使用回调的方法可用于轻松创建灵活且可扩展的中断服务程序。开发人员可以使用多种方法以这种方式使用回调。 可以是以动态的形式分配回调,也可以以静态的形式分配回调,静态分配的回调的好处是不能在运行时进行更改,但动态分配对于在执行期间可能需要更改中断行为的应用程序非常有用。
假设我们有一个 UART 或 USART,可以在多个应用程序中重复使用。我们为它们设计一个硬件抽象层,这样的话我们可以将驱动程序代码与应用程序代码解耦,如下所示:
void Uart_Hal_Init(UartConfig_t const * const Config); void Uart_Hal_BaudRateSet(UartChannel_t const Channel, UartConfig_t const * const Config); uint8_t Uart_Hal_CharGet(UartChannel_t const Channel); void Uart_Hal_CharPut(UartChannel_t const Channel, char const Ch); uint8_t Uart_Hal_IsDataPresent(UartChannel_t const Channel); void Uart_Hal_RegisterWrite(uint32_t const Address, uint32_t const Value); uint32_t Uart_Hal_RegisterRead(uint32_t const Address); void Uart_Hal_CallbackRegister(UartCallback_t const Function, void (*CallbackFunction)(void));
注意,这些接口具有可用于注册回调函数的方法。这种方式允许开发人员获取回调函数并将其分配给他们需要的中断,以便将其分配给这样的UART接收或传输中断。
在串口驱动程序中,我们在写代码的时候可能定义了几个不同的中断。例如,一个中断处理程序可能是:
void Uart1_ISR(void) { HAL_UART_Transmit(&huart2, (uint8_t *)aRxBuffer, 1, 0xFFFF); CBUF_Push(RxDataBuffer, aRxBuffer[0]); HAL_UART_Receive_IT(&huart2, (uint8_t *)aRxBuffer, 1); }
这段代码虽然是特定于应用程序的,但是我们希望的是它在中断服务函数触发的时候就开始工作,相反,我们可以如下设置我们的中断处理函数:
void Uart1_ISR(void) { if(NULL != UART1_ISR->function) (*UART1_ISR->function)(); }
这里的用法是我们将使用一个函数指针来指定当中断触发时应该执行哪个函数。如果我们还没有分配中断,也就是函数指针被赋值为NULL。如果分配了函数指针,就会执行这个函数。分配给函数指针的函数在运行时使用以下HAL
函数设置:
void Uart_Hal_CallbackRegister(UartCallback_t const Function, void (*CallbackFunction)(void));
我们可以使用以下这个例子为我们的应用程序定义回调函数:
void UserIsrFunction(void) { HAL_UART_Transmit(&huart2, (uint8_t *)aRxBuffer, 1, 0xFFFF); CBUF_Push(RxDataBuffer, aRxBuffer[0]); HAL_UART_Receive_IT(&huart2, (uint8_t *)aRxBuffer, 1); }
系统初始化代码然后进行以下调用以将函数分配给在中断服务处理程序中执行的函数指针:
Uart_Hal_CallbackRegister(Uart1_ISR, UserIsrFunction);
而拥有一个可以被调用以更改中断执行的函数的API
可能看起来很危险,也可能是一个安全漏洞。具有API
分配的替代方法是在编译时使用配置结构体来初始化函数指针。你会注意到Uart_Init
函数具有以下形式:
void Uart_Init(UartConfig_t const * const Config);
配置结构体UartConfig_t
可用于分配执行的功能。这里的优势是多方面的,例如:
- 函数在编译时赋值
- 分配是通过一个const表进行的
- 可以进行函数指针分配,使其驻留在 ROM 与 RAM 中,这将使其在运行时不可更改
当然有几种不同的方法可以做到这一点,但我们的想法是使驱动程序代码保持不变,甚至可以作为预编译库提供。然后应用程序代码仍然可以轻松更改中断行为,而无需查看实现细节。