Working with parameters in EEPROM, how not to wear out memory

Introduction

Good day. My last article about parameters in EEPROM was, to put it mildly, a little misunderstood. Apparently, I somehow crookedly described the goal and the task that was being solved. This time I will try to correct myself, describe in more detail the essence of the problem being solved, and this time we will expand the boundaries of the problem.





Namely, let's talk about how to store the parameters that need to be written to the EEPROM permanently.





It may seem to many that this is a very specific problem, but in fact, many devices are doing exactly this - they constantly write to the EEPROM. A water meter, a heat meter, an odometer, all sorts of user action logs and logs that store the history of measurements, or just any device that stores the time of its operation.





The peculiarity of such parameters is that they cannot be written just like that to the same place in the EEPROM, you will simply use up all the write cycles of the EEPROM. For example, if it is necessary to write the operating time once every 1 minute, then it is easy to calculate that with an EEPROM of 1,000,000 write cycles, you will ruin it in less than 2 years. And what is 2 years, if an ordinary measuring device has a verification time of 3 or even 5 years.





In addition, not all EEPROMs have 1,000,000 write cycles, many cheap EEPROMs are still produced according to old technologies with 100,000 writes. And if we consider that 1,000,000 cycles are indicated only under ideal conditions, say at high temperatures this number can be halved, then your EEPROM may turn out to be the most unreliable element already in the first year of the device's operation.





Therefore, let's try to solve this problem, and make it so that accessing the parameters was as simple as in the previous article, but at the same time the EEPROM would be enough for 30 years, well, or for 100 (purely theoretically).





So, in the last article, I hardly showed how to do it so that the parameters in the EEPROM can be worked intuitively, without thinking about where they lie and how to access them.





Let me remind you:





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