Keil MDK环境下的STM32 IAP下载学习笔记

IAP下载

IAP的引入

不同的程序下载方式

ICP

ICP(In Circuit Programing)。在电路编程,可通过 CPU 的 Debug Access Port 烧录代码,比如 ARM Cortex 的 Debug Interface 主要是 SWD(Serial Wire Debug) 或 JTAG(Joint Test Action Group);

MDK中ICP下载支持

ISP

ISP(In System Programing)。在系统编程,可借助 MCU 厂商预置的 Bootloader 实现通过板载 UART 或 USB 接口烧录代码,比如 STM32 存储映射Code 分区中的 System memory 可以预置厂商的 Bootloader,让 MCU 支持通过 UART 下载(不限于 UART,具体由厂商预置 Bootloader 实现而定);

写入器将 code 烧入,不过,芯片可以在目标板上,不用取出来,在设计目标板的时候就将接口设计在上面,所以叫"在系统编程",即不用脱离系统;

STM32 在出厂时由ST 在这个存储区间内部预置了一段 BootLoader(也即ISP 程序),这段程序出厂后无法修改。厂家提供的 BootLoader 一般支持UART 协议,可以让我们直接通过串口将程序代码烧录到 Main Flash memory 中;

下载软件
  • 常见的串口下载软件有 FlyMcu 或 MCUISP
  • ISP烧录软件

    STM32 的 ISP 下载电路
  • 通过串口的DTR和RTS信号来自动配置BOOT0和RESET信号,不需要用户手动切换它们的状态,直接串口软件自动控制,可以方便的下载代码。
  • ISP下载电路

    STM32 中的 ISP 下载介绍

    启动模式

  • 以ISP方式下载程序时需要把STM32的BOOT0引脚置1、BOOT1引脚置0,即从系统存储区(System Memory)启动。
  • 为什么设置从System Memory启动就可以使用串口来下载我们的程序呢?那是因为在芯片出厂前ST官方已经把一段自举程序(BootLoader程序)固化到这一块存储区。
  • 对于STM32F103VET6来说,System Memory的起始地址为0x1FFFF000,可在芯片手册的内存映射图里找到:
  • 内存映射图
  • 其通过串口来接收数据并烧写到用户闪存存储器的起始地址(0x08000000)。只能烧写到这个地址,若keil里设置的地址不是这个地址,则编译出来的文件将烧录不成功。(用户闪存,即User Flash,同时也称为Main Flash)。
  • 这一段BootLoader程序源码是没有开源出来的,用户是不可修改的。我们在上一篇笔记的IAP实验中,IAP程序通过FlyMCU软件进行烧录,烧录的地址就是0x08000000。
  • 注意:不同系列不同型号的STM32固化的BootLoader是不同的,即使用的通讯接口是不同的。如STM32F1xxx系列只支持USART1:
  • F1支持ISP下载的接口
  • STM32F4xxx系列只支持USART1、USART3、CAN2等接口:
  • F4支持ISP下载的接口
  • STM32其他型号的BootLoader支持的接口可查看AN2606文档
  • STM32 的 ISP 下载

    CH340G上电后DTR#和RTS#都为高电平,在用MCUISP烧写软件时,我们在软件下方选择“DTR的低电平复位,RTS高电平进BootLoader”,CH340G IC在实际操作时引脚的变化为“DTR#拉高,RTS#拉低”,即软件设置和实际情况是取非的,相反的。

    ​ 首先,FlyMcu软件控制DTR输出低电平,则DTR#引脚输出高, 然后RTS置高,则RTS#引脚输出低,这样Q3导通了,BOOT0被拉高,即实现设置BOOT0为1,同时Q2也会导通,STM32的复位脚被拉低,实现复位。

    ​ 然后,延时100ms后,FlyMcu软件控制DTR为高电平,则DTR#引脚输出低电平,RTS维持高电平,则RTS#引脚继续为低电平,此时STM32的复位引脚,由于Q2不再导通,变为高电平,STM32结束复位,但是BOOT0还是维持为1,从而进入ISP模式,接着mcuisp就可以开始连接STM32,下载代码了,从而实现一键下载。

    DTR#和RTS#信号的时序图如下图所示:

    DTR#和RTS#信号的时序图

    程序下载完毕后,如果设置了编程后执行,STM32会再次被复位,此时DTR#引脚为高,RTS#引脚为低,STM32复位后,DTR#引脚设置为低,RTS#引脚设置为高,那么Q2和Q3都不导通,此时,STM32重新开始启动后,检测到BOOT0为0,程序开始正常运行,一键下载至此就完成了。

    IAP

    IAP(In Applicating Programing)。在应用编程,由开发者实现 Bootloader 功能,比如 STM32 存储映射 Code 分区中的 Flash 本是存储用户应用程序的区间(上电从此处执行用户代码),开发者可以将自己实现的 Bootloader 存放到 Flash 区间,MCU 上电启动先执行用户的 Bootloader 代码,该代码可为用户应用程序的下载、校验、增量/补丁更新、升级、恢复等提供支持,如果用户代码提供了网络访问功能,IAP 还能通过无线网络下载更新代码,实现 OTA 空中升级功能。

    IAP

    IAP介绍补充

    一片 STM32 芯片的 Code (代码)区内一般只有一个用户程序。而 IAP 方案则是将代码区划分为两部分,两部分区域各存放一个程序,一个叫 bootloader (引导加载程序),另一个较 user application (用户应用程序)(之后简称 APP)

    bootloader 在出厂时就固定下来了,在需要变更 APP 时只需要通过触发bootloader 对 APP 的擦除和重新写入即可完成用户应用的更换

    IAP内存分配

    在程序执行初始进入 bootloader,在 bootloader 里面检测条件是否被触发(可通过按键是否被按下、串口是否接收到特定的数据、U盘是否插入等等),如果有则进行对APP 进行擦除和重新写入操作,如果没有则直接跳转到 APP 执行应用;如果有则进行擦除用户代码并重新写入新的用户代码。

    IAP 特点
  • 支持任意一种通信接口(I/O端口、USB、CAN、UART、I2C、SPI等)。
  • 需要牺牲一部分用户代码空间。
  • 可以无需下载线
  • IAP方案设计

    简析

    IAP 技术的核心在于 BootLoader 程序的设计,这段程序预先烧录在单片机中,正常的 APP 程序可以使用 BootLoader 程序中的 IAP 功能写入,也可以两部分代码一起写入,以后需要程序更新时通过 IAP 进行代码更新。每次板卡上电都会首先执行 BootLoader 程序,在程序内判断进行固件升级还是跳转到正常的 APP 程序。

    方案

    是否进行固件升级的判断可以从硬件和软件两个方面进行考虑。

    1. 硬件实现:
    2. 实现:通过拨码开关、跳线帽等方式设定单片机某一引脚电平状态,程序通过读取引脚电平判断是否需要升级。
    3. 优缺点:此种方式需要接触板卡进行操作,当板卡被封闭在外壳中或安装于不便于操作位置时很难实现。
    4. 软件实现-1:
    5. 实现:软件内设定一标志位(变量),通过判断标志位状态判断是否需要升级。该标志位状态掉电不能改变,故需要存储在外部 EEPROM 或单片机内部 FLASH 中。
    6. 优缺点:
    7. 若存储在外部 EEPROM,则需要增加额外的电路;
    8. 若存储在单片机内部 FLASH,由于 FLASH 每次写入都需要擦除一整页,会造成资源浪费。
    9. 软件实现-2:
    10. 实现:
    11. 单片机每次上电首先进入 BootLoader 程序,在 BootLoader 中等待一定时间。
    12. 若上位机软件在该时间段内发起通讯,则停留在 BootLoader 程序中等待固件升级;若该时间段内无通讯,则跳转到正常的 APP 程序。
    13. 优缺点:该方式每次上电都要等待一定时间,需要考虑是否可以接受。
    14. 软件实现-3
    15. 实现:在 APP 内 轮询 或 中断 外部升级信号,一旦信号来临,则跳转 BootLoader 程序升级。
    16. 优缺点:增加 APP程序 代码量(但可以减少 BootLoder 代码量),并使 APP 与 BootLoader 之间的耦合增加。
    17. ……

    在 IAP 过程中,单片机通过特定的通讯方式从上位机软件接收程序数据,并执行 FLASH 擦写操作对 APP 部分的程序进行更新。

    对于 STM32 的 IAP 设计研究

    不同型号STM32 FLASH大小

    STM32 的 flash 启动方式起始地址是 0x 0800 0000 ,根据不同的 STM32 单片机型号,有不同的 flash size 。

    stm32型号分析

    对于不同容量的STM32F1系列产品,其FLASH页大小是不同的,具体的容量划分规则如下:

  • 小容量产品:FLASH容量在16K至32K字节之间的STM32F101xx、STM32F102xx和STM32F103xx微控制器。
  • 中容量产品:FLASH容量在64K至128K字节之间的STM32F101xx、STM32F102xx和STM32F103xx微控制器。
  • 大容量产品:FLASH容量在256K至512K字 节之间的STM32F101xx和STM32F103xx微控制器。
  • 对于小容量和中容量的产品,其页大小为1K,对于大容量产品,其页大小为2K。

    在进行FLASH空间划分时,必须知道编写的程序占用FLASH空间大小。用MDK软件进行工程编译之后会生成一个.map文件,在该文件末尾可找到程序需要占用的FLASH空间。

    小容量

    中容量

    大容量

    查看程序占用空间大小

    打开 .map 文件

    在进行 FLASH 空间划分时,必须知道编写的程序占用 FLASH 空间大小。用 MDK 软件进行工程编译之后会生成一个 .map 文件,在该文件末尾可找到程序需要占用的 FLASH 空间。

    打开map文件

    对于 cubemx 生成的 keil 工程

    map文件位置

    因为 cubemx 生成的 map 文件不在工程 主目录 下,若要直接在 keil 软件中打开,可以修改 map 文件生成的位置。

    修改map生成路径步骤1

    修改map生成路径步骤2

    改完后再编译一下,就可以在直接在 keil 软件中打开map文件啦!

    具体map文件内容分析

    .map 文件分析

    在实际设计过程中,主要是确定 BootLoader 程序占用空间,便于确定 APP 程序的起始地址。

    在这时候可以先编写部分 BootLoader 程序,再通过 map 文件查看当前占用空间,从而预估 BootLoader 程序最终会占用的空间大小。

    在实际空间分配过程中,可以稍微大一点,以便于后续对 BootLoader 的功能扩展。

    如果对MDK的编译过程以及文件类型感兴趣,可以阅读《[野火EmbedFire]《STM32库开发实战指南——基于野火霸道开发板》—20211109》第46章的内容。

    视频链接:视频链接

    flash分析

    这个程序 flash 占用 6.95kB

    分析并设置程序 flash 地址与大小

    BootLoader 方面

    在MDK软件配置项中,可以对程序的起始位置以及大小进行设置。

    BootLoader 分配flash空间

    编写完,BootLoader 程序编译后,从 .map 文件获取 BootLoader 程序大小,就可以分配 BoorLoader 存储大小啦。

  • 这里获取的是 6.95kB ,起始位置为 0x08000000 不变,芯片是 c8t6 属于中容量,一页是1kB。c8t6 有 64kB ,所以有64页。

  • 7页 > 6.95kB > 6页,分配给他7页即可。7页是7kB,7*1024 = 7168 = 0x1C00。这个size可以填写 0x1C00 以上大小(最小单位是 1页 )。

  • APP 方面

    由于分配给 BootLoader 一部分的大小,所以 APP 在 flash 的起始地址会往后偏移,APP 所拥有的 flash 的空间大小也会减少。

  • APP 的偏移量就是 flash 的起始地址加上 BootLoader 的大小。

  • APP 的大小就是芯片 flash 的大小减去 BootLoader 的大小

  • APP 分配 flash 空间

    设置中断向量表的偏移量:
    1. 方法一:在主函数起始位置添加:

      SCB->VTOR = FLASH_BASE | 0x1C00; // 0x1C00 即 BootLoader 大小(偏移量)
      
    2. 方法二:

    3. 第一步:打开 system_stm32f1xx.c ( f1 系列 STM32 是这个文件)
    4. 第二步:找到 USER_VECT_TAB_ADDRESS 宏,解除注释
    5. 第三步:找到 VECT_TAB_OFFSET 宏,设置值为 Bootloaber 的大小
    6. 设置中断向量的偏移

    对于方法二只是把方法一的代码,在启动代码调用 SystemInit 函数时做了。

    偏移在 SystemInit 函数

    为什么要有这个偏移呢

    对于 STM32F103C8T6 的启动方式有三种:内置FLASH启动、内置SRAM启动、系统存储器ROM启动,通过 BOOT0 和 BOOT1 引脚的设置可以选择从哪中方式启动,这里选择内置的 FLASH 启动。其 FLASH 的地址为 0x0800 0000 — 0x0801 FFFF ,共128KB,这些都能从芯片数据手册中直接得到。而这里首要的一个问题是中断的问题。正常情况下发生中断的过程为:发生中断(中断请求)到中断向量表查找中断函数入口地址跳转到中断函数执行中断函数中断返回。也就是说在 STM32 的内置的 Flash 中有一个中断向量表来存放各个中断服务函数的入口地址,内置 Flash 的分配情况大致如下图:

    flash 分配

    在只有一个程序的情况下,程序执行的走向应该如图所示:

    中断向量表跳转中断函数,再跳转main,中断时在中断向量表找到中断函数入口地址

  • 代码开始的 4 个字节存放的是堆栈栈顶的地址;
  • STM32F10x 有一个中断向量表:
  • 这个中断向量表存放在代码开始部分的后 4 个字节处(即 0x0800 0004 )
  • 当发生中断后程序通过查找该表得到相应的中断服务程序入口地址,然后再跳到相应的中断服务程序中执行。
  • 图像分析:

  • 复位中断 到 main 函数:
    1. 上电后从 0x08000004 处取出复位中断向量的地址,
    2. 然后跳转到复位中断程序的入口(标号①所示),
    3. 执行结束后跳转到 main 函数中(标号②所示)。
  • 中断时:在执行 main 函数的过程中发生中断,
    1. 则 STM32 强制将 PC 指针指回中断向量表处(标号③所示),从中断向量表中找到相应的中断函数入口地址,
    2. 跳转到相应的中断服务函数(标号④所示),
    3. 执行完中断函数后再返回到 main 函数中来(标号⑤所示)。
  • 若在 STM32F103x 中使用 IAP 方案,则内置的 Flash 分配情况大致如下图:

    在内置的 Flash 里面添加一个 BootLoader 程序,BootLoader 程序和 APP 各有一个中断向量表,假设 BootLoader 程序占用的空间为 N+M 字节,

    则程序的走向如下图所示:

    IAP 与 APP 的 flash分配

    1. 上电初始程序依然从 0x08000004 处取出复位中断向量地址,
    2. 执行复位中断函数后跳转到 IAP 的 main (标号①所示),
    3. 在 IAP 的 main 函数执行完成后强制跳转到 0x08000004+N+M 处(标号②所示),
    4. 最后跳转到新的main函数中来(标号③所示),
    5. 当发生中断请求后,程序跳转到新的中断向量表中取出新的中断函数入口地址,再跳转到新的中断服务函数中执行(标号④⑤所示),执行完中断函数后再返回到main函数中来(标号⑥所示)。

    IAP设计

    IAP(BootLoader)编程关键技术

    1. 通信
    2. Flash擦写
    3. APP跳转
    通信需要考虑的问题
    1. 选用何种通信方式:串口、CAN、以太网…
    2. 通信协议:数据分发、帧头帧尾校验
    3. 配套上位机
    Flash写入流程

    1 解锁

    HAL_StatusTypeDef HAL_FLASH_Unlock(void);	//解锁
    

    2 擦除

    // 根据 配置选择 整页 或 全片 擦除
    HAL_StatusTypeDef  HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit, uint32_t *PageError);
    

    3 写入

    // 在 指定地址 编程 半字、字或双字
    HAL_StatusTypeDef HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data);
    

    4 上锁

    HAL_StatusTypeDef HAL_FLASH_Lock(void);
    

    接收多少数据写入一次?

    FLASH 写入之前必须进行页擦除

    STM32F103 FLASH 页大小:1k 或 2k

    接收满1页大小的数据写入一次

    APP跳转

    正点原子串口 IAP 实验 IAP 跳转代码

    void iap_load_app(u32 appxaddr)
    {
        // 在 RAM 的范围内( 0x20000000 - 0x20010000 )
    	if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000)	//检查栈顶地址是否合法.
    	{ 
            // 0x08000004 是位于 flash 中的中断向量表,
            // 表内存储的是具体的 中断函数地址(位于 RAM 中)
    		jump2app=(iapfun)*(vu32*)(appxaddr+4);		//用户代码区第二个字为程序开始地址(复位地址)		
    		MSR_MSP(*(vu32*)appxaddr);					//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
    		jump2app();									//跳转到APP.
    	}
    }
    

    参考安富莱代码之后更改的 IAP 跳转代码(这个更严谨,能避免一些错误。)

    void iap_load_app(u32 appxaddr)
    {
        int i = 0;
        
    	if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000)	//检查栈顶地址是否合法.
    	{ 
            /* 首地址是MSP,地址+4是复位中断服务程序地址 */
    		jump2app=(iapfun)*(vu32*)(appxaddr+4);
                
             /* 关闭全局中断 */
            __set_PRIMASK(1); 
                     
            /* 关闭滴答定时器,复位到默认值 */
            SysTick->CTRL = 0;
            SysTick->LOAD = 0;
            SysTick->VAL = 0;
            
            /* 设置所有时钟到默认状态 */
            RCC_DeInit();
            
            /* 关闭所有中断,清除所有中断挂起标志 */  
            for (i = 0; i < 8; i++)
            {
                NVIC->ICER[i]=0xFFFFFFFF;
                NVIC->ICPR[i]=0xFFFFFFFF;
            }
            
            /* 使能全局中断 */ 
            __set_PRIMASK(0);
            
            /* 这个设置在 RTOS 应用程序中比较重要,因为基于 Cortex-M 内核的 RTOS 任务堆栈基本都是使用线程堆栈指针 PSP。但系统 bootLoader 使用的是主堆栈指针 MSP,所以务必要设置下,同时让 M 内核工作于特权级。此寄存器的作用 */
            __set_CONTROL(0);
            
            MSR_MSP(*(vu32*)appxaddr);					//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
    		jump2app();									//跳转到APP.
            
            /* 跳转成功的话,不会执行到这里,用户可以在这里添加代码 */
            while (1)
            {
    
            }
    	}
    }
    

    下图是安富莱代码中的 __set_CONTROL(0); 的来源

    CONTROL寄存器中的位字段

    IAP 实现

    ST官方IAP代码

    以下是STM32官方IAP代码:

    串口IAP:

  • STM32F10xxx in-application programming using the USART (AN2557)
  • STM32F2xx in-application programming using the USART (AN3374)
  • STM32F4 in-application programming (IAP) using the USART (AN3965)
  • STM32L1xx in-application programming (IAP) using the USART (AN3310)
  • STM32F3xx in-application programming (IAP) using the USART (AN4045)
  • STM32F3xx in-application programming (IAP) using the USART (AN4045)
  • STM32F0xx in-application programming using the USART (AN4065)
  • 其他IAP:

  • STM32F107 in-application programming (IAP) over Ethernet (AN3226)
  • STM32F2x7 in-application programming (IAP) over Ethernet based on LwIP TCP/IP stack (AN3376)
  • LwIP TCP/IP stack demonstration for STM32F2x7 microcontrollers based on LwIP TCP/IP stack and FreeRTOS (AN3384)
  • LwIP TCP/IP stack demonstration for STM32F2x7 microcontrollers based on LwIP TCP/IP stack and FreeRTOS (AN3384)
  • 下载实例

    以F4单片机为例子,通过蓝牙串口烧录:

    链接选择

    软件获取

    得到软件

    有以下文件:

    有的文件

    打开项目

    选择芯片

    以上搞定后,以下开始移植

    移植实例

    以下是main函数的内容:

    int main(void)
    {
      /* 解锁Flash程序擦除控制器 */
      FLASH_If_Init();
    
      /* 初始化在STM324xG EVAL板上的按键 */
      STM_EVAL_PBInit(BUTTON_WAKEUP, BUTTON_MODE_GPIO);
    
      /* 测试STM324xG EVAL板上的按键是否被按下 */
      if (STM_EVAL_PBGetState(BUTTON_WAKEUP) == 0x00)
      {
        /* 执行IAP驱动程序以重新编程闪存 */
        IAP_Init();
        /* 显示主菜单 */
        Main_Menu ();
      }
      /* 保持用户应用程序运行 */
      else
      {
        /* 测试用户代码是否从地址“APPLICATION_address”开始编程 */
        if (((*(__IO uint32_t*)APPLICATION_ADDRESS) & 0x2FFE0000 ) == 0x20000000)
        { 
          /* 跳转到用户应用程序 */
          JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);
          Jump_To_Application = (pFunction) JumpAddress;
          /* 初始化用户应用程序的堆栈指针 */
          __set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);
          Jump_To_Application();
        }
      }
    
      while (1)
      {}
    }
    

    大致就是在复位的时候,判断一个按键是否被按下,如果按下则等待串口传入数据,如果没按下则直接跳转到应用程序。

    else 的内容就是跳转到应用程序的代码。而在 if 中,就是初始化串口以及菜单选择部分。

    修改 STM_EVAL_PBInit 按键初始化代码

    STM_EVAL_PBInit ( BUTTON_WAKEUP, BUTTON_MODE_GPIO ) ;

    传进去的两个参数都是枚举值;

    按键枚举到实际按键

    枚举通过数组找到具体按键。

    修改数组

    直接把数组修改成对应的按键 端口、引脚、时钟值即可。

    修改 STM_EVAL_PBGetState 按键初始化代码

    修改状态获取

    与 修改 STM_EVAL_PBInit 同理。

    修改 IAP_Init 串口初始化代码

    从以下这部分代码来看就是一个初始化串口的操作:

    /**
      * @brief  初始化IAP:配置USART。
      */
    void IAP_Init(void)
    {
     USART_InitTypeDef USART_InitStructure;
    
      /* USART resources configuration (Clock, GPIO pins and USART registers) ----*/
      /* USART configured as follow:
            - BaudRate = 115200 baud  
            - Word Length = 8 Bits
            - One Stop Bit
            - No parity
            - Hardware flow control disabled (RTS and CTS signals)
            - Receive and transmit enabled
      */
      USART_InitStructure.USART_BaudRate = 115200;
      USART_InitStructure.USART_WordLength = USART_WordLength_8b;
      USART_InitStructure.USART_StopBits = USART_StopBits_1;
      USART_InitStructure.USART_Parity = USART_Parity_No;
      USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
      USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    
      STM_EVAL_COMInit(COM1, &USART_InitStructure);
    }
    

    但是初始化的是哪一个串口?

    可以看到最后一行代码:STM_EVAL_COMInit ( COM1, &USART_InitStructure ) ;

    COM?UARTx

    传入的一个 COM1 ,跳进函数看,基本上所有的初始化都是调用一个COM值引导的数组值,所以我们只需要修改数组值的内容,就可以使用其他串口来远程烧录啦!

    怎么修改?

    跳转到这些数组这里,看到数组内容是一些宏,我们去修改这些宏:

    找到数组

    修改成串口2

    原来是串口3,我修改成串口2了。

    以上就修改完毕了,直接烧录到到单片机中,然后看如何使用吧!

    在使用之前先下载一个软件 SecureCRT

    对于flash 偏移方面

    根据 flash_if.h 中的偏移来设置应用代码的偏移(建议用设置中断向量表的偏移量中的方法二)。
    flash 偏移位置

    下载 SecureCRT

    下载与破解请看博客:

    SecureCRT8.5安装教程_securecrt安装教程_汗滴禾下除的博客-CSDN博客

    iap使用

    iap 与 SecureCRT 使用请看博客:

    使用stm32进行ota升级_stm32ota升级_JIAAN99的博客-CSDN博客

    参考

  • STM32的ISP下载原理
  • STM32烧录程序方式_stm32烧录方式_宇宙小虾米的博客-CSDN博客
  • IAP程序升级(全网最全)_关于在线升级iap的基础知识_果果小师弟的博客-CSDN博客
  • ISP、IAP、ICP三种烧录方式的区别
  • STM32的完整启动流程分析_stm32启动方式选择_隋边边的博客-CSDN博客
  • 单片机三种烧录方式ICP、IAP和ISP详解 – 知乎 (zhihu.com)
  • STM32的IAP技术,基于CAN总线的STM32F103 BootLoader设计
  • STM32升级方法(一):IAP升级_骑着蜗牛写代码的博客-CSDN博客
  • Keil | 解决Keil双击工程名无法打开.map的问题
  • 物联沃分享整理
    物联沃-IOTWORD物联网 » Keil MDK环境下的STM32 IAP下载学习笔记

    发表评论