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. 灵活性高:可以根据不同的场景动态切换回调函数,实现不同的业务逻辑。

五、注意事项

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

六、总结

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

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

相关推荐
David猪大卫21 小时前
【Linux修炼】环境变量、进程地址空间
linux·c语言·经验分享·笔记·学习·ubuntu·centos
xiaoye-duck1 天前
《C++ 项目:手写大模型接入 SDK》Gemini接口设计(上):初始化、全量响应实现和测试
开发语言·网络·c++·语言模型
nice_lcj5201 天前
Java Lambda 表达式入门:怎么写、怎么用、常用方法有哪些
java·开发语言·后端·面试
码农小河661 天前
基于SSM+JSP+MySQL的在线医疗服务系统的设计与实现
java·开发语言·mysql
Dev_XH1 天前
【成品设计】基于单片机的家用智能浇花系统的设计与实现
stm32·单片机·嵌入式硬件·物联网
意法半导体STM321 天前
【官方原创】LAT1717 STM32N6 如何使用单个XSPI驱动双片相同HyperRAM
stm32·单片机·嵌入式硬件·mcu·stm32n6·hyperram
木子单片机1 天前
基于单片机的直流电机调速系统设计
单片机·嵌入式硬件·51单片机·nosql·keil
Zwawa1 天前
两根线怎么挂住一堆设备:I2C 协议原理
c语言·单片机·嵌入式
nice_lcj5201 天前
ThreadLocal 入门:它是怎么隔离数据的、为什么会导致内存泄漏
java·开发语言·后端·面试·并发编程·内存泄漏·threadlocal
紧跟先前的步伐1 天前
Go AI Agent 实战 28|Agent 的预算、超时与停止条件
开发语言·人工智能·后端·ai·golang