在许多嵌入式应用程序中,需要在使代码非常高效或将代码与特定系统配置隔离以免受不断变化的需求之间进行权衡。
您通常采用哪种C结构来实现两全其美(灵活性和可重新配置而不会降低效率)?
如果你有时间,请继续阅读,看看我在说什么。
当我为安全气囊控制器开发嵌入式软件时,我们遇到的问题是,每当客户改变他们对特定要求的想法时,我们就必须更改代码的某些部分。例如,在开发期间每隔几周就会发生触发安全气囊展开的条件和事件的组合。我们讨厌经常更改那段代码。
那时,我参加了嵌入式系统大会,并听取了Stephen Mellor的精彩演讲,题为“应对不断变化的需求”。您可以阅读论文here(它们会让您注册,但它是免费的)。
这样做的主要思想是在代码中实现核心行为,但是以数据的形式配置特定的细节。数据可以轻松更改,甚至可以在EEPROM或闪存的不同部分进行编程。
这个想法对解决我们的问题听起来很棒。我和我的同事分享了这个,我们立即开始重新设计一些软件模块。
当我们在编码中尝试使用这个想法时,我们在实际实现中遇到了一些困难。对于受约束的嵌入式系统,我们的代码构造非常繁重和复杂。
为了说明这一点,我将详细说明我上面提到的例子。我们没有使用一堆if语句来决定输入组合是否处于需要安全气囊展开的状态,而是改为大表。有些条件不是很简单,所以我们使用了很多函数指针来调用许多小助手函数,这些函数以某种方式解决了一些条件。我们有几个层次的间接,一切都变得难以理解。总而言之,我们最终使用了大量的内存,运行时和代码复杂性。调试这个东西也不简单。老板让我们改变了一些东西,因为模块太重了(他可能是对的!)。
PS:在SO中有一个类似的问题,但看起来焦点是不同的。 Adapting to meet changing business requirements?答案 0 :(得分:3)
另一种观点是关于需求的变化......需求进入构建代码。那么为什么不采用元方法呢?
这样你就可以在C中维护兼容的逻辑构建块......然后在最后将这些兼容的部分粘在一起:
/* {conditions_for_airbag_placeholder} */
if( require_deployment)
trigger_gas_release()
然后保持独立的条件:
/* VAG Condition */
if( poll_vag_collision_event() )
require_deployment=1
和另一个
/* Ford Conditions */
if( ford_interrupt( FRONT_NEARSIDE_COLLISION ))
require_deploymen=1
您的构建脚本可能如下所示:
BUILD airbag_deployment_logic.c WITH vag_events
TEST airbag_deployment_blob WITH vag_event_emitter
真的在外面思考。这样你就可以获得一个紧凑的二进制blob而无需读取配置。 这有点像使用叠加 http://en.wikipedia.org/wiki/Overlay_(programming),但是在编译时这样做。
答案 1 :(得分:2)
我们的系统细分为许多组件,具有暴露的配置和测试点。在启动时读取的配置文件实际上可以帮助我们实例化组件,将它们相互连接,并配置它们的行为。
在C语言中,它非常类似于OO,偶尔会出现像继承一样的黑客行为。
在防御/航空电子世界中,软件升级受到严格控制,你不能只升级软件来修复问题......但是,出于某些奇怪的原因,你可以在没有重大战斗的情况下更新配置文件。因此,能够在这些配置文件中指定大量实现对我们来说非常有用。
在设计系统和开发人员的一些远见时,没有什么神奇之处,只需要很好地分离关注点。
答案 2 :(得分:2)
你想要准确保存什么?代码的努力重新工作?软件版本的繁文缛节?
更改代码可能是相当直接的,并且可能比更改表中的数据更容易。将经常变化的逻辑从代码移动到数据只有在由于某种原因,修改数据而不是代码的努力较少时才有用。如果更改在数据形式中更好地表达(例如存储在EEPROM中的数字参数),则可能是这样。或者,如果客户的请求需要发布新版本的软件,并且新的软件版本是一个昂贵的构建过程(大量的文书工作,或者芯片制造商烧毁的OTP芯片),则可能是真的。
模块化是这类事情的非常好的原则。听起来好像你已经在某种程度上做到了。目标是将经常变化的代码隔离到尽可能小的区域,并尝试将其余代码(“辅助”函数)分开(模块化)并尽可能保持稳定。
答案 3 :(得分:2)
我不会使代码本身不受需求更改的限制,但我总是通过在注释中添加唯一字符串来标记实现需求的代码段。有了需求标签,我可以在需求需要更改时轻松搜索该代码。这种做法也满足CMMI过程。
例如,在需求文档中:
以下是一份清单 与RST相关的要求:
- [RST001] Juliet应在点火时延迟5分钟开始RST 关闭。
在代码中:
/* Delay for RST when ignition is turned off [RST001] */
#define IGN_OFF_RST_DELAY 5
...snip...
/* Start RST with designated delay [RST001] */
if (IS_ROMEO_ON())
{
rst_set_timer(IGN_OFF_RST_DELAY);
}
答案 4 :(得分:1)
我想你可以做的是根据你可以从EEPROM或I / O端口获取的数据的字节或字来指定几个有效行为,然后创建通用代码来处理那些描述的所有可能事件字节。
例如,如果您有一个指定释放安全气囊要求的字节,它可能是这样的:
位0:后方碰撞
位1:速度超过55mph(用于概括速度值的奖励积分!)
第2位:乘客在车上
...
等
然后你拉入另一个字节,说明发生了什么事件并比较两者。如果它们是相同的,则执行命令,否则,执行命令。
答案 5 :(得分:1)
为了适应不断变化的要求,我将专注于使代码模块化并易于更改,例如:通过对可能发生变化的参数使用宏或内联函数。
W.r.t。一个可以独立于代码更改的配置,我希望可重新配置的参数也在需求中指定。特别是对安全气囊控制器等安全关键的东西。
答案 6 :(得分:0)
如果你有内存和处理器能力,那么用动态语言挂钩可以成为救星。
让C与硬件通信,然后将一组已知事件传递给Lua等语言。让Lua脚本解析事件并回调到相应的C函数。
一旦你的C代码运行良好,除非硬件发生变化,否则你不必再次触摸它。所有业务逻辑都成为脚本的一部分,在我看来,创建,修改和维护起来要容易得多。