通过网盘分享的文件:STM32G474VET6+NOR FLASH 验证.zip
链接: https://pan.baidu.com/s/1323CdUaAsV1m6juSMh9i9g?pwd=pbbn 提取码: pbbn
–来自百度网盘超级会员v4的分享
CUBE MAX配置:
本文将通过分析提供的代码和流程图,详细解释STM32与SST39VF800A Flash存储器之间的操作流程,特别是字编程、扇区擦除和设备ID读取的实现。
配FMC好后你往BANK中地址赋值,FMC会自动根据地质与数值生成对应信号时序![]()
比如:
就是:
#define FLASH_WORD_TO_BYTE(addr_word) ((addr_word) << 1)
*((volatile uint16_t*)(0x60000000 + FLASH_WORD_TO_BYTE(0x2AAA))) = (0x0055);
也就是FLASH内部一个地址代表stm32映射的两个地址所以要乘以2
他就会生成往地址0x00002AAA地址赋值




一、核心概念:Flash命令序列
从流程图中可以看出,SST39VF800A的所有操作都遵循命令序列模式。与RAM的简单读写不同,Flash需要特定的"解锁-命令-数据/地址"序列来执行编程、擦除等操作。
关键特点:
三周期解锁:所有命令都需要先发送两个解锁序列
地址/数据复用:相同地址(5555H/2AAAH)在不同命令中传输不同数据
状态检测:操作完成后需要检测Toggle Bit或Data# Polling
二、代码实现与流程图的对应关系
1. 解锁序列实现 (SST_SendUnlockSequence)
流程图要求:
1. 向地址5555H写入数据00AAH
2. 向地址2AAAH写入数据0055H
代码实现:
static void SST_SendUnlockSequence(void)
{
/* First unlock cycle – 使用Flash字地址 */
SST_WRITE_CMD(SST_CMD_ADDR_1, SST_CMD_UNLOCK1); // 0x5555 -> 0x00AA
/* 小延时确保命令完成 */
Delay_Us(1);
/* Second unlock cycle – 使用Flash字地址 */
SST_WRITE_CMD(SST_CMD_ADDR_2, SST_CMD_UNLOCK2); // 0x2AAA -> 0x0055
/* 小延时确保命令完成 */
Delay_Us(1);
}
注意:代码中的地址是Flash字地址,不是STM32的字节地址。宏SST_WRITE_CMD内部会将字地址转换为字节地址。
2. 字编程实现 (SST_ByteProgram)
图18流程:
1. 解锁序列(前两步)
2. 向5555H写入A0H
3. 向目标地址写入数据
4. 等待操作完成
代码实现:
static bool SST_ByteProgram(uint32_t address, uint16_t data)
{
/* 1-3步:解锁序列 + 编程命令 */
SST_SendUnlockSequence(); // 解锁步骤1-2
SST_WRITE_CMD(SST_CMD_ADDR_1, SST_CMD_PROGRAM); // 步骤3: 0x5555 -> 0x00A0
Delay_Us(1);
/* 4步:向目标地址写入数据 */
NOR_WRITE_BYTE(address, data); // 写入用户数据
/* 5步:等待编程完成(使用Toggle Bit或Data# Polling) */
return SST_WaitForOperationComplete(address, data, false);
}
重要说明:
-
address参数是字节地址,需要保证字对齐(偶数地址)
-
写入操作前必须确保目标地址已擦除(值为0xFFFF)
3. 扇区擦除实现 (SST_SectorErase)
根据数据手册的6周期擦除序列:
1. 解锁序列(5555H->AAH, 2AAAH->55H)
2. 再次向5555H写入80H
3. 再次解锁序列(5555H->AAH, 2AAAH->55H)
4. 向扇区地址写入30H
代码实现:
static bool SST_SectorErase(uint32_t sector_address)
{
/* 转换为Flash字地址 */
uint16_t sector_word_addr = FLASH_BYTE_TO_WORD(sector_address) & 0x7FFF;
/* 1-2步:解锁序列 + 擦除设置命令 */
SST_SendUnlockSequence(); // 步骤1-2
SST_WRITE_CMD(SST_CMD_ADDR_1, 0x0080); // 步骤3: 5555H -> 80H
Delay_Us(1);
/* 3-4步:再次解锁序列 + 扇区擦除确认 */
SST_SendUnlockSequence(); // 步骤4-5
NOR_WRITE_BYTE(sector_address, SST_CMD_SECTOR_ERASE); // 步骤6: 扇区地址 -> 30H
/* 等待擦除完成 */
return SST_WaitForOperationComplete(sector_address, 0xFFFF, true);
}
关键点:
-
擦除操作针对整个4KB扇区
-
必须使用扇区对齐的地址(地址低12位为0)
-
擦除时间较长(最长25ms),需要适当等待
4. 设备ID读取 (SST_ReadID)
图20软件ID入口序列:
1. 解锁序列(5555H->AAH, 2AAAH->55H)
2. 向5555H写入90H
3. 读取设备ID
4. 退出软件ID模式
代码实现:
static bool SST_ReadID(uint16_t* manufacturer_id, uint16_t* device_id)
{
/* 1-2步:解锁序列 + 进入软件ID模式 */
SST_SendUnlockSequence(); // 解锁序列
SST_WRITE_CMD(SST_CMD_ADDR_1, SST_CMD_AUTOSELECT); // 5555H -> 90H
Delay_Us(10);
/* 3步:读取制造商ID和器件ID */
*manufacturer_id = NOR_READ_WORD(0x0000); // 字地址0000H -> 制造商ID
*device_id = NOR_READ_WORD(0x0001); // 字地址0001H -> 器件ID
/* 4步:退出软件ID模式 */
SST_WRITE_CMD(0x0000, SST_CMD_RESET); // 任何地址写入F0H
Delay_Us(10);
return true;
}
ID地址说明:
-
制造商ID:从字地址0000H读取(返回0x00BF表示SST)
-
器件ID:从字地址0001H读取(返回0x2781表示SST39VF800A)
三、状态检测机制详解
图19展示了三种等待方式:
1. Toggle Bit检测(代码中实现)
static bool SST_CheckToggleBit(uint32_t address)
{
volatile uint16_t* flash_addr = (volatile uint16_t*)(NOR_FLASH_BASE_ADDR + address);
uint16_t read1, read2;
read1 = *flash_addr; // 第一次读取
read2 = *flash_addr; // 第二次读取
/* 检查DQ6位是否停止翻转 */
return ((read1 & 0x0040) == (read2 & 0x0040));
}
原理:在编程/擦除期间,DQ6位会不断翻转。当操作完成时,DQ6停止翻转。
2. Data# Polling检测(代码中实现)
static bool SST_CheckDataPolling(uint32_t address, uint16_t expected_data)
{
volatile uint16_t* flash_addr = (volatile uint16_t*)(NOR_FLASH_BASE_ADDR + address);
uint16_t read_data;
read_data = *flash_addr;
/* 检查DQ7位是否与期望数据匹配 */
return ((read_data & 0x0080) == (expected_data & 0x0080));
}
原理:在编程期间,读取的DQ7位是被写入数据的反码。操作完成后,DQ7恢复为写入数据。
3. 内部定时器(未在代码中使用)
-
Flash内部有定时器控制最大操作时间
-
超时后自动终止操作
四、代码中的关键设计模式
1. 地址映射处理
/* 将Flash字地址转换为STM32字节地址(字地址 × 2)*/
#define FLASH_WORD_TO_BYTE(addr_word) ((addr_word) << 1)
/* 将Flash字节地址转换为字地址(字节地址 ÷ 2)*/
#define FLASH_BYTE_TO_WORD(addr_byte) ((addr_byte) >> 1)
/* 基于字地址的访问宏 */
#define NOR_READ_WORD(addr_word) (*((volatile uint16_t*)(NOR_FLASH_BASE_ADDR + FLASH_WORD_TO_BYTE(addr_word))))
应用示例:
// 读取制造商ID(字地址0x0000)
uint16_t mfr_id = NOR_READ_WORD(0x0000);
// 实际访问的STM32地址:0x60000000 + (0x0000 << 1) = 0x60000000
2. 复合等待策略
static bool SST_WaitForOperationComplete(uint32_t address, uint16_t expected_data, bool is_erase)
{
uint32_t timeout_us = is_erase ? SST_ERASE_TIMEOUT_MS * 1000 : SST_PROGRAM_TIMEOUT_US;
while (elapsed_us < timeout_us) {
/* 先尝试Toggle Bit检测 */
if (SST_CheckToggleBit(address)) {
/* 再使用Data Polling验证 */
if (SST_CheckDataPolling(address, expected_data)) {
/* 最后双重读取确保稳定 */
uint16_t read1 = *flash_addr;
uint16_t read2 = *flash_addr;
if (read1 == read2) {
return true;
}
}
}
// … 时间检查和延迟
}
return false; /* 超时 */
}
设计思想:结合两种检测方法,提高可靠性,避免误判。
五、测试函数解析
1. 基础测试 (LZY_WRITE_TEST)
void LZY_WRITE_TEST(uint32_t address, uint16_t data)
{
// 1. 地址范围检查
if (address > MAX_MUXED_ADDRESS) { … }
// 2. 地址对齐检查(必须为偶数)
if (address & 0x01) { … }
// 3. 读取当前值
uint16_t current_value = NOR_READ_BYTE(address);
// 4. 判断是否需要擦除
if (current_value == 0xFFFF) {
// 直接编程
SST_ByteProgram(address, data);
} else {
// 先擦除扇区
uint32_t sector_base = address & ~(NOR_FLASH_SECTOR_SIZE – 1);
SST_SectorErase(sector_base);
SST_ByteProgram(address, data);
}
// 5. 验证结果
uint16_t new_value = NOR_READ_BYTE(address);
// 比较并输出结果
}
测试逻辑:
-
检查是否已擦除(值为0xFFFF)
-
未擦除时,必须先擦除整个扇区
-
擦除后编程,并验证结果
2. 地址空间测试 (Test_Full_Address_Range)
void Test_Full_Address_Range(void)
{
// 测试低地址
LZY_WRITE_TEST(0x000000, 0x5555);
LZY_WRITE_TEST(0x001000, 0xAAAA);
// 测试中间地址(根据配置的地址线计算)
uint32_t middle_addr = MAX_MUXED_ADDRESS / 2;
middle_addr = middle_addr & ~0x01; // 确保字对齐
LZY_WRITE_TEST(middle_addr, 0x1234);
// 测试高地址
uint32_t high_addr = MAX_MUXED_ADDRESS – 0x1000;
high_addr = high_addr & ~0x01; // 确保字对齐
LZY_WRITE_TEST(high_addr, 0x5678);
}
测试策略:
-
覆盖低、中、高三个地址区域
-
考虑地址线配置限制(复用模式)
-
确保地址对齐
六、复用模式下的特殊考虑
1. 地址线数量限制
/* 在复用模式下,地址通过数据线传输,受引脚数量限制 */
#define ADDR_BITS_USED 19 /* CubeMX中配置的复用地址线数量 */
#define MAX_MUXED_ADDRESS ((1UL << ADDR_BITS_USED) – 1)
影响:
-
如果配置19位地址线,最大可访问地址为0x7FFFF(524,287字节)
-
但SST39VF800A实际有19条地址线(A0-A18),可访问1MB空间
-
如果STM32配置的地址线不足,高位地址会回绕
2. 复用模式访问
/* 复用模式下的地址访问宏 */
#define NOR_READ_BYTE(addr_byte) (*((volatile uint16_t*)(NOR_FLASH_BASE_ADDR + (addr_byte))))
工作原理:
STM32通过FMC的ALE信号锁存地址
地址和数据通过同一组线分时传输
Flash在ALE为高时锁存地址,ALE为低时读写数据
七、总结与最佳实践
1. 操作流程总结
任何Flash操作 = 解锁序列 + 命令 + 数据/地址 + 状态检测
2. 关键时序要求
-
解锁序列:每个命令前必须执行
-
命令间隔:需要微小延时(1-10μs)
-
操作超时:编程≤20μs,扇区擦除≤25ms
3. 调试建议
先读取ID:确认设备连接正确
测试基本读写:从低地址开始
检查地址映射:验证字地址到字节地址的转换
监控状态位:使用Toggle Bit或Data# Polling调试操作状态
4. 常见问题排查
-
ID读取失败:检查硬件连接、时序配置、解锁序列
-
编程失败:确认地址已擦除、地址对齐、超时设置
-
地址异常:检查地址线配置、复用模式设置
通过深入理解流程图和代码的对应关系,可以更好地掌握SST39VF800A Flash的操作原理,并有效地调试和优化Flash访问代码。
示例代码:
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2026 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ——————————————————————*/
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include "fmc.h"
/* Private includes ———————————————————-*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
/* USER CODE END Includes */
/* Private typedef ———————————————————–*/
/* USER CODE BEGIN PTD */
/**
* @brief NOR Flash Test Results Structure
*/
typedef struct {
bool write_test_passed;
bool read_id_success;
bool erase_test_passed;
uint16_t manufacturer_id;
uint16_t device_id;
uint32_t capacity_bytes;
const char* device_name;
} NOR_Test_Results_t;
/* USER CODE END PTD */
/* Private define ————————————————————*/
/* USER CODE BEGIN PD */
/* NOR Flash Configuration – SST39VF800A Specific */
#define NOR_FLASH_BASE_ADDR 0x60000000UL
#define NOR_FLASH_SIZE_BYTES (1 * 1024 * 1024) /* 8Mbit = 1MB */
#define NOR_FLASH_SECTOR_SIZE (4 * 1024) /* 2KWord = 4KB sectors */
#define NOR_FLASH_BLOCK_SIZE (64 * 1024) /* 32KWord = 64KB blocks */
/* SST39VF800A Command Addresses (in 16-bit words from datasheet) */
#define SST_CMD_ADDR_1 0x5555 /* Word address 5555H */
#define SST_CMD_ADDR_2 0x2AAA /* Word address 2AAAH */
/* SST39VF800A Command Codes (from Table 4) */
#define SST_CMD_UNLOCK1 0x00AA
#define SST_CMD_UNLOCK2 0x0055
#define SST_CMD_AUTOSELECT 0x0090
#define SST_CMD_PROGRAM 0x00A0
#define SST_CMD_CHIP_ERASE_1 0x0080
#define SST_CMD_SECTOR_ERASE 0x0030
#define SST_CMD_RESET 0x00F0
/* SST39VF800A Manufacturer and Device IDs (from Table 2) */
#define SST_MANUFACTURER_ID 0x00BF
#define SST39VF800A_DEVICE_ID 0x2781
/* Timing Parameters from datasheet */
#define SST_PROGRAM_TIMEOUT_US 20000UL /* 20μs max from Table 17 */
#define SST_ERASE_TIMEOUT_MS 100UL /* 100ms max for chip erase */
#define SST_SECTOR_ERASE_TIMEOUT_MS 25UL /* 25ms max for sector erase */
/* 复用模式下的关键宏定义 */
/* 在复用模式下,地址通过数据线传输,需要ALE信号锁存 */
#define ENABLE_ALE_LATCH() // 在复用模式下,FSMC会自动管理ALE
/* USER CODE END PD */
/* Private macro ————————————————————-*/
/* USER CODE BEGIN PM */
/* 复用模式下的地址映射 */
/*
* 在复用模式下,地址和数据共享同一组线
* STM32的地址总线是字节地址,需要转换为Flash字地址
* 注意:在CubeMX中配置为19位复用地址线
*/
#define ADDR_BITS_USED 19 /* CubeMX中配置的复用地址线数量 */
#define MAX_MUXED_ADDRESS ((1UL << ADDR_BITS_USED) – 1)
/* 将Flash字地址转换为STM32字节地址(字地址 × 2)*/
#define FLASH_WORD_TO_BYTE(addr_word) ((addr_word) << 1)
/* 将Flash字节地址转换为字地址(字节地址 ÷ 2)*/
#define FLASH_BYTE_TO_WORD(addr_byte) ((addr_byte) >> 1)
/* 复用模式下的地址访问宏 */
/*
* 重要:在复用模式下,FSMC会自动处理地址/数据复用
* 我们只需要提供字节地址,FSMC会通过ALE信号锁存地址
* 然后通过同一组线传输数据
*/
#define NOR_READ_BYTE(addr_byte) (*((volatile uint16_t*)(NOR_FLASH_BASE_ADDR + (addr_byte))))
#define NOR_WRITE_BYTE(addr_byte, data) (*((volatile uint16_t*)(NOR_FLASH_BASE_ADDR + (addr_byte))) = (data))
/* 基于Flash字地址的访问宏 */
#define NOR_READ_WORD(addr_word) (*((volatile uint16_t*)(NOR_FLASH_BASE_ADDR + FLASH_WORD_TO_BYTE(addr_word))))
#define NOR_WRITE_WORD(addr_word, data) (*((volatile uint16_t*)(NOR_FLASH_BASE_ADDR + FLASH_WORD_TO_BYTE(addr_word))) = (data))
/* 命令写入宏 – 使用Flash字地址 */
#define SST_WRITE_CMD(addr_word, data) \\
NOR_WRITE_WORD(addr_word, data)
/* USER CODE END PM */
/* Private variables ———————————————————*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes ———————————————–*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* SST39VF800A Specific Functions */
static bool SST_ReadID(uint16_t* manufacturer_id, uint16_t* device_id);
static bool SST_SectorErase(uint32_t sector_address);
static bool SST_ByteProgram(uint32_t address, uint16_t data);
static bool SST_CheckToggleBit(uint32_t address);
static bool SST_CheckDataPolling(uint32_t address, uint16_t expected_data);
static bool SST_WaitForOperationComplete(uint32_t address, uint16_t expected_data, bool is_erase);
static void SST_SendUnlockSequence(void);
static void Delay_Us(volatile uint32_t microseconds);
/* LZY Test Functions */
void LZY_NOR_Init(void);
void LZY_NOR_DisplayInfo(void);
void LZY_WRITE_TEST(uint32_t address, uint16_t data);
void Test_Full_Address_Range(void);
void Verify_Address_Mapping(void);
void Test_Muxed_Mode_Compatibility(void);
/* USER CODE END PFP */
/* Private user code ———————————————————*/
/* USER CODE BEGIN 0 */
/**
* @brief Delay for specified microseconds (approximate)
* @param microseconds: Number of microseconds to delay
* @retval None
*/
static void Delay_Us(volatile uint32_t microseconds)
{
/* Assuming 85MHz system clock, 1us ≈ 85 cycles */
volatile uint32_t cycles = microseconds * 85;
while (cycles–) {
__NOP();
}
}
/**
* @brief Send unlock sequence (common for all commands)
* @retval None
*/
static void SST_SendUnlockSequence(void)
{
/* 在复用模式下,FSMC会自动通过ALE锁存地址,然后传输数据 */
/* First unlock cycle – 使用Flash字地址 */
SST_WRITE_CMD(SST_CMD_ADDR_1, SST_CMD_UNLOCK1);
/* 小延时确保命令完成 */
Delay_Us(1);
/* Second unlock cycle – 使用Flash字地址 */
SST_WRITE_CMD(SST_CMD_ADDR_2, SST_CMD_UNLOCK2);
/* 小延时确保命令完成 */
Delay_Us(1);
}
/**
* @brief Check Toggle Bit (DQ6) status
* @param address: 字节地址 to check
* @retval true if ready (stopped toggling), false if still busy
*/
static bool SST_CheckToggleBit(uint32_t address)
{
volatile uint16_t* flash_addr = (volatile uint16_t*)(NOR_FLASH_BASE_ADDR + address);
uint16_t read1, read2;
/* Read twice to check for toggling */
read1 = *flash_addr;
read2 = *flash_addr;
/* If DQ6 stops toggling, device is ready */
return ((read1 & 0x0040) == (read2 & 0x0040));
}
/**
* @brief Check Data Polling (DQ7) status
* @param address: 字节地址 to check
* @param expected_data: Expected data value
* @retval true if ready (DQ7 matches), false if still busy
*/
static bool SST_CheckDataPolling(uint32_t address, uint16_t expected_data)
{
volatile uint16_t* flash_addr = (volatile uint16_t*)(NOR_FLASH_BASE_ADDR + address);
uint16_t read_data;
read_data = *flash_addr;
/* Check DQ7 (bit 7) */
return ((read_data & 0x0080) == (expected_data & 0x0080));
}
/**
* @brief Wait for operation to complete using both methods
* @param address: 字节地址 to monitor
* @param expected_data: Expected data (for programming) or 0xFFFF (for erase)
* @param is_erase: true if waiting for erase, false for program
* @retval true if operation completed, false if timeout
*/
static bool SST_WaitForOperationComplete(uint32_t address, uint16_t expected_data, bool is_erase)
{
uint32_t timeout_us = is_erase ? SST_ERASE_TIMEOUT_MS * 1000 : SST_PROGRAM_TIMEOUT_US;
uint32_t start_time = HAL_GetTick();
uint32_t elapsed_us = 0;
while (elapsed_us < timeout_us) {
/* Try toggle bit first */
if (SST_CheckToggleBit(address)) {
/* Then verify with data polling */
if (SST_CheckDataPolling(address, expected_data)) {
/* Double-check by reading twice more as per datasheet */
volatile uint16_t* flash_addr = (volatile uint16_t*)(NOR_FLASH_BASE_ADDR + address);
uint16_t read1 = *flash_addr;
uint16_t read2 = *flash_addr;
if (read1 == read2) {
return true;
}
}
}
/* Update elapsed time */
elapsed_us = (HAL_GetTick() – start_time) * 1000;
Delay_Us(10); /* Small delay between checks */
}
return false; /* Timeout */
}
/**
* @brief Erase a 4KB sector in SST39VF800A
* @param sector_address: Sector 字节地址 (must be sector aligned)
* @retval true if successful, false otherwise
*/
static bool SST_SectorErase(uint32_t sector_address)
{
/* Convert byte address to word address for command */
uint16_t sector_word_addr = FLASH_BYTE_TO_WORD(sector_address) & 0x7FFF; /* A14-A0 for 8Mbit device */
/* Sector-Erase Command Sequence (6 cycles) from Table 4 */
/* First 3 cycles: Unlock sequence */
SST_SendUnlockSequence();
/* Fourth cycle: Setup erase command */
SST_WRITE_CMD(SST_CMD_ADDR_1, 0x0080);
Delay_Us(1);
/* Fifth and sixth cycles: Unlock sequence again */
SST_SendUnlockSequence();
/* Seventh cycle: Sector address and erase confirm */
NOR_WRITE_BYTE(sector_address, SST_CMD_SECTOR_ERASE);
/* Wait for erase completion */
return SST_WaitForOperationComplete(sector_address, 0xFFFF, true);
}
/**
* @brief Program a single word (16-bit) in SST39VF800A
* @param address: 字节地址 to program (must be word-aligned)
* @param data: Data to program
* @retval true if successful, false otherwise
*/
static bool SST_ByteProgram(uint32_t address, uint16_t data)
{
/* Word-Program Command Sequence (4 cycles) from Table 4 */
/* First 3 cycles: Unlock sequence */
SST_SendUnlockSequence();
/* Fourth cycle: Program command and data */
SST_WRITE_CMD(SST_CMD_ADDR_1, SST_CMD_PROGRAM);
Delay_Us(1);
/* Write the data to the target address */
NOR_WRITE_BYTE(address, data);
/* Wait for programming completion */
return SST_WaitForOperationComplete(address, data, false);
}
/**
* @brief Read SST39VF800A Manufacturer and Device ID
* @param manufacturer_id: Pointer to store manufacturer ID
* @param device_id: Pointer to store device ID
* @retval true if successful, false otherwise
*/
static bool SST_ReadID(uint16_t* manufacturer_id, uint16_t* device_id)
{
/* Software ID Entry Command Sequence (3 cycles) */
SST_SendUnlockSequence();
SST_WRITE_CMD(SST_CMD_ADDR_1, SST_CMD_AUTOSELECT);
Delay_Us(10);
/* Read Manufacturer ID (at word address 0000H, A0=0) */
*manufacturer_id = NOR_READ_WORD(0x0000);
/* Read Device ID (at word address 0001H, A0=1) */
*device_id = NOR_READ_WORD(0x0001);
/* Exit Software ID mode */
SST_WRITE_CMD(0x0000, SST_CMD_RESET);
Delay_Us(10);
return true;
}
/**
* @brief 测试复用模式兼容性
* @retval None
*/
void Test_Muxed_Mode_Compatibility(void)
{
printf("=== 测试复用模式兼容性 ===\\n");
// 检查CubeMX配置
printf("1. CubeMX配置检查:\\n");
printf(" – Memory type: Muxed NOR Flash\\n");
printf(" – Data/Address: %d bits\\n", ADDR_BITS_USED);
printf(" – 理论最大地址: 0x%06lX\\n", MAX_MUXED_ADDRESS);
// 测试地址线是否工作
printf("\\n2. 地址线测试:\\n");
uint32_t test_addresses[] = {
0x000000, // 地址线全0
0x000002, // A0=1
0x000004, // A1=1
0x000008, // A2=1
0x000010, // A3=1
0x000020, // A4=1
0x000040, // A5=1
0x000080, // A6=1
0x000100, // A7=1
0x000200, // A8=1
0x000400, // A9=1
0x000800, // A10=1
0x001000, // A11=1
0x002000, // A12=1
0x004000, // A13=1
0x008000, // A14=1
0x010000, // A15=1
0x020000, // A16=1
0x040000, // A17=1
0x080000, // A18=1 (最高位)
};
bool all_passed = true;
for (int i = 0; i < sizeof(test_addresses)/sizeof(test_addresses[0]); i++) {
uint32_t addr = test_addresses[i];
if (addr > MAX_MUXED_ADDRESS) {
printf(" 地址 0x%06lX: 超出配置的地址线范围\\n", addr);
continue;
}
// 尝试读取该地址
uint16_t value = NOR_READ_BYTE(addr);
printf(" 地址 0x%06lX (A%d): 读取值 0x%04X",
addr, i, value);
// 检查是否地址回绕
if (addr >= NOR_FLASH_SIZE_BYTES) {
printf(" [警告:超出Flash物理范围]\\n");
} else {
printf("\\n");
}
// 如果是擦除状态,可能是有效的
if (value != 0xFFFF && value != 0x0000) {
printf(" 注意:读取到非默认值,可能已编程\\n");
}
}
// 测试高位地址回绕
printf("\\n3. 高位地址回绕测试:\\n");
uint32_t wrap_test_addrs[] = {
0x100000, // 1MB位置(如果只有19位地址线,会回绕到0x000000)
0x200000, // 2MB位置
0x300000, // 3MB位置
};
for (int i = 0; i < sizeof(wrap_test_addrs)/sizeof(wrap_test_addrs[0]); i++) {
uint32_t addr = wrap_test_addrs[i];
uint16_t value = NOR_READ_BYTE(addr);
printf(" 高位地址 0x%06lX: 读取值 0x%04X", addr, value);
// 计算回绕地址
uint32_t wrapped_addr = addr & MAX_MUXED_ADDRESS;
printf(" (回绕到 0x%06lX)\\n", wrapped_addr);
}
printf("\\n4. 复用模式特定测试:\\n");
// 检查ALE引脚是否配置
#ifdef FMC_Bank1_ALE_ENABLE
printf(" ALE引脚已启用 (复用模式)\\n");
// 测试连续访问模式
printf(" 测试连续访问…\\n");
volatile uint16_t* base_ptr = (volatile uint16_t*)NOR_FLASH_BASE_ADDR;
// 连续读取多个地址
for (int i = 0; i < 10; i++) {
uint32_t offset = i * 0x1000;
if (offset > MAX_MUXED_ADDRESS) break;
uint16_t val = base_ptr[offset / 2]; // 除以2因为是指针偏移
printf(" 偏移 0x%04X: 0x%04X\\n", offset, val);
}
#else
printf(" 警告:ALE引脚未启用,可能不是真正的复用模式\\n");
#endif
printf("===============================\\n\\n");
}
/**
* @brief 验证地址映射的正确性
* @retval None
*/
void Verify_Address_Mapping(void)
{
printf("=== 验证地址映射 ===\\n");
// 测试几个关键地址
uint32_t test_offsets[] = {
0x000000, // 起始地址
0x07FFFE, // 第一个扇区末尾(4KB对齐)
0x3FFFFE, // 19位地址线的最大地址(如果配置为19位)
0x0FFFFE, // 1MB Flash的最大地址
};
for (int i = 0; i < sizeof(test_offsets)/sizeof(test_offsets[0]); i++) {
uint32_t byte_offset = test_offsets[i];
uint32_t word_offset = FLASH_BYTE_TO_WORD(byte_offset);
printf("字节偏移: 0x%06lX, 字偏移: 0x%06lX\\n",
byte_offset, word_offset);
// 尝试读取
uint16_t value = NOR_READ_BYTE(byte_offset);
printf(" 读取值: 0x%04X", value);
// 检查是否超出配置的地址线范围
if (byte_offset > MAX_MUXED_ADDRESS) {
printf(" [超出配置的%d位地址线范围]", ADDR_BITS_USED);
}
// 检查是否地址回绕
if (byte_offset >= NOR_FLASH_SIZE_BYTES) {
printf(" [警告:超出Flash物理范围!]\\n");
} else {
printf("\\n");
}
}
printf("=====================\\n\\n");
}
/**
* @brief LZY NOR Flash 初始化
* @retval None
*/
void LZY_NOR_Init(void)
{
uint16_t manufacturer_id, device_id;
printf("=== SST39VF800A Flash Init ===\\n");
// 在复用模式下,读取ID可能需要特殊处理
printf("模式:复用模式 (Muxed Mode)\\n");
printf("配置地址线:%d bits\\n", ADDR_BITS_USED);
if (SST_ReadID(&manufacturer_id, &device_id)) {
printf("Manufacturer: 0x%04X\\n", manufacturer_id);
printf("Device ID: 0x%04X\\n", device_id);
if (manufacturer_id == SST_MANUFACTURER_ID && device_id == SST39VF800A_DEVICE_ID) {
printf("Device: SST39VF800A 8Mbit (1MB)\\n");
printf("状态:设备识别成功!\\n");
} else {
printf("Unknown device!\\n");
printf("可能的原因:\\n");
printf(" 1. 复用模式配置不正确\\n");
printf(" 2. 硬件连接问题\\n");
printf(" 3. 时序配置不当\\n");
}
} else {
printf("Device not found!\\n");
printf("在复用模式下,可能需要检查:\\n");
printf(" 1. ALE信号是否正确连接\\n");
printf(" 2. 时序配置是否匹配Flash要求\\n");
printf(" 3. 硬件连接是否正确\\n");
}
printf("Base Address: 0x%08lX\\n", NOR_FLASH_BASE_ADDR);
printf("Sector Size: %d bytes\\n", NOR_FLASH_SECTOR_SIZE);
printf("Total Size: %d bytes (1MB)\\n", NOR_FLASH_SIZE_BYTES);
printf("配置地址线:%d bits (最大地址: 0x%06lX)\\n",
ADDR_BITS_USED, MAX_MUXED_ADDRESS);
printf("==============================\\n\\n");
}
/**
* @brief LZY NOR Flash 显示信息
* @retval None
*/
void LZY_NOR_DisplayInfo(void)
{
printf("=== SST39VF800A Info ===\\n");
printf("Capacity: 1MB (8Mbit)\\n");
printf("Organization: 512K x 16-bit\\n");
printf("Sector Size: 4KB\\n");
printf("Block Size: 64KB\\n");
printf("Voltage: 2.7-3.6V (3.3V typical)\\n");
printf("Address Lines: A0-A18 (19条)\\n");
printf("Data Bus: 16-bit\\n");
printf("Interface: 非复用模式 (但STM32配置为复用)\\n");
printf("注意事项:\\n");
printf(" – STM32配置为复用模式\\n");
printf(" – 需要外部地址锁存器或特殊硬件设计\\n");
printf(" – 地址线配置:%d bits\\n", ADDR_BITS_USED);
printf("=======================\\n\\n");
}
/**
* @brief LZY Flash 写入测试函数
* @param address: 要写入的字节地址
* @param data: 要写入的数据(16位)
* @retval None
*/
void LZY_WRITE_TEST(uint32_t address, uint16_t data)
{
// 检查地址是否超出配置的地址线范围
if (address > MAX_MUXED_ADDRESS) {
printf("错误:地址 0x%08lX 超出配置的%d位地址线范围!\\n",
NOR_FLASH_BASE_ADDR + address, ADDR_BITS_USED);
printf("最大可访问地址:0x%06lX\\n", MAX_MUXED_ADDRESS);
return;
}
// 确保地址是字对齐的(偶数地址)
if (address & 0x01) {
printf("错误:地址必须字对齐!0x%08lX\\n", NOR_FLASH_BASE_ADDR + address);
return;
}
uint16_t current_value = NOR_READ_BYTE(address);
printf("Addr: 0x%08lX, Current: 0x%04X -> ",
NOR_FLASH_BASE_ADDR + address, current_value);
/* 如果要写入数据,必须先检查是否已擦除 */
if (current_value == 0xFFFF) {
/* 位置已擦除,可以直接编程 */
if (SST_ByteProgram(address, data)) {
printf("Write 0x%04X OK", data);
} else {
printf("Write FAIL!");
}
} else {
/* 必须先擦除整个扇区 */
uint32_t sector_base = address & ~(NOR_FLASH_SECTOR_SIZE – 1);
printf("Erasing sector 0x%08lX… ", NOR_FLASH_BASE_ADDR + sector_base);
if (SST_SectorErase(sector_base)) {
printf("Erased, ");
if (SST_ByteProgram(address, data)) {
printf("Write 0x%04X OK", data);
} else {
printf("Write FAIL!");
}
} else {
printf("Erase FAIL!");
}
}
/* 重新读取验证 */
uint16_t new_value = NOR_READ_BYTE(address);
printf(", Read: 0x%04X %s\\n",
new_value,
(new_value == data) ? "[OK]" : "[FAIL]");
}
/**
* @brief 测试完整地址空间
* @retval None
*/
void Test_Full_Address_Range(void)
{
printf("=== 测试完整地址空间 ===\\n");
// 注意:在复用模式下,我们只能测试配置的地址线范围内的地址
// 测试低地址
printf("1. 测试低地址区域:\\n");
LZY_WRITE_TEST(0x000000, 0x5555);
LZY_WRITE_TEST(0x001000, 0xAAAA);
// 测试中间地址
printf("\\n2. 测试中间地址区域:\\n");
// 根据配置的地址线数量确定中间地址
uint32_t middle_addr = MAX_MUXED_ADDRESS / 2;
middle_addr = middle_addr & ~0x01; // 确保字对齐
if (middle_addr < NOR_FLASH_SIZE_BYTES) {
LZY_WRITE_TEST(middle_addr, 0x1234);
} else {
printf("中间地址超出Flash范围,跳过\\n");
}
// 测试高地址(在配置的地址线范围内)
printf("\\n3. 测试高地址区域:\\n");
// 测试接近最大配置地址的位置
uint32_t high_addr = MAX_MUXED_ADDRESS – 0x1000;
high_addr = high_addr & ~0x01; // 确保字对齐
if (high_addr < NOR_FLASH_SIZE_BYTES) {
LZY_WRITE_TEST(high_addr, 0x5678);
} else {
printf("高地址超出Flash范围,跳过\\n");
}
// 测试边界条件
printf("\\n4. 测试边界条件:\\n");
// 测试接近19位地址线最大地址的位置
uint32_t boundary_addr = MAX_MUXED_ADDRESS – 0x20;
boundary_addr = boundary_addr & ~0x01; // 确保字对齐
if (boundary_addr < NOR_FLASH_SIZE_BYTES) {
LZY_WRITE_TEST(boundary_addr, 0x9ABC);
} else {
printf("边界地址超出Flash范围,跳过\\n");
}
printf("========================\\n\\n");
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* MCU Configuration——————————————————–*/
HAL_Init();
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_FMC_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
/* 系统信息 */
printf("\\nSTM32 + SST39VF800A Flash Test\\n");
printf("模式:复用模式 (Muxed Mode)\\n");
printf("配置地址线:%d bits\\n", ADDR_BITS_USED);
printf("===============================\\n\\n");
/* 测试复用模式兼容性 */
Test_Muxed_Mode_Compatibility();
/* 初始化NOR Flash */
LZY_NOR_Init();
/* 验证地址映射 */
Verify_Address_Mapping();
/* 显示设备信息 */
LZY_NOR_DisplayInfo();
/* 测试完整地址空间(在配置的地址线范围内) */
Test_Full_Address_Range();
/* 初始测试 */
printf("=== 初始测试 ===\\n");
LZY_WRITE_TEST(0x1000, 0x5555); // 测试地址0x1000,写入0x5555
LZY_WRITE_TEST(0x2000, 0xAAAA); // 测试地址0x2000,写入0xAAAA
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* 循环测试不同的地址和数据 */
static uint32_t test_count = 0;
// 在配置的地址线范围内循环测试
uint32_t max_test_addr = MAX_MUXED_ADDRESS;
if (max_test_addr > NOR_FLASH_SIZE_BYTES – 2) {
max_test_addr = NOR_FLASH_SIZE_BYTES – 2;
}
// 生成测试地址(在安全范围内)
uint32_t base_addr = 0x1000 + (test_count % 8) * 0x1000;
// 确保地址在范围内
if (base_addr > max_test_addr) {
base_addr = max_test_addr – 0x8000; // 回退到一个安全地址
}
base_addr = base_addr & ~0x01; // 确保字对齐
uint16_t test_data = 0x1234 + test_count * 0x1111;
printf("Test #%lu: ", test_count);
LZY_WRITE_TEST(base_addr, test_data);
test_count++;
HAL_Delay(2000); // 每2秒测试一次
// 每10次测试打印一次状态
if (test_count % 10 == 0) {
printf("\\n=== 测试统计 ===\\n");
printf("已执行测试次数: %lu\\n", test_count);
printf("当前最大可访问地址: 0x%06lX\\n", max_test_addr);
printf("配置地址线: %d bits\\n", ADDR_BITS_USED);
printf("==================\\n\\n");
}
}
/* USER CODE END 3 */
}
/* 其他函数保持不变… */
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1_BOOST);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV2;
RCC_OscInitStruct.PLL.PLLN = 85;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
printf("\\n[ERROR] Error_Handler called! System halted.\\n");
__disable_irq();
while (1)
{
// Flash LED rapidly to indicate error
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_0);
HAL_Delay(100);
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
printf("[ERROR] Assert failed: file %s, line %d\\n", file, (int)line);
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
网硕互联帮助中心




评论前必须登录!
注册