跳转至

迁移公告

本文档中心已迁移至 新域名(https://document.nationalchip.com/) ,当前文档中心维护有效期至 2025年6月30日。 请更新您的书签或外部链接,感谢您的支持!

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");