LV02-02-STM32内部FLASH读写

摘要:

  这篇笔记主要是 STM32 的内部FLASH读写相关笔记。若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用平台
Windows windows11
Keil uVision5 uVision V5.29.0.0
JLINK驱动 Windows版本 V688
正点原子战舰V3开发板 STM32F103ZET6
点击查看本文更新记录

2022-9-24 更新内容

创建笔记。

点击查看本文参考资料

  上一篇笔记已经了解了STM32的内部FLASH,这一部分就是对FLASH的读写实现。其实主要是几个函数的实现说明,对于FLASH的操作,需要注意的是每次写入或者读取都是半字,在32位机器下,就是2字节

一、相关宏定义

1.自定义宏

1
2
3
4
5
6
7
8
9
10
11
12
13
#define STM32_FLASH_SIZE 512 	 		/* 所选STM32的FLASH容量大小(单位为K) */
#define STM32_FLASH_WREN 1 /* 使能FLASH写入(0,不是能;1,使能) */

#define STM32_FLASH_BASE 0x08000000 /* STM32 FLASH的起始地址 */

/* 定义FLASH每一页的大小 */
#if STM32_FLASH_SIZE < 256
#define STM_SECTOR_SIZE 1024 /* 字节 */
#else
#define STM_SECTOR_SIZE 2048 /* 字节 */
#endif
u16 STMFLASH_BUF[STM_SECTOR_SIZE/2];/* 缓存数组 */

  • STM32_FLASH_SIZE:表示所选的STM32的FLASH容量的大小,我们选用的是STM32F103ZET6,所以这里定义成512KB。
  • STM32_FLASH_SIZE :FLASH 页的大小,FLASH小于256KB的,每一页是1KB,大于或者等于256KB的FLASH每一页是2KB。
  • STMFLASH_BUF :FLASH 中数据缓存区,这个数组是u16类型,也就是unsigned short int类型的,每个元素是2个字节,所以只需要1024个元素就可以存储2048个字节的数据,所以后边的元素个数需要除以2。这个数组有两个用处,一是存放读取的整个扇区的数据,而是缓存我们要写入的数据,其实它的存在也保护了部分数据,因为我们擦除FLASH的时候会擦除整个页,所以即便我们写入的数据不会占满整页,但是这一整页还是会被擦除,提前读取整页数据其实也保护了部分数据。

二、函数实现

1.读半字

1
2
3
4
u16 STMFLASH_ReadHalfWord(u32 faddr)
{
return *(vu16*)faddr;
}

【函数说明】该函数用于从指定地址读取半字数据(32位机器下半字为16位,也就是2个字节)。

【函数参数】

  • faddr: u32类型,就是uint32_t类型,再继续追踪会发现其实就是 unsigned int 类型,表示要读取的FLASH地址。

【返回值】u16类型,就是uint16_t类型,继续追踪的话就是unsigned short int类型,表示读取到的数据。

【使用格式】none

【注意】none

2.读指定长度

1
2
3
4
5
6
7
8
9
void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead)
{
u16 i;
for(i=0; i<NumToRead; i++)
{
pBuffer[i]=STMFLASH_ReadHalfWord(ReadAddr);/* 读取2个字节 */
ReadAddr+=2;/* 偏移2个字节 */
}
}

【函数说明】该函数用于从指定地址读取指定长度的数据到指定的空间,该函数内部调用库函数中的 STMFLASH_ReadHalfWord() 函数完成一次读取,每次读取16位数据,也就是2个字节。

【函数参数】

  • ReadAddr: u32类型,其实就是 unsigned int 类型,表示要读取的FLASH地址。
  • pBuffer :u16类型,其实就是 unsigned short int 类型,它是一个指针变量,指向读出数据将要存放空间的首地址。
  • NumToRead :u16类型,其实就是 unsigned short int 类型,表示我们要读取的多少数据,每次读取2字节,一共将会读取 2倍的NumToRead 个字节数据。也就是说,这个参数乘以2就可以得到我们读取的总的字节数啦。

【返回值】none

【使用格式】none

【注意】每次读取都是读2个字节,地址也要注意递增2字节。

3.不检查写入

1
2
3
4
5
6
7
8
9
void STMFLASH_Write_NoCheck(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)
{
u16 i;
for(i=0; i<NumToWrite; i++)
{
FLASH_ProgramHalfWord(WriteAddr,pBuffer[i]);
WriteAddr+=2;/* 地址增加2 */
}
}

【函数说明】该函数用于向指定地址写入指定长度的数据,该函数内部调用库函数中的 FLASH_ProgramHalfWord() 函数完成一次写入,每次写入16位数据(半字),也就是2个字节,但是该函数不会检查要写入的区域数据是否是已经擦除过的,一般用于写入整个扇区数据。

【函数参数】

  • ReadAddr: u32类型,其实就是 unsigned int 类型,表示要写入的FLASH地址。
  • pBuffer :u16类型,其实就是 unsigned short int 类型,它是一个指针变量,指向写入数据现在存放空间的首地址。
  • NumToWrite :u16类型,其实就是 unsigned short int 类型,表示我们要写入多少数据,每次写入2字节,一共将会写入 2倍的NumToWrite 个字节数据。也就是说,这个参数乘以2就可以得到我们写入的总的字节数啦。

【返回值】none

【使用格式】none

【注意】每次写入都是写2个字节,地址也要注意递增2字节。

4.写入指定长度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
void STMFLASH_Write(u32 WriteAddr, u16 *pBuffer, u16 NumToWrite)
{
/* 0.相关变量定义 */
u32 secpos; //扇区地址
u16 secoff; //扇区内偏移地址(16位字计算)
u16 secremain; //扇区内剩余地址(16位字计算)
u16 i;
u32 offaddr; //去掉 0X08000000 后的地址,也就是相对于 0X08000000 的偏移地址
/* 1.判断要写入的地址是否合法 */
if (WriteAddr < STM32_FLASH_BASE || (WriteAddr >= (STM32_FLASH_BASE + 1024 * STM32_FLASH_SIZE)))
return; // 非法地址直接返回
/* 2.解锁FLASH */
FLASH_Unlock(); // 解锁
/* 3.计算各个参数 */
offaddr = WriteAddr - STM32_FLASH_BASE; // 计算实际偏移地址,要写入的地址 - 0x0800 0000
secpos = offaddr / STM_SECTOR_SIZE; // 计算要写的地址是哪个扇区,也就是页,偏移地址/2048即可。扇区地址 0~255 for STM32F103ZET6
secoff = (offaddr % STM_SECTOR_SIZE) / 2; // 计算在扇区内的偏移(2个字节为基本单位,因为我们每次写入的是2个字节.)
secremain = STM_SECTOR_SIZE / 2 - secoff; // 计算扇区剩余空间大小
if (NumToWrite <= secremain) // 判断整个页中剩下的空间是否足够写入我们要写入的数据
secremain = NumToWrite; // 要写的数据长度不大于该扇区范围的话,我们将剩余长度设置为要写入的长度
/* 4.循环写入数据,可以跨多扇区写入 */
while (1)
{
/* 4.1读出整个扇区的数据 */
STMFLASH_Read(secpos * STM_SECTOR_SIZE + STM32_FLASH_BASE, STMFLASH_BUF, STM_SECTOR_SIZE / 2); //读出整个扇区的内容,扇区号*扇区大小+基地址就可以得出所要读取扇区的首地址
/* 4.2判断是否需要擦除页,然后进行不同的写入操作 */
for (i = 0; i < secremain; i++) //校验数据,判断瓦片们要写的区域中的数据是否都是0xFFFF
{
if (STMFLASH_BUF[secoff + i] != 0XFFFF) // 判断其中的数据是否都是0xFFFF,若有不是0xFFFF的,那么整个页面就需要擦除
break; //需要擦除
}
if (i < secremain) //需要擦除
{
FLASH_ErasePage(secpos * STM_SECTOR_SIZE + STM32_FLASH_BASE); // 擦除整个扇区
for (i = 0; i < secremain; i++) // 复制要写入的数据
{
STMFLASH_BUF[i + secoff] = pBuffer[i]; // 直接从偏移的位置复制数据,保护了其他不需要被覆盖的数据
}
STMFLASH_Write_NoCheck(secpos * STM_SECTOR_SIZE + STM32_FLASH_BASE, STMFLASH_BUF, STM_SECTOR_SIZE / 2); //写入整个扇区
}
else // 要写的空间已经擦除过,不需要再擦除了
STMFLASH_Write_NoCheck(WriteAddr, pBuffer, secremain); // 写已经擦除了的区域,可以直接写入扇区剩余区间.
/* 4.3判断是否写入结束,若结束则直接跳出循环,否则说明一个扇区不够,还要写下一个扇区 */
if (NumToWrite == secremain)
break; // 写入结束了
else // 写入未结束
{
secpos++; // 扇区地址增1
secoff = 0; // 偏移位置为0
pBuffer += secremain; // 指针偏移
WriteAddr += (secremain * 2); // 写地址偏移
NumToWrite -= secremain; // 字节(16位)数递减
if (NumToWrite > (STM_SECTOR_SIZE / 2))
secremain = STM_SECTOR_SIZE / 2; //下一个扇区还是写不完
else
secremain = NumToWrite; //下一个扇区可以写完了
}
};
/* 5.写入完毕后对FLASH上锁 */
FLASH_Lock(); //上锁
}

【函数说明】该函数用于向指定地址写入指定长度的数据,函数内部进行了是否需要擦除的判断以及是否需要跨页的判断。

【函数参数】

  • ReadAddr: u32类型,其实就是 unsigned int 类型,表示要写入的FLASH地址。
  • pBuffer :u16类型,其实就是 unsigned short int 类型,它是一个指针变量,指向写入数据现在存放空间的首地址。
  • NumToWrite :u16类型,其实就是 unsigned short int 类型,表示我们要写入多少数据,每次写入2字节,一共将会写入 2倍的NumToWrite 个字节数据。也就是说,这个参数乘以2就可以得到我们写入的总的字节数啦。

【返回值】none

【使用格式】none

【注意】none

三、读写测试

1.相关定义

1
2
3
4
5
//要写入到STM32 FLASH的字符串数组
const u8 TEXT_Buffer[]={"STM32F103 FLASH TEST"};
#define SIZE sizeof(TEXT_Buffer) //数组长度
#define FLASH_SAVE_ADDR 0X08070000 //设置FLASH 保存地址(必须为偶数,且其值要大于本代码所占用FLASH的大小+0X08000000)

2.主函数

  这里只写了重要相关语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

//要写入到STM32 FLASH的字符串数组
const u8 TEXT_Buffer[] = {"STM32F103 FLASH TEST"};
#define SIZE sizeof(TEXT_Buffer) //数组长度
#define FLASH_SAVE_ADDR 0X08070000 //设置FLASH 保存地址(必须为偶数,且其值要大于本代码所占用FLASH的大小+0X08000000)

int main(int argc, char *argv[])
{
// ... ...
STMFLASH_Write(FLASH_SAVE_ADDR, (u16 *)TEXT_Buffer, SIZE);
// ... ...
STMFLASH_Read(FLASH_SAVE_ADDR, (u16 *)datatemp, SIZE);
// ... ...
return 0;
}