回调函数的注册机制,为什么在嵌入式固件开发中应用如此广泛?

嵌入式ARM 2023-04-12 12:01
在我们平时开发STM32或者其它单片机时,我们经常都会用到原厂提供的固件库函数,固件库函数中有非常多回调函数。那么,什么是回调函数呢?
回调函数是作为参数传递给另一个函数的函数。接受回调作为参数的函数预计会在某个时间点执行它。回调机制允许下层软件层调用上层软件层定义的函数。

上图表示用户应用程序代码和硬件驱动程序之间的交互。硬件驱动程序是一个独立的可重用驱动程序,它不了解上面的层(在本例中为用户应用程序)。硬件驱动程序提供 API 函数,允许用户应用程序将函数注册为回调。然后,此回调函数由硬件驱动程序作为执行的一部分进行调用。如果不使用回调,就会被编码为直接调用。这将使硬件驱动程序特定于特定的高级软件级别,并降低其可重用性。回调机制的另一个好处是,在程序执行期间可以动态更改被调用的回调函数。

1、C语言中的回调

不同的编程语言有不同的实现回调的方式。在本文中,我们将重点介绍C编程语言,因为它是用于嵌入式软件开发的最流行的语言。C语言中的回调是使用函数指针实现的。函数指针就像普通指针一样,但它不是指向变量的地址,而是指向函数的地址。在程序运行期间,可以设置相同的函数指针指向不同的函数。下面的代码中,我们可以看到如何使用函数指针将函数作为参数传递给函数。该函数将函数指针和两个整数值作为参数和。将执行的算术运算取决于将传递给函数指针参数的函数。

uint16_t cal_sum(uint8_t a, uint8_t b) {
    return a + b;
}
uint16_t cal_mul(uint8_t a, uint8_t b) {
    return a * b;
}
uint16_t cal_op (uint16_t (*callback_func)(uint8_t, uint8_t),uint8_t a, uint8_t b) {   
    return callback_func(a,b);
}

void main() {
    cal_op(cal_mul,4,10); 
    cal_op(cal_sum,9,5); 
}

2、回调的实际使用

回调可用于多种情况,并广泛用于嵌入式固件开发。它们提供了更大的代码灵活性,并允许我们开发可由最终用户进行微调而无需更改代码的驱动程序。

在我们的代码中具有回调功能所需的元素是:

  • 将被调用的函数(回调函数)
  • 将用于访问回调函数的函数指针
  • 将调用回调函数的函数("调用函数")

接下来,介绍一下使用回调函数的简单流程。首先声明一个函数指针,用于访问回调函数我们可以简单地将函数指针声明为:

uint8_t (*p_CallbackFunc)(void);

但是,对于更清晰的代码,最好定义一个函数指针类型:

typedef uint8_t (*CallbackFunc_t) (void); 

定义回调函数——重要的是要注意回调函数只是一个函数。由于它的使用方式(通过函数指针访问),我们将其称为回调。所以这一步只是我们之前声明的指针将指向的函数的定义。

uint8_t Handler_Event(void) {
/* code of the function */
}

注册回调函数——这是为函数指针分配地址的操作。在我们的例子中,地址应该是回调函数的地址。可以有一个专门的函数来注册回调函数,如下所示:

static CallbackFunc_t HandlerCompleted;

/*用来注册回调函数的功能函数*/
void CallbackRegister (CallbackFunc_t callback_func) {
     HandlerCompleted = callback_func;
}

/* 注册Handler_Event作为回调*/
CallbackRegister(Handler_Event);

3、代码应用案例

3.1、事件回调

在这个例子中,我们展示了如何使用回调来处理事件。下面的示例代码是基于较低级别物理通信接口(例如 UART、SPI、I2C 等)构建的数据通信协议栈。通信协议栈实现了两种不同类型的帧——标准通信帧和增强型通信帧。有两种不同的函数用于处理接收到的字节事件。在初始化函数中,函数指针被分配了应该使用的函数的地址用于处理事件。这是注册回调函数的操作。

/*指向回调函数的函数指针*/
uint8_t ( *Receive_Byte) ( void );

/*
 * 简化的初始化函数
 * 这里函数指针被分配了一个函数的地址(注册回调函数)
 */
void Comm_Init( uint8_t op_mode) {
        switch ( op_mode ) {
        case STD_FRAME:           
            Receive_Byte     = StdRxFSM;
            break;
        case ENHANCED_FRAME:  
            Receive_Byte     = EnhancedRxFSM;
            break;
        default:
            Receive_Byte     = EnhancedRxFSM;
        }
}

/* 这些是在通信栈中实现的函数(回调)
* 它们不会在任何地方直接调用,而是使用函数指针来访问它们 */
uint8_t  StdRxFSM(void) {
    //在这里完成处理工作
}
uint8_t  EnhancedRxFSM(void) {
    //在这里完成处理工作
}

当从物理通信接口(例如 UART)接收到新字节(事件)时,用户应用程序代码会调用我们示例中的回调函数。

extern uint8_t (*Receive_Byte)( void );
void receive_new_byte() 
{
   Receive_Byte(); 
}

3.2、寄存器中的多个回调

这个例子展示了我们如何创建一个寄存器来存储回调函数。它是使用数据类型元素的数组实现的。数据类型是具有成员和成员的结构。用于为寄存器中的每个回调函数分配一个标识(唯一编号)。函数指针被分配与唯一关联的回调函数的地址。以下实现的是添加和删除回调的功能:

#define FUNC_REGISTER_SIZE 255
#define FUNC_ID_MAX 127
//函数指针类型
typedef  uint8_t (*callback_func) ( uint8_t * p_data, uint16_t len );
typedef struct 
{
    uint8_t           function_id;
    callback_func p_callback_func;
} function_register_t;

//一组函数处理程序,每个处理程序都有一个id
static function_register_t func_register[FUNC_REGISTER_SIZE];

//注册函数回调
uint8_t RegisterCallback (uint8_t function_id, callback_func p_callback_func ) {
    uint8_t    status;
    if ((0 < function_id) && (function_id <= FUNC_ID_MAX)) 
    {
        //向寄存器添加函数
        if ( p_callback_func != NULL ) { 
            for (int i = 0; i < FUNC_REGISTER_SIZE; i++ ) {
                if (( func_register[i].p_callback_func == NULL ) ||
                    ( func_register[i].p_callback_func == p_callback_func )) {
                    func_register[i].function_id = function_id;
                    func_register[i].p_callback_func = p_callback_func;
                    break;
                }
            }
     if (i != FUNC_REGISTER_SIZE) {
        status = SUCESSFULL;
     }
     else {
        status = FAILURE;
     }
        }
        else { 
            //从寄存器中删除
            for ( i = 0; i < FUNC_REGISTER_SIZE; i++ ) {
                if ( func_register[i].function_id == function_id ) {
                    func_register[i].function_id = 0;
                    func_register[i].p_callback_func = NULL;
                    break;
                }
            }
            status = SUCESSFULL;
        }
    }
    else {
        status = FAILURE; /* Invalid argument */
    }
    return status;
}

在下面的代码中,我们可以看到一个函数示例,该函数可用于根据函数 id 调用回调。

//具有特定函数代码的回调函数如何被调用的示例
uint8_t execute_callback(uint8_t FuncCode, uint8_t * p_data_buf, uint16_t len) 
{  
    uint8_t status;
    status = FAILURE;
    for( i = 0; i < FUNC_REGISTER_SIZE; i++ ){
        /* No more callbacks registered, exit. */
        if( func_register[i].function_id == 0 ){
            break;
        }
        else if( func_register[i].function_id == FuncCode) {
            status = func_register[i].p_callback_func( p_data_buf, len );
            break;
        }
     }
     return status;
}

4、结论

我们可以编写不使用回调的程序,但是通过将它们添加到我们的工具库中,它们可以使我们的代码更高效且更易于维护。明智地使用它们很重要,否则过度使用回调(函数指针)会使代码难以进行排查和调试。另一件需要考虑的事情是使用函数指针可能会阻止编译器执行的一些优化(例如函数内联)。

5、文献引用

[1]王铬. 回调函数在软件设计中的应用[J]. 河南教育学院学报:自然科学版, 2003, 12(3):3.

[2]李建波, 陈榕福, & 王劲. (2020). Stm32cube mx串口中断回调函数的研究. 电子世界(5), 2.

END

来源:嵌入式应用研究院

版权归原作者所有,如有侵权,请联系删除。

推荐阅读
国产MCU厂商,一网打尽!
外资芯片大厂又裁员:研发一个不留!
国产FPGA开发板上手体验:不足百元,冲击高端

→点关注,不迷路←

嵌入式ARM 关注这个时代最火的嵌入式ARM,你想知道的都在这里。
评论 (0)
  • 二、芯片的设计1、芯片设计的基本流程 (1)需求定义: 明确芯片功能(如处理器、存储、通信)、性能指标(速度、功耗、面积)及目标应用场景(消费电子、汽车、工业)。 (2)架构设计: 确定芯片整体框架,包括核心模块(如CPU、GPU、存储单元)的协同方式和数据流路径。 (3)逻辑设计: 通过硬件描述语言(如Verilog、VHDL)将架构转化为电路逻辑,生成RTL(寄存器传输级)代码。 (4)物理设计: 将逻辑代码映射到物理布局,涉及布局布线、时序优化、功耗分析等,需借助EDA工具(如Ca
    碧海长空 2025-04-15 11:30 184浏览
  • 一、芯片的发展历程总结:1、晶体管的诞生(1)电子管时代 20世纪40年代,电子管体积庞大、功耗高、可靠性差,无法满足计算机小型化需求。(2)晶体管时代 1947年,贝尔实验室的肖克利、巴丁和布拉顿发明点接触晶体管,实现电子信号放大与开关功能,标志着固态电子时代的开端。 1956年,肖克利发明晶体管。(3)硅基晶体管时代 早期晶体管采用锗材料,但硅更耐高温、成本低,成为主流材料。2、集成电路的诞生与发展 1958年,德州仪器工程师基尔比用锗材料制成世界上第一块含多个晶体管的集成电路,同年仙童半导
    碧海长空 2025-04-15 09:30 143浏览
  •   无人装备作战协同仿真系统软件:科技的关键支撑   无人装备作战协同仿真系统软件,作为一款综合性仿真平台,主要用于模拟无人机、无人车、无人艇等无人装备在复杂作战环境中的协同作战能力、任务规划、指挥控制以及性能评估。该系统通过搭建虚拟战场环境,支持多种无人装备协同作战仿真,为作战指挥、装备研发、战术训练和作战效能评估,提供科学依据。   应用案例   系统软件供应可以来这里,这个首肌开始是幺伍扒,中间是幺幺叁叁,最后一个是泗柒泗泗,按照数字顺序组合就可以找到。   核心功能   虚拟战
    华盛恒辉l58ll334744 2025-04-14 17:24 90浏览
  • 你知道精益管理中的“看板”真正的意思吗?在很多人眼中,它不过是车间墙上的一块卡片、一张单子,甚至只是个用来控制物料的工具。但如果你读过大野耐一的《丰田生产方式》,你就会发现,看板的意义远不止于此。它其实是丰田精益思想的核心之一,是让工厂动起来的“神经系统”。这篇文章,我们就带你一起从这本书出发,重新认识“看板”的深层含义。一、使“看板”和台车结合使用  所谓“看板”就是指纸卡片。“看板”的重要作用之一,就是连接生产现场上道工序和下道工序的信息工具。  “看板”是“准时化”生产的重要手段,它总是要
    优思学院 2025-04-14 15:02 118浏览
  • 一、智能语音播报技术演进与市场需求随着人工智能技术的快速发展,TTS(Text-to-Speech)技术在商业场景中的应用呈现爆发式增长。在零售领域,智能收款机的语音播报功能已成为提升服务效率和用户体验的关键模块。WT3000T8作为新一代高性能语音合成芯片,凭借其优异的处理能力和灵活的功能配置,正在为收款机智能化升级提供核心技术支持。二、WT3000T8芯片技术特性解析硬件架构优势采用32位高性能处理器(主频240MHz),支持实时语音合成与多任务处理QFN32封装(4x4mm)实现小型化设计
    广州唯创电子 2025-04-15 08:53 123浏览
  • 三、芯片的制造1、制造核心流程 (1)晶圆制备:以高纯度硅为基底,通过拉晶、切片、抛光制成晶圆。 (2)光刻:光刻、离子注入、薄膜沉积、化学机械抛光。 (3)刻蚀与沉积:使用干法刻蚀(等离子体)精准切割图形,避免侧壁损伤。 (4)掺杂:注入离子形成PN结特性,实现晶体管开关功能。2、材料与工艺创新 (1)新材料应用: 高迁移率材料(FinFET中的应变硅、GaN在射频芯片中的应用); 新型封装技术(3D IC、TSV硅通孔)提升集成度。 (2)工艺创新: 制程从7nm到3nm,设计架构由F
    碧海长空 2025-04-15 11:33 232浏览
  • 一、引言:健康管理数字化浪潮下的血压监测转型在慢性病高发与老龄化加剧的双重压力下,家庭健康监测设备正从“被动测量工具”向“主动健康管家”演进。传统血压计虽能提供基础数值,却无法解决用户的核心痛点:数据如何解读?异常如何干预?风险如何预防?WT2605C芯片方案的诞生,通过“AI对话+云端互联+个性化服务”三重技术突破,重新定义了血压计的价值边界——它不仅是一台测量仪器,更是一个全天候在线的健康管理生态系统。二、传统血压计的局限与用户需求升级1. 功能单一性困境数据孤岛:仅显示收缩压/舒张压数值,
    广州唯创电子 2025-04-16 08:55 82浏览
  • 四、芯片封测技术及应用场景1、封装技术的发展历程 (1)DIP封装:早期分立元件封装,体积大、引脚少; (2)QFP封装:引脚密度提升,适用于早期集成电路。 (3)BGA封装:高密度互连,散热与信号传输优化; (4)3D封装:通过TSV(硅通孔)实现垂直堆叠,提升集成度(如HBM内存堆叠); (5)Chiplet封装:异质集成,将不同工艺节点的模块组合(如AMD的Zen3+架构)。 (6)SiP封装:集成多种功能芯片(如iPhone的A系列SoC整合CPU、GPU、射频模块)。2、芯片测试 (1
    碧海长空 2025-04-15 11:45 236浏览
  • 展会名称:2025成都国际工业博览会(简称:成都工博会)展会日期:4月23 -25日展会地址:西部国际博览城展位号:15H-E010科士威传动将展示智能制造较新技术及全套解决方案。 2025年4月23-25日,中国西部国际博览城将迎来一场工业领域的年度盛会——2025成都国际工业博览会。这场以“创链新工业,共碳新未来”为主题的展会上,来自全球的600+ 家参展企业将齐聚一堂,共同展示智能制造产业链中的关键产品及解决方案,助力制造业向数字化、网络化、智能化转型。科士威传动将受邀参展。&n
    科士威传动 2025-04-14 17:55 90浏览
  • 2025年4月13日(中国武汉)——在全球经济分化与地缘政治不确定性加剧的背景下,科技与金融的深度融合已成为推动创新与繁荣的关键动力。为实现科技创新、产业进步和金融发展有机结合,发挥金融对科技创新和产业进步的支持作用,国际金融论坛(IFF)科技金融委员会启动大会暨首届科技金融圆桌会议于4月13日在湖北省武汉市武汉产业创新发展研究院成功举行。同时,IFF科技金融委员会由国际金融论坛IFF与武创院联合成立。本次大会汇聚了来自政府、产业与学术研究机构及金融等多领域的精英,共同探讨科技金融如何更好地服务
    华尔街科技眼 2025-04-15 20:53 65浏览
  •   高空 SAR 目标智能成像系统软件:多领域应用的前沿利器   高空 SAR(合成孔径雷达)目标智能成像系统软件,专门针对卫星、无人机等高空平台搭载的 SAR传感器数据,融合人工智能与图像处理技术,打造出的高效目标检测、识别及成像系统。此软件借助智能算法,显著提升 SAR图像分辨率、目标特征提取能力以及实时处理效率,为军事侦察、灾害监测、资源勘探等领域,提供关键技术支撑。   应用案例系统软件供应可以来这里,这个首肌开始是幺伍扒,中间是幺幺叁叁,最后一个是泗柒泗泗,按照数字顺序组合
    华盛恒辉l58ll334744 2025-04-14 16:09 155浏览
  • 一、智能门锁市场痛点与技术革新随着智能家居的快速发展,电子门锁正从“密码解锁”向“无感交互”进化。然而,传统人体感应技术普遍面临三大挑战:功耗高导致续航短、静态人体检测能力弱、环境适应性差。WTL580微波雷达解决方案,以5.8GHz高精度雷达感知技术为核心,突破行业瓶颈,为智能门锁带来“精准感知-高效触发-超低功耗”的全新交互范式。二、WTL580方案核心技术优势1. 5.8GHz毫米波雷达:精准感知的革命全状态人体检测:支持运动、微动(如呼吸)、静态(坐卧)多模态感知,检测灵敏度达0.1m/
    广州唯创电子 2025-04-15 09:20 106浏览
  • 一、引言:智能化趋势下的学爬玩具开发挑战随着早教理念的普及,学爬玩具作为婴幼儿早期运动能力开发的重要工具,市场需求持续增长。然而,传统学爬玩具开发面临多重挑战:需集成红外遥控、语音交互、电机控制等多模块,开发周期长、硬件成本高;复杂的红外编解码与语音功能实现依赖工程师深度参与,技术门槛陡增。如何以更低成本、更快速度打造差异化产品,成为行业亟待解决的痛点。二、传统开发模式痛点分析硬件冗余红外接收模块、语音芯片、主控MCU分立设计,导致PCB面积增加,BOM成本攀升。开发周期长需工程师独立完成红外协
    广州唯创电子 2025-04-16 08:40 79浏览
  • 在当今汽车电子化和智能化快速发展的时代,车规级电子元器件的质量直接关系到汽车安全性能。三星作为全球领先的电子元器件制造商,其车规电容备受青睐。然而,选择一个靠谱的三星车规电容代理商至关重要。本文以行业领军企业北京贞光科技有限公司为例,深入剖析如何选择优质代理商。选择靠谱代理商的关键标准1. 授权资质与行业地位选择三星车规电容代理商首先要验证其授权资质及行业地位。北京贞光科技作为中国电子元器件行业的领军者,长期走在行业前沿,拥有完备的授权资质。公司专注于市场分销和整体布局,在电子元器件领域建立了卓
    贞光科技 2025-04-14 16:18 152浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦