The Sunday Blog: Dynamic Data display

Using arrays (or kind of)

Everything started when I got a PM from a member of a Nextion related Facebook group. Let’s call him M. M. reached out for help. His hobby is apparently tuning cars, and especially engines, as far as I could discover looking at his FB profile. He seems to be on a project using an extremely powerful MCU (Teensy 4.1) to read and decode CAN bus or ODBII live data and to display these in real time. His question was:
“I want to display 4 values simultaneously on my screen, out of a list of 44 values. That’s why my MCU does not only send the 4 values to display, but also four corresponding ids between 1 and 44 to let the Nextion know which type of data each is. I want then the Nextion to display the corresponding (to the id) captions and units. My first approach is long, and slow. Basically, I would for each of the four values, take the corresponding id and then have 44 if() clauses to add the caption and the unit texts, which makes in total 4 * 44 = 176 if() clauses each time when the screen is refreshed. Is there a more efficient way? As far as I understand, there are no arrays in Nextion language which could simplify my code…”

My answer was: “Hi, M.! There are arrays (or kind of) in Nextion language and there is for sure a simpler and quicker way to “automatically” add captions and units. Are you ok with me making a blog project from this? If yes, you’ll get the solution for free on Sunday.”

He was ok with that idea and here is the solution. Since I knew from questions he had asked earlier that he had also been struggling with displaying floating point or decimal numbers, I thought, I’d add that, too, since I had written about this last Sunday. And finally, I decided to use a packing strategy as described here, so that his MCU will from now on only have to set 4 numeric variables (and without transmitting separate ids) on the Nextion. Spoiler: It takes just 10 lines of code on the Nextion side, or even 8 if you don’t count the almost empty lines which only hold brackets. But one after the other…

Think first, code later!

For this demo project, I didn’t use the whole list of 44 possible ids, but only 9 which doesn’t change anything on the code side. But I’m not an engine specialist, thus I decided to limit to the few engine parameters which I know and which I understand. Since M. lives in Germany, I decided to use Celsius temperatures and metric units, but the principle is the same. There are (as a purely theoretical example)…

  • The engine speed, measured in rpm, displayed without (0) decimals (id=1)
  • The oil and coolant temperatures, measured in °C with 1 decimal (id=2,3)
  • The airflow, measured in g/s without (0) decimals (id=4)
  • The air temperature, measured in °C with 1 decimal (id=5)
  • The fuel pressure, measured in bar, with 3 decimals (id=6)
  • The throttle, measured in % without (0) decimals (id=7)
  • The short term fuel trim (STFT) and the long term fuel trim (LTFT), measured in % with 1 decimal (id=8,9)

It’s important to have such a finalized data table like structure before you start coding! This allows you to do all the required data transformations at design time. Captions and units are both text components, thus we may use the spstr function to extract the required caption and unit with the help of the id from much longer text variables which hold all the caption texts or unit texts, separated by a semicolon. That’s why on the page, there is text variables called captions and units and which are preset at design time with all the captions and units, adding an additional separator at the beginning to return an empty value if the id is 0 (zero), because that’s where the spstr function starts.

Building the pseudo-arrays and retrieving data from these

captions‘ .txt attribute looks like this: ;Engine speed;Oil temp;Coolant temp;Airflow;Air temp;Fuel press;Throttle;STFT;LTFT;…
Thus, if I wanted to set the .txt attribute of the caption1 text component according to id=7, spstr captions.txt,caption1.txt,”;”,7 does that job and writes “Throttle” in the caption1 text component.

units‘ .txt attribute is preset to: ;rpm;°C;°C;g/sec;°C;bar;%;%;%;…
Again, spstr units.txt,unit1.txt,”;”,7 will write the 7th unit text, “%” in the unit1 text component

Since each data type, defined by its id, has a fixed number of decimals as shown in the list above, a similar proceeding helps us to retrieve the corresponding number of decimals to display. It’s just that the spstr function alway returns text and not a numeric value. So, we have to extract the number of decimals for our id from our “array” into a temporary txt variable with spstr and then use covx to transform it into a number which will determine the .vvs1 attribute of our Xfloat component which displays the data. The advantage is already here that the MCU can send an integer value as a pseudo float because the Nextion cares about the decimals.

decimals‘ .txt attribute looks like this: 0;0;1;1;0;1;3;0;1;1;… thus, spstr decimals.txt, tmp.txt,”;”,7 will place a “0” string in tmp.txt and covx tmp.txt,value1.vvs1,1,0 will set the .vvs1 attribute (number of decimals) of our Xfloat to a numeric 0 from that string.

Almost the last step: packing

A 32bit integer can hold values from -2147483648 to 2147483647 which allows the representation of more than 10 significant digits. So much precision is not required for these data types. Thus, we may sacrifice 6 bits of the 32 to hold an id (between 0 and 63, even a little more than required) and still get a range from -33554432 to 33554431, more than 8 significant digits.

That means that our MCU can pack the value to display as an integer (the Nextion will know how many digits are decimals) by shifting it 6 bits to the left and adding the id, before sending it. As an example, for the second (Fuel pressure) line in the above picture, the MCU would hold the pseudo float as an integer 3134 and pack it with the id=6 in a one-liner directly before sending it to the Nextion: int32_t tmp = 3141<<6+6; and then NexSerial.print(“va0.val=”);NexSerial.print(tmp);Nexserial.print(“0xFF0xFF0xFF”) which greatly reduces the data to transmit and speeds up communication, compared to sending separate ids for each value.

And finally the indirect addressing of the components

In order to loop through our 4 input variables va0 to va3, and to set the caption text components caption0 to caption3, the value xfloat components value0 to value3, and the unit text components unit0 to unit3 accordingly with indirect addressing, you’ll have to take care when placing the components on the page.

All 4 components in a block (input variables, caption texts, value xfloats, unit texts) must have consecutive ids. va0 has 1, va1 has 2 and so on, then caption0 has 5, caption1 has 6,…, value0 has 9, value1 has 10,…,unit0 has 13, unit1 has14, you see the principle.

This allows us to have a loop variable k from 0 to 3. The id of each input var vaX is then va0.id+k, which we hold in a global index helper variable ihlp. Then, the packed value which came from the MCU is in b[ihlp].val (yes, components on a page can directly be addressed via the b[] array!). The corresponding caption field can then be addressed by b[ihlp+4], the value field by b[ihlp+8], and the unit field by b[ihlp+12]. That’s it. And that’s why everything can be done as a timer event code in 8/10 lines, especially since the unpacking is a simple as the packing was on MCU side:

for(k=0;k<4;k++) 
{
  ihlp=va0.id+k // get the index of the current input variable to process 
  id=b[ihlp].val&0x3F // get the type of the current data 
  spstr captions.txt,b[ihlp+4].txt,";",id // display the corresponding caption 
  spstr decimals.txt,tmp.txt,";",id // get the # of decimals from string 
  covx tmp.txt,b[ihlp+8].vvs1,1,0 // transform into number and adjust decimal display accordingly 
  b[ihlp+8].val=b[ihlp].val>>6 // display the value
  spstr units.txt,b[ihlp+12].txt,";",id // display the corresponding unit
}

And here is the demo project:datarraydemo.HMI . To check it out, you don’t even need to have an external MCU to feed your Nextion with data. Load it in the editor and then start the simulator. Use the Instruction Input Area to set va0 to va3 with packed values like in the example below, which gives you the screen display as in the picture above:

va0.val=2800<<6+1
va1.val=3141<<6+6
va2.val=45<<6+7
va3.val=825<<6+3

Happy Nextioning!