Path: Home => AVR-EN => assembler introduction => Integer math    (Diese Seite in Deutsch: Flag DE) Logo

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