RT1061在线擦除外部QSPI Flash

转载于i.MXRT1050在线擦写QSPI Flash作数据备份

之前提到RT1050在外部QSPI Flash启动并且Code在QSPI Flash执行是目前来讲比较常用的做法,那进而有用户会提出一种需求,很多应用都会有用到一些常数参数的存储(比如字库,数学上的三角函数查找表等等),以及重要数据的备份与实时记录或者整个应用firmware的在线升级,我们自然会想到外部那么大空间的QSPI Flash这下可以好好利用一番了,毕竟只是保存应用代码岂不是大大的浪费。所以下面就简单说明下实现RT1050在QSPI Flash XIP的同时还可以在线擦写QSPI Flash所需要注意的几点,并随本笔记附上测试代码供参考,实际上大家看了代码就弄明白怎么操作了,不过有以下几个方面我必须提出来,提醒大家需要重点注意的:

(1)我们的代码以XIP的方式在QSPI Flash上执行的同时还要能擦写自己,首先想到的就是冲突,QSPI Flash只接了一块(RT1050可以接两块独立的QSPI Flash,这种情况不提)而且QSPI Flash又不是双端口的,这样擦写QSPI flash相关的函数就务必要保证运行在SRAM里才行,是不能边执行边擦写自己的,这逻辑上肯定是不对的,所以需要把Flash操作相关操作的API分配到了SRAM里执行(代码基于Keil工程,为了尽量兼容且减少改动工程配置,我直接把相关函数分配到了SRAM的NoCacheable数据段,实际上分配到任意一个SRAM区都行,只是需要手动在.scf文件里RAM区新添加个地址段),然后将需要分配到SRAM区的Flash相关操作函数通过添加__attribute__((section("NonCacheable")))编译属性即可实现,如下图所示,我将flash擦除的操作放到RAM里了;

![clip_image002](RT1050在线擦除外部QSPI Flash/636588650028112193587047.jpg)

![clip_image004](RT1050在线擦除外部QSPI Flash/636588650076713936426845.jpg)

(2)外部QSPI Flash是通过RT1050的FlexSPI接口连接的,所以对其操作还得通过FlexSPI外设来进行。不过由于外部Boot模式已经选择了QSPI启动,所以系统每次复位后RT1050已经通过Flash配置信息字配置好了FlexSPI模块,所以我们千万不要再次对FlexSPI相关配置寄存器进行再次初始化了,因为一旦系统启动成功,代码已经通过FlexSPI正常运行在QSPI Flash上了,此时再去动态修改FlexSPI的寄存器会影响代码执行的(很容易hardfault或者程序执行乱套了),我们需要做的只是手动把擦写Flash相关的命令更新到FlexSPI的LUT查找表里就行了,如下图:

![clip_image006](RT1050在线擦除外部QSPI Flash/636588650176565776853274.jpg)

(3)上一步提到需要手动添加Flash擦写需要的相关命令到LUT表里,下面我就给出了擦写外部QSPI Flash需要用到的几个命令,把它放到自定义的LUT表里如下图,然后通过调用FLEXSPI_UpdateLUT将其更新到FlexSPI的真正LUT表。这里需要注意的是FlexSPI的LUT总共可以存放16个序列,每个序列最多可以存放8条可执行的指令,而QSPI启动时必须要前面几个LUT序列存放的是代码执行相关的指令(主要是第一个LUT,必须存放Flash读取命令供程序正常执行使用),所以这里我特意把前面的LUT都reserve了,而是使用第10到15个序列存放擦写Flash的命令(注意FLEXSPI_UpdateLUT时我也是特意只从第10个LUT表开始更新,10之前的LUT序列不受影响);

![clip_image008](RT1050在线擦除外部QSPI Flash/636588650233445386461685.jpg)

![clip_image010](RT1050在线擦除外部QSPI Flash/636588650299388232595591.jpg)

(4)另外需要注意的是RT1050的Dcache默认是打开的,这样我们在每次回读做校验的时候最好使用SCB_InvalidateDCache(); API用来清一下Dcache,避免回读的数据是cache里的从而造成校验失败。

![clip_image012](RT1050在线擦除外部QSPI Flash/636588650375800676076320.jpg)

然后还有个特别需要注意的地方就是 FlexSPI底层相关的部分函数也是需要copy到RAM里执行的(比如FLEXSPI_TransferBlocking)


下面是本人测试通过后的代码,经过验证 FlexSPI 底层相关的部分函数~~可以不用修改~~

05-27纠正:还是需要修改 FlexSPI 底层相关部分函数的,之前未修改成功估计是因为cache的缘故,导致屏蔽后仍能够正常访问

同时在操作flash时,需要添加临界区,防止RTOS打断操作

flexspi_nor_flash_ops.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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#include "fsl_flexspi.h"

#define NOR_CMD_LUT_SEQ_IDX_READ_NORMAL 7
#define NOR_CMD_LUT_SEQ_IDX_READ_FAST 13
#define NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD 0
#define NOR_CMD_LUT_SEQ_IDX_READSTATUS 1
#define NOR_CMD_LUT_SEQ_IDX_WRITEENABLE 2
#define NOR_CMD_LUT_SEQ_IDX_ERASESECTOR 3
#define NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_SINGLE 6
#define NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD 4
#define NOR_CMD_LUT_SEQ_IDX_READID 8
#define NOR_CMD_LUT_SEQ_IDX_WRITESTATUSREG 9
#define NOR_CMD_LUT_SEQ_IDX_ENTERQPI 10
#define NOR_CMD_LUT_SEQ_IDX_EXITQPI 11
#define NOR_CMD_LUT_SEQ_IDX_READSTATUSREG 12
#define NOR_CMD_LUT_SEQ_IDX_ERASECHIP 5

#define FLASH_QUAD_ENABLE 0x40
#define FLASH_BUSY_STATUS_POL 1
#define FLASH_BUSY_STATUS_OFFSET 0

static status_t __attribute__((section("NonCacheable"))) flexspi_nor_write_enable(FLEXSPI_Type *base, uint32_t baseAddr)
{
flexspi_transfer_t flashXfer;
status_t status;

/* Write neable */
flashXfer.deviceAddress = baseAddr;
flashXfer.port = kFLEXSPI_PortA1;
flashXfer.cmdType = kFLEXSPI_Command;
flashXfer.SeqNumber = 1;
flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_WRITEENABLE1;

status = FLEXSPI_TransferBlocking(base, &flashXfer);

return status;
}

static status_t __attribute__((section("NonCacheable"))) flexspi_nor_wait_bus_busy(FLEXSPI_Type *base)
{
/* Wait status ready. */
bool isBusy;
uint32_t readValue;
status_t status;
flexspi_transfer_t flashXfer;

flashXfer.deviceAddress = 0;
flashXfer.port = kFLEXSPI_PortA1;
flashXfer.cmdType = kFLEXSPI_Read;
flashXfer.SeqNumber = 1;
flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_READSTATUS1;
flashXfer.data = &readValue;
flashXfer.dataSize = 4;

do
{
status = FLEXSPI_TransferBlocking(base, &flashXfer);
if (status != kStatus_Success)
{
return status;
}
if (FLASH_BUSY_STATUS_POL)
{
if (readValue & (1U << FLASH_BUSY_STATUS_OFFSET))
{
isBusy = true;
}
else
{
isBusy = false;
}
}
else
{
if (readValue & (1U << FLASH_BUSY_STATUS_OFFSET))
{
isBusy = false;
}
else
{
isBusy = true;
}
}

} while (isBusy);


return status;
}

status_t __attribute__((section("NonCacheable"))) flexspi_nor_flash_erase_sector(FLEXSPI_Type *base, uint32_t address)
{
status_t status;
flexspi_transfer_t flashXfer;

/* Write enable */
flashXfer.deviceAddress = address;
flashXfer.port = kFLEXSPI_PortA1;
flashXfer.cmdType = kFLEXSPI_Command;
flashXfer.SeqNumber = 1;
flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_WRITEENABLE1;

status = FLEXSPI_TransferBlocking(base, &flashXfer);

if (status != kStatus_Success)
{
return status;
}

flashXfer.deviceAddress = address;
flashXfer.port = kFLEXSPI_PortA1;
flashXfer.cmdType = kFLEXSPI_Command;
flashXfer.SeqNumber = 1;
flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_ERASESECTOR1;
status = FLEXSPI_TransferBlocking(base, &flashXfer);

if (status != kStatus_Success)
{
return status;
}

status = flexspi_nor_wait_bus_busy(base);

return status;
}

status_t __attribute__((section("NonCacheable"))) flexspi_nor_flash_page_program(FLEXSPI_Type *base, uint32_t dstAddr, const uint32_t *src, size_t dataSize)
{
status_t status;
flexspi_transfer_t flashXfer;

/* Write neable */
status = flexspi_nor_write_enable(base, dstAddr);

if (status != kStatus_Success)
{
return status;
}

/* Prepare page program command */
flashXfer.deviceAddress = dstAddr;
flashXfer.port = kFLEXSPI_PortA1;
flashXfer.cmdType = kFLEXSPI_Write;
flashXfer.SeqNumber = 1;
flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_SINGLE1;
flashXfer.data = (uint32_t *)src;
flashXfer.dataSize = dataSize;
status = FLEXSPI_TransferBlocking(base, &flashXfer);

if (status != kStatus_Success)
{
return status;
}

status = flexspi_nor_wait_bus_busy(base);

return status;
}

status_t __attribute__((section("NonCacheable"))) flexspi_nor_flash_read_sector(FLEXSPI_Type *base, uint32_t address,const uint32_t *src,size_t leng)
{
//uint32_t temp;
flexspi_transfer_t flashXfer;
flashXfer.deviceAddress = address;
flashXfer.port = kFLEXSPI_PortA1;
flashXfer.cmdType = kFLEXSPI_Read;
flashXfer.SeqNumber = 1;
flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_READ_NORMAL1;
flashXfer.data = (uint32_t *)src;
flashXfer.dataSize = leng;

status_t status = FLEXSPI_TransferBlocking(base, &flashXfer);

// *vendorId = temp;

return status;
}

status_t __attribute__((section("NonCacheable"))) flexspi_nor_get_vendor_id(FLEXSPI_Type *base, uint8_t *vendorId)
{
uint32_t temp;
flexspi_transfer_t flashXfer;
flashXfer.deviceAddress = 0;
flashXfer.port = kFLEXSPI_PortA1;
flashXfer.cmdType = kFLEXSPI_Read;
flashXfer.SeqNumber = 1;
flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_READID1;
flashXfer.data = &temp;
flashXfer.dataSize = 1;

status_t status = FLEXSPI_TransferBlocking(base, &flashXfer);

*vendorId = temp;

return status;
}

interface_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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
#include "fsl_debug_console.h"


#include "fsl_flexspi.h"
#include "fsl_debug_console.h"
#include "fsl_cache.h"

#include "pin_mux.h"
#include "board.h"
#include "clock_config.h"
#include "fsl_common.h"

#include "FreeRTOS.h"
#include "task.h"

#include "flexspi_nor_flash_ops.h"

#define NOR_CMD_LUT_SEQ_IDX_READID1 10
#define NOR_CMD_LUT_SEQ_IDX_READSTATUS1 11
#define NOR_CMD_LUT_SEQ_IDX_WRITEENABLE1 12
#define NOR_CMD_LUT_SEQ_IDX_ERASESECTOR1 13
#define NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_SINGLE1 14
#define NOR_CMD_LUT_SEQ_IDX_READ_NORMAL1 15

#define FLASH_PAGE_SIZE 256

/* Fixed 64 LUTs, suggest to reserve the LUTs in the front */
static const uint32_t customLUT[64] = {
/* Read ID */
[4 * NOR_CMD_LUT_SEQ_IDX_READID1] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0xAB, kFLEXSPI_Command_DUMMY_SDR, kFLEXSPI_1PAD, 0x18),
[4 * NOR_CMD_LUT_SEQ_IDX_READID1 + 1] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),
/* Read extend parameters */
[4 * NOR_CMD_LUT_SEQ_IDX_READSTATUS1] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x05, kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04),
/* Write Enable */
[4 * NOR_CMD_LUT_SEQ_IDX_WRITEENABLE1] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x06, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),
/* Erase Sector */
[4 * NOR_CMD_LUT_SEQ_IDX_ERASESECTOR1] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x20, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18),
/* Page Program - single mode */
[4 * NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_SINGLE1] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x02, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18),
[4 * NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_SINGLE1 + 1] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_WRITE_SDR, kFLEXSPI_1PAD, 0x04, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),
/* Normal read mode -SDR */
[4 * NOR_CMD_LUT_SEQ_IDX_READ_NORMAL1] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x03, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18),
[4 * NOR_CMD_LUT_SEQ_IDX_READ_NORMAL1 + 1] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),
};


/*********************************************************************
*
* 函数名: qspi_erase_sector()
* 功 能: 擦出指定扇区, 4k大小
* 参 数: FLEXSPI_Type *base spi 参数 EXAMPLE_FLEXSPI
* 参 数:uint8_t address : 擦出扇区的起始地址, : EXAMPLE_SECTORn(x)
* 注 意:
擦出的参数为扇区锁
* 返 回:
*
**********************************************************************/
uint8_t __attribute__((section("NonCacheable"))) qspi_erase_sector(FLEXSPI_Type *base, uint32_t address)
{

status_t status;

taskENTER_CRITICAL(); //使能临界区
SCB_InvalidateDCache();
status = flexspi_nor_flash_erase_sector(base, address);
taskEXIT_CRITICAL(); //退出临界区

if (status != kStatus_Success)
{
PRINTF("Erase sector failure !\r\n");
return 0;
}
// PRINTF("Erase sector success !\r\n");
return 1;
}

/*********************************************************************
*
* 函数名: qspi_page_program()
* 功 能: 写入数据到flash 的一页中
* 参 数: FLEXSPI_Type *base spi 参数 EXAMPLE_FLEXSPI
* 参 数:uint8_t dstAddr : 写入数据起始地址
* 参 数:uint8_t *src : 写入数据缓冲区
* 参 数:size_t length : 写入数据的长度
* 注 意:
写入的数据的起始地址和 数据大小不能超过这一页,
* 返 回:
*
**********************************************************************/
uint8_t __attribute__((section("NonCacheable"))) qspi_page_program(FLEXSPI_Type *base, uint32_t dstAddr, uint32_t *src, size_t length)
{
status_t status;

taskENTER_CRITICAL(); //使能临界区
SCB_InvalidateDCache();
status = flexspi_nor_flash_page_program(base, dstAddr, (void *)src, length);
taskEXIT_CRITICAL(); //退出临界区
if (status != kStatus_Success)
{
PRINTF("Page program failure !\r\n");
return 0;
}
// PRINTF("Page program success !\r\n");
return 1;
}

/*********************************************************************
*
* 函数名: Qspi_Read_Sector()
* 功 能: 从特定flah 地址读取指定数据长度
* 参 数: FLEXSPI_Type *base spi 参数 EXAMPLE_FLEXSPI
* 参 数:uint8_t address : 读取数据起始地址
* 参 数:uint8_t *read_buf: 读取数据缓冲区
* 参 数:size_t leng : 读取数据的长度
* 注 意:
由于rt1050 是再外部flash 中启动的, 所以在启动的时候会将flash 的0 地址映射0x60000000 地址上,
所以读取的时候可以直接 更具映射地址memcpy
* 返 回:
*
**********************************************************************/
uint8_t Qspi_Read_Sector(FLEXSPI_Type *base, uint32_t address, uint8_t *read_buf, size_t leng)
{
/*如果程序运行在QSPI flash或者SRAM中,则可以通过以下代码来读取QSPI flash中的数据*/
// status_t status;

// taskENTER_CRITICAL(); //使能临界区
// SCB_InvalidateDCache();
// status = flexspi_nor_flash_read_sector(base, address, (void *)read_buf, leng);
// taskEXIT_CRITICAL(); //退出临界区

// if (status != kStatus_Success)
// {
// PRINTF("Page read failure !\r\n");
// return 0;
// }
// // PRINTF("Page read success !\r\n");
// return 1;
/*仅仅针对程序运行在QSPI flash上的情况,此时可以通过以下代码来读取QSPI flash中的数据*/
memcpy(read_buf, (void *)(FlexSPI_AMBA_BASE + (address)), leng);
return 1;
}

/*********************************************************************
*
* 函数名: Qspi_Write_NoCheck()
* 功 能: 向flash 中写入输入
* 参 数: FLEXSPI_Type *base spi 参数 EXAMPLE_FLEXSPI
* 参 数:uint8_t* pBuffer : 写入的数据缓冲区
* 参 数:uint32_t WriteAddr: 写入数据的地址
* 参 数:uint16_t NumByteToWrite: 写入数据的长度
* 注 意:
该函数使用时尽量不要跨越 扇区
当写入的数据长度 NumByteToWrite 大于256 的时候需要注意一下
* 返 回:
*
**********************************************************************/
uint8_t Qspi_Write_NoCheck(FLEXSPI_Type *base, uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
uint16_t pageremain;
pageremain = FLASH_PAGE_SIZE - WriteAddr % FLASH_PAGE_SIZE; //单页剩余的字节数
if (NumByteToWrite <= pageremain)
{
pageremain = NumByteToWrite; //不大于256个字节
}

while (1)
{
qspi_page_program(base, WriteAddr, (void *)pBuffer, pageremain);
if (NumByteToWrite == pageremain)
break; //写入结束了
else // NumByteToWrite>pageremain
{
pBuffer += pageremain;
WriteAddr += pageremain;

NumByteToWrite -= pageremain; //减去已经写入了的字节数
if (NumByteToWrite > FLASH_PAGE_SIZE)
pageremain = FLASH_PAGE_SIZE; //一次可以写入256个字节
else
pageremain = NumByteToWrite; //不够256个字节了
}
}

return 1;
}

void SysFlashOpen(void)
{
status_t status;
uint8_t vendorID = 0;

/* Update LUT table. */
FLEXSPI_UpdateLUT(FLEXSPI, NOR_CMD_LUT_SEQ_IDX_READID1*4, &customLUT[NOR_CMD_LUT_SEQ_IDX_READID1*4], 64);

taskENTER_CRITICAL(); //使能临界区
SCB_InvalidateDCache();
/* Get vendor ID. */
status = flexspi_nor_get_vendor_id(FLEXSPI, &vendorID);
taskEXIT_CRITICAL(); //退出临界区
if (status != kStatus_Success)
{
return;
}
PRINTF("QSPI Flash Vendor ID: 0x%x\r\n", vendorID);
}
static unsigned char buff[0x1000] = {0};
//addr: flash地址 相对偏移 0x4000以下均不能使用
void SysFlashErase(unsigned int addr, unsigned int length)
{
unsigned int endaddr = addr + length;

// PRINTF("--> addr: 0x%x\r\n", addr);

if(length >= 0x1000)//超过 4k 需要单独考虑
{
if(addr % 0x1000 != 0)
{
//向读取出 lenremain
Qspi_Read_Sector(FLEXSPI, addr&0xfffff000, (uint8_t *)buff, addr%0x1000);//读取前面的数据
qspi_erase_sector(FLEXSPI, addr&0xfffff000);//擦除 4k
Qspi_Write_NoCheck(FLEXSPI, (uint8_t *)buff, addr&0xfffff000, addr%0x1000);//写入前面的数据
// PRINTF("--> Erase Sector: 0x%x\r\n", addr);
addr = (addr&0xfffff000) + 0x1000;//偏移到下一个 4k
// PRINTF("--> Erase Sector: 0x%x\r\n", addr);
}

// PRINTF("--> header ok\r\n");

while(addr < (endaddr&0xfffff000))
{
// PRINTF("--> erase 0x%x\r\n", addr);
qspi_erase_sector(FLEXSPI, addr);//擦除 4k
addr += 0x1000;
}

// PRINTF("--> start tail\r\n");

if(endaddr % 0x1000 != 0)
{
Qspi_Read_Sector(FLEXSPI, endaddr, (uint8_t *)buff, 0x1000-(endaddr%0x1000));//读取后面的数据
qspi_erase_sector(FLEXSPI, endaddr&0xfffff000);//擦除 4k
Qspi_Write_NoCheck(FLEXSPI, (uint8_t *)buff, endaddr, 0x1000-(endaddr%0x1000));//写入后面的数据
}
}else
{
Qspi_Read_Sector(FLEXSPI, addr&0xfffff000, (uint8_t *)buff, addr%0x1000);//读取前面的数据
Qspi_Read_Sector(FLEXSPI, endaddr, (uint8_t *)&buff[endaddr%0x1000], 0x1000-(endaddr%0x1000));//读取后面的数据
qspi_erase_sector(FLEXSPI, addr&0xfffff000);//擦除 4k
Qspi_Write_NoCheck(FLEXSPI, (uint8_t *)buff, addr&0xfffff000, addr%0x1000);//写入前面的数据
Qspi_Write_NoCheck(FLEXSPI, (uint8_t *)&buff[endaddr%0x1000], endaddr, 0x1000-(endaddr%0x1000));//写入后面的数据
}
}

void SysFlashRead(unsigned char* buffer, unsigned int len, unsigned int offset)
{
Qspi_Read_Sector(FLEXSPI, offset, buffer, len);
}

void SysFlashWrite(unsigned char* buffer, unsigned int len, unsigned int offset)
{
Qspi_Write_NoCheck(FLEXSPI, buffer, offset, len);
}