Flash与OTP读写指南*
第一章 OTP区域功能特性*
OTP区域为One Time Program区域,即一次性编程区域,只能写一次,用于存储标志性信息(chipid/chipname等)以及个性化配置,写之后该信息无法被修改。
用户也可以使用这个区域,做一些产品数据的一次性烧录,比如:产品的名称,SN号,加解密KEY等。
1.1 功能特性*
-
读写OTP区域,OTP区域总共32个字节
-
lock OTP 区域,lock后只能读不能写
-
默认值都是0,只能由0写为1
-
用户使用的区域,只有9字节大小,由地址22开始,到地址30结束
第二章 OTP区域读写操作*
2.1 API接口说明*
#ifndef __GX_EFUSE_H__
#define __GX_EFUSE_H__
/**
* @brief 初始化函数
*
*/
void gx_otp_init(void);
/**
* @brief 反初始化函数
*/
void gx_otp_exit(void);
/**
* @brief 硬件复位函数, 烧写数据后需要复位后生效。
*/
void gx_otp_reset(void);
/**
* @brief otp 读取函数
*
* @param addr 读取地址
* @param data 存储读取后数据
* @param size 读取数据的长度
* @return int 是否成功
* @retval 0 成功
* @retval -1 失败
*/
int gx_otp_read(unsigned int addr, char *data, int size);
/**
* @brief otp 烧写函数
*
* @param start_addr 烧写地址
* @param data 烧写数据
* @param size 烧写数据的长度
* @return int 是否成功
* @retval 0 成功
* @retval -1 失败
*/
int gx_otp_write(unsigned int addr, char *data, int size);
/**
* @brief otp 读取publicid函数
*
* @param public_id 存储读取后数据
* @return int 是否成功
* @retval 0 成功
* @retval -1 失败
*/
int otp_get_publicid(unsigned long long *public_id);
/**
* @brief otp section lock 函数, lock 后无法继续烧写
*
* @param section 需要 lock 的 section
* @return int 是否成功
* @retval 0 成功
* @retval -1 失败
*/
int otp_lock(int section);
/**
* @brief otp 获取 section lock 信息函数
*
* @param section 获取 section 的 lock 信息
* @return int
* @retval 1 section is locked
* @retval 0 section is not locked
* @retval -1 失败
*/
int otp_get_lock(int section);
#endif
2.2 读写例子*
读chipid
unsigned long long chip_id = 0;
gx_otp_init();
otp_get_publicid(&chip_id);
printf("chip_id = %llu\n", chip_id);
读指定OTP区域数据
unsigned char val;
gx_otp_init();
gx_otp_read(19, (char*)&val, 1);
写指定OTP区域
unsigned char val = 0x12;
gx_otp_init();
gx_otp_write(22, (char*)&val, 1); // 地址范围 22 - 30
Lock OTP 区域
一但lock后,OTP区域再也无法写数据了
用户开发,只需要关注 OTP_LOCK_USER_ADDRESS 区
gx_otp_init();
otp_lock(1); // 0:OTP_LOCK_DIG_AO_ADDRESS 1:OTP_LOCK_USER_ADDRESS
第三章 Flash区域读写操作*
3.1 功能特性*
各芯片的flash情况:
芯片型号 | FLASH |
---|---|
GX8301A | 内置 512KB |
GX8301B | 内置 512KB |
GX8302A | 内置 1MB |
GX8302B | 需要外挂 |
SPI NOR FLASH 是一种常用的非易失性存储器,常用于嵌入式系统和存储设备中。它通过 SPI 接口与主控制器通信,提供了快速的数据读写和可靠的存储性能。
芯片仅支持SPI NOR Flash。
需要更大的存储,可以使用SDIO支持的SD卡。
支持特性:
- 支持 SPI 接口标准,FLASH 通过 SPI 接口与主控交互
- 支持 FLASH 读、写基本功能,支持 FLASH 单倍速、双倍速、四倍速模式
- 支持 FLASH XIP (片上启动) 功能,可使应用程序直接在 FLASH 闪存内运行
- 支持 通过 DMA 对 FLASH 进行读写操作
- 支持 FLASH 写保护
- 支持只读Cache。读操作,当发现 Flash cache 命中时,直接从缓存中返回总线数据,提高读的系统性能
3.2 API接口说明*
/*
* NationalChip Apus Project
* Copyright (C) 2001-2023 NationalChip Co., Ltd
* ALL RIGHTS RESERVED!
*
* gx_spi_nor_flash.h: spi flash driver header file
*
*/
#ifndef __GX_SPI_NOR_FLASH_H__
#define __GX_SPI_NOR_FLASH_H__
#include <gx_spi_nor_flash.h>
int gx_nor_init(void);
int gx_nor_xip_init(int xip_mode, uint8_t ddr_en);
uint32_t gx_nor_xip_read(uint32_t flash_addr, void *data, uint32_t len);
uint32_t gx_nor_read(GX_SPINOR_RW_T *r);
uint32_t gx_nor_write(GX_SPINOR_RW_T *w);
int gx_nor_erase_data(unsigned int addr, unsigned int len);
int gx_nor_erase_chip(void);
int gx_nor_get_info(GX_SPINOR_INFO_TYPE_E flash_info);
void gx_nor_set_div_and_sample_delay(uint32_t div, uint32_t sample_delay);
int gx_nor_write_protect_status(unsigned int *len);
int gx_nor_write_protect_lock(unsigned int protect_len);
int gx_nor_write_protect_unlock(void);
int gx_nor_write_protect_mode(void);
int gx_nor_get_uid(unsigned char *buf, unsigned int buf_len, unsigned int *ret_len);
int gx_nor_sync(void);
void gx_nor_set_div_and_sample_delay(uint32_t div, uint32_t sample_delay);
#endif
3.3 获取flash参数信息*
可以获取flash的ID,名称和大小等信息,可以参考下面参数说明。
/**
* @brief flash信息类型
*/
typedef enum {
GX_SPINOR_CHIP_TYPE, /*!< flash 类型,片上flash默认为nor flash */
GX_SPINOR_CHIP_NAME, /*!< flash 型号名称 */
GX_SPINOR_CHIP_ID, /*!< flash 芯片ID */
GX_SPINOR_CHIP_SIZE, /*!< flash 容量 */
GX_SPINOR_BLOCK_SIZE, /*!< flash block大小 */
GX_SPINOR_BLOCK_NUM, /*!< flash block数量 */
GX_SPINOR_SECTOR_SIZE, /*!< flash sector大小 */
GX_SPINOR_SECTOR_NUM, /*!< flash sector数量 */
GX_SPINOR_PAGE_SIZE, /*!< flash page大小 */
GX_SPINOR_PAGE_NUM, /*!< flash page数量 */
GX_SPINOR_ERASE_SIZE, /*!< flash 擦除大小 */
GX_SPINOR_ERASE_NUM, /*!< flash 擦除数量 */
GX_SPINOR_PROTECT_MODE, /*!< flash 写保护方向,TOP or BOTTOM */
GX_SPINOR_MATCH_MODE, /*!< flash 匹配模式 */
}GX_SPINOR_INFO_TYPE_E;
int ret = 0;
unsigned int flash_size;
flash_size = gx_nor_get_info(GX_SPINOR_CHIP_SIZE);
printf("id: %x, name: %s\r\n", ret == 0 ? gx_nor_get_info(GX_SPINOR_CHIP_ID) : ret, gx_nor_get_info(GX_SPINOR_CHIP_NAME));
unsigned int real_len = 0;
uint8_t buffer[128];
memset(buffer, 0x00, 128);
ret = gx_nor_get_uid(buffer, 128, &real_len);
3.4 写flash例子*
写数据流程:
- step 1:解除写保护
- step 2:进行Erase擦除操作
- step 3:写入数据
- step 4:开启写保护
#define FLASH_TEST_START_ADDR (FLASH_KVS_ENV_ADDR)
#define NOR_TEST_MALLOC_TEST_LEN (FLASH_KVS_ENV_SIZE)
unsigned char my_data[4] = {0x10,0x12,0x13,0x14};
unsigned int flash_size;
flash_size = gx_nor_get_info(GX_SPINOR_CHIP_SIZE);
gx_nor_write_protect_unlock();
gx_nor_erase_data(FLASH_TEST_START_ADDR, NOR_TEST_MALLOC_TEST_LEN);
GX_SPINOR_RW_T w = {0};
w.addr = FLASH_TEST_START_ADDR;
w.len = 4;
w.data = my_data;
gx_nor_write(&w);
gx_nor_write_protect_lock(flash_size);
3.5 读flash例子*
unsigned char my_data[4] = {0};
GX_SPINOR_RW_T r = {0};
r.addr = FLASH_TEST_START_ADDR;
r.len = 4;
r.data = my_data;
gx_nor_read(&r);
3.6 XIP方式读flash例子*
使用XIP的方式,可以把flash当做内存来用,但是只能读而不能写。
因此,一些资源文件,比如图片,只读的其它数据,可以通过外部方式烧录到flash指定的地址;或者在代码内,通过宏 ATTR_SECTION_XIP_RODATA 声明数组。
使用 ATTR_SECTION_XIP_RODATA 声明的数组,不会占用内存资源,而只会保存在flash上。但是读取的时候,可以像就保存在内存一样,进行操作。
#define ATTR_SECTION_XIP_RODATA __attribute__((section(".xip_rodata")))
const ATTR_SECTION_XIP_RODATA uint8_t my_data[4] = {0x11,0x22,0x33,0x44};
如果是使用外部烧录的手段,把数据烧写到了flash指定的位置。那么在代码里,可以通过这个位置地址,转为为 XIP的地址,直接使用,而不需要通过flash的 gx_nor_read 接口获取数据。
存储器 | cpu总线地址 | 空间大小 |
---|---|---|
FLASH XIP | 0x1300_0000 - 0x13FF_FFFF | 16MB |
例如:
我们外部把数据保存在flash地址0x10000上的数据,可以在芯片内部,直接xip的方式读出来。
需要加上基地址 #define MCU_FLASH_XIP_BASE 0x13000000
比如把flash地址0x10000上的32字节数据,读取到buffer中,
unsigned char buffer[32] = {0};
memcpy(buffer, 0x10000+MCU_FLASH_XIP_BASE, 32);
这样可以直接把flash看作只读内存来使用,而不需要用flash驱动底层的read接口了。
3.7 KV读写子系统*
使用了开源的 EasyFlash。
KV是个键值数据库 :是一种非关系数据库,它将数据存储为键值(Key-Value)对集合,其中键作为唯一标识符。KVDB 操作简洁,可扩展性强。
API接口:
#ifndef __KVS_H__
#define __KVS_H__
#include <stddef.h>
int kvs_init(void);
int kvs_write(const char *key, const void *buf, size_t size);
int kvs_read(const char *key, void *buf, size_t size);
int kvs_delete(const char *key);
#endif
使用例子:
#include <kvs.h>
#include <osal.h>
static void print_usage(void)
{
printf("kvs_test delete kvsname\n");
printf("kvs_test read kvsname\n");
printf("kvs_test write kvsname value\n");
}
static int _test_kvs_read(int argc, char * const argv[])
{
if (argc != 3) {
print_usage();
return -1;
}
char value[100];
int len = kvs_read(argv[2], value, 100);
if(len < 0) {
printf("read failed\n");
} else {
for(int i = 0 ;i <len; i++) {
printf("%c", value[i]);
}
printf("\n");
}
return len;
}
static int _test_kvs_write(int argc, char * const argv[])
{
if (argc != 4) {
print_usage();
return -1;
}
int len = kvs_write(argv[2], argv[3], strlen(argv[3]));
if(len < 0) {
printf("write failed\n");
}
return len;
}
static int _test_kvs_delete(int argc, char * const argv[])
{
if (argc != 3) {
print_usage();
return -1;
}
int len = kvs_delete(argv[2]);
if(len < 0)
printf("delete failed\n");
return len;
}
static int do_kvs(int argc, char * const argv[])
{
static int init_flag = 0;
if(!init_flag)
kvs_init();
if(argc <2) {
print_usage();
return -1;
}
if(0 == strcmp(argv[1], "read")) {
_test_kvs_read(argc, argv);
}else if(0 == strcmp(argv[1], "write")) {
_test_kvs_write(argc, argv);
}else if(0 == strcmp(argv[1], "delete")) {
_test_kvs_delete(argc, argv);
}else {
print_usage();
}
return 0;
}
COMMAND_EXPORT_ALIAS(do_kvs, kvs_test, "apus kvs test cmd");