华大MCU(五):HC32F460串口IAP升级boot部分

您所在的位置:网站首页 医疗器械的认证是什么 华大MCU(五):HC32F460串口IAP升级boot部分

华大MCU(五):HC32F460串口IAP升级boot部分

2024-07-16 08:43:30| 来源: 网络整理| 查看: 265

HC32F460通过串口实现IAP升级

接着以前的文章华大MCU(四):HC32F460串口IAP升级

现在其实几乎所有的iap实现都大同小异,只是有的做了很多安全的机制和更多的功能,本文只介绍最基本的方案。

说实话华大HC32是我心目中的mcu,它并不是仿照stm32,而是引入很多在mcu领域很少见的东西。我最喜欢的是两点:

灵活的引脚配置,串口引脚配置可以非常随意,方便。为每个中断单独绑定中断号。避免了stm32那种进了中断,还需要用if来判断是哪个中断。

但是就是它的与众不同导致你可能不能像GD32那样快速的熟悉它。所以我针对我写IAP进行一次总结,希望对还在坑中的人有帮助。

文章目录 HC32F460通过串口实现IAP升级1. 系统环境2. 原理3. BootLoader设计的过程-基于YModem3.1 主要的逻辑框图3.2. iap内的宏定义3.2. HC32F460用TIM实现1s的中断3.3. 串口初始化3.4 将上位机的bin数据写到指定flash地址3.5 程序跳转 4. 插入一个话题:我建议使用下面方法验证一个iap程序4.1 找一个最简单的app程序4.2 编写BootLoader文件4.3 验证能否正常从BootLoader跳转到app中4.4 利用上位机,将app的bin文件发送给mcu 5. HC32F460的iap问题5.1 时钟发生错乱-mcu莫名超频

1. 系统环境 系统:win10sdk版本:hc32f460_ddl_Rev2.0.0数据手册版本:HC32F460系列用户手册 Rev1.21.pdfide:keil5开发板:官方评估板(EVB-HC32F460) 2. 原理

先说废话,这些虽然是废话,但有很多细节,小细节最容易翻车,希望认真读完。

首先,我们都知道串口的传输虽然有奇偶检验,可以检查错误,但是对于通信来讲,上位机向mcu串口传输,当串口收到错误数据,也只能抛弃,上位机根本不知道mcu的情况,对于重要的数据这将是致命的。因此产品的iap升级都不会直接用串口直接分包下发(隐患太大),而是通过一种发送应答机制(常见的使用CRC错误侦测方法)来完成bin文件传输,这类的协议很多,甚至可以自己写一套自己的协议(原理不难),现实中有很多开源的这种协议。而现实中最常见的用于串口传输的协议就是XModem,YModem,ZModem。

ASCII:这是最快的传输协议,但只能传送文本文件(只是为补全知识点添加的,不用管)。Xmodem:一种在串口通信中广泛使用的异步文件传输协议,分为XModem和1k-XModem协议两种,前者使用128字节的数据块,后者使用1024字节即1k字节的数据块。这种古老的传输协议速度较慢,但由于使用了CRC错误侦测方法,传输的准确率可高达99.6%,现在基本被淘汰。Ymodem:Xmodem的改良版,使用了1024位区段传送,速度比Xmodem要快,mcu升级最常用。Zmodem:采用了串流式(streaming)传输方式,传输速度较快,而且还具有自动改变区段大小和断点续传、快速错误侦测等功能。这是目前最流行的文件传输协议,在linux中常见,我曾调试全志v3s的时候用过,文件批量传输,避免了像ssh等网络方法的繁琐配置。

利用这些的传输协议,可以不用开发上位机的,因为有很多软件自带这些协议,如XShell,SecureCRT等。同时这些协议比较健壮,所以推荐直接使用现成的协议就完了,方便。这三种协议网上有大量的教程,本文对于它的原理就不赘述了。

我曾写过两套HC32F460芯片的BootLoader,核心原理都是一样的,上位机将app的bin文件发送到串口,串口接收上位机数据,写到mcu的flash内,下载完后,在跳转到app中执行。区别在于一个是开机检测一个引脚,当引脚为高就代表升级,串口会进入接收app的bin文件模式,当收完自动跳转到app执行,另外一个是开机读取串口数据,若在3s内(tim实现的)收到升级指令(其实就是一段字符串),就进入接bin状态,接收完跳到app执行。若未收到升级指令,超时则直接跳到app执行。

本次介绍倒计时实现BootLoader的方案,正好介绍一下定时器的使用。有几个注意事项一定要提前了解:

由于使用串口升级,串口传输不易过快,115200应该已经足够了。同时你的电路板可能设计了RS485,RS232等接口转串口来升级程序的,那么千万别忘了在BootLoader程序里面设置这些电平转换芯片的使能引脚。HC32F460的flash和sram都分了好几个区,每个区都有差别,为了方便,地址就选到普通的区,别选什么加密、校验、保护功能的区。既然需要写mcu内部flash,那么写flash都要注意的事项,flash的页的大小,擦除的最小页数单位,开始地址要按手册要求字节对齐,写的时候要先解保护等问题。当您在BootLoader中用到资源的时候,跳转app前一定要关闭这些资源,比如定时器中断,引脚中断,跳转前一定要disable掉。不建议用串口中断接收app升级的bin数据,mcu的BootLoader往往都是裸机功能简单的小程序。同时弄得跟linux的uboot似的没啥意义。HC32F460的flash很大的,别扣扣索索的,那个0x400地址(开头那篇文章有讲),一定要在BootLoader的flash范围里,也就是把app的那个flash开始地址设置的大一点。这样万一将来BootLoader变大了也有余量。省的还要重新编译app的位置。 3. BootLoader设计的过程-基于YModem 我BootLoader的flash地址和ram的地址如下:

在这里插入图片描述

我app的flash地址和ram的地址如下: 在这里插入图片描述

我认为两个程序的iram的地址是可以重叠的,但是为了方便懒得验证了,(sram大,不在乎)。

模板:我是直接在stm32的官网的iap的demo改的。这样我就省的再去移植YModem了。

这个stm32的模板我会直接传到csdn上。下载链接

3.1 主要的逻辑框图 Created with Raphaël 2.3.0 上电启动 初始化时钟为200M 初始化串口接收 初始化定时器为1s中断 在3s内串口收到升级命令 (其实就是先定义好的 字符串) 接收串口发来的数据包 存储到flash中 关闭bootloader程序使能的外设 释放资源(很重要) 获取app的栈地址和函数入口 跳转到app程序中 跳转到APP中 yes no 3.2. iap内的宏定义

根据HC32F460的数据手册,还有我们在编译app所选择的flash地址,可以声明下面这些宏:

//这些在向flash写数据是用的到的 #define PAGE_SIZE (0x2000) /* 8 Kbyte */ //每个扇区为8KBytes #define FLASH_SIZE (0x40000) /* 256 KBytes */ //flash容量 //将hc的app的起始地址定义到 地址重映射区域0(REMAP0) #define ApplicationAddress 0x020000 3.2. HC32F460用TIM实现1s的中断

mcu的时钟的配置请参考我另一篇文章华大MCU(二):HC32f460启动过程和时钟分析,在这里直接默认系统时钟为200M。

//定时器初始化,适配hc处理器 #define TIMERA_UNIT1 (M4_TMRA1) #define TIMERA_UNIT1_CLOCK (PWC_FCG2_PERIPH_TIMA1) #define TIMERA_UNIT1_OVERFLOW_INT (INT_TMRA1_OVF) //将计时器计数累计1000次,那么完成1秒定时 #define TIMERA_COUNT_OVERFLOW (SystemCoreClock/2U/128U/1000U - 1U) // 1ms //普通定时器timerA初始化 void TIMA_init(void) { stc_timera_base_init_t stcTimeraInit; stc_irq_regi_conf_t stcIrqRegiConf; /* configuration structure initialization */ MEM_ZERO_STRUCT(stcTimeraInit); MEM_ZERO_STRUCT(stcIrqRegiConf); /* Configuration peripheral clock */ PWC_Fcg2PeriphClockCmd(TIMERA_UNIT1_CLOCK | TIMERA_UNIT2_CLOCK, Enable); /* Configuration timera unit 1 structure */ stcTimeraInit.enClkDiv = TimeraPclkDiv128;//将主频200M时钟进行128分频 stcTimeraInit.enCntMode = TimeraCountModeSawtoothWave; stcTimeraInit.enCntDir = TimeraCountDirUp; stcTimeraInit.enSyncStartupEn = Disable; stcTimeraInit.u16PeriodVal = TIMERA_COUNT_OVERFLOW;//计数 TIMERA_BaseInit(TIMERA_UNIT1, &stcTimeraInit); TIMERA_IrqCmd(TIMERA_UNIT1, TimeraIrqOverflow, Enable); /* Configure interrupt of timera unit 1 */ stcIrqRegiConf.enIntSrc = TIMERA_UNIT1_OVERFLOW_INT; stcIrqRegiConf.enIRQn = Int008_IRQn; stcIrqRegiConf.pfnCallback = &TimeraUnit1_IrqCallback; enIrqRegistration(&stcIrqRegiConf); NVIC_ClearPendingIRQ(stcIrqRegiConf.enIRQn); NVIC_SetPriority(stcIrqRegiConf.enIRQn, DDL_IRQ_PRIORITY_DEFAULT); NVIC_EnableIRQ(stcIrqRegiConf.enIRQn); //使能定时器 TIMERA_Cmd(TIMERA_UNIT1, Enable); } //启动/停止定时器,主要是在跳转到app的时候,一定要关了tim void start_timA(u8 on) { if(on==1)TIMERA_Cmd(TIMERA_UNIT1, Enable); else TIMERA_Cmd(TIMERA_UNIT1, Disable); } //定时器中断回调函数 static void TimeraUnit1_IrqCallback(void) { TIMERA_Cmd(TIMERA_UNIT1, Disable);//先暂留,感觉没啥用 //计数时钟 system_clock++; TIMERA_Cmd(TIMERA_UNIT1, Enable); TIMERA_ClearFlag(TIMERA_UNIT1, TimeraFlagOverflow);//清中断 }

在程序跳转到app前要失能定时器,并清除定时器资源:

start_timA(0);//关闭定时器 TIMERA_DeInit(TIMERA_UNIT1); 3.3. 串口初始化 //定义串口 /* USART channel definition */ #define USART_CH (M4_USART2) /* USART baudrate definition */ #define USART_BAUDRATE (57600ul) /* USART RX Port/Pin definition */ #define USART_RX_PORT (PortA) #define USART_RX_PIN (Pin06) #define USART_RX_FUNC (Func_Usart2_Rx) /* USART TX Port/Pin definition */ #define USART_TX_PORT (PortA) #define USART_TX_PIN (Pin05) #define USART_TX_FUNC (Func_Usart2_Tx) //对用来升级的串口进行初始化 void IAP_Init() { en_result_t enRet = Ok; uint32_t u32Fcg1Periph = PWC_FCG1_PERIPH_USART1 | PWC_FCG1_PERIPH_USART2 | \ PWC_FCG1_PERIPH_USART3 | PWC_FCG1_PERIPH_USART4; const stc_usart_uart_init_t stcInitCfg = { UsartIntClkCkNoOutput, UsartClkDiv_1, UsartDataBits8, UsartDataLsbFirst, UsartOneStopBit, UsartParityNone, UsartSampleBit8, UsartStartBitFallEdge, UsartRtsEnable, }; /* Enable peripheral clock */ PWC_Fcg1PeriphClockCmd(u32Fcg1Periph, Enable); /* Initialize USART IO */ PORT_SetFunc(USART_RX_PORT, USART_RX_PIN, USART_RX_FUNC, Disable); PORT_SetFunc(USART_TX_PORT, USART_TX_PIN, USART_TX_FUNC, Disable); /* Initialize USART */ enRet = USART_UART_Init(USART_CH, &stcInitCfg);//初始化串口 if (enRet != Ok) { while (1) { } } else { } /* Set baudrate */ enRet = USART_SetBaudrate(USART_CH, USART_BAUDRATE); //设置波特率 if (enRet != Ok) { while (1) { } } else { } USART_FuncCmd(USART_CH, UsartRx, Enable); //开启读 USART_FuncCmd(USART_CH, UsartTx, Enable); //开启写 }

串口接收数据的几个函数的实现:

//获取串口数据,读到数据就返回,读不到数据就在这里面卡着,可以在这里加判断,和定时器联动,超时返回 uint8_t GetKey(void) { uint8_t key = 0; /* Waiting for user input */ while (1) { if (SerialKeyPressed((uint8_t*)&key)) break; } return key; } //上面的函数会不断的调用这个函数 uint32_t SerialKeyPressed(uint8_t *key) { if (USART_GetStatus(USART_CH, UsartRxNoEmpty)) { *key = (uint8_t)USART_RecData(USART_CH); return 1; } else { return 0; } }

上层通过函数

key = GetKey();

来不间断的扫描串口的值,并将串口的值存在key中。不建议用中断来接收串口数据,若真的必要用串口接收,那么请参考我另一篇文章 华大MCU(三):HC32F460实现串口dma发送和中断接收。

在程序跳转到app前要失能串口,并清除串口资源:

USART_DeInit(USART_CH); 3.4 将上位机的bin数据写到指定flash地址

首先要操作flash,当然先解锁使能

EFM_Unlock(); //开锁flash EFM_FlashCmd(Enable);//使能flash

下面是Ymodem协议将数据向flash写入,这个函数来自官方stm32的iap的demo,我连注释都没改,而是直接将stm32的擦除flash和写flash换成了hc32的。

这时候一定要注意,因为每种单片机的flash的页容量不同,而且每次擦除的最小单位也不同,因此部分逻辑要修改。

因为Ymodem协议导致buf只有1024个字节,而hc32f460的最小擦除是8k字节,写的时候4个字节写一次。因此修改如下:

uint32_t FlashDestination = ApplicationAddress;//app存储的起始地址 uint16_t PageSize = PAGE_SIZE; //擦除的最小单位为8k int32_t Ymodem_Receive (uint8_t *buf) { uint8_t packet_data[PACKET_1K_SIZE + PACKET_OVERHEAD], file_size[FILE_SIZE_LENGTH], *file_ptr, *buf_ptr; int32_t i, j, packet_length, session_done, file_done, packets_received, errors, session_begin, size = 0; /* Initialize FlashDestination variable */ FlashDestination = ApplicationAddress; for (session_done = 0, errors = 0, session_begin = 0; ;) { for (packets_received = 0, file_done = 0, buf_ptr = buf; ;) { switch (Receive_Packet(packet_data, &packet_length, NAK_TIMEOUT)) { case 0: errors = 0; switch (packet_length) { /* Abort by sender */ case - 1: Send_Byte(ACK); return 0; /* End of transmission */ case 0: Send_Byte(ACK); file_done = 1; break; /* Normal packet */ default: if ((packet_data[PACKET_SEQNO_INDEX] & 0xff) != (packets_received & 0xff)) { Send_Byte(NAK); } else { if (packets_received == 0) { /* Filename packet */ if (packet_data[PACKET_HEADER] != 0) { /* Filename packet has valid data */ for (i = 0, file_ptr = packet_data + PACKET_HEADER; (*file_ptr != 0) && (i file_size[i++] = *file_ptr++; } file_size[i++] = '\0'; Str2Int(file_size, &size); /* Test the size of the image to be sent */ /* Image size is greater than Flash size */ if (size > (FLASH_SIZE - 1)) { /* End session */ Send_Byte(CA); Send_Byte(CA); return -1; } /* Erase the needed pages where the user application will be loaded */ /* Define the number of page to be erased */ NbrOfPage = FLASH_PagesMask(size); /* Erase the FLASH pages */ for (EraseCounter = 0; EraseCounter Send_Byte(ACK); file_done = 1; session_done = 1; break; } } /* Data packet */ else { memcpy(buf_ptr, packet_data + PACKET_HEADER, packet_length); RamSource = (uint32_t)buf; for (j = 0;(j /* End session */ Send_Byte(CA); Send_Byte(CA); return -2; } FlashDestination += 4; RamSource += 4; } Send_Byte(ACK); } packets_received ++; session_begin = 1; } } break; case 1: Send_Byte(CA); Send_Byte(CA); return -3; default: if (session_begin > 0) { errors ++; } if (errors > MAX_ERRORS) { Send_Byte(CA); Send_Byte(CA); return 0; } Send_Byte(CRC16); break; } if (file_done != 0) { break; } } if (session_done != 0) { break; } } return (int32_t)size; } 3.5 程序跳转

我相信来看这个文章的人都用过stm32的,这是经典的iap跳转app的程序,只是要注意检查栈地址是否合法的时候要根据自己所选择的那块iram。

typedef void (*pFunction)(void); ... extern pFunction Jump_To_Application; extern uint32_t JumpAddress; ... //检查栈地址是否合法,然后到程序起始地址开始运行 //我的ApplicationAddress的值为0x020000,这是个地址,地址里面存的是栈底地址,所以跳转前要先检查栈地址是否匹配 //SRAM2的地址范围0x20010000到0x2001FFFF if (((*(__IO uint32_t*)ApplicationAddress) & 0x2FFE0000 ) == 0x20000000) { /* Jump to user application */ JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4); Jump_To_Application = (pFunction) JumpAddress; /* Initialize user application's Stack Pointer */ __set_MSP(*(__IO uint32_t*) ApplicationAddress); Jump_To_Application(); } ... 4. 插入一个话题:我建议使用下面方法验证一个iap程序

其实我们重点要验证两个方面:

程序能否正常从BootLoader跳转到app中。BootLoader能否正常接收上位机的bin文件,并放到指定的flash区域。 4.1 找一个最简单的app程序

首先写一个最简单的华大mcu程序,比如说led闪烁(用while加delay堵塞实现,别用定时器),不要调用mcu的其他资源,防止和BootLoader资源冲突导致误判,将flash的链接脚本flash位置改到启动地址0x00;让它就做一个普通程序,烧录到hc32,确定能够正常运行来保证app程序没有问题,led别闪的太快,因为我们通过闪的快慢可以分析直接运行和iap跳转会不会导致时钟剧烈变化(我就偶然发现HC32F460会超频,后面会讲)。

然后对照数据手册,给它找个新flash位置,编译生成HEX文件备用,同时利用工具在生成bin文件备用。

4.2 编写BootLoader文件

编写一个BootLoader文件,测试它可以运行,不死机,生成HEX文件。

4.3 验证能否正常从BootLoader跳转到app中

将BootLoader的HEX和app的HEX合并成一个HEX,若不知道方法,请参考我的另一篇文章将两个HEX文件合并成一个HEX文件。

将新生成的HEX文件用J-LINK下载到mcu中,在不触发iap升级情况下,看看app能否执行,来验证链接脚本的时候flash和sram设置的地址有没有问题,bootloader的跳转那段程序是否有问题。

4.4 利用上位机,将app的bin文件发送给mcu

BootLoader启动,将bin发给它,然后看下载的bin的app能否运行,来验证bootloader的下载保存那部分代码没有问题。

观察有boot和没有boot的app程序的led闪烁情况,来确定时钟配置没发生问题(一般时钟出问题都会时钟变化很大,所以看led就可以看出来)。

5. HC32F460的iap问题

按正常情况,你现在已经完成了HC32F460的iap的完整过程,但是我在测试的时候发现了非常怪异的问题。

5.1 时钟发生错乱-mcu莫名超频

​ 开始的时候我用一个led闪烁作为了app程序,结果发现当从bootloader跳转过来后,发现led的闪烁变快了(快了非常多,肉眼可见),后来经过测是分析,我认为是时钟变快导致的,也就是app运行发生了超频。

后来我不断检查发现:

若我在bootloader里面初始化外部200M时钟,而在app里面不初始化时钟就会发生这件事。若我在bootloader里面不初始化外部200M时钟,而在app里面在初始化时钟就不会发生这件事。若我在bootloader里面初始化外部200M时钟,而在app里面再初始化一遍时钟就不会发生这件事。但是我猜测在我app开始执行,到还未执行初始化外部时钟的这段时间,mcu应该一直处于超频。

至于配置外部时钟的方法我另外的文章有讲:华大MCU(二):HC32f460启动过程和时钟分析

我认为发生的问题的原因是,在app启动的汇编程序调用内部时钟初始化,应该是有寄存器没初始化到,把本来外部配置好的时钟树搞乱了。解决办法我认为有3个,第一种是对照时钟树,把app汇编里面那个初始化时钟函数给补全了,第二种简单粗暴,把app汇编里面那个初始化时钟函数给改成调用外部时钟函数。第三种就是若在bootloader里面初始化外部200M时钟,就在app里面再初始化一遍时钟就不会发生这件事,但是这种可能有隐患。



【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭