keaibukelian 发表于 2025-6-14 10:42

单片机中面向对象的思维

前言:

      面向对象的编程方式用在C语言中,听起来是不是很怪。从C语言入门开始,老师就和你说过,C语言是面向过程的,你现在却要用它来实现面向对象操作。可能是太久没谈对象了,想要new一个对象出来解闷。开玩笑的,面向对象自然有面向对象的好处,C语言是面向过程的语言,他的小弟C++是半面向对象,半面向过程的。所以我们使用C语言类比C++的方式实现面向对象的操作。面向对象三大考点,封装,继承,多态。有的时候我们在操作的时候会面临重复的操作,你有想到过面向对象的特点嘛,考虑面向对象的特点是不是就可以避免重复的操作。

一、面向对象特点与面向过程的类比

      面向过程的代码就是便于直观的查看整体流程,而面向对象的则需要你能够将你所需要的部分抽象出来,但是不管是整么个方式,程序猿最重要的还是代码能跑就行,不然就得你,我的朋友跑了。C++中对象class(类)包含的的成员属性,成员函数,C语言是没有这个叫法的,但是我们可以通过struct结构体成员类比出来,通过void*万能指针实现成员函数的调用。

二、总结:你就得有抽象思维,只要够抽象就不怕找不到对象。

三、接下来我以按键为例简单展示一下单片机中的面向对象思维。

3.1 先简单说一下按键功能:

      我们通常在单片机中用按键作为外部触发事件,按下按键,实现一段操作,松开按键实现另外的操作,每次按键的触发都会存在 “消抖” 问题,每次消抖都是在重复一样的操作,这个时候我就想到了,既然是重复的操作,那干嘛写那么多重复的函数呢,所以我就想到了面向对象的多态。只要创建对象,改写一下他的成员函数就可以了。下面是我原先按键的代码(面向过程的方式消抖)。

/*****************************************************************************
函 数 名: KEY1EintProc
功能描述: KEY1 消抖延时函数,双边沿中断触发中断后开启20ms定时任务
输入参数: u8 power
返 回 值: void
作者    : Bright
创建日期 : 20250607
*****************************************************************************/
void KEY1EintProc(void)
{
    u8 mkeyStatu = KEY_VALUE_RELEASED;
    mkeyStatu=KEY1_STATUS_READ;
    if((mkeyStatu == KEY_VALUE_PRESSED)&&(KEY_VALUE_RELEASED == KEY1LastState))
    {
         DelayMs(20);
      mkeyStatu=KEY1_STATUS_READ;
      if(KEY_VALUE_PRESSED==mkeyStatu)//KEY1 按下
      {
            KEY1LastState=mkeyStatu;
            KEY1_NOTICE_OUT_EN_0;//按键1被拉低后,拉高对应引脚通知安卓端
      }
    }else if((mkeyStatu == KEY_VALUE_RELEASED)&&( KEY_VALUE_PRESSED== KEY1LastState))//松开
    {
         DelayMs(20);
      mkeyStatu=KEY1_STATUS_READ;
      if(KEY_VALUE_RELEASED==mkeyStatu)//KEY1释放
      {
            KEY1LastState=mkeyStatu;
            KEY1_NOTICE_OUT_EN_1;//按键1被拉高后,拉高对应引脚通知安卓端
      }
    }
}
/*****************************************************************************
函 数 名: KEY2EintProc
功能描述: KEY2 消抖延时函数,双边沿中断触发中断后开启20ms定时任务
输入参数: u8 power
返 回 值: void
作者    : Bright
创建日期 : 20250607
*****************************************************************************/
void KEY2EintProc(void)
{
    u8 mkeyStatu = KEY_VALUE_RELEASED;
    mkeyStatu=KEY2_STATUS_READ;
    if((mkeyStatu == KEY_VALUE_PRESSED)&&(KEY_VALUE_RELEASED == KEY2LastState))
    {
         DelayMs(20);
      mkeyStatu=KEY2_STATUS_READ;
      if(KEY_VALUE_PRESSED==mkeyStatu)//KEY2按下
      {
            KEY2LastState=mkeyStatu;
            KEY2_NOTICE_OUT_0;//拉低
      }
    }else if((mkeyStatu == KEY_VALUE_RELEASED)&&( KEY_VALUE_PRESSED== KEY2LastState))//松开
    {
         DelayMs(20);
      mkeyStatu=KEY2_STATUS_READ;
      if(KEY_VALUE_RELEASED==mkeyStatu)
      {
            KEY2LastState=mkeyStatu;
            KEY2_NOTICE_OUT_1;//拉高
      }
    }
}
/*****************************************************************************
函 数 名: KEY3EintProc
功能描述: PTT 消抖延时函数,双边沿中断触发中断后开启20ms定时任务
输入参数: u8 power
返 回 值: void
作者    : Bright
创建日期 : 20250607
*****************************************************************************/
void KEY3EintProc(void)
{
    u8 mkeyStatu = KEY_VALUE_RELEASED;
    mkeyStatu=KEY3_STATUS_READ;
    if((mkeyStatu == KEY_VALUE_PRESSED)&&(KEY_VALUE_RELEASED == KEY3LastState))
    {
         DelayMs(20);
      mkeyStatu=KEY3_STATUS_READ;
      if(KEY_VALUE_PRESSED==mkeyStatu)//KEY3 按下
      {
            KEY3LastState=mkeyStatu;
             KEY3_NOTICE_OUT_0;//拉低,按下KEY3后对应操作
      }
    }else if((mkeyStatu == KEY_VALUE_RELEASED)&&( KEY_VALUE_PRESSED== KEY3LastState))//松开
    {
         DelayMs(20);
      mkeyStatu=KEY3_STATUS_READ;
      if(KEY_VALUE_RELEASED==mkeyStatu)//KEY3 松开相对操作
      {
            KEY3LastState=mkeyStatu;
            KEY3_NOTICE_OUT_1;//拉高
      }
    }
}
/*****************************************************************************
函 数 名:KEY4EintProc
功能描述: key4 消抖延时函数,双边沿中断触发中断后开启20ms定时任务
输入参数: u8 power
返 回 值: void
作者    : Bright
创建日期 : 20250607
*****************************************************************************/
void KEY4EintProc(void)
{
    u8 mkeyStatu = KEY_VALUE_RELEASED;
    mkeyStatu=KEY4_STATUS_READ;
    if((mkeyStatu == KEY_VALUE_PRESSED)&&(KEY_VALUE_RELEASED ==KEY4LastState))
    {
         DelayMs(20);
      mkeyStatu=KEY4_STATUS_READ;
      if(KEY_VALUE_PRESSED==mkeyStatu)//
      {
         KEY4LastState=mkeyStatu;
         KEY4_OUT_0;//拉低
      }
    }else if((mkeyStatu == KEY_VALUE_RELEASED)&&( KEY_VALUE_PRESSED==KEY4LastState))//松开
    {
         DelayMs(20);
      mkeyStatu=KEY4_STATUS_READ;
      if(KEY_VALUE_RELEASED==mkeyStatu)//
      {
         KEY4LastState=mkeyStatu;
         KEY4_OUT_1;//拉高
      }
    }
}


      可以看出在我消抖后的函数中一直在重复一样的内容,比较繁琐,但简单。

3.2 面向对象的操作,就需要我们将它的不变的内容抽象出来形成统一的模板。然后只需改变成员属性就可以了。下面是面向对象的操作方式。

3.2.1首先是整体对象属性

// 按键设备结构体
typedef struct {
    uint8_t (*read)(void);      // 读取状态函数指针
    void (*setState)(uint8_t);// 读取状态后操作函数
    uint8_t lastState;          // 上次状态
} KEY_Device;


3.2.2建立对象,以及初始化对象函数

//将消抖函数抽象出来
KEY_Device KEY1;
KEY_Device KEY2;
KEY_Device KEY3;
KEY_Device KEY4;
// 初始化PTT设备
void KEY_Device_init(KEY_Device* dev,
                   uint8_t (*readFunc)(void),
                   void (*setStateFunc)(uint8_t)) {
    dev->read = readFunc;
    dev->setState = setStateFunc;
    dev->lastState = KEY_VALUE_RELEASED;
}


3.2.3设备对象初始化

/*****************************************************************************
函 数 名: KEY_DevicesInit
功能描述: 输入设备面向对象初始化
输入参数: u8 power
返 回 值: void
作者    : Bright
创建日期 : 20250607
*****************************************************************************/
void KEY_DevicesInit(void)
{
    // 初始化KEY1
    KEY1.read = readKEY1;
    KEY1.setState = KEY1PressFunc;
    KEY1.lastState = KEY_VALUE_RELEASED;

    // 初始化KEY2
    KEY2.read = readKEY2;
    KEY2.setState = KEY2PressFunc;
    KEY2.lastState = KEY_VALUE_RELEASED;

    // 初始化KEY3
    KEY3.read = readKEY3;
    KEY3.setState = KEY3PressFunc;
    KEY3.lastState = KEY_VALUE_RELEASED;

    // 初始化KEY4
    KEY4.read = readKEY4;
    KEY4.setState = KEY4Func;
    KEY4.lastState = KEY_VALUE_RELEASED;
}


3.2.4 抽象出来的通用部分

// 处理KEY事件的通用方法
/*****************************************************************************
函 数 名: KEY_Device_process
功能描述: 消抖函数
输入参数: u8 power
返 回 值: void
作者    : Bright
创建日期 : 20250607
*****************************************************************************/
uint8_t KEY_Device_process(KEY_Device* dev) {
    uint8_t currentState = dev->read();

    // 按下检测(上升沿)
    if ((currentState == KEY_VALUE_PRESSED) && (dev->lastState == KEY_VALUE_RELEASED)) {
      DelayMs(20);// 防抖延时
      if (dev->read() == KEY_VALUE_PRESSED) {
            dev->lastState = KEY_VALUE_PRESSED;
            dev->setState(KEY_VALUE_PRESSED);
            return 1;// 返回按下事件
      }
    }
    // 释放检测(下降沿)
    else if ((currentState == KEY_VALUE_RELEASED) && (dev->lastState == KEY_VALUE_PRESSED)) {
      DelayMs(20);// 防抖延时
      if (dev->read() == KEY_VALUE_RELEASED) {
            dev->lastState = KEY_VALUE_RELEASED;
            dev->setState(KEY_VALUE_RELEASED);
            return 2;// 返回释放事件
      }
    }
    return 0;// 无状态变化
}


3.2.5 各自的按键操作函数

/*****************************************************************************
函 数 名: readKEY1
功能描述: 读取引脚函数
输入参数: u8 power
返 回 值: void
作者    : Bright
创建日期 : 20250607
*****************************************************************************/
uint8_t readKEY1(void)
{
    return KEY1_STATUS_READ;
}
/*****************************************************************************
函 数 名: KEY1PressFunc
功能描述: 读取按键后操作
输入参数: state:高低电平
返 回 值: void
作者    : Bright
创建日期 : 20250607
*****************************************************************************/
void KEY1PressFunc(uint8_t state)
{
    if(state)//KEY1松开后
    {
       KEY1_NOTICE_OUT_EN_1;//通知安卓松开KEY1

    }else{//KEY1 按下
       KEY1_NOTICE_OUT_EN_1; //通知安卓按下KEY1
               
    }
}
/*****************************************************************************
函 数 名: readKEY2
功能描述: 读取ptt2按键
输入参数: u8 power
返 回 值: void
作者    : Bright
创建日期 : 20250607
*****************************************************************************/
// DMR PTT2 相关函数
uint8_t readKEY2(void)
{
    return KEY2_STATUS_READ;
}
/*****************************************************************************
函 数 名: KEY_Device_process
功能描述: 读到状态后操作
输入参数: state:高低电平
返 回 值: void
作者    : Bright
创建日期 : 20250607
*****************************************************************************/
void KEY2PressFunc(uint8_t state)
{
    if(state)//Ptt1松开后
    {
       KEY2_NOTICE_OUT_EN_1;//通知安卓松开KEY2
       //PA4 DMR 停止发射
    }else{//ptt2 按下
       KEY2_NOTICE_OUT_EN_0; //通知安卓按下KEY2
       //PA4 发射
    }
}
/*****************************************************************************
函 数 名: readKEY3
功能描述: 读取M6状态
输入参数: void
返 回 值: void
作者    : Bright
创建日期 : 20250607
*****************************************************************************/
// M6 PTT 相关函数
uint8_t readKEY3(void)
{   
    return KEY3_STATUS_READ;
}
/*****************************************************************************
函 数 名: KEY3PressFunc
功能描述: 消抖函数
输入参数: u8 power
返 回 值: void
作者    : Bright
创建日期 : 20250607
*****************************************************************************/
void KEY3PressFunc(uint8_t state)
{
    if(state)//KYE3松开后
    {
      KEY3_NOTICE_OUT_1;
       //
    }else{//KYE3 按下
      KEY3_NOTICE_OUT_0;
       //PA4 发射
    }   
}
/*****************************************************************************
函 数 名: KEY_Device_process
功能描述: 消抖函数
输入参数: u8 power
返 回 值: void
作者    : Bright
创建日期 : 20250607
*****************************************************************************/
// KEY4 相关函数
uint8_t readKEY4(void)
{
    return KEY4_STATUS_READ;
}
/*****************************************************************************
函 数 名: KEY_Device_process
功能描述: 消抖函数
输入参数: u8 power
返 回 值: void
作者    : Bright
创建日期 : 20250607
*****************************************************************************/
void KEY4Func(uint8_t state)
{
    //KEY4按下后对应操作
}
/*****************************************************************************
函 数 名: KEY_Device_process
功能描述: 消抖函数
输入参数: u8 power
返 回 值: void
作者    : Bright
创建日期 : 20250607
*****************************************************************************/
// 主处理函数


3.2.6 主函数

/*****************************************************************************
函 数 名: KEY_Device_process
功能描述: 消抖函数
输入参数: u8 power
返 回 值: void
作者    : Bright
创建日期 : 20250607
*****************************************************************************/
// 主处理函数
uint8_t ExtiProc(uint8_t pttPort)
{
    // 使用switch处理不同端口
    switch(pttPort) {
      case KEY1_Port:
            return KEY_Device_process(&KEY1);

      case KEY2_Port:
            return KEY_Device_process(&KEY2);

      case KEY3_Port:
            return KEY_Device_process(&KEY3);

      case KEY4Statu_Port:
            return KEY_Device_process(&KEY4);

      default:
            return 0;// 未知端口
    }
}
            return KEY_Device_process(&KEY3);

      case KEY4Statu_Port:
            return KEY_Device_process(&KEY4);

      default:
            return 0;// 未知端口
    }
}


四、总体上只要找到找到函数的特点,就能够利用面向对象的思想来实现。面向对象的方式只要将抽象部分写完,后续只需要新建对象,写各自的函数就可以了,整体上看起来比较清爽。
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/apple_71040140/article/details/148606040

mattlincoln 发表于 2025-7-2 12:24

通过抽象接口隐藏实现细节,修改内部逻辑不影响外部调用

beacherblack 发表于 2025-7-2 13:23

直接移植 C++ 等 OOP 语言的编译器 会消耗大量资源
页: [1]
查看完整版本: 单片机中面向对象的思维