Path:
Home =>
AVR-EN =>
assembler introduction => Integer math
(Diese Seite in Deutsch:
)
Beginner's introduction to AVR assembler language
Integer calculations in assembler language
Integers only, please
For confusing the starter in assembly language the following expression does not what
expected:
.equ number=3/2
The constant NUMBER is not 1.5, as expected. Instead the assembler
gavrasm lists the following in its listing:
List of symbols:
Type nDef nUsed Decimalval Hexval Name
C 1 0 1 01 NUMBER
(Note: the list of symbols is generated by the assembler gavrasm only,
other assemblers don't have that feature!)
Instead of 1.5 there is a one in the constant number. Already
the columns in that list are designed for integers only and cannot
display broken decimal values.
That results from the fact that assembler can only handle integers.
That makes sense in the binary world: if only zeroes and ones are
available, no half, quarter or tenth are available. Those who try
to introduce broken decimals into assembler, in order to allow
C-Programmer's unwillingness to learn and understand, produces
nonsense: the divider in a timer can only handle integers and can
never divide by 1.5!
Rounding up when dividing
And, as the 3 / 2 example demonstrated, assembler even do not round
up. He simply skips the tenth and hundredth and anything else
following the decimal dot. If we want to round the 0.5 up, we'll
have to do the following:
.equ number=(3+1)/2
Prior to dividing by two we'll have to add half of the divider. Only
that brings rounding-up the result, and our constant will be 2:
List of symbols:
Type nDef nUsed Decimalval Hexval Name
C 1 0 2 02 NUMBER
When calculating more complex expressions, a separate constant
holding the divider only can be recommended. Take as an example
the calculation of the frequency of a divided clock in a timer.
The prescaler divides the clock by e. g. 64 and the timer
divides it by 15. We assume further that the timer toggles an
output pin when he reaches 15 and clears. That makes an additional
division by two. With
.equ divider=15*64*2
we calculate the whole divider rate, by which the divider divides
the controller's clock frequency.
If the controller works at a clock rate of 1 MHz, the resulting
frequency will be, rounded up, as follows:
.equ frequency = (1000000 + divider/2) / divider
The rule that * and / has a higher priority than + and - causes that
we do not have to place divider/2 into separate brackets. The divider/2
is calculated by the assembler first, addition of 1000000 follows.
Before dividing this sum by divider we'll have to insert the sum into
brackets.
If you do that, the following results:
List of symbols:
Type nDef nUsed Decimalval Hexval Name
C 1 2 1920 0780 DIVIDER
C 1 0 521 0209 FREQUENCY
Normally the calculation would have yielded a frequency of 520.8333
(period), therefore rounding the frequency up to 521 is correct.
What if those skipped digits behind the decimal dot are interesting?
That would be the case if our timer uses a precscaler value of 1,024
and divides by 256. Then a rectangle of the output pin of
ftoggle = 1.000.000 / 1.024 / 256 / 2 = 1.907 Hz
would result and the 0.907 behind the dot would be good to see.
If we simply calculate the 1000-fold, e. g.
.equ divider = 1024 * 256 * 2
.equ fmHz = (1000 * 1000000 + divider / 2) / divider
we will get the frequency in mHz instead of Hz.
List of symbols:
Type nDef nUsed Decimalval Hexval Name
C 1 2 524288 080000 DIVIDER
C 1 0 1907 0773 FMHZ
The 1907 now are the mHz, the three decimals behind the dot get visible.
And: the last digit is rounded! Unfortunately the lack of assembler
to handle small and large letters has made MHz from our mHz, but we
could name the constant fmillihertz, so have a more correct name for
the constant.
Additional consequences of integer math
When dividing a number by a larger one, zero results. That can have
further consequences, as the following example demonstrates.
We use a 16-bit timer (that can divide by up to 65,536) and a prescaler
of 1,024. Together this divides by 67,108,864, and is larger than
any clock rate of an AVR can be. If we calculate the frequency we'll
get an integer zero. If we calculate the cycle time from the frequency,
e. g. in µs, we can do that with that formulation:
.equ divider = 65536 * 1024 * 2
.equ f=(1000000 + divider/2) / divider
.equ tus=(1000000 + f/2) / f
then we run into the following error message:
===> Error 101: Division by Zero
Line: .equ tus=(1000000+f/2) / f
because f is zero:
List of symbols:
Type nDef nUsed Decimalval Hexval Name
C 1 2 1342189568 50003000 DIVIDER
C 1 2 0 00 F
C 1 0 0 00 TUS
By looking into the symbol list, we'll find the reason for that
error very fast.
Avoiding this is simple: just use a more correct dimension of numbers.
So, if you want to express the duration of a clock cycle at 1 MHz,
just change from
.equ clock = 1000000 ; Clock to 1 MHz
.equ clockduration = (1 + clock/2) / clock ; Duration, rounded
which will, of course, yield 0 for clockduration in integer math. To
avoid this, change to one of the following three formulations:
.equ clockduration_us = (1000000+clock/2) / clock ; clock duration in micro-seconds, rounded
.equ clockduration_ns = (1000000000+clock/2) / clock ; clock duration in nano-seconds, rounded
.equ clockduration_ps = (1000000000000+clock/2) / clock ; clock duration in pico-seconds, rounded
Having the dimension as part of the constant's name, makes clear enough
what the number represents. Even if you use such a variable in further
calculations, it is good to have the dimension as part of the name, because
you immediately see, which conversions factors you'll have to apply.
Don't be afraid, that your atto- and femto-seconds provoke an overflow: even
only 32-bit integers can handle numbers up to 2 billions (2E9), so the
pico-second example will end with an overflow error. The nowadays usual
64-bit integers in more modern assemblers (Assembler 2 in Studio 4.19) can
handle numbers up to 9E+18, so even the atto- or femto-seconds are not a
problem.
Number limits in assembler
Earlier assembler worked with 24 or 32 bit wide integers. To allow
signed integers (positive and negative), the highest bit was reserved
for as sign bit. The numbers that can be handled span from +2^23-1 to
-2^23, approx. +/-8 millions resp. +2^31-1 to -2^31 (approx.
+/-2 billions).
Newer assemblers use 64-bit numbers, and there are rarely any cases
where you come near to the maximum and (negative) minimum of integers.
So you can calculate your frequency in pico-Hertz, if you love to see
useless digits hanging around in your symbol list.
Remind
Assembler only handles integers. If, during dividing, you'll need
rounding, just add half the divider before dividing. Use dimension
abbreviations as part of the symbol names.
To the page top
©2002-2020 by http://www.avr-asm-tutorial.net