- Positive whole numbers (Integers),
- Signed whole numbers,
- Binary Coded Digits, BCD,
- Packed BCDs,
- ASCII-formatted numbers.

The single bytes of a word or a double word can be stored in whatever register you prefer. Operations with these single bytes are programmed byte by byte, so you don't have to put them in a row. In order to form a row for a double word we could store it like this:

.DEF r17 = dw1

.DEF r18 = dw2

.DEF r19 = dw3

dw0 to dw3 are in a row in the registers. If we need to initiate this double word at the beginning of an application (e.g. to 4,000,000), this should look like this:

So we have splitted this decimal number called dwi to its binary portions and packed them into the four byte packages. Now you can calculate with this double word.

To the top of that page

In one byte the biggest number to be handled is +127 (binary 0,1111111), the smallest one is -128 (binary 1,0000000). In other computer languages this number format is called short integer. If you need a bigger range of values you can add another byte to form a normal integer value, ranging from +32,767 .. -32,768), four bytes provide a range from +2,147,483,647 .. -2,147,483,648, usually called a LongInt or DoubleInt.

To the top of that page

Bit value | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
---|---|---|---|---|---|---|---|---|

R16, Digit 1 = 2 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |

R17, Digit 2 = 5 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |

R18, Digit 3 = 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |

Instructions to use:

You can calculate with these numbers, but this is a bit more complicated in assember than calculating with binary values. The advantage of this format is that you can handle as long numbers as you like, as long as you have enough storage space. The calculations are as precise as you like (if you program AVRs for banking applications), and you can convert them very easily to character strings.

To the top of that page

Byte | Digits | Value | 8 | 4 | 2 | 1 | 8 | 4 | 2 | 1 |
---|---|---|---|---|---|---|---|---|---|---|

2 | 4,3 | 02 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |

1 | 2,1 | 50 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 |

Instructions for setting:

To set this correct you can use the binary notation (0b...) or the hexadecimal notation (0x...) to set the proper bits to their correct nibble position.

Calculating with packed BCDs is a little more complicated compared to the binary form. Format changes to character strings are as easy as with BCDs. Length of numbers and precision of calculations is only limited by the storage space.

To the top of that page

Within the ASCII code system the decimal digit 0 is represented by the number 48 (hex 0x30, binary 0b0011.0000), digit 9 is 57 decimal (hex 0x39, binary 0b0011.1001). ASCII wasn't designed to have these numbers on the beginning of the code set as there are already command chars like the above mentioned EOT for the teletype. So we still have to add 48 to a BCD (or set bit 4 and 5 to 1) to convert a BCD to ASCII. ASCII formatted numbers need the same storage space like BCDs. Loading 250 to a register set representing that number would look like this:

LDI R17,'5'

LDI R16,'0'

The ASCII representation of these characters are written to the registers.

To the top of that page

If we have a register that is already set to hex 0x30 we can use the OR with this register to convert the BCD:

Back from an ASCII character to a BCD is a bit more complicated in AVR assembler, because the instruction

that isolates the lower four bits (= the lower nibble) is only possible with registers above R15. If you need to do this, use one of the registers R16 to R31!

If the hex value 0x0F is already in register R2, you can AND the ASCII character with this register:

The other instructions for manipulating bits in a register are also limited for registers above R15. They would be formulated like this:

If one or more bits of a byte have to be inverted you can use the following instruction (which is not possible for use with a constant):

To invert all bits of a byte is called the One's complement:

inverts the content in register R1 and replaces zeros by one and vice versa. Different from that is the Two's complement, which converts a positive signed number to its negative complement (subracting from zero). This is done with the instruction

So +1 (decimal: 1) yields -1 (binary 1.1111111), +2 yields -2 (binary 1.1111110), and so on.

Besides the manipulation of the bits in a register copying a single bit is possible using the so-called T-bit of the status register. With

the T-bit is loaded to bit 0 of register R1. The T-bit can be set or cleared and then copy its content to any bit in any register:

To the top of that page

Multiplication with 2 is easily done by shifting all bits of a byte one binary digit left and writing a zero to the least significant bit. This is called logical shift left. The former bit 7 of the byte will be shiftet to the carry bit in the status register.

The inverse division by 2 is the instruction called logical shift right.

The former bit 7, now shifted to bit 6, is filled with a 0, while the former bit 0 is shifted into the carry bit of the status register. This carry bit could be used to round up and down (if set, add one to the result). Example, division by four with rounding:

LSR R1

So, dividing is easy with binaries as long as you divide by multiples of 2.

If signed integers are used the logical shift right would overwrite the sign-bit in bit 7. The instruction arithmetic shift right leaves bit 7 untouched and shifts the 7 lower bits, inserting a zero in bit 6.

Like with logical shifting the former bit 0 goes to the carry bit in the status register.

What about multiplying a 16-bit word by 2? The most significant bit of the lower byte has to be shifted to yield the lowest bit of the upper byte. In that step a shift would set the lowest bit to zero, but we need to shift the carry bit from the previous shift of the lower byte into bit 0. This is called a rotate. During rotation the carry bit in the status register is shifted to bit 0, the former bit 7 is shifted to the carry during rotation.

The logical shift left in the first instruction shifts bit 7 to carry, the ROL instruction rolls it to bit 0 of the upper byte. Following the second instruction the carry bit has the former bit 7. The carry bit can be used to either indicate an overflow (if 16-bit-calculation is performed) or to roll it into upper bytes (if more than 16 bit calculation is done).

Rolling to the right is also possible, dividing by 2 and shifting carry to bit 7 of the result:

It's easy dividing with big numbers. You see that learning assembler is not THAT complicated.

The last instruction that shifts four bits in one step is very often used with packed BCDs. This instruction shifts a whole nibble from the upper to the lower position and vice versa. In our example we need to shift the upper nibble to the lower nibble position. Instead of using

ROR R1

ROR R1

ROR R1

we can perform that with a single

This exchanges the upper and lower nibble. Note that the upper nibble's content will be different after applying these two methods.

To the top of that page

To start complicated we add two 16-bit-numbers in R1:R2 and R3:R4. In this notation we mean that the first register is the most signifant byte, the second the least significant.

Instead of a second

We subtract R3:R4 from R1:R2.

Again the same trick: during the second instruction we subract another 1 from the result if the result of the first instruction had an overflow. Still breathing? If yes, cope with the following!

Now we compare a 16-bit-word in R1:R2 with the one in R3:R4 to evaluate whether it is bigger than the second one. Instead of

If the carry flag is set now, R1:R2 is smaller than R3:R4.

Now we add some more complicated stuff. We compare the content of R16 with a constant: 0b10101010.

If the Zero-bit in the status register is set after that, we know that R16 is 0xAA. If the carry-bit is set, we know, it is smaller. If it is not set and the Zero-bit is not set either, we know it is bigger.

And now the most complicated test. We evaluate whether R1 is zero or negative:

If the Z-bit is set the register R1 is zero and we can follow with the instructions BREQ, BRNE, BRMI, BRPL, BRLO, BRSH, BRGE, BRLT, BRVC or BRVS to branch around a bit.

Still with us? If yes, here is some packed BCD calculations. Adding two packed BCDs can result in two different overflows. The usual carry shows an overflow, if the higher of the two nibbles overflows to more than 15 decimal. Another overflow, from the lower to the upper nibble occurs, if the two lower nibbles add to more than 15 decimal. To take an example we add the packed BCDs 49 (=hex 49) and 99 (=hex 99) to yield 148 (=hex 0x148). Adding these in binary math, results in a byte holding hex 0xE2, no byte overflow occurs. The lower of the two nibbles has had an overflow because 9+9=18 and the lower nibble can only handle numbers up to 15. The overflow was added to bit 4, the lowest significant bit of the upper nibble. Which is correct! But the lower nibble should be 8 and is only 2 (18 = 0b0001.0010). We should add 6 to that nibble to yield a correct result. Which is quite logic, because whenever the lower nibble reaches more than 9 we have to add 6 to correct that nibble.

The upper nibble is totally incorrect, because it is 0xE and should be 3 (with a 1 overflowing to the next upper digit of the packed BCD). If we add 6 to this 0xE we get to 0x4 and the carry is set (=0x14). So the trick is to add these two numbers and then add 0x66 to correct the 2 digits of the packed BCD. But halt: what if adding the first and the second number would not result in an overflow to the upper nibble? And not result in a digit above 9 in the lower nibble? Adding 0x66 would then result in a totally incorrect result. The lower 6 should only be added if the lower nibble either overflows to the upper nibble or results in a digit greater than 9. The same with the upper nibble.

How do we know, if an overflow from the lower to the upper nibble has occurred? The MCU sets a H-bit in the status register, the half-carry bit. The following table shows the different cases that are possible after adding R1 and R2 and adding hex 0x66 after that.

Add R1,R2 (Half)Carry-Bit | Add Nibble,6 (Half)Carry-Bit | Correction |
---|---|---|

0 | 0 | subtract 6 |

1 | 0 | none |

0 | 1 | none |

1 | 1 | (not possible) |

BRHC NoHc1

ADD R2,R16

BRHC NoHc2

SUB R2,R17

A little bit shorter than that:

ADD R2,R16

ADD R2,R3

BRCC NoCy

INC R1

ANDI R16,0x0F

NoCy:

BRHC NoHc

ANDI R16,0xF0

NoCy:

SUB R2,R16

Question to think about: Why is that equally correct and where is the trick?

To the top of that page

Conversion of packed BCDs is not very complicated either. First we have to copy the number to another register. With the copied value we change nibbles with

A little bit more complicated is the conversion of BCD digits to a binary. Depending on the numbers to be handled we first clear the necessary bytes that will hold the result of the conversion. We then start with the highest BCD digit. Before adding this to the result we multiply the result with 10. In order to do this we copy the result to somewhere else. Then we multiply the result by four (two left shifts resp. rolls). Adding the previously copied result to this yields a multiplication with 5. Now a mulitiplication with 2 (left shift/roll) yields the 10-fold of the result. Now we add the BCD and repeat that algorithm until all decimal digits are converted. If, during one of these operations, there occurs a carry of the result, the BCD is too big to be converted.

The conversion of a binary to BCDs is even more complicated than that. If we convert a 16-bit-binary we can subtract 10,000, until an overflow occurs, yielding the first digit. Then we repeat that with 1,000 to yield the second digit. And so on with 100 and 10, then the remainder is the last digit. The constants 10,000, 1,000, 100 and 10 can be placed to the program memory storage in a wordwise organised table, like this:

.DW 10000, 1000, 100, 10

and can be read wordwise with the

An alternative is a table that holds the decimal value of each bit in the 16-bit-binary, e.g.

.DB 0,1,6,3,8,4

.DB 0,0,8,1,9,2

.DB 0,0,4,0,9,6

.DB 0,0,2,0,4,8

Then you shift the single bits of the binary left out of the registers to the carry. If it is a one, you add the number in the table to the result by reading the numbers from the table using

A third method is to calculate the table value, starting with 000001, by adding this BCD with itself, each time after you have shifted a bit from the binary to the right and added the BCD.

To the top of that page

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