STM32F407单片机编程入门(十三) 单片机IAP(在应用编程)详解及实战源码

文章目录

一.概要

STM32单片机程序升级方法有很多种,主要有以下几种:

1.将编译生成的hex/bin文件使用ST-Link/J-Link工具直接下载进 Flash 即可,Keil中点击下载就能下载,下载后的代码会存放在Flash的起始地址0x08000000处。

2.ISP(In System Programing),这个是利用了STM32单片机自带的 Bootloader 升级程序。一般可通过USART串口对Flash重新编程,再通过电脑上的ISP下载软件导入程序。在用户参考手册中,可以看到下表,关于启动模式设置的,ISP就是BOOT1引脚为0,BOOT0引脚为1,单片机就进入ISP模式。

  1. IAP(In Application Programing),即在应用编程,与之相对应的叫做ISP,两者的不同是ISP需要依靠烧写器在单片机复位离线的情况下编程,需要人工的干预,而IAP则是用户自己的程序在运行过程中对Flash 的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。

使用IAP技术能很好地降低现场工作量,实现IAP有两个很重要的前提

1.单片机程序能对自身的内部Flash 进行擦写。

2.单片机要有能够和外部进行通讯的方式,无论是网络还是别的方式,只要能传输数据就行。

二.STM32F407VET6单片机IAP介绍

1.STM32F407VET6单片机IAP基本原理

以STM32F407VET6单片机为例,每次程序复位是从0x08000000的位置开始执行主程序,如果不做IAP则这256KB(0x40000)空间都可以用来存放应用程序,但为了实现IAP,需要有划出一部分空间存放BOOT程序,BOOT程序跟应用程序是两个独立的工程,BOOT程序主要功能是来接收外部通讯(串口,485等)协议传输的应用程序代码文件(bin文件),并调用FLASH写入函数把bin文件分成N个32Bit数据,写入到应用程序地址空间,就实现对应用程序的升级。

2.STM32F407VET6单片机IAP基本流程

单片机先在BOOT工程的程序中跑,BOOT程序通过串口1(PA9,PA10引脚)接收上位机发来的.bin文件(应用程序工程),检查后将.bin文件写入到Flash特定位置(0x08004000开始的地址),bin文件写完后,单片机就从BOOT程序的空间跳转到应用程序的空间运行。

Bin文件是直接包含单片机的机器码,不包含调试信息或其他额外数据,这使得在传输过程中,无论是通过串口、网络还是其他接口,都能实现高效的二进制数据传输,所以一般IAP升级,就选用Bin文件下载到单片机指定的地址空间,就实现了程序升级目的。

三.配置一个BOOT工程

本实验配置一个包含跳转的程序工程,程序起始地址是0x0800000,大小是0x4000,包含FLASH写入以及Y-Modem协议。

打开STM32CubeMX软件,新建工程

Part Number处输入STM32F407VE,再双击就创建新的工程

配置下载口引脚

配置外部晶振引脚

配置系统主频168Mhz,使用外部晶振

配置PA9,PA10配置成串口1收发脚,波特率115200,8位数据,无校验,1位停止位。

串口1中断使能

配置工程文件名,保存路径,KEIL5工程输出方式

生成工程

用Keil5打开工程

添加代码

主要代码如下:

c 复制代码
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
			Main_Menu();//实现程序升级并跳转,Ymodem协议
  }
  /* USER CODE END 3 */
}

void Main_Menu(void)
{
  SerialPutString("\r\n  STM32F407 In-Application Programming Application  \r\n");
  while (1)
  {
		SerialDownload();
  }
}
void SerialDownload(void)
{

  int32_t Size = 0;

  Size = Ymodem_Receive(&tab_1024[0]);
  if (Size > 0)
  {
    SerialPutString("\r\n Programming Completed Successfully!\r\n ");
	  HAL_Delay(10);
		SerialPutString("----------Jump To App-------------\r\n");
		CLI();
		RCC_DeInit();
		NVIC_DeInit();
		JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);//定义跳转地址是0x08004000
		JumpToApplication = (pFunction) JumpAddress;
		/* Initialize user application's Stack Pointer */
		__set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);
		JumpToApplication();//跳转到应用程序

  }
  else if (Size == -1)
  {
    SerialPutString("\n\n\rThe image size is higher than the allowed space memory!\n\r");
  }
  else if (Size == -2)
  {
    SerialPutString("\n\n\rVerification failed!\n\r");
  }
  else if (Size == -3)
  {
    SerialPutString("\r\n\nAborted by user.\n\r");
  }
  else
  {
    SerialPutString("\n\rFailed to receive the file!\n\r");
  }
}

int32_t Ymodem_Receive (uint8_t *buf)
{
 uint8_t*file_ptr, *buf_ptr;
  int32_t i, packet_length, session_done, file_done, packets_received, errors, session_begin, size = 0;
  uint32_t flashdestination, ramsource;

  /* Initialize flashdestination variable */
  flashdestination = APPLICATION_ADDRESS;
  
  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_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);

                    /* 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_If_Erase(APPLICATION_ADDRESS);
										fmc_erase_pages();
                    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);
                  ramsource = (uint32_t)buf;
                  TimeOutCounter=0;
                  /* Write received data in Flash */
                  if (FLASH_If_Write(&flashdestination,ramsource, (uint16_t) packet_length/4)  == 0)
                  {
                    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 ++;
          }
					TimeOutCounter++;
					if(TimeOutCounter>=50)
					{
							SerialPutString("----------Jump To App-------------\r\n");
							CLI();
							RCC_DeInit();
							NVIC_DeInit();
							JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);//定义跳转地址是0x08004000
							JumpToApplication = (pFunction) JumpAddress;
							/* Initialize user application's Stack Pointer */
							__set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);
							JumpToApplication();//跳转到应用程序
						
						
					}
          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;
}

四.配置一个APP工程

本实验需要配置一个LED闪烁的程序工程,并改变程序起始地址到0x08004000

打开STM32CubeMX软件,新建工程

Part Number处输入STM32F407VE,再双击就创建新的工程

配置下载口引脚

配置外部晶振引脚

可以查看STM32F407VET6开发板原理图,PB4连接LED灯,所以配置PB4为GPIO输出

配置系统主频168Mhz,使用外部晶振

配置工程文件名,保存路径,KEIL5工程输出方式

生成工程

用Keil5打开工程

配置应用程序起始地址

输出.bin文件配置,配置完,编译的时候就会生成.bin文件

中断向量表偏移配置

主要代码如下:

c 复制代码
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset)
{ 
  /* Check the parameters */
  assert_param(IS_NVIC_VECTTAB(NVIC_VectTab));
  assert_param(IS_NVIC_OFFSET(Offset));  
   
  SCB->VTOR = NVIC_VectTab | Offset;
}
#define CLI() __set_PRIMASK(1)//关闭总中断  
#define SEI() __set_PRIMASK(0)//打开总中断
int main(void)
{
  /* USER CODE BEGIN 1 */
	SEI();						//打开全局中断,因为在IAP中关闭了全局中断
 	NVIC_SetVectorTable(FLASH_BASE, 0x4000);	//定位中断向量表
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();//初始化1毫秒 Tick

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();//外部8M晶振,系统168M主频

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
			HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_4);//PB4引脚翻转输出
			HAL_Delay(100);//等待100ms
  }
  /* USER CODE END 3 */
}

实验效果:

BOOT程序:主要实现YMODEM协议以及内部FLASH编程,程序烧录完之后,由APP程序生成的APP.Bin文件烧录到APP程序的FLASH地址空间,再实现程序跳转。

1.Keil5打开BOOT工程,编译,并烧录BOOT程序。

  1. Keil5打开APP程序,编译,生成APP.bin文件,文件在工程Objects目录下。

3.插上USB转TTL,接上TX<--->板子PA10,RX<--->板子PA9,GND<--->板子GND。打开超级终端,配置115200波特率,无校验,无数据流控制。



  1. 复位板子,超级终端会显示下图所示,会有C字符显示。
  1. 在C字符显示的过程中,选择传送,发送文件,选择407.bin文件,在APP\MDK-ARM\407路径下,协议选择YMODEM,点击发送。


  1. 会有进度条发送显示,并显示发送成功,并跳转到APP,如果超时没有发送,也会在超时几秒后自动跳转到APP。

五.工程源代码下载

通过网盘分享的文件:14.IAP升级实验.zip

链接: https://pan.baidu.com/s/16gM7B6-xoq9d4KBajG3qBg 提取码: 9nd2

如果链接失效,可以联系博主给最新链接

程序下载下来之后解压就行

CSDN资源

六.小结

在单片机应用中,在线升级功能是必不可少的,它可以让我们在不破坏硬件的情况下对程序进行升级和修正,提高了开发效率。

相关推荐
LateBloomer7773 小时前
FreeRTOS——信号量
笔记·stm32·学习·freertos
wenchm4 小时前
细说STM32单片机DMA中断收发RTC实时时间并改善其鲁棒性的另一种方法
stm32·单片机·嵌入式硬件
编码追梦人5 小时前
如何实现单片机的安全启动和安全固件更新
单片机
电子工程师UP学堂5 小时前
电子应用设计方案-16:智能闹钟系统方案设计
单片机·嵌入式硬件
飞凌嵌入式5 小时前
飞凌嵌入式T113-i开发板RISC-V核的实时应用方案
人工智能·嵌入式硬件·嵌入式·risc-v·飞凌嵌入式
blessing。。7 小时前
I2C学习
linux·单片机·嵌入式硬件·嵌入式
嵌新程8 小时前
day03(单片机高级)RTOS
stm32·单片机·嵌入式硬件·freertos·rtos·u575
Lin2012308 小时前
STM32 Keil5 attribute 关键字的用法
stm32·单片机·嵌入式硬件
电工小王(全国可飞)9 小时前
STM32 RAM在Memory Map中被分为3个区域
stm32·单片机·嵌入式硬件
maxiumII9 小时前
Diving into the STM32 HAL-----DAC笔记
笔记·stm32·嵌入式硬件