嵌入式C数据存储模块设计

嵌入式C数据存储模块设计,c,database,embedded,C,Database,Embedded,我正在设计一个嵌入式C数据存储模块。它将由希望访问此“共享”系统范围数据的文件/模块包含。多个任务聚合数十个输入(GPIO、CAN、I2C/SPI/SSP数据等),并使用API存储这些值。然后,其他任务可以通过API安全地访问数据。该系统是一个带有RTOS的嵌入式应用程序,因此使用互斥锁来保护数据。无论实施情况如何,都将使用这些想法 我以前设计过类似的东西,我正在努力改进它。我目前正在进行一个新的实现,我遇到了一些小问题,从一个全新的角度来看,我会受益匪浅 快速概述本模块的要求: 理想情况下,将

我正在设计一个嵌入式C数据存储模块。它将由希望访问此“共享”系统范围数据的文件/模块包含。多个任务聚合数十个输入(GPIO、CAN、I2C/SPI/SSP数据等),并使用API存储这些值。然后,其他任务可以通过API安全地访问数据。该系统是一个带有RTOS的嵌入式应用程序,因此使用互斥锁来保护数据。无论实施情况如何,都将使用这些想法

我以前设计过类似的东西,我正在努力改进它。我目前正在进行一个新的实现,我遇到了一些小问题,从一个全新的角度来看,我会受益匪浅

快速概述本模块的要求:
  • 理想情况下,将有一个接口可以访问变量(一个get,一个set)。
  • 我想返回不同的变量类型(float、int等)。这意味着可能需要宏。
  • 我并不迫切需要代码空间,但它始终是一个问题
  • 快速获取/设置是绝对重要的(这意味着在xml/json不可用的情况下存储字符串)
  • 运行时不需要添加新变量。一切都是在开机时静态定义的


    问题是你将如何着手设计这样的东西?枚举、结构、访问器、宏等?我不是在这里寻找代码,只是讨论一般的总体设计思想。如果互联网上有解决这类问题的解决方案,也许仅仅一个链接就足够了。

    我自己也经历过几次这种情况。每次我结束“滚我自己的”的时候,我肯定不会患上非发明于此(NIH)综合症。有时,空间、处理周转时间或可靠性/可恢复性要求只是使这条路最不痛苦

    因此,我不想就此主题写一部伟大的美国小说,我只想在这里抛开一些想法,因为你的问题相当广泛(但至少感谢你提出一个问题并提供背景)

    是C++的表吗?内联函数、模板和一些Boost库在这里可能很有用。但我猜这是C

    如果您使用的是C99,那么至少可以使用内联函数,在类型安全方面,内联函数比宏高出一步

    您可能需要考虑使用多个互斥体来保护数据的不同部分;即使更新很快,您也可能希望将数据分成多个部分(例如,配置数据、初始化数据、错误记录数据、跟踪数据等),并为每个部分提供自己的互斥,从而减少漏斗/瓶颈

    <>你也可以考虑让所有的数据访问都通过服务器任务。所有读写操作都通过与服务器任务通信的API进行。服务器任务从队列中按顺序提取读写请求,通过写入RAM镜像快速处理它们,如果需要发送响应(至少对于读请求),然后在后台将数据缓冲到NVM(如果需要)。与简单的互斥体相比,它听起来很重,但在某些用例中有其优势。对你的申请了解不够,不知道这是否有可能

    我要说的一件事是,通过标记(例如,可能是一个枚举列表,如配置数据、地址数据等)获取/设置的想法比直接寻址数据(例如,“给我地址ox42000处的256字节)前进了一大步.我看到许多商店在整个物理寻址方案最终崩溃时承受着巨大的痛苦&他们需要重新考虑/重新设计。试着将“什么”与“如何”分离开来-客户不必知道或关心东西存储在哪里,有多大,等等。(你可能已经知道了这一切,如果是的话,很抱歉,我只是一直在看……)


    最后一件事。你提到了互斥体。当心优先级反转…这会使那些“快速访问”"在某些情况下需要很长的时间。大多数内核互斥实现允许您考虑这一点,但通常默认情况下它不会启用。再次,如果这是一条老消息,我很抱歉…

    我通常使用一个简单的字典,如API,使用int作为键和一个固定大小的值。这执行速度很快,使用非常少的程序RAM和has可预测的数据RAM使用情况。换句话说,最低级别的API如下所示:

    void data_set(uint16 key, uint32 value);
    uint32 data_get(uint16 key);
    
    键成为常量列表:

    #define KEY_BOGOMIPS 1
    #define KEY_NERDS_PER_HOUR 2
    
    您可以通过强制转换来处理不同的数据类型。这很糟糕,但您可以编写宏以使代码更清晰:

    #define data_get_float(key) (float)data_get(key)
    
    如果不为每个项目编写单独的宏或访问函数,就很难实现类型安全。在一个项目中,我需要验证输入数据,这就成为了类型安全机制

    数据的物理存储结构取决于您拥有的数据内存、程序内存、周期和独立密钥的数量。如果您拥有大量的程序空间,请对密钥进行散列,以获得一个较小的密钥,您可以直接在数组中查找。通常,我将底层存储设置为:

    struct data_item_t {
        uint16 key;
        uint32 value;
    }
    
    struct data_item_t items[NUM_ITEMS];
    
    对我来说,即使在非常小的(8位)微控制器上,这也足够快,尽管如果你有很多东西,它可能不适合你


    请记住,您的编译器可能会很好地内联或优化写操作,因此每次访问的周期可能比您预期的要低。

    我有过一些经验,并且发现每种方法都适合自己的需要。请写下我对这个问题的想法,希望这能给您一些想法

    • 对于具有大量依赖关系和约束的更复杂的数据,我发现通常最好使用更高级别的模块,即使是在牺牲速度的情况下。它为您省去了很多麻烦,而且根据我的经验,这通常是一种方法,除非您有非常严格的约束。问题是存储大部分“静态”数据,在这种类型的模块中变化不大
      #ifndef __LIB_DATA_H
      #define __LIB_DATA_H
      
      #include <type.h>
      
      /****************************************************************************************
       * Constant Definitions
       ***************************************************************************************/
      
      /* Varname, default value (uint32_t) */
      #define DATA_LIST                                                                       \
        DM(D_VAR1, 0)                                                                         \
        DM(D_VAR2, 1)                                                                         \
        DM(D_VAR3, 43)
      
      #define DM(y, z)    y,
      
      /* create data structure from the macro */
      typedef enum {
          DATA_LIST
          NUM_DATA_VARIABLES
      } dataNames_t;
      
      typedef struct {
          dataNames_t name;
          uint32_t value;
      } dataPair_t;
      
      /* the macro has to be undefined to allow the fault list to be reused without being
       * defined multiple times
       *
       * this requires:
       * a file specific lint option to suppress the rule for use of #undef
       */
      #undef DM
      
      /****************************************************************************************
       * Data Prototypes
       ***************************************************************************************/
      
      /****************************************************************************************
       * External Function Prototypes
       ***************************************************************************************/
      
      /**
       * Fetch a machine parameter
       *
       * \param dataName The variable from DATA_LIST that you want to fetch
       *
       * \return The value of the requested parameter
       *
       */
      uint32_t lib_data_Get(dataNames_t dataName);
      
      /**
       * Set a machine parameter
       *
       * \param dataName The variable from DATA_LIST that you want to set
       * \param dataVal The value you want to set the variable to
       *
       * \return void
       *
       */
      void lib_data_Set(dataNames_t dataName, uint32_t dataVal);
      
      #endif /* __LIB_DATA_H */
      
      #include <type.h>
      #include "lib_data.h"
      
      /****************************************************************************************
       * Variable Declarations
      ***************************************************************************************/
      /* Used to initialize the data array with defaults ##U appends a 'U' to the bare
       * integer specified in the DM macro */
      
      #define DM(y, z)               \
          dataArray[y].name = y;     \
          dataArray[y].value = z##U;
      
      static bool_t dataInitialized = FALSE;
      static dataPair_t dataArray[NUM_DATA_VARIABLES];
      
      /****************************************************************************************
       * Private Function Prototypes
       ***************************************************************************************/
      static void lib_data_Init(void);
      
      /****************************************************************************************
       * Public Functions
       ***************************************************************************************/
      uint32_t lib_data_Get(dataNames_t dataName) {
          if(!dataInitialized) {
              lib_data_Init();
          }
      
          /* Should only be used on systems that do word-sized asm reads/writes.
           * If the lib gets extended to multi-word storage capabilities, a mutex
           * is necessary to protect against multi-threaded access */
          return dataArray[dataName].value;
      }
      
      void lib_data_Set(dataNames_t dataName, uint32_t dataVal) {
          if(!dataInitialized) {
              lib_data_Init();
          }
      
          /* Should only be used on systems that do word-sized asm reads/writes.
           * If the lib gets extended to multi-word storage capabilities, a mutex
           * is necessary to protect against multi-threaded access */
          dataArray[dataName].value = dataVal;
      }
      
      /****************************************************************************************
       * Private Functions
       ***************************************************************************************/
      
      /**
       * initialize the machine data tables
       *
       * \param none
       *
       * \return none
       *
       */
      static void lib_data_Init(void) {
          /* Invoke the macro to initialize dataArray */
          DATA_LIST
          dataInitialized = TRUE;
      }
      
      Std_ReturnType NvM_ReadBlock(NvM_BlockIdType BlockId, uint8* data);
      Std_ReturnType NvM_WriteBlock(NvM_BlockIdType BlockId, uint8* data);
      
       Std_ReturnType NvM_ReadAll(void);
       Std_ReturnType NvM_WriteAll(void);
      
       Std_ReturnType NvM_GetErrorStatus(NvM_BlockIdType BlockId, NvM_RequestResultType* res);