Файл: Beginers introduction to the Assebly Language of ATMEL-AVR Microprocessors (Gerhard Schmidt,2003, англ).pdf

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 13.06.2025

Просмотров: 644

Скачиваний: 1

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

Avr-Asm-Tutorial

36

http://www.avr-asm-tutorial.net

The test binaries 0xAAAA and 0x55, to be divided, are written to the registers R1:R0 and R3.

The interim register R2 and the result register pair are set to their predfined values.

R1:R0 was rotated left to R2, from 0xAAAA the doubled value of 0x015554 was yielded.

No overflow from rotation into carry has occurred and 0x01 in R2 was smaller than 0x55 in R3, so subtraction was skipped. A zero in the carry is rotated into the result register R5:R4. The former content of the result register, a single 1-bit in position 0 has rotated to position 1 (content now: 0x0002). As a zero was rotated out of the result register pair, the next step to be executed is a branch to the beginning of the division loop start and the loop is repeated.


Avr-Asm-Tutorial

37

http://www.avr-asm-tutorial.net

After executing the loop 16 times we have reached the breakpoint set at the end of the division routine. The result register in R5:R4 holds 0x0202, the result of the division. The registers R2:R1:R0 are empty, so we do not have a remainder left. If a remainder would have been resulted we can use it to decide whether an incrementation of the result should take place, rounding of the result up. This step is not coded here.

The whole division needs 60 micro-seconds processor time (open a processor view in the studio menue). A rather long time for a division.

Number conversion

Number conversion routines are not included here. Please refer to the website, if you need the source code or a better understanding.

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.

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

Avr-Asm-Tutorial

38

http://www.avr-asm-tutorial.net

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 numbers 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:

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 on the website). 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).

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

;Demonstrates floating point conversion in Assembler, (C)2003 www.avr-asm-tutorial.net

;The task: You read in an 8-bit result of an analogue-digital-converter, number is in the range from hex 00 to FF.

;You need to convert this into a floating point number in the range from 0.00 to 5.00 Volt

;The program scheme:

;1. Multiplication by 502 (hex 01F6).That step multiplies by 500, 256 and divides by 255 in one step!

;2. Round the result and cut the last byte of the result. This step divides by 256 by ignoring the last byte of the result.

;Before doing that, bit 7 is used to round the result.

;3. Convert the resulting word to ASCII and set the correct decimal sign. The resulting word in the range from 0 to 500

;is displayed in ASCII-characters as 0.00 to 5.00.

;The registers used:

;The routines use the registers R8..R1 without saving these before. Also required is a multipurpose register called rmp,

;located in the upper half of the registers. Please take care that these registers don't conflict with the register use in the

;rest of your program.

;When entering the routine the 8-bit number is expected in the register R1. The multiplication uses R4:R3:R2 to hold

;the multiplicator 502 (is shifted left max. eight times during multiplication). The result of the multiplication is calculated

;in the registers R7:R6:R5. The result of the so called division by 256 by just ignoring R5 in the result, is in R7:R6. R7:R6

;is rounded, depending on the highest bit of R5, and the result is copied to R2:R1.

;Conversion to an ASCII-string uses the input in R2:R1, the register pair R4:R3 as a divisor for conversion, and places the

;ASCII result string to R5:R6:R7:R8 (R6 is the decimal char).

;Other conventions:

;The conversion uses subroutines and the stack.The stack must work fine for the use of three levels (six bytes SRAM).


Avr-Asm-Tutorial

39

http://www.avr-asm-tutorial.net

;Conversion times:

;The whole routine requires 228 clock cycles maximum (converting $FF), and 79 clock cycles minimum (converting $00).

;At 4 MHz the times are 56.75 microseconds resp. 17.75 microseconds.

;Definitions:

;Registers

.DEF rmp = R16 ; used as multi-purpose register

;AVR type: Tested for type AT90S8515, only required for stack setting, routines work fine with other AT90S-types also

.NOLIST

.INCLUDE "8515def.inc"

.LIST

;Start of test program

;Just writes a number to R1 and starts the conversion routine, for test purposes only

.CSEG

.ORG $0000 rjmp main

main:

ldi rmp,HIGH(RAMEND) ; Set the stack out SPH,rmp

ldi rmp,LOW(RAMEND) out SPL,rmp

ldi rmp,$FF ; Convert $FF mov R1,rmp

rcall fpconv8 ; call the conversion routine no_end: ; unlimited loop, when done

rjmp no_end

;Conversion routine wrapper, calls the different conversion steps fpconv8:

rcall fpconv8m ; multiplicate by 502 rcall fpconv8r ; round and divide by 256 rcall fpconv8a ; convert to ASCII string ldi rmp,'.' ; set decimal char

mov R6,rmp ret ; all done

;Subroutine multiplication by 502

fpconv8m:

clr R4 ; set the multiplicant to 502 ldi rmp,$01

mov R3,rmp ldi rmp,$F6 mov R2,rmp

clr R7 ; clear the result clr R6

clr R5 fpconv8m1:

or R1,R1 ; check if the number is all zeros brne fpconv8m2 ; still one's, go on convert ret ; ready, return back

fpconv8m2:

lsr R1 ; shift number to the right (div by 2)

brcc fpconv8m3 ; if the lowest bit was 0, then skip adding add R5,R2 ; add the number in R6:R5:R4:R3 to the result adc R6,R3

adc R7,R4 fpconv8m3:

lsl R2 ; multiply R4:R3:R2 by 2 rol R3

rol R4

rjmp fpconv8m1 ; repeat for next bit

;Round the value in R7:R6 with the value in bit 7 of R5 fpconv8r:

clr rmp ; put zero to rmp lsl R5 ; rotate bit 7 to carry

adc R6,rmp ; add LSB with carry adc R7,rmp ; add MSB with carry

mov R2,R7 ; copy the value to R2:R1 (divide by 256) mov R1,R6

ret

;Convert the word in R2:R1 to an ASCII string in R5:R6:R7:R8 fpconv8a:

clr R4 ; Set the decimal divider value to 100 ldi rmp,100

mov R3,rmp

rcall fpconv8d ; get ASCII digit by repeated subtraction mov R5,rmp ; set hundreds string char

ldi rmp,10 ; Set the decimal divider value to 10 mov R3,rmp

rcall fpconv8d ; get the next ASCII digit mov R7,rmp ; set tens string char

ldi rmp,'0' ; convert the rest to an ASCII char add rmp,R1

mov R8,rmp ; set ones string char ret


Avr-Asm-Tutorial

40

http://www.avr-asm-tutorial.net

;Convert binary word in R2:R1 to a decimal digit by substracting the decimal divider value in R4:R3 (100, 10) fpconv8d:

ldi rmp,'0' ; start with decimal value 0 fpconv8d1:

cp R1,R3 ; Compare word with decimal divider value cpc R2,R4

brcc fpconv8d2 ; Carry clear, subtract divider value ret ; done subtraction

fpconv8d2:

sub R1,R3 ; subtract divider value sbc R2,R4

inc rmp ; up one digit

rjmp fpconv8d1 ; once again

;End of conversion test routine

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

This example is a bit more complicated. Refer to the website if you need it.

Avr-Asm-Tutorial

41

http://www.avr-asm-tutorial.net

Annex

Commands sorted by function

For the abbreviations used see the list of abbreviations.

Function

Register set

Copy

Add

Subtract

Shift

Binary

Subfunction

Command

Flags

Clk

0

CLR r1

Z N V

1

255

SER rh

1

Constant

LDI rh,c255

1

Register => Register

MOV r1,r2

1

SRAM => Register, direct

LDS r1,c65535

2

SRAM => Register

LD r1,rp

2

SRAM => Register and INC

LD r1,rp+

2

DEC, SRAM => Register

LD r1,-rp

2

SRAM, displaced => Register

LDD r1,ry+k63

2

Port => Register

IN r1,p1

1

Stack => Register

POP r1

2

Program storage Z => R0

LPM

3

Register => SRAM, direct

STS c65535,r1

2

Register => SRAM

ST rp,r1

2

Register => SRAM and INC

ST rp+,r1

2

DEC, Register => SRAM

ST -rp,r1

2

Register => SRAM, displaced

STD ry+k63,r1

2

Register => Port

OUT p1,r1

1

Register => Stack

PUSH r1

2

8 Bit, +1

INC r1

Z N V

1

8 Bit

ADD r1,r2

Z C N V H

1

8 Bit + Carry

ADC r1,r2

Z C N V H

1

16 Bit, constant

ADIW rd,k63

Z C N V S

2

8 Bit, -1

DEC r1

Z N V

1

8 Bit

SUB r1,r2

Z C N V H

1

8 Bit, constant

SUBI rh,c255

Z C N V H

1

8 Bit - Carry

SBC r1,r2

Z C N V H

1

8 Bit - Carry, constant

SBCI rh,c255

Z C N V H

1

16 Bit

SBIW rd,k63

Z C N V S

2

logic, left

LSL r1

Z C N V

1

logic, right

LSR r1

Z C N V

1

Rotate, left over Carry

ROL r1

Z C N V

1

Rotate, right over Carry

ROR r1

Z C N V

1

Arithmetic, right

ASR r1

Z C N V

1

Nibble exchange

SWAP r1

1

And

AND r1,r2

Z N V

1

And, constant

ANDI rh,c255

Z N V

1

Or

OR r1,r2

Z N V

1

Or, constant

ORI rh,c255

Z N V

1

Exclusive-Or

EOR r1,r2

Z N V

1

Ones-complement

COM r1

Z C N V

1

Twos-complement

NEG r1

Z C N V H

1


Avr-Asm-Tutorial

42

http://www.avr-asm-tutorial.net

Function

Bits change

Statusbit set

Statusbit clear

Compare

Immediate

Jump

Subfunction

Command

Flags

Clk

Register, set

SBR rh,c255

Z N V

1

Register, clear

CBR rh,255

Z N V

1

Register, copy to T-Flag

BST r1,b7

T

1

Register, copy from T-Flag

BLD r1,b7

1

Port, set

SBI pl,b7

2

Port, clear

CBI pl,b7

2

Zero-Flag

SEZ

Z

1

Carry Flag

SEC

C

1

Negative Flag

SEN

N

1

Twos complement carry Flag

SEV

V

1

Half carry Flag

SEH

H

1

Signed Flag

SES

S

1

Transfer Flag

SET

T

1

Interrupt Enable Flag

SEI

I

1

Zero-Flag

CLZ

Z

1

Carry Flag

CLC

C

1

Negative Flag

CLN

N

1

Twos complement carry Flag

CLV

V

1

Half carry Flag

CLH

H

1

Signed Flag

CLS

S

1

Transfer Flag

CLT

T

1

Interrupt Enable Flag

CLI

I

1

Register, Register

CP r1,r2

Z C N V H

1

Register, Register + Carry

CPC r1,r2

Z C N V H

1

Register, constant

CPI rh,c255

Z C N V H

1

Register, ≤0

TST r1

Z N V

1

Relative

RJMP c4096

2

Indirect, Address in Z

IJMP

2

Subroutine, relative

RCALL c4096

3

Subroutine, Address in Z

ICALL

3

Return from Subroutine

RET

4

Return from Interrupt

RETI

I

4