015.Nor功能实现|千篇笔记实现嵌入式全栈/裸机篇
⚠️裸机仓库https://gitee.com/simonchina_carel_li/mini2440-bare-metal.git⚠️Tag:15-nor1. 要实现什么功能读id扇区擦除全片擦除字读写CFI信息查询2. AI分析数据手册以我手上的这颗Nor芯片为例型号为S29AL016J把数据手册扔给AI让它总结跟编程有关的主要信息类别关键信息存储架构支持字节x8/ 半字x16模式由 BYTE# 引脚控制采用扇区擦除架构扇区划分两种启动配置- 顶启动Top Boot1 个 16KB 2 个 8KB 1 个 32KB 31 个 64KB 扇区- 底启动Bottom Boot1 个 16KB 2 个 8KB 1 个 32KB 31 个 64KB 扇区地址起始不同关键时序字节 / 半字编程时间 Typ. 6µs扇区擦除时间 Typ. 0.5s芯片擦除时间 Typ. 16s可靠性单扇区擦写寿命 100 万次数据保留期 20 年支持 1bit/512Byte ECC 适配操作状态判断软件通过连续读取DQ6是否停止反转停止就空闲了操作结果操作完成后通过DQ5判断是否成功置位表示失败CFI支持√命令集AMD命令集(0x0002)3. 实现3.1 要实现哪些功能读设备IDvoid nor_read_id(uint16_t *pManID, uint16_t *pDevID);扇区擦除bool nor_erase_sector(uint32_t addr);全片擦除bool nor_erase_chip(void);字写入bool nor_write_word(uint32_t addr, uint16_t data)字读取uint16_t nor_read_word(uint32_t addr)CFI信息读取bool nor_read_cfi(nor_cfi_info_t *info)连续数据快速写入bool nor_write_buffer_fast(uint32_t start_addr, const uint16_t *buf, uint32_t word_count);3.2 具体实现参考和具体的细节见注释新建文件common/nor_flash.c,#include s3c2440a.h #define K9F2G08x_ENABLE 1 // S3C2440 BANK0 基地址 #define NOR_FLASH_BASE 0x00000000 // 将基地址转换为 16-bit / 8-bit 的 volatile 指针 #define NOR_PTR ((volatile uint16_t *)NOR_FLASH_BASE) // #define NOR_PTR8 ((volatile uint8_t *)NOR_FLASH_BASE) // 内部函数向 NOR Flash 的特定逻辑偏移写入命令以 16-bit word 为单位 static void nor_write_cmd(uint32_t word_offset, uint16_t cmd) { // 关键点当 word_offset0x555 时实际物理地址是 0x00000000 (0x555 * 2) 0x0AAA NOR_PTR[word_offset] cmd; } // 内部函数读取 NOR Flash 的特定逻辑偏移以 16-bit word 为单位 static uint16_t nor_read_data(uint32_t word_offset) { return NOR_PTR[word_offset]; } // --------------------------------------------------------- // 基于 Word 地址读取 CFI 字节 // --------------------------------------------------------- static uint8_t nor_cfi_read_byte(uint32_t word_offset) { // 读取 16-bit 数据只取低 8 位 return (uint8_t)(nor_read_data(word_offset) 0x00FF); } // --------------------------------------------------------- // 跨 Word 组装 16 位 CFI 数据 // --------------------------------------------------------- static uint16_t nor_cfi_read_le16(uint32_t word_offset) { // 下一个 CFI 字节在下一个 Word 地址里 uint8_t lo nor_cfi_read_byte(word_offset); uint8_t hi nor_cfi_read_byte(word_offset 1); return (uint16_t)(lo | ((uint16_t)hi 8)); } // 内部函数等待操作完成 (利用 DQ6 Toggle Bit 机制) static bool nor_wait_ready(uint32_t offset) { uint16_t val1, val2; while (1) { val1 nor_read_data(offset); val2 nor_read_data(offset); // DQ6 (bit 6) 在编程或擦除期间会不断翻转 // 如果两次读取的 DQ6 一致说明操作已经完成 if ((val1 0x0040) (val2 0x0040)) { return true; } // DQ5 (bit 5) 是错误标志位。如果为 1说明操作超时或失败 if (val1 0x0020) { // 发生错误后需要最后再检查一次 DQ6以防它在置位DQ5的瞬间刚好完成 val1 nor_read_data(offset); val2 nor_read_data(offset); if ((val1 0x0040) (val2 0x0040)) { return true; } return false; // 确实失败了 } } } static void nor_reset(void) { // 复位命令向任意地址写入 0xF0 nor_write_cmd(0x000, 0x00F0); } void nor_init(void) { nor_reset(); } bool nor_read_cfi(nor_cfi_info_t *info) { if (info NULL) { return false; } // 进入 CFI Query 模式 (参考 AMD/Spansion CFI 进入序列) nor_write_cmd(0x55, 0x0098); // CFI 规范在 word offset 0x10 处应为 Q, 0x11 为 R, 0x12 为 Y info-qry[0] (char)nor_cfi_read_byte(0x10); info-qry[1] (char)nor_cfi_read_byte(0x11); info-qry[2] (char)nor_cfi_read_byte(0x12); if (info-qry[0] ! Q || info-qry[1] ! R || info-qry[2] ! Y) { nor_reset(); return false; } // 主命令集 ID / 扩展表地址等这里只用主命令集 ID 作简单展示 info-primary_cmdset nor_cfi_read_le16(0x13u); // 设备容量Device Size 2^N bytes, N 在 offset 0x27 info-device_size_exp nor_cfi_read_byte(0x27u); if (info-device_size_exp 8u || info-device_size_exp 31u) { // 明显异常避免移位溢出 info-device_size_bytes 0; } else { info-device_size_bytes (uint32_t)1u info-device_size_exp; } // 擦除区域数量 (offset 0x2C) info-num_erase_regions nor_cfi_read_byte(0x2Cu); if (info-num_erase_regions NOR_CFI_MAX_ERASE_REGIONS) { info-num_erase_regions NOR_CFI_MAX_ERASE_REGIONS; } // 每个擦除区域 4 字节描述 (NumBlocks-1)[15:0], BlockSizeInBytes (value * 256) // 起始 word offset 0x2D for (uint8_t i 0; i info-num_erase_regions; i) { uint32_t base (0x2Du (uint32_t)i * 4u); // 转为字节地址 uint16_t raw_block_cnt nor_cfi_read_le16(base 0); uint16_t raw_block_size nor_cfi_read_le16(base 2); uint32_t block_cnt (uint32_t)raw_block_cnt 1u; uint32_t block_size ((uint32_t)raw_block_size) * 256u; info-erase_region[i].block_count block_cnt; info-erase_region[i].block_size block_size; } // 退出 CFI 模式恢复正常读取 nor_reset(); return true; } void nor_read_id(uint16_t *pManID, uint16_t *pDevID) { // 进入自动选择模式 (Autoselect Command Sequence) nor_write_cmd(0x555, 0x00AA); nor_write_cmd(0x2AA, 0x0055); nor_write_cmd(0x555, 0x0090); // 读取 Manufacturer ID (偏移 0x00) *pManID nor_read_data(0x000); // 读取 Device ID (偏移 0x01) *pDevID nor_read_data(0x001); // 退出自动选择模式恢复正常读取 nor_reset(); } bool nor_erase_sector(uint32_t addr) { // 将物理字节地址转换为 16-bit 数组的索引 uint32_t offset addr 1; // 扇区擦除命令序列 (6个总线周期) nor_write_cmd(0x555, 0x00AA); nor_write_cmd(0x2AA, 0x0055); nor_write_cmd(0x555, 0x0080); nor_write_cmd(0x555, 0x00AA); nor_write_cmd(0x2AA, 0x0055); nor_write_cmd(offset, 0x0030); // 最后一个命令写入目标扇区地址 // 等待擦除完成 (擦除通常需要几百毫秒) bool ret nor_wait_ready(offset); nor_reset(); return ret; } /** * brief 擦除整个 NOR Flash 芯片 (Chip Erase) * return true: 擦除成功; false: 擦除失败或超时 * note 警告全片擦除极其耗时通常需要几秒到几十秒不等。 * 并且会抹除 NOR Flash 中的 Bootloader如 Supervivi/U-boot * 在裸机调试时请谨慎调用 */ bool nor_erase_chip(void) { // 发送 6 个周期的全片擦除命令序列 nor_write_cmd(0x555, 0x00AA); // 周期 1: 解锁 nor_write_cmd(0x2AA, 0x0055); // 周期 2: 解锁 nor_write_cmd(0x555, 0x0080); // 周期 3: 建立擦除命令 nor_write_cmd(0x555, 0x00AA); // 周期 4: 解锁 nor_write_cmd(0x2AA, 0x0055); // 周期 5: 解锁 nor_write_cmd(0x555, 0x0010); // 周期 6: 发送全片擦除命令 (0x10) // 等待擦除完成 // 在全片擦除期间读取任意地址的 DQ6 都会翻转所以这里传入偏移 0 即可 bool ret nor_wait_ready(0); nor_reset(); return ret; } bool nor_write_word(uint32_t addr, uint16_t data) { uint32_t offset addr 1; // 半字编程命令序列 (4个总线周期) nor_write_cmd(0x555, 0x00AA); nor_write_cmd(0x2AA, 0x0055); nor_write_cmd(0x555, 0x00A0); nor_write_cmd(offset, data); // 写入真实数据到目标地址 // 等待烧写完成 bool ret nor_wait_ready(offset); nor_reset(); return ret; } uint16_t nor_read_word(uint32_t addr) { // XIP特性读取就像访问普通内存一样 return NOR_PTR[addr 1]; } /** * brief 进入解锁绕过模式 (Unlock Bypass Entry) */ static void nor_unlock_bypass_enter(void) { nor_write_cmd(0x555, 0x00AA); nor_write_cmd(0x2AA, 0x0055); nor_write_cmd(0x555, 0x0020); // 0x20: 进入 Unlock Bypass 模式 } /** * brief 退出解锁绕过模式 (Unlock Bypass Reset / Exit) */ static void nor_unlock_bypass_exit(void) { // 在绕过模式下向任意地址写入 0x90再写入 0x00 即可退出 nor_write_cmd(0x000, 0x0090); nor_write_cmd(0x000, 0x0000); } /** * brief 在解锁绕过模式下写入一个半字 * note 调用此函数前必须已经调用过 nor_unlock_bypass_enter() */ static bool nor_unlock_bypass_program_word(uint32_t offset, uint16_t data) { // 在绕过模式下只需 2 个周期 nor_write_cmd(0x000, 0x00A0); // 发送编程命令到任意地址 nor_write_cmd(offset, data); // 写入真实数据到目标偏移 // 依然需要等待数据烧录完成 return nor_wait_ready(offset); } /** * brief 快速连续编程 (批量烧写一段内存到 NOR Flash) * param start_addr: NOR Flash 中的目标物理起始地址 (必须偶数对齐) * param buf: 内存中准备写入的数据缓冲区指针 * param word_count: 准备写入的 16-bit 半字总数 * return true: 成功; false: 失败 */ bool nor_write_buffer_fast(uint32_t start_addr, const uint16_t *buf, uint32_t word_count) { uint32_t offset start_addr 1; // 转换为 Word 偏移 bool ret true; // 1. 进入快速编程模式 nor_unlock_bypass_enter(); // 2. 循环快速写入每一个半字 for (uint32_t i 0; i word_count; i) { if (!nor_unlock_bypass_program_word(offset i, buf[i])) { ret false; break; // 一旦出错立刻终止烧写 } } // 3. 退出快速编程模式 (极其重要否则无法正常读取和执行代码) nor_unlock_bypass_exit(); // 为了安全起见最后再发一次全局软复位确保彻底回到读取模式 nor_reset(); return ret; }3.3 测试程序实现新建文件nor/main.c,主要步骤是读id、CFI信息擦除验证、写入验证、连续快写验证等#include s3c2440a.h int main() { // 必须关闭中断因为在操作NOR Flash时会产生中断导致数据错误 irq_enable(false); uint16_t man_id, dev_id; uint32_t test_addr 0x00100000; // 选取 1MB 偏移处作为测试点 nor_cfi_info_t cfi; uart0_printf(--- NOR Flash Test ---\n); nor_init(); // 1. 读取 ID nor_read_id(man_id, dev_id); uart0_printf(Manufacturer ID: 0x%x\n, man_id); uart0_printf(Device ID: 0x%x\n, dev_id); // 1.1 读取 CFI 信息 if (nor_read_cfi(cfi)) { uart0_printf(\n\nCFI QRY: %c%c%c\n, cfi.qry[0], cfi.qry[1], cfi.qry[2]); uart0_printf(CFI Primary CmdSet: 0x%x\n, cfi.primary_cmdset); uart0_printf(CFI Device Size: 2^%d %d bytes\n, cfi.device_size_exp, cfi.device_size_bytes); uart0_printf(CFI Erase Regions: %d\n, cfi.num_erase_regions); for (uint8_t i 0; i cfi.num_erase_regions; i) { uart0_printf( Region %d: %d blocks, block size %d bytes\n, i, (unsigned)cfi.erase_region[i].block_count, (unsigned)cfi.erase_region[i].block_size); } } else { uart0_printf(CFI read FAILED (no QRY)\n); } // 2. 擦除扇区 uart0_printf(\n\nErasing sector at 0x%x...\n, test_addr); if (nor_erase_sector(test_addr)) { uart0_printf(Erase SUCCESS!\n); } else { uart0_printf(Erase FAILED!\n); return -1; } // 3. 验证擦除结果 (擦除后全为 0xFFFF) uint16_t val nor_read_word(test_addr); uart0_printf(Data after erase: 0x%x\n, val); // 4. 写入数据 uart0_printf(Writing 0x55AA to 0x%x...\n, test_addr); if (nor_write_word(test_addr, 0x55AA)) { uart0_printf(Write SUCCESS!\n); } else { uart0_printf(Write FAILED!\n); return -1; } // 5. 验证写入结果 val nor_read_word(test_addr); if (val ! 0x55AA) { uart0_printf(Data after write is not 0x55AA! FAIL\n); return -1; } else { uart0_printf(Data after write is 0x55AA! PASS\n); } // 6. 擦除整个芯片 uart0_printf(\n\nErasing chip...\n); if (nor_erase_chip()) { uart0_printf(Erase SUCCESS!\n); } else { uart0_printf(Erase FAILED!\n); return -1; } // 7. 验证擦除结果 (擦除后全为 0xFFFF) val nor_read_word(test_addr); uart0_printf(Data after erase: 0x%x\n, val); // 8. 使用快速编程写入连续数据并验证 #define DATA_SIZE 65535 uint16_t data[DATA_SIZE]; for (uint16_t i 0; i DATA_SIZE; i) { data[i] i; } if (nor_write_buffer_fast(test_addr, data, DATA_SIZE)) { uart0_printf(Write SUCCESS!\n); } else { uart0_printf(Write FAILED!\n); return -1; } // 9. 验证写入结果 for (uint16_t i 0; i DATA_SIZE; i) { if (nor_read_word(test_addr i * 2) ! data[i]) { uart0_printf(Data after write is not 0x%x! FAIL\n, data[i]); return -1; } } uart0_printf(PASS\n); return 0; }4. 运行编译make nor,烧录运行