• STM32物联网项目-回调函数


    回调函数

    一般解释

    回调函数就是一个通过函数指针调用的函数。如果你把函数的地址传递给中间函数的形参,中间函数通过函数指针调用其所指向的函数时,我们就说这是回调函数。

    Fun1()			//应用层
    {
    	Fun2(Fun3);		//Fun2中间层,Fun3回调函数
    }
    
    • 1
    • 2
    • 3
    • 4

    通俗解释

    函数Fun1调用函数Fun2,同时将函数Fun3作为形参传递给Fun2,此时,Fun1为应用层函数,Fun2为中间层函数,Fun3为回调函数,回调函数是一种说法而已。当Fun2被调用时,Fun3也会被接着调用

    意义

    利于代码结构,将代码分为应用层,中间层,硬件驱动层,彼此独立,方便程序的编辑,阅读,修改与移植;

    结构化编程时,结构体只需要定义中间函数,减小内存的开销。

    伪代码1

    Fun1()
    {
        Fun2()
        {
        	Fun3_1();
        	Fun3_2();
            …………
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这种函数调用写法的缺陷:

    当Fun3有多个功能函数时,每次增加一个,都要修改Fun2,这样代码的耦合性就大

    通过函数指针的好处是,Fun2与Fun3实现隔离,比如Fun3具有多个功能函数,增加或减少时,不需要修改Fun2的代码。

    伪代码2

    LED.h

    定义带有三个函数指针的结构体类型

    //定义结构体类型
    typedef struct
    {
        LED_Num_t LED_Num;
        void (*LED_ON)(uint8_t );
        void (*LED_OFF)(uint8_t );
        void (*LED_Flip)(uint8_t );
    }LED_t;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    LED.c

    初始化结构体,每个函数指针都指向一个函数

    LED_t LED = 
    {
        LED1,
        LED_ON,
        LED_OFF,
        LED_Flip
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    main.c

    通过结构体调用函数

    int main()
    {
    	LED.LED_ON();
    	LED.LED_OFF();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这种函数调用写法的缺陷:

    1、如果功能函数增多,那结构体里面的函数指针也要增多,增加内存开销

    2、main函数或其他调用函数要修改,main函数想要调用增多的功能函数,也要增加调用的操作

    回调函数调用关系

    在这里插入图片描述

    回调函数代码使用示例

    原本LED.h头文件里的结构体

    //定义结构体类型
    typedef struct
    {
        void (*LED_ON)(uint8_t );
        void (*LED_OFF)(uint8_t );
        void (*LED_Flip)(uint8_t );
    }LED_t;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    修改后

    本来结构体里是三个分别指向 LED_ON、LED_OFF、LED_Flip的指针,现在改为只定义一个中间层的函数指针LED_Fun,该指针名字为LED_Fun,可指向一个函数,该函数无返回值,一个参数类型为uint8_t,另一个参数为一个函数指针(指向一个无返回值,参数类型为uint8_t的函数)

    本来 LED_ON、LED_OFF、LED_Flip函数是用 static 修饰为静态函数的,只能通过结构体变量进行调用,现在用回调函数后,通过函数指针调用,所以要在源文件中把这三个函数的 static 去掉,并在头文件中加 extern 声明为外部可调用

    //定义枚举类型
    typedef enum
    {
        LED1 = (uint8_t)0x01,
        LED2 = (uint8_t)0x02,
        LED3 = (uint8_t)0x03
    }LED_Num_t;
    
    //定义结构体类型
    typedef struct
    {
        void (*LED_Fun)(uint8_t,void (*CallBack)(uint8_t));		//中间层函数
    }LED_t;
    
    /* extern variables-----------------------------------------------------------*/
    extern LED_t LED;
    extern void LED_ON(uint8_t );		//函数声明为外部可调用
    extern void LED_OFF(uint8_t );
    extern void LED_Flip(uint8_t );
    /* extern function prototypes-------------------------------------------------*/ 
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    LED.c

    源文件里实现中间函数LED_Fun(),直接调用CallBack指针指向的函数;在结构体里初始化LED_Fun函数

    /* Private variables----------------------------------------------------------*/
    static void LED_Fun(uint8_t ,void (*CallBack)(uint8_t));
    /* Public variables-----------------------------------------------------------*/
    LED_t LED = 
    {
        LED_Fun
    };
    /* Private function prototypes------------------------------------------------*/
    
    /*
    * @name   LED_Fun
    * @brief  LED功能函数,中间虚拟函数(回调函数)
    * @param  LED_NUM:LED灯编号,CallBack:回调函数指针
    * @retval None   
    */
    static void LED_Fun(uint8_t LED_NUM,void (*CallBack)(uint8_t))
    {
        (*CallBack)(LED_NUM);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    main函数或其他函数使用

    STA_Machine.c状态机源文件通过结构体变量LED,调用LED_Fun函数,并传入第一个参数LED灯编号,然后第二个参数就传入具体实现功能的函数,因为函数名就等于函数的首地址,所以把LED_OFF函数的首地址给到了函数指针CallBack,当LED_Fun函数被调用,则LED_OFF函数也会紧接着被调用,从而实现回调函数机制

    /*
    * @name   Fun_STA1
    * @brief  状态函数1
    * @param  None
    * @retval None   
    */
    static void Fun_STA1()
    {
        HAL_Delay(500);                     //延时500ms
        LED.LED_Fun(LED1,LED_OFF);         //熄灭LED1    LED.LED_Fun(LED2,LED_OFF);         //熄灭LED2
        LED.LED_Fun(LED3,LED_OFF);         //熄灭LED3
        STA_Machine.ucSTA_Machine_Status = STA2;      //切换到STA2状态
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  • 相关阅读:
    vue点击按钮收缩菜单
    Nginx日志管理之访问日志配置
    什么是运营商精准大数据?又有什么作用?
    聊聊GPU与CPU的区别
    卷积神经网络实践-猫狗分类
    2024年两会-区块链方向-新质生产力-先进制造业集群
    【毕业设计】基于STM32的天气预报盒子 - 嵌入式 单片机 物联网
    Java使用ffmpeg指令实现音频格式转换
    Unity VFX Output 模块详解
    文件夹名称中空格如何替换为符号
  • 原文地址:https://blog.csdn.net/weixin_46251230/article/details/126688674