introduction
Salut, Habr. Enfin, j'ai du temps libre et je peux partager un peu plus mon expérience, peut-être que cela sera utile à quelqu'un et l'aidera dans son travail, et j'en serai certainement ravi. Bien ...
En regardant comment les étudiants font leurs cours, j'essaie de remarquer les moments qui leur causent des problèmes. L'un de ces points est de travailler avec une EEPROM externe. C'est là que les préférences de l'utilisateur et d'autres informations utiles sont stockées et ne doivent pas être effacées après la mise hors tension. L'exemple le plus simple est le changement d'unités de mesure. L'utilisateur appuie sur le bouton et change les unités de mesure. Eh bien, ou écrit les coefficients d'étalonnage via un protocole externe, tel que Modbas.
Chaque fois qu'un étudiant décide de sauvegarder quelque chose dans l'EEPROM, cela se traduit par de nombreux bugs liés à la fois à l'architecture mal choisie et à un simple facteur humain. En fait, un étudiant va généralement en ligne et trouve quelque chose comme ceci :
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>
{
};
Et maintenant nous pouvons utiliser nos paramètres n'importe où, très simple et élémentaire :
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;
}
Vous pouvez leur transmettre une référence à n'importe quelle classe, via un constructeur ou un modèle.
template<const auto& param>
struct SuperSubsystem
{
void SomeMethod()
{
std::cout << "SuperSubsystem read param" << param.Get() << std::endl;
}
};
int main()
{
SuperSubsystem<myFloatData> superSystem;
superSystem.SomeMethod();
}
C'est tout. Désormais, les étudiants peuvent travailler avec l'EEPROM de manière plus conviviale et faire moins d'erreurs, car le compilateur effectuera certaines des vérifications à leur place.
Lien vers un exemple de code ici
PS Je voulais aussi vous parler de la façon dont vous pouvez implémenter un pilote pour travailler avec EEPROM via QSPI (les étudiants ont compris comment cela fonctionne trop longtemps), mais le contexte s'est avéré trop hétéroclite, je pense donc le décrire dans un autre article , si c'est bien sûr intéressant.