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程序。