IAP+YMODEM+CRC16+AES256+PC端软件+hex合并

origin: http://www.openedv.com/thread-78079-1-1.html

由于项目需要,花时间研究了一下有关IAP 的知识,虽然在原子哥的教程中有讲到关于IAP ,但是实际项目中并不会那样使用,也不会使用串口直接不通过协议传输文件,但是原子哥的教程很好的讲明白了IAP 的思路以及实现的方法,至于细节部分就是我开贴的原因,希望能多把这种实际项目一定会用到的知识为大家所知,一下代码使用的平台是STM32F0 ,改成F1/F4 也很简单,可根据自己情况修改
IAP
学习 IAP 之前建议大家先看看原子哥的教程了解一下大概,知道 IAP 的作用以及实现,下面提供一个网址 http://www.openedv.com/posts/list/11494.htm)   ,是原子哥的 IAP 文字教程,看完后起码按照教程顺序下来实现串口升级代码了,但是教程中有个跳转至中断向量表的知识点我觉得可能存在问题,我也发帖询问过,但是没有得到解答,以下是我的提问帖,希望能得到答复
YMODEM
Ymodem的知识简介自己百度一下吧,传送门 ( http://baike.baidu.com/link?url=Z7sLoTcGJjKH580EUmlnqTFIZnPYUM4IH-Tj-TMYVOy7vOmp7L_J5E5ADX8O97rHLvjX-AVM6LAPkslPUvV6qK),原子哥采用的是串口直传升级文件,显然在实际项目我们一般不会那么做,因为可能传输出现错误,需要采取些容错、重发和校验等一些措施来避免传输错误,原本有考虑自己写这个协议来实现文件传输,但是上ST官网一查,已经有了现成的YMODEM+CRC16的代码,所以没多想,直接移植过来好了,也许就是所谓的傻瓜式编程了。但是我觉得去了解YMODEM协议是如何实现的对自己技术水平的提高有很大的帮助,在遇到移植出错时起码知道从哪里下手修改代码,官方找来的IAP+Ymodem的代码在附件中,需要的可以自行下载查看。这里主要讲解一下几个重要的函数,其中主要有menu.c /ymodem.c / flash_if.c / common.c 这几个头文件,menu.c主要实现的是调用ymodem协议的接口然后对传输是否成功以及传输文件的大小名字做一些显示,最关键还是ymodem.c,实现通过ymodem协议发送和接收文件,其中作为接收一方,我们只用到“Ymodem_Receive(uint8_t *buf)”这个函数,其中buf是将接收到的1024字节通过ymodem协议接收下来存到缓存buf里面,每传送完成1024字节就写入一次FLASH,这样只需要开辟一个1024字节的缓存就可以实现升级了,而不需要开辟一个很大的数组存储所有代码,没必要浪费那么大一片存储空间,这就是边写边存的好处,在源码中flash_if.c主要就是对flash的操作了,里面有个FLASH_If_Write函数很好的实现了这个功能,而common.c里面就是现实些整数和字符串的转换和串口发送。把这个协议移植到自己代码中的时候还是跳了不少坑的,官方的东西有时候也不一定靠谱,我发现我ymodem接收数据后写入flash的时候总是写入错误,写入flash指定地址后从同个地址读出那个值做二次判断的时候居然不同,我使用的是电脑的超级终端,win7网上可以随便下载得到,XP有自带。这个软件我也会添加到下面的附件当中,里面集成了各种文件传输协议,其中就有ymodem,只要将代码生成bin文件后直接通过ymodem发送出去,然后串口收到的数据通过官方的这段代码就直接能用了。回到话题,写入flash总是出错,后面经过debug发现官方在写flash之前居然不解锁和上锁!不解锁和上锁!!!后面我在写入前加了解锁,写入后上锁,写入错误这个bug就没了,但是还有个问题官方处理的不太严谨, 就是代码文件是否写入完成不是按照文件的大小来判断的,而是通过设定的代码区flash大小来决定的,所以我修改了一些代码,在往flash写入数据的时候判断是否已经把整个文件写完了,由于ymodem协议传输文件时首先会传递文件的大小的一些信息,所以这个文件的大小通过第一个包就可以获取到了,修改的是“FLASH_If_Write”这个函数,可以对比查看,另外官方在写入前会将要写入的那个flash地址先擦除一遍,但是也没有先解锁再上锁的操作,这个地方我也加上了,我会将我修改后ymodem协议添加到附件中,官方的我也会给在附件中,需要的可以自己查看
“Ymodem_Receive (uint8_t *buf)”这个函数的区别。
在官方例程中还需要修改一个串口发送和接收函数(在commo.h中),官方的代码的串口发送这样的:
[C]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void SerialPutChar(uint8_t c)
{
   USART_SendData(EVAL_COM1, c);
   
   while (USART_GetFlagStatus(EVAL_COM1, USART_FLAG_TXE) == RESET)
   {}
}
 
//改成如下:串口几可以根据自己的情况修改
void SerialPutChar(uint8_t c)
{
   USART_SendData(USART1, c);
   
   while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET)
   {}
}
 
//官方的串口接收如下:
uint32_t SerialKeyPressed(uint8_t *key)
{
   if ( USART_GetFlagStatus(EVAL_COM1, USART_FLAG_RXNE) != RESET)
   {
     *key = USART_ReceiveData(EVAL_COM1);
     return 1;
   }
   else
   {
     return 0;
   }
}
 
//改成:
uint32_t SerialKeyPressed(uint8_t *key)
{
   if ( USART_GetFlagStatus(USART1, USART_FLAG_RXNE) != RESET)
   {
     *key = USART_ReceiveData(USART1);
     return 1;
   }
   else
   {
     return 0;
   }
}

在程序中我使用的升级方式是通过串口发送字符‘1’(可自行修改),串口收到字符‘1’后,向指定的地址写入0xAAAA,然后使用软复位回到bootloader,bootloader中判断那个地址是不是0xAAAA,如果是则升级,升级完成后擦除这个地址的flash内容跳到APP,如果不是则跳直接回到APP执行,由于升级不可能频繁升,所以这里不用担心把falsh擦写坏。

AES256+PC软件:
完成 ymodem 协议传输代码后紧接着又有一个问题了,以往的代码中如果不需要用到升级功能我们会加上读保护,保证有一层明锁,不至于能直接读出代码,但是一旦有了 APP bootloader ,虽然你代码还是能加上读保护,但是当 APP 变成一个 bin 文件给到客户升级的时候如果你不进行加密,很容易就给别人读出来,所以 APP 就要想办法再给到客户的时候又不至于那么容易让他破解,因此需要采用代码加密, AES 是目前最流行的加密算法之一,破解也有一定的难度,关于 AES 的介绍看传送门
对代码加密有更加严格需求,想加暗锁的可以看下坛友写的一些方法
一开始也是想自己写 AES 来着,研究了网上的很多资料,偏理论性的东西实在太多了,后面找到了一个动图,可以很清晰的了解每个加密过程,看了后感觉茅塞顿开,动图贴附件中,需要自己写 AES 的可以根据这个动图来写。 AES 分加密和解密两个步奏,了解了原理以后偷懒上网查了一下有没现成的可以用,几次查找后发现了个好东西,如果自己写的话首先需要写解密到 MCU 上,然后 PC 需要自己写个软件来对 bin 进行加密,这份资料已经做完了这些,我只需要直接拿来用就好了,使用的是 AES256
想想也没那么简单,拿来就能直接用?作者提供 MCU AES 算法倒是没问题,问题在 PC 端软件根本用不了,下载了他的软件后用 VS 编译器调试了下,发现作者不是纯用 C# ,界面用 C# 写的, AES 的实现用 C 写的,用 C 代码生成 dll C# 调用,问题就出现这个 dll ,不知道什么原因总之调用这个 dll 就报错,后面我重新将这部分 C 代码重新生成一个 dll 软件把平台改成 X86 后就可以正常使用了,接着只要将生成的 hex 文件导入这个软件,写入 AES 密匙后会自动生成一个加密后的代码,接着通过 Ymodem 传输给 MCU MCU 边接收别解密,解密后写入 flash ,这个作者的原网址
(http://www.amobbs.com/thread-5069186-1-1.html) ,他给的文件包无法直接用,我给的文件包估计也没法在你们的电脑上上直接用,所以只能用你们自己用电脑将 C 代码生成 dll 才能正常用了,怎么把 C 代码生成 dll C# 调用,这个百度一大把,很容易 ( 底下有教程,需要 VS 环境 )
下面是我加了AES的yemodem接收代码,由于是之前写的,现在只加了一些关键备注,附件中的源代码都有注释,这里截取关键的发出来
[C]  纯文本查看  复制代码
?
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
int32_t Ymodem_Receive (uint8_t *buf)
{                    //1024 + 5
     uint8_t bufferOut[16]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
   uint8_t packet_data[PACKET_1K_SIZE + PACKET_OVERHEAD], file_size[FILE_SIZE_LENGTH], *file_ptr, *buf_ptr;
   uint8_t *BufferIn;
     int32_t i, j, packet_length, session_done, file_done, packets_received, errors, session_begin, size = 0;
   uint32_t flashdestination, ramsource;
 
     aesDecInit(); //AES解密初始化
     
   /* Initialize flashdestination variable */
   flashdestination = APPLICATION_ADDRESS; //APP代码的起始地址,APPLICATION_ADDRESS = 0x8005000,可在target界面根据情况设置
                                            //这些都在ymodem.h里面的宏进行设置
 
   
   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: //成功接收到1K
           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))
               { //序号00(文件名)
                 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_NAME_LENGTH);)
                     {
                       FileName[i++] = *file_ptr++; //保存文件名
                     }
                     FileName[i++] = '\0' ; //字符串形式
                     for (i = 0, file_ptr ++; (*file_ptr != ' ' ) && (i < (FILE_SIZE_LENGTH - 1));)
                     {
                       file_size[i++] = *file_ptr++; //文件大小
                     }
                     file_size[i++] = '\0' ;
                     Str2Int(file_size, &size); //Convert a string to an integer
 
                     /* Test the size of the image to be sent */
                     /* Image size is greater than Flash size */
                     if (size > (USER_FLASH_SIZE + 1))
                     {
                       /* End session */
                       Send_Byte(CA);
                       Send_Byte(CA);
                       return -1;
                     }
                     /* erase user application area */
                                         FLASH_Unlock();                     //解锁
                     FLASH_If_Erase(APPLICATION_ADDRESS); //This function does an erase of all user flash area
                                         FLASH_Lock();             //上锁
                                         
                                         Send_Byte(ACK);
                                         Send_Byte(CRC16);
                   }
                   /* Filename packet is empty, end session */
                   else
                   {
                     Send_Byte(ACK);
                     file_done = 1;
                     session_done = 1;
                     break ;
                   }
                 }
                 /* Data packet */
                 else  //文件信息保存完后开始接收数据
                 {
                   memcpy (buf_ptr, packet_data + PACKET_HEADER, packet_length);
                                     
                                     /*----------------------------------------------------------------------------------------------*/
                                     BufferIn=buf;
                                     for (j = 0; j < packet_length; j += 16) //每次解密16字节
                                     {
                                         //解密数据包
                                         aesDecrypt(BufferIn,bufferOut); //由于参数使用的是指针,所以解密后依旧存在buf里面
                                         BufferIn+=16;
                                     }
                                     /*----------------------------------------------------------------------------------------------*/
                                 
                   ramsource = (uint32_t)buf;
                   
                   /* Write received data in Flash */                                   //这个size参数是自己加进入的,便于判断文件传输完成
                   if (FLASH_If_Write(&flashdestination, (uint32_t*) ramsource, (uint16_t) packet_length/4, size)  == 0)
                   { //写入FLASH 
                     Send_Byte(ACK);
                   }
                   else /* An error occurred while writing to Flash memory */
                   {
                     /* End session */
                     Send_Byte(CA);
                     Send_Byte(CA);
                     return -2;
                   }
                 }
                 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;
}

Hex合并以及Hex bin
首次下载代码时为了方便需要合并bootloader和APP的hex文件,Hex合并本论坛有人已经提供了一个很好的方法 ( http://www.openedv.com/thread-70162-1-1.html),转成一个hex文件后通过上面那个PC软件,可以直接生成bin文件,之后通过超级终端接通串口传输过去就行了。

无线方式传输
我试过用电脑蓝牙传输( 使用超级终端发送) ,接收方也用蓝牙,可以实现升级文件的传输,但是一般客户不会使用电脑蓝牙来发送,只有笔记本有,台式需要蓝牙适配器,当然使用USB 转串口来实现升级也是不错的选择,但是需要个USB 转串口芯片,也可以直接改修串口发送函数改成USB 发送,加上USB 驱动就行了。综合考虑还是在产品的APP 通过ymodem 协议发送会比较符合现在客户的主流需求,安卓系统的我在网上有找到java 现成的ymodem 发送代码,但是苹果系统如果使用OC 进行开发,我目前还没有找到,希望有资源的坛友能提供,下面需要java 代码的ymodem 协议的可以在这个网址搜   
安卓手机也可以下载有些通用的串口助手,有些也有带ymodem 协议。至于其他的无线方式也是一样的。
整个升级过程分为以下步奏:
1. 完成ymodem 移植,修改官方ymodem 串口发送以及修改flash 的一些操作( 操作前解锁,操作完上锁) ,修改ymodem.h 的宏配置,对应编译器的bootloader 区和APP 代码区的地址
2. 移植aes.c 的代码,添加到ymodem 接收函数中
3. target 界面修改APP 的代码区地址,写入跳回Bootloader 的条件,接收到串口字符’1’ 后往指定地址写入0xAAAA ,软复位在bootloader 判断这个地址是否是0xAAAA( 这个写入我是另外开辟了1Kflash 专门用于存储掉电不丢失数据区,比如0xAAAA 就是) ,是则升级后跳回APP
4. 修改附件提供的PC AES256 加密软件,用VS 编译器重新将环境改为X86 C 代码(.c 文件) 生成DLL C# 调用,C# 代码中替换下关键字即可,如果我附件中的软件能直接用则不需要做任何修改( 直接能用的概率不高)
5. 合并两个hex 文件后,通过PC 软件加上16 字节的密匙后自动生成bin
6.下载超级终端,设置为ymodem发送文件,首先发送两个字符‘1’,进入bootloader后发送文件完成升级

PC软件

刚拿到这个软件的作者( 网址在上面有提到) 时会出现如下报警,经修改后可以正常使用。

VS 如何生成DLL 文件让PC 端软件能够正常使用,先根据下面这个网址操作一遍生成dll 工程

接着先打开项目工程

先将平台修改为X86

修改完后编译一遍,可以看到多了一个x86 的文件夹

之后将 Debug 里面有关 MYAES256 5 个文件都 copy x86->debug 里面,如下

接下来要做的就是用这个“ MYAES256.dl ”替代作者的那个“ AES256.dll ”了,替换关键字

修改完后编译程序,PC软件就可以正常运行了,密匙根据自己的MCU解密匙来写,如下

生成一个bin在桌面,之后用“超级终端”发送这个bin文件就可以了


升级资料分享.zip

15.23 MB, 下载次数: 87381


  • 16
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: STM32F429串口IAP(Ymodem)升级是一种在STM32F429上通过串口进行最小系统升级的方法。其中,IAP全称为In-Application Programming,可以实现对单片机程序在应用程序的控制下进行在线升级的功能;而Ymodem则是一种通用的串行数据传输协议,可以保证数据的可靠性。 该方法的使用过程如下: 1.使用对应的工具(如ST-LINK Utility)将应用程序和BOOTLOADER程序分别烧录至单片机的Flash中。其中,BOOTLOADER程序一定要占用Flash的起始位置,并且大小应该尽量小。 2.编写在应用程序中调用的IAP程序,该程序通过解析Ymodem数据包的方式将更新数据升级到Flash中。同时,IAP程序需要包含一些自我保护措施以避免出现卡死等问题。 3.通过串口将更新数据以Ymodem协议的方式发送给单片机。在发送数据之前,需要保证串口配置正确(比如波特率、数据位等)。 4.单片机收到数据后,进行解析并将数据写入Flash中。在写入数据时需要判断支持Flash的型号和大小,以及使用哪个扇区。 该方法的优点在于可以实现在线升级,并且对于不同的Flash型号和大小都有较好的兼容性。同时,采用Ymodem协议可以保证了数据的可靠性,避免了出现数据出错、丢失等问题。缺点则在于需要编写一定的IAP程序,并且在升级时存在一定的安全风险。 ### 回答2: STM32F429作为一款高性能的微控制器,具有多种升级方式。其中比较常用的方式为串口IAPYmodem升级。 串口IAP(In-Application Programming)是通过串口通信升级系统的一种简单可行的方法。在程序中添加IAP函数库,修改引脚配置,通过串口连接PC,将升级文件发送至微控制器,程序将自动更新Flash存储器中的程序。 而Ymodem升级则是通过调用UART外设与上位机之间通信,采用基于CRC-16校验的Ymodem-M协议完成数据传输的无需Bootloader的升级方式。该方法优点是可以通过任何终软件直接实现,缺点是升级速度可能会受到波特率和数据带宽限制,传输时间可能长。 综合来看,根据具体需求和情况选择合适的升级方式,既能提高升级效率,又能确保升级的稳定性和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值