Path: Home => AVR-Overview => Binary calculations => Fixed decimals
AT90S8515

Conversion to fixed decimal numbers in AVR assembler

Sense and Nonsense of floating decimal fractions

First: Do not use any floating points, unless you really need them. Floating points are resource killers in an AVR, lame ducks und need extreme execution times. Run into this dilemma, if you think assembler is too complicated, and you prefer Basic or other languages like C and Pascal.
Not so, if you use assembler. You'll be shown here, how you can perform the multiplication of a fixed point real number in less than 60 micro-seconds, in special cases even within 18 micro-seconds, at 4 Mcs/s clock frequency. Without any floating point processor extensions and other expensive tricks for people too lazy to use their brain.

How to do that? Back to the roots of math! Most tasks with floating point reals can be done using integer numbers. Integers are easy to program in assembler and perform fast. The decimal point is only in the brain of the programmer, and is added somewhere in the decimal digit stream. No one realizes, that this is a trick.

To the top of that page

Linear conversions

As an example the following task: an 8-Bit-AD-Converter measures an input signal in the range from 0.00 to 2.55 Volt, and returns as the result a binary in the range from $00 and $FF. The result, a voltage, is to be displayed on a LCD display. Silly example, as it is so easy: The binary is converted to a decimal ASCII string between 000 and 255, and just behind the first digit the decimal point has to be inserted. Done!

The electronics world sometimes is more complicated. E.g., the AD-Converter returns an 8-Bit-Hex for input voltages between 0.00 and 5.00 Volt. Now we're tricked and do not know how to proceed. To display the correct result on the LCD we would have to multiply the binary by 500/255, which is 1.9608. This is a silly number, as it is almost 2, but only almost. And we don't want that kind of inaccuracy of 2%, while we have an AD-converter with around 0.25% accuracy.

To cope with this, we multiply the input by 500/255*256 or 501.96 and divide the result by 256. Why first multiply by 256 and then divide by 256? It's just for enhanced accuracy. If we multiply the input by 502 instead of 501.96, the error is just in the order of 0.008%. That is good enough for our AD-converter, we can live with that. And dividing by 256 is an easy task, because it is a well-known power of 2. By dividing with number that are a power of 2, the AVR feels very comfortable and performs very fast. By dividing with 256, the AVR is even faster, because we just have to skip the last byte of the binary number. Not even shift and rotate!

The multiplication of an 8-bit-binary with the 9-bit-binary 502 (hex 1F6) can have a result greater than 16 bits. So we have to reserve 24 bits or 3 registers for the result. During multiplication, the constant 502 has to be shifted left (multiplication by 2) to add these numbers to the result each time a one rolls out of the input number. As this might need eight shifts left, we need futher three bytes for this constant. So we chose the following combination of registers for the multiplication:
NumberValue (example)Register
Input value255R1
Multiplicator502R4:R3:R2
Result128,010R7:R6:R5
After filling the value 502 (00.01.F6) to R4:R3:R2 and clearing the result registers R7:R6:R5 the multiplication goes like this:
  1. Test, if the input number is already zero. If yes, we're done.
  2. If no, one bit of the input number is shifted out of the register to the right, into the carry, while a zero is stuffed into bit 7. This instruction is named Logical-Shight-Right or LSR.
  3. If the bit in carry is a one, we add the multiplicator (during step 1 the value 502, in step 2 it's 1004, a.s.o.) to the result. During adding, we care for any carry (adding R2 to R5 by ADD, adding R3 to R6 and R4 to R7 with the ADC instruction!). If the bit in the carry was a zero, we just don't add the multiplicator to the result and jump to the next step.
  4. Now the multiplicator is multiplied by 2, because the next bit shifted out of the input number is worth double as much. So we shift R2 to the left (by inserting a zero in bit 0) using LSL. Bit 7 is shifted to the carry. Then we rotate this carry into R3, rotating its content left one bit, and bit 7 to the carry. The same with R4.
  5. Now we're done with one digit of the input number, and we proceed with step 1 again.
The result of the multiplication by 502 now is in the result registers R7:R6:R5. If we just ignore register R5 (division by 256), we have our desired result. To enhance occuracy, we can use bit 7 in R5 to round the result. Now we just have to convert the result from its binary form to decimal ASCII (see Conversion bin to decimal-ASCII). If we just add a decimal point in the right place in the ASCII string, our voltage string is ready for the display.

The whole program, from the input number to the resulting ASCII string, requires between 79 and 228 clock cycles, depending from the input number. Those who want to beat this with the floating point routine of a more sophisticated language than assembler, feel free to mail me your conversion time (and program flash and memory usage).

To the top of that page

Example 1: 8-bit-AD-converter with fixed decimal output

The program described above is, a little optimized, available in HTML-form or as assembler source code file. The source has all necessary routines for the conversion in a compact form, to be exported to other programs. The head of the program is a test setting, so you can test the program in the simulator.

To the top of that page

Example 2: 10-bit-AD-converter with fixed decimal output

8-bit-AD-converters are rare, 10 bits are more often used. Because 10 bits are more accurate, the conversion is done to yield a four-digit-decimal. Don't be surprised, if the last digit is not very stable. Here we have the HTML-form and here is the assembler source code file of the program.

Caused by the expanded accuracy, the program needs some more registers. It is a bit slower, but not much. I still keep up with the above offer for Basic, C and Pascal programers, if you beat this.

To the top of that page

©2003 by http://www.avr-asm-tutorial.net