汽车电控系统bootloader知识介绍



01


原理概述

单片机通常烧录有三种:
  • ISP(In-System Programming)
在系统编程,使用引导程序(Bootloader)加上外围UART/SPI等接口进行烧录。
  • ICP (In-circuit programmer)
在电路编程,使用SWD/JTAG接口。
  • IAP(In-Application Programming)
指MCU可以在系统中获取新代码并对自己重新编程,即用程序来改变程序。
平时开发使用主要IAP升级方式,对ECU更是如此。
单片机正常时运行上电/复位,第一条指令是固定的,程序正常顺序运行到Bootloader,由Bootloader跳转到APP程序运行。
汽车电控系统bootloader知识介绍的图1
图1-1 Bootloader简易流程

02


技术分析

2.1 什么是Bootloader
单片机正常运行时总是从固定地方取指令,顺序运行,这将对编写程序的人产生巨大的挑战,程序更新时需要使用烧录器等工具烧录,于是有人将程序设计成,由一个程序跳转到另一个程序,这个程序通常称作Bootloader,另一个叫做APP。
Bootloader程序常常具有通信接口和擦写内部存储空间的功能,可将需要更新的APP擦除,写入新的APP。有时会设计成相互跳转,技术也是可以实现的。有些为了保证程序不丢失,单独预留出备份区和出厂版本,出现某些错误时可以恢复到出厂版本或使用其他APP均可。

汽车电控系统bootloader知识介绍的图2

图1-2 Bootloader扩展流程
2.2 ECU的Bootloader
ECU是MCU的一种,专门用在汽车上。ECU经常会用在汽车零部件中,零部件密封性等要求都比较苛刻,并且装车,如果想取下零部件可能需要将车拆解才可以做到,这种行为是不被允许的,成本极高,操作复杂,因此大多主机厂(厂商)要求ECU具有升级功能,并且通过多年的积淀制定了行业标准UDS。
UDS简介:
UDS(Unified Diagnostic Services,统一诊断服务)诊断协议是用于汽车行业诊断通信的需求规范,由ISO 14229系列标准定义。应用于OSI七层模型的应用层(第7层),它只规定了与诊断相关的服务需求,并未涉及通信机制,所以,它可以在不同的汽车总线(例如CAN, LIN, Flexray, Ethernet 和 K-line)上实现。
ISO 14229-1 定义了诊断服务,只有应用层,不涉及网络及实现。
ISO 14229-3定义了UDS在CAN总线上的实现。
诊断通信用于建立诊断仪与ECU之间的通信连接,并负责将ECU中的诊断结果输送到诊断仪中。
UDS的作用非常广泛,几乎跟随ECU软件开发的全过程。
  • ECU开发过程要用到它来构建bootloader,上传和下载数据,即软件刷写,控制器Reset;

  • 测试时要用它来读写存储,控制外设;

  • 产线上,要用它来校准机械件,控制例程,进行下线执行器测试,刷新软件,配置防盗,读取号码,下线配置等。

  • 在行驶过程中,要用它来监测各种故障,并记下故障码;

  • 4S店里,技师需要读取当前故障、历史故障,读取故障发生时刻环境信息状态,清除故障,判断故障发生点,还可以用来升级ECU程序。

汽车明确规定通过UDS进行更新程序,主机厂要求擦写内部存储的代码不可写入正常代码中。汽车电子中ECU一旦设计完成,装车量产就很难再拆卸并返回零部件供应商完成功能升级或补丁修复。一旦出现售后质量问题,如果召回的话,零部件供应商和整车厂将面临严重的经济损失,因此设计基于CAN总线的ECU在线程序更新Bootloader可以很好的解决这一问题,将零部件供应商和整车厂的损失降低到最小。目前国外大部分汽车整机厂(主机厂)和全球的一级汽车零部件供应商 (Tier 1) 都要求在其设计的ECU实现Bootloader功能。
汽车电控系统bootloader知识介绍的图3
图1-3 Bootloader简易框图
假如使用CAN,框架则会设计成如图1-3。

2.3 Bootloader框架
Bootloader由主机厂或者自己,可以选择用或者不用,本次主要针对使用Bootloader情况进行分析。主机使用协议由自己进行定义,ECU启动模式选择由芯片厂商进行技术支持(如果没有厂商支持是不可以的,是不被主机厂认可的,大多数是购买商业软件包,由服务商进行技术支持与芯片厂商共同支持的)。内部编写均需要遵循协议,大多数开发都是由多年开发经验沉淀下来,修改而成的,协议依然在进步,代码可能无法维护而无法支持,主机厂也会被迫选择使用旧版协议。
汽车电控系统bootloader知识介绍的图4
图1-4 Bootloader架构
2.4 ECU Bootloader原理
主机厂规定不可把擦写内部代码的功能直接写入程序中,因此,只能每次用时才能将代码放入ECU,ECU内部可以有Bootloader,但不可以有擦写内部代码的功能,擦写代码的功能称作NVM (None Valitale Momory–非易失性存储器)驱动程序。
汽车电控系统bootloader知识介绍的图5
图1-5 NVM驱动示意图
主机将NVM驱动程序下载到RAM区域,用NVM驱动程序对内部NVM进行擦除写入新数据等,在最后跳转即可完成更新。
UDS服务设计复杂,Bootloader升级一般分为以下三步:
1、预编程:主要进行一些环境配置
2、编程:刷写过程
3、刷新完成:恢复配置
Bootloader可以保证在上述三个阶段任一问题出现都能再次进入该过程重新刷新。
1、预编程阶段
在进入刷新之前,UDS的85服务和28服务,关DTC和非诊断报文。使整个CAN网络处于安静的状态。这是对整车网络进行操作的,一般都是以功能寻址的方式来发送。注意先用85服务关闭DTC,再使用28服务关报文。通常都会关闭故障码保存,关闭CAN通信是为了加快刷写速度,过程如下:
汽车电控系统bootloader知识介绍的图6
图1-6 预编程流程
2、编程阶段
汽车电控系统bootloader知识介绍的图7
图1-7 编程流程
UDS设计了安全访问功能,安全访问是为了保证ECU数据的安全,实现方式是由ECU发送一个种子到主机,主机通过dll文件算法算出结果与ECU算出结果进行比对,结果一致则解锁成功通过安全验证。ECU解锁可以存在多个等级。
写时候先写DID指纹,标记写软件人的身份(按照主机厂要求),擦写下载等操作。
3、编程结束
刷写完成之后,ECU进行重启,重新进入扩展会话,打开之前关闭的配置即可。

03


实现

3.1 Bootloader开发难点分析

(1) 硬件初始化:
当硬件上电之后,运行的第一条指令就是Bootloader的代码(如果各式各样的BIOS忽略不计的话)。Bootloader需要完成硬件初始化的一系列工作。然后才可以进入正常的逻辑,例如加载OS Image等。对于软件工程师而言,硬件的初始化工作是很冗长乏味的。需要详细阅读各类硬件的资料规范。然后就是一系列的对寄存器的操作。虽然Bootloader中不需要初始化板子上的所有的硬件,而只需要初始化最基本的可以让Loader正常工作的硬件就可以,有一些外设可以放到OS启动的时候,甚至驱动加载的时候再进行初始化不迟。即使是这样,要初始化的硬件也不在少数,对于一个典型的ARM系统而言,有可能要做的事:初始化内存控制器,初始化MMU,配置GPIO口,配置调试串口,对RTC进行读写操作,如果要通过以太网下载,还需要驱动网络接口……这一系列的工作,没有一个不是体力活。需要细心的琢磨,很有可能对寄存器某一个bit的粗心设置,就会导致整个Bootloader无法工作。所以这一部分内容通常马虎不得,需要耐心完成。

(2) 代码编写和构建:
由于Bootloader是最底层的代码,汇编语言肯定是少不了的了。就连一些整天喊“汇编已死”的人也不能否认,系统启动的那一段代码,还是需要汇编语言的。而汇编语言通常也是软件工程师们不太希望去碰的“硬骨头”。这也增加了Bootloader编写的困难。
代码写好之后,当然要编译成机器码才可以在板子上运行。目前的编译器大多数只会把代码编译成某些流行的可执行文件格式,例如Windows上的PE和*Nix上的ELF等。这些带有格式的可执行文件,也没有办法再目标设备上直接运行。所以,通常OS都会提供一些工具,把这些可执行文件去头去尾,转成纯二进制格式,这样才可以在目标设备上运行。例如ADS提供的FromELF工具与Windows CE提供的ROMImage工具就是完成这类工作的。通常,我们需要为这类工具做一些配置,例如告诉这些工具代码段放在什么地方,起始地址是多少等等。如果这些参数没有配置正确,很有可能最终生成的Bootloader的映像是不可用的。那么烧写到目标设备上,自然也就无法运行。同样,对于这些参数的配置,也需要仔细检查核对,一步步进行。

(3) 开发的效率:
Bootloader的另外一个开发困难的原因是它的开发效率。通常当我们做了一些代码修改之后,都需要把修改后的二进制文件使用烧写工具烧写到目标设备的Flash中,无论是NAND还是NOR Flash,烧写的过程都不快。所以,即使是改了一行代码,也需要经过编译->烧写->运行这样一个完整的流程。一般而言再快也要10分钟左右。这样算算,一个钟头可以修改个5次代码,一天可以修改个50次代码就相当不错了。机械的重复这一过程,经常会使开发人员感到开发效率低下,从而产生反感和抵触情绪。这也是Bootloader开发的一大劣势。一个解决的方法是使用硬件调试工具把Bootloader的映像直接灌到RAM里面运行,往RAM里面灌通常比烧写Flash要快。但是这样需要调试工具来初始化RAM,又有很多的其他逻辑上的和细节上的事情要做。

(4) 调试
上面说的几大问题,其实还都是可以克服的问题。其实在我看来,开发Bootloader最大的问题还是调试问题。试想:无论汇编多么难,我还是写好了,无论烧写多么烦,我还是烧写下去了,但是当我怀着激动的心情按下Reset键的时候,整个硬件设备毫无反应。我怎么知道我的代码写的正确还是不正确呢?如果不正确,我又怎么能定位到我的错误呢?现在的软件开发中,无论是编译型语言还是script,一般都会提供相应的Debugger,让开发人员来定位代码错误。“摸黑”写代码是软件工程师们最害怕的事情。代码出了问题,如果没有行之有效的手段来做问题定位,十有八九会造成项目“卡壳”。如果定位准确,那么问题也就解决了一大半了。所以归根结底,还是调试的方法论问题。Bootloader中难以调试,是因为可以使用的手段非常少,也不常规。在OS下开发应用程序用到的那些调试手段手段,在Bootloader的开发中通常都用不上。需要有“非常”手段来调试。下面的专题,就向大家介绍一些Bootloader的常用调试方法。
3.2 一些调试技巧

(1) 硬件调试器
相信很多从应用开发走过来的开发人员,都会对Set Breakpoint,Step into, Step over等这些调试手段相当怀念。但是那些东西都是调试器在搞鬼。到了裸板上,可没有Debugger来帮我们了。那怎么办?好在我们有硬件调试器。很多CPU体系结构都提供了相应的硬件调试器,例如ARM CPU的仿真器。借助硬件调试器,我们可以完成汇编级的Set Breakpoint,Step into, Step over这些操作。这对于调试Bootloader来说,实在是太重要了。至少我们可以看到我们烧写下去的代码是否正确,然后可以看到是否在运行。这对于调试Reset后的第一段代码来说,实在是雨后春笋一般的珍贵。
但是硬件调试器也有很多不足:首先,它们一般都价格不菲,不用国内自己的D的话,几W是少不了了。其次,大多数硬件仿真器只能实现汇编级的调试,当代码进入了C语言之后,硬件调试器就显得力不从心了。第三,一劣质的仿真器,通常还无法对MMU打开后的虚拟内存进行仿真。所以限制也是很多的。

(2) 一闪一闪亮晶晶――LED灯
如果我们买不起硬件仿真器,或者手头上根本就没有。那么只好通过硬件的一些输出端口来输出一些信息,让我们知道代码到底运行到哪里了。在诸多的端口中,最简单的恐怕就是LED灯了。一般而言,用个几句汇编,就可以让LED闪烁起来。这样,我们就可以在代码中安插让LED闪烁的语句。来看我们的代码到底在哪里跑飞或者挂掉的。可惜LED等只能闪烁,想通过LED来获得更多的消息,是很困难的。例如我们想知道某个变量的值,用LED的闪烁表示出来,恐怕就很伤脑筋。用LED来闪烁出摩尔斯代码是一种可行方案,但是除了开开玩笑之外,恐怕真的去实践的人是不存在的……

(3) 终于可以Printf了――调试串口
目前为止,在Bootloader甚至整个OS开发过程中,串口可能是使用最广泛的调试端口了。它的设备简约却不简单。用一根串口线,把PC跟目标设备连接起来,这样我们可以通过串口来输出一些字符,在PC一端接收。终于,在有OS的应用程序开发中最传统的,最原始的调试方法printf就可以用了。在Bootloader的开发中,如果可以通过串口输出调试信息,那调试的手段就前进了一大步了。至少我们可以通过printf来跟踪目前代码在哪个函数里,分支语句走了哪个流程,某个变量的值到底是不是我们想要的……世界真美好。串口的问题是它不像LED那样想用就用,还是需要初始化的。好多嵌入式CPU都把串口控制器集成到了里面,甚至有一些会在CPU上电的时候自动初始化串口,这使得串口初始化相对简单。但是如果有一些串口是外挂的控制器,那么初始化一个串口有可能也需要耗费你半天时间。

(4) 走进新时代――内核调试器
随着开发进一步复杂,串口输出调试信息恐怕又不能满足我们的要求了。首先,输出的信息一多,很容易乱套。其次,输出信息业是需要花费时间的。在中断处理函数等一些时间敏感的地方使用串口输出,有时候依然不明智。如果能把远程调试器接上,那对BootLoader的调试跟对应用程序的调试就没有什么二致了(源码级设置断点,一步步跟,随时看某些数据结构的值,都成了可能)。Linux提供了GDB,Windows CE提供了KD。但是这都需要实现相当多的工作,实现一些调试器Stub。GDB可能串口就可走。但CE的KD通常是要通过KITL,通过以太网的。虽然复杂,长远来看还是非常有必要的,所以,当感觉其它手段力不从心的时候,可以考虑启用更高级别的调试器了。

(5) 其它歪门邪道
如果我的板子上没有硬件调试器,没有LED,串口也不工作,更别提内核调试器了,那我怎么办?别着急,只要你的硬件有输入输出设备,我们都是可以想出办法来的。只要能想办法往输出设备中输出一点东西,并且可以通过一些手段得到这些输出信息,我们调试的目的就达到了。例如:往喇叭里面输出一段杂音,往块设备上写一个扇区,往屏幕上画一些乱七八糟的东西……代码运行起来之后,只要不是当“瞎子”,都可以实现调试的目的。再走投无路的时候,这些手段不妨想想。

3.3 实例
以NXP推荐设计简单讲解原理。NVM驱动NXP官方提供完整的实现函数库,不需要自行实现,实际开发中也是如此,大部分是对工具的使用,而不是从零开始开发功能。
使用脚本文件抽取指定ECU的NVM驱动代码,全部代码是以C语言const数组存储。
将NVM驱动函数地址存储在指定地址作为 NVM 驱动函数地址表。
因为全部使用const关键词修饰,全部存储在常量区域,只读数据段,修改链接文件将只读数据段固定到RAM设定地址,则NVM驱动编译后存储在RAM区域,生成S19文件。
从得到的S19文件分离出NVM驱动和NVM 驱动函数地址表的S19文件,称为NVM 驱动S19文件
将NVM 驱动S19文件与应用S19文件合并生成完整的Bootloader。NVM 驱动S19文件需要保持在整个文件的开始,以保证系统能够正常运行找到NVM驱动。
NVM驱动是NVM 独立驱动是灵活可裁剪的。因此可以根据 Bootloader 的功能选择必要的 NVM驱动函数,从而减少其占用的 RAM 空间,以适应小RAM尺寸的ECU(比如 1KB RAM 的ECU 系列), 当然还需要改变其编译地址和 NVM 驱动函数映射地址表。


04


总结

虽然是一个比较复杂的问题,在分析问题时,将问题分解,比如,整个Bootloader分为通信、存储等,梳理过原理之后,可以预测到代码实现逻辑,再追踪定位,验证预测。为保证安全提出并实现了一种基于总线通信将NVM驱动程序由上位机下载到 RAM 中运行而非让其驻留于ECU片上FLASH的安全Bootloader 设计,有效避免了应用程序跑飞运行至驻留于片上 FLASH的NVM驱动代码所造成的程序/数据丢失失效。但对于汽车上不是一朝一夕能实现的,虽然单一功能简单,为了保证这个功能而设计很多框架用来保证,框架是各种协议的实现,难度极大,需要长时间积淀才能理解其中奥义。
默认 最新
当前暂无评论,小编等你评论哦!
点赞 评论 收藏
关注