单片机怎么用回调函数在不同文件之间传递数据

发布者:幸福家庭最新更新时间:2022-12-21 来源: zhihu关键字:单片机  回调函数  传递数据 手机看文章 扫描二维码
随时随地手机看文章

我们先来理解一下回调函数的作用。

函数我一般喜欢分为输出型和输入型(个人理解)。

输出型:

就是我们主动去调用的控制函数,比如说控制LED灯去亮和灭,控制蜂鸣器响和不响,控制LCD显示,控制继电器吸合和断开。

简单来说,就是我们知道什么时候该去调用这些函数,比如说满足某些条件的时候,我们就会主动去调用这些函数。

这种函数,就是输出型函数。

输入型:

输入型函数一般是用在不同.c文件/不同层(硬件层、应用层)之间传递信号和数据的,比如说按键检测、串口数据。

我们不知道什么时候按键会被按下、什么时候串口会有数据过来对吧?

当然,我们可以写一个带返回值的函数,然后定时去检测,比如说定时10ms去扫描一下按键。

unsigned char ScanKey(){
  //按键检测程序…}

然后我们在主程序用:

while(1){
  unsigned char key;if(10ms时间到){Key = ScanKey();}
       if(Key == 有效按键值)
       {
              //执行按键功能程序}}

这样不断地去扫描按键,检测按键是否被按下。

这种方式当然也是可以的,只是不够专业,不够好。

因为这个我需要一直在while循环里判断Key的值,然后根据Key的值来判断有没有按键按下,在一定程度上,造成了cpu资源的浪费。

而且有些应用场景,这种方式不好实现,比如说串口数据,你不能一直在while循环里判断是否有新的串口数据过来吧?

那我们理想的一种状态是什么?

就是如果有按键按下了,或者有新的数据来了,再通知我。

这种通知方式一般叫事件触发,就是触发了按键这个事件,我才去处理。

所以,这个时候回调函数就能很好地解决这种需求。

我们还是拿按键来举例。

前面我说每个人写回调函数的风格可能都不一样,STM32固件库的那些中断处理函数基本都是回调函数,但是跟我的编写风格还是有些差异。

我们在写回调函数的时候,需要以下几步:

第一步:

自定义一个函数指针类型,类型名称是KeyEvent_CallBack_t。

typedef void (*KeyEvent_CallBack_t)(KEY_VALUE_TYPEDEF keys);

还有这个一般是要自定义在头文件,因为别的.c文件也会用到。

这是一个无返回值的形参是KEY_VALUE_TYPEDEF枚举类型函数指针类型

一般这个形参keys就是我们最终要通过回调函数传递到别的.c文件的信号/数据,如果是按键检测的话也就是按键值,是哪个按键按下的。

我们来看下KEY_VALUE_TYPEDEF这个枚举都有哪些值?

typedef enum{
       KEY_IDLE_VAL,
       KEY1_CLICK,
       KEY1_CLICK_RELEASE,
       KEY1_LONG_PRESS,
       KEY1_LONG_PRESS_CONTINUOUS,
       KEY1_LONG_PRESS_RELEASE,           //5       KEY2_CLICK,                                                   //6       KEY2_CLICK_RELEASE,
       KEY2_LONG_PRESS,
       KEY2_LONG_PRESS_CONTINUOUS,
       KEY2_LONG_PRESS_RELEASE,
       KEY3_CLICK,                                            //11       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,
       KEY4_LONG_PRESS_RELEASE,
       KEY5_CLICK,                                     //21       KEY5_CLICK_RELEASE,
       KEY5_LONG_PRESS,
       KEY5_LONG_PRESS_CONTINUOUS,
       KEY5_LONG_PRESS_RELEASE,
       KEY6_CLICK,                                     //26       KEY6_CLICK_RELEASE,
       KEY6_LONG_PRESS,
       KEY6_LONG_PRESS_CONTINUOUS,
       KEY6_LONG_PRESS_RELEASE,}KEY_VALUE_TYPEDEF;

我们这个项目总共有6个按键,每个按键需要检测短按、短按释放、长按、长按释放、连续长按5个功能,所以总共有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 = pCBS;
       }}

这个函数的作用就是把我们前面定义的KeyScanCBS函数指针指向外部的函数地址(也就是要指向那个函数的函数名)。

当然,这个函数不是必须的,只是我的思维和代码风格,你也可以不单独写这样的函数,只要用之前把KeyScanCBS指向外部函数就可以了,否则等着程序死机吧哈哈哈。

第三步:

准备好这几步以后,我们继续来说下怎么去使用它。

我们哪里要用到按键的功能,就在那个.c文件那里重写一个同样的函数

比如说app.c这个文件是产品功能代码(应用层),我需要在应用层使用按键功能。

重写函数的时候,返回值和形参要跟那个函数指针类型一样。

如果你忘记了,那我们再来回顾下。

typedef void (*KeyEvent_CallBack_t)(KEY_VALUE_TYPEDEF keys);

无返回值,形参为KEY_VALUE_TYPEDEF类型。

只有这样,你才能把这个函数的地址赋值给KeyScanCBS这个指针,才能正常传递数据。

重写的这个函数就是通过形参来接收硬件层按键值的,如果是串口数据,也是同理,只是形参不一样。

然后,我们在产品功能初始化的函数里直接调用刚刚hal_key.c的注册函数

把KeyEventHandle这个函数的地址赋值给hal_key.c的KeyScanCBS这个函数指针。

所以,最终KeyScanCBS可以理解成等同于KeyEventHandle函数

我们在hal_key.c文件里,看按键检测解析程序,最终就是执行KeyScanCBS把我们keys(按键值)传递到我们app.c文件的。

这样,就能做到以事件去驱动,只有按键按下,并且真实有效,我才会调用KeyScanCBS,才会把按键值传递给应用层。

而中间,两个文件之间没有任何全局变量的依赖,也完全可以独立,大家可以细品消化一下。

这里有个细节就是为什么我函数的形参要用枚举类型。

如果你对接过一些模块(WiFi、蓝牙等)二次开发就知道了,模块核心代码都是封装成lib这种库给你的,你并看不到源代码。

只能用他们的函数,如果不用枚举,那你不知道形参可以传入什么值对吧?

如果用枚举,我把能用的值都列出来给你,并且起好名字,让你一看就知道是啥意思,这是不是就很方便?

Ok,今天就写到这里,大家下去可以做下实验。


关键字:单片机  回调函数  传递数据 引用地址:单片机怎么用回调函数在不同文件之间传递数据

上一篇:单片机怎么做定时器矩阵,彻底解决各种定时问题?
下一篇:单片机干嘛的?嵌入式是单片机吗?

推荐阅读最新更新时间:2024-11-17 01:18

基于MAX194在ARM单片机系统中的设计
火力发电厂和大型工业锅炉,通常采用向炉水中添加少量磷酸盐以防止钙、镁水垢的生成,磷酸根浓度不够,不能有效防止结垢,磷酸根离子含量过高,会导致炉水的pH值变高。因此磷酸根离子浓度是炉水检测的重要参数。ARM处理器具备高性能、低功耗、低成本等优点,将其应用于在线磷酸根离子分析仪的管理控制系统,可以提高磷酸根分析仪的处理速度和精度。 1 结构及测量原理简介 磷酸根离子分析仪整体结构包括光路系统、水路系统和管理控制系统三个部分。 光路系统主要包括:专用的单色LED冷光源、比色皿和光电传感器。 水路系统由比色皿、柱塞泵、多通道切换阀、流通池、样水/标液切换阀、流量计、排污阀、溢流管等组成。 利用化学吸光法原理,即在一定的
[单片机]
基于MAX194在ARM<font color='red'>单片机</font>系统中的设计
8051单片机端口结构—P1口
P1口也是一个准双向口,作通用I/O使用。 从P1口的结构上可以看出,P1口输出驱动部分与P0口不同,内部有上拉负载与电源相连。实质上电阻是两个场效应管FET并在一起,一个FET为负载管,其电阻固定;另一个FET可工作在导通或截止两种状态,使其总电阻值变化近为0或阻值很大两种情况。当阻值近似为0时,可将引脚快速上拉至高电平,当阻值很大时,P1口为高阻输入状态。当P1口输出高电平时,能向外提供拉电流负载,所以不必再接上拉电阻。在端口用作输入时,也必须先向对应的锁存器写入“1”,使FET截止。由于片内负载电阻较大,约20-40K欧。所以不会对输入的数据产生影响。
[单片机]
8051<font color='red'>单片机</font>端口结构—P1口
通过软件程序消除单片机由外界干扰产生的异常复位的影响
前言: 首先简单介绍一下外界干扰对单片机的2点影响: (1)异常复位 在刚上电或外部复位引脚为复位电平时,单片机系统进入一个预定的状态——复位状态。在复位状态下,控制寄存器的值是确定的,而数据寄存器的值是随机的,程序计数器也被赋予一个确定的值。但多数情况下控制寄存器的初始值并非我们需要的,不确定的数据寄存器的值也是无法使用的,需要初始化把它们设置成一个预期的、确定的且安全的状态。初始化完成后,系统进入待命状态。系统在工作过程中,因来自电源的干扰,也可能执行复位操作,称为异常复位,这时如不采取措施,记录工作过程的数据又会被初始化,从而造成异常停机。 (2)程序跑飞 所谓程序跑飞是程序没按预定的顺序执行。因为单片机
[单片机]
通过软件程序消除<font color='red'>单片机</font>由外界干扰产生的异常复位的影响
PIC系列8位MCU选择树
8pin 普通型 PIC12C508 PIC12C509; PIC12CE518 PIC12CE519 (E2PROM) 带A/D PIC12C671 PIC12C672; PIC12CE673 PIC12CE674 (E2PROM) 高级 FLASH程序/数据区/8x8乘法器 PIC18F010 PIC18F020; PIC18F012 PIC18F022 (10位A-D) 14pin 普通型 PIC16C505 18/20pin 基本级 普通型 PIC16C52 PIC16C54 PIC16C56 PIC16C58 高电压(3.5~15V) PIC16HV540 中级 普通型 PIC16C554
[单片机]
基于单片机USB接口的数据采集存储电路的设计
在一些特殊的工业场合,有时需要将传感器的信号不断的实时 采集 和存储起来,并且到一定时间再把 数据 回放到PC机中进行分析和处理。在工作环境恶劣的情况下采用高性能的单片机和工业级大容量的FLASH存储器的方案恐怕就是最适当的选择了。CYGNAL公司的C8051F320 SOC是一种具有8051内核的高性能单片机,运行速度为普通8051的12倍。该芯片内部528字节随机RAM和2048字节XRAM为数据缓冲和 程序 运行提供了充足的空间。更受欢迎的是它的串行扩展功能为当前的各种串行芯片和外部 设备 接口的扩展提供了极大的方便。高速的SPI硬件接口与串行FLASH RAM的无缝连接大大简化了电路板布线,而片内自带的USB接口功能使数据
[应用]
基于SH79F085单片机的电子秤应用
电子秤是衡器中的一种,随着科学技术的发展与进步,电子秤经历了由简单到复杂、粗糙到精密的全电子化称重过程。近年来,电子秤广泛应用于商业计价、精密衡器、工业包装、仓储运输等领域。   目前,市场上的电子秤系统主要采用两种方案实现对传感器模拟信号采样:双积分电路和高精度模数转换器(ADC)。   双积分电路是采用一种间接式的A/D转换器,它的基本原理是把待转换的模拟电压变换为与之成比例的时间间隔t,并在t时间内,用恒定频率的脉冲去计数,这就把时间t转换成了数字信号量。双积分电路由于电路复杂,转换时需要软件干预,以及精度较低(一般小于12位)不能满足高端电子秤应用,因此逐渐被市场淘汰。   高精度ADC一般采用Σ-Δ型转换器,
[单片机]
74LS164在单片机中的使用
在单片机系统中,如果并行口的IO资源不够,那么我们可以使用74LS164来扩展并行IO口,节约单片机IO资源。74LS164是一个串行输入并行输出的移位寄存器,并带有清除端。 74LS164的引脚可以查看数据手册。 proteus仿真图和代码附上。 #include reg51.h #define HIGH 1 #define LOW 0 #define SEG_PORT P0 sbit DATA = P0^4; sbit CLK = P0^5; unsigned char Timer0IRQEvent = 0; unsigned char Time1SecEve
[单片机]
74LS164在<font color='red'>单片机</font>中的使用
瑞萨发布用于工业和消费设备等领域的带有片上闪存的R32C/118家族32位微控制器
-- R32C/100高性能CPU内核与现有M16C系列微控制器产品兼容,并具有近2.2倍的处理性能-- 东京-2007年5月28日- 瑞萨科技(Renesas Technology Corp.)宣布,推出带有片上闪存的R32C/118家族微控制器,该微控制器是集成了R32C/100高性能32位CPU内核的R32C/100系列微控制器的一部分。R32C/118家族目前包括2个型号,适用于各种应用,包括诸如音频组件的工业设备和消费类电子产品。据悉,该微处理器的样品交付将于2007年9月从日本开始。 R32C/100系列是M16C系列『注1』的顶级产品,它是一个CISC(复杂指令集计算机)微控制器,也是一款在汽车、工业和消费设备
[新品]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
  • ARM裸机篇--按键中断
    先看看GPOI的输入实验:按键电路图:GPF1管教的功能:EINT1要使用GPF1作为EINT1的功能时,只要将GPFCON的3:2位配置成10就可以了!GPF1先配 ...
  • 网上下的--ARM入门笔记
    简单的介绍打今天起菜鸟的ARM笔记算是开张了,也算给我的这些笔记找个存的地方。为什么要发布出来?也许是大家感兴趣的,其实这些笔记之所 ...
  • 学习ARM开发(23)
    三个任务准备与运行结果下来看看创建任务和任运的栈空间怎么样的,以及运行输出。Made in china by UCSDN(caijunsheng)Lichee 1 0 0 ...
  • 学习ARM开发(22)
    关闭中断与打开中断中断是一种高效的对话机制,但有时并不想程序运行的过程中中断运行,比如正在打印东西,但程序突然中断了,又让另外一个 ...
  • 学习ARM开发(21)
    先要声明任务指针,因为后面需要使用。 任务指针 volatile TASK_TCB* volatile g_pCurrentTask = NULL;volatile TASK_TCB* vol ...
  • 学习ARM开发(20)
  • 学习ARM开发(19)
  • 学习ARM开发(14)
  • 学习ARM开发(15)
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved