在嵌入式开发中,回调函数是实现模块化、解耦代码的核心技术之一。它通过函数指针的方式,将 "业务逻辑" 与 "底层处理" 分离,让代码更易维护、扩展。本文将结合视频中的核心知识点,通过完整代码示例,带你从原理到实战掌握回调函数在单片机开发中的应用。
一、回调函数的核心本质
回调函数的本质是将函数地址作为参数传递给另一个函数,当满足特定条件时,该函数会被自动调用。这种方式可以避免大量的条件判断语句,让代码结构更清晰,实现 "定义匹配格式的函数,满足条件自动执行" 的效果。
二、回调函数的实现步骤
1. 定义回调函数指针类型
首先需要定义一个回调函数的指针类型,用于规定回调函数的参数和返回值格式。
// 定义回调函数指针类型:参数为int,返回值为void
typedef void (*CallBackFunc)(int val);
2. 编写通用处理函数
通用处理函数接收回调函数指针作为参数,在合适的时机调用回调函数。
// 通用设备处理函数:接收检测数据和回调函数
void DeviceHandle(int data, CallBackFunc cb)
{
printf("设备检测数值:%d,触发回调\n", data);
// 调用传入的回调函数
cb(data);
}
3. 编写自定义回调函数
根据业务需求编写不同的回调函数,这些函数必须与定义的回调函数指针类型格式一致。
// 自定义回调1:报警处理
void AlarmCb(int val)
{
printf("警告:数值%d超出阈值!\n", val);
}
// 自定义回调2:正常处理
void NormalCb(int val)
{
printf("运行正常,当前数值:%d\n", val);
}
4. 调用通用处理函数
在主函数中,将不同的回调函数地址传递给通用处理函数,实现不同的业务逻辑。
int main(void)
{
// 传递报警回调
DeviceHandle(85, AlarmCb);
// 传递正常回调
DeviceHandle(25, NormalCb);
return 0;
}
三、单片机开发中的四大常用场景
回调函数在单片机开发中应用广泛,以下是四个典型场景:
1. 外部中断回调:不同按键按下执行对应动作
在单片机外部中断中,不同按键按下时可以触发不同的回调函数,实现按键功能的模块化。
// 定义按键回调函数指针类型
typedef void (*KeyCallBack)(void);
// 按键1回调
void Key1Handler(void)
{
printf("按键1按下,执行功能1\n");
}
// 按键2回调
void Key2Handler(void)
{
printf("按键2按下,执行功能2\n");
}
// 外部中断处理函数
void EXTI_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0) != RESET)
{
// 按键1按下,调用对应回调
Key1Handler();
EXTI_ClearITPendingBit(EXTI_Line0);
}
if(EXTI_GetITStatus(EXTI_Line1) != RESET)
{
// 按键2按下,调用对应回调
Key2Handler();
EXTI_ClearITPendingBit(EXTI_Line1);
}
}
3. 多级菜单回调:切换页面时绑定不同显示函数
在 OLED/LCD 等显示设备的多级菜单中,切换页面时可以绑定不同的显示回调函数,实现菜单的模块化。
// 定义菜单回调函数指针类型
typedef void (*MenuCallBack)(void);
// 主菜单显示回调
void MainMenuDisplay(void)
{
printf("显示主菜单\n");
}
// 设置菜单显示回调
void SettingMenuDisplay(void)
{
printf("显示设置菜单\n");
}
// 菜单切换函数
void MenuSwitch(MenuCallBack cb)
{
// 清屏
printf("清屏\n");
// 调用对应菜单显示回调
cb();
}
// 主函数中切换菜单
int main(void)
{
// 显示主菜单
MenuSwitch(MainMenuDisplay);
// 切换到设置菜单
MenuSwitch(SettingMenuDisplay);
return 0;
}
4. 定时器回调:定时执行不同任务
在定时器中断中,不同的定时任务可以绑定不同的回调函数,实现定时任务的模块化。
// 定义定时器回调函数指针类型
typedef void (*TimerCallBack)(void);
// 定时任务1回调
void TimerTask1(void)
{
printf("定时任务1执行\n");
}
// 定时任务2回调
void TimerTask2(void)
{
printf("定时任务2执行\n");
}
// 定时器中断处理函数
void TIM_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
static uint32_t count = 0;
count++;
if(count % 1000 == 0)
{
// 1秒执行一次任务1
TimerTask1();
}
if(count % 2000 == 0)
{
// 2秒执行一次任务2
TimerTask2();
}
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
四、回调函数的核心优势
- 功能解耦:底层处理代码与业务逻辑分离,新增功能时无需修改底层代码,只需新增回调函数即可。
- 工程维护轻松:代码结构清晰,每个功能对应一个回调函数,便于调试和维护。
- 灵活性高:可以根据不同的场景动态切换回调函数,实现不同的业务逻辑。
五、注意事项
回调函数的参数和返回值必须与定义的函数指针类型完全一致,否则会导致编译错误。
六、总结
回调函数是单片机开发中实现模块化的重要技术,通过函数指针的方式,将业务逻辑与底层处理分离,让代码更易维护、扩展。本文通过完整的代码示例,介绍了回调函数的实现步骤和在单片机开发中的四大常用场景,希望能帮助你掌握这一核心技术。
关注我,持续更新技术文章