1. 背景

GDK8和幽兰中的刘姥姥驱动程序是一个经典的小例子。第一个版本写在十年前,没有硬件依赖,2021年,移植到GDK8时,增加了一些针对GDK8硬件的功能,比如观察系统寄存器,读芯片温度等。今年移植到幽兰代码本时,再次进行改进,增加了对RK3588芯片的支持。

2. 条件编译方法

在从GDK8移植到幽兰时,为了处理硬件差异,引入了编译宏,通过宏来隔离两套代码。

宏具有许多优点,比如代码复用和模块化:宏允许你将一段重复使用的代码片段抽象为一个宏,并在多个地方进行调用,从而避免了代码的重复书写,并提高了代码的复用性和模块化程度。简化代码:使用宏可以减少冗长或复杂的表达式,在编码过程中能够以更简洁和易读的方式来描述逻辑和操作,等等。同时也有许多缺点,比如难以调试和跟踪:由于宏在编译时被展开,它们往往不具备标准函数的调试能力。在调试过程中,很难直接跟踪宏内部的执行流程和变量值。错误定位困难:宏展开后代码的行号可能与原始代码不一致,当出现编译错误时,很难根据错误信息快速定位到具体的源代码位置。

具体来说,下面是使用预定义宏方式的代码。通过判断该宏是否定义,条件选择定义哪一种硬件所需要的宏。


从上面的代码可以看出来,我们只能在编译时通过宏来选择一套代码。如果在使用驱动时定义错了宏,那么在不同硬件中会就有严重的错误,因为访问无效的外设地址而导致整个系统挂死。

同时,宏的方式也非常不灵活,可拓展性很低,每添加一种硬件,就需要添加一种硬件的宏定义,同时执行函数中也需要添加一种宏定义,导致代码冗长累赘。

3. 动态检查方法

针对上述问题,我们对症下药,设计了一种在运行期动态检查硬件,选择代码逻辑的方法。下面是这种方法的细节。

首先,定义了一个结构体,归纳硬件的特征,如下:

![](https://www.nanocode.cn/wiki/uploads/blog/202307/attach_176ea06684c7b9fd.png)

它是所有硬件的通用体(成员可以添加),可以赋予不同的值代表不同的硬件。

接下来,定义一个全局变量,实例化上述结构体。在模块进行初始化时,检查硬件,给这个去全局变量赋予合适的值。

之后进行各种操作时,只需将它传入即可。用它的成员变量值去计算,即可完成所有操作,又免去了多个if,else的条件编译及判断。

这是选择动态选择方法改进后的代码:

从中我们可以看出来执行函数完全不用在进行选择了,只需应用传入的结构体变量成员即可,不仅方便函数调用,而且减少了代码量,对于以后更多的硬件,也只需在初始化的时候多加一种硬件即可,可拓展性极强。

4. 总结

最后我们看看刘姥姥模块在不同硬件中的执行效果。

a. 在ULAN版本中,使用刘姥姥模块执行温度函数。效果如下:

![](https://www.nanocode.cn/wiki/uploads/blog/202307/attach_176ea007e294814a.png)

b. 在ULAN版本中,使用刘姥姥模块执行温度函数。效果如下:

![](https://www.nanocode.cn/wiki/uploads/blog/202307/attach_176ea0264e2eef1a.png)

可以看到执行模块初始化的时候也会打印出当前的版本,同时它也可以流畅的进行温度函数执行。

经过改进,在代码数量和可拓展性两方面都是优于条件编译方法的。

回过头看,虽然两种方法都可以解决硬件差异的问题,但是我们对于代码质量,应该要精益求精。一种问题总是可以找到多种解决方法,我们应该选择最优,最简洁明了的方法。

苏轼曾说:横看成岭侧成峰,远近高低各不同。这两句诗告诉我们,从不同的角度去看事和解决问题,会有不同的结果!处理硬件差异是软件工程中的一个典型任务,动态监测的方法具有更好的动态适应性和可维护性。

作者:Zhang Yinkui  创建时间:2023-07-07 17:37
最后编辑:沈根成  更新时间:2024-05-06 17:42