1、前言
在熟悉 嵌入式软件开发之程序架构(一) 、嵌入式软件开发之程序分层(二)和嵌入式软件开发之模块化程序设计(三) 三篇关于软件架构、分层和模块设计后,实际开发时会遇到一个问题,部分底层模块需要调用上层应用的函数实现功能,这样就会打乱程序分层中设置的规则(上层允许调用下层接口,但是下层禁止调用上层接口,这是分层的宗旨)。
常见的情况如定时器中断、串口接收中断和按键触发立即响应等,如部分需求场景需要使用精准的定时策略,而即使采用 嵌入式软件开发之程序架构(一)提到的“时间片论法”或者操作系统,也不能保证 100% 定时精准地执行,因此通常做法都是将需要精准执行的函数放在定时器中断里面进行处理。
由于定时器模块属于硬件抽象层,而需要执行的函数可能是功能模块层或者应用层,如果定时器调用上层函数(需要包含上层模块头文件),这样就违背了程序分层的宗旨。
2、解决思路
上述情况,可以采用回调函数的实现方式避免该状况。
回调函数是一个被作为参数传递的函数。在C语言中,回调函数只能使用函数指针实现(不清楚回调函数的可以自行百度)。
针对上面说到的定时器问题,定义一个函数指针,由定时器模块提供一个参数为函数指针的接口函数,上层模块通过该函数将需要被调用的函数地址通过函数指针的方式给到定时器模块,定时器模块则根据设置的条件不停地执行回调函数,也就是上层模块的函数,这样就避免了下层模块直接调用上层模块的函数。
分层这样做的好处在同一硬件平台只移植该驱动模块代码时,基本无需修改即可使用。也许有人感觉这样麻烦,但是良好的程序架构是清晰可见的,难免需要麻烦(优先的程序架构一般前期设计开发时间长,后期维护时间短)。
从运行效率看,通过回调函数调用和直接调用是一样的,因为函数指针指向的是函数地址,函数名本身也是一个地址。
看图理解:
Callback 是一个函数指针类型的变量,通过函数 FML_TIME_Attach 拿到了应用层代码函数 OnFunction(...) 的函数地址,之后在定时器中断函数中根据触发条件调用 Callback 即可,调用方式和直接调用 OnFunction(...) 没有太大差异,只不过名字不一样(可以理解成取了一个别名),为了保证系统运行安全,调用前要确保 Callback 不为 NULL,否则会引起程序异常。
3、示例代码
定时器头文件:
#ifndef __TIMER_H
#define __TIMER_H
#include <stdint.h>
// 定义函数指针
typedef void (*TimeFunCB)(void);
extern void FML_TIME_Init(void);// 定时器初始化
extern int FML_TIME_Attach(TimeFunCB pfnCallback, uint32_t time);// 注册回调函数
extern int FML_TIME_Detach(TimeFunCB pfnCallback); // 注销回调函数
#endif
定时器源文件实现具体功能:
#include "timer.h"
/**
* @brief 定义回调函数需要执行的条件信息
*/
typedef struct{
TimeFunCB pfnCallback; /*!< 回调函数 */
uint32_t u1msTic; /*!< 1ms定时计时 */
uint32_t u1msMaxTime; /*!< 回调周期时间 */
} TimeFun_t;
/**
* @brief 可注册的定时回调函数最大数目
*/
#define SUPPORT_FUN_MAX_NUM 10
/*!< 注册的定时回调函数 */
static TimeFun_t sg_tCallFun[SUPPORT_FUN_MAX_NUM];
// 定时器初始化
void FML_TIME_Init(void)
{
// 完成定时器1ms的初始化
}
// 注册回调函数
int FML_TIME_Attach(TimeFunCB pfnCallback, uint32_t time)
{
int i;
for (i = 0; i < SUPPORT_FUN_MAX_NUM; i++)
{
if (sg_tCallFun[i].pfnCallback == 0)
{
sg_tCallFun[i].pfnCallback = pfnCallback;
sg_tCallFun[i].u1msTic = 0;
sg_tCallFun[i].u1msMaxTime = time;
return 0;
}
}
return -1;
}
// 注销回调函数
int FML_TIME_Detach(TimeFunCB pfnCallback)
{
int i;
for (i = 0; i < SUPPORT_FUN_MAX_NUM; i++)
{
if (sg_tCallFun[i].pfnCallback == pfnCallback)
{
sg_tCallFun[i].pfnCallback = 0;
return 0;
}
}
return -1;
}
// 定时器中断处理函数.
void Time_ISRHandle(void)
{
int i;
// 清除中断标志位
for (i = 0; i < SUPPORT_FUN_MAX_NUM; i++)
{
if (sg_tCallFun[i].pfnCallback == 0)
{
sg_tCallFun[i].u1msTic++;
if (sg_tCallFun[i].u1msTic > sg_tCallFun[i].u1msMaxTime)
{
sg_tCallFun[i].u1msTic = 0;
sg_tCallFun[i].pfnCallback();
}
}
}
}
Demo 程序:
#include "timer.h"
void TestFun(void)
{
printf("1S 定时调用\r\n");
}
void main(void)
{
FML_TIME_Init();
FML_TIME_Attach(TestFun, 1000);
while (1);
}