迁移公告
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 __OTP_H__
#define __OTP_H__
#include <attr_def.h>
#define OTP_CHIP_NAME_LEN (2)
#define OTP_CHIP_ID_LEN (8)
#define OTP_USER_DATA_MAX_LEN (8)
typedef struct {
unsigned char chip_name[OTP_CHIP_NAME_LEN];
unsigned char sub_name;
unsigned char sub_name_reserved[2];
unsigned char chip_id[OTP_CHIP_ID_LEN];
}ATTR_PACKED otp_chip_info_t;
int otp_chip_info_load(otp_chip_info_t *info);
const char *otp_chip_name_str(const otp_chip_info_t *info);
unsigned int otp_user_data_load(unsigned char *user_data, unsigned int size);
#endif
2.2 读写例子*
读chipid,芯片名称和用户私有的数据
printf("Chip Label: ");
otp_chip_info_t chip_info;
if(otp_chip_info_load(&chip_info) == 0) {
const char *chip_name = otp_chip_name_str(&chip_info);
printf("%s-", chip_name);
for(int i = 0; i < 8; i++) {
printf("%02x", chip_info.chip_id[i]);
}
printf("
");
} else {
printf("Invalid
");
}
读用户私有的数据
#include <string.h>
#include <otp.h>
#include <utils_lib.h>
int chip_otp_signature_verify(const unsigned char *key, unsigned int size)
{
int ret = -1;
otp_chip_info_t chip_info = {0};
if(otp_chip_info_load(&chip_info) == 0) {
const unsigned char *uid = chip_info.chip_id;
unsigned char otp_user[OTP_USER_DATA_MAX_LEN] = {0};
unsigned int otp_user_len = 0;
if((otp_user_len = otp_user_data_load(otp_user, OTP_USER_DATA_MAX_LEN)) > 0) {
unsigned char out_sign_data[OTP_USER_DATA_MAX_LEN] = {0};
unsigned int out_sign_size = 0;
// 用户自定义校验算法,通过chipid计算的校验值是否与otp存储的值一致
ret = user_calc(uid, OTP_CHIP_ID_LEN, key, size, out_sign_data, &out_sign_size);
if(ret == 0) {
ret = memcmp(otp_user, out_sign_data, out_sign_size);
}
}
}
return ret;
}
第三章 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
", 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; // 使用spi单线模式,速度比较慢
w.len = 4;
w.data = my_data;
gx_nor_write(&w);
gx_nor_write_protect_lock(flash_size);
使用qspi的方式,速度更快
static int _dfu_write_flash(unsigned int addr, void *buf, unsigned int size)
{
GX_SPINOR_RW_T wr_cfg;
unsigned int len;
wr_cfg.addr = addr;
wr_cfg.len = size;
wr_cfg.data = (void *)buf;
wr_cfg.bus_mode = GX_SPINOR_SPI_QUAD;
wr_cfg.dma_en = 0;
wr_cfg.data_width_8or32bit = 0;
len = gx_nor_write(&wr_cfg);
if(len != size) {
dfu_debug("write error len = %d, expect = %d
", len, size);
return -1;
}
return 0;
}
注意
flash写之前,必现要先做擦除动作。擦除建议64KB对齐。 支持通过 DMA 对 FLASH 进行读写操作,但是读写接口还是同步的。如果读写的数据量特别大,使用dma才有效果,否则不推荐使用dma方式
3.5 读flash例子*
unsigned char my_data[4] = {0};
GX_SPINOR_RW_T r = {0};
r.addr = FLASH_TEST_START_ADDR; // 使用spi单线模式,速度比较慢
r.len = 4;
r.data = my_data;
gx_nor_read(&r);
使用qspi的方式,速度更快
static int _dfu_read_flash(unsigned int addr, void *buf, unsigned int size)
{
GX_SPINOR_RW_T rd_cfg;
unsigned int len;
rd_cfg.addr = addr;
rd_cfg.len = size;
rd_cfg.data = (void *)buf;
rd_cfg.bus_mode = GX_SPINOR_SPI_QUAD;
rd_cfg.dma_en = 0;
rd_cfg.data_width_8or32bit = 0;
len = gx_nor_read(&rd_cfg);
if(len != size) {
dfu_debug("read error len = %d, expect = %d
", len, size);
return -1;
}
return 0;
}
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
");
printf("kvs_test read kvsname
");
printf("kvs_test write kvsname value
");
}
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
");
} else {
for(int i = 0 ;i <len; i++) {
printf("%c", value[i]);
}
printf("
");
}
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
");
}
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
");
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");