STM32F103读写内部flash

本文最后更新于:2022年5月29日 上午

最近由于项目需要,有一些程序运行参数需要保存。这些数据的特点是:数量少而且不需要经常修改,但又不能定义为常量,因为以后还有修改的可能。考虑到这些数据量比较少,使用专门的存储单元既不经济,也没有必要。

因此,在需要保存一些芯片掉电之后依旧需要保存的数据(数据量不是特别大)时,运用内置flash的空闲部分可以为我们省去一颗eeprom或外置flash的花销。


一、介绍

这里以中容量产品STM32F103C8T6为例,其Flash容量为64K(0x8000000~0x800FFFF),可以将其中一部分用作数据存储。该芯片的地址分配如下:

QhGWB8.png

stm32的flash地址起始于0x0800 0000,结束地址是0x0800 0000加上芯片实际的flash大小,不同的芯片flash大小不同。

  1. 主存储器(Main memory):一般我们说 STM32 内部 FLASH 的时候,都是指这个主存储器区域它是存储用户应用程序的空间,芯片型号说明中的 64K FLASH、 128K FLASH 都是指这个区域的大小。与其它 FLASH 一样,在写入数据前,要先按扇区擦除。
  2. 系统存储区(System memory):系统存储区是用户不能访问的区域,它在芯片出厂时已经固化了启动代码,它负责实现串口、 USB 以及 CAN 等 ISP 烧录功能。
  3. 选项字节(Option Bytes):选项字节用于配置 FLASH 的读写保护、电源管理中的 BOR 级别、软件/硬件看门狗等功能,这部分共 32 字节。可以通过修改 FLASH 的选项控制寄存器修改。

二、对内部Flash的写入过程

具体操作可参考ST官方资料:STM32F10xxx Flash memory microcontrollers

  1. 解锁 (固定的KEY值)
    (1) 往 Flash 密钥寄存器 FLASH_KEYR 中写入 KEY1 = 0x45670123
    (2) 再往 Flash 密钥寄存器 FLASH_KEYR 中写入 KEY2 = 0xCDEF89AB
  2. 数据操作位数
    最大操作位数会影响擦除和写入的速度,其中 64 位宽度的操作除了配置寄存器位外,还需要在 Vpp 引脚外加一个 8-9V 的电压源,且其供电间不得超过一小时,否则 FLASH可能损坏,所以 64 位宽度的操作一般是在量产时对 FLASH 写入应用程序时才使用,大部分应用场合都是用 32 位的宽度。
  3. 擦除扇区
    在写入新的数据前,需要先擦除存储区域, STM32 提供了扇区擦除指令和整个FLASH 擦除(批量擦除)的指令,批量擦除指令仅针对主存储区。

扇区擦除的过程如下:
(1) 检查 FLASH_SR 寄存器中的“忙碌寄存器位 BSY”,以确认当前未执行任何 Flash 操作;
(2) 在 FLASH_CR 寄存器中,将“激活扇区擦除寄存器位 SER ”置 1,并设置“扇区编号寄存器位 SNB”,选择要擦除的扇区;
(3) 将 FLASH_CR 寄存器中的“开始擦除寄存器位 STRT ”置 1,开始擦除;
(4) 等待 BSY 位被清零时,表示擦除完成。
4. 写入数据
擦除完毕后即可写入数据,写入数据的过程并不是仅仅使用指针向地址赋值,赋值前还还需要配置一系列的寄存器,步骤如下:
(1) 检查 FLASH_SR 中的 BSY 位,以确认当前未执行任何其它的内部 Flash 操作;
(2) 将 FLASH_CR 寄存器中的 “激活编程寄存器位 PG” 置 1;
(3) 针对所需存储器地址(主存储器块或 OTP 区域内)执行数据写入操作;
(4) 等待 BSY 位被清零时,表示写入完成。

三、查看工程内存的分布

在使用内部 FLASH 存储其它数据前需要了解哪一些空间已经写入了程序代码,存储了程序代码的扇区都不应作任何修改。通过查询应用程序编译时产生的“ *.map”后缀文件,打开 map 文件后,查看文件最后部分的区域,可以看到一段以“ Memory Map of the image”开头的记录(若找不到可用查找功能定位)。

QhGfHS.png

基地址加上Size就是代码指令与变量占用的空间。本例子中应用程序使用 的内部 FLASH 是从 0x08000000 至(0x0800 0000+0x0000 0d80)地址的空间区域。
所以从扇区4(地址 0x0800 1000)后的存储空间都可以作其它用途,使用这些存储空间时不会篡改应用程序空间的数据。

四、简单的小例程代码实现

例子功能:

​ 1、将数据存储在stm32F103单片机的主存储区0x0800FC00地址开始的扇区,(0x0800FC00 应该是该单片机大约63个扇区的开始地址位置即页63起始地址,即最后一页扇区)。

​ 2、将该单片机的页63(page63=0x0800FC00)处的数据再读出来;

在STM32F103C8T6测试通过,具体实现代码如下:

1,flash驱动模块

(1)stm_flash.h
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
#ifndef __STM_FLASH_H
#define __STM_FLASH_H

#include "stm32f10x.h"

/*************************************************************************************/
// Set according to your needs
#define STM32_FLASH_SIZE 64 // FLASH capacity of the selected MCU (unit is K)
#define STM32_FLASH_WREN 1 // Enable FLASH write (0, disable; 1, enable)

// FLASH start address
#define STM32_FLASH_BASE 0x08000000 // STM32 FLASH start address

#if FLASH_SIZE<256
#define SECTOR_SIZE 1024 // byte
#else
#define SECTOR_SIZE 2048 // byte
#endif
/*************************************************************************************/

void STMFLASH_Write_NoCheck(uint32_t WriteAddr,uint16_t *pBuffer,uint16_t NumToWrite);
void STMFLASH_Write(uint32_t WriteAddr,uint16_t *pBuffer,uint16_t NumToWrite);
void STMFLASH_Read(uint32_t ReadAddr,uint16_t *pBuffer,uint16_t NumToRead) ;

#endif /* __STM_FLASH_H */

(2)stm_flash.c
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/**
******************************************************************************
* @file stm_flash.c
* @author Soso
* @date 2019-12-15
* @brief STM32 FLASH driver code.
******************************************************************************
*/
#include "stm_flash.h"


/**
* @brief Read the half word (16-bit data) of the specified address.
* @param faddr: read address (this address must be a multiple of 2 !!).
* @retval Corresponding data.
*/
static uint16_t STMFLASH_ReadHalfWord(uint32_t faddr)
{
return *(vu16*)faddr;
}


#if STM32_FLASH_WREN // If enable flash write
/**
* @brief Do not check writes.
* @param WriteAddr: starting address.
* @param pBuffer: data pointer.
* @param NumToWrite: half-word (16-bit) number.
* @retval None.
*/
void STMFLASH_Write_NoCheck(uint32_t WriteAddr,uint16_t *pBuffer,uint16_t NumToWrite)
{
uint16_t i;
for(i=0;i<NumToWrite;i++)
{
FLASH_ProgramHalfWord(WriteAddr,pBuffer[i]);
WriteAddr+=2; // Address increased by 2
}
}

// A sector size is (SECTOR_SIZE) bytes,
// A uint16_t type data is 2 bytes,
// So a sector can hold a maximum of (SECTOR_SIZE/2) uint16_t type data.
uint16_t STMFLASH_BUF[SECTOR_SIZE/2];

/**
* @brief Write data of specified length from the specified address.
* @param WriteAddr: Start address(this address must be a multiple of 2 !!).
* @param pBuffer: data pointer.
* @param NumToWrite: Halfword (16-bit) number.
* @retval None.
*/
void STMFLASH_Write(uint32_t WriteAddr,uint16_t *pBuffer,uint16_t NumToWrite)
{
uint16_t i;
uint32_t secpos; // Sector address
uint16_t secoff; // Intra-sector offset address (16-bit word calculation)
uint16_t secremain; // Remaining address in the sector (16-bit word calculation)
uint32_t offaddr; // Remove the address after 0X08000000

if(WriteAddr<STM32_FLASH_BASE||(WriteAddr>=(STM32_FLASH_BASE+1024*STM32_FLASH_SIZE)))
return; // Illegal address

FLASH_Unlock(); // Unlock flash
offaddr = WriteAddr-STM32_FLASH_BASE; // Actual offset address.
secpos = offaddr/SECTOR_SIZE; // Sector address 0 ~ 63 for STM32F103C8T6
secoff = (offaddr%SECTOR_SIZE)/2; // Offset within the sector (2 bytes as the basic unit)
secremain = SECTOR_SIZE/2-secoff; // Sector remaining space
if(NumToWrite <= secremain) // Not greater than the sector range
secremain = NumToWrite;

while(1)
{
STMFLASH_Read(secpos*SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,SECTOR_SIZE/2); // Read the entire sector
for(i=0;i<secremain;i++) // Check data
{
if(STMFLASH_BUF[secoff+i] != 0XFFFF)
break; // Need to erase
}

if(i < secremain) // Need to erase
{
FLASH_ErasePage(secpos*SECTOR_SIZE+STM32_FLASH_BASE); // Erase this sector
for(i=0;i<secremain;i++) // Copy data
{
STMFLASH_BUF[i+secoff] = pBuffer[i];
}
// Write entire sector
STMFLASH_Write_NoCheck(secpos*SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,SECTOR_SIZE/2);
}
else // If it has been erased, it is directly written into the remaining sector.
STMFLASH_Write_NoCheck(WriteAddr,pBuffer,secremain);

if(NumToWrite == secremain) // Writing is complete
break;
else // Writing is not complete
{
secpos++; // Sector address increased by 1
secoff = 0; // Offset position is 0
pBuffer += secremain; // Pointer offset
WriteAddr += secremain; // Write address offset
NumToWrite -= secremain;// Decrement the number of bytes (16 bits)
if(NumToWrite > (SECTOR_SIZE/2))// Data cannot be written in the next sector
secremain = SECTOR_SIZE/2;
else // Data can be written in the next sector
secremain = NumToWrite;
}
};
FLASH_Lock(); // Lock flash
}
#endif


/**
* @brief Read data of specified length from specified address.
* @param ReadAddr: Start address(this address must be a multiple of 2 !!).
* @param pBuffer: data pointer.
* @param NumToWrite: Halfword (16-bit) number.
* @retval None.
*/
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); // Read 2 bytes.
ReadAddr += 2; // Offset 2 bytes.
}
}

2,串口调试模块

(1)usart.h
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
#ifndef __USART_H
#define __USART_H

#include "stm32f10x.h"
#include "stdio.h"

//Pin definition
/*******************************************************/
#define DEBUG_USART USART3
#define DEBUG_USART_CLK RCC_APB1Periph_USART3
#define DEBUG_USART_AF RCC_APB2Periph_AFIO
#define DEBUG_USART_GPIO_CLK RCC_APB2Periph_GPIOB
#define DEBUG_USART_BAUDRATE 115200

#define DEBUG_USART_RX_GPIO_PORT GPIOB
#define DEBUG_USART_RX_PIN GPIO_Pin_11

#define DEBUG_USART_TX_GPIO_PORT GPIOB
#define DEBUG_USART_TX_PIN GPIO_Pin_10


#define DEBUG_USART_IRQ USART3_IRQn
#define DEBUG_USART_IRQHandler USART3_IRQHandler

/************************************************************/

void USART_Config(void);
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch);
void Usart_SendString( USART_TypeDef * pUSARTx, char *str);

void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch);

#endif /* __USART_H */

(2)usart.c
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/**
******************************************************************************
* @file uasrt.c
* @author Soso
* @date 2018-09-16
* @brief Redirect c library printf function to usart port, interrupt receiving mode.
******************************************************************************
*/

#include "usart.h"


/* Configuring nested vectored interrupt controller NVIC */
static void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;

/* Nested vector interrupt controller group selection */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

/* Preemptio Priority is 1 */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
/* Sub Priority is 1 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
/* Enable interrupt */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

/* Configure USART as the interrupt source */
NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;
/* Initialize configuration NVIC */
NVIC_Init(&NVIC_InitStructure);
}

void USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;

/* Enable port alternate function and enable USART GPIO clock */
RCC_APB2PeriphClockCmd(DEBUG_USART_AF|DEBUG_USART_GPIO_CLK,ENABLE);
/* Enable USART Clock */
RCC_APB1PeriphClockCmd(DEBUG_USART_CLK,ENABLE);

/* Configure the Tx pin for alternate function */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_PIN;
GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);

/* Configure the Rx pin for the alternate function */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_PIN;
GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);

/* Configuration USART mode */
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
/* USART mode control: simultaneous enable send and receive */
USART_InitStructure.USART_Mode = USART_Mode_Tx|USART_Mode_Rx;

USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
USART_Init(DEBUG_USART, &USART_InitStructure);

/* Configure the serial port to receive interrupts. */
NVIC_Configuration();

/* Enable serial port receive interrupt */
USART_ITConfig(DEBUG_USART, USART_IT_RXNE, ENABLE);

/* Enable serial */
USART_Cmd(DEBUG_USART, ENABLE);
}


/***************** Send a character **********************/
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{
/* Send a byte of data to the USART */
USART_SendData(pUSARTx,ch);

/* Waiting for the transmit data register to be empty */
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}

/***************** Send string **********************/
void Usart_SendString( USART_TypeDef * pUSARTx, char *str)
{
unsigned int k=0;
do
{
Usart_SendByte( pUSARTx, *(str + k) );
k++;
} while(*(str + k)!='\0');

/* Waiting for transmission to complete */
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET)
{}
}

/***************** Send a 16-digit number **********************/
void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch)
{
uint8_t temp_h, temp_l;

/* Take out the high eight */
temp_h = (ch&0XFF00)>>8;
/* Take the lower eight */
temp_l = ch&0XFF;

/* Send high eight */
USART_SendData(pUSARTx,temp_h);
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);

/* Send the lower eight digits */
USART_SendData(pUSARTx,temp_l);
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}

/* Redirect the c library function printf to the serial port,
use the printf function after redirection. */
int fputc(int ch, FILE *f)
{
/* Send a byte of data to the serial port */
USART_SendData(DEBUG_USART, (uint8_t) ch);

/* Waiting to send */
while (USART_GetFlagStatus(DEBUG_USART, USART_FLAG_TXE) == RESET);

return (ch);
}

/* Redirect the c library function scanf to the serial port,
rewrite backwards can use scanf, getchar and other functions. */
int fgetc(FILE *f)
{
/* Waiting for serial input data */
while (USART_GetFlagStatus(DEBUG_USART, USART_FLAG_RXNE) == RESET);

return (int)USART_ReceiveData(DEBUG_USART);
}
/*********************************************END OF FILE*********************************************/

3,中断配置模块

把下面的代码添加到 stm32f10x_it.c 文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "stm32f10x_it.h"
#include "usart.h"

extern uint8_t flag;
/**
* @brief This function handles USART3_IRQHandler interrupt request.
* @param None
* @retval None
*/
void DEBUG_USART_IRQHandler(void)
{
uint8_t ucTemp;
if(USART_GetITStatus(DEBUG_USART,USART_IT_RXNE) != RESET)
{
ucTemp = USART_ReceiveData(DEBUG_USART);
USART_SendData(DEBUG_USART,ucTemp);

if(ucTemp == 'r')
flag = 1;
else if(ucTemp == 'w')
flag = 2;
}
}

4,主函数main.c

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
62
63
64
65
66
/**
******************************************************************************
* @file main.c
* @author Soso
* @date 2019-12-15
* @brief STM32F103C8T6 internal FLASH read and write
*
******************************************************************************
*/

#include "stm32f10x.h"
#include "usart.h"
#include "stm_flash.h"


#define ARRAY_SIZE 6 // 数组长度
#define FLASH_SAVE_ADDR 0x0800FC00 // 设置FLASH 保存地址(必须为偶数,且其值要大于本代码所占用FLASH的大小+0X08000000)

uint8_t flag = 0;

/**
* @brief Main function
* @param None
* @retval None
*/
int main(void)
{
Debug_USART_Config();

uint16_t Data_Buffer[ARRAY_SIZE] = {2,0,1,9,1,2};
uint16_t Text_Buffer[ARRAY_SIZE] = {0};
uint16_t i = 0;

printf("initialization successful.\n");
printf("Press the 'r' to Read data from flash.\n");
printf("Press the 'w' to write data to flash.\n");
printf("\n-------------------------------------------------\n\n");

while(1)
{
if(flag == 1) // Read data from flash
{
flag = 0;
printf("Read data from flash:\n");

STMFLASH_Read(FLASH_SAVE_ADDR,Text_Buffer,ARRAY_SIZE);
for(i=0;i<ARRAY_SIZE;i++)
printf("Text_Buffer[%d]: %d\n",i,Text_Buffer[i]);
printf("\n-------------------------------------------------\n\n");
}
else if(flag == 2) // Write data to flash
{
flag = 0;
printf("Start write data to flash.\n");
STMFLASH_Write(FLASH_SAVE_ADDR,Data_Buffer,ARRAY_SIZE);
printf("Write Finish.\n");
printf("\n-------------------------------------------------\n\n");
}
}
}




/*********************************************END OF FILE*********************************************/

五、元器件接线说明

【*】 引脚分配
        STM32        串口模块
        PB11    ->     Tx
        PB10    ->     Rx
        +3.3V   ->     +3.3V
        GND     ->     GND
        波特率:115200

六、实验结果

使用串口打印信息,

按下键盘 ‘r’ 键,读取flash特定地址内容并显示;

按下键盘 ‘w’ 键,往flash特定地址写入指定内容,再按下键盘 ‘r’ 键,读取flash特定地址内容并显示。

QIZuVA.png

参考链接:STM32学习笔记:读写内部Flash(介绍+附代码)


STM32F103读写内部flash
https://kevinloongc.github.io/posts/9820a5cb.html
作者
Kevin Loongc
发布于
2019年12月15日
许可协议