简单嵌入式系统软件架构

article/2025/9/14 14:58:36

本文为原创,以下链接有比较及时的更新:
https://www.yuque.com/docs/share/334f4a3d-2974-49db-8f68-4db6601a0d21?# 《简单嵌入式系统》

引言

本文描述的内容,适用范围是简单嵌入式系统。举一些可能不恰当的例子,如手环、蓝牙温湿度传感器、小家电这一类产品的软件复杂程度,在我看来,就是一个简单嵌入式系统可把控的。

基于此,提到简单嵌入式系统的软件架构,我脑海中立马浮现这样的画面:

在这里插入图片描述

看到这张图,不同的人,可能会有不同的感受:有的高手能一眼看破,能马上进行万千补充、引申;有的会心领神会,从而期待后面的内容;而有的,可能会一头雾水,或懵懵懂懂。

就本人而言,我当前的技术水平是能用代码将这张图构建的相对稳定、完整;期望有一天,我(或我们)能站在万米高空俯视这张图,一眼看破框图背后的种种玄机,轻松写意地构建出一个个优雅的嵌入式系统软件。

回归正题,为什么要进行以上框图所示的层次划分?我是这么考虑的:

关于硬件层:

一般会说,设计该层次的目的在于封装掉硬件的细节,使在其上层的软件具备跨平台的移植性。不过在我看来,想要做到这点其实非常困难。就本人所在领域(BLE芯片)的开发而言,固件工程师一般都会在芯片厂商提供的 SDK 的基础上进行开发。换芯片、换 SDK,几乎不可能只靠修改硬件层就能完成适配。

因此,在我看来,设计该层次的主要目的是为了方便维护,统一管理。通过对硬件相关的软件模块进行一定的抽象,我们能找到他们之间的一些共性:

比如,对硬件的操作,一般都会有一个初始化、工作、解初始化流程 。因此,接口设计上,会有:

void xxx_init(param);void xxx_start(param);void xxx_pause(param);void xxx_deinit(param);或者:void xxx_init(param);void xxx_send(param);void xxx_recv_cb_register(param);void xxx_deinit(param);

又比如,将片内外设的 IO 接口、外设配置单独列在一个头文件中,方便进行统一的 IO 口、外设的适配:

#ifndef __XXX_COMMON_H__
#define __XXX_COMMON_H__#include <stdint.h>
#include <stdbool.h>#include "iic.h"
#include "spi.h"/*****************************************iic configuration
*****************************************/
#define XXX_IIC_IO_SDA_PORT 
#define XXX_IIC_IO_SDA_PIN
#define XXX_IIC_MODE/*****************************************spi configuation
*****************************************/
#define XXX_SPI_IO_CS_PORT  
#define XXX_SPI_IO_CS_PIN
#define XXX_SPI_MODE#endif // __XXX_COMMON_H__

该层次各个模块的职责范围,应只是进行单纯的硬件操作。有多纯?比如,对于 IO 口按键,在该层次,IO 口模块应只提供 IO 口的初始化、读写、中断配置等功能,而不应该提供比如短按、长按、双击等动作触发功能。因为按键长按等动作的实现,需要调用两个该层次的软件模块:io_driver 和 timer_driver,这会导致层次内模块和模块之间产生了依赖,如图:

在这里插入图片描述

同层次,模块和模块之间不要产生依赖,这个可以理解,但上面这个图,好像没什么问题啊,我们可以把硬件层分成两个层次:片内外设和片外外设?

在这里插入图片描述

是可以的。

但这里还有另外一个思路:在同一个层次内,我们仍然可以区分片内外设和片外外设,但可以通过一些 C 语言技巧来让他们不产生依赖,从而可以不用再分出一个层次。还是以按键功能为例,最终我们要实现的效果的框图是:

在这里插入图片描述

以上四个软件模块,可以假设其功能为:

button:和系统的消息总线对接、根据底层的按键事件,向系统发送按键消息。

drv_button:实现按键的触发、消抖、短按、长按等逻辑,并在各个节点产生回调。

xxx_io:配置寄存器,提供 IO 口的读、写、中断配置等操作

xxx_timer:配置寄存器,提供 timer 寄存器的配置等操作

把按键逻辑抽象出来,做到不对 io 和 timer 产生依赖,有一个好处:为上层的 button 组件兼容各种按键类型提供便利。button 组件可以随意地组合 io, timer, 各种按键逻辑(IO 按键,触摸按键,矩阵键盘等),统一进行管理。

另外,从硬件层的角度,在编写 drv_button 的时候,也不需要为未来可能的应用过多的考虑,在接口的灵活性设计上花费精力。

综上,对于下面这两种层次架构:

在这里插入图片描述

其优缺点,我的理解是:

  • 前者的 drv_button 的应用比较难。因为需要 button 组件自己、结合 timer 和 io 来实现完整的功能。后者的 drv_button 功能相对完善
  • 前者相对比较灵活。button 组件可以相对随意地组合 drv_button, io, timer。后者在某些场景下,可能会导致重写 drv_button。举个例子,drv_button_1 用到了 io1, drv_button_2 也用到了 io1, 这时候,编写 drv_button 的工程师就得考虑 io 1 被复用的场景,否则会导致 drv_button 需要重写。
  • 前者的层次架构简单

简而言之,前者在应对未来的需求改动上,比较有优势;后者适用于需求比较固定的场景,它的实现相对简单且符合直觉(一般人自然而然就会这么写)。

两者的示例代码如下:

。。。

好吧,第一个图片里的架构有点写不出来。。。感觉这里有点过度设计了。

写不出来的原因主要是:在不直接引用 xxx_timer 的情况下,比较难抽象出 drv_io_button/drv_matrix_button 模块的逻辑;因为 drv_xxx_button 的实现和和 xxx_timer 模块息息相关(也和 xxx_io 模块的使用关联性比较大),上层的 button 模块需要了解比较多 drv_xxx_button 模块的内在逻辑才能比较好的应用它们;这给上层模块造成了过大的负担。并且,在这种架构下的 button 模块的实现,会随着需求的增加而变得过于庞大和复杂。

其实,上文中对于“片内外设”层,和“片内外设”层的概念的抽象,对于简单嵌入式系统来说也是一个没有必要的行为。

行文至此,我应该把前面的内容删除部分的,不过也可以保留着作为一种思路历程的记录。

经过反思,由于有 “drv_io_button” 和 “drv_matrix_button” 两种按键驱动的需求,对于按键功能的实现,有如下设计:

在这里插入图片描述

见上图,增加了 “bsp_button” 模块,用于抽象出硬件驱动的一些共性,屏蔽底层硬件实现的细节,从而为上层提供一个简单应用的接口(外观模式)。

反思后的架构示例代码如下:

硬件层实现示例代码

bsp_button

该模块主要为了封装低层的硬件实现细节,向上层提供一个简单的接口。

采用自顶向下的设计思想,先把接口写出来,然后再根据这个接口来写低层的实现代码。

接口有:

/* bsp_button.h */#define BSP_EVT_ID_BTN_BASE         0x0100enum bsp_evt_id_btn_e
{BSP_EVT_ID_BTN_INVALID = BSP_EVT_ID_BTN_BASE,BSP_EVT_ID_BTN_SHORT,BSP_EVT_ID_BTN_DOUBLE,BSP_EVT_ID_BTN_LONG,BSP_EVT_ID_BTN_CONTINUE,
};typedef struct bsp_btn_evt_param_s
{uint8_t type;                               // 0:io button, 1: matrix buttonuint8_t state;                              // 0: released, 1: presseduint16_t number;                            // sequence number of the buttons
} bsp_btn_evt_param_t;typedef struct bsp_btn_evt_s
{uint16_t                evt_id;             // bsp_evt_id_btn_ebsp_btn_evt_param_t*    p_evt_param;        // bsp_btn_evt_param_t
} bsp_btn_evt_t;typedef void (*bsp_btn_evt_hanlder_t)(bsp_btn_evt_t);uint8_t bsp_button_init(bsp_btn_evt_hanlder_t);uint8_t bsp_button_deinit(void);uint8_t bsp_button_enable(void);uint8_t bsp_button_disable(void);

此处采用“事件回调”的方式来和上层对接,上层只需要注册一个回调函数给到该模块,便可“坐等”各种事件的通知,然后再根据各种事件做相应的处理。

有一些细节、思路:

一、以下关于事件的定义
事件的参数使用的是 void * 类型,主要考虑到之后的扩展性。
typedef struct bsp_btn_evt_s
{
uint16_t evt_id; // bsp_evt_id_btn_e
void* p_evt_param; // bsp_btn_evt_param_t
} bsp_btn_evt_t;
目前该接口不能支持两个或以上按键同时按下的情况;

但当未来有这种需求时,可再增加一些按键事件 ID,并另外定义一种事件参数类型来添加。

二、关于 BSP_EVT_ID_BTN_BASE
当 BSP 模块增多(如 bsp_spi, bsp_iic)时,可通过不同的 BASE 来区分不同模块并在考虑增加一个 bsp_config.h 文件来统一定义、管理

三、bsp_btn_evt_param_t 中的 number 参数
用于标识不同的硬件按键。是 bsp_button 这个模块的核心!
从宏观上来观察这个模块,它完成了具体的“硬件按键”到抽象的“按键事件”的映射;
上层在应用这个模块的时候,可以再把“按键事件”映射为“系统消息”(也是上层(app_button 组件)的核心功能);
以此形成层层映射,由底至上搭建成套的基于“事件回调”机制的软件架构。

drv_io_button

关于组件层:

组件,顾名思义、组成部件。理想状态下,更换某个组件,应该像更换一个积木一样直观、简单;但现实往往是,需要像治疗癌症一样,清除掉一个癌细胞,损伤一片好的细胞。组件接口的设计是一门艺术,大神抽象出来的组件接口令人叹为观止。而有的人嘛,一个组件就是一个项目……

关于组件接口的设计,我的思考是:


http://chatgpt.dhexx.cn/article/1h83453m.shtml

相关文章

嵌入式软件设计层级划分概念

嵌入式软件设计层级划分概念 设计过程中体会的细化更新部分&#xff1a; 层级描述备注应用层直接控制应用&#xff0c;比如led_light_on(),led_light_off() 器件层&#xff08;如果操作复杂可进一步划分为器件应用层和器件驱动层&#xff09;比如&#xff1a;实现led_light_on …

浅议嵌入式软件测试

近年来&#xff0c;随着嵌入式系统的功能和复杂性不断增加&#xff0c;其开发时间和成本也随之不断上升。对于安全关键领域的嵌入式系统和软件来说&#xff0c;其稳定性和可靠性往往需要通过大量的测试和验证来保证。 01.一般软件测试vs嵌入式软件测试 嵌入式软件测试针对嵌入…

嵌入式程序设计思路

项目做的多了&#xff0c;深切地体会到架构的重要性。 俗话说&#xff0c;没有好的架构&#xff0c;移植和复用是件很痛苦的事&#xff0c;只能重复的造轮子。特别是嵌入式的代码&#xff0c;如果应用层中间穿插着驱动层的代码&#xff0c;维护起来是一件相当痛苦的事情。 这…

嵌入式应用软件架构设计

要做到嵌入式应用的代码逻辑清晰&#xff0c;且避免重复的造轮子&#xff0c;没有好的应用架构怎么行。 如果没有好的架构&#xff0c;移植将会是一件很痛苦的事情。 如果没有好的架构&#xff0c;复用是最大的难题&#xff0c;没法更大限度的复用原有的代码。 如果没有好的架…

嵌入式软件设计(1)--概述

嵌入式软件的定义及特点 嵌入式系统的定义 嵌入式系统是以应用为中心&#xff0c;以计算机技术为基础&#xff0c;软硬件可裁剪、功能、可靠性、功耗严格要求的专用计算机系统。 其中要说明的几个点是 1. 嵌入式系统是专用系统&#xff0c;而不是通用系统&#xff0c;其往往…

嵌入式软件设计必看书籍

提高C语言编程能力 以上4本书籍可以提高C语言编程能力&#xff0c;深入理解C语言指针用法&#xff0c;深入理解C语言标准。 提高软件架构设计能力 以上2本书籍掌握以下知识&#xff1a; 1、软件设计原则。 2、软件设计模式。 3、软件设计构架。 4、软件设计思维。 提升对RTO…

嵌入式软件架构的设计

嵌入式软件架构的设计 大多数嵌入式程序员学习编程&#xff0c;都是从开发板的附带例程开始。之后工作也会继续参考那些例程&#xff0c;很多编程习惯、方式也会受之影响。 其实开发板式的编程方式与工作中实际需求的并不完全一致。 开发板的通常卖给初学者&#xff0c;注重…

嵌入式软件设计(stm32快速上手)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 有很多的培训机构在培训嵌入式的时候,一上来会讲很多的理论知识。其实这个时候要是能通过实操帮助学员快速入门、快速上手、快速看到效果,或许这才是最重要的。 1、安装mdk软件 类…

举例说明嵌入式框架该如何分层

关注、星标公众号&#xff0c;直达精彩内容 素材来源&#xff1a;https://blog.csdn.net/weixin_46185705/article/details/122536374 整理&#xff1a;技术让梦想更伟大 | 李肖遥 前言 为了能够使得产品得到更好的开发速度与以后更好的迭代和移植&#xff0c;框架分层是很有必…

嵌入式软件架构设计

如何设计一个好的软件架构&#xff0c;如何提高软件的扩展性&#xff0c;移植性&#xff0c;复用性和可读性&#xff1f; 很多做嵌入式开发的朋友经常会遇到这种情况&#xff1a;一个项目软件设计完成了&#xff0c;客户提出了一些新的功能需求。这时侯如果客户新需求不多&…

《嵌入式软件设计方法》--设计原则

设计原则&#xff08;SOLID&#xff09; 使用各个原则的首字母组成了一个单词:SOLID。 SRP:Single Responsibility Principle&#xff0c;单一职责原则&#xff1b;OCP:Open Closed Principle&#xff0c;开闭原则&#xff1b;LSP:Liskov Substitution Principle&#xff0c;…

嵌入式软件设计之设计模式

文章目录 前言1.设计模式之适配器模式2.设计模式之单例模式3.设计模式之命令模式4.设计模式之门面模式 前言 在嵌入式软件设计过程中&#xff0c;也会用到一些设计模式&#xff0c;所以说设计模式并不是面向对象语言的专利&#xff0c;下面我通过查资料以及自己的思考总结的几…

2、【已解决】Oracle错误问题(ORA-03113)

在执行一条sql时出现了如下问题&#xff1a; 处理方案网上找了一下日志&#xff0c;说是备份日志满了的原因&#xff1b; 通过以下方法看了下错误日志信息&#xff1a; 1、export TIME_STYLE%Y-%m-%d %H:%M:%S #格式化文件日期&#xff0c;便于查找错误文件 2、sqlplus &qu…

搭建ADG过程中复制报错 RMAN-03009 ORA-03113

搭建ADG过程中复制报错 RMAN-03009 ORA-03113 猜测主备之间网络路由过多导致。。。 开启mrp进程报错 发现数据文件是主库ASM的路径&#xff0c;备库是单机的 switch database to copy; 报错RMAN-6571 report schema;switch database to copy; select name from v$datafile;s…

oracle 启动报错03113,oracle数据库无法启动,总报ora-03113错误

虚机上装的oracle&#xff0c;由于系统突然宕机&#xff0c;导致了以下问题&#xff1a; 问题&#xff1a; 1、监听无法启动&#xff1a; $ lsnrctl start LSNRCTL for Linux: Version 11.2.0.4.0 - Production on 22-NOV-2018 15:27:11 Copyright (c) 1991, 2013, Oracle. Al…

oracle 启动报错03113,Oracle 入门之Oracle启动报错“ORA-03113”

Oracle 入门之Oracle启动报错“ORA-03113” [日期&#xff1a;2010-09-25] 来源&#xff1a;Linux社区 作者&#xff1a;naruto6006 [字体&#xff1a;大 中 小] 早上连接Oracle,发现oracle无法正常工作&#xff0c;无法shutdown immediate方式关闭&#xff0c;shutdown abort方…

linux oracle 03113,Oracle数据库关闭时出现ORA-03113错误

Oracle数据库关闭时&#xff0c;出现ORA-03113错误&#xff1a; SQL> shutdown immediate ORA-03113: end-of-file on communication channel Process ID: 3437 Session ID: 125 Serial number: 5 SQL> startup ORA-24324: service handle not initialized ORA-01041: in…

oracle startup open ora 03113,oracle宕机,startup报错ora03113

一、故障情况 应用无法连接数据库&#xff0c;检查oracle发现已经宕机。 startup 后报错 ORA-03113: end-of-file on communication channel 二、查找原因 查看alter日志 tail -500 /oracle/database/oracle/diag/rdbms/udb/udb1/trace/alter_UDB1.log Unable to create archiv…

ora03113通信通道的文件结尾 会话id 149 序列号 3

ORA-19815: 警告: db_recovery_file_dest_size 字节 已使用100.00%, 尚有 0 字节可用。” 是db_recovery_file_dest_size也叫归档日志空间不足导致 解决方法 第一步&#xff0c;将空间设置大点&#xff0c;另一个就是将多余的文件删除掉即可&#xff0c;那么我们就将这两个办法…

ORA-03113:通信通道的文件结尾

转自--------------http://blog.csdn.net/zwk626542417/article/details/39667999 由来 今天跟往常一样&#xff0c;登陆PL/SQL&#xff0c;确登陆失败&#xff0c;出现一个错误“ORA-01034”和“ORA-27101”如图&#xff1a; 然后就就通过命令提示符去登陆Oracle&#xff0c;去…