64.单片机回调函数实战:从原理到模块化工程落地

在嵌入式开发中,回调函数是实现模块化、解耦代码的核心技术之一。它通过函数指针的方式,将 "业务逻辑" 与 "底层处理" 分离,让代码更易维护、扩展。本文将结合视频中的核心知识点,通过完整代码示例,带你从原理到实战掌握回调函数在单片机开发中的应用。

一、回调函数的核心本质

回调函数的本质是将函数地址作为参数传递给另一个函数,当满足特定条件时,该函数会被自动调用。这种方式可以避免大量的条件判断语句,让代码结构更清晰,实现 "定义匹配格式的函数,满足条件自动执行" 的效果。

二、回调函数的实现步骤

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);
    }
}

四、回调函数的核心优势

  1. 功能解耦:底层处理代码与业务逻辑分离,新增功能时无需修改底层代码,只需新增回调函数即可。
  2. 工程维护轻松:代码结构清晰,每个功能对应一个回调函数,便于调试和维护。
  3. 灵活性高:可以根据不同的场景动态切换回调函数,实现不同的业务逻辑。

五、注意事项

回调函数的参数和返回值必须与定义的函数指针类型完全一致,否则会导致编译错误。

六、总结

回调函数是单片机开发中实现模块化的重要技术,通过函数指针的方式,将业务逻辑与底层处理分离,让代码更易维护、扩展。本文通过完整的代码示例,介绍了回调函数的实现步骤和在单片机开发中的四大常用场景,希望能帮助你掌握这一核心技术。

关注我,持续更新技术文章

相关推荐
秋名RG6 分钟前
Java 核心特性一览
java·开发语言
平头哥AI33 分钟前
Day 01 | go run 跑通第一个 Go 程序,go build 留下一个能拷走的 exe
开发语言·后端·golang
糖糖单片机设计1 小时前
STM32 老人跌倒监测报警系统
stm32·单片机·嵌入式硬件
葡萄城技术团队1 小时前
工业数据可视化:使用活字格 AIcoding + Three.js 构建轻量化三维设备监控大屏
开发语言·javascript·信息可视化
qiaosaifei1 小时前
JAVA老项目中有大量的类名_编译时报找不到符号错误
java·开发语言
聪明蛋子哟1 小时前
MCP协议深度落地:如何用Python/Java双语言实现统一的工具调用网关?
java·开发语言·python
是隼人1 小时前
buuctf-pwn [HarekazeCTF2019]baby_rop2(64位ret2libc)题解(学习过程持续更新)
c语言·学习·安全·pwn入门·ctf入门
一晌小贪欢2 小时前
python-第27天:Python面向对象详解
开发语言·python·数据可视化·面向对象·python办公
智闲电子设计2 小时前
STM32 硬件 CRC 与 UID 实战:固件校验防变砖,UID 防抄板,这两个外设白用太可惜
stm32·单片机·嵌入式硬件
嵌入式阿蔡2 小时前
CoAP / LwM2M 轻量协议:受限设备为什么不用 MQTT
单片机·物联网·嵌入式