【架构】工程代码结构(附带NXP、ST官方demo)
1. 起因
最近在做我们应用层内部代码从2.0到3.0版本的迁移,除了删减增加模块,规范写法,最主要的就是重新规划了代码的结构。我们代码的特点就是并行了很多项目,这些项目有共同的地方,又有区别。把哪些部分抽出来做共有库,哪些做自己的项目内部,makefile怎么构思(关于makefile我也会写一篇快速入门)。
有时候回头看看自己的代码,怎么可以写的这么乱,模块化一点都不好,接口乱七八糟。最近重构代码的冲动越来越强烈。
这么想想,小工程就算了,大一点的项目,还是应该提前把结构和接口设计好,代码怎么分层,功能怎么划分,再开始写。
不过很多工程,前期设计好了,也经不住需求的一再变更,改改改改,就变成了一坨忍不住想重写的翔……
算了不抱怨了,还是看看别人是怎么分层的。
2. Demo结构解析
这里列的几个例子不是linux应用层的,是单片机的demo。
2.1 ST公司的STM32Cube
Cube版本:
简单来说,布局如下:
工程名
- Document:文档
- Driver:硬件驱动
- Bsp:板级的接口,比如串口、IO、定时器……
- CMSIS:内核接口,这个一般是内核厂家提供,代码不用改,
- STM32F7xx_HAL_Driver:ST厂家提供的HAL库,与以前提供的3.5的标准外设库属于同级,都是对寄存器配置的封装。HAL库是一个硬件抽象层,便于不同芯片之间的移植。(大家底层不一样,但是HAL库的接口都一样)
- Middleware:中间件包含的是一些位于系统代码和应用代码之间的,这里感觉就是在HAL库和用户调用之间又封装了一层,把一些协议栈封装进来了。
- ST:ST自己的一些协议栈
- STemWin:ST自己的界面
- STM32_Audio:音频驱动
- STM32_USB_Device_Library:封装了USB协议
- STM32_USB_Host_Library
- Third_Party:第三方库(协议栈)
- Fatfs:文件系统
- FreeRTOS:操作系统
- LwIP:IP协议栈
- ……
- ST:ST自己的一些协议栈
- Project:不同的项目,我理解应用层的代码都写在这里面,就是流程,对BSP的调用,对middleware的调用
- Utilities :这一块不太懂,是公共库?公共资源?字库图库这些?
- xxxx.ioc : 图形化配置文件,可以用CubeMx打开
2.2 NXP公司的S32DS
S32DS版本:S32 Design Studio for Power Architecture Version: 2.1
NXP家的S32DS架构如下:
工程
- Include:我理解是 应用层的头文件
- Generated Code:这个是根据图形化配置,系统自动生成的代码(配置数组),实际使用的使用,把这些配置数组加载到系统接口上就能实现对外设的配置。
- Project_Settings:
- Debugger:调试器配置
- Linker_File:链接.ld文件
- startup_code:启动代码,这个应该是厂家提供,不用改的
- SDK
- plantform
- devices:设备
- driver:类似bsp,对寄存器操作的封装
- pal:NXP基于driver层上又封装了一层PAL层,把不同芯片的接口统一了。类似ST的HAL库,都是便于移植用的。
- rtos
- FreeRTOS_PA:没啥说的,就是移植的freetos
- osif: NXP自己的一个裸核的系统。bareMetal。
- plantform
- Source:我理解是应用层的代码
- Document
- xxx.pe:图形化配置界面。
- xxx.ld:链接文件
2.3 自己写的应用层
这是我目前在整理的结构,个人感觉还挺好的,毕竟已经是做的第2次重构了,比较贴合我们的真实需求。
因为保密考虑,只列了一部分简单结构,能体现出来结构优势即可。
- api:一些公有库的头文件
- BaseApi
- lib
- BaseLib:包含一些硬件模块的驱动
- Can:Can的驱动接口
- Gpio:Gpio的驱动接口
- NetSend:网络驱动接口
- I2C:I2C的驱动接口
- Uart:串口的驱动接口
- …
- Isp:摄像头芯片的图像调整
- Common:读写文件、读写DDR、时间获取
- Xcore:cache映射
- ……
- BaseLib:包含一些硬件模块的驱动
- project:以上都属于共有库的部分,适合于一些项目来做开发,在project目录下,做不同项目的区别,流程、交互、协议等。
- zu3_foresight
- main
- main.c
- Buf_Manage:buf管理,不同项目的buf分的数据大小管理方式都不一样的。
- Buf_Send:buf发送,涉及到和上位机(显示界面)的交互协议,不同项目定的不同
- Weight_Load:权重加载,前视环视的的weight结构是不同的,也是跟着项目走的
- Fpga_Cfg:Fpga配置,不同项目FPGA的bit不同,寄存器列表也不同。
- ……
- main
- zu5_fisheye
- 7020_foresight
- ……
- zu3_foresight
这样的好处就是,能多个项目做在一起,最大限度的共用一些库。
某些东西在一个项目中优化了,不用所有的分支全部合一遍。
比较典型的就是ISP模块,ISP工程师更新了ISP模块,只要更新到共同依赖库的Isp模块中,保持接口不变,其他的项目就都可以同步使用到这个改进,而不像一起,N个分支要全部合一遍。
对于我们这种需求多变,分支超级超级多的版本,就比较好用。不同的项目,在project文件夹下建立自己的项目工程,写自己的makefile就好。
外面的库都编成公共库,最后链接进来就好。
3. 实践
参考了两家的代码,感觉做非应用层项目,和底层关系会比较近,还是ST的架构会好一些,模块化分层会好做一些。
以一台PH滴定仪的设计为例,实操一下。
- Document
- Driver
- Core:内核接口,CMSIS
- HAL:HAL库接口,STM32F7xx_HAL_Driver
- BSP:基于HAL又封装一层,应用层可以直接调用
- Middleware
- Third_Party:一些第三方库的移植
- FreeRTOS
- FATFS
- EasyFlash
- FreeModbus
- Module:一些功能模块,基于BSP层,封装了一些功能模块的接口,供流程调用
- FuncCommon 版本控制,硬件版本,软件版本 IO口
- FuncRelay 继电器模块 依赖 IO口
- FuncPumpT600 泵T600模块 依赖串口
- FuncValueSv01 六通阀模块 依赖串口
- FuncPumpKzq1 柱塞泵模块
- FuncTemperaturePT 测温模块 Max31865 依赖IIC
- FuncDebug 调试模块 依赖 flash
- FuncAuthentication 加密模块 依赖 IIC 随机数
- FuncStriier 搅拌器模块 依赖 IO
- FuncLiquidLevel 液位模块 依赖IO
- FuncMcgs屏幕MCGS交互模块 依赖串口
- 接口:更新结果,更新步骤,更新温度,更新状态…
- FuncWarn:
- 接口:报警事件更新,报警触发,报警消除
- Third_Party:一些第三方库的移植
- Project:放置不同项目,以F离子滴定仪为例展开子文件夹
- PH_AutoTitration:PH滴定仪
- ORP_AutoTitration:ORP滴定仪
- Spectrophotometer:分光光度计
- F_Spectrophotometer:F离子滴定仪
- Workspace
- KeilMdk521:基于keil MDK 5.21建立的工程文件
- sourceInsight4:基于SourceInsight4.0 建立的工程文件
- CubeIde:基于cube建立的工程文件
- UsrApp:放一些业务代码(server)
- SvcWarn :报警流程
- SvcMcgs:屏幕(MCGS组态屏)交互,modbus主机
- SvcProcess:流程控制
- SvcComm:外部通信交互,modbus从机,维护通信协议
- main.c
- Workspace