学习STM32的IAP(在应用编程功能),可以把没有用到的片上的FLASH用作数据存储。数据手册请参看第2章/
不同型号的STM32,其FLASH容量也有所不同,最小的只有16k字节,最大的则达到1024k字节,星光的枭龙STM32开发板选择的是STM32F103VET6的FLASH容量为512K字节,属于大容量产品(另外还有中容量和小容量产品),其闪存结构图如下:
编程和擦除闪存
闪存编程一次可以写入16位(半字)。
闪存擦除操作可以按页面擦除或完全擦除(全擦除)。全擦除不影响信息块
为了确保不发生过度编程,闪存编程和擦除控制器块是由一个固定的时钟控制的。
写操作(编程或擦除)结束时可以触发中断。仅当闪存控制器接口时钟开启时,此中断可以用来从WFI(等待中断)模式退出。
闪存读取
闪存的指令和数据访问是通过AHB总线完成的。预取模块是用于通过ICode(访问指令)总线读取指令的。仲裁是作用在闪存接口,并且DCode(访问数据)总线上的数据访问优先。
读访问可以有以下配置选项:
等待时间:可以随时更改的用于读取操作的等待状态的数量。
预取缓冲区(2个64位):在每一次复位以后被自动打开,由于每个缓冲区的额大小(64位)与闪存的带宽相同,因此只通过需一次读闪存的操作即可更新整个缓冲区的内容。由于预取缓冲区的存在,CPU可以工作在更高的主频CPU
每次取指最多为32位的字,取一条指令时,下一条指令已经在缓冲区中等待。
半周期:用于功耗优化
注意:
1.这些选项应与闪存存储器的访问时间一起使用。等待周期体现了系统时钟(SYSCLK)频率与闪存访问时间的关系:
0等待周期,当 0 < SYSCLK < 24MHZ
1等待周期,当24MHz < SYSCLK < = 48MHz
2等待周期,当48MHz < SYSCLK <= 72MHz
2.半周期配置不能与使用了预分频的AHB(高级高性能总线)一起使用,时钟系统应该等于HCLK时钟。该特性只能用在时钟频率为8MHz或低于8MHz时,可以直接使用的内部RC振荡器(HSI),或者是主振荡器(HSE),但不能用PLL(锁相环倍频输出)
3.当AHB预分频系数不为1时,必须预取缓冲区处于开启状态。
5.使用DMA:DMA在DCode总线上访问闪存存储器,它的优先级比ICode上的取指高。DMA在每次传送完成后具有一个空余的周期。有些指令可以和DMA传输一起执行。
具体寄存器内容,请参看数据手册第2章节的内容。
main.c
下载后打开串口调试助手,波特率为115200,程序功能是向STM32F103VET6的FLASH页200,即地址0x08064000开始处写入250字节数据,然后再读出来,比较读出和写入是否一致,正确串口打印“STM32 FLASH ReadWrite OK”,错误串口打印"STM32 FLASH ReadWrite ERROR"
#include "MyIncludes.h"
#define FLASH_PAGE 200
//页号 200页
#define PAGE_NUM 1
//擦除页数 1
#define PAGE_ADDR (STM32_FLASH_BASE + FLASH_PAGE * STM32_PAGE_SIZE)
//编程地址 0x08000000(FLASH的起始地址)+ 200 * 2048
//16进位制转换为0x08064000;
u16 sys_cnt = 0;
void systick_isr(void)
{
if(sys_cnt <100 )
sys_cnt++;
else
{
sys_cnt = 0;
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_4|GPIO_PIN_5);
}
}
u8 res;
u8 wr_buff[250];
//写数组
u8 rd_buff[250];
//读数组
int main(void)
{
u8 i;
System_Init();
LED_Init();
SysTick_Init(systick_isr);
USART1_Init(115200,NULL,NULL);
for( i = 0; i<250 ; i++)
wr_buff[i] = i+1;
//写数组 1-250
res = StmFlash_Erase(FLASH_PAGE,PAGE_NUM);
//STM32F1的FLASH页擦除操作 200页 要擦除1页
res = StmFlash_Program(PAGE_ADDR,(u16*)wr_buff,250);
//STM32F1的FLASH页编程,每页2K字节 编程地址 编程字节
StmFalsh_Read(PAGE_ADDR,rd_buff,250);
//STM32F1的FLASH读取
if(strncmp((char *)wr_buff,(char *)rd_buff,250) != 0)
{
printf("STM32 FLASH ReadWrite ERROR!!!\r\n");
while(1);
}
printf("STM32 FLASH ReadWrite OK!!!\r\n");
while(1)
{
}
}
stm32_flash.h
#ifndef __STM32_FLASH_H_
#define __STM32_FLASH_H_
#include "stm32f1xx.h"
#include "stm32_types.h"
#include "stm32f1xx_hal.h"
#define STM32_FLASH_BASE 0x08000000
//STM32 FLASH的起始地址
#define STM32_PAGE_SIZE 2048
//页大小2KB
enum{FLASH_SUCESS = 0,FLASH_ERROR};
void StmFalsh_Read(uint32_t ReadAddr,uint8_t *rd_buff,uint32_t Len);
//Stm32F1FlASH读取函数
uint32_t StmFlash_Program(uint32_t WriteAddr,uint16_t *wr_buf,uint32_t Len);
//STM32F1的FLASH编程函数
uint32_t StmFlash_Erase(uint32_t Page_ID,uint32_t NbOfPages);
//STM32F1的FLASH页擦除操作函数
#endif
stm32_flash.c
#include "stm32_flash.h"
FLASH_EraseInitTypeDef EraseInitStruct;
//结构体成员:类型擦除(大容量擦除和页面擦除)
//列祖:当启用大容量擦除时,选择要擦除的列组。
//页地址:禁用大容量擦除时要擦除的初始闪存页地址
//删除页数:要删除的页数
void StmFalsh_Read(uint32_t ReadAddr,uint8_t *rd_buff,uint32_t Len)
//STM32F1的FLASH读取(读地址,读取数据存储空间,读取长度)
{
uint32_t i;
for(i = 0; i<Len; i++)
{
*(rd_buff++) = *((uint8_t *)(ReadAddr + i));
//*(uint8_t *)0x8000000;//就是读取FLASH中地址0x8000000处的数据
//将数据存在读数组中
}
}
uint32_t StmFlash_Program(uint32_t WriteAddr,uint16_t *wr_buff,uint32_t Len)
//STM32F1的FLASH页编程(编程起始地址,待编程的数据 , 编程长度)
{
uint8_t err = FLASH_SUCESS;
//err = 0;
uint32_t Address,EndAddress;
//开始地址 结束地址
Address = WriteAddr;
//Address = 0x08000000(FLASH的起始地址)+ 200 * 2048
EndAddress = WriteAddr + Len;
//EndAddress =0x08000000(FLASH的起始地址)+ 200 * 2048 +250;
HAL_FLASH_Unlock();
//解锁
while(Address < EndAddress)
{
if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD ,Address,*wr_buff) == HAL_OK )
//第一个参数是指定地址编写半个字
{
Address += 2;
wr_buff++;
//按半字编程,一次写2个字节
//*在这里可能有疑问,为什么读250次
//(看void StmFalsh_Read()),而写的话,写 250/2次,
// 这是因为读是读一个字节,而写的话是半个字。 一个字等于4个字节 ,半个字就是2个字节
// *(uint32_t *)0x8000000;//读一个字
// *(uint8_t *)0x8000000;//读一个字节;
// *(uint16_t *)0x8000000;//读半字; */
}
else
{
err = FLASH_ERROR;
break;
}
}
HAL_FLASH_Lock();
//上锁
return err;
}
uint32_t StmFlash_Erase(uint32_t Page_ID,uint32_t NbOfPages)
{
uint8_t err;
uint32_t FirstPage = 0;
uint32_t SECTORError = 0;
HAL_FLASH_Unlock();
//解锁
FirstPage = STM32_FLASH_BASE + Page_ID * STM32_PAGE_SIZE;
// FirstPage = 0x08000000 + 200 * 2048
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
//擦除类型:页面擦除
EraseInitStruct.PageAddress = FirstPage;
//擦除页面地址:第一页
EraseInitStruct.NbPages = NbOfPages;
//要删除的页数
EraseInitStruct.Banks = FLASH_BANK_1;
//选择要擦除的列组 1 1个bank最大为512KB
err = HAL_FLASHEx_Erase(&EraseInitStruct,&SECTORError);
HAL_FLASH_Lock();
//上锁
return err;
}