Skip to content

Latest commit

 

History

History
289 lines (203 loc) · 16.1 KB

AN1154.md

File metadata and controls

289 lines (203 loc) · 16.1 KB

Using Tokens for Non-Volatile Data Storage (Rev. 0.3)

本文档将介绍 Token,并展示如何在 EmberZNet PRO、Silicon Labs Flex 和 Silicon Labs Thread 应用中将使用 Token 进行非易失性数据存储。

目录

1. 关于 Token

Token 是一种抽象数据常量,它对于应用具有特殊持久化意义。Token 在重启和断电期间用于保存某些重要数据。这些 Token 存储在非易失性存储器中。Token 具有两个部分:token keytoken dataToken key 是一个唯一的标识符,用于存储和检索 token data。在多数情况下,“token” 一词用于表示 token keytoken datakey + data。通常根据上下文可以清楚地知道 token 要表示的意思。在本文档中,token 总是表示为 key + data

Token 的写入取决于它们的用途。在芯片的生命周期中,Manufacturing token 写入的次数极少,它们存储在 Flash 的绝对地址上。

Dynamic token(经常需要访问的)存储在 Flash 的一个专用区域上,并使用循环存储(memory-rotation)算法来防止 Flash 的过度使用。Silicon Labs 提供了三种不同的 dynamic token 实现:SimEEv1(模拟 EEPROM 第一版)、SimEEv2(模拟 EEPROM 第二版)和 NVM3(第三代非易失性存储器)。有关非易失性数据存储概念的概述及三种实现的描述,请参见 UG103.7: Non-Volatile Data Storage Fundamentals

Dynamic token 有两种类型:

  • Non-indexed/basic dynamic token。这种类型可以视为一个简单的 char 变量类型。它们可以用来存储数组,但如果其中一个元素发生了更改,则必须重写整个数组。
    • Counter token 是一种特殊类型的 non-indexed dynamic token,它们用来存储每次递增 1 的数字。
  • Indexed dynamic token 可以视为一个 char 变量的链接数组(linked array),其中每个元素都应该独立地进行更改,因此这种类型在内部被存储为独立的 token,并通过 token API 显式访问。

2. Dynamic Token

与一般的 RAM 用法相比,dynamic token 系统的根本目的是允许 token data 在重启和断电期间持久化。通过使用 token key 来标识相应的数据,请求 token data 的应用不需要知道数据的确切存储位置。这简化了应用设计和代码重用。

由于 EM3x 和 EFR32 不提供内部 EEPROM,因此使用一部分内部 Flash 来实现 dynamic token 存储机制,以供协议栈和应用使用。对于 SimEEv1,EM35x 和 EFR32 需要 4kB 或 8kB 以上的 Flash 进行非易失性数据存储。SimEEv2 需要 36kb 以上的 Flash。使用 SimEEv2 需要一个来自 Silicon Labs 的特殊密钥,这样做的目的是防止从第一版意外升级到第二版。降级会导致所有数据丢失,而升级则可能不会保留所有 token。使用 NVM3,可以从 3 个 Flash 页上配置存储大小。

使用 dynamic token 存储非易失性数据的器件具有不同的 Flash 性能(可保障擦除周期)。EM35x-I Flash 可以保证 2,000 次擦除周期;其他 EM35x Flash 可以保证 20,000 次擦除周期;EFR32 Flash 可以保证 10,000 次擦除周期,具体情况请参阅器件的数据手册。由于擦除周期的限制,dynamic token 存储机制实现了一种耗损平衡(wear-leveling)算法,该算法有效地扩展了单个 token 的擦除周期数。

Silicon Labs 建议应用设计者熟悉不同的 dynamic token 存储机制,以便他们设计的应用的令牌使用达到最佳 Flash 擦除周期。有关 SimEEv1/v2 的详情,请参阅文档 AN703: Using Simulated EEPROM version 1 and version 2 for the EM35x and EFR32 SoC Platforms;有关 NVM3 的详情,请参阅 AN1135: Using Third Generation Non-Volatile Memory (NVM3) Data Storage

网络协议栈提供了一组简单的 API 用来访问 token data。完整的文档可以在 stack API reference 中找到。Non-indexed/basic token 的 API 函数包括:

void halCommonGetToken(data, token);
void halCommonSetToken(token, data);

在本例中 “token” 是 token key,“data” 是 token data

Indexed token 的 API 函数包括:

void halCommonGetIndexedToken(data, token, index);
void halCommonSetIndexedToken(token, index, data);

还有一个特殊的 API 用来增量 counter token

void halCommonIncrementCounterToken(token);

注意:尽管可以使用 halCommonSetToken() 写入 counter token,但是这样做的效率很低,并且违背了使用 counter token 的目的。

2.1 定义 Token

dynamic token 添加到头文件包括三个步骤:

  1. 定义 token 的名称。
  2. 如果 token 使用的是应用定义的(application-defined)类型,则添加 token 所需的任何 typedef。
  3. 定义 token 的存储。

2.1.1 定义 Token 的名称

在定义名称时,不要在前面添加单词 TOKEN。对于 SimEEv1/v2 dynamic token,使用单词 CREATOR

/**
 * Custom Application Tokens
 */
// Define token names here
#define CREATOR_DEVICE_INSTALL_DATA  (0x000A)
#define CREATOR_HOURLY_TEMPERATURES  (0x000B)
#define CREATOR_LIFETIME_HEAT_CYCLES (0x000C)

对于 NVM3 dynamic token,使用单词 NVM3KEY。对于不同的协议栈,NVM3 域是不同的。

注意:下面示例假定为一个 ZigBee 应用。

另请注意:HOURLY_TEMPERATURESNVM3KEY 值被设置为一个值,其后面的 0x7F 值未被使用,因为这是一个 indexed token。有关 NVM3 默认实例密钥空间的详情以及为 indexed token 选择 NVM3KEY 值的限制条件,请参阅 AN1135: Using Third Generation Non-Volatile Memory (NVM3) Data Storage

/**
 * Custom Zigbee Application Tokens
 */
// Define token names here
#define NVM3KEY_DEVICE_INSTALL_DATA  (NVM3KEY_DOMAIN_USER | 0x000A)
// This key is used for an indexed token and the subsequent 0x7F keys are also reserved
#define NVM3KEY_HOURLY_TEMPERATURES  (NVM3KEY_DOMAIN_USER | 0x1000)
#define NVM3KEY_LIFETIME_HEAT_CYCLES (NVM3KEY_DOMAIN_USER | 0x000C)

这些示例定义了 token key 并将其链接到一个程序化变量。Token 的名称实际上是 DEVICE_INSTALL_DATAhourly_temperatureLIFETIME_HEAT_CYCLES。根据使用情况,将不同的标签添加到名称的前面。因此,在示例代码中将它们参考为 TOKEN_DEVICE_INSTALL_DATA 等。

Token key 值在此设备中必须是唯一的。Token key 是应用程序的使用与对应数据的链接的关键,因此在定义新 token 甚至在更改现有 token 的结构时,应该始终保证 token key 的唯一性。CREATOR 代码值为 16-bit;NVM3KEY 代码值为 20-bit。

对于 SimEEv1/v2,第 1 位预留给 manufacturing tokenstack token 和应用框架定义的 application token,因此所有 custom tokentoken key 都应该小于 0x8000;对于 NVM3,custom token 应该使用 NVM3KEY_DOMAIN_USER 范围,以避免与 NVM3KEY_DOMAIN_ZIGBEE 范围内的 stack token 发生冲突。

2.1.2 定义 Token 的类型

前面的 CREATOR 代码示例中的每个 token 都是不同的类型,其中 HOURLY_TEMPERATURESLIFETIME_HEAT_CYCLES 的类型是 C 中的内置类型,DEVICE_INSTALL_DATA 的类型是自定义的数据结构。

Token 的类型使用 4.2 Custom Token 中介绍的结构来定义,然后根据如下示例。

注意:Token 的类型必须只在一个地方定义,因为如果定义了两次相同的数据结构,编译器将会报错。

#ifdef DEFINETYPES
// Include or define any typedef for tokens here
typedef struct {
    int8u install_date[11]; /** YYYY-mm-dd + NULL */
    int8u room_number;     /** The room where this device is installed */
} InstallationData_t;
#endif //DEFINETYPES

2.1.3 定义 Token 的存储

在定义了任何的自定义类型后,将定义 token 的存储。这将通知 token 管理软件已定义的 token。每个 token,无论是自定义的还是默认的,在这一部分中都拥有自己的条目:

#ifdef DEFINETOKENS
// Define the actual token storage information here
DEFINE_BASIC_TOKEN(DEVICE_INSTALL_DATA, InstallationData_t, {0, {0,...}})
DEFINE_INDEXED_TOKEN(HOURLY_TEMPERATURES, int16u, HOURS_IN_DAY, {0,...})
DEFINE_COUNTER_TOKEN(LIFETIME_HEAT_CYCLES, int32u, 0}
#endif //DEFINETOKENS

下面将详细介绍此过程中的每个步骤。

DEFINE_BASIC_TOKEN 有三个参数:名称(DEVICE_INSTALL_DATA)、数据类型(InstallationData_t),以及 token 的默认值({0, {0, ...}})。

默认值采用与 C 默认初始化相同的语法。在这种情况下,第一个值(room_number)初始化为 0,下一个值(install_date)设置为全 0,因为 {0, ...} 语法用 0 填充数组的其余部分。

DEFINE_COUNTER_TOKEN 的语法与 DEFINE_BASIC_TOKEN 相同。

DEFINE_INDEXED_TOKEN 需要数组的长度(在本例中为 HOURS_IN_DAY 或 24)。其最终参数是数组中每个元素的默认值。同样,在这种情况下,它被初始化为全 0。

2.2 访问 Basic (Non-indexed) Token

某些应用可能需要在安装时存储配置数据。这通常使用的是 basic token。假设已经定义了一个 basic token 来使用 token key DEVICE_INSTALL_DATA,并且数据结构如下所示:

typedef struct {
    int8u install_date[11]; /** YYYY-mm-dd + NULL */
    int8u room_number;      /** The room where this device is installed */
} InstallationData_t;

然后您可以使用这样的代码片段来访问它:

InstallationData_t data;
// Read the stored token data
halCommonGetToken(&data, TOKEN_DEVICE_INSTALL_DATA);
// Set the local copy of the data to new values
data.room_number = < user input data >
MEMCOPY(data.install_date, < user input data>, 0, sizeof(data.install_date));
// Update the stored token data with the new values
halCommonSetToken(TOKEN_DEVICE_INSTALL_DATA, &data);

2.2.1 访问 Counter Token

计算恒温器(thermostat)启动的加热循环次数通常使用 counter token。假设它被命名为 LIFETIME_HEAT_CYCLES,并且是一个 int32u

void requestHeatCycle(void)
{
    /// < application logic to initiate heat cycle >
    halCommonIncrementCounterToken(TOKEN_LIFETIME_HEAT_CYCLES);
}

int32u totalHeatCycles(void)
{
    int32u heatCycles;
    halCommonGetToken(&heatCycles, TOKEN_LIFETIME_HEAT_CYCLES);
    return heatCycles;
}

2.3 访问 Indexed Token

要存储一组类似的值,例如一天中首选温度设置的数组,应使用默认数据类型 int16s 来存储要求的温度,并定义一个名为 HOURLY_TEMPERATURESindexed token

整个数据集的本地副本应该是这样的:

int16s hourlyTemperatures[HOURS_IN_DAY]; /** 24 hours per day */

在应用代码中,您可以使用 indexed token 的函数访问或更新一天中的某个值:

int16s getCurrentTargetTemperature(int8u hour)
{
    int16s temperatureThisHour = 0; /** Stores the temperature for return */
    if (hour < HOURS_IN_DAY) {
        halCommonGetIndexedToken(&temperatureThisHour, TOKEN_HOURLY_TEMPERATURES, hour);
    }
    return temperatureThisHour;
}

void setTargetTemperature(int8u hour, int16s targetTemperature)
{
    if (hour < HOURS_IN_DAY) {
        halCommonSetIndexedToken(TOKEN_HOURLY_TEMPERATURE, hour, &temperatureThisHour);
    }
}

3. Manufacturing Token

Manufacturing token 被定义并视为 basic (non-indexed) token。主要区别在于 token 的存储及访问方式,manufacturing token 在专用的 Flash 页面上(具有固定的绝对地址)。当芯片启用了读保护特性时,只能从片上代码读取 manufacturing token;如果未启用读保护特性,则可以通过外部编程工具来读取它们。仅当 token 处于擦除状态时,才能从片上代码中写入 manufacturing tokenManufacturing token 应使用专用的 API halCommonGetMfgToken()halCommonSetMfgToken() 来访问,这些 API 的参数与 basic token API 相同。使用 MfgToken API的两个主要目的是:

  1. 更快地访问
  2. 在调用 emberInit() 之前的 boot 过程的早期访问

Manufacturing token 也可以通过 basic token API halCommonGetToken()halCommonSetToken() 来访问。

Manufacturing token 只能用外部编程工具覆盖(而不能用片上代码覆盖),因为要覆盖已写入的任何 manufacturing token 需要擦除 manufacturing token 的专用 Flash 页面。如果启用了读保护,则必须先禁用它,否则将会擦除芯片的内容(作为副作用)。Manufacturing token 没有耗损平衡,所以尽量不要覆盖它们。

4. Default and Custom Token

4.1 Default Token

网络协议栈包含了一些 default token。这些 token 根据它们的软件用途进行分组:

  • Stack Token 是由协议栈设置的运行时配置选项,应用不应该更改这些内容。
  • Application Framework Token 是由应用框架使用并由 AppBuilder 生成的 application token,在项目生成后应用不应该更改这些内容。这些示例包括 ZCL attribute tokenplugin token
  • Manufacturing Token 在制造时设置,不能通过应用更改。

要查看 stack token,请参阅文件:

<install-dir>/stack/config/token-stack.h

要查看 Application Framework token,请在 AppBuilder 中生成项目后,在项目目录下参阅文件 <project_name>_tokens.hafv2-token.h<project_name>_tokens.h 包含了应用选择存储在非易失性存储器中的 ZCL attributes token。文件 afv2-token.h 包括了 plugin token 头文件和 custom application token 头文件。

要查看 EFR32 或 EM3x 系列芯片的 manufacturing token,请分别参阅以下文件:

<install-dir>/hal/micro/cortexm3/efm32/token-manufacturing.h
<install-dir>/hal/micro/cortexm3/token-manufacturing.h

搜索 CREATOR 以查看已定义的名称。如果整个文件看起来难以阅读,则请只关注描述 token 的部分。在创建板子时,供应商可以设置一些固定的 manufacturing token。例如,可以设置自定义 EUI-64 地址,以覆盖 Silicon Labs 提供的内置 EUI-64 地址。其他 token(如内部 EUI-64)不能被覆盖。

有关制造和 token 编程的详情,请参阅文档 AN710: Bringing up Custom Devices for the Ember® EM35x SoC or NCP Platform

4.2 Custom Token

在 Simplicity Studio 中,可以通过 .isc 文件中的 “Includes” 选项卡下的 “Token Configuration” 部分添加 custom application dynamic tokenmanufacturing token。有关定义 custom application dynamic token 的详细信息,请参阅 2.1 定义 Token。定义 custom manufacturing token 较为罕见,您可以参考 default manufacturing token 定义文件(请参阅 4.1 Default Token 部分)以获取完整的说明和示例。

Custom application dynamic token 文件应该具有以下结构:

/**
 * Custom Application Tokens
 */
// Define token names here
#ifdef DEFINETYPES
// Include or define any typedef for tokens here
#endif //DEFINETYPES
#ifdef DEFINETOKENS
// Define the actual token storage information here
#endif //DEFINETOKENS

您可以参考 stack token 定义文件 <install-dir>/stack/config/token-stack.h,以作为创建 application token 的指南。

注意:对于 custom token 文件,不应该使用 token-stack.h 中的包含保护(#ifndef)。