总结:今天我们继续讲回调函数。 输入类型 输入类型函数通常用于在不同文件之间或不同硬件层或应用层之间传输信号或数据,例如串行端口数据的击键检测。 最终,通过让这个指针指向另一个文件,其作用是实现不同文件之间的数据传输,同时保持可移植性、相互独立、互不干扰。
大家好,我是Wiji。
今天我们继续讲回调函数。
我之前写过一篇文章,得到了很多老兵的认可。
最近有几个新同学对回调函数感到困惑。
我不明白为什么你需要创建一个这样的函数,其中指针来回移动,来回指向。
先写一篇文章热身,然后晚上直播讲解一下并回答问题。
实际上,我们不想把简单的事情复杂化,但是如果你想写出好的代码结构,回调函数是必不可少的。
如果你看一下高手写的程序,你会发现蓝牙协议栈、实时操作系统、STM32固件库等都是这样做的。
每个人可能写法不同,但本质是一样的。
首先我们来了解一下回调函数的作用。
我通常喜欢将函数分为输出和输入类型(我的理解)。
输出类型:
主动调用的控制函数,例如控制LED灯开/关、控制蜂鸣器发声或不发声、控制LCD等。 显示、控制 继电器打开/关闭。
简单地说,我们知道何时调用这些函数。 例如,如果满足某些条件,则主动调用这些函数。
这种函数是输出函数。
输入类型:
输入类型函数通常处理不同.c文件/不同层(硬件层、应用层)之间的信号和数据,例如按键检测、串口等。用于转移。 端口数据。
您不知道何时按下按钮或何时从串行端口发送数据。
当然,您也可以创建一个带有返回值的函数并定期检测它(例如,每 10 毫秒扫描一次键)。
Unsigned char ScanKey()
{
//按键检测程序... }
然后在主程序中使用. :
p>
while(1)
{
无符号字符密钥;
If(10ms时间以上)
{
Key = ScanKey() }
if(Key == 有效键值)
{
//运行按键功能程序
}
}
这样就不断地扫描按键。
当然这个方法也是可以的,但是不够专业,不够充分。
因此,在while循环中必须始终判断Key的值,并根据Key的值判断某个键是否被按下。 这在一定程度上浪费了CPU资源。
而且,这种方法在某些应用场景下实现起来比较困难。 比如串口数据,总不能在while循环内判断是否有新的串口数据吧?
我们的理想状态是什么?
当按下按钮或新数据到达时我们会收到通知。
这种通知方法通常称为事件触发。 仅当触发关键事件时才进行处理。
所以目前来说,回调函数可以很好地解决这个需求。
我们以按钮为例。
我之前提到过,每个人编写回调函数的风格可能会有所不同。 STM32固件库中的中断处理函数基本上都是回调函数,但我编写的方式还是略有不同。
创建回调函数如果您这样做,则需要执行以下步骤:
第1步:
自定义函数指针类型。 类型名称为 KeyEvent_CallBack_t。
typedef void (*KeyEvent_CallBack_t)(KEY_VALUE_TYPEDEF Keys);
这通常是必需的。 它位于头文件中,因为其他 .c 文件也使用它。
这是一个没有返回值的函数指针类型,形参是一个KEY_VALUE_TYPEDEF枚举类型。
通常,形式参数键是最终通过回调函数传递给其他 .c 文件的信号/数据。 对于按键检测,它显示按键的值以及按下的按键。
让我们看看KEY_VALUE_TYPEDEF枚举中有哪些值。
typedef 枚举
{
KEY_IDLE_VAL,
KEY1_CLICK,
KEY1_CLICK_RELEASE,
key1_long_press,
key1_long_press_continuous,
key1_long_press_release, // 5
key2_click, // 6 p> key2_long_press,
key2_long_long_continuo美国,
KEY2_LONG_PRESS_RELEASE,
KEY3_CLICK,KEY3_CLICK_RELEASE,
KEY3_LONG_PRESS,
KEY3_LONG_PRESS_CONTINUOUS,
KEY3_LONG_PRESS_RELEASE,
KEY4_CLICK,//16
KEY4_CLICK_RELEASE,
KEY4_LONG_PRESS,
KEY4_LONG_PRESS_CONTINUOUS,
p>
KEY4_LONG_PRESS_RELEASE,
KEY5_CLICK,//21
KEY5_CLICK_RELEASE,
EY5_LONG_PRESS,
KEY5_LONG_PRESS_CONTINUOUS,
KEY5_LONG_PRESS_RELEASE,
KEY5_LONG_PRESS_RELEASE,
p >
KEY6_CLICK,KEY6_CLICK,//26
KEY6_CLICK_RELEASE,
KEY6_LONG_PRESS,
KEY6_LONG_PRESS_CONTINUOUS,
KEY6_LONG_PRESS_RELEASE,
}KEY_VALUE_TYPEDEF;
该项目共有6个按钮,每个按钮都需要检测由于有短按、短按释放、长按、长按释放、连续长按五种功能,所以一共有30个不同的枚举值对应不同按键的不同功能。
第二步:
自定义函数指针类型后,可以通过类型名KeyEvent_CallBack_t来定义函数指针变量。
KeyEvent_CallBack_t KeyScanCBS;
KeyScanCBS 是一个函数指针,因此返回值为 void 类型,形参为 KEY_VALUE_TYPEDEF 枚举类型。
最终,我们希望这个指针指向其他.c文件中的功能,同时仍然保持良好的可移植性(相互独立,互不干扰),实现不同.c文件之间的数据传输。 彼此))。
你怎么指点? 我的方法是重新定义函数以指向此指针,以便可以轻松地从其他 .c 文件调用它。 该函数称为注册函数。
例如以下函数:
void hal_KeyScanCBSRegister(KeyEvent_CallBack_t pCBS)
{
if(KeyScanCBS == 0)
这个函数是做什么的?即将之前定义的KeyScanCBS函数指针指向外部函数地址(即指向该函数的函数名)。
当然,这个功能不是必需的。 这只是我的思考和编码风格。 您也可以创建这样的函数,而无需任何其他详细信息。 使用KeyScanCBS之前只需指定外部函数即可,否则等待。 程序崩溃了,哈哈哈。
第 3 步:
完成这些步骤后,让我们继续了解如何使用它们。
如果需要使用按键函数,只需在.c文件中重写相同的函数即可。
例如文件app.c是产品的功能代码(应用层),必须使用应用层的主要功能。
重写函数时,返回值和形参必须与函数指针的类型相同。
如果您忘记了,请再次查看。
typedef void (*KeyEvent_CallBack_t)(KEY_VALUE_TYPEDEF key);
无返回值。 形式参数的类型为 KEY_VALUE_TYPEDEF。
只有这样才能将该函数的地址赋给KeyScanCBS指针并成功传输数据。
重写后的函数通过形参接收硬件层密钥值。 串口数据也是如此,只是形参不同。
接下来,仅在产品函数的初始化函数中直接调用hal_key.c中的注册函数。
将KeyEventHandle函数的地址赋值给hal_key.c中的KeyScanCBS函数指针。
所以最终你可以理解KeyScanCBS相当于KeyEventHandle函数。
我们现在将检查 hal_key.c 文件中的密钥检测解析器。最后,运行KeyScanCBS并将密钥(密钥值)传输到app.c文件中。
这样就可以由事件驱动。 仅当按键被按下且实际激活时,调用KeyScanCBS将按键值传递给应用层。
中间,两个文件之间不存在全局变量的依赖关系,使得它们完全独立。 可以仔细消化一下。
这里的细节之一是为什么函数的形参使用枚举类型。
如果你做过一些模块(WiFi、蓝牙等)的二次开发,你可能已经注意到,模块的核心代码是封装在我理解的lib这样的库中提供的。 我看不到源代码。
只有这些功能可用。 如果你不使用枚举,你不知道你可以传递什么值作为形式参数,对吗?
使用枚举时,列出所有可能的值并给它们适当的名称,以便它们的含义一目了然。 这不是很方便吗?
好的,今天就到此为止。 你可以下去做一些实验。
原创并不容易。 尝试用最常见的语言表达它。 如果有帮助的话,请分成3部分^^。
评论前必须登录!
注册