.. _VM 掉电存储:  

VM 掉电存储说明
=========================================

下面分为4个部分来介绍VM的基本原理与特点:
        1. ":ref:`VM 记忆的基本原理`"
        2. ":ref:`VM 记忆的系统局限性`"
        3. ":ref:`NEW VM的预擦除`"
        4. ":ref:`NEW VM常见BUG`"


.. _VM 记忆的基本原理:  

VM 记忆的基本原理
*****************************
SDK提供了 vm掉电记忆存储功能 ,该功能用于在 内置flash里保存断电之后还需要保存的信息。

1、VM会将自己所分得的内部FLASH区域划为A,B两块区域,

2、例如A区域负责记录VM数据,B区域此时处于空闲状态,当A区域数据趋于饱和时,会把A区域的数据整合到B区域(即格式整理),后续B区域记录VM数据,A区域处于空闲状态,以此往复。

3、若此时格式整理后,A区域原有数据没有被擦除(未调用擦除函数)

4、用户去读取数据,会读取到原有区域的数据(即旧数据),导致数据错误或者读不到数据。


.. _VM 记忆的系统局限性:  

VM 记忆的系统局限性
*****************************

VM的数据记忆主要记录到系统flash中,系统flash中存放着整个系统的代码,大量的代码需要在系统flash中执行;

VM的数据记忆过程中有可能需要对系统flash进行擦除操作;

系统flash在被擦除时不可以被访问,且不同的flash擦除时间不一致;

基于以上几点 **当VM触发到系统flash擦除时,这时系统flash代码是不能执行的** ,典型的现象就是 **解码卡音**。

为了优化上述的问题,编写新的NEW VM驱动,**在一定程度上改善此问题**。


.. _NEW VM的预擦除:  

NEW VM的预擦除
*****************************

为了解决解码时,擦除系统flash可能导致的卡音,new vm尽量不在系统繁忙时对系统flash进行擦除;

new vm提供了预擦除函数nvm_pre_erasure_next,可以在系统空闲时对系统flash当前与空闲区进行 **提前预擦除**、或 **滞后擦除**;

new vm的写函数在AB区格式整理时,也不会对老区域进行擦除,而依赖预擦除在空闲时擦除;(未擦除再次上电会有老数据残留风险)


.. _NEW VM常见BUG:  

NEW VM常见BUG
*****************************

部分客户在使用NEW VM,自己编写功能中,遗忘或者未能合理调用预擦除功能导致,数据不能有效更新;

此问题有两种解决方式:

1、在写入数据后,系统掉电或关机前,调用预擦除;(推荐)

2、打开config_vm_erasure_after_format_en变量,打开此变量后,new vm驱动会在需要擦除时实时擦除系统flash,这样一来NEW VM驱动的行为将和老的VM一直,不再具备对卡音问题的优化;


^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


下面分为7个部分来介绍VM功能:
        1. ":ref:`VM 掉电存储使用说明`"
        2. ":ref:`VM 掉电存储版本简单说明`"
        3. ":ref:`新旧VM文件组成及版本选择`"
        4. ":ref:`VM相关参数介绍`"
        5. ":ref:`VM掉电记忆存储接口`"
        6. ":ref:`VM常见问题介绍`"

.. _VM 掉电存储使用说明:  

VM 掉电存储使用说明
******************************
    
    **VM记忆存储使用的步骤分为三步:**
        - 第一步:系统初始化时,调用函数vm_init_api()初始化VM功能。
        - 第二步:使用vm_write()接口把需要保存的数据写入FLASH。
        - 第三步:使用vm_read()接口读取写入的数据。

    可参考":ref:`VM简单使用demo`"
    
^^^^^^^^^^^^^^^^^^^^^^^

.. _VM 掉电存储版本简单说明:

VM 掉电存储版本简单说明
******************************
    目前SDK中VM有新旧两个版本可以选择:

.. note:: 旧版VM功能: 

        1. 可存储id号数量:128个;
        2. 单次写入最大数据长度:128个byte;
        3. 没有cache管理;
        4. 没有预擦除功能;

.. note:: 新版VM功能

        1. 可存储id号数量:512个;
        2. 单次写入最大数据长度:4096个byte;(若EEPROM分配给VM区域只有8K,则单次写入最大数据长度为:4092个byte);
        3. 增加cache管理,提升读取速度;
        4. 增加预擦除功能。

.. note:: 新旧版本VM功能对比:

        1. 新版VM id号数量增多;
        2. 新版VM 单次写入最大数据长度增多;
        3. 新版VM 增加cache管理,提升VM读取速度;
        4. 新版VM 增加预擦除功能,可以在系统空闲时进行预擦除操作,可尽可能避免VM整理时关闭flash时间过长导致的卡音问题;
        5. 新版ID号 与 旧版index句柄 含义相同;

^^^^^^^^^^^^^^^^^^^^^^^

.. _新旧VM文件组成及版本选择:

新旧VM文件组成及版本选择
******************************

    vm模块由vm_api.c和vm_api.h文件构成API层,包含ovm_api.c和nvm_api.c文件;
    
.. note:: 在SDK 为 AD14/15/AC104的v1.7.0版本(包括1.7.0版本)以前版本,用户可在 **vm_api.h** 文件里选择使用新 / 旧版VM.
    
    .. image:: vm_switch.png
        :alt: "新旧VM功能选择"
        :align: center
    .. centered:: 新旧VM功能选择

.. note:: 在SDK 为 AD14/15/AC104的v1.7.0版本以后,vm配置选项更改位置,用户可在 **app_config.h** 文件里选择使用新 / 旧版VM.

    .. image:: vm_mode_switch.png
        :alt: "新旧VM功能选择"
        :align: center
    .. centered:: 新旧VM功能选择


.. _VM相关参数介绍:   

VM相关参数介绍
******************************
    对于用户,我们需要关心的参数分别为:

    1. ":ref:`ID号(vm_api.h)`"
    2. ":ref:`vm读/写入数据长度`"
    3. ":ref:`vm存储区域大小`"
   
对于新版VM

    4. ":ref:`新版VM格式整理时缓存空间设置`"
    5. ":ref:`新版vm格式整理后是否立即擦除旧半区区域功能`"
    6. ":ref:`新版vm是否支持多段读功能`"

    下面逐一介绍:

.. _ID号(vm_api.h):  

ID号(vm_api.h)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    .. image:: 1-2-nvm_id.png
        :alt: "vm_write 参数 id“
        :align: center
    .. centered:: vm_write参数


.. important:: 旧版VM  ID号最多128个,新版VM  ID号最多512个;(旧版VM  index号在新版vm仍可通用)

    新版VM  ID号最多支持512个,且不再需要自定义对应ID的数据长度。

    用户可在vm_api.h的VM_INDEX里添加自己的id号。
    
    **注意:**
    1~32号ID为系统使用,用户不可修改其顺序

    .. image:: 1-3-nvm_index.png
        :alt: "旧版VM掉电存储记忆项的定义(vm_api.h)“
        :align: center
    .. centered:: 旧版VM掉电存储记忆项的定义(vm_api.h)

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. _vm读/写入数据长度:

读/写入数据长度
^^^^^^^^^^^^^^^^^^^^^^^^^^^^


    .. image:: 1-4-nvm_len.png
        :alt: "vm_write参数 length"
        :align: center
    .. centered:: vm_write参数length

.. important:: 旧版VM读/写长度最多支持128个byte;

    新版VM读/写长度最多支持4096个byte;


^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. _vm存储区域大小:   

vm存储区域大小
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. important:: VM记忆的数据 是 EEPROM区划分出一定空间大小给VM使用,该区域大小可由下载目录里的isd_config.ini文件配置。
    (该配置参数详细说明可参考":ref:`isd_config.ini文件介绍`")
    
    **注意:**
        VM存储区域大小分配的EEPROM空间不得低于8K字节,且需为4K字节的倍速,即8K,12K,16K等等。
    
    .. image:: vm_area_config.png
        :alt: "VM区域大小设置"
        :align: center
    .. centered:: VM区域大小设置"

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. _新版VM格式整理时缓存空间设置: 

新版VM格式整理时缓存空间设置new_vm_buff(new_vm.h)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. important:: new_vm_buff,该buff用于vm格式整理时数据的缓存。

    该buff由 **用户自定义最大id数量(BIT_MAP_SIZE)** + **单个id在整理时数据缓存的最大长度(BIT_MAX_LEN)** 两部分组成。

    **BIT_MAP_SIZE** :即BIT_MAP:vm最多可以存储的id数量,固定为512个。

    **BIT_MAX_LEN** :即VM格式整理时数据缓存的大小,该值越大,则整理速度越快,该值越小,则整理速度越慢。(注意:该buf与读/写数据最大长度为两个概念)

    
    .. image:: 1-1-nvm_api.png
        :alt: "VM掉电存储记忆项的定义(nvm_api.c和new_vm.h)“
        :align: center
    .. centered:: VM掉电存储记忆项的定义(nvm_api.c和new_vm.h)
    
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


.. _新版vm格式整理后是否立即擦除旧半区区域功能:

新版vm格式整理后是否立即擦除旧半区区域功能
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    
    .. image:: vm_special_cfg.png
        :alt: "config_vm_erasure_after_format_en“
        :align: center
    .. centered:: config_vm_erasure_after_format_en
    
    | config_vm_erasure_after_format_en 用于控制新版vm格式整理后是否立即擦除旧半区区域;
    | 0 : 不立即擦除(默认值)
    | 1 : 立即擦除

.. important:: 该功能直观影响为:打开该功能后,vm进行格式整理 **会** 调用到flash擦除,如果系统此时正在解码,那么解码可能会出现卡音的现象。

    而默认关闭该功能后,vm进行格式整理 **不会** 调用到flash擦除,而是等到 **系统空闲时程序员调用预擦除函数进行擦除动作** ,这样就不会影响到系统此时正进行的解码。

    **关闭该功能后,未在系统空闲时进行擦除动作,那么可能导致下次上电,VM读取到老旧的数据。**

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. _新版vm是否支持多段读功能:

新版vm是否支持多段读功能
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    .. image:: vm_special_cfg.png
        :alt: "config_vm_multiple_read_en“
        :align: center
    .. centered:: config_vm_multiple_read_en
    
    | config_vm_multiple_read_en 用于控制新版vm是否支持多段读功能;
    | 0 : 不支持多段读(默认值)
    | 1 : 支持多段读

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. _VM掉电记忆存储接口: 

VM掉电记忆存储接口
****************************************************************************************************

函数u32 syscfg_vm_init(u32 addr, u32 size)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    此函数用来初始化vm虚拟存储系统,其中参数:
        1. eeprom_saddr:vm区域的起始地址。
        2. eeprom_size:vm区域的空间长度。
        3. 返回值:
                | 成功:0;
                | 失败:错误号,可参考errno-base.h;


函数u32 vm_write(u32 id,u8 \*buf,u32 len)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    此函数用来把对应hdl的数据存储到vm虚拟存储系统,其中参数:
        1. hdl:访问vm的id。
        2. buf:vm读数据buffer。
        3. len:数据长度。
        4. 返回值:
                | 成功:写入长度;
                | 失败:错误号,可参考errno-base.h;


函数u32 vm_read(u32 id ,u8 \*buf,u32 len)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    此函数用来把对应hdl的数据从vm虚拟存储系统读出来,其中参数:
        1. id:访问vm的id。
        2. buf:vm读数据buffer。
        3. len:数据长度。
        4. 返回值:
                | 成功:写入长度;
                | 失败:错误号,可参考errno-base.h;

函数 u32 vm_pre_erase(void)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    | 此函数用于预擦除设备,在系统空闲时主动调用提前擦除flash,可尽量避免VM做整理时关flash时间过长导致卡音问题;
    | 该函数只有在新版VM处生效,旧版VM没有该功能。
    | 示例:音乐模式下,500MS消息来时若系统处于空闲状态,则会进行预擦除动作:

    .. image:: 2-1-pre_erase.png
        :alt: "图2.1 系统空闲预擦除动作“
        :align: center
    .. centered:: 图2.1 系统空闲预擦除动作


函数 nvm_pre_erasure_next(NEW_VM_OBJ \*p_nvm, u16 using_next, u16 idle_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    此函数为NEW VM中独有接口,由2.4 函数 u32 vm_pre_erase() 调用,用于系统空闲时选择擦除当前VM已存储的数据往后的flash扇区,以及空闲VM需要往后擦除的flash扇区数,用户可自行修改需要擦除的flash扇区数,其中参数: ::

        1、p_nvm:NEW VM句柄指针;
        2、using_next:当前VM存储块需要往后擦除的flash扇区数;
        3、idle_next:空闲VM存储块需要往后擦除的flash扇区数;
        4、返回值:无

.. _VM常见问题介绍: 

VM常见问题介绍
****************************************************************************************************

.. _VM简单使用demo: 

VM简单使用demo:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    用户可在app/bsp/start/flash_init.c里的vm_init_api()函数找到demo示例。

    .. image:: vm_demo.png
        :alt: "vm_demo“
        :align: center
    .. centered:: vm_demo

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

调整VM区域大小设置:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    节省VM占用系统空间可以从两个地方入手:

    1. 减少VM区域大小。
        具体参考上面":ref:`vm存储区域大小`"章节的 vm存储区域大小 部分

    2. 新版VM可以减少VM格式整理时的缓存new_vm_buff
        可以适当减少BIT_MAX_LEN的长度。

        具体参考上面":ref:`新版VM格式整理时缓存空间设置`"章节的 新版VM格式整理时缓存空间设置 部分。

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

新版VM在关机或者掉电后可能存在数据丢失BUG:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    该问题原因可能有:

        1. AD14v1.7.0、v1.8.0 voice_toy工程 app_ld.c文件缺少(\*.data)段(优先排查);

        解决办法: `FAQ:AD14v1.7.0、v1.8.0 voice_toy工程new_vm存在掉电后读取数据错误问题? <https://gitee.com/Jieli-Tech/fw-AD15N/issues/I99LOM>`_

        2. 系统在关机前格式整理后没有做预擦除动作。

        解决办法: `FAQ:AD14/15/17/AC104 new_vm功能在关机或者掉电后可能存在数据丢失BUG <https://gitee.com/Jieli-Tech/fw-AD15N/issues/I7SD1A>`_

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

VM格式整理原理:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    **VM格式整理原理:**
    
    | VM功能将EEPROM区域划分为A、B两块,A块记录VM数据,B块处于空闲状态,
    | 当A块数据接近饱和时,会擦除B块并将A块的数据整合到B块,后续B块记录VM数据,A块处于空闲状态,以此往复。