0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

应用笔记 | 浅谈STM32库里的回调函数

STM32单片机 来源:未知 2023-09-14 17:10 次阅读

wKgZomUCzxeAaQAcAAHUHXdxAL4057.gif

关键字:回调函数,HAL库

目录预览

1.回调函数

2.STM32固件库里的回调函数

3.STM32库函数里的回调机制及触发事件

4.常见问题

01 回调函数

有人对STM32固件库里的回调函数有些好奇甚至纠结,这里简单地介绍下,以供参考。其实从用法及功能上讲他们并没有什么特别的,跟其他函数一样,也是实现特定功能的代码段。一般来讲,所谓回调函数,泛指基于事件触发而被调用执行的函数,简单点说,就是条件满足了就调用的函数,往往会跟函数指针结合起来通过函数指针实现调用。

经常会有人基于类似下面的代码介绍回调函数:

wKgZomUCzxeAeJQJAAE3ih5ygAI645.png

wKgZomUCzxeAcQ0qAACzLxsXWNI996.png

在上面代码中,那四个有关加减乘除的函数可以看成回调函数,具体何时被调用,根据函数Compute(float a,float b,float(*Action)(float a,float b))里的函数指针的赋值情况来定,被赋予哪个回调函数的地址就调用哪个回调函数。当然,使用函数指针并不是回调函数的核心特征,因事件驱动而被调用才是其核心特征。

生活中我们有时会对某人说,回头再谈、回头再聊。潜台词往往就是等时机成熟了、条件满足了再来具体交涉。这里就充满着浓浓的回调意味。

回调函数可以理解为事件响应函数或者说事件驱动函数。即使相同的事件、基于不同的场景可能会有不同应对处理,从软件代码角度讲就对应不同的回调函数代码。

我们不妨看个生活中的例子。生活中有人中了六合彩了,针对这一事件,中奖人可能有下面诸多举动之一【这里简化下,多选一】。但这件事发生在不同人身上,右边的选择很可能不尽一样。换言之,中奖了,到底会选择右边哪一项还得结合具体的人来定。

wKgZomUCzxeAFlJnAACj-D4y6D8586.png

图1 中六合彩的可能后续行为

我们再切换到STM32的嵌入式开发中来,以UART接收完成事件为例。针对这一事件,不同的应用场景的应对处理往往也是五花八门、五彩缤纷。

wKgZomUCzxeAEWUJAACtWY4vIss183.png

图2 UART接收完成后可能后续动作

显然,特定的应用场景对应着特定的回调函数,一般来讲,没法简单地仅仅基于事件就拟定一段既能适用于各种场景而又富有针对性的代码。

结合上面的描述,稍微小结下。回调函数除了具有基于事件的触发而被调用执行的特征外,还具有相同事件因应不同应用场景可能需要不同的回调函数之特征,即基于特定应用场景的回调函数其内容具有特定性。

02

STM32固件库里的回调函数

说到这里,我们具体结合STM32外设固件库里回调函数来聊聊。

首先,作为一个函数库,除了个别初始化函数外,里面不存在现存的完整的回调函数。结合前面的介绍,我们知道回调函数需要结合具体场景而拟定,作为函数库根本做不到这一点,它没法事先知晓发生某个事件时不同的应用会需要采取怎样的操作。

其次,STM32库函数的确采用了回调机制,并基于可能的各种事件为STM32开发者预留了只有函数定义而无具体内容的回调函数,或者是只定义了一些基于各类事件的函数指针,具体的回调函数需要用户完成并将函数地址赋给相应的函数指针而被调用。简单点说,函数库给我们事先预留了众多的回调函数接口

STM32固件库里的回调函数采用了两种调用方式:

第一种是legacy方式,传统的回调方式,库以weak方式定义了各种空的回调函数,像下面这些。STM32库里都给我们准备好了。【下面是有关UART部分事件的弱回调函数体,内容为空】

wKgZomUCzxeADzDJAANeplyjz2o647.png

图3 UART传输事件相关弱回调函数定义

具体开发时,我们根据事件和应用场景基于类似上面的weak函数进行重写,重写时拿掉weak,库里预留的弱定义函数尽量不用动它。比方像下面这些都是最终的用户回调函数。

wKgZomUCzxiAT000AATeo_OfHWQ133.png

图4 UART传输事件相关的用户回调函数

另外一种就是指针方式,或称注册方式。即函数库里事先基于各类事件定义好了各种回调函数指针,具体的回调函数由用户基于不同事件和应用需求撰写,然后将函数地址赋给函数指针,这个动作我们称之为回调函数进行注册,之后回调函数就可以通过函数指针而被适时调用。

比方下面是UART外设里定义的一些函数指针:【星号所指的是与UART传输完成事件有关的回调函数所用的指针】

wKgZomUCzxiAS8UPAAfoAb6dGr8950.png

图5 UART传输事件相关的回调函数指针

当我们将回调函数写好后,将函数地址赋给函数指针即可在相应事件发生时被调用。比方类似下面的操作代码。红星标所指代码就是在做回调函数的注册。

wKgZomUCzxiAABYkAADrvcGtRLk282.png

图6 UART传输完成事件用户回调函数及注册

给函数指针赋地址可以直接赋地址或通过调用库函数xxx_RegisterCallback完成【见上图星标代码】。

这种指针方式需要我们对C语言中的结构体、函数指针有相应的了解,库只是给我们提供了相应的函数指针,具体的用户回调函数由用户根据需要来编写,将其地址赋给相应的函数指针以供调用。

而前面介绍的传统型回调函数,库则帮我们把可能涉及到的回调函数全部以弱定义的方式都准备好了,我们按需针对性选用,去掉weak填空重写。使用起来相对更直观些,无需我们对函数指针有太多了解。

目前STM32库回调机制中,作为用户到底使用上面的哪种回调方式呢?在每个系列的固件库的配置头文件中有针对各个外设事件回调函数使用方式的选择,比方以STM32F4系列为例,这里有个stm32f4xx_hal_conf.h的头文件,我们可以看到基于各个外设事件回调函数使用方式选择的宏。

wKgZomUCzxiAGgUlAAUt6fbPy1Q732.png

图7 回调函数调用方式的选择配置

若我们不对该头文件的相应外设事件的回调函数调用方式的宏定义做调整,则默认传统回调方式,即legacy方式,非指针方式。若将相应的宏值改为1,则该外设事件相关回调函数采用指针注册方式。

03 STM32库函数里的回调机制及触发事件

整体上讲,STM32外设库里的API函数大体由三部分组成,分别是:

初始化函数

启动型执行函数

回调函数【弱定义函数或回调函数指针,最终靠用户具体完成编写】

这样的安排,让整个工程代码结构比较清晰,可以让人快速了解库结构,同时现存的API函数大大减少开发工作量,预留的回调函数接口一方面给开发者提供了便利,另一方面让用户基于不同应用场景自由组织代码而又不破坏整个软件架构。

对于回调函数,可以由哪些事件触发呢?大致分三类,分别是外设初始化操作、外设处理完成【中断】事件、外设出错【中断】事件。我们关注最多是外设处理完成中断事件相关的回调函数。

wKgZomUCzxmAMpSLAAGwSBkNnWY201.png

图8 回调函数触发事件的分类

04 常见问题

4.1 STM32库函数里的回调函数是什么,有何用?

回调函数终究乃用户所编写,是用户基于特定事件和应用需求而编写的功能模块,与其他函数并无本质区别。形式上讲,STM32库预先为用户做了回调函数的弱定义或基于事件的函数指针的定义。因基于特定条件发生后被调用执行而被冠以回调称号。

严格来讲,库函数里没有完整的回调函数,只有基于各类事件的弱定义的不具备实际功能的空回调函数,或者是针对各类事件而定义的各种用于调用回调函数的函数指针。我们的程序监测相应条件或事件往往是有的放矢,当相应事件出现时我们需要做相应的处理,这正是回调函数要实现的功能,也是其功用所在。

4.2 STM32工程里的回调函数与中断函数有什么区别?

STM32外设库里的回调函数的确多数时候跟中断事件及中断服务程序息息相关,往往在中断服务程序中基于特定事件调用相应的用户回调函数。很多时候,我们完全可以将用户回调函数看成中断函数的一个调用模块或延伸。

一个中断服务程序里可以因不同事件而调用不同的回调函数,即一个中断服务程序里可能包含多个不同的回调函数。比方,我们在定时器中断服务程序里可以涉及多个事件及相应的用户回调函数,定时器中断服务程序可能涉及更新事件、不同通道的比较事件或捕获事件,相应的用户回调函数往往因应用场景而异。

当然,回调函数的调用还可以是中断事件以外的其他事件触发调用,比方可以基于初始化操作来调用相应初始化回调函数。当然,在库里对某个外设的初始化可能有些默认操作,但这个默认操作很难是放之四海而皆准的操作,这时我们就得根据实际应用针对性编写初始化代码,即初始化型回调函数。

4.3 STM32库函数里的回调函数是否可以不用?

STM32库函数里的回调机制是库设计者为了便于软件框架清晰、减少开发者工作量等因素事先准备的函数声明及接口,用户使用时只需根据具体应用编写相关函数体。当然,你如果不想理睬这些回调函数声明及定义也是可以的,你根据具体应用自行组织代码完全可行。

4.4 STM32库函数里似乎存在着类似半成品的库回调函数?

STM32库函数里的确准备了一些包含用户回调函数的由库定义的回调函数,是库设计者基于各类特定事件而准备的回调函数,它会针对特定事件做一些基本而必要的操作,比方状态的检查、标志监测及清除,但它没有办法彻底写完整,因为它无法知道该事件发生后用户的真实需求是什么,该如何操作,所以它终究还是需要调用真正的用户回调函数。这样做的目的还是为了给开发者减少开发工作量、以及减少出错等。

我们不妨具体看个实例。下面的回调函数采样的指针注册方式,我们看看UART的DMA传输完成中断里传输完成的回调函数的调用过程。

首先,在UART的DMA启动函数HAL_UART_Transmit_ DMA()里有这样一部分内容:

wKgZomUCzxmAenU3AALdRrAC7tY951.png

图9 外设启动运行代码中库回调函数的赋值

库里就DMA传输事件准备了几个回调函数【传输完成、半完成、出错】,即上图中红线标示出来的。其实这几个回调函数还不算完整的用户回调函数,是库定义的并会做一些在它看来用户必定需要完成的一些操作,它事先帮助完成,之后才调用最终的户回调函数。我们以传输完成事件为例来看看,上图星号所标的函数。

wKgZomUCzxmAfylfAAQP-ZU9bwQ322.png

图10 库回调函数进一步调回用户回调函数

在这个库定义的UART_DMATransmitCplt()函数里,它对DMA的传输模式做了判断,如果是Normal模式,就将UART的传输数据长度设置为0,禁止DMA后续传输功能,使能UART传输完成中断的使能。然后才来调用用户回调函数【上图中箭头所指】。如果DMA工作在循环模式,代码进到UART_DMATransmitCplt()函数后就直接调用最终的用户回调函数。也就说这些库定义的回调函数在用户回调函数的基础上做了些必要操作,用户回调函数可以看成这类库回调函数的子集。

4.5 基于STM32库来组织用户回调函数要注意什么?

前面提过了,用户回调函数主要基于初始化事件或中断事件而组织的代码。那些中断事件的回调函数的调用基本都是在中断服务程序里发生的。所以,我们在编写回调函数时要结合具体情况灵活地组织代码。要考虑中断优先级、具体事件响应的实时性等。具体点说,我们在组织回调函数时,要考虑是否一定要一股脑地全写在中断服务程序里,会不会影响别的中断响应。对于有些不紧急而又耗时的事件响应代码,可以考虑只在回调函数里设置相应标志,真正的处理代码放到主循环去完成。

还提醒一点,STM32库设计者主动给我们准备了弱定义回调函数或基于各个事件的回调函数指针,尽管很丰富了,但未必能包罗万象,必要时我们可能还得根据具体情况来额外组织些类似回调函数的事件/中断响应代码。

关于STM32 HAL库里的回调函数就简单介绍到这里,希望能帮到一些STM32开发者。

完整内容请点击“阅读原文”下载原文档。

wKgZomUCzxmARlaPAAOyOP2Y2vs804.png订阅号

关注STM32

wKgZomUCzxmAEEnTAACDSIYrXK4469.jpg视频

wKgZomUCzxmAZ1vaAAAfRB2s2NQ304.pngB站账号

点击“阅读原文”,可下载原文档


原文标题:应用笔记 | 浅谈STM32库里的回调函数

文章出处:【微信公众号:STM32单片机】欢迎添加关注!文章转载请注明出处。


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 单片机
    +关注

    关注

    6029

    文章

    44480

    浏览量

    631609
  • STM32
    +关注

    关注

    2264

    文章

    10852

    浏览量

    354100

原文标题:应用笔记 | 浅谈STM32库里的回调函数

文章出处:【微信号:STM32_STM8_MCU,微信公众号:STM32单片机】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    定时器函数能不能用ICACHE_FLASH_ATTR定义?

    非 OS SDK 在中断处理函数中,请勿使用任何 ICACHE_FLASH_ATTR 定义的函数。 请问: 1、定时器和hw定时器的函数
    发表于 07-22 06:33

    esp8266怎么找到函数被调用的地方?

    esp8266里的程序怎么运行? 在user_init里注册了espconn_regist_sentcb,espconn_regist_recvcb这几个函数,怎么找到这几个
    发表于 07-10 08:24

    在HTTP的demo里面,函数不执行的原因?

    在HTTP的demo里面,我用下面这个函数注册了一个函数,但是发现有时候正常执行有时候不执行,只是偶尔不执行,大部分时间是正常的。有没有人能提供一下原因可能的方向,谢谢!! es
    发表于 07-10 08:23

    芯海通用 MCU应用笔记 :在 IAR 及 MDK 开发环境下使用 printf 函数重定向移植差异指南

    本应用笔记,旨在帮助客户在使用不同 IDE(MDK Keil 或 IAR)时,对使用 printf 函数来打印输出UART 串口信息时遇到的无法打印、打印乱码等移植问题作出梳理,理清不同 IDE
    发表于 05-16 11:56

    CSU18MX86应用笔记

    本应用笔记旨在为用户提供关于CSU18MX86的详细信息和使用指南,帮助用户快速开发基于CSU18MX86的应用。*附件:CSU18MX86应用笔记_V1.0.pdf
    发表于 05-16 10:21

    stm32中断函数改变的变量在while中不变怎么解决?

    关于stm32 中断函数改变的变量在while中不变问题
    发表于 04-26 07:44

    stm32cubemx生成rtos后中断处理后执行的函数是否也有优先级?

    如题:函数是否也有优先级,我发现我的函数不能按照预想的来运行.
    发表于 04-25 08:16

    在uCGUI的函数里加了行代码,stm32无法启动怎么解决?

    我是一个初学者。最近在写一个uCGUI上的小应用的时候遇到了这样的问题,我在MULTIEDIT控件的函数里加了一行代码,运行我自己写的一个用于计算的函数,在没有加这一行代码的时候运
    发表于 04-24 07:06

    STM32H750VBT6 ADC1,ADC2,ADC3,加DMA为什么ADC3函数不能正常工作?

    STM32H750VBT6ADC1,ADC2,ADC3,加DMA为什么ADC3函数不能正常工作 ADC1,ADC2
    发表于 03-29 07:23

    函数(callback)是什么?函数的实现方法

    函数是一种特殊的函数,它作为参数传递给另一个函数,并在被调用函数执行完毕后被调用。
    发表于 03-12 11:46 2589次阅读

    STM32cubeIDE PA0口外部中断改变LED灯状态时,GPIO翻转函数放在外部中断函数中不被调用怎么解决?

    STM32cubeIDE PA0口外部中断改变LED灯状态时,GPIO翻转函数放在外部中断函数中不被调用,放在EXTI0_IRQHand
    发表于 03-12 06:32

    函数指针与函数的应用实例

    通常我们说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数函数指针可以像一般函数一样,用于调用函数、传递参数。
    的头像 发表于 03-07 11:13 352次阅读
    <b class='flag-5'>函数</b>指针与<b class='flag-5'>回</b><b class='flag-5'>调</b><b class='flag-5'>函数</b>的应用实例

    ​​嵌入式中函数的实现方法

    函数的命名规范没有固定的标准,但是根据通用惯例和编码规范,函数的命名应该能够反映
    发表于 03-04 14:49 612次阅读

    函数指针的五大作用

    1,函数机制函数指针常用于实现函数。通过将
    的头像 发表于 12-06 08:00 1791次阅读
    <b class='flag-5'>函数</b>指针的五大作用

    一文解析BLE观察者模式机制

    nRF5 SDK从版本14开始,对事件机制做了更新,引入了观察者模式,以解耦不同BLE Layer对BLE事件的函数
    的头像 发表于 11-27 10:07 891次阅读
    一文解析BLE观察者模式<b class='flag-5'>回</b><b class='flag-5'>调</b>机制