如何在嵌入式设备上进行TDD?


17

我对编程并不陌生,甚至在AVR上使用过一些低级C和ASM,但我确实无法涉足更大规模的嵌入式C项目。

由于Ruby的TDD / BDD理念退化,我无法理解人们是如何编写和测试这样的代码的。我并不是说这是一个不好的代码,我只是不明白这是如何工作的。

我想更多地参与一些底层编程,但是我真的不知道该如何处理,因为它看起来就像是我习惯的完全不同的思维方式。我在理解指针算术或分配内存的方式方面没有问题,但是当我看到与Ruby相比C / C ++代码看起来多么复杂时,这似乎很难。

由于我已经为自己订购了Arduino开发板,所以我很想深入了解一些低级C并真正了解如何正确地做事,但是似乎高级语言的规则都不适用。

甚至有可能在嵌入式设备上或在开发驱动程序或自定义引导加载程序之类的东西时进行TDD吗?


3
嗨,达斯(Darth),我们确实不能摆脱您对C的恐惧,但是关于嵌入式设备上TDD的问题在这里很重要:我已经对您的问题进行了修订,以使其具有特色。

Answers:


18

首先,您应该知道,尝试理解未编写的代码比自己编写代码难5倍。您可以通过阅读生产代码来学习C,但是这比做中学的时间要长得多。

由于Ruby的TDD / BDD理念退化,我无法理解人们是如何编写和测试这样的代码的。我并不是说这是一个不好的代码,我只是不明白这是如何工作的。

这是一种技巧;你会变得更好。大多数C程序员都不了解人们如何使用Ruby,但这并不意味着他们不能。

甚至有可能在嵌入式设备上或在开发驱动程序或自定义引导加载程序之类的东西时进行TDD吗?

好吧,有关于这个主题的书:

在此处输入图片说明 如果大黄蜂可以做到,您也可以!

请记住,采用其他语言的做法通常不起作用。TDD非常普遍。


2
我在嵌入式系统上看到的每个TDD都只发现了系统中容易解决的错误,而这些错误本人很容易解决。他们永远找不到我需要的帮助,即与其他芯片的时间相关交互以及中断交互。
Kortuk

3
这取决于您正在使用哪种系统。我发现使用TDD来测试软件,再加上良好的硬件抽象,实际上使我能够更轻松地模拟那些与时间有关的交互。人们经常查看的另一个好处是,自动化的测试可以在任何时间运行,并且不需要有人坐在逻辑分析仪上就可以确保软件能够正常工作。TDD仅在我当前的项目中就节省了数周的调试时间。通常,正是我们认为容易发现的错误才导致了我们意想不到的错误。
Nick Pascucci

加上它允许开发和测试脱靶。
cp.engr

我可以按照本书来理解非嵌入式C的TDD吗?对于任何用户空间的C编程?
过度兑换

15

这里有各种各样的答案...主要以各种方式解决该问题。

25年来,我一直使用多种语言(主要是C语言)编写嵌入式低级软件和固件(但后来转移到了Ada,Occam2,PL / M和各种汇编程序中)。

经过长时间的思考和反复试验后,我决定使用一种方法,该方法可以相当快速地获取结果,并且很容易创建测试包装和工具(它们可以增加价值!)

该方法是这样的:

  1. 为要使用的每个主要外围设备编写驱动程序或硬件抽象代码单元。还要编写一个以初始化处理器并进行所有设置(这使环境变得友好)。通常在小型嵌入式处理器上(以您的AVR为例),可能会有10到20个这样的单元,全部很小。这些可能是用于初始化,将A / D转换为未缩放内存缓冲区,按位输出,按钮输入(仅采样后不进行反跳),脉宽调制驱动器,UART /简单串行驱动器,使用中断和小型I / O缓冲区的单元。可能还有更多-例如用于EEPROM,EPROM或其他I2C / SPI设备的I2C或SPI驱动程序。

  2. 然后,对于每个硬件抽象(HAL)/驱动程序单元,我编写一个测试程序。这依赖于串行端口(UART)和处理器初始化-因此第一个测试程序仅使用这两个单元,并且仅执行一些基本的输入和输出。这可以让我测试是否可以启动处理器以及是否具有基本的调试支持串行I / O。一旦可行(并且只有这样),我才可以开发其他HAL测试程序,并在已知的优秀UART和INIT单元上构建这些程序。因此,我可能有一些测试程序,用于读取按位输入并在串行调试终端上以一种不错的形式(十六进制,十进制等)显示这些输入。然后,我可以进入更大而更复杂的事物,例如EEPROM或EPROM测试程序-我使大多数菜单受驱动,因此我可以选择要运行的测试,运行它并查看结果。我不能脚本,但通常我不

  3. 一旦我的所有HAL运行完毕,我便找到一种获取常规计时器刻度的方法。该速率通常在4到20毫秒之间。这必须是常规的,在中断中生成。计数器的翻转/溢出通常是可以做到的。然后,中断处理程序增加一个字节大小的“信号量”。此时,如果需要,您还可以摆弄电源管理。信号量的想法是,如果其值> 0,则需要运行“主循环”。

  4. EXECUTIVE运行主循环。它几乎只是等待该信号量变为非0(我将这个细节抽象掉了)。此时,您可以使用计数器来计算这些报价(因为您知道报价速度),因此您可以设置标志来显示当前执行价是否间隔1秒,1分钟以及其他常见间隔可能要使用。一旦执行人员知道信号量> 0,它就会通过每个“应用程序”进程“更新”功能运行一次。

  5. 应用程序进程有效地并排放置,并通过“更新”标记定期运行。这只是执行人员调用的功能。这实际上是穷人的多任务处理,它具有非常简单的本地RTOS,该RTOS依赖于所有进入,执行少量工作和退出的应用程序。应用程序需要维护自己的状态变量,并且不能进行长时间运行的计算,因为没有抢先式操作系统可以强制公平。显然,应用程序的运行时间(总计)应该小于主时钟周期。

上面的方法很容易扩展,因此您可以添加诸如异步运行的通信堆栈之类的东西,然后可以将comms消息传递到应用程序(向每个函数添加一个新函数,即“ rx_message_handler”,然后编写一个消息调度程序,派遣到哪个应用程序)。

这种方法几乎适用于您要命名的任何通信系统-它可以(并且已经完成)用于许多专有系统,开放标准通信系统,甚至可以用于TCP / IP堆栈。

它还具有在具有明确定义的接口的模块化部件中构建的优势。您可以随时拉入和拉出零件,替换为其他零件。在此过程中的每个点上,您都可以添加测试工具或处理程序,这些处理程序或处理程序基于已知的良好底层组件(以下内容)。我发现,大约30%到50%的设计可以通过添加通常很容易添加的特别编写的单元测试而受益。

我已将所有步骤都更进一步了(这个想法我已经从做过此事的其他人那里汲取了灵感),并用等效的PC代替了HAL层。因此,例如,您可以在PC上使用C / C ++和winforms或类似软件,并通过仔细编写代码可以模拟每个接口(例如EEPROM =读入PC内存的磁盘文件),然后在PC上运行整个嵌入式应用程序。使用友好的调试环境的能力可以节省大量的时间和精力。通常只有真正大型的项目才能证明这种努力的合理性。

上面的描述并不是我在嵌入式平台上做事的方式所独有的-我遇到了许多从事类似工作的商业组织。它的完成方式通常在实现上有很大的不同,但是原理通常是相同的。

我希望以上内容能给您带来些帮助...这种方法适用于运行在几kB的小型嵌入式系统中,并具有积极的电池管理能力,甚至可以提供100K或更多永久供电的电源线。如果您在Windows CE等大型操作系统上运行“嵌入式”,则上述所有内容都不重要。但这不是真正的嵌入式编程。


2
您无法通过UART测试的大多数硬件外设,因为通常您主要对时序特性感兴趣。如果要检查ADC采样率,PWM占空比,其他串行外设(SPI,CAN等)的行为,或者仅检查程序某些部分的执行时间,则无法通过UART。任何认真的嵌入式固件测试都包括一台示波器-如果没有嵌入式示波器,则无法对嵌入式系统进行编程。

1
哦,是的,绝对如此。我只是忘了提到那一个。但是一旦您的UART启动并运行,它就很容易设置测试或测试用例(这就是问题所在),刺激事物,允许用户输入,获得结果并以友好的方式显示。这+您的CRO使生活变得非常轻松。
quick_now 2011年

2

对于多个平台,具有悠久的增量开发和优化历史的代码(例如您选择的示例)通常较难阅读。

关于C的事情是,它实际上能够跨越各种API丰富性和硬件性能(以及缺乏)的平台。MacVim可以在内存和处理器性能比当今的典型智能手机少1000倍的机器上运行。您的Ruby代码可以吗?这就是它看起来比您选择的成熟C示例更简单的原因之一。


2

相反,我过去9年中的大部分时间都是C程序员,最近又在Ruby on Rails前端上工作。

我在C语言中从事的工作大部分是用于控制自动化仓库的中型定制系统(典型成本为几十万英镑,最高可达几百万英镑)。示例功能是一个自定义的内存数据库,可连接到响应时间较短且对仓库工作流进行更高级别管理的机器。

我可以首先说,我们不做任何TDD。我曾尝试过多次介绍单元测试,但是在C语言中,这麻烦多于其价值,至少在开发定制软件时如此。但是我想说,TDD在C语言中比在Ruby中需要的少得多。主要是因为编译了C,并且如果编译时没有警告,则您已经完成了与Rails中的rspec自动生成的脚手架测试相当数量的测试。没有单元测试的Ruby是不可行的。

但是我要说的是,C不必像某些人那样难。许多C标准库都是一堆难以理解的函数名,许多C程序都遵循此约定。我很高兴地说我们没有,实际上有很多用于标准库功能的包装器(ST_Copy代替strncpy,ST_PatternMatch代替regcomp / regexec,CHARSET_Convert代替iconv_open / iconv / iconv_close等)。我们内部的C代码对我的阅读要比我读过的大多数其他东西要好。

但是当您说其他高级语言的规则似乎不适用时,我会不同意。很多好的C代码“感觉”面向对象。您经常看到一种初始化资源句柄,调用将句柄作为参数传递的函数并最终释放资源的模式。确实,面向对象编程的设计原理主要来自人们在过程语言中所做的美好事情。

C变得非常复杂的时候通常是在做诸如设备驱动程序和OS内核之类的事情时,这些事情从根本上说是非常底层的。在编写更高级别的系统时,还可以使用C的更高级别的功能,并避免低级别的复杂性。

您可能想要浏览的一件非常有趣的事情是Ruby的C源代码。在Ruby API文档(http://www.ruby-doc.org/core-1.9.3/)中,您可以单击并查看各种方法的源代码。有趣的是,这段代码看起来非常漂亮和雅致-看起来并不像您想象的那么复杂。


……您还可以使用C……的更高级别的功能 ”,有吗?;-)
alk

我的意思是比您通常在设备驱动程序类型代码中看到的位操作和指针向导更高的级别!而且,如果您不担心几个函数调用的开销,则可以制作看起来相当高级的C代码。
asc99c 2011年

……您可以使C代码看起来确实相当高级。 ”,绝对地,我完全同意这一点。但是,尽管“ ...高级功能... ”不是C的,但在您的脑海中,不是吗?
ALK

2

我所做的是将与设备无关的代码与与设备无关的代码分离出来,然后测试与设备无关的代码。有了良好的模块化和纪律,你会会风了一个大多经过良好测试的代码库。


2

没有理由不能这样做。问题在于,可能没有像其他类型的开发中那样好的“现成的”单元测试框架。没关系。这仅意味着您必须采用“自行解决”的方法进行测试。

例如,您可能必须对仪器进行编程才能为A / D转换器生成“伪输入”,或者可能必须生成“伪数据”流以供嵌入式设备响应。

如果遇到使用“ TDD”一词的阻力,可将其称为“ DVT”(设计验证测试),这将使EE对这个想法更加满意。


0

甚至有可能在嵌入式设备上或在开发驱动程序或自定义引导加载程序之类的东西时进行TDD吗?

前段时间,我需要为ARM CPU编写一级引导程序。实际上,有一个人出售这种CPU。我们使用了一种方案,其中他们的引导程序引导我们的引导程序。但这很慢,因为我们需要将两个文件闪存到NOR闪存中,而不是一个,我们需要将引导加载程序的大小构建到第一个引导加载程序中,并在每次更改引导加载程序时进行重建,依此类推。

因此,我决定将其引导程序的功能集成到我们的引导程序中。因为它是商业代码,所以我必须确保所有事情都按预期工作。因此,我修改了QEMU以模拟该CPU的IP块(不是全部,仅是那些接触引导加载程序的IP块),然后将代码添加到QEMU以“ printf”全部读/写到控制PLL,UART,SRAM控制器和以此类推。然后,我升级了引导加载程序以支持该CPU,然后比较了提供给引导加载程序及其仿真器的输出,这有助于我发现一些错误。它部分是用ARM汇编程序编写的,部分是用C语言编写的。在修改后的QEMU帮助我发现了一个错误之后,我还是无法使用JTAG和真正的ARM CPU来捕捉它。

因此,即使使用C和汇编器,您也可以使用测试。


-2

是的,可以在嵌入式软件上进行TDD。人们说这是不可能的,不相关的或不适用的是不正确的。与任何软件一样,从嵌入式TDD中获得巨大价值。

他们最好的方法不是在目标上运行测试,而是抽象硬件依赖关系并在主机PC上编译和运行。

在进行TDD时,您将创建并运行许多测试。您需要软件来帮助您做到这一点。您需要一个测试框架,使其能够通过自动测试发现和模拟生成来快速,轻松地执行此操作。

目前,C语言的最佳选择是Ceedling。这是我写过的一则帖子:

http://www.electronvector.com/blog/try-embedded-test-driven-development-right-now-with-ceedling

它是内置在Ruby中的!您无需了解任何 Ruby即可使用它。


答案应独立存在。在Stack Exchange上,强迫读者寻求外部资源来查明实质的内容(“阅读文章或查看Ceedling”)。考虑进行编辑以使其符合站点质量规范
gnat'1

Ceedling是否有任何机制支持异步事件?实时嵌入式应用程序更具挑战性的方面之一是,它们要处理来自非常复杂的系统的输入,而这些系统本身很难建模...
Jay Elston

@Jay它没有专门支持的东西。但是,我已经通过模拟并通过建立支持它的体系结构成功测试了这种事情。例如,我最近在一个项目中工作,该项目将中断驱动的事件放入队列,然后在“事件处理程序”状态机中使用。本质上,这只是一个事件发生时调用的函数。测试该函数时,我可以模拟将事件从队列中拉出的函数调用,从而可以模拟系统中发生的任何事件。试驾在这里也有帮助。
cherno
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.