感慨
本人大约三四年没有碰单片机了,遥想当年我还是用的keil工具。
有幸以援助的身份介入公司的嵌入式项目,结合自身经验讲讲。
工作是一个长期的过程,开头不注意则会产生蝴蝶效应,导致接下来的工作一直处于挖坑填坑的状态,最终大好青春年华耗费在一些无谓的事情上。
本文不过多去讲具体操作,只描述针对问题的思考方式。
工程方面指的是freertos工程。
背景需求
做任何事情都要有目标,朝着目标去前进。本文关注的目标如下:
1. 代码如何复用,达到多项目复用的目的
2. 如何满足多项目复用的前提下,又不混淆各自的代码模块
分析
代码复用,在嵌入式中,关键是模块的复用。模块又分为硬件模块和软件模块两类。同时,不管是硬件模块还是软件模块,关键点上要有相应的模块测试。
硬件模块
硬件模块主要是驱动相关的函数操作,见识过linux驱动的小伙伴应该知道有四个关键点需要实现: 加载、卸载、读、写。
同时,硬件模块分为内设和外设。内设驱动通过工程配置可以自动生成;外设驱动往往需要自己开发。
软件模块
软件模块是衔接硬件和业务主体的中间层,开发过程中一定要注意解耦。软件模块必须在不同硬件平台上是可移植的!
模块测试
代码要复用,必须有相应的模块测试,否则更换硬件平台时,很大概率看着一堆跑不起来的代码发呆。
工程结构
.ioc文件和.xml文件配置
要实现不同硬件平台下的工程代码复用,首先要按照不同工程去创建不同的ioc文件,该文件涉及硬件平台,每次调整修改后,会重新生成内设驱动,内设驱动文件直接生成在Src目录下;并且include配置也会被刷新掉。
随便打开一个内设驱动代码,添加部分说明,如下:
void ADC_IRQHandler(void)
{
/* USER CODE BEGIN ADC_IRQn 0 */
// USER CODE注释之间的代码,重新生成是不会消失的
/* USER CODE END ADC_IRQn 0 */
HAL_ADC_IRQHandler(&hadc1);
/* USER CODE BEGIN ADC_IRQn 1 */
// USER CODE注释之间的代码,重新生成是不会消失的
/* USER CODE END ADC_IRQn 1 */
}
include配置被刷掉的话,通过以下Import和Export即可解决,将会保存为.xml文件,其中包括了include路径和define;define的设置尤为关键,用它来实现硬件设施的切换
总结,硬件设施由.ioc来定义,而.xml则定义了软件行为,在不同的硬件设施下,对应不同的软件行为,采用切换文件配置来实现代码统一维护。
代码目录
在了解STM32CubeIDE开发工具的特点以及模块测试相关内容后,简要讲解一下代码上要如何排布。
源文件目录,分为硬件模块dev、框架配置framework、业务主体main、软件架构soft、测试单元test,如下:
头文件目录,则少了业务主体和测试单元相应的头文件,因为这两者是引用别人,同时不必暴露自身变量和函数,故无需提供头文件。
依赖关系
学过java的同学,会知道一个细节,当import一个不使用的包时,IDE会给你相应的提示。而在C/C++的IDE中,则没有那么方便的提示。所以在写C工程时,需要仔细处理好依赖关系,要有一定的章法,而不是随意引用,能够运行就不管了。
仿造Nginx源码的组织方式,在Nginx中,有一个头文件ngx_core.h包含了所有头文件,而其他文件统一引用该文件即可。
那么在STM32CubeIDE中,哪个文件适合干这种事呢?答案是main.h文件!主要原因在于,其包含是自动生成的内设驱动;并在其中添加咱自定义的config.h文件,之后不再动main.h文件。
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "config.h"
/* USER CODE END Includes */
config
config主要用途在于控制一个函数指针指向,参数及返回不是关键点,为void即可,声明如下:
void (*init)(void);
调用方放在freertos.c中,该文件生成时已经引用了main.h,从而引用了config.h
/* USER CODE BEGIN RTOS_THREADS */
init();
/* USER CODE END RTOS_THREADS */
至此,已经分离解耦了硬件设施和软件设施,通过一个init函数指针来衔接。
控制工程行为自然是采用宏定义来完成,代码如下:
#if(PRO==CONFIG_MAIN)
extern void main_init();
void(*init)(void) = main_init;
#elif(PRO==CONFIG_TEST_HTTP)
extern void test_http_init(void);
void(*init)(void) = test_http_init;
#endif
只需要在Symbols中配置好,即可完成不同功能的切换。
开发流程
上面讲述了关键点,具备了一个通用工程架构的基础雏形,还需要一定的开发流程规范才能完整。
首先,你需要一个完整的A项目,而B项目和A项目,仅仅是少量需求不同,硬件接口也可能稍有不同。
接着,构建好B项目的ioc文件,自动生成代码,然后将A项目中的测试逐个在B项目上运行,运行不通过的地方,使之兼容,并确保A项目不受影响。
最后,开开心心在上面开发你的B项目吧,可别忘了export你的xml配置哟。