Trabalhando com parâmetros em EEPROM, como não desgastar a memória

Introdução

Dia bom. Meu último artigo sobre parâmetros na EEPROM foi, para dizer o mínimo, um pouco mal interpretado. Aparentemente, eu de alguma forma descrevi de maneira tortuosa o objetivo e a tarefa que estava sendo resolvida. Desta vez, tentarei me corrigir, descrever com mais detalhes a essência do problema que está sendo resolvido e, desta vez, expandiremos os limites do problema.





Ou seja, vamos falar sobre como armazenar os parâmetros que precisam ser gravados na EEPROM permanentemente.





Pode parecer a muitos que este é um problema muito específico, mas na verdade, muitos dispositivos estão fazendo exatamente isso - eles gravam constantemente na EEPROM. Um medidor de água, um medidor de calor, um hodômetro, todos os tipos de registros de ações do usuário e registros que armazenam o histórico das medições, ou apenas qualquer dispositivo que armazena o tempo de sua operação.





A peculiaridade de tais parâmetros é que eles não podem ser escritos dessa forma no mesmo lugar na EEPROM, você simplesmente usará todos os ciclos de escrita da EEPROM. Por exemplo, se for necessário escrever o tempo de operação uma vez a cada 1 minuto, então é fácil calcular que com uma EEPROM de 1.000.000 ciclos de gravação, você irá estragá-la em menos de 2 anos. E o que é 2 anos, se um dispositivo de medição comum tem um tempo de verificação de 3 ou mesmo 5 anos.





Além disso, nem todos os EEPROMs têm 1.000.000 de ciclos de gravação, muitos EEPROMs baratos ainda são produzidos de acordo com tecnologias antigas com 100.000 gravações. E se considerarmos que 1.000.000 de ciclos são indicados apenas em condições ideais, digamos que em altas temperaturas esse número pode ser reduzido pela metade, então sua EEPROM pode acabar sendo o elemento menos confiável no primeiro ano de operação do dispositivo.





Portanto, vamos tentar resolver este problema, e fazer com que o acesso aos parâmetros seja tão simples como no artigo anterior, mas ao mesmo tempo a EEPROM bastasse para 30 anos, bem, ou para 100 (puramente teoricamente).





Então, no último artigo, dificilmente mostrei como fazer para que os parâmetros da EEPROM possam ser trabalhados intuitivamente, sem pensar onde estão e como acessá-los.





Deixe-me lembrá-lo:





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