当今开发团队面临的一个重大挑战是编写可扩展且可配置的嵌入式软件。过去,嵌入式开发的重点是开发只能用于单一、短期产品的软件。然而,行业中出现了一个明显的转变,即开发可用于支持多个项目长达五年或更长时间的平台软件。这一趋势凸显了系统开发的复杂性和成本日益增加。
为多个项目甚至多个嵌入式目标配置嵌入式软件非常复杂。不过,嵌入式开发人员可以使用查找表(LUT)来减轻开发负担。
LUT是一种强大的数据结构,用于映射输入和输出值。LUT在加快执行速度方面的效率改变了游戏规则。系统无需在运行时执行计算或复杂的决策逻辑,而是可以快速从LUT中检索预先计算的结果,从而提高开发人员的效率。
例如,假设您需要将摄氏度转换为华氏度。您可以在一个函数中实现如下转换:
float celsius_to_fahrenheit(float celsius) {
return (celsius * 9.0 / 5.0) + 32.0;
}
虽然这种方法可以正常运行,但查找表可以提高运行效率。例如,您可以编写一个查找表来查找摄氏度的整数值,如下所示:
// Example LUT for temperature conversion
float celsius_to_fahrenheit[101] = {
32.0, 33.8, 35.6,..., 212.0
};
// Precomputed values float
convert_to_fahrenheit(int celsius) {
return celsius_to_fahrenheit[celsius];
}
LUT的执行速度要快得多,因为您只需提供一个输入值,函数就会通过将其索引到数组中来返回结果。
虽然LUT确实会降低您的计算量,但它们会增加您的存储占用,因为这些查找值需要存储在闪存中。当我们使用LUT时,我们实际上是在以存储换取速度。您可能需要权衡对LUT的使用,但您会发现用于嵌入式软件的大多数表根本不占用太多闪存。
如果您研究一下软件行业其他领域的软件开发方法,您就会发现一种被称为表驱动设计的概念。表驱动设计是一种软件开发方法,其中决策逻辑被编码在表中,而不是复杂的控制流语句中(如嵌套的if-else或switch-case结构)。这种方法增强了代码的可读性、可维护性和灵活性。
当客户要求更改软件时,我们不会大幅更改项目逻辑,而是根据客户的新偏好更新表格。虽然嵌入式开发乍一看似乎完全不同,但事实证明我们其实并没有那么不同。
在嵌入式开发中,有多个领域可以利用表驱动设计和LUT来提高软件的可配置性、灵活性和可扩展性。以下是一些示例:
可能的用途几乎是无穷无尽的!让我们看一个我最喜欢的简单示例:配置微控制器的外围设备。
您可能最不希望使用LUT的地方就是配置微控制器外围设备。毕竟,我们习惯于让微控制器供应商为我们提供自动生成的内存映射结构。虽然我们非常感谢他们的努力,但您很快就会发现,这些工具和生成的代码没有为外围设备提供清晰的配置。它往往散落在整个代码中或出现在不显眼的地方。
那么,如果您有一个GPIO外围设备并想为其创建一个LUT,您会怎么做?
您可以遵循以下几个简单的步骤:
在步骤1中,你可能会得到这样一个列表:
对于GPIO,具体功能会因供应商不同而略有差异。
在第2步中,您可以利用该列表创建一个结构。对于我的示例功能,结构可能如下所示:
typedef struct {
Pin_t name;
PinMode_t mode;
PinState_t state;
PinResistor_t resistor;
}GpioConfig_t;
您会发现,每个结构部分都有一个自定义类型。这些类型通常是枚举,列出了可以使用的有效值。例如,模式可能定义如下:
typedef enum {
INPUT,
OUTPUT
} PinMode_t;
最后,在步骤3中,使用该结构为每个GPIO引脚创建配置。如果我们只有一个带有8个引脚的GPIO端口,它可能看起来像下面这样:
GpioConfig_t gpioConfigLUT[] = {
{PORT_A0, OUTPUT, HIGH, NO_PULL},
{PORT_A1, INPUT, HIGH, PULL_UP},
{PORT_A2, OUTPUT, LOW, NO_PULL},
{PORT_A3, INPUT, HIGH, PULL_UP},
{PORT_A4, INPUT, HIGH, PULL_UP},
{PORT_A5, INPUT, HIGH, PULL_UP},
{PORT_A6, INPUT, HIGH, PULL_UP},
{PORT_A7, INPUT, HIGH, PULL_UP},
};
如您所见,LUT是可读的。很明显,每一行都是一个引脚,并包含该引脚的配置。如果您需要添加引脚,则需要添加额外的行。如果您需要添加GPIO功能,则在结构上添加一个新功能,并在表中添加一列。如果您需要更改引脚的配置,则只需要更新表。
LUT的重要之处在于,您可以编写一个配置函数来读取该表,然后将其映射到外围设备寄存器中!编写完该函数后,除非更改硬件,否则无需更改它。即便要改,您也会发现更新过程既快又省事。
LUT是一种有效的工具,但嵌入式软件开发人员对其利用不足。您已经看到,为映射到硬件的外围设备配置创建一个配置LUT是多么快捷。只需几行代码即可循环遍历表并在内存中设置正确的位。
我经常半开玩笑地说,我的嵌入式软件“全是LUT”。原因是如果它可以配置或将来可能会更改,我就会在我的软件中创建一个表来管理它。我为外围设备、外部设备、RTOS 启动的线程、应用程序代码、状态机等都创建了配置表。
LUT需要一点额外的闪存空间来存储表,但我认为这是非常值得的。它让我能够创建可扩展的嵌入式软件,我可以根据需要快速轻松地重新配置,并且它也十分简洁易读。
那么,接下来该怎么做呢?我建议您查看一下自己的软件,确定需要配置的区域。它可能是外围设备、线程代码、状态机或类似的东西。然后,设计一个LUT来管理软件的这一部分。
获得LUT后,请尝试以下操作:
如果您尝试了其中的一些操作,您很快就会发现在实施过程中利用LUT是多么重要。如果您有策略地使用LUT,您会发现您的代码将更具可配置性和可扩展性,这可能有助于您编写更高质量的软件并按时交付。
(原文刊登于EDN姊妹网站Embedded,参考链接:Use LUTs to ease your development burden,由Ricardo Xie编译。)