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.