The Sunday blog: Boost your HMI with advanced mathematics!

Part 2: The binary fixed point format

As we have seen in part 1, it is basically possible to perform calculations with (almost) any accuracy, even on systems that have only very limited mathematical capabilities and no coprocessor. This is not only true for our Nextion HMIs, but also for many other small 32-bit microprocessor systems, which can be found in industrial control and hobby applications, for example ATSAMD21, TEENSY LC, STM32F0 and many more. With the latter we often don’t have to worry about, because the C/C++ compiler replaces missing functionality with corresponding software routines on assembler level. But such operations are much slower than if they are executed directly by the corresponding hardware, which can lead to timing problems.

With our Nextion, however, we only have our own programming language. But it can do a lot more with it than the first look at the documentation suggests, as we will see below and in the next episodes. But first of all we will continue with the basics:

Decimal places in binary format

In the last episode, we used some examples in the decimal system to illustrate what it means to perform arithmetic operations when some digits at the right end of an integer are “rededicated” as decimal places by definition. That was quite encouraging. So why switch to the binary system now?

Remember: Especially for multiplication and division, it was necessary to either “borrow” digits or (possibly after rounding) cut them off. This requires additional multiplications and divisions. In the binary system, on the other hand, it is sufficient to shift our significant digits to the left or right with the simple bit-shift operations << and >>, which uses significantly less CPU resources and is faster.

The q15 format

One of the most popular variants in the 32-bit world is the use of the q15 format. This means that 15 LSBs are considered as decimals, leaving 16 bits for the integer part and the MSB for the sign:

Why just 15 bits? Let’s remember: When multiplying two numbers with n decimal places, the result will initially have 2n decimal places, in our case 30 of the 32 available bits. If we don’t want the sign bit to be lost due to overflow, the multiplicand and multiplier must not have an integer part anymore. In this respect, this format is perfectly suited for numbers between -1 and 1, as they are often found in trigonometry. The accuracy is also excellent, since e.g. the number -1 in q15 format is represented as 10000000 0000000b, the last digit has a value of 2^(-15), which is about 0.00003. If such a result were used to calculate x and y coordinates, our HMI would have to have a height or width of more than 32768 pixels before we risk being off by even a single pixel.

The calculation rules

As shown above, adding and subtracting two q15 numbers also produces a q15 result. The multiplication of two q15 numbers first produces a q30 intermediate result, which we can then scale (and possibly round) back to q15 by a >>15 operation. The direct division of two q15 numbers would yield an (extremely inaccurate) q0 result, which is why we first extend the dividend to q30 with a <<15 operation. After division by the q15 divisor, the result is then back in the desired q15 format. Scaling with an integer (q0), e.g. to calculate screen coordinates, is done by multiplying q0 by q15, which first produces a q15 result, which we then take to q0, i.e. an integer again, with >>15. We see that it is the same algorithm as for the q15xq15 multiplication. So it’s not the absolute numbers that matter, but the way we look at them!

The cornerstone of our “function library”

With what we know so far, we can already come up with the first subroutine for multiplication in Nextion Code, which will make our lives easier in the future:

q15_mul_q15_q15(r,x,y) : r=x*y>>15 //simple multiplication (also known as q0_mul_q15_q0)
q15_mlr_q15_q15(r,x,y) : r=x*y+16384>>15 //rounded multiplication (also as q0_mlr_q15_q0)

We will need Division less frequently, but it is mentioned here nevertheless:
q15_div_q15_q15(r,x,y) : r=x<<15/y

Polynomials

In many cases, more complex functions in computer-based mathematics are approximated by polynomials. In this respect, let us take a look at a classical second-order polynomial of the form y=a(x^2)+bx+c, whose diagram is a parabola. What we have (perhaps unconsciously) used in our function library, namely the fact that the Nextion interpreter processes our arithmetic instructions strictly from left to right, efficient and processor register based, without moving forth and back, trying to observe the ” classical ” rules and priorities, but always uses the last intermediate result as a new operand, seems to make arithmetic work with polynomials more difficult at first glance, since we would have to calculate powers and products in advance and buffer them before adding them up. But no, there is a simple way, which is so to speak tailored to our nextion:

The Ruffini-Horner Method

As early as the 19th century, when many things still had to be calculated “by hand”, people were already looking for ways to simplify their lives. Mr. Ruffini and Mr. Horner came up with a simple system independently of each other in Italy and England. By slightly rewriting the functional equation of the polynomial, all that remained was a simple series of alternate multiplications and additions, performed one after the other, always using the preceding intermediate result as an operand. So exactly how our Nextion works!

For example, if we simply rewrite y=a(x^2)+bx+c mathematically correct as y=(ax+b)x+c, then we can simply write this in the nextion code as y=a*x+b*x+c, and thanks to the unconventional way of calculating the nextion, we get the correct result.

It is just as easy with higher order polynomials: y=ax^4+bx^3+cx^2+dx+e is rewritten as y=(((ax+b)x+c)x+d)+e and in the Nextion code editor simply as y=a*x+b*x+c*x+d*x+e. This way you can make a virtue out of necessity!

Depending on the context, we then only have to replace the ” simple ” multiplications by our q15 macros. Nevertheless, we can also write complex calculations many times in nextion code as ” one-liners “.

In the next episode, we will finally put the many words into action and start to calculate and plot our Nextion function graphs and geometric figures. Thank you for reading – stay tuned!