Path: Home =>
AVR-Overview =>
Binary calculations => Fixed decimals
Conversion to fixed decimal numbers in AVR assembler
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
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:
| Number | Value (example) | Register |
| Input value | 255 | R1 |
| Multiplicator | 502 | R4:R3:R2 |
| Result | 128,010 | R7: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:
- Test, if the input number is already zero. If yes, we're done.
- 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.
- 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.
- 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.
- 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
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
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