本文记录了对一些知识点的理解、操作方法,如有错误,请务必批评指正!!
最终的测试截图:
目录??
一、内部FLASH要点
关于地址:
关于解锁:
关于擦除:
关于写入:
二、读取数据
三、存储数据
四、应用示例
一、内部FLASH要点
关于地址:
- 内部FLASH地址开始地址:0x0800 0000;
- 结束地址:0x08000000+FLASH大小
- FLASH的大小,可根据芯片型号得知,如F103x8=64K,? F103xC=256K, F103xE=512K
- FLASH的大小,也可读*(uint16_t*)0x1FFFF7E0直接获得;
关于解锁:
- 对FLASH寄存器的操作, 需要在操作之前解锁;
- 如果解锁过程发生错误,FLASH寄存器操作将死锁至掉电;
- 解锁步骤(1): FLASH->KEYR= (uint32_t)0x45670123;
- 解锁步骤(2): FLASH->KEYR= (uint32_t)0xCDEF89AB;
关于擦除:
- 内部FLASH空间,按页划分成连续的数据块;
- 每次擦除的最小单位是:页;
- F103中,中小容量FLASH的页(扇区)大小为1K, 而大容量的页大小为2K
- 每页擦除时长:中小容量约30ms, 大容量约50ms
- 页寿命:中小容量约可擦: 1K次, 大容量约可擦: 10K次
- 擦除步骤(1):FLASH->CR|= 1<<1;? ? ? ? ? ? ?// 选择以页进行擦除
- 擦除步骤(2):FLASH->AR = 0x0800xxxx;? // 要擦除的页地址
- 擦除步骤(3):FLASH->CR|= 0x40;? ? ? ? ? ? ?// 开始擦除
- 擦除步骤(4):while((FLASH->SR & 0x01); // 等待空闲,即BSY=0
关于写入:
- 写入的地址,必须是偶数;
- 每一次写入,必须以半字进行(uint16_t)!单字节或全字将发生错误;
- 写入步骤(1):? FLASH->CR |= 0x01<<0;? ? ? ? ? ? ? ? // 使能可编程
- 写入步骤(2):? *(uint16_t*)addr = (uint16_t)value;? // 写入数据??
- 写入步骤(3):??while((FLASH->SR & 0x01);? ? ? ? ? ?// 等待空闲,即BSY=0
- 写入步骤(4):??FLASH->CR &= ((uint32_t)0x00001FFE);??// 关闭编程
二、读取数据
- 可以按byte读取,也可以按半字、全字方式读取;
- 读取的方式很简单,如:short value?=?*(uint16_t*)readAddr;
下面的读取函数,经多次完善,按字节读取,方便匹配奇数数量的数据读取;
/******************************************************************************
* 函 数: System_ReadInteriorFlash
* 功 能: 在芯片的内部FLASH里,读取指定长度数据
* 参 数: uint32_t readAddr 数据地址
* uint8_t *pBuffer 读出后存放位置
* uint16_t numToRead 读取的字节数量
* 返回值: 0_成功 1_失败,地址小于FLASH基址 2_失败,地址大于FLASH最大值
******************************************************************************/
uint8_t System_ReadInteriorFlash (uint32_t readAddr, uint8_t *pBuffer, uint16_t numToRead)
{
// 获取芯片FLASH大小
uint16_t flashSize = *(uint16_t*)(0x1FFFF7E0); // 读取芯片FLASH大小;本寄存器值为芯片出厂前写入的FLASH大小,只读单位:KByte
// 判断地址有效性
if(readAddr < STM32_FLASH_ADDR_BASE) return 1; // 如果读的地址,小于FLASH的最小地址,则退出
if(readAddr > (STM32_FLASH_ADDR_BASE+(flashSize*1024))) return 2; // 如果读的地址,超出FLASH的最大地址,则退出
// 开始复制
while(numToRead--){
*pBuffer = *(__IO uint8_t*)readAddr;
pBuffer++; // 指针后移一个数据长度
readAddr++; // 偏移一个数据位
}
return 0; // 成功,返回0;
}
三、存储数据
1:在存储函数前,需要三个数据的定义:
// 下面这三个定义,用于存取内部FLASH数据,F103系列都不用修改
#ifdef STM32F10X_HD
#define STM32_FLASH_SECTOR_SIZE 2048 // 内部FLASH页大小, 单位:bytes (注:STM32F10xx系列下,小中容量存储器扇区为1K, 大容量存储器扇区为2K)
#else
#define STM32_FLASH_SECTOR_SIZE 1024 // 内部FLASH页大小, 单位:bytes (注:STM32F10xx系列下,小中容量存储器扇区为1K, 大容量存储器扇区为2K)
#endif
#define STM32_FLASH_ADDR_BASE 0x08000000 // 芯片内部FLASH基址(这个基本不用修改)
static uint8_t sectorbufferTemp[STM32_FLASH_SECTOR_SIZE]; // 开辟一段内存空间,作用:在内部FLASH写入时作数据缓冲
2:等待空闲的函数:
// 作用:内部FLASH写入时,等待空闲,BSY位标志:0闲1忙
static uint8_t waitForFlashBSY(uint32_t timeOut)
{
while((FLASH->SR & 0x01) && (timeOut-- != 0x00)) ; // 等待BSY标志空闲
if(timeOut ==0)
return 1; // 失败,返回1, 等待超时;
return 0; // 正常,返回0
}
3:完整可用的存储函数:
/******************************************************************************
* 函 数: System_WriteInteriorFlash
* 功 能: 在芯片的内部FLASH里,写入指定长度数据
* 参 数: uint32_t writeAddr 写入地址,重要:写入地址必须是偶数!!!
* uint8_t *writeToBuffer
* uint16_t numToWrite
*
* 返回值: 0_成功,
* 1_失败,地址范围不正确
* 2_失败,FLASH->SR:BSY忙超时
* 3_失败,擦除超时
******************************************************************************/
uint8_t System_WriteInteriorFlash(uint32_t writeAddr, uint8_t *writeToBuffer, uint16_t numToWrite)
{
uint16_t flashSize = *(uint16_t*)(0x1FFFF7E0); // 读取芯片FLASH大小;本寄存器值为芯片出厂前写入的FLASH大小,只读,单位:KByte
uint32_t addrOff = writeAddr - STM32_FLASH_ADDR_BASE; // 去掉0x08000000后的实际偏移地址
uint32_t secPos = addrOff / STM32_FLASH_SECTOR_SIZE;; // 扇区地址,即起始地址在第几个扇区
uint16_t secOff = addrOff%STM32_FLASH_SECTOR_SIZE ; // 开始地始偏移字节数: 数据在扇区的第几字节存放
uint16_t secRemain = STM32_FLASH_SECTOR_SIZE - secOff; // 本扇区需要写入的字节数 ,用于判断够不够存放余下的数据
// 判断地址有效性
if(writeAddr < STM32_FLASH_ADDR_BASE) return 1; // 如果读的地址,小于FLASH的最小地址,则退出,返回1_地址失败
if(writeAddr > (STM32_FLASH_ADDR_BASE+(flashSize*1024))) return 1; // 如果读的地址,超出FLASH的最大地址,则退出, 返回1_地址失败
// 0_解锁FLASH
FLASH->KEYR = ((uint32_t)0x45670123);
FLASH->KEYR = ((uint32_t)0xCDEF89AB);
if(numToWrite <= secRemain) secRemain=numToWrite;
while(1){
// 1_读取当前页的数据
if(waitForFlashBSY(0x00888888)) return 2; // 失败,返回:2, 失败原因:FLASH->SR:BSY忙超时
System_ReadInteriorFlash ( secPos*STM32_FLASH_SECTOR_SIZE+STM32_FLASH_ADDR_BASE , sectorbufferTemp, STM32_FLASH_SECTOR_SIZE ); // 读取扇区内容到缓存
// 2_擦险指定页(扇区)
if(waitForFlashBSY(0x00888888)) return 2; // 失败,返回:2, 失败原因:FLASH->SR:BSY忙超时
FLASH->CR|= 1<<1; // PER:选择页擦除;位2MER为全擦除
FLASH->AR = STM32_FLASH_ADDR_BASE + secPos*STM32_FLASH_SECTOR_SIZE; // 填写要擦除的页地址
FLASH->CR|= 0x40; // STRT:写1时触发一次擦除运作
if(waitForFlashBSY(0x00888888)) return 2; // 失败,返回:3, 失败原因:擦除超时
FLASH->CR &= ((uint32_t)0x00001FFD); // 关闭页擦除功能
for(uint16_t i=0; i<secRemain ; i++) // 原始数据写入缓存
sectorbufferTemp[secOff+i] = writeToBuffer[i];
for(uint16_t i=0; i<STM32_FLASH_SECTOR_SIZE/2 ; i++){ // 缓存数据写入芯片FLASH
if(waitForFlashBSY(0x00888888)) return 2; // 失败,返回:2, 失败原因:FLASH->SR:BSY忙超时
FLASH->CR |= 0x01<<0; // PG: 编程
*(uint16_t*)(STM32_FLASH_ADDR_BASE + secPos*STM32_FLASH_SECTOR_SIZE +i*2) = (sectorbufferTemp[i*2+1]<<8) | sectorbufferTemp[i*2] ; // 缓存数据写入设备
if(waitForFlashBSY(0x00888888)) return 2; // 失败,返回:2, 失败原因:FLASH->SR:BSY忙超时
FLASH->CR &= ((uint32_t)0x00001FFE) ; // 关闭编程
}
if(secRemain == numToWrite){
break; // 已全部写入
}
else{ // 未写完
writeToBuffer += secRemain ; // 原始数据指针偏移
secPos ++; // 新扇区
secOff =0; // 新偏移位,扇区内数据起始地址
numToWrite -= secRemain ; // 剩余未写字节数
secRemain = (numToWrite>STM32_FLASH_SECTOR_SIZE)?(STM32_FLASH_SECTOR_SIZE):numToWrite; // 计算新扇区写入字节数
}
}
FLASH->CR |= 1<<7 ; // LOCK:重新上锁
return 0;
}
四、应用示例
下面代码中,分别测试了四种数据类型的存储、读取、转换。
注意:string不是独立的数据类型;
printf("\r\n>>>>开始测试内部FLASH读写,及数据转换:\r\n");
// 测试1:字符
uint32_t addrChar = 0x08000000+38*1024; // 注意地址必须是偶数,且地址段尽量安排在FLASH尾部,避免覆盖了代码段数据
char writeChar='F';
System_WriteInteriorFlash(addrChar, (uint8_t*)&writeChar, 1); // 存入数据,函数内自动解锁,擦除,写入
char readChar=0;
System_ReadInteriorFlash(addrChar, (uint8_t*)&readChar, 1); // 读取数据,函数内自动按半字读取
printf("测试char 地址:0x%X 写入:%c 读取:%c \r\n", addrChar, writeChar, readChar);
// 测试2:带符号的16位
uint32_t addrShort = 0x08000000+155*1024-2; // 注意地址必须是偶数,且地址段尽量安排在FLASH尾部,避免覆盖了代码段数据
short writeShort=-888;
System_WriteInteriorFlash(addrShort, (uint8_t*)&writeShort, sizeof(writeShort)); // 存入数据,函数内自动解锁,擦除,写入
short readShort=0;
System_ReadInteriorFlash(addrShort, (uint8_t*)&readShort, sizeof(writeShort)); // 读取数据,函数内自动按半字读取
printf("测试short 地址:0x%X 写入:%d 读取:%d \r\n", addrShort, writeShort, readShort);
// 测试3:无符号的32位
uint32_t addrUint = 0x08000000+254*1024; // 注意地址必须是偶数,且地址段尽量安排在FLASH尾部,避免覆盖了代码段数据
uint32_t writeUint= 0x12345678;
System_WriteInteriorFlash(addrUint, (uint8_t*)&writeUint, 4); // 存入数据,函数内自动解锁,擦除,写入
uint32_t readUint = 0;
System_ReadInteriorFlash(addrUint, (uint8_t*)&readUint, 4); // 读取数据,函数内自动按半字读取
printf("测试uint 地址:0x%X 写入:0x%X 读取:0x%X \r\n", addrUint, writeUint, readUint);
// 测试4:字符串
uint32_t addrString = 0x08000000+166*1024-6; // 注意地址必须是偶数,且地址段尽量安排在FLASH尾部,避免覆盖了代码段数据
char writeString[100]="早吖! 去哪呢?";
System_WriteInteriorFlash(addrString, (uint8_t*)writeString, sizeof(writeString)); // 存入数据,函数内自动解锁,擦除,写入
char readString[100];
System_ReadInteriorFlash(addrString, (uint8_t*)readString, sizeof(readString)); // 读取数据,函数内自动按半字读取
printf("测试string 地址:0x%X 写入:%s 读取:%s \r\n", addrString, writeString, readString);
之前建了个Q群,玩STM32的朋友,可以一起吹吹水:262901124.
|