闪存控制器(FLASH)
一、FLASH介绍
闪存由主存储器、外部存储器、信息块、闪存寄存器这四个部分组成。
主存储器容量高达 1024K 字节,分为第 1 片闪存(bank1)和第 2 片闪存(bank2) 外部存储器容量最高可达 16M 字节
信息块由 16K 字节的系统启动程序代码区和用户系统数据区组成。系统启动程序使用
USART1、USART2 或者 USB(DFU)接口实现 ISP 编程
1024K 字节容量的主存储器分为片 1 和片 2 闪存,每片闪存容量为 512K 字节,每片闪存包含 256 扇区,
每扇区大小为 2K 字节。
外部存储器容量可高达 16M 字节,包含 4096 扇区,每扇区大小为 4K 字节。
表 1 闪存存储结构(1024K)
512K 字节容量的主存储器只有片 1 闪存,包含 256 扇区,每扇区大小为 2K 字节。
外部存储器容量可高达 16M 字节,包含 4096 扇区,每扇区大小为 4K 字节。
表 2 闪存存储组织(512K)
256K 字节容量的主存储器只有片 1 闪存,包含 128 扇区,每扇区大小为 2K 字节。
外部存储器容量可高达 16M 字节,包含 4096 扇区,每扇区大小为 4K 字节。
表 3 闪存存储组织(256K)
下面以雅特力官方库版本 AT32F403A_407_Firmware_Library-v2.0.8 flash驱动为例,介绍AT32F403A内部flash读写的具体流程和读写测试方法
二、AT32F403A 内部flash读写
1. 内部flash写入
当想要改写主存储器的内容时,可以通过主存储器编程流程完成一次写入 32 位、16 位或 8 位的数据。
主存储器编程流程: 检查闪存状态寄存器 x(FLASH_STSx)的 OBF 位,确认没有正在进行的闪存操作; 对闪存控制寄存器 x(FLASH_CTRLx)的 FPRGM 位置 1,此时可以接受对主闪存的
编程指令;
对指定的地址写入要编程的数据(任意字/半字/字节);
等待闪存状态寄存器 x(FLASH_STSx)的 OBF 位变为‘0’,并查询闪存状态寄存器 x (FLASH_STSx)的 EPPERR 位、PRGMERR 位和 ODF 位,确认编程结果。
注意:1.当要写入的地址未被提前擦除时,除非要写入的数据值是全 0,否则编程不被执
行,并置位闪存状态寄存器 x(FLASH_STSx)的 PRGMERR 位来告知编程发生错
误。
2.编程期间进行读闪存的操作,将导致 CPU 会被暂停直到编程完成才处理读闪存操
作。
图 4 主存储器编程流程
1、不带校验直接写入
/**
* @brief write data using halfword mode without checking
* @param write_addr: the address of writing
* @param p_buffer: the buffer of writing data
* @param num_write: the number of writing data
* @retval none
*/
void flash_write_nocheck(uint32_t write_addr, uint16_t *p_buffer, uint16_t num_write)
{
uint16_t i;
for(i = 0; i < num_write; i++)
{
flash_halfword_program(write_addr, p_buffer[i]);
write_addr += 2;
}
}
2、带校验写入
/**
* @brief write data using halfword mode with checking
* @param write_addr: the address of writing
* @param p_buffer: the buffer of writing data
* @param num_write: the number of writing data
* @retval none
*/
void flash_write(uint32_t write_addr, uint16_t *p_buffer, uint16_t num_write)
{
uint32_t offset_addr;
uint32_t sector_position;
uint16_t sector_offset;
uint16_t sector_remain;
uint16_t i;
flash_unlock();
offset_addr = write_addr - FLASH_BASE;
sector_position = offset_addr / SECTOR_SIZE;
sector_offset = (offset_addr % SECTOR_SIZE) / 2;
sector_remain = SECTOR_SIZE / 2 - sector_offset;
if(num_write <= sector_remain)
sector_remain = num_write;
while(1)
{
flash_read(sector_position * SECTOR_SIZE + FLASH_BASE, flash_buf, SECTOR_SIZE / 2);
for(i = 0; i < sector_remain; i++)
{
if(flash_buf[sector_offset + i] != 0xFFFF)
break;
}
if(i < sector_remain)
{
flash_sector_erase(sector_position * SECTOR_SIZE + FLASH_BASE);
for(i = 0; i < sector_remain; i++)
{
flash_buf[i + sector_offset] = p_buffer[i];
}
flash_write_nocheck(sector_position * SECTOR_SIZE + FLASH_BASE, flash_buf, SECTOR_SIZE / 2);
}
else
{
flash_write_nocheck(write_addr, p_buffer, sector_remain);
}
if(num_write == sector_remain)
break;
else
{
sector_position++;
sector_offset = 0;
p_buffer += sector_remain;
write_addr += (sector_remain * 2);
num_write -= sector_remain;
if(num_write > (SECTOR_SIZE / 2))
sector_remain = SECTOR_SIZE / 2;
else
sector_remain = num_write;
}
}
flash_lock();
}
2. 内部flash读取
通过 CPU 的 AHB 总线可以直接寻址访问主闪存存储区。
/**
* @brief read data using halfword mode
* @param read_addr: the address of reading
* @param p_buffer: the buffer of reading data
* @param num_read: the number of reading data
* @retval none
*/
void flash_read(uint32_t read_addr, uint16_t *p_buffer, uint16_t num_read)
{
uint16_t i;
for(i = 0; i < num_read; i++)
{
p_buffer[i] = *(uint16_t*)(read_addr);
read_addr += 2;
}
}
3. 内部flash读写测试
1、头文件 flash.h
#ifndef __FLASH_H__
#define __FLASH_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "at32f403a_407_board.h"
/** @defgroup FLASH_write_read_functions
* @{
*/
u16 flash_ReadHalfWord(u32 faddr);
void flash_read(uint32_t read_addr, uint16_t *p_buffer, uint16_t num_read);
void flash_write_nocheck(uint32_t write_addr, uint16_t *p_buffer, uint16_t num_write);
void flash_write(uint32_t write_addr,uint16_t *p_Buffer, uint16_t num_write);
/**
* @}
*/
2、源文件 flash.c
/**
**************************************************************************
* @file flash.c
* @version v2.0.8
* @date 2022-04-02
* @brief flash program
**************************************************************************
* Copyright notice & Disclaimer
*
* The software Board Support Package (BSP) that is made available to
* download from Artery official website is the copyrighted work of Artery.
* Artery authorizes customers to use, copy, and distribute the BSP
* software and its related documentation for the purpose of design and
* development in conjunction with Artery microcontrollers. Use of the
* software is governed by this copyright notice and the following disclaimer.
*
*
* THIS SOFTWARE IS PROVIDED ON "AS IS" BASIS WITHOUT WARRANTIES,
* GUARANTEES OR REPRESENTATIONS OF ANY KIND. ARTERY EXPRESSLY DISCLAIMS,
* TO THE FULLEST EXTENT PERMITTED BY LAW, ALL EXPRESS, IMPLIED OR
* STATUTORY OR OTHER WARRANTIES, GUARANTEES OR REPRESENTATIONS,
* INCLUDING BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
*
**************************************************************************
*/
#include "at32f403a_407_board.h"
#include "flash.h"
/** @addtogroup AT32F403A_periph_examples
* @{
*/
/** @addtogroup 403A_FLASH_write_read
* @{
*/
#define SECTOR_SIZE 2048 /* this parameter depends on the specific model of the chip */
uint16_t flash_buf[SECTOR_SIZE / 2];
uint16_t flash_buf[SECTOR_SIZE / 2];
//读取指定地址的半字(16位数据)
//faddr:读地址(此地址必须为2的倍数!!)
//返回值:对应数据.
u16 flash_ReadHalfWord(u32 faddr){
return *(vu16*)faddr;
}
/**
* @brief read data using halfword mode
* @param read_addr: the address of reading
* @param p_buffer: the buffer of reading data
* @param num_read: the number of reading data
* @retval none
*/
void flash_read(uint32_t read_addr, uint16_t *p_buffer, uint16_t num_read)
{
uint16_t i;
for(i = 0; i < num_read; i++)
{
p_buffer[i] = *(uint16_t*)(read_addr);
read_addr += 2;
}
}
/**
* @brief write data using halfword mode without checking
* @param write_addr: the address of writing
* @param p_buffer: the buffer of writing data
* @param num_write: the number of writing data
* @retval none
*/
void flash_write_nocheck(uint32_t write_addr, uint16_t *p_buffer, uint16_t num_write)
{
uint16_t i;
for(i = 0; i < num_write; i++)
{
flash_halfword_program(write_addr, p_buffer[i]);
write_addr += 2;
}
}
/**
* @brief write data using halfword mode with checking
* @param write_addr: the address of writing
* @param p_buffer: the buffer of writing data
* @param num_write: the number of writing data
* @retval none
*/
void flash_write(uint32_t write_addr, uint16_t *p_buffer, uint16_t num_write)
{
uint32_t offset_addr;
uint32_t sector_position;
uint16_t sector_offset;
uint16_t sector_remain;
uint16_t i;
flash_unlock();
offset_addr = write_addr - FLASH_BASE;
sector_position = offset_addr / SECTOR_SIZE;
sector_offset = (offset_addr % SECTOR_SIZE) / 2;
sector_remain = SECTOR_SIZE / 2 - sector_offset;
if(num_write <= sector_remain)
sector_remain = num_write;
while(1)
{
flash_read(sector_position * SECTOR_SIZE + FLASH_BASE, flash_buf, SECTOR_SIZE / 2);
for(i = 0; i < sector_remain; i++)
{
if(flash_buf[sector_offset + i] != 0xFFFF)
break;
}
if(i < sector_remain)
{
flash_sector_erase(sector_position * SECTOR_SIZE + FLASH_BASE);
for(i = 0; i < sector_remain; i++)
{
flash_buf[i + sector_offset] = p_buffer[i];
}
flash_write_nocheck(sector_position * SECTOR_SIZE + FLASH_BASE, flash_buf, SECTOR_SIZE / 2);
}
else
{
flash_write_nocheck(write_addr, p_buffer, sector_remain);
}
if(num_write == sector_remain)
break;
else
{
sector_position++;
sector_offset = 0;
p_buffer += sector_remain;
write_addr += (sector_remain * 2);
num_write -= sector_remain;
if(num_write > (SECTOR_SIZE / 2))
sector_remain = SECTOR_SIZE / 2;
else
sector_remain = num_write;
}
}
flash_lock();
}
/**
* @}
*/
3、主程序main.c
将闪存存储结构片1连续写入3000字节数据并读取出来放到buffer_read[TEST_BUFEER_SIZE]缓存
#define TEST_BUFEER_SIZE 3000
#define TEST_FLASH_ADDRESS_START (0x08000000 + 1024 * 512)
uint16_t buffer_write[TEST_BUFEER_SIZE];
uint16_t buffer_read[TEST_BUFEER_SIZE];
error_status buffer_compare(uint16_t* p_buffer1, uint16_t* p_buffer2, uint16_t buffer_length);
/**
* @brief compares two buffers.
* @param p_buffer1, p_buffer2: buffers to be compared.
* @param buffer_length: buffer's length
* @retval SUCCESS: p_buffer1 identical to p_buffer2
* failed: p_buffer1 differs from p_buffer2
*/
error_status buffer_compare(uint16_t* p_buffer1, uint16_t* p_buffer2, uint16_t buffer_length)
{
while(buffer_length--)
{
if(*p_buffer1 != *p_buffer2)
{
return ERROR;
}
p_buffer1++;
p_buffer2++;
}
return SUCCESS;
}
/**
* @brief main function.
* @param none
* @retval none
*/
int main(void)
{
uint32_t index=0;
system_clock_config();
at32_board_init();
/* fill buffer_write data to test */
for(index = 0; index < TEST_BUFEER_SIZE; index++)
{
buffer_write[index] = index;
}
while(1)
{
/* write data to flash */
flash_write(FLASH_ADDRESS_START, buffer_write, TEST_BUFEER_SIZE);
/* read data from flash */
flash_read(FLASH_ADDRESS_START, buffer_read, TEST_BUFEER_SIZE);
/* compare the buffer */
if(buffer_compare(buffer_write, buffer_read, TEST_BUFEER_SIZE) == SUCCESS)
{
at32_led_on(LED2);
at32_led_on(LED3);
at32_led_on(LED4);
}
delay_sec(1);
}
}
4. 存储器擦除
和stm32一样,AT32内部flash编程之前必须先进行擦除操作,主存储器有扇区擦除和整片擦除两种擦除方式。
1、扇区擦除步骤:
1检查FLASH_CR的LOCK是否解锁,如果没有则先解锁
2检查FLASH_SR寄存器中的BSY 位,确保当前未执行任何FLASH操作
3在FLASH_CR寄存器中,将SER位置1,并从主存储块的12个扇区中选择要擦除的扇区 (SNB)
4将FLASH_CR寄存器中的STRT位置1,触发擦除操作
5等待BSY位清零
经过以上五步,就可以擦除某个扇区。
2、批量擦除步骤
1检查FLASH_SR寄存器中的BSY 位,确保当前未执行任何FLASH操作
2在FLASH_CR寄存器中,将MER位置1 (STM32F407xx)其他板子不同
3将FLASH_CR寄存器中的STRT位置1,触发擦除操作
4等待BSY位清零
经过以上四步,就可以批量擦除扇区。