工程实践中的软件科学
从软件系统分析和设计到软件系统概念原型
——基于工程实践:智能物联结点的设计与实现
一、概述
本文主要是针对智能物联结点的设计与实现这一工程实践,对其进行软件系统分析和设计到总结出软件系统概念原型。阐述项目中所蕴含的软件结构特点,比如基本结构和特殊机制、设计模式、软件架构风格和策略等,并给出数据库核心设计的分析以及源代码的文件目录结构。ESP-WROVER-KIT是一款基于ESP32无线网络和蓝牙/蓝牙低能耗(BLE)片上系统(SoC)的全功能开发板。该开发板兼容基于Espressif系统的双核无线双模蓝牙模块,包括 ESP-WROOM-32 和 ESP32-WROVER。
二、软件的基本结构和特殊机制
基本结构包括五种:顺序结构、分支结构、循环结构、函数调用框架以及继承和对象组合。前三种最基本的结构是实现大项目必须的,在ESP32的开发板的各个硬件设备实现中,均用到了函数调用:GPIO引脚函数中调用高低电平设置函数、看门狗调用一系列操作TWDT的函数等。继承和对象组合是类与类之间的两种关系,在软件设计中,考虑到对象间的依赖程度和耦合程度,对象组合下的两个类表现为松散耦合。继承可以重用代码,但是会破坏了代码的封装特性,增加了父类与子类之间的代码模块耦合。所以,避免使用继承,在每个ESP32的硬件设计中,多半采用对象组合来实现目标,比如,在实现ESP32的VFS中,记录了文件路径与文件系统之间的对应关系的 vfs_entry_t 结构与文件系统结构的映射关系就是对象组合模式。
同继承、对象组合同样关键的概念还有一个多态,多态是一种比较特殊的机制。
1 const esp_timer_create_args_t periodic_timer_args = { 2 .callback = &periodic_timer_callback, 3 // 设置回调函数 4 .name = "periodic" // 定时器名字 5 }; 6 // 定义一个单次运行的定时器结构体 7 const esp_timer_create_args_t oneshot_timer_args = { 8 .callback = &oneshot_timer_callback, 9 // 设置回调函数 10 /* 这里指定的参数将被传递给定时器回调函数 */ 11 .arg = (void*) periodic_timer, 12 .name = "one-shot" // 定时器名字 13 };
另外,还有回调函数,如上代码。回调函数是一个面向过程的概念,通过函数指针调用的函数。把函数指针地址作为参数传递给另一个函数。还有的特殊机制包括:闭包、异步调用以及匿名函数。这里闭包找到的是同一地址下的父级函数中对应变量最终的值。此工程实践中,在Timer定时器设计中,运用了大量的回调函数,作用就是通过设置等待时间,在到达等待时间之后执行指定的硬件操作。
二、接口API
这里分析的API接口是基于ESP-WROVER-KIT开发板中各个硬件设备的独立API。
2.1 GPIO设备
通用输入/输出口(引脚),通过这些引脚输出高低电平或者读入引脚的状态。列出部分API函数名和功能:
1 esp_err_tgpio_config//GPIO通用配置 2 esp_err_t gpio_reset_pin//将GPIO重置为默认状态 3 esp_err_t gpio_set_intr_type//设置GPIO中断触发类型 4 esp_err_t gpio_intr_enable//使能GPIO模块中断信号 5 esp_err_t gpio_set_direction//配置GPIO的方向 6 esp_err_t gpio_set_level//设置GPIO的输出电平
2.2 UART设备
通用异步收发传输器。列出部分API函数名和功能:
1 esp_err_t uart_intr_config//配置UART中断 2 esp_err_t uart_set_word_length//设置UART数据位 3 esp_err_t uart_set_parity//设置UART奇偶校验模式 4 esp_err_t uart_set_stop_bits//设置UART停止位 5 esp_err_t uart_set_mode//UART设置通讯模式 6 esp_err_t uart_driver_install//安装UART驱动程序 7 int uart_write_bytes//从给定的缓冲区和长度将数据发送到UART端口 8 int uart_read_bytes//UART从UART缓冲区读取字节 9 int uart_tx_chars//从给定的缓冲区和长度将数据发送到UART端口
2.3 LEDC设备
全彩LED灯。给出其API接口的示意图:
2.4 Timer
通过设置等待时间,在到达等待时间之后执行指定的硬件操作的部件。列出部分API函数名和功能:
1 esp_err_t esp_timer_init//初始化esp_timer库 2 esp_err_t esp_timer_create//创建一个esp_timer实例esp_err_t esp_timer_start_once//启动一个单次计时器 3 esp_err_t esp_timer_start_periodic//启动一个周期计时器 4 esp_err_t esp_timer_stop//停止计时器 5 esp_err_t esp_timer_delete//删除esp_timer实例
2.5 WatchDog
可以在一定时间内被复位的计数器。列出部分API函数名和功能:
1 esp_err_t esp_task_wdt_init//配置并初始化TWDT 2 esp_err_t esp_task_wdt_deinit//取消初始化任务WD定时器 3 esp_err_t esp_task_wdt_add//将任务订阅到任务WD定时器 4 esp_err_t esp_task_wdt_delete//从任务WD计时器退订任务
第三次作业已经列出PCNT、RMT、ADC和DAC的API函数介绍,此处略去。
三、设计模式及原则
设计模式是一套总结,对于大型的软件工程项目,设计模式可以增加代码的可重用性以及可维护性,让代码更容易被理解,使代码真正工程化。根据设计模式可以完成的任务类型,可以把设计模式分为创建型模式(怎样创建对象)、结构型模式(如何将类或对象按照某种布局组成更大的结构)和行为型模式(程序在运行时复杂的流程控制)三种类型。由于此工程实践是嵌入式层面设计,更多的是面向过程的C语言实现,所以面向对象的设计模式基本没有实现,也无法提供相应实例进行说明。但运用了一些设计模式的思想,比如建造者模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别构建他们,最后构建成该复杂对象。
设计原则,“SOLID”:开闭原则、Liskov替换原则、依赖倒置原则、单一职责原则、迪米特法则。这里更多的体现的是Single Responsibility Principle,其可以降低类的复杂度,因为一个类只负责一项职责,其逻辑肯定要比负责多职责简单得多;同时也可以提高类的内聚度。比如在ESP32中的VFS里,分别对应文件路径、文件系统以及文件路径和文件系统之间的对应关系这三个类。其下是这些数据结构之间的关系:
四、软件架构
4.1 软件架构设计
首先,我们常见的软件架构有三种:第一种,三层架构:层次化架构是利用面向接口编程的原则将层次化的结构型设计模式作为软件的主体结构。在嵌入式开发板的设计中,更多的是这种界面层——业务逻辑层——数据访问层的三层架构模式。第二种,MVC即为Model-View-Controller(模型-视图-控制器),其是一种涉及模式。M代表一个存取数据对象以及数据模型、V代表模型包含的数据的表达方式、C作用于模型和视图上,控制数据流向模型对象,并在数据变化时更新视图。控制器可以使视图与模型分离开解耦合,是模型和视图的桥梁。第三种,MVVM即为Model-View-ViewModel,和MVC模式一样,只要目的是分离视图和模型,有着以下一些优点:低耦合、可重用性、独立开发和可测试性。
其次,构建软件架构模型的基本方法就是在不同层次上分解系统并抽象出其中的关键要素。常见的分解方法按照处理的事务类型来分类:功能、特征、数据、并发、事件和对象。这些软件架构风、层次分解方法和以上的设计模式没有明显的优劣之分,都有自己的适用领域。因此,需要分析采取最适合当前项目的软件架构风格、分解方法和设计模式,以使项目得到最大化的收益。
最后,需要描述关键要素之间的关系,软件设计里通过某些关键视图来实现这一过程,即软件架构的描述方法。为了理解软件架构中的关键要素所表现出来的特征,我们先来看一下软件架构的风格和策略,然后再逐一分析软件架构的视图。
4.2 软件架构的风格
本项目是基于开发板的嵌入式开发,所以存在着较为复杂的软件体系层次,仅仅从平面展开的角度进行模块化分解是不够的,还需要从垂直纵深的角度讲软件单元按层次化组织,类似与OSI的七层模型,从用户交互层到最后的硬件物理层。在ESP32的HTTP协议中,运用了客户-服务风格的思想,它通常运行在TCP之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。请求和响应消息的头以ASCII码形式给出;而消息内容则具有一个类似MIME的格式。以下是客户端和服务端的运行结果,可以看到在客户-服务模式中,客户是主动的,服务是被动的。客户知道它像哪个服务发出请求,而服务却不知道他正在为哪个客户提供服务,甚至不知道正在为多少客户提供服务。此种设计,降低了系统中客户和服务构件之间耦合度,提高了服务构件的可重用性。
同属于软件架构风格的还有:管道-过滤器、P2P、发布-订阅以及CRUD。
4.3 软件架构的描述方法
软件架构模型是通过一组关键视图来描述的,同一个软件架构,由于选取的角度和抽象层次不同可以得到不同的视图。一般来说,我们常说的几种视图有分解视图、依赖视图、泛化视图、执行视图、实现视图、部署视图和工作任务分配视图。
4.3.1 分解视图
分解是构建软件架构模型的关键步骤,分解视图也是描述软件架构模型的关键视图,一般分解视图呈现为较为明晰的分解结构特点。分解视图用软件模块勾划出系统结构,往往会通过不同抽象层级的软件模块形成层次化的结构。软件架构代表了软件系统的整体设计结构,它应该是所有这些视图的集合。但我们不会将不同角度的这些视图整合起来,因为不便于阅读和更新。不过我们会有意识地将不同角度的视图之间的映射关系和重叠部分了然于胸,从而深刻理解软件架构内在的一致性和完整性,这就是系统概念原型。根据对开发板硬件模块的分类可以做出如下分解视图:
4.3.2 依赖视图
依赖视图展现了软件模块之间的依赖关系。比如一个软件模块A调用了另一个软件模块B,那么我们说软件模块A直接依赖软件模块B。如果一个软件模块依赖另一个软件模块产生的数据,那么这两个软件模块也具有一定的依赖关系。它能帮助我们找到没有依赖关系的软件模块或子系统,以便独立开发和测试,同时进一步根据依赖关系确定开发和测试软件模块的先后次序。
4.3.3 泛化视图
泛化视图展现了软件模块之间的一般化或具体化的关系,典型的例子就是面向对象分析和设计方法中类之间的继承关系。值得注意的是,采用对象组合替代继承关系,并不会改变类之间的泛化特征。因此泛化是指软件模块之间的一般化或具体化的关系,不能局限于继承概念的应用。
泛化视图有助于描述软件的抽象层次,从而便于软件的扩展和维护。比如通过对象组合或继承很容易形成新的软件模块与原有的软件架构兼容。
4.3.4 执行视图
执行视图展示了系统运行时的时序结构特点,比如流程图、时序图等。执行视图中的每一个执行实体,一般称为组件(Component),都是不同于其他组件的执行实体。如果有相同或相似的执行实体那么就把它们合并成一个。执行实体可以最终分解到软件的基本元素和软件的基本结构,因而与软件代码具有比较直接的映射关系。在设计与实现过程中,我们一般将执行视图转换为伪代码之后,再进一步转换为实现代码。
抽取部分硬件模块内的流程图:
UART的源码流程图:
DAC的例程流程图:
Timer例程流程图:
WatchDog例程流程图:
4.3.5 实现视图
实现视图是描述软件架构与源文件之间的映射关系。比如软件架构的静态结构以包图或设计类图的方式来描述,一般我们通过目录和源文件的命名来对应软件架构中的包、类等静态结构单元,这样典型的实现视图就可以由软件项目的源文件目录树来呈现。实现视图有助于码农在海量源代码文件中找到具体的某个软件单元的实现。实现视图与软件架构的静态结构之间映射关系越是对应的一致性高,越有利于软件的维护,因此实现视图是一种非常关键的架构视图。
本项目的代码按照不同的硬件模块设备进行编写,不同模块间没有具体的联系,同一模块内通过C语言面向过程的特点实现,只存在一个主void函数。所以,这里我将代码的实现视图转化为不同硬件的实现视图来理解:
4.3.6 部署视图
部署视图是将执行实体和计算机资源建立映射关系。这里的执行实体的粒度要与所部署的计算机资源相匹配,比如以进程作为执行实体那么对应的计算机资源就是主机,这时应该描述进程对应主机所组成的网络拓扑结构,这样可以清晰地呈现进程间的网络通信和部署环境的网络结构特点。当然也可以用细粒度的执行实体对应处理器、存储器等。部署视图有助于设计人员分析一个设计的质量属性,比如软件处理网络高并发的能力、软件对处理器的计算需求等。
4.3.7 工作分配视图
工作分配视图将系统分解成可独立完成的工作任务,以便分配给各项目团队和成员。工作分配视图有利于跟踪不同项目团队和成员的工作任务的进度,也有利于在个项目团队和成员之间合理地分配和调整项目资源,甚至在项目计划阶段工作分配视图对于进度规划、项目评估和经费预算都能起到有益的作用。
五、运行环境和技术选型说明
1)开发版说明:ESP-WROVER-KIT是一款基于ESP32无线网络和蓝牙/蓝牙低能耗(BLE)片上系统(SoC)的全功能开发板。该开发板兼容基于Espressif系统的双核无线双模蓝牙模块,包括 ESP-WROOM-32 和 ESP32-WROVER。ESP-WROVER-KIT 可满足4.5MB内存和双核240兆赫兹CPU的高性能要求,搭载先进的多协议通用串行总线桥(FTDI FT2232HL),允许开发人员直接通过 USB接口,使用 JTAG 对 ESP32 进行调试,无需额外的 JTAG 调试器。ESP-WROVER-KIT还提供板载高速微显卡接口、VGA摄像头接口,以及3.2英寸SPI液晶面板和输入/输出扩展功能。旨在协助用户快速开发物联网 (IoT) 应用,可满足用户对 Wi-Fi、蓝牙、低功耗等方面的要求。
2)硬件资源:
(1)兼容基于 ESP32 的双核 Wi-Fi双模蓝牙模块
(2)车载高速微型SD卡
(3)精密 32.768KHz 晶体振荡器在深度睡眠模式下为芯片提供低功耗时钟
(4)JTAG 接口
(5)VGA 摄像机接口
(6)3.2 英寸 SPI 液晶面板
(7)SPI 接口连接到外部闪存(PSRAM)
(8)I/O 扩展功能
(9)多协议USB网桥(FTDI FT2232HL)
(10)重置和启动用户按钮
(11)USB接口作为板卡的电源和PC机与ESP32模块之间的通信接口
3)软件资源
(1)BootLoader
(2)FreeRTOS
(3)ESP-IDF
六、系统概念原型的核心工作机制
概念是人对能代表某种事物或发展过程的特点及意义所形成的思维结论,概念原型是一种虚拟化的、理想化的软件产品形式。也就是说,概念原型 = 用例 + 数据模型。
软件架构代表了软件系统的整体设计结构,它应该是所有这些视图的集合。但我们不会将不同角度的这些视图整合起来,因为不便于阅读和更新。不过我们会有意识地将不同角度的视图之间的映射关系和重叠部分了然于胸,从而深刻理解软件架构内在的一致性和完整性,这就是系统概念原型。
七、总结
这次的作业是基于软件设计的一个软件系统设计方案,由于个人的项目是嵌入式开发且面向过程的编程,在总结软件结构特点时,由于学习深度和实力不限,很难找到对应的概念匹配。所以,就根据个人的理解剖析硬件设计里体现的软件思想来继续编写。也希望其他大佬多多指点和建议。
八、参考资料
Software Engineering: Theory and Practice (Fourth Edition),Shari Lawrence Pfleeger,Joanne M. Atlee