Trabajando con parámetros en EEPROM, cómo no desgastar la memoria

Introducción

Buenos días. Mi último artículo sobre parámetros en EEPROM fue, por decirlo suavemente, un poco incomprendido. Aparentemente, de alguna manera describí torcidamente el objetivo y la tarea que se estaba resolviendo. Esta vez intentaré corregirme, describir con más detalle la esencia del problema que se está resolviendo, y esta vez expandiremos los límites del problema.





Es decir, hablemos sobre cómo almacenar los parámetros que deben escribirse en la EEPROM de forma permanente.





A muchos les puede parecer que este es un problema muy específico, pero de hecho, muchos dispositivos están haciendo exactamente esto: escriben constantemente en la EEPROM. Un contador de agua, un contador de calor, un cuentakilómetros, todo tipo de bitácoras de acción del usuario y bitácoras que almacenan el historial de medidas, o cualquier dispositivo que almacene el tiempo de su funcionamiento.





La peculiaridad de tales parámetros es que no se pueden escribir así en el mismo lugar en la EEPROM, simplemente usará todos los ciclos de escritura de la EEPROM. Por ejemplo, si es necesario escribir el tiempo de operación una vez cada 1 minuto, entonces es fácil calcular que con una EEPROM de 1,000,000 de ciclos de escritura, lo arruinará en menos de 2 años. Y qué son 2 años, si un dispositivo de medición ordinario tiene un tiempo de verificación de 3 o incluso 5 años.





Además, no todas las EEPROM tienen 1,000,000 de ciclos de escritura, muchas EEPROM baratas todavía se producen de acuerdo con tecnologías antiguas con 100,000 escrituras. Y si consideramos que 1,000,000 de ciclos se indican solo en condiciones ideales, digamos a altas temperaturas, este número se puede reducir a la mitad, entonces su EEPROM puede resultar ser el elemento menos confiable en el primer año de funcionamiento del dispositivo.





Por lo tanto, intentemos solucionar este problema, y ​​hacer que acceder a los parámetros sea tan sencillo como en el artículo anterior, pero al mismo tiempo la EEPROM sería suficiente para 30 años, bueno, o para 100 (puramente teóricamente).





Entonces, en el último artículo, apenas mostré cómo hacerlo para que los parámetros en la EEPROM se puedan trabajar de manera intuitiva, sin pensar en dónde se encuentran y cómo acceder a ellos.





Déjame recordarte:





ReturnCode returnCode = NvVarList::Init();  //     EEPROM
returnCode = myStrData.Set(tString6{ "Hello" }); // Hello  EEPOM myStrData.
auto test = myStrData.Get();                //    

myFloatData.Set(37.2F);    // 37.2  EEPROM.
myUint32Data.Set(0x30313233);
      
      



, , . @Andy_Big @HiSER .





, , HART, FF PF, . , HART - , , , , .. , . 500 - 600, 200.





, @HiSER- , 1 byte, EEPROM. , 200 4 , 1600 EEPROM, 500, 4000.





, 4-20 , 3 , , , BLE . EEPROM . , .





, , , . , , 500 , 1 ( , , ). , 4000 SPI 70 , ( 7 ), , 3 , , .





, . , , , , .





- .





EEPROM,

, . , .





, EEPROM . , 100 000, 1 000 000. , 10 000 000 ? , EEPROM .





, EEPROM . . , EEPROM , , 16, 32 64 . - , EEPROM , , . , . .. , 1 , . - , .





, 1 000 000 , 1 000 000 , . .. , , . 10 , . , 10 , 1.





, , . - .





, . . , , - AntiWearNvData



( ). , , .





//   EEPROM        
ReturnCode returnCode = NvVarList::Init();       
returnCode = myStrData.Set(tString6{ "Hello" }); // Hello  EEPROM myStrData.
auto test = myStrData.Get();                     //   

myFloatData.Set(37.2F);                          // 37.2  EEPROM.
myUint32Data.Set(0x30313233);

myFloatAntiWearData.Set(10.0F);          //   10.0F  EEPROM  
myFloatAntiWearData.Set(11.0F);
myFloatAntiWearData.Set(12.0F);
myFloatAntiWearData.Set(13.0F);
...
//      EEPROM 11 000 000 . 
myFloatAntiWearData.Set(11'000'000.0F);  
myUint32AntiWearData.Set(10U);              //    int
myStrAntiWearData.Set(tString6{ "Hello" }); //     
      
      



:





  • EEPROM





    • (), EEPROM. :





















, , - , .





  • () EEPROM





    • ,





  • , , ,





    • , runtime, .





  • EEPROM,





    • EEPROM I2C SPI, , , .





    • , .





  • .





    • , . , . , .





. , :





AntiWearNvData



, CachedNvData



, . EEPROM, , , . EEPROM , - . uint32_t



30 - 100 000 .





:





, .





EEPROM

CachedNvData



updateTime



. , EEPROM. EEPROM . , :





using tSeconds = std::uint32_t;

constexpr std::uint32_t eepromWriteCycles = 1'000'000U;
constexpr std::uint32_t eepromPageSize = 32U;
//   EEPROM  10 
constexpr tSeconds eepromLifeTime = 3600U * 24U * 365U * 10U;
      
      



updateTime



. . , , . , , , :





template<typename NvList, typename T, const T& defaultValue, tSeconds updateTime, auto& nvDriver>
class AntiWearNvData
{
  private:
      struct tAntiWear
      {
         T data = defaultValue;
          std::uint32_t index = 0U;
      };
  
      inline static tAntiWear nvItem;
  public:
      //   2     . 
      //          
      static constexpr auto recordSize = sizeof(nvItem) * 2U;
      // ,       
      //      ,    
      //       ,
      //  ,   .    .
      static_assert(eepromPageSize/recordSize != 0, "Too big parameter");
      static constexpr size_t recordCounts =  (eepromPageSize/recordSize) * 
                                               eepromLifeTime / 
                                               (eepromWriteCycles * updateTime);
      
      



, , ,

, , . :









  • , / , .





tAntiWear



. Set(...)



, , , 1.





template<typename NvList, typename T, const T& defaultValue, tSeconds updateTime, auto& nvDriver>
class AntiWearNvData
{
 public:
  ReturnCode Set(const T& value) const
  {
    tAntiWear tempData = {.data = value, .index = nvItem.index};
    //         EEPROM
    const auto calculatedAddress = GetCalculatedAdress(nvItem.index);

    ReturnCode returnCode = nvDriver.Set(calculatedAddress, 
                                         reinterpret_cast<const tNvData*>(&tempData), 
                                         sizeof(tAntiWear));

    //   ,     , 
    //     1,   
    if (!returnCode)
    {
      nvItem.data = value;
      nvItem.index ++;
    }
      return returnCode;
  }
...
};
      
      



:





template<typename NvList, typename T, const T& defaultValue, tSeconds updateTime, auto& nvDriver>
class AntiWearNvData
{
...
private:
  static size_t GetCalculatedAdress(std::uint32_t ind)
  {
    constexpr auto startAddress = GetAddress();
    //         
    //  ,     
    //      ,   
    //   -     EEPROM.
    size_t result = startAddress + recordSize * ((ind % recordCounts));
    assert(result < std::size(EEPROM));
    return result;
  }

  constexpr static auto GetAddress()
  {
    return NvList::template GetAddress<const AntiWearNvData<NvList, T, defaultValue, updateTime, nvDriver>>();
  }
};
      
      



EEPROM,

Get()



- ,





template<typename NvList, typename T, const T& defaultValue, tSeconds updateTime, auto& nvDriver>
class AntiWearNvData
{
public:
   T Get() const
    {
        return nvItem.data;
    }
};
      
      



, , . , , , , .





template<typename NvList, typename T, const T& defaultValue, tSeconds updateTime, auto& nvDriver>
class AntiWearNvData
{
public:
  static ReturnCode Init()
  {
    const auto ind = FindLastRecordPosition();
    constexpr auto startAddress = GetAddress();
    const auto calculatedAddress =  startAddress + recordSize * ind;

    return nvDriver.Get(calculatedAddress, reinterpret_cast<tNvData*>(&nvItem), sizeof(tAntiWear));
  }
...
private:
  static std::uint32_t FindLastRecordPosition()
  {
    //      ,    
    //            
    //  ,  ,       
    //    0.
    return  0U;
   }
};
      
      



- , :





template<typename NvList, typename T, const T& defaultValue, tSeconds updateTime, auto& nvDriver>
class AntiWearNvData
{
 public:
    ReturnCode Set(const T& value) const
    {
        tAntiWear tempData = {.data = value, .index = nvItem.index};
        //     4        .
        //   2,         
        const auto calculatedAddress = GetCalculatedAdress(nvItem.index);

        ReturnCode returnCode = nvDriver.Set(calculatedAddress, reinterpret_cast<const tNvData*>(&tempData), sizeof(tAntiWear));
        //  std::cout << "Write at address: " << calculatedAddress << std::endl;
        //   ,     ,      1,   
        if (!returnCode)
        {
          nvItem.data = value;
          //     ,  ,     
          nvItem.index ++;
        }

        return returnCode;
    }

    static ReturnCode Init()
    {
        const auto ind = FindLastRecordPosition();
        constexpr auto startAddress = GetAddress();
        const auto calculatedAddress =  startAddress + recordSize * ind;

        return nvDriver.Get(calculatedAddress, reinterpret_cast<tNvData*>(&nvItem), sizeof(tAntiWear));
    }

    T Get() const
    {
        return nvItem.data;
    }

    static ReturnCode SetToDefault()
    {
        ReturnCode returnCode = nvDriver.Set(GetCalculatedAdress(nvItem.index), reinterpret_cast<const tNvData*>(&defaultValue), sizeof(T));
        return returnCode;
    }
 private:

   static size_t GetCalculatedAdress(std::uint32_t ind)
   {
       constexpr auto startAddress = GetAddress();
       size_t result = startAddress + recordSize * ((ind % recordCounts));
       assert(result < std::size(EEPROM));
       return result;
   }
   static std::uint32_t FindLastRecordPosition()
   {
       //             ,  ,
       //          1 -    15   5.
       return  1U;
   }
   constexpr static auto GetAddress()
   {
     return NvList::template GetAddress<const AntiWearNvData<NvList, T, defaultValue, updateTime, nvDriver>>();
   }

   struct tAntiWear
   {
    T data = defaultValue;
    std::uint32_t index = 0U;
   };

   inline static tAntiWear nvItem;

  public:
      static constexpr auto recordSize = sizeof(nvItem) * 2U;
      static_assert(eepromPageSize/recordSize != 0, "Too big parameter");
      static constexpr size_t recordCounts =  (eepromPageSize/recordSize) * eepromLifeTime / (eepromWriteCycles * updateTime);

};
      
      



CachedNvData



, , , CachedNvData



, AntiWearNvData



.





, IAR ++17, , . , SetToDefault



Init



. , , . , .





template<const tNvAddress startAddress, typename ...TNvVars>
struct NvVarListBase
{
static ReturnCode SetToDefault()
{
return ( ... || TNvVars::SetToDefault());
}

    static ReturnCode Init()
    {
        return ( ... || TNvVars::Init());
    }
    template<typename T>
    constexpr static size_t GetAddress()
    {
        return startAddress + GetAddressOffset<T, TNvVars...>();
    }

 private:

    template <typename QueriedType, typename T, typename ...Ts>
    constexpr static size_t GetAddressOffset()
    {
        auto result = 0;
        if constexpr (!std::is_same<T, QueriedType>::value)
        {
            //  ,       .
            result = T::recordSize * T::recordCounts + GetAddressOffset<QueriedType, Ts...>();
        }
        return result;
    }
};
      
      



CachedNvData



recordSize



recordCounts = 1



. .





, :





struct NvVarList;
constexpr NvDriver nvDriver;

using tString6 = std::array<char, 6U>;

inline constexpr float myFloatDataDefaultValue = 10.0f;
inline constexpr tString6 myStrDefaultValue = { "Popit" };
inline constexpr std::uint32_t myUint32DefaultValue = 0x30313233;
inline constexpr std::uint16_t myUin16DeafultValue = 0xDEAD;

constexpr CachedNvData<NvVarList, float, myFloatDataDefaultValue, nvDriver> myFloatData;
constexpr CachedNvData<NvVarList, tString6, myStrDefaultValue, nvDriver> myStrData;
constexpr CachedNvData<NvVarList, std::uint32_t, myUint32DefaultValue, nvDriver> myUint32Data;
constexpr AntiWearNvData<NvVarList, std::uint32_t, myUint32DefaultValue, 60U, nvDriver> myUint32AntiWearData;
constexpr AntiWearNvData<NvVarList, float, myFloatDataDefaultValue, 60U, nvDriver> myFloatAntiWearData;

struct SomeSubsystem
{
   static constexpr auto test = CachedNvData < NvVarList, std::uint16_t, myUin16DeafultValue,  nvDriver>();
};

//*** Register the Shadowed Nv param in the list *****************************
struct NvVarList : public NvVarListBase<0,
                                        decltype(myStrData),
                                        decltype(myFloatData),
                                        decltype(SomeSubsystem::test),
                                        decltype(myUint32Data),
                                        decltype(myFloatAntiWearData),
                                        decltype(myUint32AntiWearData)
                                       >
{
};
      
      



, , , , . CachedNvData



.





int main()
{
   NvVarList::SetToDefault();
   ReturnCode returnCode = NvVarList::Init();

    myFloatData.Set(37.2F);
    myStrData.Set(tString6{"Hello"});

    myFloatAntiWearData.Set(10.0F);
    myFloatAntiWearData.Set(11.0F);
    myFloatAntiWearData.Set(12.0F);
    myFloatAntiWearData.Set(13.0F);
    myFloatAntiWearData.Set(14.0F);

    myUint32AntiWearData.Set(10U);
    myUint32AntiWearData.Set(11U);
    myUint32AntiWearData.Set(12U);
    myUint32AntiWearData.Set(13U);
    myUint32AntiWearData.Set(14U);
    myUint32AntiWearData.Set(15U);

    return 1;
}
      
      



, 10,11,12...15 . , + + . , .





, 15 5 , 10 .





, , 5 15 .





, , , .





.








All Articles