如何使您的嵌入式 C 代码不受需求变化的影响,而不增加太多的开销和复杂性?
在许多嵌入式应用程序中,需要在使代码非常高效或将代码与特定系统配置隔离以免受不断变化的需求影响之间进行权衡。
您通常采用什么类型的 C 构造来实现两全其美(灵活性和可重新配置性而不损失效率)?
如果您有时间,请继续阅读以了解我在说什么。
当我为安全气囊控制器开发嵌入式软件时,我们遇到了这样的问题:每当客户改变对特定要求的想法时,我们就必须更改代码的某些部分。 例如,在开发过程中,触发安全气囊展开的条件和事件的组合每隔几周就会发生变化。 我们讨厌经常更改那段代码。
当时,我参加了嵌入式系统大会,听到了 Stephen Mellor 的精彩演讲,题为“应对不断变化的需求”。 您可以在此处阅读该论文(他们要求您注册,但免费)。
其主要思想是在代码中实现核心行为,但以数据的形式配置具体细节。 您可以轻松更改数据,甚至可以在 EEPROM 或闪存的不同部分中对其进行编程。
这个想法听起来很适合解决我们的问题。 我与同事分享了这一点,我们立即开始重新设计一些软件模块。
当我们尝试在编码中使用这个想法时,我们在实际实现中遇到了一些困难。 对于受限的嵌入式系统,我们的代码构造变得非常繁重和复杂。
为了说明这一点,我将详细说明上面提到的示例。 我们没有用一堆 if 语句来决定输入组合是否处于需要安全气囊展开的状态,而是改为一张大表。 有些条件并不简单,因此我们使用了很多函数指针来调用很多小辅助函数,从而以某种方式解决了一些条件。 我们有几个间接层次,一切都变得难以理解。 长话短说,我们最终使用了大量的内存、运行时间和代码复杂性。 调试这个东西也不是那么简单。 老板让我们改回一些东西,因为模块变得太重了(他也许是对的!)。
PS:SO中也有类似的问题,但看起来焦点不同。 适应不断变化的业务需求?
In many embedded applications there is a tradeoff between making the code very efficient or isolating the code from the specific system configuration to be immune to changing requirements.
What kinds of C constructs do you usually employ to achieve the best of both worlds (flexibility and reconfigurabilty without losing efficiency)?
If you have the time, please read on to see exactly what I am talking about.
When I was developing embedded SW for airbag controllers, we had the problem that we had to change some parts of the code every time the customer changed their mind regarding the specific requirements. For example, the combination of conditions and events that would trigger the airbag deployment changed every couple weeks during development. We hated to change that piece of code so often.
At that time, I attended the Embedded Systems Conference and heard a brilliant presentation by Stephen Mellor called "Coping with changing requirements". You can read the paper here (they make you sign-up but it's free).
The main idea of this was to implement the core behavior in your code but configure the specific details in the form of data. The data is something you can change easily and it can even be programmable in EEPROM or a different section of flash.
This idea sounded great to solve our problem. I shared this with my colleague and we immediately started reworking some of the SW modules.
When trying to use this idea in our coding, we encountered some difficulty in the actual implementation. Our code constructs got terribly heavy and complex for a constrained embedded system.
To illustrate this I will elaborate on the example I mentioned above. Instead of having a a bunch of if-statements to decide if the combination of inputs was in a state that required an airbag deployment, we changed to a big table of tables. Some of the conditions were not trivial, so we used a lot of function pointers to be able to call lots of little helper functions which somehow resolved some of the conditions. We had several levels of indirection and everything became hard to understand. To make a long story short, we ended up using a lot of memory, runtime and code complexity. Debugging the thing was not straightforward either. The boss made us change some things back because the modules were getting too heavy (and he was maybe right!).
PS: There is a similar question in SO but it looks like the focus is different. Adapting to meet changing business requirements?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
作为改变需求的另一种观点......需求进入构建代码。 那么为什么不采取元方法来解决这个问题:
这样您就可以在 C 中维护兼容的逻辑构建块...然后坚持最后将这些兼容的部分放在一起:
然后保持独立的条件:
另一个
您的构建脚本可能如下所示:
真正大声思考。 这样你就可以得到一个紧凑的二进制 blob,而无需读取 config。
这有点像使用覆盖 http://en.wikipedia .org/wiki/Overlay_(programming) 但在编译时进行。
As another point of view on changing requirements ... requirements go into building the code. So why not take a meta-approach to this:
This way you are maintaining compatible logic-building blocks in C ... and then sticking those compatible parts together at the end:
Then maintain independent conditions:
and another
Your build script could look like:
Thinking outloud really. This way you get a tight binary blob without reading in config.
This is sort of like using overlays http://en.wikipedia.org/wiki/Overlay_(programming) but doing it at compile-time.
我们的系统被细分为许多组件,并具有公开的配置和测试点。 有一个在启动时读取的配置文件,它实际上帮助我们实例化组件、将它们相互连接并配置它们的行为。
它非常像 C 语言中的 OO,偶尔会进行 hack 来实现继承之类的东西。
在国防/航空电子领域,软件升级受到非常严格的控制,您不能仅仅升级软件来解决问题……但是,由于某些奇怪的原因,您可以更新配置文件而无需进行重大斗争。 因此,能够在这些配置文件中指定很多实现对我们来说非常有用。
没有什么魔法,只是在设计系统时良好地分离关注点以及开发人员的一些远见。
Our system is subdivided into many components, with exposed configuration and test points. There is a configuration file that is read at start-up that actually helps us instantiate components, attach them to each other, and configure their behavior.
It's very OO-like, in C, with the occasional hack to implement something like inheritance.
In the defense/avionics world software upgrades are very strictly controlled, and you can't just upgrade SW to fix issues... however, for some bizarre reason you can update a configuration file without a major fight. So it's been darn useful for us to be able to specify a lot of our implementation in those configuration files.
There is no magic, just good separation of concerns when designing the system and a bit of foresight on the part of the developers.
你究竟想拯救什么? 代码重新工作的努力? 软件版本发布的繁文缛节?
更改代码可能相当简单,并且很可能比更改表中的数据更容易。 仅当出于某种原因修改数据比修改代码更省力时,将经常更改的逻辑从代码转移到数据才有用。 如果变化能够更好地以数据形式表达(例如存储在 EEPROM 中的数字参数),则可能是这样。 或者,如果客户的要求使得有必要发布新版本的软件,并且新的软件版本的构建过程成本高昂(大量文书工作,或者可能是芯片制造商烧毁的 OTP 芯片),则可能是这样。
对于这类事情来说,模块化是非常好的原则。 听起来好像你已经在某种程度上做到了。 最好的目标是将经常更改的代码隔离到尽可能小的区域,并尝试使其余代码(“辅助”函数)保持独立(模块化)并尽可能稳定。
What are you trying to save exactly? Effort of code re-work? The red tape of a software version release?
It's possible that changing the code is reasonably straight-forward, and quite possibly easier than changing data in tables. Moving your often-changing logic from code to data is only helpful if, for some reason, it's less effort to modify data rather than code. That might be true if the changes are better expressed in a data form (e.g. numeric parameters stored in EEPROM). Or it might be true if the customer's requests make it necessary to release a new version of software, and a new software version is a costly procedure to build (lots of paperwork, or perhaps OTP chips burned by the chip maker).
Modularity is very good principle for these sort of things. Sounds as though you're already doing it to some degree. It's good to aim to isolate the often-changing code to as small an area as possible, and try to keep the rest of the code ("helper" functions) separate (modular) and as stable as possible.
我不会使代码本身不受需求更改的影响,但我总是通过在注释中放置唯一的字符串来标记实现需求的代码部分。 有了需求标签,当需求需要更改时,我可以轻松搜索该代码。 此实践也满足 CMMI 流程。
例如,在需求文档中:
并在代码中:
I don't make the code immune to requirements changes per se, but I always tag a section of code that implements a requirement by putting a unique string in a comment. With the requirements tags in place, I can easily search for that code when the requirement needs a change. This practice also satisfies a CMMI process.
For example, in the requirements document:
And in the code:
我想您可以做的是根据可以从 EEPROM 或 I/O 端口获取的数据字节或字(如有必要)指定几种有效行为,然后创建通用代码来处理这些字节描述的所有可能事件。
例如,如果您有一个字节指定释放安全气囊的要求,则可能类似于:
位 0:追尾碰撞
位 1:速度高于 55 英里/小时(概括速度值的奖励分!)
位 2:车内乘客
。 ..
等等
然后你拉入另一个字节来说明发生了什么事件并比较两者。 如果它们相同,则执行您的命令,如果不同,则不执行。
I suppose what you could do is to specify several valid behaviors based on a byte or word of data that you could fetch from EEPROM or an I/O port if necessary and then create generic code to handle all possible events described by those bytes.
For instance, if you had a byte that specified the requirements for releasing the airbag it could be something like:
Bit 0: Rear collision
Bit 1: Speed above 55mph (bonus points for generalizing the speed value!)
Bit 2: passenger in car
...
Etc
Then you pull in another byte that says what events happened and compare the two. If they're the same, execute your command, if not, don't.
为了适应不断变化的需求,我将集中精力使代码模块化并且易于更改,例如通过对可能更改的参数使用宏或内联函数。
对于可以独立于代码进行更改的配置,我希望在需求中也指定可重新配置的参数。 特别是对于安全关键的东西,如安全气囊控制器。
For adapting to changing requirements I would concentrate on making the code modular and easy to change, e.g. by using macros or inline functions for parameters which are likely to change.
W.r.t. a configuration which can be changed independently from the code, I would hope that the parameters which are reconfigurable are specified in the requirements, too. Especially for safety-critical stuff like airbag controllers.
如果您有足够的内存和处理器能力,使用动态语言进行挂钩可能会成为救星。
让 C 与硬件对话,然后将一组已知事件传递给 Lua 等语言。 让 Lua 脚本解析事件并回调到适当的 C 函数。
一旦你的 C 代码运行良好,你就不必再次接触它,除非硬件发生变化。 所有业务逻辑都成为脚本的一部分,在我看来,脚本更容易创建、修改和维护。
Hooking in a dynamic language can be a lifesaver, if you've got the memory and processor power for it.
Have the C talk to the hardware, and then pass up a known set of events to a language like Lua. Have the Lua script parse the event and callback to the appropriate C function(s).
Once you've got your C code running well, you won't have to touch it again unless the hardware changes. All of the business logic becomes part of the script, which in my opinion is a lot easier to create, modify and maintain.