The Sunday Blog: Data packing and saving EEPROM space

A practical example

After we learned about the theory of bit manipulation and using it to pack digital information efficiently into bigger entities like bytes (8 bits) or words (32 bits) in last week’s Sunday Blog, we will, after a few additional informations and considerations, see how we can save the state of 8 dual state buttons and 3 sliders in a single 32bit integer variable (even without using its full width) which takes only 4 of the 1024 EEPROM bytes. As a side effect, you may discover in today’s example project how to have something similar to the switch component, only available on the Intelligent HMI series, in virtually all Nextion HMI models, just by optically tweaking the dual state button component.

A word before about binary and hexadecimal

Last week, we learned about bit manipulation using the example of 8bit groups (bytes). The same applies naturally to 32bit groups (words). But that seemed not clear to some readers as I could discover from the feedback on social media. It’s just that the binary (bit for bit) representation of a 32bit word is difficult to read and takes up much space. Think of
0 0 0 0 1 0 1 0 1 0 1 0 1 0 0 0 0 1 1 0 0 0 0 1 1 1 1 0 0 1 0 0 … That’s why most people prefer a different representation of the same number, grouping the binary into groups of 4 bits first, and then replacing each 4bit group (which is also called nibble or half-byte) by its hexadecimal equivalent. Lets do it:
0 0 0 0 | 1 0 1 0 | 1 0 1 0 | 1 0 0 0 | 0 1 1 0 | 0 0 0 1 | 1 1 1 0 | 0 1 0 0
0 | A | A | 8 | 6 | 1 | E | 8 or commonly noted as 0x0AA861E8 or 0AA861E8h. The 0x prefix or the h suffix indicate that it is a hexadecimal representation, which we will use from now on.

The demo project

On our demo screen, we see 8 dual state buttons whose .val attribute can take the values 0 or 1 which means that one single bit is sufficient to save its state. Then, there are 3 sliders whose .val attribute can take any number from 0 to 100. That asks to reserve 7 bits for each, since 7 bits allow to represent any number between 0 and 127. Thus, we organise the 32 bits of our word as follows:
A1 A2 A3 A4 A5 A6 A7 A8 S1’6 S1’5 S1’4 S1’3 S1’2 S1’1 S1’0 S2’6 S2’5 S2’4 S2’3 S2’2 S2’1 S2’0 S3’6 S3’5 S3’4 S3’3 S3’2 S3’1 S3’0 while le 3 leftmost bits are unused and remain at 0 0 0.

As a reminder: One particular bit in position n of a word can be set by generating a so-called bit mask 1 << n and to logical OR the word with it. To clear this bit, we negate the bit mask (which toggles our bit from 1 to 0 and all others from 0 to 1) and we AND it with our 32bit word. In a similar way, we proceed with our 7bit groups for the sliders. It’s just that we can’t simply OR our 7bit value if we don’t know the actual value of the corresponding bit field. That’s why we build a 7bit mask (with 0x7F) first, shift it in place for S1 or S2, negate it and AND it first with our word, so that the bit field is cleared, before we take the new value, shift it if needed, and OR it to set the corresponding bits.

Thus, the state of all the 11 screen controls is saved in a single 32bit word and can be stored in the EEPROM and later be recalled, which requires then “unpacking” everything to update our components’ values from it. In the project, you’ll find another trick to reduce the risk of wearing out the EEPROM which has infinite read cycles but only a limited number of write cycles: Before writing, we compare the value to write with the result of the last reading (saved in a global variable rom_data) and we execute the wepo function at the address defined in the rom_address variable only if they are different since it makes absolutely no sense to overwrite data with an identical value…

That makes, that our program.s file is very short and compact, defining 4 variables and loading the start page. 2 of the 4 variables could also be taken from the old style sys variables which are still supported for backwards compatibility, but I prefer moving on and define variables as I need them.

int rom_address=0,rom_data,tmp,k
page 0                       //Power on start page 0

In the page PostInitialize Event, we need to recall the saved state from the EEPROM and to update the controls. This is done by simply clicking the Recall hotspot with

click m1,1

What happens when the Recall hotspot is clicked ? We read the 32bit word from the EEPROM, copy it into the tmp variable and use a sophisticated combination of looping, shifting masking and indirect component addressing to update everything on our screen from it. That allows to do everything in only 15 lines of code (even only 11 if you don’t count the brackets). And we get 15 components updated from it, when you take the additional number components near the sliders and the data control field at the bottom into account.

repo rom_data,rom_address
tmp=rom_data
n3.val=tmp // update the packed data display
for(k=14;k>=10;k--)
{
  b[k].val=tmp&0x0000007F // extract last 7 bits and update number component
  k--
  b[k].val=b[k+1].val // set slider.val to number component
  tmp>>=7 //shift the next 7bit group into rightmost position
}
for(k=8;k>0;k--)
{
  b[k].val=tmp&0x00000001 // extract last single bit and update dual state button
  tmp>>=1 //shift the next bit into rightmost position
}

And now, no, packing is not done by pressing the Save hotspot. Since it’s a pedagogic project, packing is done on the fly and the result displayed in real time in the int32 data display at the bottom of the screen. To avoid having a dedicated and redundant packing routine in the TouchRelease event of each component, everything is concentrated in the TouchRelease event of a TouchCap component, using only 16 lines (without comments and rackets) :

// handle the dual state buttons
if(tc0.val>0&&tc0.val<9)
{
  // build the bit mask
  k=29-tc0.val
  tmp=1<<k
  if(b[tc0.val].val==1)
  {
    n3.val|=tmp // set the bit with OR
  }else
  {
    tmp=-1-tmp
    n3.val&=tmp // clear the bit by AND with negated mask
  }
}
// handle the sliders
if(tc0.val==13||tc0.val==11||tc0.val==9)
{
  b[tc0.val+1].val=b[tc0.val].val // update the number field
  //determine the start position and build the 7bit mask
  k=13-tc0.val/2*7 // 0, 7 or 14
  tmp=0x0000007F<<k
  tmp=-1-tmp
  n3.val&=tmp //clear the 7bit group
  // sanitize the slider value and shift it into position
  tmp=b[tc0.val].val&0x0000007F<<k
  n3.val|=tmp // set the previously cleared bit group with OR
}

Last step is saving the already packed data in n3.val to the EEPROM after checking if the content has changed, compared to the rom_data variable, in the TouchPress event of the Save hotspot:

// update eeprom only if data has changed and read back
if(n3.val!=rom_data)
{
  wepo n3.val,rom_address
  repo rom_data,rom_address
}

That’s it. No black magic, no rocket science, simply efficient coding. To play with that and to re-use it in your own projects, you may – as usual – download the hmi file here: datapack480x320K.HMI

Happy Nextioning!