硬汉嵌入式论坛

 找回密码
 立即注册
查看: 980|回复: 5
收起左侧

[技术讨论] 在单片机中使用C++编程实例片段

[复制链接]

27

主题

95

回帖

181

积分

初级会员

积分
181
发表于 2022-3-3 15:49:02 | 显示全部楼层 |阅读模式
本帖最后由 chinacool 于 2022-3-3 16:45 编辑


以前单片机中写程序,基本都是C语言开发,包括底层驱动和上层应用程序。诚然,用C完全能够完成我的项目,何况人家LINUX那
么大的系统内核代码也基本都是C开发的。

但是当系统软件规模上去后,务必要对整个系统各个模块要进行划分,同时要深入嵌入面向对象开发的思想,否则越往后,软件将越来越
难以维护。另外,在开发软件过程中,经常会遇到一个情况:由于成本或部件停产等等基他原因,原来的某个部件要换型号或厂家,
如果软件没有很好的做到接口与实现分离,后期的改动将很痛苦。而“接口与实现分离”是面向对象编程的基本原则。

再则,C++提供了STL库,函数丰富,同时支持各种高级的搞法,如类,多态,模板等等,这些都可以通过C语言模拟出来,但最后会发现那指针指得头发晕,
如果用C++写可以让代码写得更加优雅和轻松。

废话不多说了,来个小demo,

场景:我原来机器上用了一个柱塞泵A,后来因需要又要换一个厂家的柱塞泵B,接口协议啥的都不一样。
解决思路:将不同厂家柱塞泵相同的东西抽离出来,设计成基类,将不同部分设计成派生类。柱塞泵都有一些相同的动作,比如:吸液,排液,滴定控制,停止等等

// 定义注塞泵/注射泵父类结构
class pistonpump_base
{
protected:
    uint8_t isManual; //0=自动,1=手动
    uint8_t stopflag; //0=无动作,1=急停
    int16_t motor_steps;  //滴定时电机步进数
    uint32_t titrate_num;  //滴定数
public:
    explicit pistonpump_base(uint8_t mode, uint8_t flag, int16_t steps, uint32_t num)
                            : isManual(mode), stopflag(flag), motor_steps(steps), titrate_num(num)
                            {}

    virtual ~pistonpump_base(){};

    .....

    virtual int drainEmpty(void)=0; //排空
    virtual int suction(void)=0; //吸满
    virtual int titrate(void)=0; //滴定
    virtual int stop(void)=0; //停止
    virtual int get_position(int*)=0; //查询活塞位置
    virtual int valve_ctrl(Piston_Valve_Ctrl_t )=0; //阀控制 0=关闭,1=打开
};



柱塞泵A:继承自柱塞泵基类。
class keyto_pump : public pistonpump_base
{
public:
    keyto_pump();
    ~keyto_pump(){}
    virtual int drainEmpty(void) override; //排空
    virtual int suction(void) override; //吸满
    virtual int titrate(void) override; //滴定
    virtual int stop(void) override; //停止
    virtual int get_position(int*) override; //查询活塞位置
    virtual int valve_ctrl(Piston_Valve_Ctrl_t ) override; //阀控制 0=关闭,1=打开
};



在派生类中去实现具体的应用接口。

某一天,公司说要再增加一款柱塞泵,或者更换一款泵,这时就可以从容的再加一点代码,

#include "pistonpump.hpp"
/////////////////////////////////////////////////////////////////////////////////////////

class runze_sy08 : public pistonpump_base
{
public:
    runze_sy08();
    ~runze_sy08(){}
    virtual int drainEmpty(void) override; //排空
    virtual int suction(void) override; //吸满
    virtual int titrate(void) override; //滴定
    virtual int stop(void) override; //停止
    virtual int get_position(int*)override; //查询活塞位置
    virtual int valve_ctrl(Piston_Valve_Ctrl_t ) override; //阀控制 0=关闭,1=打开
};





应用程序基本不用作任何修改,因为应用程序调用的是基类的接口。至于下面倒底是谁在实现跟本不用管,通常我需要添加两个相应的文件.cpp,  .h就行了。

//手动操作: 排空/吸液/滴定
            switch(act.step)
            {
                case STEP_DRAIN: //柱塞泵排液
                    res = pistonpump->drainEmpty();
                    break;
                case STEP_SUCTION: //柱塞泵吸液
                    res = pistonpump->suction();
                    break;
                case STEP_TITRATE: //柱塞泵滴定
                    res = pistonpump->titrate();
                    break;
                default:
                    break;
            }



如果再要增加其他柱塞泵,那就再接着加好了,心中一点不慌。这样就实现了接口与实现的完全分离。而实现这样效果的就是C++中的多态性。

用C语言也是可以 实现多态性的,操作起来C++效果差不多,但在应用 程序中理解起来就比较废劲了,因为指针用的很多,最开始我用这种方法实现,

后来我自己也受不了了,就全部改成C++实现了:

这里要补充说明一下,C++实现多态的核心是虚函数,当类中有虚函数声明时,编译器会给类创建一个虚表vtab,这个虚表中装着基类或派生类的虚函数地址,
在创建类对象时,编译器会给对象内存中安插一个指针vptr, 这个指针指向vtab.  用C模拟C++多态也就要手动做一个类似的虚表。

看另外一个C实现的例子:

.h 文件.....................................
struct potentio_VTable; //虚表前置声明

// 定义父类结构
typedef struct potentio_s
{
  int Pot_addr;
  int Pot_ref_reg;
  int Pot_ana_reg;
        
  struct potentio_VTable *vptr; // 虚表指针
        
}potentio_t;


// 父类中的虚表
struct potentio_VTable{
    HAL_StatusTypeDef (*para_set)(potentio_t*, Potentio_Reg_typedef, int, int);
};


.c 文件  .....................................

static HAL_StatusTypeDef _potentio_set_base(potentio_t *this, Potentio_Reg_typedef reg_type, int value, int wipe)
{
    printf("Pure virtual function is don't have instance\r\n");
        return HAL_OK;
}


void potentio_ctor(potentio_t *this)
{
    static struct potentio_VTable vtab = {
                _potentio_set_base,
        };

        this->vptr = &vtab;        

        this->Pot_addr = 0;
        this->Pot_ana_reg = 0;
        this->Pot_ref_reg = 0;
}

//外部调用
HAL_StatusTypeDef potentio_set(potentio_t *this, Potentio_Reg_typedef reg_type, int value, int wipe)
{
    return (this->vptr->para_set(this, reg_type, value, wipe));
}



派生类:

typedef struct //ad5423_s
{
        potentio_t parent;  //继承父类
        struct potentio_VTable *vptr;  //虚表指针
}ad5243_t;


//构造函数
void ad5423_ctor(ad5243_t * this)
{
    static struct potentio_VTable vtab = {
                _ad5423_set,
        };

    potentio_ctor(&this->parent); //构造父类对象

    this->parent.vptr = &vtab; //重点:将父类的虚表指针重映射指向派生类的虚表,以实现多态性。

        this->parent.Pot_addr = 0x5E;
        this->parent.Pot_ana_reg = 0x80;
        this->parent.Pot_ref_reg = 0x00;
}


==========================


//外部调用
HAL_StatusTypeDef potentio_set(potentio_t *this, Potentio_Reg_typedef reg_type, int value, int wipe)
{
    return (this->vptr->para_set(this, reg_type, value, wipe));
}


后面在应用 程序中调用 potentio_set()函数时,就可以根据传入的对像的实际值调用相应的设置函数。
由此,我们可以派生不同的器件,但应用程序不用关心,也不用修改代码。


但是像this->vptr->para_set() 这种操作 ,估计对代码不熟的人找出处都要找半天。一般的IDE也无法直接跳转到para_set()函数。


而这种操作,在LINUX内核中真的是随处可见,代码读起来真的费劲,跳来跳去,不用多久就晕头转向。


目前项目代码应用层的代码大量使用C++编写,维护的确是要方便不少。









































































评分

参与人数 1金币 +100 收起 理由
eric2013 + 100 很给力!

查看全部评分

回复

使用道具 举报

29

主题

231

回帖

318

积分

高级会员

积分
318
发表于 2022-3-3 16:01:35 | 显示全部楼层
优秀了
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106721
QQ
发表于 2022-3-4 00:49:56 | 显示全部楼层
谢谢楼主分享,
回复

使用道具 举报

2

主题

11

回帖

17

积分

新手上路

积分
17
发表于 2022-3-8 11:19:06 | 显示全部楼层
在单片机中使用c++,中断怎么改造?我的意思是,怎么将芯片原厂的库函数中断改写。
回复

使用道具 举报

0

主题

12

回帖

12

积分

新手上路

积分
12
发表于 2022-3-8 11:31:05 | 显示全部楼层
scarecrow 发表于 2022-3-8 11:19
在单片机中使用c++,中断怎么改造?我的意思是,怎么将芯片原厂的库函数中断改写。

可以考虑c和c++混编
回复

使用道具 举报

8

主题

26

回帖

55

积分

初级会员

积分
55
发表于 2022-4-6 20:39:04 | 显示全部楼层
用C++做项目的的我遇到的还是非常少的。觉着高级语言以后一定是大趋势。我也在C++上面爬了很多坑。但就MDK来说很多正确设计模式都是编译不了的。在中断中就别想着用什么STL了,最好还是用C。业务层用C++。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2024-5-1 05:30 , Processed in 0.193787 second(s), 29 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表