The Sunday Blog

Nextion HMI as a full featured HEX calculator

I’m for sure not the only one among all Nextion HMI users who does a lot of embedded programming. In the old times, with 8bit MCUs, most of the calculus with numbers between 0 and 255 or between -128 and +127 could easily be done in mind or with pencil and paper. But then came the 16 and 32bit MCUs and things like calculating jump and port addresses or bit masks just got more difficult. Not everyone could afford a HP-16C programmer’s pocket calculator back in the mid 1980ies and they are not longer in production, today.

So, I thought by myself “That could be a nice blog project, we have a 32bit Cortex M0 MCU inside the Nextion HMI, that should be doable!”. And it was doable. I made a few design considerations, first: Not using RPN, HP’s uncommon but efficient input system, but “classic” algebraic input. Then, I listed and ordered all the functions I wanted to have and all the keys I wanted to see on the screen. This helped me to divide everything up into 3 groups: Entry keys (0-9, A-F) which allow entering numbers into the main display register (X), immediate function keys which don’t require waiting for a second operand: CLR, CE, X<>Y, VIEW, +/- (change sign), ! (bitwise NOT), SGN (extract the sign), and, as a special case “=”. And finally, the third group, operator keys like +, -, *, /, & (bitwise AND), | (bitwise OR), ^ (bitwise XOR), % (modulo), << (logical shift left), and >> (arithmetic shift right).

On top of that, I made an attempt to keep everything small, memory-wise, to teach a little about efficient programming. The HMI, when compiled for a 3.5″ Discovery HMI, uses 932 of the 3584 RAM bytes (26%) and 321868 of the 16M FLASH bytes (1.9%).

Now, with this in mind, I placed the keys in a special order on the page: First the entry keys 0-F with object ids from 1 to 16 (id0 being always the page itself), then the immediate function keys with object ids from 17 to 24, finally the operator keys with object ids from 25 to 34. This sequential setup allows to get around individual event handlers for each key and to process almost everything with the TouchCap component tc0. But first of all, we need to define a few numeric variables in programs.s:

int n_key,i_obj,n_buf,n_pop,n_lop
page 0                       //Power on start page 0

One TouchCap handles it all

After pressing any key, the TouchCap’s .val attribute will hold the object id of the key and then execute its touchPress and touchRelease event code, accordingly. The touchPress code handles basically the three cases cited above:
– If it’s an entry key, the key’s value 0-F will simply be appended to the main display (X) number component. Since it’s set for hexadecimal display, the 32bit numbers are displayed in up to 8 nibbles, each a 4bit value from 0 to F. Appending means shifting the content left by 4 bits and adding the key value, which is the key’s object id minus 1.
– If it’s an immediate function key, the corresponding function will directly be executed. To keep the code clean and easy to read, I packed these in a subroutine, the touchRelease event code of the hidden hotspot called “proc”, so that there is just a click proc,0.
– If it’s an operator key, the content of the main display (X) register is copied over to the secondary display register (the smaller one above) and the X register is freed up for the second operand to come. The pending operator is saved in a numeric variable n_pop to control the action of the “=” key when it will be pressed later. As a feature, the pending operator is also displayed in a small text component above the main display until it is executed.

Finally, the key number (object id-1) of the key will be stored in another variable n_lop to allow an undo of the last keystroke (if it was an entry or an operator key) with the “CE” function. 16 lines without the comments.

// get the key index
n_key=tc0.val-1
if(n_key>=0&&n_key<16)
{
  // simple number entry
  n_x.val=n_x.val<<4+n_key 
}else if(n_key>=16&&n_key<24) 
{ 
  // immediate operation 
  click proc,0 
}else if(n_key>=24&&n_key<34)
{
  //check if there is already a pending operation and execute it first (like pressing "=")
  click proc,1
  //save and display pending operation
  n_pop=n_key
  t_opr.txt=b[tc0.val].txt
  //copy x to y and clear x
  n_y.val=n_x.val
  n_x.val=0
}
// save last key for CE (undo)
n_lop=n_key

The immediate function handler

There are 8 functions to handle in the touchRelease event of the proc hotspot:
– CLR which clears all, the X and Y registers, and the pending operation if there is one.
– CE which undoes the last keystroke if it was an entry (just shift the x register right by 4 bits) or an operator (clear the n_pop and the corresponding display) key.
– X<>Y which swaps immediately the X and Y registers.
– VIEW which switches temporarily the display from HEX to DEC until released
– +/- which changes the sign of the value in the X register. Since the display is basically hexadecimal, you’ll see the 2’s complement of the number. But using VIEW will show you the decimal equivalent with the sign.
– ! which replaces the X value by its bitwise NOT value (1’s complement).
– SGN which replaces the X value by the value of its sign: 1 for positive numbers, -1 for negative numbers, and 0 for 0 values.
– = which executes the pending operation (if there is one) with the values of the X and Y registers and places the result in the X register. Since we are again distinguishing different cases, I packed that stuff in the touchPress event of our proc hotspot, so that we need only another click command here. 48 lines without the comments

if(n_key==16)
{
  //CLR
  n_x.val=0
  n_y.val=0
  n_pop=0
  t_opr.txt=""
}else if(n_key==17)
{
  //CE
  if(n_lop<16) 
  {
    //It was a number entry 
    n_x.val=n_x.val>>4
  }else if(n_lop>=24)
  {
    //it was a pending operator
    n_pop=0
    t_opr.txt=""
  }
}else if(n_key==18)
{
  //X<>Y
  n_buf=n_x.val
  n_x.val=n_y.val
  n_y.val=n_buf
}else if(n_key==19)
{
  //VIEW
  n_x.format=0
  n_y.format=0
  t_fmt.txt="DEC"
}else if(n_key==20)
{
  //+/- change sign
  n_x.val=-1*n_x.val
}else if(n_key==21)
{
  //! bitwise not
  n_x.val^=0xFFFFFFFF
}else if(n_key==22)
{
  //SGN replace x by the sign of x
  if(n_x.val>0)
  {
    n_x.val=1
  }else if(n_x.val<0)
  {
    n_x.val=-1
  }else
  {
    n_x.val=0
  }
}else if(n_key==23)
{
  // =
  click proc,1
}

Almost everything here is immediately executed when the key is pressed. The only key which needs a handler when released, is the VIEW key, to come back to the “normal” hexadecimal display. This is done in the touchRelease event code of our TouchCap component tc0. 7 lines without the comments:

// get the key  index
n_key=tc0.val-1
if(n_key==19)
{
  // return to hex view
  n_x.format=2
  n_y.format=2
  t_fmt.txt="HEX"
}

The big finale…

…happens, when “=” is pressed. There are 10 kinds of operators which can be pending in the n_pop variable, and that’s the moment where X = Y (operator) X is executed, before the Y register is cleared up. The code is pretty straightforward (37 lines without the comments):

if(n_pop==24)
{
  n_x.val=n_y.val+n_x.val
}else if(n_pop==25)
{
  n_x.val=n_y.val-n_x.val
}else if(n_pop==26)
{
  n_x.val=n_y.val*n_x.val
}else if(n_pop==27)
{
  n_x.val=n_y.val/n_x.val
}else if(n_pop==28)
{
  n_x.val=n_y.val&n_x.val
}else if(n_pop==29)
{
  n_x.val=n_y.val|n_x.val
}else if(n_pop==30)
{
  n_x.val=n_y.val^n_x.val
}else if(n_pop==31)
{
  n_x.val=n_y.val%n_x.val
}else if(n_pop==32)
{
  n_x.val=n_y.val<<n_x.val
}else if(n_pop==33)
{
  n_x.val=n_y.val>>n_x.val
}
if(n_pop>=24&&n_pop<34)
{
  // clean up only after a valid operation was processed
  n_y.val=0
  n_pop=0
  t_opr.txt=""
}

The end

Using all the new Nextion features and thoughtfully structuring your algorithm before you begin to write code and to design the GUI allows you to do a very efficient programming. This Demo project is naturally far from being perfect. For the sake of simplicity, there are no error handlers (for example when dividing by 0, or when there is an overflow in the result). But these are things which you may add to taste to this easily readable and understandable code.

As always, you may download the HMI file here: hexcalc.HMI and study it, play with it, improve it, or whatever you want to do.

Happy Nextioning!