Trabajando con parámetros en EEPROM

Introducción

Hola, Habr. Finalmente, tengo tiempo libre y puedo compartir un poco más mi experiencia, tal vez sea de utilidad para alguien y ayude en su trabajo, y seguro que me alegraré de esto. Bien ...





Al observar cómo los estudiantes hacen su trabajo de curso, trato de notar los momentos que les causan problemas. Uno de estos puntos es trabajar con una EEPROM externa. Aquí es donde se almacenan las preferencias del usuario y otra información útil y no se deben borrar después de apagar la alimentación. El ejemplo más simple es cambiar las unidades de medida. El usuario presiona el botón y cambia las unidades de medida. Bueno, o escribe los coeficientes de calibración a través de algún protocolo externo, como Modbas.





Cada vez que un estudiante decide guardar algo en la EEPROM, esto da como resultado una gran cantidad de errores relacionados tanto con la arquitectura elegida incorrectamente como solo con un factor humano. En realidad, un estudiante generalmente se conecta a Internet y encuentra algo como esto:





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>
{
};
      
      



Y ahora podemos usar nuestros parámetros en cualquier lugar, muy simple y elemental:





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;
}
      
      



Puede pasarles una referencia a cualquier clase, a través de un constructor o plantilla.





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

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



Eso es todo. Ahora los estudiantes pueden trabajar con EEPROM de forma más sencilla y cometer menos errores, porque el compilador hará algunas de las comprobaciones por ellos.





Enlace al código de muestra aquí





PD : también quería contarles cómo se puede implementar un controlador para trabajar con EEPROM a través de QSPI (los estudiantes entendieron cómo funciona durante demasiado tiempo), pero el contexto resultó ser demasiado variopinto, así que creo describirlo en otro artículo. , si por supuesto es interesante.








All Articles