Working with parameters in EEPROM

Introduction

Hi, Habr. Finally, I have free time and I can share my experience a little more, maybe it will be useful for someone and help in his work, and I will certainly be glad of this. Well ...





By looking at how students do their coursework, I try to notice the moments that cause them trouble. One of these points is working with an external EEPROM. This is where user settings and other useful information are stored and should not be erased after turning off the power. The simplest example is changing units of measurement. The user presses the button and changes the units of measurement. Well, or writes down the calibration coefficients through some external protocol, such as Modbas.





Whenever a student decides to save something in the EEPROM, this results in a lot of bugs related to both the wrongly chosen architecture and just a human factor. Actually, a student usually goes online and finds something like this:





int address = 0;
float val1 = 123.456f;
byte val2 = 64;
char name[10] = "Arduino";

EEPROM.put(address, val1);
address += sizeof(val1); //+4
EEPROM.put(address, val2);
address += sizeof(val2); //+1
EEPROM.put(address, name);
address += sizeof(name); //+10
      
      



, 100 EEPROM , , . , - .





, , , EEPROM , . EEPROM, , .





:





  • EEPROM . EepromManager



    , EEPROM , , , EEPROM.





    : EEPROM, .





    : , - , Modbus , , - , , . , . , , , . . , - , , .





  • - .





    , . EEPROM , .





    : , , - , EEPROM.





    , , , 5 , EEPROM , . , , EEPROM, , , , ( .. ) 5 10 , .





, , , , , , , :





// 10.0F  EEPROM  ,   myEEPROMData  
myEEPROMData = 10.0F;
      
      



, , EEPROM . , - :





//  EEPROM   5     myStrData
auto returnStatus = myStrData.Set(tStr6{"Hello"}); 
if (!returnStatus)
{
	std::cout << "Ok"
}
//  EEPROM float     myFloatData
returnStatus = myFloatData.Set(37.2F); 
      
      







, , .





, . :





  • () EEPROM





    • , , , ,





  • EEPROM,





    • EEPROM I2C SPI. , , .





  • , EEPROM, - .





  • EEPROM, EEPROM, , , , , .





  • :)





, : CahedNvData







CachedNvData





, :





Init()



EEPROM .





, . data



, - , Get()



.





, EEPROM nvDriver



. nvDriver, , Set()



Get()



. , .





NvDriver





@gleb_l , EEPROM, , , , , .





, , . , , , EEPROM - . .





, 3 :





//  6 
constexpr CachedNvData<NvVarList, tString6, myStrDefaultValue,  nvDriver> myStrData;
//  4 
constexpr CachedNvData<NvVarList, float, myFloatDataDefaultValue, nvDriver> myFloatData;
//  4 
constexpr CachedNvData<NvVarList, std::uint32_t, myUint32DefaultValue,  nvDriver> myUint32Data; 

      
      



- :





NvVarList<100U, myStrData, myFloatData, myUint32Data>
      
      



myStrData



100, myFloatData



- 106, myUint32Data



- 110. .





, EEPROM. GetAdress()



, .





, , . , , , .





, NvVarListBase:





NvVarListBase





.





- . ,





CahedNvData

template<typename NvList, typename T, const T& defaultValue, const auto& nvDriver>
class CahedNvData
{
  public:
    ReturnCode Set(T value) const
    {
      //  EEPROM    
      constexpr auto address = 
                NvList::template GetAddress<NvList,T,defaultValue,nvDriver>();
      //    EEPROM
      ReturnCode returnCode = nvDriver.Set(
                                address,
                                reinterpret_cast<const tNvData*>(&value), sizeof(T));
      //   ,    
      if (!returnCode)
      {
        memcpy((void*)&data, (void*)&value, sizeof(T));
      }
      return returnCode;
    }

    ReturnCode Init() const
    {
      constexpr auto address = 
                NvList::template GetAddress<NvList,T,defaultValue,nvDriver>();
      //   EEPROM
      ReturnCode returnCode = nvDriver.Get(
                                address, 
                                reinterpret_cast<tNvData*>(&data), sizeof(T));
      //     EEPROM,    
      if (returnCode)
      {
        data = defaultValue;
      }
      return returnCode;
    }

    T Get() const
    {
      return data;
    }
    
    using Type = T;
  private:
    inline static T data = defaultValue;
};
      
      



template<const tNvAddress startAddress, const auto& ...nvVars>
struct NvVarListBase
{    
    template<typename NvList, typename T, const T& defaultValue, const auto& nvDriver>
    constexpr static size_t GetAddress()
    { 
      // EEPROM     
      //CahedNvData<NvList, T, defaultValue, nvDriver>
      using tQueriedType = CahedNvData<NvList, T, defaultValue, nvDriver>;      
      
      return startAddress + 
            GetAddressOffset<tQueriedType>(NvVarListBase<startAddress,nvVars...>());
    }
    
  private:
    
   template <typename QueriedType, const auto& arg, const auto&... args>    
   constexpr static size_t GetAddressOffset(NvVarListBase<startAddress, arg, args...>)
   {
    //      , 
    //        
    auto test = arg;
    //        ,   
    if constexpr (std::is_same<decltype(test), QueriedType>::value)
    {
        return  0U;
    } else
    {
      //          
      //   .
        return sizeof(typename decltype(test)::Type) + 
                GetAddressOffset<QueriedType>(NvVarListBase<startAddress, args...>());
    }
  }    
};
      
      



.





:





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

inline constexpr float myFloatDataDefaultValue = 10.0f;
inline constexpr tString6 myStrDefaultValue = {"Habr "};
inline constexpr std::uint32_t myUint32DefaultValue = 0x30313233;
      
      



:





//    ,    . 
// forward declaration
struct NvVarList;   
constexpr NvDriver nvDriver;
//   NvVarList   EEPROM 
constexpr CahedNvData<NvVarList, float, myFloatDataDefaultValue, nvDriver> myFloatData;
constexpr CahedNvData<NvVarList, tString6, myStrDefaultValue,  nvDriver> myStrData;
constexpr CahedNvData<NvVarList, uint32_t, myUint32DefaultValue,  nvDriver> myUint32Data;
      
      



. , EEPROM . NvVarListBase, .





struct NvVarList : public NvVarListBase<0, myStrData, myFloatData, myUint32Data>
{
};
      
      



And now we can use our parameters anywhere, very simple and elementary:





struct NvVarList;
constexpr NvDriver nvDriver;
using tString6 = std::array<char, 6U>;

inline constexpr float myFloatDataDefaultValue = 10.0f;
inline constexpr tString6 myStrDefaultValue = {"Habr "};
inline constexpr uint32_t myUint32DefaultValue = 0x30313233;

constexpr CahedNvData<NvVarList, float, myFloatDataDefaultValue, nvDriver> myFloatData;
constexpr CahedNvData<NvVarList, tString6, myStrDefaultValue,  nvDriver> myStrData;
constexpr CahedNvData<NvVarList, uint32_t, myUint32DefaultValue,  nvDriver> myUint32Data;

struct NvVarList : public NvVarListBase<0, myStrData, myFloatData, myUint32Data>
{
};

int main()
{    
    myStrData.Init();
    myFloatData.Init();
    myUint32Data.Init()
    
    myStrData.Get();
    returnCode = myStrData.Set(tString6{"Hello"});
    if (!returnCode)
    {
        std::cout << "Hello has been written" << std::endl;
    }
    myStrData.Get();
    myFloatData.Set(37.2F);    
    myUint32Data.Set(0x30313233);    
    return 1;
}
      
      



You can pass a reference to them to any class, through a constructor or template.





template<const auto& param>
struct SuperSubsystem
{
  void SomeMethod()
  {
    std::cout << "SuperSubsystem read param" << param.Get() << std::endl; 
  }
};

int main()
{  
  SuperSubsystem<myFloatData> superSystem;
  superSystem.SomeMethod();
}
      
      



That's all. Now students can work with EEPROM more user-friendly and make fewer mistakes, because the compiler will do some of the checks for them.





Link to sample code here





PS I also wanted to tell you about how you can implement a driver for working with EEPROM via QSPI (students understood how it works for too long), but the context turned out to be too motley, so I think I will describe it in another article, if of course it will be interesting.








All Articles