尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

STM32系列知识点--IAP串口升级

STM32系列知识点--IAP串口升级 一、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接收新固件、擦除并写入FLASH2.User APP存放在Flash后面比如0x0800 4000 ~ 结束○是真正的用户程序○正常工作时运行APP○如果检测要升级例如接收到升级命令或者按键就跳回Bootloader3.升级过程○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)。但完全可以改成别的方式。比如通过以太网从云端远程接收数据包然后进行升级。这不就是所谓的OTAOver The Air了你的手机、车、电脑不就是用这种方式升级的嘛结语我们这里是简化了很多东西去讲Bootloader的大致的流程。很多细节的东西我们都暂且忽略了接下来就是一点点填坑把这些细节一点点补上了。比如main前面有什么东西干啥用的工程的起始地址具体要怎么放到指定的ROM地址上Bootloader工程和App工程它们用的RAM是一起用的还是分开用的Bootloader跑起来的时候怎么知道当前是上电流程还是升级流程如果是上电流程就要直接跳App工程如果是升级流程就要停留在Bootloader里面跑升级功能一级Boot、二级Boot又是怎么回事升级包接收到流程具体是怎么样的App能不能倒反天罡去升级Boothex和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_HandlerFlash 分区怎么设计BootLoader 开发的第一步是规划 Flash。下面以 APP 起始地址为0x08010000为例BootLoader 起始地址0x08000000 BootLoader 大小 64KB APP 起始地址 0x08010000Flash 分区示意----------------------------- 0x08000000 | BootLoader | | | | 启动判断、升级、APP 跳转 | ----------------------------- 0x08010000 | APP | | | | 用户应用程序 | -----------------------------如果使用STM32F103C8T6这类 Flash 较小的芯片可以改成BootLoader0x08000000 ~ 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 ~ 0x0807FFFSTM32F407 的 Flash 不是等大小 Page而是不等长 SectorF407 做 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 0x20000UF407 分区时尤其要注意Flash 擦除以 Sector 为单位。如果把 BootLoader 和 APP 放在同一个 Sector 里那么升级 APP 时一擦除这个 Sector就可能把 BootLoader 一起擦掉。因此F407 的 BootLoader、APP、参数区都应该按完整 Sector 划分。不要在同一个 Sector 里混放 BootLoader 和 APP。BootLoader 跳转 APP 的核心原理是什么假设 APP 起始地址为#define APP_ADDRESS 0x08010000U那么 APP 的栈顶地址位于APP_ADDRESSAPP 的复位入口地址位于APP_ADDRESS 4BootLoader 跳转 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、RxPA.9、PA.10通过USBTTL连接到电脑上3.将芯片的boot0引脚接高电平、boot1引脚接低电平。这是为了让芯片上电的时候从系统存储区启动原厂的isp程序保存在那里地址是0x1FFF 000 ~ 0x1FFF 77FF。系统存储区是用户不能访问的区域它在芯片出厂时已经固化了启动程序它负责实现串口、USB以及CAN等isp烧录功能。4.打开上位机配置如图波特率可多选这是因为上位机在发送握手0x7F时芯片接收到0x7F后就能将波特率算出来然后给自身串口初始化跟上位机设置一样的波特率。接着给芯片上电上位机选择bin文件下载到芯片里面后将boot0和boot1引脚接低重新上电就能运行刚下载的程序了。这样就可以在没有烧录器的情况下下载程序了当然如果要进行调试的话还是需要烧录器。除了上面这两种给芯片下载新程序的方法还可以在芯片运行中给自身flash存储器写入新程序。这就是iapIn 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 *pdataaddress; 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(); //上锁保护数据 StatusFLASH_WaitForLastOperation(0xFFFFFF);//等待烧写结束参数是等待的超时时间 if(StatusFLASH_COMPLETE){ //写入成功 } Printf(“data%d”,*pdata);//打印确认是否写入成功 }要注意address的地址不能指向自身的代码区域因为修改了自身的程序会造成不可预测的效果所以要指向自身程序后面的空余区域一般来说从0x08000000加上程序的大小后面的就是空余区域。下图是对于芯片stc32f103c8t6的工程配置2.流程图不太会写简单地将过程描述一遍iap策略如下对于升级的方式可以选择以下几种如USARTIICCANUSB以太网接口甚至是无线射频通道将程序文件发送到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-in0; thiz-out0; thiz-cntMaxBufSize; thiz-pBufBufAddress; } 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 ((dataCnt1u) 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 bootloaderversion0x22; static unsigned char cmd_count11; 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*)(appaddr4); jump2app(); } } unsigned char checksum(unsigned char *data, int len) //计算p开始len个字节的checksum也就是计算异或 { int i; unsigned char cs; cs 0; for ( i0; i cs ^ data[i]; return cs; } void Getcommand(void) //AN2606 page10 { unsigned char i; sengdata(cmd_count); sengdata(bootloaderversion); for(i0;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 addr0,temp1; unsigned int* flashdata; void ReadMemorycommand(void) //AN2606 page16
返回列表