8. 闪存与存储
在嵌入式系统设计中,闪存和存储管理是至关重要的部分。NXP LPC1100 系列单片机基于 ARM Cortex-M0 核心,提供了丰富的闪存和存储资源。本节将详细介绍 LPC1100 系列的闪存与存储管理,包括闪存的结构、存储器映射、编程与擦除操作、存储器保护机制以及使用外部存储器的扩展方法。
8.1 闪存结构
LPC1100 系列单片机的闪存结构设计用于存储程序代码和常量数据。闪存的大小根据具体的型号而有所不同,但通常在 32KB 到 256KB 之间。闪存被组织成多个扇区,每个扇区的大小和数量也因型号而异。例如,LPC1114 型号的闪存被分为 16 个 4KB 的扇区。
8.1.1 闪存扇区
闪存扇区是闪存的基本编程单位。每个扇区可以独立进行擦除和编程操作。在 LPC1100 系列中,闪存扇区的擦除和编程操作通常需要特定的时序和命令序列。扇区擦除操作会将整个扇区的数据清零,而编程操作则将数据写入指定的闪存地址。
8.1.2 闪存编程与擦除
闪存的编程和擦除操作需要遵循特定的步骤。以下是一个简单的示例,展示如何在 LPC1100 系列单片机上进行闪存编程和擦除:
#include "LPC11xx.h"
// 定义闪存起始地址
#define FLASH_START_ADDR 0x00000000
// 定义闪存扇区大小
#define FLASH_SECTOR_SIZE 512
// 定义要编程的数据
uint32_t data_to_program[] = {0x12345678, 0x9ABCDEF0};
// 闪存编程函数
void flash_program(uint32_t *addr, uint32_t *data, uint32_t len) {
// 检查地址是否对齐
if ((uint32_t)addr % 4 != 0) {
// 地址未对齐
return;
}
// 检查数据长度是否为 4 的倍数
if (len % 4 != 0) {
// 数据长度未对齐
return;
}
// 启用闪存编程模式
LPC_FLASH->FLMCR = 1;
// 编程数据
for (uint32_t i = 0; i < len; i += 4) {
*addr = *data;
addr++;
data++;
}
// 禁用闪存编程模式
LPC_FLASH->FLMCR = 0;
}
// 闪存扇区擦除函数
void flash_erase_sector(uint32_t sector_addr) {
// 检查地址是否对齐
if (sector_addr % FLASH_SECTOR_SIZE != 0) {
// 地址未对齐
return;
}
// 启用闪存编程模式
LPC_FLASH->FLMCR = 1;
// 擦除扇区
LPC_FLASH->FLAR = (1 << 24) | (sector_addr & 0x000FFFFF);
// 等待擦除完成
while (LPC_FLASH->FLSR & (1 << 0));
// 禁用闪存编程模式
LPC_FLASH->FLMCR = 0;
}
int main() {
// 擦除闪存第 0 扇区
flash_erase_sector(FLASH_START_ADDR);
// 编程闪存第 0 扇区
flash_program((uint32_t *)FLASH_START_ADDR, data_to_program, sizeof(data_to_program));
// 读取编程后的数据
uint32_t read_data[2];
for (uint32_t i = 0; i < 2; i++) {
read_data[i] = ((uint32_t *)FLASH_START_ADDR)[i];
}
// 检查编程是否成功
if (read_data[0] == data_to_program[0] && read_data[1] == data_to_program[1]) {
// 编程成功
} else {
// 编程失败
}
while (1) {
// 主循环
}
}
8.1.3 闪存保护
为了防止意外写入或擦除闪存,LPC1100 系列提供了闪存保护机制。可以通过配置闪存保护寄存器(FLPWP)来启用或禁用保护。以下是一个示例,展示如何配置闪存保护:
#include "LPC11xx.h"
// 启用闪存保护
void enable_flash_protection() {
// 启用闪存编程模式
LPC_FLASH->FLMCR = 1;
// 启用保护
LPC_FLASH->FLPWP = 1;
// 禁用闪存编程模式
LPC_FLASH->FLMCR = 0;
}
// 禁用闪存保护
void disable_flash_protection() {
// 启用闪存编程模式
LPC_FLASH->FLMCR = 1;
// 禁用保护
LPC_FLASH->FLPWP = 0;
// 禁用闪存编程模式
LPC_FLASH->FLMCR = 0;
}
int main() {
// 禁用闪存保护
disable_flash_protection();
// 擦除闪存第 0 扇区
flash_erase_sector(FLASH_START_ADDR);
// 编程闪存第 0 扇区
flash_program((uint32_t *)FLASH_START_ADDR, data_to_program, sizeof(data_to_program));
// 启用闪存保护
enable_flash_protection();
while (1) {
// 主循环
}
}
8.2 存储器映射
LPC1100 系列单片机的存储器映射包括闪存、SRAM、外设寄存器等。以下是一个典型的存储器映射表,展示了各个区域的地址范围:
存储器区域 | 地址范围 | 大小 |
---|---|---|
闪存 | 0x00000000 - 0x0003FFFF | 256KB |
SRAM | 0x10000000 - 0x10007FFF | 32KB |
IO 外设 | 0x40000000 - 0x5FFFFFFF | 256MB |
8.2.1 SRAM
SRAM 是单片机的内部随机存取存储器,用于存储运行时的数据。LPC1100 系列的 SRAM 大小通常为 8KB 到 32KB,具体取决于型号。SRAM 的地址范围从 0x10000000 开始。
8.2.2 外部存储器扩展
LPC1100 系列单片机可以通过外部存储器接口(如 SPI、I2C 或 UART)扩展存储器。以下是一个使用 SPI 扩展外部闪存的示例:
#include "LPC11xx.h"
#include "spi.h"
// 定义外部闪存的 SPI 配置
#define SPI_MOSI 0
#define SPI_MISO 1
#define SPI_SCK 2
#define SPI_CS 3
// 初始化 SPI 接口
void spi_init() {
// 配置 SPI 引脚
LPC_SCU->SFSPINSEL[SPI_MOSI] = (LPC_SCU->SFSPINSEL[SPI_MOSI] & 0xFFFF0FFF) | 0x0000A000;
LPC_SCU->SFSPINSEL[SPI_MISO] = (LPC_SCU->SFSPINSEL[SPI_MISO] & 0xFFFF0FFF) | 0x0000A000;
LPC_SCU->SFSPINSEL[SPI_SCK] = (LPC_SCU->SFSPINSEL[SPI_SCK] & 0xFFFF0FFF) | 0x0000A000;
LPC_SCU->SFSPINSEL[SPI_CS] = (LPC_SCU->SFSPINSEL[SPI_CS] & 0xFFFF0FFF) | 0x0000A000;
// 初始化 SPI 控制器
LPC_SPI->CR = 0x00000002; // 使能 SPI
LPC_SPI->DIV = 0x00000004; // 设置时钟分频
LPC_SPI->TCR = 0x00000001; // 设置数据传输模式
}
// 读取外部闪存数据
uint8_t spi_read(uint8_t addr) {
// 选择外部闪存
LPC_GPIO1->FIOCLR = (1 << SPI_CS);
// 发送读取命令
uint8_t command = 0x03;
LPC_SPI->DR = command;
// 等待传输完成
while (!(LPC_SPI->SR & (1 << 5)));
// 发送地址
LPC_SPI->DR = addr;
while (!(LPC_SPI->SR & (1 << 5)));
// 读取数据
uint8_t data = LPC_SPI->DR;
while (!(LPC_SPI->SR & (1 << 5)));
// 取消选择外部闪存
LPC_GPIO1->FIOSET = (1 << SPI_CS);
return data;
}
// 写入外部闪存数据
void spi_write(uint8_t addr, uint8_t data) {
// 选择外部闪存
LPC_GPIO1->FIOCLR = (1 << SPI_CS);
// 发送写入命令
uint8_t command = 0x02;
LPC_SPI->DR = command;
// 等待传输完成
while (!(LPC_SPI->SR & (1 << 5)));
// 发送地址
LPC_SPI->DR = addr;
while (!(LPC_SPI->SR & (1 << 5)));
// 写入数据
LPC_SPI->DR = data;
while (!(LPC_SPI->SR & (1 << 5)));
// 取消选择外部闪存
LPC_GPIO1->FIOSET = (1 << SPI_CS);
}
int main() {
// 初始化 SPI 接口
spi_init();
// 写入数据到外部闪存
spi_write(0x00, 0x55);
// 读取外部闪存数据
uint8_t read_data = spi_read(0x00);
while (1) {
// 主循环
}
}
8.3 存储器保护机制
LPC1100 系列单片机提供了多种存储器保护机制,以确保系统的安全性和可靠性。这些保护机制包括内存保护单元(MPU)、闪存保护寄存器(FLPWP)和外部存储器保护寄存器(EMC)。
8.3.1 内存保护单元(MPU)
内存保护单元(MPU)可以配置为保护特定的内存区域,防止非法访问。以下是一个配置 MPU 的示例:
#include "LPC11xx.h"
// 配置 MPU 保护区域
void configure_mpu(uint32_t base_addr, uint32_t size, uint32_t access_perms) {
// 启用 MPU
LPC_MPU->CTRL = 0x00000001;
// 配置 MPU 区域
LPC_MPU->RNR = 0; // 区域编号
LPC_MPU->RBAR = (base_addr & 0xFFFF8000) | 0x00000001; // 基地址
LPC_MPU->RASR = (size & 0x00000FFE) | access_perms; // 区域大小和访问权限
// 使能 MPU
LPC_MPU->CTRL = 0x00000001;
}
int main() {
// 配置 MPU 保护 SRAM 区域,只允许读写访问
configure_mpu(0x10000000, 0x00008000, 0x00000003);
while (1) {
// 主循环
}
}
8.3.2 外部存储器保护寄存器(EMC)
外部存储器保护寄存器(EMC)可以用于保护外部存储器的访问。以下是一个配置 EMC 的示例:
#include "LPC11xx.h"
// 配置外部存储器保护
void configure_emc_protection(uint32_t base_addr, uint32_t size, uint32_t access_perms) {
// 配置 EMC 保护区域
LPC_EMC->PROTECTION[0] = (base_addr & 0xFFFF0000) | (size & 0x000000FF) | (access_perms & 0x00000003);
// 使能 EMC 保护
LPC_EMC->PROTECTION[1] = 1;
}
int main() {
// 配置 EMC 保护外部闪存区域,只允许读写访问
configure_emc_protection(0x40000000, 0x00008000, 0x00000003);
while (1) {
// 主循环
}
}
8.4 存储器管理最佳实践
在嵌入式系统中,合理管理存储器资源是至关重要的。以下是一些存储器管理的最佳实践:
8.4.1 闪存使用注意事项
- 扇区擦除:每次编程前必须先擦除相应的扇区。
- 数据对齐:确保编程和读取的数据地址和长度对齐。
- 编程顺序:遵循正确的编程时序和命令序列。
- 保护机制:启用闪存保护以防止意外写入或擦除。
8.4.2 SRAM 使用注意事项
- 初始化:在使用 SRAM 之前,确保其已正确初始化。
- 数据缓存:合理使用数据缓存,提高系统性能。
- 堆栈管理:确保堆栈大小和位置合理,避免堆栈溢出。
8.4.3 外部存储器使用注意事项
- 接口配置:正确配置外部存储器接口(如 SPI、I2C、UART)。
- 时序要求:遵循外部存储器的时序要求,确保数据传输的可靠性。
- 电源管理:确保外部存储器的电源管理,避免因电源问题导致数据丢失。
8.5 存储器故障诊断
在嵌入式系统开发过程中,存储器故障是常见的问题。以下是一些常用的存储器故障诊断方法:
8.5.1 闪存故障诊断
- 读写验证:在编程后读取数据,验证是否正确写入。
- 保护检查:检查闪存保护寄存器的配置,确保保护机制正常。
- 擦除检查:在擦除后读取数据,确保扇区已清零。
8.5.2 SRAM 故障诊断
- 初始化检查:确保 SRAM 已正确初始化。
- 数据一致性:检查 SRAM 中的数据一致性,确保没有意外更改。
- 堆栈检查:检查堆栈使用情况,确保没有堆栈溢出。
8.5.3 外部存储器故障诊断
- 通信检查:检查与外部存储器的通信是否正常。
- 数据校验:使用校验和或 CRC 校验外部存储器的数据完整性。
- 电源检查:检查外部存储器的电源是否稳定,确保正常工作。
8.6 存储器性能优化
为了提高嵌入式系统的性能,合理优化存储器的使用是必要的。以下是一些存储器性能优化的方法:
8.6.1 闪存性能优化
- 批量擦除:尽可能批量擦除扇区,减少擦除次数。
- 预编程:在系统启动时预编程常用数据,减少运行时的编程操作。
- 代码优化:优化程序代码,减少闪存的访问次数。
8.6.2 SRAM 性能优化
- 数据缓存:合理使用数据缓存,减少 SRAM 的访问延迟。
- 堆栈管理:优化堆栈管理,避免频繁的堆栈操作。
- 内存分配:合理分配内存,避免内存碎片。
8.6.3 外部存储器性能优化
- 批量传输:尽可能批量传输数据,减少通信次数。
- 数据压缩:使用数据压缩技术,减少外部存储器的存储需求。
- 接口选择:选择合适的外部存储器接口,如 SPI、I2C 或 UART,以满足性能需求。
8.7 存储器应用示例
以下是一些存储器应用的示例,展示如何在实际项目中使用闪存和外部存储器。
8.7.1 闪存日志记录
在嵌入式系统中,日志记录是一个常见的功能。闪存可以用于存储日志数据,以便在系统重启或故障时进行调试。以下是一个使用闪存记录日志的示例:
#include "LPC11xx.h"
// 定义日志存储区域的起始地址和大小
#define LOG_START_ADDR 0x0003F000
#define LOG_SIZE 1024
// 定义日志条目结构
typedef struct {
uint32_t timestamp;
char message[64];
} LogEntry;
// 日志条目计数器
uint32_t log_counter = 0;
// 闪存编程函数
void flash_program(uint32_t *addr, uint32_t *data, uint32_t len) {
// 检查地址是否对齐
if ((uint32_t)addr % 4 != 0) {
// 地址未对齐
return;
}
// 检查数据长度是否为 4 的倍数
if (len % 4 != 0) {
// 数据长度未对齐
return;
}
// 启用闪存编程模式
LPC_FLASH->FLMCR = 1;
// 编程数据
for (uint32_t i = 0; i < len; i += 4) {
*addr = *data;
addr++;
data++;
}
// 禁用闪存编程模式
LPC_FLASH->FLMCR = 0;
}
// 闪存扇区擦除函数
void flash_erase_sector(uint32_t sector_addr) {
// 检查地址是否对齐
if (sector_addr % 1024 != 0) {
// 地址未对齐
return;
}
// 启用闪存编程模式
LPC_FLASH->FLMCR = 1;
// 擦除扇区
LPC_FLASH->FLAR = (1 << 24) | (sector_addr & 0x000FFFFF);
// 等待擦除完成
while (LPC_FLASH->FLSR & (1 << 0));
// 禁用闪存编程模式
LPC_FLASH->FLMCR = 0;
}
// 记录日志到闪存
void log_to_flash(LogEntry *log_entry) {
// 计算日志存储的地址
uint32_t log_addr = LOG_START_ADDR + log_counter * sizeof(LogEntry);
// 检查是否需要擦除扇区
if (log_addr % 1024 == 0) {
flash_erase_sector(log_addr);
}
// 编程日志数据
flash_program((uint32_t *)log_addr, (uint32_t *)log_entry, sizeof(LogEntry));
// 增加日志计数器
log_counter++;
}
int main() {
// 初始化日志条目
LogEntry log_entry = {
.timestamp = 123456789,
.message = "System started"
};
// 记录日志
log_to_flash(&log_entry);
while (1) {
// 主循环
}
}
8.7.2 外部存储器数据存储
外部存储器可以用于存储大量数据,例如传感器数据、历史记录等。以下是一个使用 SPI 扩展外部闪存存储数据的示例:
#include "LPC11xx.h"
#include "spi.h"
// 定义外部闪存的 SPI 配置
#define SPI_MOSI 0
#define SPI_MISO 1
#define SPI_SCK 2
#define SPI_CS 3
// 初始化 SPI 接口
void spi_init() {
// 配置 SPI 引脚
LPC_SCU->SFSPINSEL[SPI_MOSI] = (LPC_SCU->SFSPINSEL[SPI_MOSI] & 0xFFFF0FFF) | 0x0000A000;
LPC_SCU->SFSPINSEL[SPI_MISO] = (LPC_SCU->SFSPINSEL[SPI_MISO] & 0xFFFF0FFF) | 0x0000A000;
LPC_SCU->SFSPINSEL[SPI_SCK] = (LPC_SCU->SFSPINSEL[SPI_SCK] & 0xFFFF0FFF) | 0x0000A000;
LPC_SCU->SFSPINSEL[SPI_CS] = (LPC_SCU->SFSPINSEL[SPI_CS] & 0xFFFF0FFF) | 0x0000A000;
// 初始化 SPI 控制器
LPC_SPI->CR = 0x00000002; // 使能 SPI
LPC_SPI->DIV = 0x00000004; // 设置时钟分频
LPC_SPI->TCR = 0x00000001; // 设置数据传输模式
}
// 读取外部闪存数据
uint8_t spi_read(uint8_t addr) {
// 选择外部闪存
LPC_GPIO1->FIOCLR = (1 << SPI_CS);
// 发送读取命令
uint8_t command = 0x03;
LPC_SPI->DR = command;
// 等待传输完成
while (!(LPC_SPI->SR & (1 << 5)));
// 发送地址
LPC_SPI->DR = addr;
while (!(LPC_SPI->SR & (1 << 5)));
// 读取数据
uint8_t data = LPC_SPI->DR;
while (!(LPC_SPI->SR & (1 << 5)));
// 取消选择外部闪存
LPC_GPIO1->FIOSET = (1 << SPI_CS);
return data;
}
// 写入外部闪存数据
void spi_write(uint8_t addr, uint8_t data) {
// 选择外部闪存
LPC_GPIO1->FIOCLR = (1 << SPI_CS);
// 发送写入命令
uint8_t command = 0x02;
LPC_SPI->DR = command;
// 等待传输完成
while (!(LPC_SPI->SR & (1 << 5)));
// 发送地址
LPC_SPI->DR = addr;
while (!(LPC_SPI->SR & (1 << 5)));
// 写入数据
LPC_SPI->DR = data;
while (!(LPC_SPI->SR & (1 << 5)));
// 取消选择外部闪存
LPC_GPIO1->FIOSET = (1 << SPI_CS);
}
// 记录传感器数据到外部存储器
void log_sensor_data(uint8_t addr, uint16_t data) {
// 写入数据
spi_write(addr, (uint8_t)(data >> 8));
spi_write(addr + 1, (uint8_t)(data & 0xFF));
}
int main() {
// 初始化 SPI 接口
spi_init();
// 记录传感器数据
log_sensor_data(0x00, 0x1234);
while (1) {
// 主循环
}
}
8.7.3 存储器应用总结
通过上述示例,我们可以看到闪存和外部存储器在嵌入式系统中的重要应用。闪存主要用于存储程序代码和关键数据,而外部存储器则可以扩展存储容量,存储大量数据。合理使用这些存储资源,可以提高系统的性能和可靠性。
8.8 存储器管理总结
在嵌入式系统设计中,闪存和存储管理是至关重要的部分。LPC1100 系列单片机提供了丰富的闪存和存储资源,通过合理的配置和使用,可以确保系统的高效运行和数据的安全。以下是一些关键点总结:
- 闪存结构:了解闪存的扇区结构和大小,合理规划存储空间。
- 闪存编程与擦除:遵循正确的时序和命令序列进行编程和擦除操作。
- 闪存保护:启用保护机制,防止意外写入或擦除。
- 存储器映射:了解存储器映射表,合理使用各个存储区域。
- 外部存储器扩展:通过外部接口扩展存储容量,满足数据存储需求。
- 存储器保护机制:使用 MPU 和 EMC 等保护机制,确保系统的安全性和可靠性。
- 存储器最佳实践:注意闪存、SRAM 和外部存储器的使用注意事项,优化存储器性能。
- 存储器故障诊断:掌握常用的存储器故障诊断方法,确保系统的正常运行。
通过这些方法和技巧,可以有效地管理和优化 LPC1100 系列单片机的存储资源,提高系统的整体性能和可靠性。