STM32系列知识点--IAP串口升级
2026/8/23 8:58:55 网站建设 项目流程

一、IAP串口升级概念

1、IAP概念

IAP--In-Application Programming,是指在应用程序运行过程中,通过某种通信接口(如UART、USB、CAN、PC等)来擦除和重新写入MCU内部FLASH的一种机制。

与之对应的还有两种:ISP--In-System Programming,名为系统编程,一般在工厂通过专用工具写入。

通俗来说,IAP是出厂后,通过其它途径(串口或其他接口)烧写程序到固件;ISP是出厂时烧录的。

2、为什么需要它?

产品部署之后,无法通过ST-Link或者JTAG方式重新烧录程序时:

“固件发现BUG;需要增加新功能;远程维护。”

这是就可以通过IAP升级的方式,让设备自己接收新的固件数据,写入FLASH,重启后运行新程序。这也就是常说的“串口升级”或“远程升级”。

3、IAP的基本原理

核心思想:

MCU的内部分为两个区域:Bootloader(引导程序)和User APP(用户程序)

流程如下:

1.Bootloader固定在flash前面(比如0x0800 0000 ~ 0x0800 3FFF)

○它负责启动判断:要进入APP还是升级模式

○它负责通过UART接收新固件、擦除并写入FLASH

2.User APP存放在Flash后面(比如0x0800 4000 ~ 结束)

○是真正的用户程序

○正常工作时运行APP

○如果检测要升级(例如接收到升级命令或者按键),就跳回Bootloader

3.升级过程

○Bootloader运行

○通过串口接收新的.bin文件

○擦除旧APP区

○写入新APP

○验证成功

○设置启动标志

○跳转到新APP

二、Bootloader概述

什么叫Bootloader?(通俗来讲)

它一个项目下面,有2个工程,一个工程叫App、一个工程叫Boot。

Boot的Main函数跑了一些东西之后,会执行跳转,这个跳转代码好强,执行它就会跳到App工程里面。

Boot的大概作用就是升级。

BootLoader 是单片机上电后最先运行的一段程序。

我们知道,ROM是断电数据不丢失的、只读的,RAM是断电后数据会丢失、可读可写的,但是仅仅了解到这些是不够用的!我们要深入去了解,在实际的代码中,ROM对应哪些部分?RAM对应哪些部分?

第一、函数和常量,函数是用来实现各种逻辑的代码,常量是不可变化的的只读变量,因此函数和常量都放在ROM里面

第二、局部、全局变量,这两种变量都是代码运行过程中可以修改的、且断电是会丢失的。因此局部、全局变量是放在RAM中的

MCU的地址?是什么?怎么理解?(我们需要研究芯片手册)

简单来说,我们可以把芯片理解成一个容器,因此这个容器上一定有地方可以放刚刚说的ROM和RAM。

对于工程里面的函数和常量,刷代码就是将他们刷到MCU的ROM所在的地址里面。对于工程里面的全部变量,刷代码就是将全局变量的地址指定到MCU的某个RAM地址上面。

Bootloader的原理是什么?(通俗来讲,循序渐进)

第一步、现在你手里有一个工程,只有一个main函数,先假设mcu上电后,不去管main函数之前会执行什么东西,就假设main函数之前什么东西也没有,这个mian函数里面就实现了一个东西,就是点灯(LED1)。根据上面内容所说函数放在了ROM里,那我们就将这个工程(也就是函数的起始位置)放到芯片手册里面ROM的起始地址:0x8000 0000。(具体怎么放的先不管)

第二步、你把刚刚那个工程拷贝一份,然后改一下main函数的实现内容(甚至你不改也行)。也就是这个工程是点亮另一个灯吧(LED2)。同样的这个函数也放在ROM中。对于工程2的起始地址(也就是这个mian函数的起始地址),放在了第一个工程的后面,我们再给用第一个工程预留一些位置(方便工程1后续添加功能),暂且工程2的起始地址为0x8020 0000。

经过第一、二步的操作,现在MCU的ROM里面成功放进了2个工程进去,同时我们将MCU上电后跑起来的起始地址设为0x8000 0000,也就是工程1的mian起始地址。

那么这里遇到一个问题,就是这两个工程怎么衔接起来?这就要靠下面的代码了。

((void (*)(void))(0x80200000))();

我们将这段代码放在工程1点亮LED1代码的后面。于是MCU上电后,代码跑起来工程1点亮LED1后就会直接跳转到工程2的main函数的起始地址,然后就执行工程2的点亮LED2的任务了。

这里要说明一下:实际工程的起始地址肯定不是main的,main的前面还会跑别的东西。我们这里只是简化理解。但是mian前面的东西也是放在ROM中的,我们不需要纠结前面是什么。

到目前来说,你的MCU已经可以跑2个工程了,有点Bootloader的雏形了。

接下来,再进一步。

比如我们简简单单,实现一个平衡小车的功能。主要的逻辑功能放哪呢?由于工程2的起始地址比工程1的靠后。所以我们放到工程2里面,工程1我们后续有大用。于是,在工程2里面就有了各种应用功能逻辑代码。比如读取陀螺仪的方向转角代码啦,PID算法代码啦,等等。

突然有一天,烧录器丢了!代码烧不进去了!平衡车的代码还没优化完怎么办?

好在之前你从网上ctrl-c、ctrl-v,移植了下面这样功能的代码到工程1里面,并且在烧录器没被偷之前就刷写进MCU了。你移植的这部分代码干的事情也不多主要是下面这些:

1、擦除指定ROM地址数据

数据写入指定ROM地址之前,我们需要先把MCU这段ROM地址上的数据擦除。(比如我们现在需要刷写工程2到MCU里面,那么我们就需要从ROM地址0x80200000开始擦除,至于擦除到哪里结束,则取决于这个工程2使用的ROM大小了。)

至于它是怎么擦除的,实际代码就是调用一个函数接口,这个函数是MCU厂家的软件包就有这个接口,因此不需要担心。

2、接收升级包

比如你编译工程2之后,会生成一个.hex文件。这个.hex文件就是升级包。

然后,我们电脑会有一个上位机(类似于串口工具那样的上位机),它可以载入这个.hex文件,然后通过UART(或者CAN)发送给MCU

至于MCU如何接收整个数据包的过程,我们现在先不管。

3、把升级包写入指定ROM

在擦除完成之后,MCU就把接收到的升级包刷写至指定的ROM里面。

至于它是怎么写入的,也是类似于擦除那样,MCU厂家的软件包就有这个函数调用接口,直接调用就行,只需要把数据本身、数据大小和写入地址传进函数里面,它就会去写入了。

到这里我们整理一下:

工程2,实现各种想要的应用功能代码,用来搞功能逻辑的,比如你的平衡小车功能。

工程1,不搞应用逻辑功能,作用是通过UART(或CAN)直接把工程2的代码刷写进MCU。

好了,MCU里面有了工程1在,你的烧录器就算被偷了,也能通过UART(或者CAN)直接把工程2的代码刷写进去:

1、擦除工程2在MCU中所在的ROM地址

2、接收工程2的hex文件

3、把接收到的工程2的hex文件数据写入指定ROM地址

这个工程1的作用,就是执行这3个关键步骤去升级工程2。

朋友们,这个工程1,不就是所以谓的Bootloader嘛。

有了工程1在,我们的MCU不就具备升级功能了嘛。

我们这里接收升级包的方式是通过UART(或者CAN)。

但完全可以改成别的方式。

比如通过以太网从云端远程接收数据包,然后进行升级。

这不就是所谓的OTA(Over The Air)了,你的手机、车、电脑不就是用这种方式升级的嘛!

结语

我们这里是简化了很多东西去讲Bootloader的大致的流程。

很多细节的东西我们都暂且忽略了,接下来就是一点点填坑,把这些细节一点点补上了。

比如:

main前面有什么东西,干啥用的?

工程的起始地址具体要怎么放到指定的ROM地址上?

Bootloader工程和App工程它们用的RAM是一起用的还是分开用的?

Bootloader跑起来的时候,怎么知道当前是上电流程还是升级流程?(如果是上电流程就要直接跳App工程,如果是升级流程就要停留在Bootloader里面跑升级功能)

一级Boot、二级Boot又是怎么回事?

升级包接收到流程具体是怎么样的?

App能不能倒反天罡,去升级Boot?

hex和bin有啥关系,好像两者都能用于升级?

等等......

真正要搞起来,细节可太多了。(这一系列的问题下面内容将会一一解答)


以下内容是补充:

在普通 STM32 工程中,用户程序通常从内部 Flash 的起始地址运行:

0x8000 0000

加入 BootLoader 之后,Flash 通常会被划分为两个区域:

0x08000000 BootLoader 区域 0x08010000 APP 应用程序区域

BootLoader 的主要作用包括:

1、判断是否需要升级 APP 2、接收新的 APP 固件 3、擦除和写入 STM32 内部 Flash 4、校验 APP 是否有效 5、跳转到 APP 运行

简单理解,BootLoader 就是 STM32 的“启动管理程序”。它本身也是一个 STM32 程序,只不过它负责决定后面的 APP 是否运行、是否升级。

STM32程序的启动流程是什么?

STM32 从 Flash 启动时,会从起始地址读取两个重要数据:

0x08000000:主堆栈指针 MSP 0x08000004:复位中断入口 Reset_Handler

如果没有 BootLoader,芯片启动后会直接进入 APP 的Reset_Handler

加入 BootLoader 后,启动过程变成:

STM32 上电 | v 运行 BootLoader | v 判断是否需要升级 | +-- 需要升级:接收并写入新 APP | +-- 不需要升级:跳转 APP

所以 BootLoader 跳转 APP 的本质就是:

1、读取 APP 起始地址处的栈顶地址 2、读取 APP 起始地址加 4 处的复位入口地址 3、设置 MSP 4、跳转到 APP 的 Reset_Handler

Flash 分区怎么设计?

BootLoader 开发的第一步是规划 Flash。

下面以 APP 起始地址为0x08010000为例:

BootLoader 起始地址:0x08000000 BootLoader 大小: 64KB APP 起始地址: 0x08010000

Flash 分区示意:

+-----------------------------+ 0x08000000 | BootLoader | | | | 启动判断、升级、APP 跳转 | +-----------------------------+ 0x08010000 | APP | | | | 用户应用程序 | +-----------------------------+

如果使用STM32F103C8T6这类 Flash 较小的芯片,可以改成:

BootLoader:0x08000000 ~ 0x08003FFF APP: 0x08004000 ~ ...

分区时需要注意:

1、BootLoader 区域要能放下完整 BootLoader 程序 2、APP 起始地址必须与工程链接地址一致 3、APP 向量表偏移必须与 APP 起始地址一致 4、擦除 APP 时不能擦到 BootLoader 区域

以三种 STM32 型号为例的 Flash 划分:

不同 STM32 的 Flash 起始地址通常都是0x08000000,但是容量和擦除粒度不同。做 BootLoader 分区时,最重要的一点是:

APP 起始地址必须对齐 Flash 擦除单元。

对于 STM32F103C8T6,擦除单元是 1KB Page;对于 STM32F407VET6,擦除单元是不等长 Sector;对于 STM32G473VET6,擦除单元是 2KB Page。

STM32F103C8T6官方 Flash 容量为 64KB,地址范围为:

0x08000000 ~ 0x0800FFFF

它属于 STM32F103 中容量器件,Flash 按 1KB Page 擦除:

Page 0 :0x08000000 ~ 0x080003FF Page 1 :0x08000400 ~ 0x080007FF ... Page 63 :0x0800FC00 ~ 0x0800FFFF

对于 F103C8T6 这种 Flash 较小的芯片,BootLoader 不能写得太大。一个比较常见的划分方式如下:

对应的宏可以这样定义:

#define BOOTLOADER_ADDRESS 0x08000000U #define APP_ADDRESS 0x08004000U #define BOOT_INFO_ADDRESS 0x0800FC00U #define VECT_TAB_OFFSET 0x4000U

如果不需要单独保存升级标志,也可以不保留BootInfo区域,让 APP 一直使用到0x0800FFFF。但是实际项目中,建议至少预留最后 1 个 Page,用于保存升级状态、固件长度、CRC 等信息。

需要注意:有些Blue Pill开发板上的STM32F103C8T6可能会被工具读出 128KB Flash,但按官方型号定义,C8是 64KB Flash。正式项目建议按 64KB 设计,不要依赖超出规格的区域。

STM32F407VET6 Flash 划分示例

STM32F407VET6官方 Flash 容量为 512KB,地址范围为:

0x08000000 ~ 0x0807FFF

STM32F407 的 Flash 不是等大小 Page,而是不等长 Sector:

F407 做 BootLoader 时,不能简单地按 1KB 或 2KB 去切。APP 起始地址最好直接放到某个 Sector 的起始地址。

如果 BootLoader 只是串口升级、YMODEM 升级,可以这样划分:

对应的宏:

#define BOOTLOADER_ADDRESS 0x08000000U #define APP_ADDRESS 0x08010000U #define VECT_TAB_OFFSET 0x10000U

如果 BootLoader 比较复杂,例如包含 TCP、W5500、以太网、文件协议、加密校验等功能,64KB 可能不够。此时可以把 BootLoader 扩大到 128KB:

对应的宏:

#define BOOTLOADER_ADDRESS 0x08000000U #define APP_ADDRESS 0x08020000U #define VECT_TAB_OFFSET 0x20000U

F407 分区时尤其要注意:Flash 擦除以 Sector 为单位。如果把 BootLoader 和 APP 放在同一个 Sector 里,那么升级 APP 时一擦除这个 Sector,就可能把 BootLoader 一起擦掉。

因此,F407 的 BootLoader、APP、参数区都应该按完整 Sector 划分。不要在同一个 Sector 里混放 BootLoader 和 APP。

BootLoader 跳转 APP 的核心原理是什么?

假设 APP 起始地址为:

#define APP_ADDRESS 0x08010000U

那么 APP 的栈顶地址位于:

APP_ADDRESS

APP 的复位入口地址位于:

APP_ADDRESS + 4

BootLoader 跳转 APP 前,需要完成以下工作:

  • 判断 APP 地址是否合法
  • 关闭中断
  • 初始化 HAL 和时钟
  • 关闭 SysTick
  • 设置 MSP 为 APP 的栈顶地址
  • 跳转到 APP 的复位入口

如果这些步骤遗漏,常见现象是跳转后程序跑飞、中断异常、串口不工作、HardFault 等。

BootLoader 跳转 APP 示例代码:

#define APP_ADDRESS 0x08010000U typedef void (*pFunction)(void); static uint8_t IsAppValid(uint32_t appAddress) { uint32_t stackTop = *(__IO uint32_t *)appAddress; uint32_t resetHandler = *(__IO uint32_t *)(appAddress + 4); if ((stackTop & 0x2FFE0000U) != 0x20000000U) { return 0; } if ((resetHandler < 0x08000000U) || (resetHandler > 0x080FFFFFU)) { return 0; } return 1; } void JumpToApp(void) { uint32_t jumpAddress; pFunction appEntry; if (!IsAppValid(APP_ADDRESS)) { return; } __disable_irq(); HAL_RCC_DeInit(); HAL_DeInit(); SysTick->CTRL = 0; SysTick->LOAD = 0; SysTick->VAL = 0; jumpAddress = *(__IO uint32_t *)(APP_ADDRESS + 4); appEntry = (pFunction)jumpAddress; __set_MSP(*(__IO uint32_t *)APP_ADDRESS); appEntry(); }

这段代码中,IsAppValid()用于简单判断 APP 是否有效。

主要判断内容有两个:

  • APP 栈顶地址是否位于 SRAM 区域
  • APP 复位入口地址是否位于 Flash 区域

实际项目中,还可以继续增加 CRC 校验、版本号校验、APP 信息区校验等机制。

APP 工程需要修改什么?

BootLoader 工程从0x08000000启动。APP 工程不能再从0x08000000开始链接,而要从 APP 分区地址开始。

以 Keil 为例,需要修改 APP 工程的 IROM 地址。


接下来以STM32F103C8T6为例,怎么用串口下载代码?

1.PC端下载一个上位机Flash Loader Demo(自己要做一个)

2.芯片的串口引脚Tx、Rx(PA.9、PA.10)通过USB>TTL连接到电脑上

3.将芯片的boot0引脚接高电平boot1引脚接低电平。这是为了让芯片上电的时候从系统存储区启动,原厂的isp程序保存在那里,地址是0x1FFF 000 ~ 0x1FFF 77FF。系统存储区是用户不能访问的区域,它在芯片出厂时已经固化了启动程序,它负责实现串口、USB以及CAN等isp烧录功能。

4.打开上位机,配置如图,波特率可多选,这是因为上位机在发送握手0x7F时,芯片接收到0x7F后,就能将波特率算出来,然后给自身串口初始化,跟上位机设置一样的波特率。接着给芯片上电,上位机选择bin文件,下载到芯片里面后,将boot0和boot1引脚接低,重新上电,就能运行刚下载的程序了。

这样就可以在没有烧录器的情况下下载程序了,当然如果要进行调试的话,还是需要烧录器。

除了上面这两种给芯片下载新程序的方法,还可以在芯片运行中给自身flash存储器写入新程序。这就是iap(In application Programing在应用编程)。做一个产品,研发时一般都是在PC端借助烧录器升级,但到客户手里一般是用U盘升级,只要把U盘插入到机器中,就能给自身升级。其中,在插入U盘后,芯片检测到需要升级,就会跳转到iap程序段里面去,然后读取U盘里面的程序,再将U盘的程序文件拷贝到自身的flash里,拷贝完成之后,跳转到新的程序中运行。下面是通过串口给自身升级的iap案例,将实现的过程代码详细说明。

stc32f103c8t6内部有一个64k的flash存储器,用于储存代码,在电脑上编译好的程序,通过烧录器把它烧录到内部flash中。Flash里面的内容掉电不会丢失,烧录完,芯片重新上电,就可以从内部flash中加载代码(起始地址一般是0x0800 0000)。

内部falsh除了用烧录器读写外,还可以在芯片运行时,对自身的内部flash进行读写。如果flash储存了程序后还有剩余的空间,那么可以把它用来保存程序运行时产生需要掉电保存的数据;也可以在芯片运行时将另一个编译后的二进制程序文件写到剩余的flash,然后进行跳转到新的程序上面运行。这也是iap的实现原理。

1.先介绍怎么利用stm库对flash进行操作

所有flash操作相关的函数接口在stm32f10x_flash.h里面。读flash里面的数据直接根据地址读出来就行了。往flash里面写数据,需要解锁,擦除,写入数据,上锁;擦除后存储单元都变成1,因为储存单元不能由0变1,所以在写入之前一定要先擦除,不然会写入失败。

操作代码如下:

#define address 0x08006000 //写入的flash地址 #define value 0x55aa55aa //将要写的数据 void flash_test(void) { uint32_t *pdata=address; Printf(“data=%d”,*pdata); //先将原来的数据打印出来 FLASH_Unlock(); //解锁 FLASH_ErasePage(address);//擦除,擦除只能按页擦,擦除address地址所在的页,不同的芯片一页的大小不一样,对于stc32f103c8t6来说,一页就是1024字节,也就是1k。 FLASH_ProgramWord(address,value); /*将data写入address地址里面,除了写入uint32_t类型,还可以写uint16_t类型的数据,对于一份很长的代码来说,只能这样一个个的写进去flash,对应接口如下: FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data); FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);*/ FLASH_Lock(); //上锁,保护数据 Status=FLASH_WaitForLastOperation(0xFFFFFF);//等待烧写结束,参数是等待的超时时间 if(Status==FLASH_COMPLETE){ //写入成功 } Printf(“data=%d”,*pdata);//打印确认是否写入成功 }

要注意,address的地址不能指向自身的代码区域,因为修改了自身的程序会造成不可预测的效果,所以要指向自身程序后面的空余区域,一般来说从0x08000000+加上程序的大小,后面的就是空余区域。下图是对于芯片stc32f103c8t6的工程配置:

2.流程图不太会写,简单地将过程描述一遍,iap策略如下:

对于升级的方式,可以选择以下几种,如USART,IIC,CAN,USB,以太网接口甚至是无线射频通道,将程序文件发送到iap。

存储区划分:

Bootloader工程:0x800 0000-0x800 2BFF (11k) 升级标志:0x800 2C00-0x800 2FFF (1K) App工程:0x800 3000 -0x801 0000 (52k)

芯片上电首先是进入bootloader工程的,所以把bootloader放在前面。升级标志可能会有人问为啥要1k这么大,一个字节不行了吗?首先,bootloader和app都可能会对升级标志的值进行修改或读取,所以不能保存在RAM,只能保存在ROM,那么对ROM的数据进行修改就是flash读写操作,上面提了要先擦除,而且擦除是按页擦,一页就是1k,所以就算标志位不需要1k这么大,只用其一个字节,那剩下的也不能用到其他地方,因为它随时会被擦除。

下面开始说明这两部分的代码实现,其中的一些配置也要细心注意。

3.Bootloader工程

Bootloader程序开机引导app程序,在运行app程序中,若收到升级信号,则从app跳转到bootloader里,然后boorloader通过串口接收新的程序文件,对app进行升级。所以,我们还需要一个上位机将程序文件通过串口发送给bootloader,为了方便,我没自己做上位机,直接用Flash Loader Demo,这个可以网上下载。那么上位机有了,还要了解它的通讯协议, 到底数据是怎么从上位机发送过来了,bootloader该怎么接收数据。

其实我们要做的bootloader工程就是要实现原厂isp的功能,跟上位机同步,接受上位机数据。我们无法得到人家的isp代码,但是可以上st的官网下载它的isp协议。了解了它的协议就能自己写单片机端的代码了。协议下载链接。这里不对这个协议进行细说,直接说明实现的代码,下图是原厂isp所支持的命令。

建立bootloader工程,打开一个新的带stm32标准库的keil工程,对工程进行如下配置。

第一步,初始化USART1外设,这里不做波特率自适应,把波特率固定为115200,那么上位机配置就要跟其保持一致。

#ifndef __USART1_INIT_H__ #define __USART1_INIT_H__ #include "stm32f10x.h" #include void USART1_Configuration(void);//打印输出串口初始化 void sengdata(unsigned char data); unsigned char waitdata(void); #endif
#include "USART1.h" #include "Queue.h" void USART1_Configuration(void)//打印输出串口初始化 { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; //配置串口1 (USART1) 时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); //配置串口1接收终端的优先级 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); //配置串口1 发送引脚(PA.09) GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); //配置串口1 接收引脚 (PA.10) GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); //串口1工作模式(USART1 mode)配置 USART_InitStructure.USART_BaudRate = 115200;//设置波特率; USART_InitStructure.USART_WordLength = USART_WordLength_8b; //数据位为8个字节 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; //接收发送模式 USART_Init(USART1, &USART_InitStructure); USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启中断 USART_Cmd(USART1, ENABLE);//使能串口 } void sengdata(unsigned char data) { USART_SendData(USART1, (unsigned char) data); while( USART_GetFlagStatus(USART1,USART_FLAG_TC)!= SET); } extern QueueT RxQueueEntity; unsigned char waitdata(void) //阻塞等待一个数据到来 { while(1){ if(getDataCount(&RxQueueEntity)!=0){ return outQueue(&RxQueueEntity); } } }

第二步,创建接收队列,因为上位机发送过来的数据很多,芯片不能及时处理,那么就要先把数据放进队列,然后逐个拿出来处理。这样就不会丢失数据。直接复制下面代码就行,可以先不用理解。

#ifndef __QUEUE__H__ #define __QUEUE__H__ #include "core_cm3.h" typedef struct { u16 in; u16 out; u16 cntMax; u8* pBuf; }QueueT; /*队列的特点:先进先出,若队列满了,不能再放数据。可循环使用队列的位置*/ void QueueCreate(QueueT* thiz,u8* BufAddress,u16 BufSize); //创建一个队列,初始化结构体里面的成员 u16 getDataCount(QueueT* thiz); //获取队列里面有效的数据的大小 u16 getEmptyCount(QueueT* thiz); //获取队列里面还剩余多少空的位置 u8 inQueue(QueueT* thiz,u8 data); //将一个数据放进队列 u8 outQueue(QueueT* thiz); //从队列里面拿一个数据出来 #endif //__QUEUE__H__
#include "Queue.h" void QueueCreate(QueueT* thiz,u8* BufAddress,u16 BufSize) { thiz->in=0; thiz->out=0; thiz->cntMax=BufSize; thiz->pBuf=BufAddress; } u16 getDataCount(QueueT* thiz) { if (thiz->in >= thiz->out){ return (thiz->in - thiz->out); }else{ return (thiz->in + thiz->cntMax - thiz->out); } } u16 getEmptyCount(QueueT* thiz) { u16 dataCnt; if (thiz->in >= thiz->out){ dataCnt=(thiz->in - thiz->out); }else{ dataCnt=(thiz->in + thiz->cntMax - thiz->out); } if ((dataCnt+1u) >= thiz->cntMax){ return 0; //fifo full } return (thiz->cntMax-dataCnt-1u); } u8 inQueue(QueueT* thiz,u8 data) { u16 in; in = thiz->in + 1u; if (in >= thiz->cntMax){ in = 0; } if (in == thiz->out){ //full fifo return 0; } thiz->pBuf[thiz->in] = data; thiz->in = in; return 1; } u8 outQueue(QueueT* thiz) { u8 data; u16 out; if (thiz->in == thiz->out){ //empty fifo return 0; } out = thiz->out; data = thiz->pBuf[out]; out++; if (out >= thiz->cntMax){ out = 0; } thiz->out = out; return data; }

第三步,建立与上位机的通讯协议,接收上位机的命令并作出相应的应答。下面代码是根据协议AN2606来写给上位机的应答的。

#ifndef __IAP_H__ #define __IAP_H__ #include "core_cm3.h" #define updata_flagaddr 0x8002C00 //升级标志 #define verify_flagaddr 0x800FF10 //在app里面 #define ACK 0x79 //肯定应答 #define NACK 0x1F //否定应答 typedef struct{ unsigned char cmd; void (*pfunction)(void); }CommandHandleStruct; typedef void (*iapfun)(void); void jump_to_app(u32 appxaddr); extern const CommandHandleStruct CmdHdlStr[11]; #endif //__IAP_H__
#include "iap.h" #include "USART1.h" static unsigned char bootloaderversion=0x22; static unsigned char cmd_count=11; static unsigned char cmd[11]={0x00,0x01,0x02,0x11,0x21,0x31,0x43,0x63,0x73,0x82,0x92}; void jump_to_app(u32 appaddr) //跳转函数 { iapfun jump2app; if(((*(vu32*)appaddr)&0x2FFE0000)==0x20000000) { __set_PRIMASK(1); __set_MSP(*(vu32*)appaddr); jump2app=(iapfun)*(vu32*)(appaddr+4); jump2app(); } } unsigned char checksum(unsigned char *data, int len) //计算p开始len个字节的checksum,也就是计算异或 { int i; unsigned char cs; cs = 0; for ( i=0; i cs ^= data[i]; return cs; } void Getcommand(void) //AN2606 page10 { unsigned char i; sengdata(cmd_count); sengdata(bootloaderversion); for(i=0;i sengdata(cmd[i]); } sengdata(ACK); } void GetVersion(void) //AN2606 page12 { sengdata(bootloaderversion); sengdata(0x01); sengdata(0x01); sengdata(ACK); } void GetID(void) //AN2606 page14 { sengdata(0x01); sengdata(0x04); sengdata(0x10); sengdata(ACK); } unsigned int addr=0,temp1; unsigned int* flashdata; void ReadMemorycommand(void) //AN2606 page16

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询