Trabalhando com parâmetros em EEPROM

Introdução

Oi, Habr. Enfim, tenho tempo livre e posso compartilhar um pouco mais minha experiência, talvez seja útil para alguém e ajude no seu trabalho, e certamente ficarei feliz com isso. Nós vamos ...





Ao observar como os alunos realizam suas aulas, tento perceber os momentos que lhes causam problemas. Um desses pontos é trabalhar com uma EEPROM externa. É aqui que as preferências do usuário e outras informações úteis são armazenadas e não devem ser apagadas após desligar o instrumento. O exemplo mais simples é alterar as unidades de medida. O usuário pressiona o botão e altera as unidades de medida. Bem, ou anota os coeficientes de calibração por meio de algum protocolo externo, como Modbas.





Sempre que um aluno decide salvar algo na EEPROM, isso resulta em uma série de bugs relacionados à arquitetura escolhida incorretamente e apenas a um fator humano. Na verdade, um aluno geralmente fica online e encontra algo assim:





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



E agora podemos usar nossos parâmetros em qualquer lugar, muito simples e elementares:





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



Você pode passar uma referência a eles para qualquer classe, por meio de um construtor ou modelo.





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

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



Isso é tudo. Agora os alunos podem trabalhar com EEPROM de forma mais amigável e cometer menos erros, porque o compilador fará algumas das verificações para eles.





Link para o código de amostra aqui





PS Eu também queria falar sobre como você pode implementar um driver para trabalhar com EEPROM via QSPI (os alunos entenderam como ele funciona por muito tempo), mas o contexto acabou sendo muito heterogêneo, então acho que vou descrevê-lo em outro artigo , se é claro que é interessante.








All Articles