自动驾驶数据闭环与工程化 (下)

来源:焉知智能汽车
2022-08-16
1290

1.3. 测试与仿真的闭环

 

每个或大或小的独立研发闭环,也要为这个闭环主要的研发内容提供工程上可行的测试方法与测试环境。自动驾驶系统的软硬件又往往是异构平台,功能集成难度大,测试方式也比一般软件测试复杂,对测试工具的依赖也更多,会用到各种在环仿真系统。

 

图 5 表示出各种级别的测试已经仿真的范围和演进关系。我们从测试与仿真的层级、工具、执行者几个角度进行讨论。

 

单元测试与模块测试

 

单元测试主要是指函数级别的测试。理论上要求每一个独立函数、或者类型的成员函数都应该有对应的单元测试代码。单元测试的重要性,怎么说都不为过。单元测试中,我们要考核代码覆盖率和分支覆盖率,要求都达到100%。也就是每一行代码,每一个可能分支都被单元测试程序执行过。

 

单元测试还对软件架构的合理性有重要作用,这个很少被人提及。原因就是它可以考验软件架构在函数粒度上的可测性。

 

不是能够为每一个函数都写出有效的测试代码的,一个函数输入的参数组合可能有几万种,就不可能全被覆盖,一个函数的执行依赖函数外部的状态(比如全局变量),同样的输入,因为外部状态可能有不同的结果,单元测试也不好写(了解一下什么是纯函数)。一个函数干了太多事情,比如有两千行,单元测试基本就没法做了。

 

所以当能为所有函数写出单元测试代码时,就已经强迫目标代码在函数级别设计上做了优化:函数功能尽量单一,尽量写成纯函数,一个函数代码别太长等等。

 

可以看一个反面例子,请搜索:“丰田事件 1万多全局变量建了个超大bug基地”。

 

函数级别的单元测试关注点是检查函数在不同输入的情况下,其输出的正确性。而且这个函数最好内部不保存状态(纯函数)。这个测试范围再大一些,多个函数(或多个代码文件)一起协同工作,在多次关联调用之间需要保存状态,一般而言,我喜欢把这个范围称为功能模块。可以把模块理解为更大粒度的函数。模块有其对外公开的调用接口,已经可被观察到的对外相应,在内部有多个不公开的子模块或函数,有内部保存的状态。

 

从程序语言上看,一个模块相当于 Python 的模块,Rust 的模块,Java 的 package , C++ 的一个多个类的集和,C 语言关联度比较高的一个或多个代码文件。

 

对模块的测试关注点是不同输入情况下,模块的响应是否正确,模块的内部状态是否正确。

 

单元测试和模块测试一般都可以使用特定的单元测试框架来编写,例如 gtest 用来写 C++ 的单元测试,Rust 内置有单元测试机制。同时还有各种代码检查工具来验证代码是否符合编码规范,检查代码的圈复杂度,分析潜在的代码错误。

 

单元测试和模块测试的编写者就是开发该功能模块的程序员本身或其小组成员。一般来说程序员每次代码提交前,关联模块的单元测试和模块测试代码都应该能执行通过。每一处代码修改,与其关联到所有模块的单元测试代码都要被重新执行。

单元测试和模块测试的模拟数据一般就写在测试代码中,看测试框架的能力,也可以从外部加载测试数据。

 

单元测试和模块测试很重要,不幸的是,在研发中还是经常会出现没有单元测试的情况。原因很简单,项目太紧,没时间。没有单元测试不影响代码运行,只是某些情况没有被测试而已,这样的代码合并到整个系统中,就是潜在的风险。

 

子系统

 

模块往上是子系统。一个子系统表示能够完成某个方面的功能体系,比如完成了“视频采集子系统”“车辆的识别子系统”,“泊车路径规划子系统”,“用户交互子系统”等等。存在形式可能是Linux内核中的一个驱动,应用层上的一个独立的进程或一个 SOA 服务,MCU RTOS 上一个或多个关联到 TASK,CP AUTOSAR 上的一个 SWC 等等。

 

与模块测试一样的是,子系统一般会有内部状态,需要检查在响应外部输入过程中,内部状态的正确性。

 

一个子系统应该能够单独被测试,就是它能独立的在一个测试闭环中运行并被校验。然后再做加法,即把更多的系统一个一个的逐步加入进测试闭环中,最后形成整个系统。

 

子系统测试跟模块测试的差别在于模块测试的访问接口一般都是 API 级别,也就是模块的 API 函数会直接被测试框架进行调用。而子系统的接口往往是与其它子系统的之间的交互,可能是通过各种网络(Can, FlexRay,以太网等等),也可以是通过共享内存、信号量等进程间通讯机制。

 

这决定了子系统的测试不太能跟模块测试采用同样的工具。子系统的测试不光要提供模拟数据,还要有能把模拟数据送入子系统的方法,以及检测子系统响应结果的方法。

 

子系统的测试一般要采购一些专用工具,同时要开发一些工具。采购的工具往往有总线模拟类(如 CanOE)。还有整体的自动化测试框架,如 ECUTest 。也有开源的替代品,如Robot Framework。自己开发的工具需要能将采购的工具连接起来,或者为特定的设备或子系统接口开发驱动。

 

“发布订阅”设计模式对子系统测试有重要意义。因为“发布订阅”模式解耦了消息的生产者和消费者,所以我们测试一个子系统时,就可以比较方便的模拟出该子系统需要对数据发送给它,同时也可以通过接收该子系统发出的消息来校验其行为或内部状态是否正确。

 

无论是自动驾驶常用中间件ROS/ROS2、百度 Cyber RT、DDS,还有SOA的基础协议SOME/IP都是支持发布订阅模式的。

 

同构与异构

 

自动驾驶系统是一个异构系统。硬件上有高实时性的MCU和高性能多功能的 SoC芯片。MCU上运行RTOS系统,SoC 上有 Linux 或 QNX 也可以运行RTOS系统。SoC上有通用CPU,有用于渲染的GPU,有用于数学计算的 DSP 和深度学习的NPU,其中每个部分的软件实现方式也是不一样的。

 

单元测试、模块测试甚至基于CPU的子系统都可以在x86的开发设备上进行测试。但是如果子系统涉及到嵌入式平台的专用设备,其真实效果,尤其是性能表现只能到目标嵌入式平台进行测试。一般我们称之为处理器在环(Processor in Loop)。

所以整个测试闭环以及测试工具系统,需要考虑对嵌入式平台的支持。

 

图 5 测试与仿真的类型.png

图 5 测试与仿真的类型

 

X in Loop

 

除了异构子系统集成测试比较特殊之外,前面讲的测试概念其实跟一般软件测试没太大区别。自动驾驶系统测试中比较特殊是各种形式的在环测试。

 

1.2.3节中的闭环A是典型模型在环(MiL, Model in Loop), 闭环C是属于软件在环(SiL,Software in Loop),将模型生成了软件代码进行执行。

 

重点是Simulink 模型直接运行在快速原型设备里,仿真软件直接给出传感器的真值数据。也就是说就是仿真软件渲染了场景动画,也是给人看的,实际的真值直接发送给模型作为输入,当然真值数据怎么从仿真软件到模型需要工程师写代码来集成。

 

前文异构子系统集成测试就是一种处理器在环测试(PiL,Processor in Loop)。异构子系统的集成是可以将多个子系统逐步集成进来的。如果把视觉算法也运行在目标嵌入式平台处理器上,视觉数据从仿真软件中提取出来通过网络发送到嵌入式平台,那就是 PiL 和 MiL 的混合模式。这时候仿真软件渲染的图像是有用的,运行在嵌入式平台的视觉算法根据图像识别出目标发送给Simulink模型。比纯粹的 MiL ,这一部集成了更多内容进仿真闭环中。

 

标准的硬件在环(HiL,Hardware in Loop) 是要用正式量产的ECU硬件,而且数据与 ECU 硬件交互的方式都应该与实际在车上是一样。虽然车辆周围的环境是通过仿真模拟出来的,但从ECU本身的视角,几乎是分不出自己是在仿真测试环境还是在真实车上。这样整个测试的环境就与真实车辆更接近了了。

 

在PiL测试中,如果需要使用原始的图像数据,一般图像数据是通过对视频文件的回放,并通过网络发送到目标嵌入式平台(ECU),而 HiL 则是直接通过物理设备将仿真出来的图像转成原始图像格式注入到 ECU 的视频采集端口,只是通过仿真跳过了传感器模数转换和ISP 处理的过程。这样ECU的采集模块也备集成到测试闭环中了。当然,完成这个视频数据从仿真软件到ECU视频端口,需要专门的硬件设备,这就是HiL台架的作用了。

 

车辆在环仿真(ViL, Vehicle in Loop) 实际上跟 HiL 很接近,但是全套HiL设备要能小型化后装到车上,控制算法的执行不是基于仿真软件的动力学模型,而是实际物理车辆的控制系统。仿真软件把模拟的场景注入到ECU,相当于物理汽车认为自己在某个道路场景中驾驶,ViL 跟HiL 的差别在于使用了真实的车辆控制系统。

 

表格 2 列出了各种测试和仿真的定义和说明。

微信截图_20220816160233.png

表格 2 各种测试与仿真类型的比较

 


02

产品级闭环

 

上一章讲的是研发闭环,偏重与研发与测试仿真的协同。这一章我们从产品的角度来讨论。

 

自动驾驶系统是复杂度非常高的系统,从产品角度看,它复杂到没有一个传统意义上的产品经理能把待开发的系统准确定义清楚。我遇到过有的公司想招自动驾驶产品经理,招了两年,产品部门走马灯似的换人,也不知道自己到底想要什么样的人。还有的JD上对产品经理的要求几乎是十项全能,像极了上海人民广场大妈手上的征婚条件,既要又要。

 

为什么自动驾驶领域的产品这么难定义?因为他不是一个单一产品,它是一个高度复合的产品,本身就是由一系列软硬件子产品组合而成,同时它又被装配到了“车辆”这个更复杂的产品中。然后这个车辆会在无限可能的交通场景中行驶。也就是说自动驾驶产品是多重复杂性的叠加,至少包括内部组成的复杂性,装配环境的复杂性,以及使用场景的复杂性。

 

在分析这种产品时,我们要把它分解成多个、多级子产品,对逐个子产品进行分析,然后分析各子产品如何构成完整的系统。每个子产品有其特定的关注点、技术领域,有其自己独立的演进方向,在整个系统中,某个子产品可能有多个可替代的选项。

 

在我另一篇文章《智能驾驶域控制器的软件架构及实现》(下) 中,我画了两张与产品分解相关的图。“图 6 软件架构鸟瞰图”讲述的是自动驾驶域控制器内部的软件构成,三个维度的分析和每一层的作用请看原文。这篇文章据此来分析内部子产品的分解。“图 7 四级产品架构”在图 6的基础上,向外延伸到自动驾驶工具链体系,向内以与自动驾驶的相关性再做了三级的层次化分解。本文中我们会更关注其中的工具链体系。

 图 6 软件架构鸟瞰图.png

图 6 软件架构鸟瞰图

 

图 7 四级产品架构.png

图 7 四级产品架构

 

2.1.各层产品的独特性

 

图 6 中横向“层级”这个维度,每一层的每一个域(性能域或实时域)都有其独特性,要解决的问题、涉及的技术领域、需要的专业技能、开发与测试的方式,差别非常大。

 

“产品”的概念

 

我们需要对“产品”的概念有一个定义。一般我们理解的“产品”是从终端用户角度去看到,比如一个咖啡机、一个手机、一台车,软件产品比如浏览器,音乐播放器、微信APP 等等。

 

我们把产品的概念抽象一下,如图 8所示,“产品”具有一系列“特性”,并提供“交互接口”给它的“用户”。

 

我们把“车”当成一个产品,它的特性就是“百公里加速时间、语音控制大屏、代客泊车功能、自动变道功能”等等,它提供了“方向盘、仪表盘、刹车、油门”等交互接口给任何具有驾驶能力的用户。

 

我们把“嵌入式Linux”作为一个产品,它的特性就是“启动时间、进程调度、内存管理和各种外设的支持能力、信息安全、通讯能力”等等,它提供符合POSIX 标准的API,还通过定制的内核驱动提供新设备的API访问接口,Linux 应用开发者可以基于这些API实现自己想要的应用功能。

 

这两个例子涉及的领域差距非常大,但它们都是产品。

 

图 8 产品概念示例.png

图 8 产品概念示例

 

如图 9所示,产品的交互接口、特性、特定的技术领域都是产品的内涵,它决定了产品的能力以及对外的交互方式。但产品还有其外延概念。

 

图 9 产品概念的内涵与外延.png

图 9 产品概念的内涵与外延

 

产品的外延至少包括了其市场定位、适用场合等,同时一个产品还需要有一定的独立性。所谓独立性是指它的设计、研发和销售是可以独立与其它产品进行的。比如说,我们可以独立的销售一张桌子,但是我们没办法只卖一支桌子腿。

 

产品的内涵与外延也是密切相关的,比如产品的独立性就与其对外的交互接口密切相关。

 

产品的独立性

 

自动驾驶系统内部的子产品构成这么复杂,引申出来其内部各子产品的独立性非常重要。各子产品相对独立就可以分到不同团队进行开发,某些子产品就可以直接从市场上采购成熟的产品,就可以使用不同供应商的产品进行替代。

 

汽车OEM的供应链管理最喜欢的事情就是同一个零部件有多家供应商。避免单一供应商无法供货时生产停滞,还可以在价格是有较好的谈判地位。

 

上汽说要掌握自己的“灵魂”,本质上也是这个产品独立性的问题。因为自动驾驶系统虽然需要域控制器硬件各种传感器硬件,但本质上任然是一个软件密集型的产品。其内部各组成部分(软件子产品)的切分就不如原先的硬件零部件那么清晰。以科技公司为主的供应商就会更倾向于软硬一体的解决方案,或者至少是在软件上提供整体的解决方案。而汽车OEM则希望软件硬件能分开,同时在软件组成上最好也能分拆成独立的子产品,这样就能在供应链上能保持更好的控制权。

 

产品内涵中的“特定技术领域”也能反映产品独立性的特征。图6中各层产品的技术领域差别非常大,相关研发人员的专业技能也相差很大,开发与测试的方法也有很大的差异。比如为嵌入式系统定制开发Linux的工程师技能与视觉算法的工程师差异非常大;能熟练在Linux上开发应用的工程师很少能熟练的编写 Linux 驱动;同样是算法工程师,从算法设计、算法实现、参数调优、嵌入式移植和优化,也需要很多不同技能的工程师来实现。所以这些不同的产品时需要不同技能背景的团队来各自独立的设计和开发。

 

当我们找一个设计用户级产品的产品经理来定义一个操作系统级的产品时,他是无法胜任的。我们让一个对操作系统非常有经验的专家去定义自动驾驶的具体功能及场景,他也需要补充很多知识,意味着要花更多时间。

 

2.2.纵横交叉的产品演进路线

 

子产品的独立性,让图6中的各层产品可以独立演进,可以交错组合成完整的客户交付产品。

 

横向分层产品的演进路线

 

图 10 中显示了每一层可能的产品演进方向。比如在“计算平台”,研发初期可以使用 x86 工控机或 Xavier 套件这样立即可得的设备开发必须的软件功能,当确定目标 SoC 平台后,可以使用该平台的原厂开发板,等基于目标平台的量产专用计算平台硬件开发完成后,再将软件系统切换过去。

 

对应的操作系统层,前期开发直接用 Ubuntu, 为了让开发人员环境一致,可以再用个 Docker;到了目标平台,就要用 Yocto 定制化一个适合的 Linux 系统,这也是目前大多数平台采用的方式。随着自动驾驶SoC 芯片性能越来越强,核心数越来越多,相应的虚拟化技术和容器技术也会被使用,相应的研究和开发可以先开始做,然后用于将来的产品。

 

图 10 横向分层产品演进路线.png

图 10 横向分层产品演进路线

 

中间件这一层,一般习惯于先基于 ROS或 ROS2开发原型系统,再转向可以用于量产的中间件,如 Adaptive AUTOSAR 或百度 CyberRT 等。

 

纵向产品组合路径

 

图 10中的每一层从左到右是单层的某个产品的演进路线。但是任何单独一层的产品是无法构成完整的自动驾驶系统的。我们不可能等到某一层的产品成熟了再去做整体的集成。相反,我们希望全系统集成的时间越早越好。

 

图 11 演示了多种可能的全系统集成路线,从上自下的每一条贯穿路线,都是一个可能的产品集成组合。

 

路径 ① 和 ② 正好对应与1.2.3节的“闭环 A”和“闭环 B”。

路径 ③ 是一个研发前期的原型系统仿真,中间件、操作系统、计算平台都是使用现成的产品,重点放在相关算法的研发。自动驾驶创业企业早期用于融资的多半就是这样的系统。

路径 ④ ⑥ 是一个典型的硬件在环仿真系统,从硬件到操作系统、中间件都已经是使用产品化的解决方案。

路径 ⑤ ⑧ ⑨ 都是量产交付的产品,因为最终的目标产品不一样,所以各层使用的子产品也不同。路径 ⑦ 是车辆在环仿真系统。

 

根据需要,还可以设计出各种不同的集成路径,每一条路径实际是也行程了一个独立的产品研发闭环。我们一般从最简单的路径开始,逐渐增加可变量,达到量产交付的最终产品形态。

 

640.png

 图 11 纵向产品组合路径

 

产品规划的闭环思路

 

横向的分层是由各层产品的相对独立性决定的,纵向组合是构成完整系统的必然要求。一般来说,企业在做产品规划时会更关注最终形态产品,而对中间各层的子产品的规划重视不够,毕竟最终产品是要做商业交付的。

 

这样就会在事到临头发现没有合适的中间子产品,或者子产品功能不满足交付要求,需要重新选型并适配,或者还需要较多的研发时间以致延误项目进度。所以企业的在产品规划时,要充分重视个层子产品的规划,要用横向和纵向两个视角进行综合考虑。

 

横向视角代表这个子产品自身的发展,要遵从由该产品技术特性决定的客观规律,不能主观上拔苗助长;要跟随技术的发展趋势保持先进性;要有合适的产品经理和架构师,建设合适的团队;要能建立能独立的开发测试闭环,使得该子产品的研发过程不依赖其它子产品,可以独立迭代。

 

纵向视角会影响子产品演进过程中各步骤的功能规划和优先级设定。每一条纵向路径虽然其目的不同(有的是测试闭环,有的是最终交付闭环),都是一个完整的全流程闭环。纵向路径对中间子产品的要求首先是能保证这个纵向路径闭环能够运转。因此子产品的横向发展步骤要与多个纵向路径的需求进行匹配协同。也就意味着我们在规划某个子产品的路线图时,要同步要把纵向集成的因素考虑进去。

 

如果某个子产品不成熟,或者还没能达到量产要求,为了纵向路径能走通,我们要寻求前期的替代品。虽然这个阶段的子产品不能用于量产,但是它很好的支持了其上层产品的开发。于此同时,抓紧进行该层子产品的开发来了来了,为下一步替代临时方案做准备。

 

横纵两个方向的拆解和组合,是为了能让每一层的子产品都能有独立进行设计和开发,这样也就让所有层的都能够并行进行开发,然后再通过纵向的路径逐步集成。所以设计良好的产品规划,包括子产品规划,能够让整个工程化落地的过程少走弯路。

 

本文的读者

本文的目标读者为从事自动驾驶系统研发的相关人员,包括产品经理、系统工程师、架构师 、算法工程师、软件工程师、数据工程师、测试经理、项目经理等角色。本文的重点不在自动驾驶的算法,而在于如何实现工程化落地。对于自动驾驶领域的投资人,本文也有参考意义,方便投资人理解和评估自动驾驶企业的落地能力。

 为了让更多人能理解工程化的方法论,本文尽量避免采用过于专业的术语,用到的术语也会在首次出现时定义它在本文中的含义,以避免歧义而影响理解。本文也不会叙述具体算法的细节原理和实现方式,因此对于行业内的大部分人,投入一定的专注力,应该都可以理解本文的主旨。


版权声明 

本文作者为萧猛。本文的电子版本允许任何不以盈利为目的的使用、复制和再分发。要求在分发过程中保留此版权声明的全文。否则视为对本文著作权的侵犯,将会承担相应的法律责任。以盈利为目的的使用请与本人联系。

 

感谢

本文写作过程中曾与很多业内人士进行沟通交流。感谢东风技术中心数字化部林斯团总工,多次讨论中给了我很多启发与帮助;感谢真点科技 CTO 袁宏先生在 NRTK 定位算法方面的解惑以及关于自动驾驶路权方面的指导。也感谢许多领导和同事在本文写作过程中给予的帮助和支持。


收藏
点赞
2000