HAL库内部flash及IAP(boot)升级

1.内部flash的用法

1.闪存模块存储器组织(STM32F407)

STM32的闪存模块由:主存储器、系统存储器、OPT区域和选项字节等4部分组成。如下图所示:

 

1.主储存器(flash):有11个扇区,并且每个区不是一样的KB。把所有的区数量相加可以知道flash为1M.该部分用来存放代码和数据常数(如const类型的数据)。我们平时烧录的代码就在flash中。B0、B1都接GND的时候,就是从0X08000000开始运行代码的。

2.系统存储器:系统存储器,这个主要用来存放STM32F407的bootloader代码,此代码是出厂的时候就固化在STM32F429里面了,专门来给主存储器下载代码的。当B0接V3.3,B1接GND的时候,从该存储器启动(即进入串口下载模式)。之所以可以通过串口下载就是因为系统存储器。

3.OTP区域:OTP区域,即一次性可编程区域,共528字节,被分成两个部分,前面512字节(32字节为1块,分成16块),可以用来存储一些用户数据(一次性的,写完一次,永远不可以擦除!!),后面16字节,用于锁定对应块。

4.选项字节区:用于配置读保护、BOR级别、软件/硬件看门狗以及器件处于待机或停止模式下的复位。

2.flash的读操作

STM23的FLASH读取是很简单的。例如,我们要从地址addr,读取一个字(字节为8位,半字为16位,字为32位),可以通过如下的语句读取:data=*(vu32*)addr;

将addr强制转换为vu32指针,然后取该指针所指向的地址的值,即得到了addr地址的值。类似的,将上面的vu32改为vu16,即可读取指定地址的一个半字。

但还需要注意:CPU 时钟频率与 Flash 读取时间之间的关系 :为了准确读取 Flash 数据,必须根据 CPU 时钟 (HCLK) 频率和器件电源电压在 Flash 存取控 制寄存器 (FLASH_ACR) 中正确地编程等待周期数 (LATENCY)。 当电源电压低于 2.1 V 时,必须关闭预取缓冲器。当 VOS =“0”时,fHCLK 最大值 = 144 MHz。 当 VOS =“1”时,fHCLK 最大值 = 168 MHz。

下图所示为 Flash 等待周期与 CPU 时钟 频率之间的对应关系:

2.flash的写操作与擦除操作

在对 STM32的Flash执行写入或擦除操作期间,任何读取Flash的尝试都会导致总线阻塞。只有在完成编程操作后,才能正确处理读操作。这意味着,写/擦除操作进行期间不能从Flash中执行代码或数据获取操作。

1.写操作与擦除操作的寄存器介绍 :

2.写操作与擦除操作flash的注意事项:

1.STM32复位后,FLASH编程操作是被保护的,不能写入FLASH_CR寄存器;通过写入特定的序列(0X45670123和0XCDEF89AB)到FLASH_KEYR寄存器才可解除写保护,只有在写保护被解除后,我们才能操作相关寄存器。      FLASH_CR的解锁序列为:       1)写0X45670123(KEY1)到FLASH_KEYR       2)写0XCDEF89AB(KEY2)到FLASH_KEYR     通过这两个步骤,即可解锁FLASH_CR,如果写入错误,那么FLASH_CR将被       锁定,直到下次复位后才可以再次解锁。

使用的函数是:HAL_FLASH_Unlock();               

2.在执行闪存写操作时,任何对闪存的读操作都会锁住总线,在写操作完成后读操作才能正确地进行;既在进行写或擦除操作时,不能进行代码或数据的读取操作。所以在每次操作之前,我们都要等待上一次操作完成这次操作才能开始。使用的函数是:

HAL_StatusTypeDef FLASH_WaitForLastOperation(uint32_t Timeout); 入口参数为等待时间,返回值是FLASH的状态,这个很容易理解,这个函数本身我们在固件库中使用得不多,但是在固件库函数体中间可以多次看到。

3.STM32闪存的编程位数可以通过FLASH_CR的PSIZE字段配置,PSIZE的设置必须和电源电压匹配。

4. STM32的FLASH在编程的时候,也必须要求其写入地址的FLASH是被擦除了的(也就是其值必须是0XFFFFFFFF),否则无法写入。

总结:在写操作之前flash不能有其它的操作。不能处于忙的状态。如上第二点注意事项。在写操作之前要解锁取消写保护。如上的第一点注意事项。在写入时需要判断是否擦除如果不为0XFF表示还没有擦除需要擦除。为0XFF表示已经擦除。

3.擦除步骤

我们在STM32的FLASH编程的时候,要先判断缩写地址是否被擦除了,所以,我们有必要再介绍一下STM32的闪存擦除,STM32的闪存擦除分为两种: 扇区擦除 整片擦除

1.检查FLASH_CR的LOCK是否解锁,如果没有则先解锁

2.检查FLASH_SR寄存器中的BSY 位,确保当前未执行任何FLASH操作 在FLASH_CR寄存器中

3.将SER位置1,并从主存储块的12个扇区中选择要擦除的扇区 (SNB)

4.将FLASH_CR寄存器中的STRT位置1,触发擦除操作

5.等待BSY位清零。

其实这么多步我们只需调用:HAL_FLASHEx_Erase 函数就可以擦除某个扇区。

经过以上五步,就可以擦除某个扇区。,整片擦除功能我们在这里就不介绍了,想了解的朋友可以看《STM32xx中文参考手册》。

3. 代码举例

1.dome1  往FLASH固定地址写入一个整型数据然后读取出来串口显示。

#include "stm32f4xx_hal.h"
#include "string.h"
#include "usart/bsp_debug_usart.h"
#include "stmflash/stm_flash.h"
#include "key/bsp_key.h"

#define ADDRE 0x08020000    // 扇区5的首地址


/* 读取flash数据 */
uint32_t ReadFlash( uint32_t faddr )
{
	return *(__IO uint32_t*)faddr; 
}
/* 写flash数据 */
void WriteFlash_byte(uint32_t faddr,uint32_t data)
{
  HAL_FLASH_Unlock();						                           //解锁
  FLASH_WaitForLastOperation(3000);                        // 等待FLASH操作完成 3000:超时时间
  HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,faddr,data);    /* 写操作 */
  FLASH_WaitForLastOperation(3000);                        // 等待FLASH操作完成 3000:超时时间
  HAL_FLASH_Lock();                                        //上锁
}
/* 擦除操作 */
void easeFlash(uint8_t sector)
{
  FLASH_EraseInitTypeDef EraseInitStruct;
  uint32_t err;
  HAL_FLASH_Unlock();						            //解锁
  FLASH_WaitForLastOperation(3000);
  EraseInitStruct.Banks =  FLASH_BANK_1;    // 选择哪个Bank 
  EraseInitStruct.Sector = sector;          // 要擦除哪个扇区
  EraseInitStruct.TypeErase = FLASH_TYPEERASE_SECTORS  ;  // 只是擦除操作
  EraseInitStruct.VoltageRange = FLASH_VOLTAGE_RANGE_3 ;  // //电压范围,VCC=2.7~3.6V
  EraseInitStruct.NbSectors = 1;                          //一次只擦除一个扇区
  HAL_FLASHEx_Erase(&EraseInitStruct,&err);  /* 擦除某一扇区 */
  FLASH_WaitForLastOperation(3000);
  HAL_FLASH_Lock();                          //上锁
}

/**
  * 函数功能: 主函数.
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
int main(void)
{
  uint32_t i= 0;
  /* 复位所有外设,初始化Flash接口和系统滴答定时器 */
  HAL_Init();
  /* 配置系统时钟 */
  SystemClock_Config();
  /* 初始化串口并配置串口中断优先级 */
  MX_DEBUG_USART_Init();
  KEY_GPIO_Init();
  while(1)                            
  {	 
    if(KEY1_StateRead()==KEY_DOWN)             //如果KEY1被单击 先擦除地址的整个扇区然后写 
           入指定地址 (但要保证这个扇区没有数据否则会被清除)
    {
        easeFlash(FLASH_SECTOR_5);
        WriteFlash_byte(ADDRE,i++);
        printf("write i = %d \r\n",i-1);
    }			
    if(KEY2_StateRead()==KEY_DOWN)             //如果KEY2被单击  读取指定地址数据
    {	
       printf(" read i = %d \r\n",ReadFlash(ADDRE));
    }
	} 

2.dome2  往FLASH固定地址写入多个整型数据然后读取出来串口显示。

flash.h

#ifndef __STMFLASH_H
#define __STMFLASH_H
#include "stm32f4xx_hal.h"
#include "string.h"
	
//FLASH起始地址
#define STM32_FLASH_BASE 0x08000000 	//STM32 FLASH的起始地址
#define FLASH_WAITETIME  50000          //FLASH等待超时时间


uint32_t STMFLASH_ReadWord(uint32_t faddr);		  	//读出字  
void STMFLASH_Write(uint32_t WriteAddr,uint32_t *pBuffer,uint32_t NumToWrite);		//从指定地址开始写入指定长度的数据
void STMFLASH_Read(uint32_t ReadAddr,uint32_t *pBuffer,uint32_t NumToRead);   		//从指定地址开始读出指定长度的数据
//测试写入
void Test_Write(uint32_t WriteAddr,uint32_t WriteData);	
#endif

flash.c

#include "stmflash/stm_flash.h"


#define ADDR_FLASH_SECTOR_0     ((uint32_t)0x08000000) /* Base address of Sector 0, 16 Kbytes   */
#define ADDR_FLASH_SECTOR_1     ((uint32_t)0x08004000) /* Base address of Sector 1, 16 Kbytes   */
#define ADDR_FLASH_SECTOR_2     ((uint32_t)0x08008000) /* Base address of Sector 2, 16 Kbytes   */
#define ADDR_FLASH_SECTOR_3     ((uint32_t)0x0800C000) /* Base address of Sector 3, 16 Kbytes   */
#define ADDR_FLASH_SECTOR_4     ((uint32_t)0x08010000) /* Base address of Sector 4, 64 Kbytes   */
#define ADDR_FLASH_SECTOR_5     ((uint32_t)0x08020000) /* Base address of Sector 5, 128 Kbytes  */
#define ADDR_FLASH_SECTOR_6     ((uint32_t)0x08040000) /* Base address of Sector 6, 128 Kbytes  */
#define ADDR_FLASH_SECTOR_7     ((uint32_t)0x08060000) /* Base address of Sector 7, 128 Kbytes  */
#define ADDR_FLASH_SECTOR_8     ((uint32_t)0x08080000) /* Base address of Sector 8, 128 Kbytes  */
#define ADDR_FLASH_SECTOR_9     ((uint32_t)0x080A0000) /* Base address of Sector 9, 128 Kbytes  */
#define ADDR_FLASH_SECTOR_10    ((uint32_t)0x080C0000) /* Base address of Sector 10, 128 Kbytes */
#define ADDR_FLASH_SECTOR_11    ((uint32_t)0x080E0000) /* Base address of Sector 11, 128 Kbytes */
 
//读取指定地址的字(32位数据) 
//faddr:读地址 
//返回值:对应数据.
uint32_t STMFLASH_ReadWord(uint32_t  faddr)
{
   return *(__IO uint32_t*)faddr; 
}

//获取某个地址所在的flash扇区
//addr:flash地址
//返回值:0~11,即addr所在的扇区
///**
//  * 函数功能: 根据输入的地址给出它所在的sector
//  * 输入参数: Address flash地址
//  * 返 回 值: 无
//  * 说    明: 无
//  */
static uint32_t GetSector(uint32_t Address)
{
  uint32_t sector = 0;
  
  if((Address < ADDR_FLASH_SECTOR_1) && (Address >= ADDR_FLASH_SECTOR_0))
  {
    sector = FLASH_SECTOR_0;  
  }
  else if((Address < ADDR_FLASH_SECTOR_2) && (Address >= ADDR_FLASH_SECTOR_1))
  {
    sector = FLASH_SECTOR_1;  
  }
  else if((Address < ADDR_FLASH_SECTOR_3) && (Address >= ADDR_FLASH_SECTOR_2))
  {
    sector = FLASH_SECTOR_2;  
  }
  else if((Address < ADDR_FLASH_SECTOR_4) && (Address >= ADDR_FLASH_SECTOR_3))
  {
    sector = FLASH_SECTOR_3;  
  }
  else if((Address < ADDR_FLASH_SECTOR_5) && (Address >= ADDR_FLASH_SECTOR_4))
  {
    sector = FLASH_SECTOR_4;  
  }
  else if((Address < ADDR_FLASH_SECTOR_6) && (Address >= ADDR_FLASH_SECTOR_5))
  {
    sector = FLASH_SECTOR_5;  
  }
  else if((Address < ADDR_FLASH_SECTOR_7) && (Address >= ADDR_FLASH_SECTOR_6))
  {
    sector = FLASH_SECTOR_6;  
  }
  else if((Address < ADDR_FLASH_SECTOR_8) && (Address >= ADDR_FLASH_SECTOR_7))
  {
    sector = FLASH_SECTOR_7;  
  }
  else if((Address < ADDR_FLASH_SECTOR_9) && (Address >= ADDR_FLASH_SECTOR_8))
  {
    sector = FLASH_SECTOR_8;  
  }
  else if((Address < ADDR_FLASH_SECTOR_10) && (Address >= ADDR_FLASH_SECTOR_9))
  {
    sector = FLASH_SECTOR_9;  
  }
  else if((Address < ADDR_FLASH_SECTOR_11) && (Address >= ADDR_FLASH_SECTOR_10))
  {
    sector = FLASH_SECTOR_10;  
  }
  else /* (Address < FLASH_END_ADDR) && (Address >= ADDR_FLASH_SECTOR_11) */
  {
    sector = FLASH_SECTOR_11;  
  }

  return sector;
}
//从指定地址开始写入指定长度的数据
//特别注意:因为STM32F4的扇区实在太大,没办法本地保存扇区数据,所以本函数
//         写地址如果非0XFF,那么会先擦除整个扇区且不保存扇区数据.所以
//         写非0XFF的地址,将导致整个扇区数据丢失.建议写之前确保扇区里
//         没有重要数据,最好是整个扇区先擦除了,然后慢慢往后写. 
//该函数对OTP区域也有效!可以用来写OTP区!

//WriteAddr:起始地址(此地址必须为4的倍数!!)
//pBuffer:数据指针
//NumToWrite:字(32位)数(就是要写入的32位数据的个数.) 
void STMFLASH_Write(uint32_t  WriteAddr,uint32_t  *pBuffer,uint32_t  NumToWrite)	
{ 
	FLASH_EraseInitTypeDef FlashEraseInit;
	HAL_StatusTypeDef FlashStatus=HAL_OK;
	uint32_t  SectorError=0;
	uint32_t  addrx=0;
	uint32_t  endaddr=0;	
	if(WriteAddr<STM32_FLASH_BASE||WriteAddr%4)return;	//非法地址
    
	HAL_FLASH_Unlock();             //解锁	
	addrx=WriteAddr;				//写入的起始地址
	endaddr=WriteAddr+NumToWrite*4;	//写入的结束地址
    
	if(addrx<0X08100000)
	{
		while(addrx<endaddr)		//扫清一切障碍.(对非FFFFFFFF的地方,先擦除)
		{
			 if(STMFLASH_ReadWord(addrx)!=0XFFFFFFFF)//有非0XFFFFFFFF的地方,要擦除这个扇区
			{   
				FlashEraseInit.TypeErase=FLASH_TYPEERASE_SECTORS;       //擦除类型,扇区擦除 
				FlashEraseInit.Sector=GetSector(addrx);   //要擦除的扇区
				FlashEraseInit.NbSectors=1;                             //一次只擦除一个扇区
				FlashEraseInit.VoltageRange=FLASH_VOLTAGE_RANGE_3;      //电压范围,VCC=2.7~3.6V之间!!
				if(HAL_FLASHEx_Erase(&FlashEraseInit,&SectorError)!=HAL_OK) 
				{
					break;//发生错误了	
				}
				}else addrx+=4;
				FLASH_WaitForLastOperation(FLASH_WAITETIME);                //等待上次操作完成
		}
	}
	FlashStatus=FLASH_WaitForLastOperation(FLASH_WAITETIME);            //等待上次操作完成
	if(FlashStatus==HAL_OK)
	{
		 while(WriteAddr<endaddr)//写数据
		 {
			if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,WriteAddr,*pBuffer)!=HAL_OK)//写入数据
			{ 
				break;	//写入异常
			}
			WriteAddr+=4;
			pBuffer++;
		}  
	}
	HAL_FLASH_Lock();           //上锁
} 

//从指定地址开始读出指定长度的数据
//ReadAddr:起始地址
//pBuffer:数据指针
//NumToRead:字(32位)数
void STMFLASH_Read(uint32_t  ReadAddr,uint32_t  *pBuffer,uint32_t  NumToRead)   	
{
	uint32_t  i;
	for(i=0;i<NumToRead;i++)
	{
		pBuffer[i]=STMFLASH_ReadWord(ReadAddr);//读取4个字节.
		ReadAddr+=4;//偏移4个字节.	
	}
}

//测试用///
//WriteAddr:起始地址
//WriteData:要写入的数据
void Test_Write(uint32_t  WriteAddr,uint32_t  WriteData)   	
{
	STMFLASH_Write(WriteAddr,&WriteData,1);//写入一个字 
}

main.c

#include "stm32f4xx_hal.h"
#include "string.h"
#include "usart/bsp_debug_usart.h"
#include "led/bsp_led.h"
#include "stmflash/stm_flash.h"
#include "key/bsp_key.h"

#define FLASH_SAVE_ADDR  0X08010000 	//设置FLASH 保存地址(必须为4的倍数,且所在扇区,要大于本代码所占用到的扇区.

const uint8_t TEXT_Buffer[]={"STM32 FLASH TEST I LOVE YOU"};
#define TEXT_LENTH sizeof(TEXT_Buffer)	 		  	//数组长度	
#define SIZE TEXT_LENTH/4+((TEXT_LENTH%4)?1:0)

/**
  * 函数功能: 主函数.
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
int main(void)
{
  //uint32_t i= 0;
  uint32_t datatemp[SIZE]; // 保存读取flash的数组
  /* 复位所有外设,初始化Flash接口和系统滴答定时器 */
  HAL_Init();
  /* 配置系统时钟 */
  SystemClock_Config();
  /* 初始化串口并配置串口中断优先级 */
  MX_DEBUG_USART_Init();
  LED_GPIO_Init();
  KEY_GPIO_Init();
  while(1)                            
  {	 
    if(KEY1_StateRead()==KEY_DOWN)             //如果KEY1被单击 先擦除地址的整个扇区然后写入指定地址 (但要保证这个扇区没有数据否则会被清除)
    {
     
      STMFLASH_Write(FLASH_SAVE_ADDR, 
      (uint32_t*)TEXT_Buffer,sizeof(TEXT_Buffer)/sizeof(TEXT_Buffer[0]));
      printf("write:%s \r\n",TEXT_Buffer);
    }			
    if(KEY2_StateRead()==KEY_DOWN)             //如果KEY2被单击  读取指定地址数据
    {	
      STMFLASH_Read(FLASH_SAVE_ADDR,(uint32_t*)datatemp,SIZE);
      printf(" read: = %s \r\n",datatemp);
    }
	}  

2.IAP boot升级

boot的目的:在不需要操作硬件平台的情况下实现升级(远程)。

1.STM32编程方式

每种STM32芯片(M0,M3,M4,M),它们的主存储器结构可能不一样,但是他们都有一个叫“系统存储器”的区域,此区域是留给ST自己用来存放芯片的bootloader程序,此程序在芯片出厂的时候已经固化在芯片内部。 系统存储器的Bootloader程序会通过串口1接受应用程序。

 1.在线编程(ICP,In-Circuit Programming):   通过JTAG/SWD协议或者系统加载程序(Bootloader)下载用户应用程序到微控制器中。

总结:平时我们用ST-link,jlink,串口烧写代码到flash中都属于ICP下载流程。

2.在程序中编程(IAP,In Application Programming):       通过任何一种通信接口(如IO端口,USB,CAN,UART,I2C,SPI等)下载程序或者应用数据到存储器中。也就是说,STM32允许用户在应用程序中重新烧写闪存存储器中的内容。然而,IAP需要至少有一部分程序已经使用ICP方式烧到闪存存储器中(Bootloader)。 

 总结:相当于先用ICP方式把代码烧写到flash中(这段代码一般称为boot)。接着boot里有UART,I2C,SPI通信方式去接收上位机发送来的bin文件(app)。看如图:

2.STM32的启动方式

 总结:其实ST可以通过串口烧写程序不用ST-link。可以改变启动模式为系统存储器(厂家出厂就固定的)就可以了。烧完后又改变启动模式。从主闪存储器。去运行串口烧好的程序就可以了。

1. M7启动模式选择 比较特殊只有BOOTO

3.一般的程序执行流程与IAP升级的程序执行流程。

STM32的内部闪存(FLASH)地址起始于0x08000000,一般情况下,程序文件就从此地址开始写入。 0x08000004开始存放中断向量表。 当中断来临,STM32的内部硬件机制亦会自动将PC指针定位到“中断向量表”处,并根据中断源取出对应的中断向量执行中断服务程序。 STM32复位后,从0X08000004地址取出复位中断向量的地址,并跳转到复位中断服务程序。 在复位中断服务程序执行完之后,会跳转到我们的main函数。 main函数执行过程中,如果收到中断请求(发生重中断),此时STM32强制将PC指针指回中断向量表处。 根据中断源进入相应的中断服务程序。 在执行完中断服务程序以后,程序再次返回main函数执行。

STM32复位后,还是从0X08000004地址取出复位中断向量的地址,并跳转到复位中断服务程序,在运行完复位中断服务程序之后跳转到IAP的main函数。 在执行完IAP以后(即将新的APP代码写入STM32的FLASH,灰底部分。新程序的复位中断向量起始地址为0X08000004+N+M),跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的main函数,如图标号②和③所示。 在main函数执行过程中,如果CPU得到一个中断请求,PC指针仍强制跳转到地址        0X08000004中断向量表处,而不是新程序的        中断向量表 程序再根据我们设置的中断向量表偏移量,跳       转到对应中断源新的中断服务程序中。 在执行完中断服务程序后,程序返回main函数继续运行。

复位之后怎么跳转到main呢?

这个其实很重要。找到.s文件启动文件

执行复位中断后-----------------》SystemInit------------------------》_main----------------------->main

3.dome

首先boot代码:

flash.h

#ifndef __STMFLASH_H__
#define __STMFLASH_H__

/* 包含头文件 ----------------------------------------------------------------*/
#include "stm32f4xx_hal.h"

/* 类型定义 ------------------------------------------------------------------*/
/* 宏定义 --------------------------------------------------------------------*/
#define ADDR_FLASH_SECTOR_0     ((uint32_t)0x08000000) /* Base address of Sector 0, 16 Kbytes   */
#define ADDR_FLASH_SECTOR_1     ((uint32_t)0x08004000) /* Base address of Sector 1, 16 Kbytes   */
#define ADDR_FLASH_SECTOR_2     ((uint32_t)0x08008000) /* Base address of Sector 2, 16 Kbytes   */
#define ADDR_FLASH_SECTOR_3     ((uint32_t)0x0800C000) /* Base address of Sector 3, 16 Kbytes   */
#define ADDR_FLASH_SECTOR_4     ((uint32_t)0x08010000) /* Base address of Sector 4, 64 Kbytes   */
#define ADDR_FLASH_SECTOR_5     ((uint32_t)0x08020000) /* Base address of Sector 5, 128 Kbytes  */
#define ADDR_FLASH_SECTOR_6     ((uint32_t)0x08040000) /* Base address of Sector 6, 128 Kbytes  */
#define ADDR_FLASH_SECTOR_7     ((uint32_t)0x08060000) /* Base address of Sector 7, 128 Kbytes  */
#define ADDR_FLASH_SECTOR_8     ((uint32_t)0x08080000) /* Base address of Sector 8, 128 Kbytes  */
#define ADDR_FLASH_SECTOR_9     ((uint32_t)0x080A0000) /* Base address of Sector 9, 128 Kbytes  */
#define ADDR_FLASH_SECTOR_10    ((uint32_t)0x080C0000) /* Base address of Sector 10, 128 Kbytes */
#define ADDR_FLASH_SECTOR_11    ((uint32_t)0x080E0000) /* Base address of Sector 11, 128 Kbytes */


/************************** STM32 内部 FLASH 配置 *****************************/
#define STM32_FLASH_WREN        1    // stm32芯片内容FLASH 写入使能(0,禁用;1,使能)
#define STM32_FLASH_SIZE        1024  // 所选STM32的FLASH容量大小(单位为K)

/* 扩展变量 ------------------------------------------------------------------*/
/* 函数声明 ------------------------------------------------------------------*/
uint16_t STMFLASH_ReadHalfWord(uint32_t faddr);		  //读出半字

void STMFLASH_WriteLenByte(uint32_t WriteAddr, uint32_t DataToWrite, uint16_t Len );	      // 指定地址开始写入指定长度的数据
uint32_t STMFLASH_ReadLenByte(uint32_t ReadAddr, uint16_t Len );					                  // 指定地址开始读取指定长度数据
void STMFLASH_Write( uint32_t WriteAddr, uint16_t * pBuffer, uint16_t NumToWrite );		      // 从指定地址开始写入指定长度的数据
void STMFLASH_Read( uint32_t ReadAddr, uint16_t * pBuffer, uint16_t NumToRead );   	        // 从指定地址开始读出指定长度的数据
void Test_Write( uint32_t WriteAddr, uint16_t WriteData );                                  // 测试写入		
static uint32_t GetSector(uint32_t Address);                                                 

#endif /* __STMFLASH_H__ */

flash.c

/* 包含头文件 ----------------------------------------------------------------*/
#include "stmflash/stm_flash.h"
#include "led/bsp_led.h"

/* 私有类型定义 --------------------------------------------------------------*/
/* 私有宏定义 ----------------------------------------------------------------*/
#if STM32_FLASH_SIZE < 256
  #define STM_SECTOR_SIZE  1024 //字节
#else 
  #define STM_SECTOR_SIZE	 2048
#endif

#if STM32_FLASH_WREN	//如果使能了写 
static uint16_t STMFLASH_BUF [ STM_SECTOR_SIZE / 2 ];//最多是2K字节
#endif

/* 私有变量 ------------------------------------------------------------------*/
uint32_t FirstSector = 0;
uint32_t SectorError = 0;
static FLASH_EraseInitTypeDef EraseInitStruct;

/* 扩展变量 ------------------------------------------------------------------*/
/* 私有函数原形 --------------------------------------------------------------*/
static uint32_t GetSector(uint32_t Address);

/* 函数体 --------------------------------------------------------------------*/
/**
  * 函数功能: 读取指定地址的半字(16位数据)
  * 输入参数: faddr:读地址(此地址必须为2的倍数!!)
  * 返 回 值: 返回值:对应数据.
  * 说    明:无
  */
uint16_t STMFLASH_ReadHalfWord ( uint32_t faddr )
{
	return *(__IO uint16_t*)faddr; 
}

#if STM32_FLASH_WREN	//如果使能了写   
/**
  * 函数功能: 不检查的写入
  * 输入参数: WriteAddr:起始地址
  *           pBuffer:数据指针
  *           NumToWrite:半字(16位)数
  * 返 回 值: 无
  * 说    明:无
  */
void STMFLASH_Write_NoCheck ( uint32_t WriteAddr, uint16_t * pBuffer, uint16_t NumToWrite )   
{ 			 		 
	uint16_t i;	
	
	for(i=0;i<NumToWrite;i++)
	{
		HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,WriteAddr,pBuffer[i]);
	  WriteAddr+=2;                                    //地址增加2.
	}  
} 

/**
  * 函数功能: 从指定地址开始写入指定长度的数据
  * 输入参数: WriteAddr:起始地址(此地址必须为2的倍数!!)
  *           pBuffer:数据指针
  *           NumToWrite:半字(16位)数(就是要写入的16位数据的个数.)
  * 返 回 值: 无
  * 说    明:无
  */
void STMFLASH_Write ( uint32_t WriteAddr, uint16_t * pBuffer, uint16_t NumToWrite )	
{
	uint16_t secoff;	   //扇区内偏移地址(16位字计算)
	uint16_t secremain; //扇区内剩余地址(16位字计算)	   
 	uint16_t i;    
	uint32_t secpos;	   //扇区地址
	uint32_t offaddr;   //去掉0X08000000后的地址
	
	if(WriteAddr<FLASH_BASE||(WriteAddr>=(FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址
	
	HAL_FLASH_Unlock();						//解锁
	
	offaddr=WriteAddr-FLASH_BASE;		//实际偏移地址.
	secpos=offaddr/STM_SECTOR_SIZE;			//扇区地址 
	secoff=(offaddr%STM_SECTOR_SIZE)/2;		//在扇区内的偏移(2个字节为基本单位.)
	secremain=STM_SECTOR_SIZE/2-secoff;		//扇区剩余空间大小   
	if(NumToWrite<=secremain)secremain=NumToWrite;//不大于该扇区范围
	
	while(1) 
	{	
		STMFLASH_Read(secpos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//读出整个扇区的内容
		for(i=0;i<secremain;i++)//校验数据
		{
			if(STMFLASH_BUF[secoff+i]!=0XFFFF)break;//需要擦除  	  
		}
		if(i<secremain)//需要擦除
		{
      /* 获取要擦除的首个扇区 */
      FirstSector = GetSector(WriteAddr);
      /* 初始化擦除结构体 */
      EraseInitStruct.TypeErase = FLASH_TYPEERASE_SECTORS;  //擦除类型,扇区擦除 
      EraseInitStruct.VoltageRange = FLASH_VOLTAGE_RANGE_3; //电压范围,VCC=2.7~3.6V
      EraseInitStruct.Sector = FirstSector;                 //要擦除的扇区
      EraseInitStruct.NbSectors = 4;                        //一次只擦除一个扇区
      if(HAL_FLASHEx_Erase(&EraseInitStruct, &SectorError) == HAL_OK)
      { 
         printf("擦除完成\n");
      }
			for(i=0;i<secremain;i++)//复制
			{
				STMFLASH_BUF[i+secoff]=pBuffer[i];	  
			}
			STMFLASH_Write_NoCheck(secpos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区  
		}else STMFLASH_Write_NoCheck(WriteAddr,pBuffer,secremain);//写已经擦除了的,直接写入扇区剩余区间. 				   
		if(NumToWrite==secremain)
    {
      break;//写入结束了
    }
		else//写入未结束
		{
			secpos++;				//扇区地址增1
			secoff=0;				//偏移位置为0 	 
		   	pBuffer+=secremain;  	//指针偏移
			WriteAddr+=secremain;	//写地址偏移	   
		   	NumToWrite-=secremain;	//字节(16位)数递减
			if(NumToWrite>(STM_SECTOR_SIZE/2))secremain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完
			else secremain=NumToWrite;//下一个扇区可以写完了
		}	 
	};	
	HAL_FLASH_Lock();//上锁
}
#endif

/**
  * 函数功能: 从指定地址开始读出指定长度的数据
  * 输入参数: ReadAddr:起始地址
  *           pBuffer:数据指针
  *           NumToRead:半字(16位)数
  * 返 回 值: 无
  * 说    明:无
  */
void STMFLASH_Read ( uint32_t ReadAddr, uint16_t *pBuffer, uint16_t NumToRead )   	
{
	uint16_t i;
	
	for(i=0;i<NumToRead;i++)
	{
		pBuffer[i]=STMFLASH_ReadHalfWord(ReadAddr);//读取2个字节.
		ReadAddr+=2;//偏移2个字节.	
	}
}

/**
  * 函数功能: 向内部flash写入数据测试
  * 输入参数: WriteAddr:起始地址
  *           WriteData:要写入的数据
  * 返 回 值: 无
  * 说    明:无
  */
void Test_Write( uint32_t WriteAddr, uint16_t WriteData )   	
{
	STMFLASH_Write(WriteAddr,&WriteData,1);//写入一个字 
}

/**
  * 函数功能: 根据输入的地址给出它所在的sector
  * 输入参数: Address flash地址
  * 返 回 值: 无
  * 说    明: 无
  */
static uint32_t GetSector(uint32_t Address)
{
  uint32_t sector = 0;
  
  if((Address < ADDR_FLASH_SECTOR_1) && (Address >= ADDR_FLASH_SECTOR_0))
  {
    sector = FLASH_SECTOR_0;  
  }
  else if((Address < ADDR_FLASH_SECTOR_2) && (Address >= ADDR_FLASH_SECTOR_1))
  {
    sector = FLASH_SECTOR_1;  
  }
  else if((Address < ADDR_FLASH_SECTOR_3) && (Address >= ADDR_FLASH_SECTOR_2))
  {
    sector = FLASH_SECTOR_2;  
  }
  else if((Address < ADDR_FLASH_SECTOR_4) && (Address >= ADDR_FLASH_SECTOR_3))
  {
    sector = FLASH_SECTOR_3;  
  }
  else if((Address < ADDR_FLASH_SECTOR_5) && (Address >= ADDR_FLASH_SECTOR_4))
  {
    sector = FLASH_SECTOR_4;  
  }
  else if((Address < ADDR_FLASH_SECTOR_6) && (Address >= ADDR_FLASH_SECTOR_5))
  {
    sector = FLASH_SECTOR_5;  
  }
  else if((Address < ADDR_FLASH_SECTOR_7) && (Address >= ADDR_FLASH_SECTOR_6))
  {
    sector = FLASH_SECTOR_6;  
  }
  else if((Address < ADDR_FLASH_SECTOR_8) && (Address >= ADDR_FLASH_SECTOR_7))
  {
    sector = FLASH_SECTOR_7;  
  }
  else if((Address < ADDR_FLASH_SECTOR_9) && (Address >= ADDR_FLASH_SECTOR_8))
  {
    sector = FLASH_SECTOR_8;  
  }
  else if((Address < ADDR_FLASH_SECTOR_10) && (Address >= ADDR_FLASH_SECTOR_9))
  {
    sector = FLASH_SECTOR_9;  
  }
  else if((Address < ADDR_FLASH_SECTOR_11) && (Address >= ADDR_FLASH_SECTOR_10))
  {
    sector = FLASH_SECTOR_10;  
  }
  else /* (Address < FLASH_END_ADDR) && (Address >= ADDR_FLASH_SECTOR_11) */
  {
    sector = FLASH_SECTOR_11;  
  }

  return sector;

iap.h

#ifndef __BSP_IAP_H__
#define	__BSP_IAP_H__

/* 包含头文件 ----------------------------------------------------------------*/
#include "stm32f4xx_hal.h"

/* 类型定义 ------------------------------------------------------------------*/
/************************** IAP 数据类型定义********************************/
typedef  void ( * pIapFun_TypeDef ) ( void ); //定义一个函数类型的参数.

/* 宏定义 --------------------------------------------------------------------*/
/************************** IAP 宏参数定义********************************/
 //是否更新 APP 到 FLASH,否则更新到 RAM
#define User_Flash                                        

#ifdef User_Flash
  #define APP_START_ADDR       0x8010000  	//应用程序起始地址(存放在FLASH)
#else
  #define APP_START_ADDR       0x20001000  	//应用程序起始地址(存放在RAM)
#endif

/************************** IAP 外部变量********************************/
#define APP_FLASH_LEN  			   56320u       //定义 APP 固件最大容量,55kB=55*1024=56320

/* 扩展变量 ------------------------------------------------------------------*/
extern struct  STRUCT_IAP_RECIEVE      //串口数据帧的处理结构体
{
	uint8_t   ucDataBuf[APP_FLASH_LEN];
	uint16_t  usLength;
} strAppBin;

/* 函数声明 ------------------------------------------------------------------*/
void IAP_Write_App_Bin( uint32_t appxaddr, uint8_t * appbuf, uint32_t applen);	//在指定地址开始,写入bin
void IAP_ExecuteApp( uint32_t appxaddr );			                              //执行flash里面的app程序

#endif /* __BSP_IAP_H__ */

iap.c

/* 包含头文件 ----------------------------------------------------------------*/
#include "IAP/bsp_iap.h"
#include "stmflash/stm_flash.h"
/* 私有类型定义 --------------------------------------------------------------*/
/* 私有宏定义 ----------------------------------------------------------------*/
/* 私有变量 ------------------------------------------------------------------*/
#if defined ( __CC_ARM )  // 使用Keil编译环境

struct STRUCT_IAP_RECIEVE strAppBin  __attribute__((at(0x20001000)))={{0},0};

#elif defined ( __ICCARM__ ) // 使用IAR编译环境

struct STRUCT_IAP_RECIEVE strAppBin;//={{0},0};

#endif


static uint16_t ulBuf_Flash_App[1024];

/* 扩展变量 ------------------------------------------------------------------*/
/* 私有函数原形 --------------------------------------------------------------*/
/* 函数体 --------------------------------------------------------------------*/
void IAP_Write_App_Bin ( uint32_t ulStartAddr, uint8_t * pBin_DataBuf, uint32_t ulBufLength )
{
	uint16_t us, usCtr=0, usTemp;
	uint32_t ulAdd_Write = ulStartAddr;                                //当前写入的地址
	uint8_t * pData = pBin_DataBuf;
	
	for ( us = 0; us < ulBufLength; us += 2 )
	{						    
		usTemp =  ( uint16_t ) pData[1]<<8;
		usTemp += ( uint16_t ) pData[0];	  
		pData += 2;                                                      //偏移2个字节
		ulBuf_Flash_App [ usCtr ++ ] = usTemp;	    
		if ( usCtr == 1024 )
		{
			usCtr = 0;
			STMFLASH_Write ( ulAdd_Write, ulBuf_Flash_App, 1024 );	
			ulAdd_Write += 2048;                                           //偏移2048  16=2*8.所以要乘以2.
		}
	}
	if ( usCtr ) 
  { 
    STMFLASH_Write ( ulAdd_Write, ulBuf_Flash_App, usCtr );//将最后的一些内容字节写进去.  
    
  }
}


#if defined ( __CC_ARM )  // 使用Keil编译环境

__asm void MSR_MSP ( uint32_t ulAddr ) 
{
    MSR MSP, r0		                   //set Main Stack value
    BX r14
}

#elif defined ( __ICCARM__ ) // 使用IAR编译环境

void MSR_MSP ( uint32_t ulAddr ) 
{
    asm("MSR MSP, r0"); 			                   //set Main Stack value
    asm("BX r14");
}


#endif

//跳转到应用程序段
//ulAddr_App:用户代码起始地址.
void IAP_ExecuteApp ( uint32_t ulAddr_App )
{
	pIapFun_TypeDef pJump2App; 
	
	if ( ( ( * ( __IO uint32_t * ) ulAddr_App ) & 0x2FF00000 ) == 0x20000000 )	  //检查栈顶地址是否合法.
	{ 
    printf("开始执行?\n");
		pJump2App = ( pIapFun_TypeDef ) * ( __IO uint32_t * ) ( ulAddr_App + 4 );	        //用户代码区第二个字为程序开始地址(复位地址)		
		MSR_MSP( * ( __IO uint32_t * ) ulAddr_App );					                            //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
		pJump2App ();								                                    	//跳转到APP.
	}
}	

main.c

/* 包含头文件 ----------------------------------------------------------------*/
#include "stm32f4xx_hal.h"
#include "string.h"
#include "usart/bsp_debug_usart.h"
#include "led/bsp_led.h"
#include "stmflash/stm_flash.h"
#include "key/bsp_key.h"
#include "IAP/bsp_iap.h"

/* 私有类型定义 --------------------------------------------------------------*/
/* 私有宏定义 ----------------------------------------------------------------*/
/* 私有变量 ------------------------------------------------------------------*/
uint8_t Rxdata;

/* 扩展变量 ------------------------------------------------------------------*/
/* 私有函数原形 --------------------------------------------------------------*/
/* 函数体 --------------------------------------------------------------------*/
/**
  * 函数功能: 系统时钟配置
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
void SystemClock_Config(void)
{

  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;
 
  __HAL_RCC_PWR_CLK_ENABLE();                                     //使能PWR时钟

  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);  //设置调压器输出电压级别1

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;      // 外部晶振,8MHz
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;                        //打开HSE 
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;                    //打开PLL
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;            //PLL时钟源选择HSE
  RCC_OscInitStruct.PLL.PLLM = 8;                                 //8分频MHz
  RCC_OscInitStruct.PLL.PLLN = 336;                               //336倍频
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;                     //2分频,得到168MHz主时钟
  RCC_OscInitStruct.PLL.PLLQ = 7;                                 //USB/SDIO/随机数产生器等的主PLL分频系数
  HAL_RCC_OscConfig(&RCC_OscInitStruct);

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;       // 系统时钟:168MHz
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;              // AHB时钟: 168MHz
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;               // APB1时钟:42MHz
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;               // APB2时钟:84MHz
  HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5);

  HAL_RCC_EnableCSS();                                            // 使能CSS功能,优先使用外部晶振,内部时钟源为备用
  
 	// HAL_RCC_GetHCLKFreq()/1000    1ms中断一次
	// HAL_RCC_GetHCLKFreq()/100000	 10us中断一次
	// HAL_RCC_GetHCLKFreq()/1000000 1us中断一次
  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);                // 配置并启动系统滴答定时器
  /* 系统滴答定时器时钟源 */
  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

  /* 系统滴答定时器中断优先级配置 */
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

/**
  * 函数功能: 主函数.
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
int main(void)
{

  /* 复位所有外设,初始化Flash接口和系统滴答定时器 */
  HAL_Init();
  /* 配置系统时钟 */
  SystemClock_Config();
  /* 初始化串口并配置串口中断优先级 */
  MX_DEBUG_USART_Init();
  LED_GPIO_Init();
  KEY_GPIO_Init();
  HAL_UART_Receive_IT(&husart_debug,&Rxdata,1);
  
#ifdef User_Flash
  printf("下面将烧写 APP 到 FLASH\n");
  printf("如果需要更新 APP,请发送 APP 固件(.bin文件),完毕后请单击 KEY1。否则单击 KEY2 执行 APP。\n");
  
  /* 无限循环 */
  while(1)                            
  {	 
    if(KEY1_StateRead()==KEY_DOWN)             //如果KEY1被单击
    {
      printf("APP 长度:%d字节\n", strAppBin .usLength );				
      printf("开始更新 APP\n");
      //更新FLASH代码  	
      IAP_Write_App_Bin(APP_START_ADDR, strAppBin .ucDataBuf, strAppBin .usLength );
      printf ( "单击 KEY2,执行 APP\n" );
    }			
    if(KEY2_StateRead()==KEY_DOWN)             //如果KEY2被单击
    {	
      printf ( "开始执行 APP\n" ); 
      //执行FLASH APP代码
      IAP_ExecuteApp(APP_START_ADDR);
    }
	}    
#else		
  printf("下面将烧写 APP 到 RAM\n");
  printf("请发送 APP 固件(.bin文件)\n");	
  printf("发送完毕后请单击 KEY2 执行 APP\n");			
  while(1)                            
  {	   
    if(KEY2_StateRead()==KEY_DOWN )             //如果KEY2被单击
    {
      printf("APP 长度:%d字节\n",strAppBin .usLength);					
      printf("开始执行 APP\n"); 					
      IAP_ExecuteApp ( APP_START_ADDR );    //执行RAM APP代码        
    }
  }
#endif
}

/**
  * 函数功能: 串口接收完成回调函数
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:无
  */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle)
{
  if(strAppBin.usLength<APP_FLASH_LEN)
  {
		strAppBin.ucDataBuf[strAppBin.usLength++]=Rxdata;
  }
  HAL_UART_Receive_IT(&husart_debug,&Rxdata,1);
}

 boot程序通过UART接收APP程序先放在 固定SRAM内存里 __attribute__((at(0x20001000)))={{0},0};。之后在放到flash里。

boot的大小:flash: Code + RO-data SRAM:RW-data + ZI-data

 所以APP要放到boot:Code + RO-data SRAM 地址之后。避免与boot重合。

 boot起始地址设置从0x8000000开始。

boot程序中断向量没有偏移如下:

 APP程序:

1.要把起始地址改为0x8010000  剩余大小:0xF0000 起始地址一定是在boot之后的

2.中断向量的偏移:要与起始的偏移一样。把VECT_TAB_OFFSET 改为 0x10000

 3.怎么生成bin文件

 

C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o YS-F4Pro.bin  YS-F4Pro\YS-F4Pro.axf

C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe 是自己keil的安装目录

YS-F4Pro.bin 生成的bin文件名  YS-F4Pro.axf生成的axf文件名   YS-F4Pro是工程名来的

所有整体流程就是先烧写boot,然后boot里接app程序。然后写入偏移的flash地址。接着跳转执行APP程序。

 

  • 5
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值