STM32的程序烧录

1、STM32烧录方法

  单片机的烧录方式主要可以分为三种,分别为ICP(In Circuit Programing)在电路编程 、ISP(In System Programing)在系统编程 以及IAP(In applicating Programing)在应用编程

  1.1、ICP(In Circuit Programing)在电路编程

    ICP是指“在电路编程”,PC上运行的编程工具通过JATAG/SWD协议接口下载器更新芯片内部APROM、LDROM、数据闪存(DataFlash)和目标用户配置字(Config)芯片。

    支持JATAG/SWD协议接口的下载器有JLINK、ULINK、CMSIS-DAP、 STLINK等,这些下载器基本都带有仿真功能。与之配套的烧录软件为J-Flash、NuMicro_ICP_Programming_Tool、st-link utility等。程序编译器烧写一般都采用仿真器,既可以烧写也可以仿真。

    (1)JTAG协议接口下载

    JTAG(Joint Test Action Group,联合测试工作组)是一种国际标准测试协议(IEEE 1149.1兼容),主要用于芯片内部测试。

    现在多数的高级器件都支持JTAG协议,如DSP、FPGA器件等。标准的JTAG接口是4线:TMS、TCK、TDI、TDO,分别为模式选择、时钟、数据输入和数据输出线。

    需要接线:VCC、GND、TRST---PB4、TDI---PA15、TMS/SWDIO---PA13、TCLK/SWCLK---PA14、TDO/SWO---PB3、RESET---NRST。

 

    (2)SWD协议接口下载

    用标准的 JTAG 调试,需要占用 5 个 IO 口,有些时候,可能造成 IO 口不够用,而用 SWD则只需要 2 个 IO 口,大大节约了 IO 数量,但他们达到的效果是一样的,所以我们强烈建议仿真器使用 SWD 模式!

    SWD 只需要 2 根线(SWCLK 和 SWDIO)就可以下载并调试代码了,这同我们使用串口下载代码差不多,而且速度非常快,能调试。所以建议大家在设计产品的时候,可以留出 SWD 来下载调试代码,而摒弃 JTAG。 STM32 的 SWD 接口与 JTAG 是共用的,只要接上 JTAG,你就可以使用 SWD 模式了(其实并不需要 JTAG 这么多线),当然,你的调试器必须支持 SWD 模式, JLINK V7/V8、ULINK2 和 ST LINK 等都支持 SWD 调试。

    特别提醒, JTAG 有几个信号线用来接其他外设了,但是 SWD 是完全没有接任何其他外设的,所以在使用的时候, 推荐大家一律使用 SWD 模式!!!

    需要接线:VCC、GND、TMS/SWDIO---PA13、TCK/SWCLK---PA14

    (3)JTAG/SWD对比

      1)SWD模式比JTAG在高速模式下面更加可靠,在大数据量的情况下JTAG下载程序会失败,但是SWD发生的几率会小很多,基本使用JTAG的模式下可以直接使用SWD模式的,前提是仿真器支持

      2)在GPIO口刚好缺一个的时候,可使用SWD仿真,这种模式支持更少的引脚,使用SW模式PA15,PB3,PB4,都自由了就可以做普通IO口了,只用了stm32的PA13和PA14两个口

      3)在PCB设计体积有限的时候推荐使用SWD模式

  1.2、ISP(In System Programing)在系统编程

  (1)、 ISP 简介

    ISP,即In-System Programming,在系统编程。目标芯片使用USB/UART/SPI/I²C/RS-485/CAN周边接口的LDROM引导代码去更新晶片内部APROM、数据闪存(DataFlash)和用户配置字(Config)。

    ISP(In-System Programming)在系统编程,指电路板上的空白器件可以编程写入最终用户代码,而不需要从电路板上取下器件,已经编程的器件也可以用ISP方式擦除或再编程。ISP技术是未来发展方向。

    ISP 的实现相对要简单一些,一般通用做法是内部的存储器可以由上位机的软件通过串口来进行改写。对于单片机来讲可以通过SPI或其它的串行接口接收上位机传来的数据并写入存储器中。

    ISP是使用STM32系统存储器中所带的引导程序通过USB/UART等接口进行烧录的,ISP 下载程序的时候需要用到(芯片内部bootloader)自举程序,自举程序存储在 STM32 器件的内部自举ROM 存储器(系统存储器)中。

    其主要任务是通过一种可用的串行外设( USART、 CAN、USB、 I2C 等)将应用程序下载到内部 Flash 中。每种串行接口都定义了相应的通信协议,其中包含兼容的命令集和序列。

    与仿真器相比, ISP 只能下载程序,不能在线调试且下载速度慢。 而利用调试工具比如JLINK、ULINK、STLINK 等就可以实时跟踪程序, 从而找到程序中的bug。

  (2)、芯片内部自带的Bootloader

 

  STM32使用ISP烧录时,使用的是芯片内部自带的Bootloader,而不是用户编写的Bootloader。
  1)内部自带的Bootloader(系统存储器)
    什么是它:这是意法半导体在芯片出厂前就固化在芯片内部ROM中的一段程序代码。这段代码是只读的,用户无法修改或擦除。
    存储位置:它存放在芯片一个被称为“系统存储器”的独立物理区域,不是你主Flash的一部分。在芯片的数据手册中,你可以找到这个区域的起始地址(例如,对于许多STM32F1系列芯片,这个地址是 0x1FFFF000)。
    如何触发:通过配置芯片的启动模式引脚来进入这个Bootloader。
    BOOT0 和 BOOT1 引脚被设置为特定电平(通常是 BOOT0=1,BOOT1=0),然后复位芯片。
    芯片复位后,CPU会从“系统存储器”开始执行,也就是运行这个内置的Bootloader。
    功能:这个内置Bootloader支持通过特定的串行接口(如USART、USB、I2C、SPI等,具体支持哪些接口因芯片系列而异)与外部编程工具(如ST的Flash Loader Demonstrator、STM32CubeProgrammer)进行通信,从而实现对主Flash的编程、擦除等操作。
  2)用户自行编写的Bootloader
    什么是它:这是用户自己编写并编译后烧录到主Flash中的一段应用程序。它通常被放置在主Flash的起始地址。
    存储位置:存放在用户可编程的主Flash中(起始地址通常是 0x08000000)。
    如何触发:通过配置启动模式引脚为从主Flash启动(通常是 BOOT0=0),或者通过软件跳转来执行它。
    功能:功能完全由用户定义。最常见的用途是实现IAP,即:
    通过以太网、Wi-Fi、CAN等高级接口来接收新的固件数据。
    然后将接收到的数据写入到主Flash的后续区域,以实现产品的固件在线升级。

 

  (2)、 ISP 普通下载流程

    现在我们针对 USART1 的 ISP 进行分析,通常的 ISP 的步骤如下:

    1)、电脑通过 USB 线连接 STM32 的USART、 CAN、USB、 I2C 等,并打开电脑端的上位机;

    2)、设置跳线保持 BOOT0 为高电平, BOOT1 为低电平(系统存储器);

    3)、复位单片机使其进入 bootloader 模式,通过上位机下载程序;

    4)、下载完毕,设置跳线保持 BOOT0 为低电平, BOOT1 为低电平(内部FLASH);

    5)、复位单片机即可启动用户代码,正常运行。

  以上步骤有个不好的地方就是下载程序需要跳线及复位操作,很繁琐。通过对 ISP 的原理认识,一键 ISP 就诞生了,它需要做的事情就是用上位机去控制 BOOT0 脚和单片机的复位脚,原理图如下:

  (3)、开始下载

  打开 mcuisp 软件,配置如下:

    1)、搜索串口,设置波特率 115200(尽量不要设置的太高)

    2)、选择要下载的 HEX 文件

    3)、校验、编程后执行

    4)、DTR 低电平复位, RTS 高电平进入 bootloader

    5)、开始编程。如果出现一直连接的情况,按一下开发板的复位键

   

  (4) ISP 一键下载

  USB 转串口估计大家都很熟悉,一般都是用到 RXD 和 TXD 这两个口,一键 ISP 电路中我们需要用 USB 转串口的芯片的 DTR 口和 RTS 口来控制单片机的 BOOT0 和 NRST,原理如下:

  1)、通过上位机控制 U6(CH340G)的 RTS 脚为低电平, Q1 导通, BOOT0 的电平上拉为高电平。

  2)、通过上位机控制 U6(CH340G)的 DTR 脚为高电平,由于 RTS 为低电平, Q2 导通,U8 的 2 脚为低电平, U18 为一个模拟开关,使能端由 4 脚控制,默认高电平, U18的 1 脚和 2 脚导通,所以 NRST 为低电平系统复位。

  3)、单片机进入 ISP 模式,此时可以将 DTR 脚设置为低电平, RTS 设置为高电平。 Q1和 Q2 为截至状态, BOOT0 和 NRST 还原默认电平。

  4)、上位机将程序下载到单片机,下载完毕之后,程序自动运行。

  5)、至此,很多人还会认为 U18、 Q1、 Q2 是多余的,用 U6 的 RTS 和 DTR 直接控制也可以。正常情况下,这样理解没有问题,但是我们忽略了一点,就是单片机上电瞬间如果 USB 转串口连接了电脑, DTR 和 RTS 的电平是变化的,如果不处理好,单片机会一直进入 ISP 模式,或者系统会复位多次,这种情况是不允许的。

  6)、于是,就有了我们全新的一键 ISP 电路。我们主要是分析上电瞬间的逻辑关系,单片机上电时我们通过示波器观察波形得知 DTR 和 RTS 的电平是变化的,但是也有一个规律就是:只要 RTS 为低电平的时候, DTR 的电平也是低,因此一般情况 Q2不会导通,但由于这两个 IO 口的电平存在“竞争冒险”,会出现 RTS 的下降沿的时候刚好遇到 DTR 的上升沿,这个时候 Q2 导通,导致系统复位,而 BOOT0 此时有可能也为高电平,就会进入 ISP 模式。这个是不受我们控制的,我们不想系统出现这样的情况。因此加入了模拟开关来切断这种干扰。

  7)、加入模拟开关 U18,通过控制 U18 的 4 脚的开关来达到隔离干扰电平的目的。下面我们分析一下延时开关电路,上电瞬间,电容 C65 通过电阻 R18 来充电,由于电阻 100k 很大,电容的充电电流很小,等电容充电达到 U18 的 4 脚的有效电平 2V时,大概耗时 1S,在这个 1S 时间内 U18 的模拟开关是断开的,因此 RTS 和 DTR的干扰电平不会影响到系统复位。系统正常运行。

  1.3、IAP(In applicating Programing)在应用编程

  IAP(In Application Programming)即在应用编程,就是通过软件实现在线电擦除和编程的方法。 IAP 是用户自己的程序在运行过程中对User Flash 的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。

  通常实现 IAP 功能时,即用户程序运行中作自身的更新操作,需要在设计固件程序时编写两个项目代码,第一个项目程序不执行正常的功能操作,而只是通过某种通信方式(如 USB、 USART)接收程序或数据,执行对第二部分代码的更新;第二个项目代码才是真正的功能代码。这两部分项目代码都同时烧录在 User Flash 中,当芯片上电后,首先是第一个项目代码开始运行,它作如下操作:

    1)检查是否需要对第二部分代码进行更新
    2)如果不需要更新则转到 4)
    3)执行更新操作
    4)跳转到第二部分代码执行

  第一部分代码必须通过其它手段,如 JTAG 或 ISP 烧入;第二部分代码可以使用第一部分代码 IAP 功能烧入,也可以和第一部分代码一起烧入,以后需要程序更新时再通过第一部分 IAP代码更新。

  我们将第一个项目代码称之为 Bootloader 程序,第二个项目代码称之为 APP 程序,他们存放在 STM32 FLASH 的不同地址范围,一般从最低地址区开始存放 Bootloader,紧跟其后的就是 APP 程序(注意,如果 FLASH 容量足够,是可以设计很多 APP 程序的,本章我们只讨论一个 APP 程序的情况)。这样我们就是要实现 2 个程序: Bootloader 和 APP。

  IAP技术是从结构上将Flash存储器映射为两个存储体,当运行一个存储体上的用户程序时,可对另一个存储体重新编程,之后将程序从一个存储体转向另一个。

  IAP就是通过软件实现在线电擦除和编程的方法,没有使用任何工具,仅仅是通过软件的方法来更新Flash中的数据。

  讲述一个案例,那就是通过4G模块来远程更新程序。将Flash分成两块区域,第一块为Boodload程序,第二块区域存放的是应用程序APP。4G模块和目标板通讯,通讯中包含是否更新的位,如果主板接收到需要更新的位,就往Flash中写入一个标志位,比如'P',之后程序跳到第一段程序Boodload程序中执行,首先判断Flash中的是否有更新程序的标志位'P',如果有则通过规定的协议进行更新应用程序中的程序,更新完毕后清除Flash中的更新标志位,跳转到应用程序中去执行。如果没有更新程序标志位‘P’,跳到应用程序执行。

  我们先来看看 STM32 正常的程序运行流程,如下图所示:

  STM32 的内部闪存(FLASH)地址起始于 0x08000000,一般情况下,程序文件就从此地址开始写入。此外 STM32 是基于 Cortex-M3 内核的微控制器,其内部通过一张“中断向量表”来响应中断,程序启动后,将首先从“中断向量表”取出复位中断向量执行复位中断程序完成启动,而这张“中断向量表”的起始地址是 0x08000004,当中断来临, STM32 的内部硬件机制亦会自动将 PC 指针定位到“中断向量表”处,并根据中断源取出对应的中断向量执行中断服务程序。

  在上图 中, STM32 在复位后,先从 0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序,如图标号①所示;在复位中断服务程序执行完之后,会跳转到我们的main 函数,如图标号②所示;而我们的 main 函数一般都是一个死循环,在 main 函数执行过程中,如果收到中断请求(发生重中断),此时 STM32 强制将 PC 指针指回中断向量表处,如图标号③所示;然后,根据中断源进入相应的中断服务程序,如图标号④所示;在执行完中断服务程序以后,程序再次返回 main 函数执行,如图标号⑤所示。当加入 IAP 程序之后,程序运行流程如下图所示:

  在上图所示流程中, STM32 复位后, 还是从 0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序,在运行完复位中断服务程序之后跳转到 IAP 的 main 函数,如图标号①所示,此部分同图 52.1.1 一样;在执行完 IAP 以后(即将新的 APP 代码写入 STM32的 FLASH,灰底部分。新程序的复位中断向量起始地址为 0X08000004+N+M),跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的 main 函数,如图标号②和③所示,同样 main 函数为一个死循环,并且注意到此时 STM32 的 FLASH,在不同位置上,共有两个中断向量表。

  在 main 函数执行过程中,如果 CPU 得到一个中断请求, PC 指针仍强制跳转到地址0X08000004 中断向量表处,而不是新程序的中断向量表,如图标号④所示;程序再根据我们设置的中断向量表偏移量,跳转到对应中断源新的中断服务程序中,如图标号⑤所示;在执行完中断服务程序后,程序返回 main 函数继续运行,如图标号⑥所示。

  通过以上两个过程的分析,我们知道 IAP 程序必须满足两个要求:

    1) 新程序必须在 IAP 程序之后的某个偏移量为 x 的地址开始;
    2) 必须将新程序的中断向量表相应的移动,移动的偏移量为 x;

  1.3.1、APP 程序起始地址设置方法

image

  默认的条件下,图中 IROM1 的起始地址(Start)一般为 0X08000000,大小(Size)为 0X80000,即从 0X08000000 开始的 512K FLASH的地址空间为我们的程序存储(因为我们的 STM32F103ZET6 的 FLASH大小是 512K)。而图中,我们设置起始地址(Start)为 0X08010000,即偏移量为 0X10000(64K 字节),因而,留给 APP 用的 FLASH 空间(Size)只有 0X80000-0X10000=0X70000(448K 字节)大小了。设置好 Start 和 Szie,就完成 APP 程序的起始地址设置。

  这里的 64K 字节,需要大家根据 Bootloader 程序大小进行选择,比如我们的 Bootloader程序为 22K 左右,理论上我们只需要确保 APP 起始地址在 Bootloader 之后,并且偏移量为 0X200的倍数即可(相关知识,请参考: http://www.openedv.com/posts/list/392.htm)。这里我们选择 64K(0X10000)字节,留了一些余量,方便 Bootloader 以后的升级修改。

  这是针对 FLASH APP 的起始地址设置,如果是 SRAM APP,那么起始地址设置如图 52.1.4所示:

image

  这里我们将 IROM1 的起始地址(Start)定义为: 0X20001000,大小为 0XC000(48K 字节),即从地址 0X20000000 偏移 0X1000 开始,存放 APP 代码。因为整个 STM32F103ZET6 的 SRAM大 小 为 64K 字 节 , 所 以 IRAM1 ( SRAM ) 的 起 始 地 址 变 为 0X2000D000( 0x20001000+0xC000=0X2000D000 ) , 大 小 只 有 0X3000 ( 12K 字 节 )。 这 样 , 整 个STM32F103ZET6 的 SRAM 分配情况为:最开始的 4K 给 Bootloader 程序使用,随后的 48K 存放 APP 程序,最后 12K,用作 APP 程序的内存。这个分配关系大家可以根据自己的实际情况修改,不一定和我们这里的设置一模一样,不过也需要注意,保证偏移量为 0X200 的倍数(我们这里为 0X1000)。

  1.3.2、中断向量表的偏移量设置方法

  之前我们讲解过,在系统启动的时候,会首先调用 systemInit 函数初始化时钟系统,同时systemInit 还完成了中断向量表的设置,我们可以打开 systemInit 函数,看看函数体的结尾处有这样几行代码:

#ifdef VECT_TAB_SRAM
    SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET;  /* Vector Table Relocation in Internal SRAM. */
#else
    SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;  /* Vector Table Relocation in Internal FLASH. */
#endif

  从 代 码 可 以 理 解 , VTOR 寄 存 器 存 放 的 是 中 断 向 量 表 的 起 始 地 址 。 默 认 的 情 况VECT_TAB_SRAM 是没有定义,所以执行 SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;

  对于 FLASH APP,我们设置为 FLASH_BASE+偏移量 0x10000,所以我们可以在 FLASH APP 的main 函数最开头处添加如下代码实现中断向量表的起始地址的重设:

SCB->VTOR = FLASH_BASE | 0x10000;

  以上是 FLASH APP 的情况,当使用 SRAM APP 的时候, 我们设置起始地址为:SRAM_bASE+0x1000,同样的方法,我们在 SRAM APP 的 main 函数最开始处,添加下面代码:

SCB->VTOR = SRAM_BASE | 0x1000;

  这样,我们就完成了中断向量表偏移量的设置。

  通过以上两个步骤的设置,我们就可以生成 APP 程序了,只要 APP 程序的 FLASH 和 SRAM大小不超过我们的设置即可。不过 MDK 默认生成的文件是.hex 文件,并不方便我们用作 IAP更新,我们希望生成的文件是.bin 文件,这样可以方便进行 IAP 升级(至于为什么,请大家自行百度 HEX 和 BIN 文件的区别!)。这里我们通过 MDK 自带的格式转换工具 fromelf.exe,来实现.axf 文件到.bin 文件的转换。该工具在 MDK 的安装目录\ARM\BIN40 文件夹里面。

  fromelf.exe 转换工具的语法格式为: fromelf [options] input_file。其中 options 有很多选项可以设置,详细使用请参考光盘《mdk 如何生成 bin 文件.doc》 .

  本章,我们通过在 MDK 点击 Options for Target→User 选项卡,在 After Build/Rebuild 栏,勾选 Run #1,并写入: D:\tools\mdk5.14\ARM\ARMCC\bin\fromelf.exe --bin -o ..\OBJ\RTC.bin..\OBJ\RTC.axf。如图 52.1.6 所示:

 

  通过这一步设置,我们就可以在 MDK 编译成功之后,调用 fromelf.exe(注意,我的 MDK 是安装在 D:\tools\mdk5.14 文件夹下, 如果你是安装在其他目录,请根据你自己的目录修改fromelf.exe 的路径),根据当前工程的 RTC.axf(如果是其他的名字,请记住修改,这个文件存放在 OBJ 目录下面,格式为 xxx.axf),生成一个 RTC.bin 的文件。并存放在 axf 文件相同的目录下,即工程的 OBJ 文件夹里面。在得到.bin 文件之后,我们只需要将这个 bin 文件传送给单片机,即可执行 IAP 升级。

  最后再来看 APP 程序的生成步骤:

    1) 设置 APP 程序的起始地址和存储空间大小

      对于在 FLASH 里面运行的 APP 程序, 我们可以按照图 52.1.3 的设置。对于 SRAM 里面运行的 APP 程序,我们可以参考图 52.1.4 的设置。
    2) 设置中断向量表偏移量

      这一步按照上面讲解,重新设置 SCB->VTOR 的值即可。

    3) 设置编译后运行 fromelf.exe,生成.bin 文件.

      通过在 User 选项卡,设置编译后调用 fromelf.exe,根据.axf 文件生成.bin 文件,用于IAP 更新。

  以上 3 个步骤,我们就可以得到一个.bin 的 APP 程序,通过 Bootlader 程序即可实现更新。

  1.4、总结

    ICP:使用JTAG/SWD接口进行烧录,如J-Link烧录器和J-Flash软件配合使用。

    ISP:使用引导程序(Bootload)加上外围UART/USB等接口进行烧录。

    IAP:软件自身实现在线电擦除和编程的方法,不使用任何工具。程序通常分成两块,分别为引导程序和应用程序。

2、IAP(In applicating Programing)在应用编程之物联网远程固件升级

  2.1、远程固件升级

image

  FOTA的全称是:Firmware Over-The-Air。就是利用无线技术,利用云服务来实现远程的设备固件更新。
  以前产品出厂后,如果需要升级程序,要么是通过预留的调试接口,要么通过串口,USB等普通通信接口利用事先写好的bootloader程序进行更新。总之都需要直接连线到板子上。这样带来的问题就是:不光需要安排人员到现场,升级的时候还需要打开机器外壳,取出主板,连接上位机才能升级。现在物联网产品,已经具备了上网的能力,我们完全可以利用这个功能,实现远程的设备固件更新,不用到现场,不用开盖,设备自动升级。有了这个功能后,可以对已经买出去的产品,修复bug,升级更稳定的软件版本等。
  实现远程固件升级的功能,可以节省人力,提高效率,有这么多的好处。那对于我们开发者,在考虑添加这个功能的时候,该考虑哪些方面呢?
  首先,FOTA这个功能一定是由两个部分共同实现:节点端的程序和云端的升级和存储服务。节点端就是需要进行固件升级的设备。运行在节点的程序,除了原有的功能以外,还要包括一部分FOTA功能,这部分程序完成与云端服务器通信,从云端下载固件,并更新MCU固件的工作。云端的服务器能够提供固件升级版本管理,升级文件存储等服务。
  其次,选择一种升级方式。从存储器的划分角度可以分为:原位升级,乒乓升级,冗余升级;从升级文件的构成可以分为:全片升级,部分升级,差分升级等。
  下载固件时,被中断的情况,是否需要考虑,怎么处理?
  下载固件时,其他的用户程序功能能否被中断?下载过程被中断怎么处理等等。
  采取什么方式来触发本地的升级?就是说,固件从云端下载下来后,是自动更新还是等用户操作?
  怎么保证升级过程中的安全?这里的安全性,我们又可以分两个方面考虑:1.如何保证固件不被窃取,对知识产权的保护;2.如何保证下载下来的文件,是真正要下载的文件。保证文件的完整性,以及对程序运行安全的保护。

  2.2、升级方式

image

  前面讲到了原位升级,乒乓升级和冗余升级方式,我们来了解一下这三种升级方式。
  (1)第一种,固件原位升级。这种方式,程序分为两部分:用户程序只负责接收云端服务器发送过来的固件更新通知,包括新软件版本和下载地址;第二部分程序是bootloader, 这部分程序主要负责从云端服务器下载新固件,并直接更新用户程序。下载完成后,再跳转执行新的用户程序。这种方式对Flash的需求最小,但它的问题是,本地用户程序没有备份,如果下载过程中出错,则用户程序无法运行,直到下载好一个完整的用户程序。最坏的是,如果下载失败,又无法连接网络,程序就没法再运行。并且这种升级方式不支持“后台下载”,即在下载和升级的时候,用户的业务逻辑无法同时运行。
  (2)第二种,固件乒乓升级。这种方式,在MCU上同时存在两个用户程序区域,分别存放不同版本的两个程序。MCU运行的时候,就在这两个用户程序区域间切换。在用户程序1的位置运行时,可以将新版本的程序写到用户程序2的位置,然后跳转执行。在用户程序2的位置运行时,可以将新版本的程序写到用户程序1的位置,然后跳转执行。这种方式的好处是支持“后台下载”,下载程序不影响正常程序的运行。比如洗衣机,可以一边洗衣服,一边下载新程序。下载失败也没关系,还可以在原程序区域继续运行。但是它对flash大小的需求就比第一种方式要大,相当于两倍用户程序的大小。
  固件乒乓升级的方式中,在两个用户程序区域之间切换执行的操作也可以由一个bootloader来实现,每次都从bootloader启动,然后跳转到用户程序1或用户程序2的位置执行。但这种方式有一个问题,就是用户程序在MCU Flash的这两个执行区域运行时的地址是不一样的,这意味着用户程序编译的时候要设定不一样的ROM地址范围。而因为无法知道设备实际升级的时候,新程序会在哪个区域运行,所以还需要为同一个软件版本准备两套程序,分别是运行在区域1和区域2的,再由设备端的程序选择合适的版本下载。这样一来,整个升级系统的设计就变得复杂,而STM32的双bank启动就可以很完美的解决这个问题。STM32MCU Flash的两个bank可以分别作为两个程序运行区域,切换程序运行区域前,在选项字中设置不同的启动地址来选择不同的bank启动。STM32 MCU复位后,根据选项字的设定,将其对应的bank映射到0x08000000地址,并直接从该bank启动。因为不管是从bank1还是bank2启动执行,都是映射到了地址0x08000000,所以程序运行的地址都是一样的。这样的话就不存在前面所说的问题了。
  (3)第三种,固件冗余升级。“固件冗余“,顾名思义就是需要额外的空间来存储固件的备份(不作为可运行区域,这是和第二种方式的区别之一)。所以使用固件冗余升级方式时,节点端包含三部分的程序:BootLoader,用户程序和一个用户程序的备份。和第一种方式相比,这里的Bootloader功能更简单,它不需要实现网络功能,仅执行本地固件更新(将程序从备份的位置,拷贝到片内的执行位置),以及上电跳转到用户程序的功能。用户程序除了其他的产品功能外,还要实现网络通信以及固件下载的功能。从云端下载的程序,放在规划好的片上或片外存储器的相应位置。它可以像第二种方式一样,下载不影响正常的程序运行,同时又不需要MCU的flash双bank。这种方式还有一个好处,Bootloader跟网络下载无关,如果不换MCU,应用层的变化都对bootloader没有影响,不需要频繁更新bootloader。如果MCU的存储空间够大,而应用程序又小,也可以将这三部分都放到片上flash中。

image

  接下来,我们就来看看今天要介绍的远程固件更新例程。该例程使用的是STM32F769探索套件,以百度IoT平台以及百度的BOS服务为例来进行演示。要升级的固件保存在百度的BOS服务器上。
  STM32F769DK探索板集成的STM32F769NIH6芯片,片上有2M字节Flash和512K字节SRAM。板子上集成了有线的以太网接口和一个WIFI模块接口,并且还支持Arduino接口。所以使用F769DK探索板既可以通过以太网方式连接到云端,也可以通过WIFI模块接口或者Arduino接口扩展的无线模块连接到云端。板子上还通过QSPI接口外接了一个64M字节的NOR Flash,通过DSI接口外接了一个LCD屏。
  今天要介绍的这个例程是通过WIFI无线的方式连接到云端的,采用的冗余升级方式。程序完成的功能有:首先与百度IoT平台建立MQTT连接。可以将开发板的状态,软件版本等信息上传到云端,并且接收云端下发的LED控制命令,新固件版本和下载地址。再根据接收到新固件下载地址,自动通过HTTP协议从对应的服务器下载新的固件。下载完成后程序通过LCD屏向用户提示可用的新版本信息,用户可以通过按键启动新固件的升级。下载和升级的过程支持固件完整性校验,一键恢复出厂默认固件。
  在MCU端,使用到的外设有串口(连接WIFI模块),QSPI接口(扩展外部Flash),DSI接口连接LCD显示屏。

image

  关于系统的内存划分:
    在本例程里使用的是前面介绍的冗余升级的方式。用到外部的QSPI Flash来保存从云端接收到的新固件和程序运行的状态。
  QSPI Flash分为三部分:
    地址0开始的64K字节范围,用来保存程序运行的状态以及接收到的固件下载地址和版本号等。
    地址0x00010000开始的4M字节,用来保存默认的应用程序固件。默认固件可以用来将当前的应用程序恢复到最初的版本。一般可以将第一个稳定的应用程序版本当做默认固件,烧写到QSPI Flash中。
    地址0x00410000开始的4M字节,用来保存收到推送后,从云端下载的新固件。
  STM32F769 MCU的内部Flash分为两部分:
    第一部分,也就是地址0x08000000开始是Bootloader程序,板子复位后,先执行的是Bootloader的程序,之后再跳转到应用程序。在bootloader程序中会根据用户的操作决定是否执行更新用户程序的任务:将QSPI Flash中的固件更新到MCU内部Flash中。
    第二部分,从地址0x08010000开始,是用户应用程序。

image

  STM32F769DK远程固件升级例程分为两部分:Bootloader和用户应用程序,各自是独立的工程。工程项目的下载链接可以在本文最后找到。
  Bootloader程序主要完成将新的应用程序从QSPI Flash中烧到MCU内部Flash中,并跳转到应用程序运行。功能相对简单。
  用户应用程序部分的实现包括接收云端推送,自动下载以及断点续传这部分功能,程序结构相对复杂。MQTT和HTTP客户端的实现也在用户应用程序中。
  每次上电启动都从bootloader开始执行,它会检查当前是否需要对MCU的固件进行更新。如果需要更新,则会先检查QSPI flash中固件的完整性,然后开始烧写用户应用程序部分。如果不需要更新,则直接跳转到用户程序执行。

 

image

  下面,我们来了解一下例程中用户应用程序的软件架构。
  用户应用程序的软件分层如下图所示,上层程序可以通过接口函数调用下层的服务:
    底层驱动部分,包括STM32F7Cube HAL库,STM32F769DK BSP板级驱动和Wifi驱动
    中间件部分,包括TCP/IP协议栈(Lwip),mbedTLS,MQTT客户端的C实现(Paho),JSON解析器(cJSON)和FreeRTOS
    应用层实现了,连接百度IoT平台的MQTT应用,HTTP客户端以及远程固件更新的应用逻辑

image

  在实际下载过程中,可能会遇到由于网络不稳定或者设备断电等因素造成的文件下载中断,文件部分内容丢失等问题。为了解决这些问题,保证下载文件的完整性,并且避免由于一小部分文件传输出错,而需要重新传输整个文件的情况,我们将原始的BIN文件划分成了多个数据块,并增添了数据长度,校验和等信息。
  下载过程中,每下载完一个数据块马上进行校验,校验不通过就重新下载这个数据块,直到成功下载。BIN文件全部下载完成后,再对整个文件进行一次校验。这些添加的信息仅在HTTP下载的过程中用到,最终烧写到QSPI Flash中的是去掉了这些冗余信息的原始BIN文件。
  上图中是升级文件格式的具体定义:
    第一部分是长度为20字节的文件头,里面包括对整个文件的描述信息:识别码,bin文件的实际长度(不包括添加的冗余信息),数据块的大小和版本信息等。
    接下来是分为多个相同大小区块的数据区,每个数据区块开头也有一段描述数据,包括这块数据的起始地址,长度,校验和等。数据块的大小由用户根据所使用的QSPI flash的规格定义好。
    最后一部分是结束标志,包含了整个bin文件的校验和。
  用户可以通过FOTABinConverter工具(随例程软件包提供)在原始的BIN文件上添加这些信息,生成后缀为cvt的文件,放在百度BOS服务器上供节点端下载。

image

  例程的运行过程可以通过这张流程图来描述。
  在这个流程图中,最左边是设备端,也就是STM32F769探索版。下面我说设备端都是指STM32F769探索版。另外还有baidu IoT hub和BOS服务器, PC上的控制端指的就是运行在PC上的MQTT.fx软件。在这个例程中,OTA云端服务器提供的文件存储和推送功能由:Baidu IoT hub, BOS存储服务器,MQTT.fx这三部分协同完成。
  下面,我们来说说这个工作流程。
    上电后,设备首先会初始化WIFI模块,连接热点。这里在图中没有画出。
    然后设备端连接百度IoT hub,连接成功后,向其订阅设备影子主题,并开始向云端发送设备的状态信息,包括当前运行的版本号。这个时候,如果通过MQTT.fx向IoT hub发送新固件的版本号和下载地址,IoT hub收到新消息后会将其推送到设备端。收到信息后,设备端的软件会先进行一个简单的判断,收到的固件版本号是否比当前QSPI flash中存储的固件的版本更新,如果是,就开始自动在后台通过HTTP协议进行下载。
    下载完成后,用户可以根据LCD上的提示启动本地的固件更新:将QSPI flash中的固件,烧写到MCU中。
    更新成功后,自动运行新的程序。新程序和IoT Hub连接成功后,会将当前运行程序的版本上报给服务器。
    图中从连接IoT Hub到接收云端下发新固件信息(版本和下载地址),都是通过MQTT协议完成的。从云端下载新固件是通过HTTP协议完成的。
    另外,你们还可以看到PC和BOS之间还有两步操作。”上传新固件“和“获取下载地址”,这两步是需要事先完成的。事先将固件上传到百度的BOS服务器,获得下载的地址,然后才可以通过MQTT.fx进行推送。

image

  通过WIFI连接百度天工的硬件环境搭建很简单。
  STM32F769I-DISCO开发板上已经集成了ST-LINK,所以不需要额外调试工具,只需要一根micro的USB线给板子提供电源就可以开始使用了。当然,如果需要更新程序的话,还需要一台带USB接口的电脑。
  所以我们需要的硬件环境包括:
    一块STM32F769I-DISCO开发板,一个ESP-01wifi模块
    一根micro USB线(供电)
    一个WIFI热点
    一台可以上网的电脑(查看消息和进行推送)

image

  软件方面需要:
    STM32F769 云端固件升级的软件包(包括一个Bootloader的程序和一个应用程序,固件包在本文最后给出下载地址)
    IAR Embedded Workbench for ARM
    STM32CubeProgrammer(可以用来烧录MCU片上和片外QSPI Flash)
    FOTABinConverter(用来转换bin文件格式,随例程软件包一起提供)
    MQTT.fx (为了演示的方便,这里使用了免费的MQTT客户端来显示从设备端接收到的数据和进行新固件信息的推送,下载链接http://mqttfx.bceapp.com/)
    百度云IOT物管理服务(提供MQTT相关服务)和BOS(存放待下载的MCU固件)

image

  在运行本例程之前,需要在云端做一些准备工作。首先,要在百度天工IoT平台上建立好对应的MQTT服务。
  在本例程里需要用到物模型,所以在创建项目时,需要选择“设备型“项目。并创建对应的物模型,添加下图中列出的属性。具体的创建方法,请参考百度天工IoT平台的使用说明。

image

  建立好云端MQTT服务后,我们得到了云端MQTT服务器的地址,MQTT连接的用户名和密码,以及主题的名称。需要在代码的baidu_iotclient_conf.h文件中修改这些内容。
  我们还需要另一个可以与MCU中烧录的应用程序区别开的程序作为新版本固件,来演示远程固件更新的功能。
  例程代码里已经做了一个简单的开关:只要定义宏OTATEST_VERSION,重新再编译一遍。新的程序就会增加一个点灯的操作,并且将软件版本从1.1改为2.2.1。新的程序运行时,还可以看到LED2一直在闪烁。

image

   其次,需要预先将要下载的新固件放到百度的BOS上。前面我们说过保存在云端的固件文件并不是bin文件,而是通过FOTABinConverter工具转换过的cvt文件。FOTABinConverter工具随软件包提供,使用方法请见该例程的使用文档。

 image

  现在我们已经完成了以下工作:建立好了云端的MQTT服务,并将连接服务器的参数适配到固件代码中;生成的新版本固件,经过格式转换后上传到百度云端的BOS服务器。
  接下来我们还要对板子做一个初始化的准备工作:使用STM32CubeProgrammer烧写MCU片上flash和片外的板载QSPI Flash。先全片擦除MCU片上的flash和外部QSPI Flash,再分别将用户程序和bootloader烧写到MCU flash的APP区域(0x08010000起始位置)和bootloader区域(0x08000000起始位置)。
  如果需要用到“一键恢复出厂固件”功能,就还需要烧录一个默认用户程序到QSPI Flash的默认固件区域(QSPI Flash 0x00010000起始的位置,remap到STM32的地址空间是0x90010000),并将该默认用户程序的大小和校验和的值,写到QSPI FLASH INFO区(QSPI Flash 0x0起始的位置,remap到STM32的地址空间是0x90000000)。这两个值在用户恢复出厂设置,即把默认用户程序烧写回片上MCU flash时,用来检查存储在QSPI Flash上程序的完整性。

image

  上电复位运行程序,Bootloader程序先运行,然后自动跳转到用户程序。开发板通过wifi模块连接到热点,自动获取IP地址并开始连接百度云IoT平台,连接成功后,每10秒向云端发送一次状态。包括:LED的状态,软件版本和运行状态。
  保持STM32F769DK板正常运行,并已连接到百度IOT服务器。此时,如果通过MQTT.fx向百度IoT平台发布新的固件信息(包括版本信息和下载地址)到主题: $baidu/iot/shadow/{deviceName}/update。 STM32F769DK板子收到推送的新固件信息后,便会自动下载新固件。
  下载完成后,提示用户当前有新版本可用。当用户按下蓝色USER按键后,程序复位。开始执行bootloader程序。
  bootloader程序会将新的固件从QSPI FLASH烧写到MCU片上的FLASH中。然后跳转执行。
  在烧写固件前, 程序会检查固件的完整性。如果发现固件被损坏,则不烧写。直接跳转执行老的程序,并且会在QSPI的INFO区做相应记录,重新从云端下载好的固件来覆盖已被损坏的固件。

   如果在运行例程时出现连接服务器失败的情况,请检查百度云物联网平台的根证书和当前例程使用的是否一致。可以在百度云物联网平台下载根证书,然后根据新的根证书内容,替换certs.h文件中BAIDU_CA_CRT的定义。

 

posted @ 2020-09-24 16:43  孤情剑客  阅读(5460)  评论(0)    收藏  举报