测试司机箱子

2021-03-07 12:14:30

欢迎来到我们&#34的第二篇文章;测试嵌入式生锈"系列。在此博客文章中我们' ll涵盖如何测试平台无话用司机箱。

在嵌入式生态系统中,术语驱动程序箱是指使用像I 2C或SPI等通信协议接口一些外部组件(例如传感器,执行器)的通用库。这里的关键点是通用的:库实现不包含平台的特定详细信息如何进行I 2C或SPI事务 - 所有这些详细信息都隐藏在特征后面(界面的Rust术语)。由于驱动程序箱是通用的,它们可以用于从嵌入式Linux到小微控制器的各种平台。

箱子里的大多数司机箱。eCosystem在嵌入式 - 哈尔克匠队中发现的特征周围是通用的。本箱覆盖嵌入式设备上的接口,如串行(AKA UART),I 2C和SPI等嵌入式设备。使用驾驶员箱子箱子箱子&# 39; LL需要实现嵌入式HAL特征的硬件抽象层(HAL);您可以为在箱子上实施这些特征的微控制器找到大量的HALS.and,如果您自己编写了一个HAL,我们如何在我们以前的博客帖子中进行测试。

作为一个例子,假设您正在为SCD30编写一个司机箱,一所碳二氧化碳(二氧化碳)传感器,顺便说一下,这是我们第一个关于建造空气质量传感器的第一个滚花会话中的任务之一(注意:目前您只能访问此内容'重新提供赞助商。但是,所有滚花材料都公开了,所以我们'一旦它做了,我们会更新这个博客帖子!)

$#而不是`exa`(铁锈工具)您可以使用`tree`命令$#https://crates.io/crates/exa $ exa -a -i' .git *' -T。 ├──船舶。── - src││──18-5 - 目标测试├──.cargo│└──config.toml├──────────卢比

根包(./cargo.toml)是司机本身。目标测试文件夹中的第二个货舱包装司司裤。它将专门用于测试。目标测试将配置用于使用Config.Toml的交叉编译,因此请勿将这两个箱子放入货物工作空间(或您可能遇到构建问题或货物错误)。

让'首先查看驱动程序箱。API的核心将是一些通用结构。SCD30传感器具有I 2C接口,因此结构将在I 2C实现周围通用。

使用embedded_hal ::阻止:: i2c; /// I2C总线`I`的SCD30传感器,PUB结构SCD30<我> (i)其中//嵌入嵌入式hal特征i:i2c :: read + i2c :: write; ///驱动程序错误PUB枚举错误< E> {/// i2c总线错误I2C(e),/// CRC验证失败InvalidCRC,} iscl< e,i> SCD30<我>其中i:i2c ::读取<错误= E> + i2c ::写入<错误= E> ,{///初始化SCD30驱动程序///,这会消耗I2C总线`i`bub fn init(i2c:i) - > self {// ..} ///返回scd30 pub fn get_firmware_version(& mut self,) - &gt的固件版本。结果< [u8; 2],错误< E>> {// ..} //省略了e.g.读取CO2级别///销毁此驱动程序并释放I2C总线`I` PUB FN Destry(Self) - >一世 { // .. } }

因为API是通用的,您可以将该箱子编译到Linux,Windows,MacOS或微控制器.Having IC接口(特征),可以在没有任何嵌入式硬件上测试PC上的箱子 - 甚至需要SCD30这个!怎么样?使用模拟实现来模拟I 2C总线。

这个想法是这样的:你用I 2C模拟实现将驱动程序实例化,可以在没有额外的硬件上在PC上运行.Thankfly,在箱子里有一个解决方案.IO生态系统:嵌入式暂停模拟箱子。它的名字表明,这个箱子包含了几个嵌入式哈尔特拉特的模拟实现。在此示例中,我们' ll使用i2c :: mock实现。

假设我们想测试Get_Firmware_version功能。SCD30接口规范(v1.0)状态(第1.4.9节)查询固件版本涉及这2个I 2C事务:

对于那些与I 2C不熟悉的人,在一个写入事务中,数据从主机流到设备,并且在读取事务中,数据流动另一个方式。在我们的情况下,主机将是运行SCD30驱动器的机器,例如,该机器。微控制器,设备将成为SCD30传感器。

在I 2C中,主机可以与连接到相同总线的多个设备(电接口)通信。要将一个设备与另一个设备区分地区,每个设备具有不同的地址。AN I 2C事务(在任一方向上)从标题开始。较低此标题的位(位0)表示方向:读取(1)或写入(0)事务。头部的7位是I 2C设备地址。如果我们向这些标题(0xc2或0xc3中的任一个换档)通过一个比特(>> 1)我们获取SCD30传感器的I 2C地址:0x61。

读取事务中的SCD30响应包含3个字节。前两个字节是固件版本:第一个字节是主要组件;第二个是次要组件。响应中的第三个字节是前两个字节的校验和(CRC)。根据接口规范,SCD30的固件版本始终为3.66。 0)文档。

//此代码生存在“测试”模块中使用Embedded_Hal_Mock :: I2C;使用super :: {错误,scd30}; #[test] fn firmware_version(){让期望= VEC! [I2C :: Transaction :: Write(0x61,Vec![0xD1,0x00]),I2C :: Transaction :: Read(0x61,Vec![0x03,0x42,0xF3]),];让模仿= I2C :: Mock :: New(&期望);让mut scd30 = scd30 :: init(模拟);让Version = SCD30 .get_firmware_version().unwrap(); assert_eq! ([3,66],版本);让mut mock = scd30 .destroy();模拟.done(); //验证期望}

要创建I 2C模拟,我们需要通过一些预期来传递。这些预期是Mock.we&#39所期望的I 2C事务; LL仅在这里测试Get_Firmware_version函数,所以只有2个交易。

请注意,事务构造函数的第一个参数是SCD30 I 2C设备地址,而不是事务的标题。

一旦我们拥有I 2C模拟,我们就可以将SCD30驱动程序与它实例化.we' ll然后在驱动程序上调用get_firmware_version方法并断言它返回3.66版。

一旦我们使用Mock完成,我们需要调用其完成方法。这方法验证了所有的预期;如果不是,则失败测试的方法泛滥。I2C :: Mock还将恐慌,如果您尝试执行它的预期集中未列出的事务,则会恐慌。

如果您的货物测试您的箱子并正确实现Get_Firmware_version方法,那么上述测试应该通过!

关于使用模拟的很酷的事情就是你可以模拟用真正的硬件生产的失败。对于实例,它,它不太可能与您' ll能够使SCD30传感器报告其存在糟糕的校验和对I 2C总线的干扰来重新且昂贵的响应,以在传感器响应中翻转一些位。用模拟可以轻松模拟这些错误并验证这些方案中的驱动程序箱是否正确行为.Let&#39 ; s看看如何为get_firmware_version API执行此操作。

#[test] fn firmware_version_bad_crc(){让期望= Vec! [事务::编写(0x61,Vec![0xd1,0x00]),//注意响应中的否定CRC字节!交易::读(0x61,Vec![0x03,0x42,!0xF3]),];让模仿= I2C :: Mock :: New(&期望);让mut scd30 = scd30 :: init(模拟);让res = scd30 .get_firmware_version(); assert_eq! (err(错误:: Invalidcrc),Res); scd30 .destroy().done(); //验证期望}

在这里,我们的测试与前一个相似的测试,但包括SCD30响应中的假误差:第三个字节,CRC被否定,这意味着所有位都被翻转。这应该被检测为错误我们的SCD30驱动程序因为校验和无效所以这次我们断言get_firmware_version返回InvalidCRC错误。

您可以使用模型来测试驱动程序箱代码的事实并不意味着应该专门使用模型来测试代码。可能会将您的代码库的部分提取为纯粹的,这意味着它们不会像我一样执行副作用/ o。然后可以使用库存测试功能轻松测试这些部件 - 没有使用模拟的开销。

在SCD30驱动程序箱中的一个很好的例子是校验和功能。来自传感器的响应包括CRC8校验和;接口规范文档介绍如何计算它并包含一些示例。在这种情况下,您可以将CRC计算重构为' s中使用的辅助函数,涉及I 2C通信的功能。辅助功能是纯粹的:它需要一些字节并返回一个字节,因此它可以在没有模型的情况下以隔离进行测试。

fn compute_crc(bytes:& [u8]) - > u8 {// ..}# (超级:: compute_crc(& [0xbe,0xef]),0x92); }}

现在是对待实施的时间,到目前为止仅在主机上进行测试,实际上是。这个想法是将SCD30驱动程序与现有的HAL箱,而不是模拟,测试。

本部分需要实际硬件:在此示例中,除了SCD30传感器之外,我们还使用NRF52840开发套件(DK)。这些实际硬件测试将居住在我们以前创建的目标测试文件夹中。配置文件夹对于交叉编译并设置探测运行和DEFMT-TEST for Target-Target测试.we' ll使用与我们之前的博客文章相同的设置,以便我们在此重复'重复这些步骤。

结构状态{SCD30:SCD30< Twim< Twim0>> ,}#[defmt_test :: tests] mod tests {使用defmt :: {assert_eq,unwrap};使用nrf52840_hal :: {gpio :: p0,twim :: {self,twim},}; #[init] fn setup() - >超级::陈述{让外围设备= unwrap! (nrf52840_hal :: pac ::外围设备:: take());让PINS = P0 ::零件::新(外设.p0);让scl = pins .p0_30 .into_floation_input().degrade();让SDA = PINS .p0_31 .into_floation_input().degrade();让PIN = TWIM :: PIN {SCL,SDA};让i2c = twim :: new(外设.twim0,引脚,twim ::频率:: k100,);让SCD30 = SCD30 :: SCD30 :: init(I2C);州{SCD30}}

#[test] fn confign_firmware_id(州:& mut状态){const预期:[U8; 2] = [3,66];让Firmware_id = state .scd30 .get_firmware_version().unwrap(); assert_eq! (预期,固件_ID); }}

现在,如果驾驶员实现在第一次尝试的真实硬件上使用真正的硬件,则不会感到惊讶!如果您首先用Mocks测试它,它会经常在我们的经验中进行测试。

如何能够测试涉及读取CO 2浓度的操作的其余部分的SCD30 API?在测试SCD30功能的其余部分时可能不是预期的响应,而只是断言其余API返回好,而不是例如块永远或返回一些I 2C或CRC错误是一个很好的开始。在CO 2浓度的情况下,您可以断言返回的F32值在某个合理范围内(例如,每百万份小于1,000份表示良好的空气交换室内空间),这不是Nan值或无穷大。

在这篇文章中,我们已经看到了如何在没有使用模型和嵌入式硬件的情况下使用额外的硬件测试驱动程序箱,使用(defmt-test)目标测试。

到目前为止在这篇文章系列中,我们已经看到了在主机(构建机器)上运行的测试和在目标上运行的测试(例如,微控制器)。在下一个博客文章我们' LL介绍第三种类型的测试#39;在测试完整嵌入式应用程序的上下文中有用。

滚环-RS主要通过GitHub赞助商提供资金。赞助商从我们建造的工具获得早期访问,并帮助我们支持和发展滚花工具和课程。感谢所有人都通过滚花项目赞助我们的工作!