Thought process of a programming problem
3/11/2024
For my LED matrix sign project I store all the customisations and text in the ESP32's NVRAM. I do this in a very naive way of writing raw memory directly to the NVRAM packed into two data structures. This has one very big flaw, if I change the internal layout of these structures in any way, the memory in NVRAM will no longer be valid.
Before making any changes to the code, this is the layout of the structure in question:
struct { int pos; int brightness; char cssid[256]; char cpassword[256]; char date[32]; } globalconf;This struct contains all the configuration settings that may be used globally.
Specifically for this, we're interested in the brightness integer.
I wanted to add automatic brightness adjustment depending on the rooms current light level. I do this using an LDR. to achieve this, I needed a way to store whether or not the auto brightness feature is enabled or not.
My first naive implementation seen in this commit simply added more fields to the globalconf structure.
This worked, however it made my the current settings stored in the NVRAM incompatible. This is because the memory size of the structure changed by 16 (or so) bytes. When this memory is read into the new structure from NVRAM, it is incorrect.
I had to come up with a new solution, so I did some brainstorming and made notes in chat. I am going to paste those logs here and explain at the end the final solutions.
I could just pack that autobrightness bool into the brightness int as it is literally 1 bit required
then i'd not break the globalconf structure
the value of brightness is literally 0-15
so i could use the MSB to store the bool
i could do:
brightness |= autoBrightness ? (brightness | 0b10000000) : (brightness & 0b01111111);set the MSB if autobrightness is true, otherwise unset the MSB
then when i read the brightness later i need to read it like:
(brightness & 0b01111111)to ensure it is unset
or i KISS and leave it as is
OR i do the smarter thing actually
create a bit field with the same size as an int and do this built in
a bitfield lets you assign certain bits to certain values in a single packed bit of memory
struct brightness { unsigned int autoBrightness : 1; unsigned int brightness : 4; unsigned int RESERVED : 3; }this will give me 3 bits to play with later
this will use 1 byte of memory
brightness is 0-15 so 4 bits is plenty (can count to 15)
so i get MORE features and use 3 bytes less (as an int is 4 bytes)
but actually... i need to use all 4 bytes to not break my current memory structure
so really i need to do:
struct brightness { unsigned int autoBrightness : 1; unsigned int brightness : 4; unsigned int RESERVED : 27; }to use all 32 bits of an int
Essentially, as seen in this commit, I simply added a struct bitfield the same size as the fields it replaces.
struct brightness { unsigned int autoBrightness : 1; unsigned int brightness : 4; unsigned int RESERVED : 27; }; struct { int pos; struct brightness brightness; char cssid[256]; char cpassword[256]; char date[32]; } globalconf;and gives me 27 bits later on to play with.