首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

一种可软硬件完全分离的软件设计方法

前言

在常规嵌入式软件开发流程中,通常需要经过需求分析、功能设计、编码实现、系统测试、发布维护等步骤,以实现产品的整个生命周期。但是,由于软硬件同时开工和意外情况的出现,往往会导致设计功能与实际功能之间的偏差。此外,嵌入式软件开发中特定功能依赖于硬件设计,对于小的功能变更,如数字量输入输出通道绑定、模拟量采集通道变更等,难以灵活调整。这给后续推出相同定位产品但配置不同的产品带来了额外的开支。为了解决这些问题,我们提出一种软硬件可完全分离的软件设计方法。该方法将设定目标功能作为对象进行抽象,并通过将对象进行实例化,在运行过程中动态改变程序配置,从而实现在同定位不同配置产品之间灵活切换固件的目的。这种方法不仅可以有效减少固件问题带来的开支,而且可以提高产品的灵活性和可维护性。

什么是对象?

在面向对象编程中,对象(Object)是指具有某些属性和方法的实体,它是程序的基本构成单位。我们通常使用类(Class)来定义对象的属性和方法。一个类可以看作是对象的模板或蓝图,它定义了对象的特征和行为。当我们创建一个对象时,实际上是创建了一个类的实例,它拥有类定义的属性和方法,我们通常使用对象来表示真实世界中的事物或概念,例如人、汽车、书籍等等。对象具有状态(属性)行为(方法)两个基本特征,它可以被实例化(创建)并被用于构建程序的不同部分。

以上是对对象的通俗解释,但上面提出了一个重要的点,即面向对象编程。众所周知,C语言并非一门面向对象的开发语言,那么我们又该如何使用C语言完成面向对象开发呢?

其实是有方法的,只不过C语言没有像其他高级语言那样预先定义对象而是需要自己实现,在C语言中,结构体(Struct)是一种用户自定义的数据类型,它允许我们定义自己的数据结构,包含多个不同类型的数据成员。结构体类型由关键字struct定义,后跟结构体名称和数据成员的名称和类型。例如,下面的代码定义了一个名为Person的结构体类型,包含两个整数类型的数据成员age和name:

struct Person {

int age;

bool gender;

char name[20];

};

定义结构体类型后,我们可以使用它来定义结构体变量,如下所示:

struct Person person1;

定义结构体变量后,我们可以通过赋值来初始化它的数据成员,例如:

person1.age = 20;

person1.bool = male;

strcpy(person1.name, "Alice");

我们也可以通过访问运算符[]来访问结构体变量的数据成员,例如:

printf("Person name: %s, age: %d\n", [person1.name](http://person1.name), person1.age);

在C语言中,结构体类型还支持多重继承(struct和类),结构体和指针,结构体指针和函数等多种用法。结构体在C语言中是一种非常强大的数据类型,可以方便地表示和操作复杂的数据结构。所以我们也可以将产品功能通过结构体的方式进行抽象定义。

创建对象

假设我们的产品是一款IO类控制设备,那么我们所需要设计的主要功能则主要是IO的控制与检测,将上述需求进行归类,其所有的功能都可以归类到IO的操作上来,那么我们就可以此为基础进行扩展,提炼出其中公共的部分。

#define MAX_PIN_ITEM 10

struct Object_pin{

bool mode;

bool state;

uint32_t pin;

void* port;

};

struct io_funt

{

void (*gpio_set)(void* port, uint32_t pin);

void (*gpio_reset)(void* port, uint32_t pin);

bool (*gpio_read)(void* port, uint32_t pin);

};

struct Object_IO

{

struct Object_pin io_list[MAX_PIN_ITEM];

struct io_funt IO_FUN;

void (*exetuct_fun)(struct Object_pin* pin);

void (*gpio_init)(struct Object_pin* pin);

};

struct Object_IO SYS_IO;

void test_exetuct_fun(struct Object_pin* pin)

{

if(pin->mode)

{

if(pin->state)

{

SYS_IO.IO_FUN.gpio_set(pin->port,pin->pin);

}

else

{

SYS_IO.IO_FUN.gpio_reset(pin->port,pin->pin);

}

}

else

{

pin->state = SYS_IO.IO_FUN.gpio_read(pin->port,pin->pin);

}

}

void your_gpio_set(void* port, uint32_t pin);

void your_gpio_reset(void* port, uint32_t pin);

bool your_gpio_read(void* port, uint32_t pin);

void your_sys_gpio_init_function(struct Object_pin* pin);

void system_io_init(void)

{

SYS_IO.IO_FUN.gpio_set = your_gpio_set;

SYS_IO.IO_FUN.gpio_reset = your_gpio_reset;

SYS_IO.IO_FUN.gpio_read = your_gpio_read;

SYS_IO.gpio_init = your_sys_gpio_init_function;

SYS_IO.exetuct_fun = test_exetuct_fun;

for(uint8_t ii = 0; ii izeof(SYS_IO.io_list[0]); ii++)

{

SYS_IO.gpio_init(&SYS_IO.io_list[ii]);

}

}

void system_io_handler(void)

{

for(uint8_t ii = 0; ii izeof(SYS_IO.io_list[0]); ii++)

{

SYS_IO.exetuct_fun(&SYS_IO.io_list[ii]);

}

}

在上面的代码片段中我们使用struct Object_pin结构体对单个IO进行定义,使用struct Object_IO结构体完成了对整个IO列表以及对应执行函数接口的定义, 同时定义了系统初始化函数和执行器函数,注意代码中并未编写实际的执行函数,需要由实际用户去实现定义。有细心的朋友可能发现了,在例子中,成员个数全部由MAX_PIN_ITEM宏定义,很明显这并不能解决在程序中动态修改IO数量的问题,所以我们需要对程序进行修改,如下代码:

struct Object_pin{

bool mode;

bool state;

uint8_t pin;

uint8_t port;

};

struct Object_IO

{

uint8_t li_cnt;

struct Object_pin *io_list;

struct io_funt IO_FUN;

void (*exetuct_fun)(struct Object_pin* pin);

void (*gpio_init)(struct Object_pin* pin);

};

struct Object_IO SYS_IO;

在上面代码中,我们将结构体成员io_list由数组对象修改为指针对象,同时修改了Object_pin结构体类型的成员变量,这就导致io_list所存储的并非直接是一个个具体的PIN结构体对象,而是一片未知的空间,所以我们应该如何使用这个未知指针对象呢?这就需要提供系统解析器以及对应的配置文件了,通过解析器对配置文件进行解析生成PIN结构体对象,将io_list指向新生成的对象从而达到动态配置的目的。

配置文件

根据结构体中的io_list对象类型可得知,该指针指向struct Object_pin类型的地址,所以我们仅需要根据Object_pin结构体类型来创建对应的结构体列表即可,首先查看struct Object_pin的结构体成员类型,发现可以将其抽象为键值对的方式进行存储,这样即可选择json格式对配置进行标识,通过json格式能够轻松的标识Object_pin对象的内容,同时json提供了数组类型可以明确表达多个对象,可以选择将文件存储在设备自身存储空间或外部空间中,使用时将文件加载到内存中进行解析。

[{

"mode" : 1,

"state": 0,

"pin" : 0,

"port" : 0

},

{

"mode" : 1,

"state": 0,

"pin" : 1,

"port" : 0

},{

"mode" : 0,

"state": 0,

"pin" : 0,

"port" : 1

},

{

"mode" : 0,

"state": 0,

"pin" : 1,

"port" : 1

}]

解析器

配置文件确定选用json格式后能够选择的解析库就很多了,可以选择著名的cjson解析库或其他解析库也可以自己编写相应的解析库。此处我们选择自有软件库完成解析任务,主要用到的接口为

jsonObj* jsonParse(char* str);

int getJsonObjInteger(jsonObj* obj, const char* key);

导入软件库后创建jsonObj类型的变量用于存储解析后的成员变量 _root,使用jsonParse函数对整个json格式字符串进行解析,并将其存储在_root变量中,使用getJsonObjInteger函数从_root变量中获取我们想要的成员即可。通过对解析后的变量进行存储并赋值给SYS_IO中的io_list指针,同时将解析到的数量赋予SYS_IO中的li_cnt成员用以标记当前指针地址中所存储的对象数量,通过以上方式即完成了动态的加载IO配置。

结语

本文介绍了一种基于对象的软硬件可完全分离的软件设计方法,以解决嵌入式软件开发中的一些问题。该方法将系统中的各个模块抽象成对象,并通过对象的实例化来动态改变程序配置。文章详细解释了对象的概念和定义,以及如何使用C语言进行面向对象编程。另外,文章还介绍了系统中IO操作的功能抽象和定义,并提出了一种动态加载IO配置的方法,通过配置文件和解析器实现了IO的灵活切换。该方法可以减少嵌入式软件开发中的固件问题带来的开支,提高产品的灵活性和可维护性。文章最后给出了相关代码示例和配置文件的格式。

特点

(1)软硬件独立

·软件设计独立于硬件。

·可以更换硬件而不需要重新设计软件。

(2)高度可维护

·独立的软硬件设计使得软件维护变得更加容易。

·可以根据硬件更新进行软件升级而不影响现有的软件系统。

(3)提高生产效率

·可以通过可维护的软件设计方法来降低软件开发成本。

·通过可软硬件完全分离的设计方法,可以大大提高软件生产效率。

设计方法

(1)基于对象的设计方法

·将系统中的各个模块抽象成对象,以实现对系统的可维护性和可扩展性。

·基于面向对象的设计原则,使得软件设计更加模块化、可复用、可扩展。

(2)硬件设计

·根据系统需求,进行硬件的选择和设计。

·确保硬件的性能、功能和接口与软件设计要求相匹配。

·保证硬件的可扩展性,以适应未来系统需求的变化。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OcqAJjahIvjEZh-6V2agxm-g0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券