| 银杏科技有限公司旗下技术文档发布平台 | 
	
		| 技术支持电话 | 0379-69926675-801 | 
	
		| 技术支持邮件 | Gingko@vip.163.com | 
	
		| 版本 | 日期 | 作者 | 修改内容 | 
	
		| V1.0 | 2020-07-11 | gingko | 初次建立 | 
实验三十二:UART_IAP_ARM实验——更新升级STM32
一、 实验目的与意义
-  了解STM32的IAP结构。 
-  了解STM32的IAP特征。 
-  掌握STM32的IAP的使用方法。 
-  掌握UART的使用方法。 
-  掌握STM32 HAL库中UART属性的配置方法。 
-  掌握KEIL MDK 集成开发环境使用方法。 
 
二、 实验设备及平台
- 
- 
-  Micro USB线缆。 
-  Keil MDK 开发平台。 
-  STM32CubeMX开发平台。 
-  装有WIN XP(及更高版本)系统的计算机。 
 
三、 实验原理
1、IAP简介
-  IAP是In Application Programming的首字母缩写,IAP是用户自己的程序在运行过程中对User Flash的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。 
-  通常在用户需要实现IAP功能时,即用户程序运行中作自身的更新操作,需要在设计固件程序时编写两个项目代码,第一个项目程序不执行正常的功能操作,而只是通过某种通信管道(如USB、USART)接收程序或数据,执行对第二部分代码的更新;第二个项目代码才是真正的功能代码。这两部分项目代码都同时烧录在User Flash中,当芯片上电后,首先是第一个项目代码开始运行,它作如下操作: - 
-  (1)检查是否需要对第二部分代码进行更新 
-  (2)如果不需要更新则转到4) 
-  (3)执行更新操作 
-  (4)跳转到第二部分代码执行 
 
-  第一部分代码必须通过其它手段,如JTAG或ISP烧入;第二部分代码可以使用第一部分代码IAP功能烧入,也可以和第一部分代码一道烧入,以后需要程序更新是再通过第一部分IAP代码更新。 
-  对于STM32来说,因为它的中断向量表位于程序存储器的最低地址区,为了使第一部分代码能够正确地响应中断,通常会安排第一部分代码处于Flash的开始区域,而第二部分代码紧随其后。 
-  在第二部分代码开始执行时,首先需要把CPU的中断向量表映像到自己的向量表,然后再执行其他的操作。 
-  如果IAP程序被破坏,产品必须返厂才能重新烧写程序,这是很麻烦并且非常耗费时间和金钱的。针对这样的需求,STM32在对Flash区域实行读保护的同时,自动地对用户Flash区的开始4页设置为写保护,这样可以有效地保证IAP程序(第一部分代码)区域不会被意外地破坏。 
 
2、UART简介
-  UART;通用同步/异步串行接收/发送器,由时钟发生器、数据发送器和接收器三大部分组成。UART是一个全双工通用同步/异步串行收发模块,该接口是一个高度灵活的串行通信设备。 
-  串口作为MCU的重要外部接口,同时也是软件开发重要的调试手段,其重要性不言而喻。现在基本上所有的MCU都会带有串口,STM32自然也不例外。STM32F767的串口资源相当丰富的,功能也相当强劲。iCore4双核心板所使用的STM32F767IGT6最多可提供8路串口,支持8/16倍过采样、支持自动波特率检测、支持Modbus通信、支持同步单线通信和半双工单线通讯、具有DMA等。 
-  UART特点: 
 
3、USART框图
四、 实验程序
1、主函数
int main(void)
{
    int i = 0;
  /* MCU配置*/
  /* 重置所有外设, 初始化Flash接口和Systick. */
  HAL_Init();
  /* 系统时钟配置 */
  SystemClock_Config();
  /* 初始化所有已配置的外设 */
  MX_GPIO_Init();
  MX_USART6_UART_Init();
 
  usart6.initialize(115200);      //初始化串口波特率 115200
  usart6.printf("\x0c");          //清屏
  usart6.printf("\033[1;32;40m"); //设置终端字体为绿色    
 
  LED_GREEN_ON;
  if(ARM_KEY_STATE == KEY_UP){    //按键松开状态直接跳向应用程序
        goto start;
    }
    while(1){                      //按键按下,进入升级状态
        if(i++ == 5000000){
            //串口发送字符C
            usart6.send_byte('C');
            i = 0;
        }    
        if(usart6.receive_buffer[0] == SOH){
            break;
        }
    }
  while (1)
  {
        if(usart6.receive_ok_flag == 1){
            usart6.receive_ok_flag = 0;
            LED_GREEN_OFF;
            xmodem.process();
            if(usart6.receive_buffer[0] == EOT){
                usart6.send_byte(ACK);  
                //读保护
                for(i = 0; i < 100000; i ++); 
                HAL_FLASH_Lock();
                //发送文件成功,绿灯亮
                LED_BLUE_OFF;
                LED_RED_OFF;
                LED_GREEN_ON;
                while(1);
            }   
        }   
  }
    start:
    //测试用户代码是否从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();
        }else{        
            led_trade();
        }   
}
 
 
2、xmodem处理函数
static int process(void)
{
/* 128 for XModem  + 3 head chars + 2 crc + nul */ 
    unsigned char xbuff[140]; 
 
    int i = 0;
    int cnt;
    unsigned long int * p;
 
    if(usart6.receive_buffer[0] == SOH){//接收到有效数据帧头
        xbuff[0]=usart6.receive_buffer[0];
        for(i=0;i<133;i++){//接收一帧数据
            xbuff[i+1]=usart6.receive_buffer[i+ 1];
        }
        if((xbuff[1]==(uint8_t)~xbuff[2])&&((packetno % 256) == xbuff[1])//包序号无误
            &&(crc16.check(&xbuff[3], 128) == (xbuff[131] << 8 | xbuff[132]))){//CRC校验无误
                if(packetno == 1){
                    LED_BLUE_ON;
                    HAL_FLASH_Unlock();
                    for(cnt = FLASH_SECTOR_2;cnt < FLASH_SECTOR_11;cnt ++){
                        FLASH_Erase_Sector(cnt,OB_BOR_LEVEL3);  
                    }
                    LED_BLUE_OFF;
                    p = (unsigned long int *)&xbuff[3];
                    for(cnt = 0;cnt < 32;cnt++){
                        HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,APPLICATION_ADDRESS + (cnt * 4),*(p + cnt));            
                    }
                    packetno++;
                    usart6.send_byte(ACK);
                    return 0;
                }
            packetno++;
            p = (unsigned long int *)&xbuff[3];
            for(cnt = 0;cnt < 32;cnt++){
                HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,APPLICATION_ADDRESS + (packetno - 2) * 128 + (cnt * 4),*(p + cnt));            
            }
            usart6.send_byte(ACK);
        }
        else{//要求重发
            for(cnt = FLASH_SECTOR_2;cnt < FLASH_SECTOR_11;cnt ++){
                FLASH_Erase_Sector(cnt,OB_BOR_LEVEL3);  
            }
            led_trade();
        }
    }
    return 0;
}
 
 
五、 实验步骤
-  把仿真器与iCore4的SWD调试口相连(直接相连或者通过转接器相连); 
-  将跳线冒插在USB UART; 
-  把iCore4(USB UART)通过Micro USB线与计算机相连,为iCore4供电; 
-  打开Keil MDK 开发环境,并打开 APP 实验工程,编译连接后,将 Objects 文件夹下的 app.hex 文件拷贝至 hex_to_bin 文件夹下,将其转化成 app.bin 文件(方法:将app.hex 拉至 HEX2BIN 应用程序); 
-  打开Keil MDK 开发环境,并打开IAP实验工程; 
-  按下 ARM-KEY 将烧写 Bootloadert 程序到iCore4上; 
-  打开 Putty,点击Files Transfer>Xmodem>Send,选择app.bin文件路径(\hex_to_bin\app.bin),通过串口发送app.bin文件; 
-  也可以进入Debug模式,单步运行或设置断点验证程序逻辑。 
 
六、 实验现象