跳转至

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