嵌入式程序设计思路

article/2025/9/14 14:56:42

项目做的多了,深切地体会到架构的重要性。

俗话说,没有好的架构,移植和复用是件很痛苦的事,只能重复的造轮子。特别是嵌入式的代码,如果应用层中间穿插着驱动层的代码,维护起来是一件相当痛苦的事情。

这篇文章就总结一下自己的代码设计思路。

整体结构框图:

说明:该图是针对于MCU+RTOS框架的应用的。

1、驱动层

驱动层代码的首要原则就是隔离硬件库代码

次要原则就是把“变量”做成宏定义

最好的借鉴就是芯片HAL库或操作系统源码

比如1个STM32或GD32的MCU程序,无论是采用标准库或是HAL,亦或者第三方库(usb协议栈、文件系统管理、UI显示等模块),都应该自己再单独封装出来一层API函数。

然后保证上层只能调用驱动API,不越级调用底层驱动

对于如何封装自己的驱动API,有这么几个思路:

首先,最好的借鉴代码就是芯片原厂驱动库,代码久经考验,完全可以按照他们的规范去写。

1.1代码要紧凑,并且充分考虑扩展性

相似的功能代码,不要搞复制粘贴,做好传参;传参和返回值设计最好在一开始就考虑好扩展性。

1.2驱动层的代码始终由上层调用

驱动层只负责写好接口,驱动初始化或功能开关由上层调用和初始化

1.3把代码移植的时候的需要修改的参数做成宏定义

功能代码固定,移植的时候只需要修改头文件的宏定义;

一般需要修改的如:GPIO引脚号、串口号、打印、延时等等。

当然也需要明白的是,驱动层代码可以通过宏定义隔离RTOS、日志等模块,但无法摆脱硬件库的依赖,比如当你从STM32HAL库换成标准库,或者换成GD32的标准库时就不得不需要大面积修改了。

通过这种良好的隔离化、模块化思想,都能尽量减少移植的工作量。

2、功用模块层

一般来讲,驱动层的模块划分,一是1个接口1个驱动文件,比如can、flash、adc,二是1个模块1个驱动文件,比如usb、bt、4g、wifi,等等。

但是常常会有一个问题,比如bt/wifi/4g等等模块,需要对模块进行复杂的AT指令操作实现某些功能(比如设置模块参数、连接外部设备等等),这样的可以在选择构建一个platform层,这样将驱动一分为二,一块是硬件驱动-只用于基本的数据透传和数据接收发送,另一块是上层功能驱动-只用来完成模块功能(或者添加数据缓冲处理)。

例如,

1)USB需要做初始化、连接状态检测、数据接收(数据队列或者二级缓冲区+信号量通知)处理,数据读取(读队列操作),等等操作;

2)蓝牙/4G同USB,同时又增加了设备名设置、设备地址读取、对外连接等AT功能操作,以及基础透传功能;

3)WiFi同USB,同时又增加了AP/STA网络连接操作和数据传输操作,可以做成状态机处理函数API,供上层调用(做好不要单独做成任务,否则代码缠在一起比较麻烦);

还有其他比较常见的模块,

FLASH管理模块,实现FLASH特定区域的读写接口,实现OTA的驱动API;

文件管理模块,实现如日志存储、文件夹管理等功能API;

UI管理模块,实现是页面初始化,加载刷新、菜单切换等API框架;

……

3、业务层

业务层,可以在RTOS中简单的理解为一个task。

比如一个CAN协议交互功能,就可以设计3个task,

        一个是命令接收处理任务,负责拆包解包;

        一个是命令发送处理任务,负责组包发送;

        最后就是协议处理任务,实现函数指针数组,收到命令后去执行对应的状态机处理函数,并上报执行结果。

从这个角度上,task就可以分为3类:

1)通讯处理任务;

        收到消息通知后,开始处理原始数据包;处理之后再推送到其他task进行进一步处理,或者直接对外发送,或者当场处理掉并发送response。

        触发源是硬件中断,比如USB接收中断和蓝牙接收中断。

2)协议处理任务;

        收到消息通知后,处理加工之后的命令包(或者处理全局变量数据);执行指定的功能操作,并反馈结果到上层。

        触发源由内部软件触发,可以是通讯处理任务的二次传递,也可以是内部软硬件定时器触发。

3)系统监控任务;

        轮询监听系统信息并保存到全局变量中,监听系统状态和参数。比如经典的ADC数据采集(温湿度、压力、电压电流电阻)和GPS信号采集。

如何模块化业务层的任务设计?

1)依赖于RTOS的任务处理代码与数据处理代码隔离开

比如下面这段代码,是一个最上层的接收数据处理task。

整体分为3个部分:

        1.等待信号量通知 OSSemPend;

        2.读取并处理数据包 UpperRxDataBaleProcess;

        3.处理命令 UpperCmdDeal;

经过这样的分层设计之后,将不需要RTOS的代码隔离出去,只调用API接口,减少更换通讯类型或RTOS时的移植工作量,也可以使代码更为简洁。

while(1)
{OSSemPend(UpperRxDataSem,0,&byErr);if(byErr == OS_ERR_NONE){while(1){pCmdBuff = UpperRxDataBaleProcess(&unCmdBuffLen);if(pCmdBuff != NULL){UpperCmdDeal(pCmdBuff,unCmdBuffLen);MemoryFree((void**)&pCmdBuff);}else{break;}}}
}

2)状态机处理函数

如下代码,是一个典型的总线命令交互式状态机处理框架:

先设计交互流程的状态定义(里面函数可以什么都不用做,只需要先做好状态跳转),在收到1条命令之后执行该状态机处理函数,并做好安全退出机制,允许外部其它命令打断状态机的执行。

void ProtocolProcess(void)
{ProtocolInfoInit();ProtocolState = STATE_UPPER_NEED_STOP_CHECK;while(1){switch(ProtocolState){case STATE_UPPER_NEED_STOP_CHECK:UpperNeedStopCheck();break;case STATE_CMD_GET_CMD_INFO:ProtocolCmdGetCmdInfo();break;case STATE_CMD_SEND_DATA:ProtocolCmdSendData();break;case STATE_CMD_RECEIVE_DATA:ProtocolCmdReceiveData();break;case STATE_CMD_RECEIVE_TIMEOUT:ProtocolCmdReceiveDataTimeOut();break;case STATE_CMD_RECEIVE_FINISH:ProtocolCmdReceiveFinish();break;case STATE_CMD_REPORT_UPPER:ProtocolCmdReportUpper();return;case STATE_EXIT:ProtocolExit();return;default:break;}}
}

2)合理的设计任务的结构体

一般的,

a.任务运行要设置一个结构体,保存任务的运行参数

        比如,任务当前执行的命令信息/类型,配置信息,运行状态,上报类型和参数

b.一条命令要设置一个结构体,结构化字节序列

        设计通讯协议,一定要注意字节序列与结构体的相互转化

4、通讯协议

典型的几种通讯协议

a. 固定头+长度位+DATA+CRC校验位+固定尾

        安全性最高,有固定的起始和结束,且CRC校验更可靠;但是需要对数据进行替换(有效数据中出现固定头尾时),且校验比较耗时(对于低性能MCU)。

b. 控制位+长度位+DATA(+校验)

       (类似MQTT)不需要进行校验,适合传输JSON数据。

c. 固定头+长度位+DATA+校验和

        复杂度较低,适合空闲中断式的数据传输,校验和计算也非常简单。

d. 起始位+DATA(+校验和)

       (类似CAN-15765协议) 有一个起始位表示报文类型和数据长度,非常适合数据量小的场合。

为什么要有校验位?原因是嵌入式设备和对端如PC、手机等等,都可能因为外部环境或自身故障,导致发送数据时出现脏数据,表象就是一串字节流数据,发出去是对的,但是到了接收方那里,中间某个或若干个字节就变成错的了。

所以除了USB\CAN\SDMMC这种具有严谨的交互机制外,其他的如UART/SPI/I2C/都是有可能发生错误的,尤其是高波特率情况和中断过于频繁时。


http://chatgpt.dhexx.cn/article/E6OBRdI9.shtml

相关文章

嵌入式应用软件架构设计

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

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

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

嵌入式软件设计必看书籍

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

嵌入式软件架构的设计

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

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

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

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

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

嵌入式软件架构设计

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

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

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

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

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

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

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

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

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

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

虚机上装的oracle,由于系统突然宕机,导致了以下问题: 问题: 1、监听无法启动: $ 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” [日期:2010-09-25] 来源:Linux社区 作者:naruto6006 [字体:大 中 小] 早上连接Oracle,发现oracle无法正常工作,无法shutdown immediate方式关闭,shutdown abort方…

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

Oracle数据库关闭时,出现ORA-03113错误: 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

一、故障情况 应用无法连接数据库,检查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也叫归档日志空间不足导致 解决方法 第一步,将空间设置大点,另一个就是将多余的文件删除掉即可,那么我们就将这两个办法…

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

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

ORA-03113:通信通道的文件结尾-完美解决方案

今天发现系统登录和查询数据特别慢,orcl进程占用内存也特别高。打开程序调试用,存储过程调用时报错【ORA-03113:通信通道的文件结尾】 解决方案: oracle 文档中对这个错误这样解释: ORA-03113 错误就是说连接到数据…

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

一、报错及错误原因 启动oracle数据库事报错:ORA-03113: 通信通道的文件结尾,报错内容如下图 出现问题后,去查看告警日志文件(D:\app\diag\rdbms\cjyorcl\cjyorcl\trace\alert_cjyorcl.log),日志报错内容如…