嵌入式软件开发之上下层函数调用(七)

1、前言

        在熟悉 嵌入式软件开发之程序架构(一)嵌入式软件开发之程序分层(二)嵌入式软件开发之模块化程序设计(三) 三篇关于软件架构、分层和模块设计后,实际开发时会遇到一个问题,部分底层模块需要调用上层应用的函数实现功能,这样就会打乱程序分层中设置的规则(上层允许调用下层接口,但是下层禁止调用上层接口,这是分层的宗旨)。

        常见的情况如定时器中断、串口接收中断和按键触发立即响应等,如部分需求场景需要使用精准的定时策略,而即使采用  嵌入式软件开发之程序架构(一)提到的“时间片论法”或者操作系统,也不能保证 100% 定时精准地执行,因此通常做法都是将需要精准执行的函数放在定时器中断里面进行处理。

        由于定时器模块属于硬件抽象层,而需要执行的函数可能是功能模块层或者应用层,如果定时器调用上层函数(需要包含上层模块头文件),这样就违背了程序分层的宗旨。


2、解决思路

上述情况,可以采用回调函数的实现方式避免该状况。

回调函数是一个被作为参数传递的函数。在C语言中,回调函数只能使用函数指针实现(不清楚回调函数的可以自行百度)。

针对上面说到的定时器问题,定义一个函数指针,由定时器模块提供一个参数为函数指针的接口函数,上层模块通过该函数将需要被调用的函数地址通过函数指针的方式给到定时器模块,定时器模块则根据设置的条件不停地执行回调函数,也就是上层模块的函数,这样就避免了下层模块直接调用上层模块的函数。

分层这样做的好处在同一硬件平台只移植该驱动模块代码时,基本无需修改即可使用。也许有人感觉这样麻烦,但是良好的程序架构是清晰可见的,难免需要麻烦(优先的程序架构一般前期设计开发时间长,后期维护时间短)。

从运行效率看,通过回调函数调用和直接调用是一样的,因为函数指针指向的是函数地址,函数名本身也是一个地址。

看图理解:

 Callback 是一个函数指针类型的变量,通过函数 FML_TIME_Attach 拿到了应用层代码函数 OnFunction(...) 的函数地址,之后在定时器中断函数中根据触发条件调用 Callback 即可,调用方式和直接调用 OnFunction(...) 没有太大差异,只不过名字不一样(可以理解成取了一个别名),为了保证系统运行安全,调用前要确保  Callback 不为 NULL,否则会引起程序异常。


3、示例代码

定时器头文件:

#ifndef __TIMER_H
#define __TIMER_H

#include <stdint.h>

// 定义函数指针
typedef void (*TimeFunCB)(void);

extern void FML_TIME_Init(void);// 定时器初始化

extern int FML_TIME_Attach(TimeFunCB pfnCallback, uint32_t time);// 注册回调函数
extern int FML_TIME_Detach(TimeFunCB pfnCallback); // 注销回调函数


#endif

定时器源文件实现具体功能:

#include "timer.h"


/**
  * @brief 定义回调函数需要执行的条件信息
  */
typedef struct{
    TimeFunCB pfnCallback;            /*!< 回调函数 */
    
    uint32_t u1msTic;                 /*!< 1ms定时计时 */
    
    uint32_t u1msMaxTime;             /*!< 回调周期时间 */
} TimeFun_t;

/**
  * @brief 可注册的定时回调函数最大数目
  */
#define SUPPORT_FUN_MAX_NUM      10


/*!< 注册的定时回调函数 */
static TimeFun_t sg_tCallFun[SUPPORT_FUN_MAX_NUM];     

// 定时器初始化
void FML_TIME_Init(void)
{
    // 完成定时器1ms的初始化
}

// 注册回调函数
int FML_TIME_Attach(TimeFunCB pfnCallback, uint32_t time)
{
    int i;

    for (i = 0; i < SUPPORT_FUN_MAX_NUM; i++)
    {
        if (sg_tCallFun[i].pfnCallback == 0)
        {
            sg_tCallFun[i].pfnCallback = pfnCallback; 
            sg_tCallFun[i].u1msTic     = 0;
            sg_tCallFun[i].u1msMaxTime = time; 
            return 0; 
        }
    }
    
    return -1;

}

// 注销回调函数
int FML_TIME_Detach(TimeFunCB pfnCallback)
{
    int i;
    
    for (i = 0; i < SUPPORT_FUN_MAX_NUM; i++)
    {
        if (sg_tCallFun[i].pfnCallback == pfnCallback)
        {
            sg_tCallFun[i].pfnCallback = 0; 
            return 0; 
        }
    }
    
    return -1;
}


// 定时器中断处理函数.
void Time_ISRHandle(void)
{
    int i;
    
    // 清除中断标志位

    for (i = 0; i < SUPPORT_FUN_MAX_NUM; i++)
    {
        if (sg_tCallFun[i].pfnCallback == 0)
        {
            sg_tCallFun[i].u1msTic++;

            if (sg_tCallFun[i].u1msTic > sg_tCallFun[i].u1msMaxTime)
            {
                sg_tCallFun[i].u1msTic = 0;
                sg_tCallFun[i].pfnCallback();   
            }
        }
    }
}

Demo 程序:

#include "timer.h"

void TestFun(void)
{
    printf("1S 定时调用\r\n");
}

void main(void)
{
    FML_TIME_Init();
    FML_TIME_Attach(TestFun, 1000);

    while (1);
}

  • 8
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
嵌入式软件开发工作中可能存在一些常见的不足之处,以下是一些可能的问题: 1. 缺乏系统级的设计思维:嵌入式软件通常需要与硬件密切配合,因此需要具备系统级的设计思维。如果开发人员只关注软件本身而忽视了硬件和系统级的特性,可能会导致性能问题、兼容性问题或者不稳定性问题。 2. 不熟悉硬件平台:嵌入式软件通常运行在特定的硬件平台上,开发人员需要熟悉硬件平台的特性和限制。如果开发人员对硬件不熟悉,可能会导致性能瓶颈、资源浪费或者不可靠的软件。 3. 缺乏对实时性要求的理解:嵌入式系统通常有严格的实时性要求,开发人员需要明确了解和理解这些要求,并在软件设计和实现中考虑到实时性。如果开发人员对实时性要求不了解或者忽视,可能会导致系统响应不及时或者无法满足实时性要求。 4. 测试不充分:由于嵌入式软件通常运行在特定的硬件平台上,测试工作相对复杂。如果测试不充分,可能会导致潜在的错误或者缺陷无法被发现和修复,最终影响系统的可靠性和稳定性。 5. 缺乏良好的文档和注释:嵌入式软件通常需要长期维护和升级,而缺乏良好的文档和注释可能会给后续的开发和维护工作带来困难。开发人员应该编写清晰、详尽的文档和注释,以便后续开发人员能够理解和修改代码。 这些不足之处可以通过加强相关知识的学习和实践经验的积累来解决。同时,良好的团队合作和项目管理也是提高嵌入式软件开发质量的关键。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大橙子疯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值