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

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

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

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

Добавлен: 13.06.2025

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

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

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

Avr-Asm-Tutorial

11

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

LDI ZH,HIGH(2*Adress)

LDI ZL,LOW(2*Adress)

LPM

Following this command the adress must be incremented to point to the next byte in program memory. As this is used very often a special pointer incrementation command has been defined to do this:

ADIW ZL,1 LPM

ADIW means ADd Immediate Word and a maximum of 63 can be added this way. Note that the assembler expects the lower of the pointer register pair ZL as first parameter. This is somewhat confusing as addition is done as 16-bit- operation.

The complement command, subtracting a constant value of between 0 and 63 from a 16-bit pointer register is named SBIW, Subtract Immediate Word. (SuBtract Immediate Word). ADIW and SBIW are possible for the pointer register pairs X, Y and Z and for the register pair R25:R24, that does not have an extra name and does not allow access to SRAM or program memory locations. R25:R24 is ideal for handling 16-bit values.

How to insert that table of values in the program memory? This is done with the assembler directives .DB and .DW. With that you can insert bytewise or wordwise lists of values. Bytewise organised lists look like this:

.DB 123,45,67,89 ; a list of four bytes

.DB "This is a text. " ; a list of byte characters

You should always place an even number of bytes on each single line. Otherwise the assembler will add a zero byte at the end, which might be unwanted.

The similiar list of words looks like this:

.DW 12345,6789 ; a list of two words

Instead of constants you can also place labels (jump targets) on that list, like that:

Label1:

[ ... here are some commands ... ] Label2:

[ ... here are some more commands ... ] Table:

.DW Label1,Label2 ; a wordwise list of labels

Labels ALWAYS start in column 1!. Note that reading the labels with LPM first yields the lower byte of the word.

A very special application for the pointer registers is the access to the registers themselves. The registers are located in the first 32 bytes of the chip's adress space (at adress 0x0000 to 0x001F). This access is only meaningful if you have to copy the register's content to SRAM or EEPROM or read these values from there back into the registers. More common for the use of pointers is the access to tables with fixed values in the program memory space. Here is, as an example, a table with 10 different 16-bit values, where the fifth table value is read to R25:R24:

MyTable:

.DW 0x1234,0x2345,0x3456,0x4568,0x5678 ; The table values, wordwise

.DW 0x6789,0x789A,0x89AB,0x9ABC,0xABCD ; organised Read5: LDI ZH,HIGH(MyTable*2) ; Adress of table to pointer Z

LDI ZL,LOW(MyTable*2) ; multiplied by 2 for bytewise access ADIW ZL,10 ; Point to fifth value in table

LPM ; Read least significant byte from program memory MOV R24,R0 ; Copy LSB to 16-bit register

ADIW ZL,1 ; Point to MSB in program memory LPM ; Read MSB of table value

MOV R25,R0 ; Copy MSB to 16-bit register

This is only an example. You can calculate the table adress in Z from some input value, leading to the respective table values. Tables can be organised byteor character-wise, too.

Recommendation for the use of registers

Define names for registers with the .DEF directive, never use them with their direct name Rx.

If you need pointer access reserve R26 to R31 for that purpose.

16-bit-counter are best located R25:R24.

If you need to read from the program memory, e.g. fixed tables, reserve Z (R31:R30) and R0 for that purpose.

If you plan to have access to single bits within certain registers (e.g. for testing flags), use R16 to R23 for that purpose.


Avr-Asm-Tutorial

12

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

Ports

What is a Port?

Ports in the AVR are gates from the central processing unit to internal and external hardand software components. The CPU communicates with these components, reads from them or writes to them, e.g. to the timers or the parallel ports. The most used port is the flag register, where results of previous operations are written to and branch conditions are read from.

There are 64 different ports, which are not physically available in all different AVR types. Depending on the storage space and other internal hardware the different ports are either available and accessable or not. Which of these ports can be used is listed in the data sheets for the processor type.

Ports have a fixed address, over which the CPU communicates. The address is independent from the type of AVR. So e.g. the port adress of port B is always 0x18 (0x stands for hexadecimal notation). You don't have to remember these port adresses, they have convenient aliases. These names are defined in the include files (header files) for the different AVR types, that are provided from the producer. The include files have a line defining port B's address as follows:

.EQU PORTB, 0x18

So we just have to remember the name of port B, not its location in the I/O space of the chip. The include file 8515def.inc is involved by the assembler directive

.INCLUDE "C:\Somewhere\8515def.inc"

and the registers of the 8515 are all defined then and easily accessable.

Ports usually are organised as 8-bit numbers, but can also hold up to 8 single bits that don't have much to do with each other. If these single bits have a meaning they have their own name associated in the include file, e.g. to enable manipulation of a single bit. Due to that name convention you don't have to remember these bit positions. These names are defined in the data sheets and are given in the include file, too. They are provided here in the port tables.

As an example the MCU General Control Register, called MCUCR, consists of a number of single control bits that control the general property of the chip (see the description in MCUCR in detail). It is a port, fully packed with 8 control bits with their own names (ISC00, ISC01, ...). Those who want to send their AVR to a deep sleep need to know from the data sheet how to set the respective bits. Like this:

.DEF MyPreferredRegister = R16

LDI MyPreferredRegister, 0b00100000 OUT MCUCR, MyPreferredRegister SLEEP

The Out command brings the content of my preferred register, a Sleep-Enable-Bit called SE, to the port MCUCR and sets the AVR immediately to sleep, if there is a SLEEP instruction executed. As all the other bits of MCUCR are also set by the above instructions and the Sleep Mode bit SM was set to zero, a mode called half-sleep will result: no further command execution will be performed but the chip still reacts to timer and other hardware interrupts. These external events interrupt the big sleep of the CPU if they feel they should notify the CPU.

Reading a port's content is in most cases possible using the IN command. The following sequence

.DEF MyPreferredRegister = R16

IN MyPreferredRegister, MCUCR

reads the bits in port MCUCR to the register. As many ports have undefined and unused bits in certain ports, these bits always read back as zeros.

More often than reading all 8 bits of a port one must react to a certain status of a port. In that case we don't need to read the whole port and isolate the relevant bit. Certain commands provide an opportunity to execute commands depending on the level of a certain bit (see the JUMP section). Setting or clearing certain bits of a port is also possible without reading and writing the other bits in the port. The two commands are SBI (Set Bit I/o) and CBI (Clear Bit I/o). Execution is like this:

.EQU ActiveBit=0 ; The bit that is to be changed

SBI PortB, ActiveBit ; The bit will be set to one CBI PortB, Activebit ; The bit will be cleared to zero

These two instructions have a limitation: only ports with an adress smaller than 0x20 can be handled, ports above cannot be accessed that way.

For the more exotic programmer: the ports can be accessed using SRAM access commands, e.g. ST and LD. Just add 0x20 to the port's adress (the first 32 addresses are the registers!) and access the port that way. Like demonstrated here:

.DEF MyPreferredRegister = R16

LDI ZH,HIGH(PORTB+32)


Avr-Asm-Tutorial

13

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

LDI ZL,LOW(PORTB+32)

LD MyPreferredRegister,Z

That only makes sense in certain cases, but it is possible. It is the reason why the first address location of the SRAM is always 0x60.

Details of relevant ports in the AVR

The following table holds the most used ports. Not all ports are listed here, some of the MEGA and AT90S4434/8535 types are skipped. If in doubt see the original reference.

Component

Portname

Port-Register

Accumulator

SREG

Status Register

Stack

SPL/SPH

Stackpointer

External SRAM/External Interrupt

MCUCR

MCU General Control Register

External Interrupt

GIMSK

Interrupt Mask Register

GIFR

Interrupt Flag Register

Timer Interrupt

TIMSK

Timer Interrupt Mask Register

TIFR

Timer Interrupt Flag Register

Timer 0

TCCR0

Timer/Counter 0 Control Register

TCNT0

Timer/Counter 0

Timer 1

TCCR1A

Timer/Counter Control Register 1 A

TCCR1B

Timer/Counter Control Register 1 B

TCNT1

Timer/Counter 1

OCR1A

Output Compare Register 1 A

OCR1B

Output Compare Register 1 B

ICR1L/H

Input Capture Register

Watchdog Timer

WDTCR

Watchdog Timer Control Register

EEPROM

EEAR

EEPROM Adress Register

EEDR

EEPROM Data Register

EECR

EEPROM Control Register

SPI

SPCR

Serial Peripheral Control Register

SPSR

Serial Peripheral Status Register

SPDR

Serial Peripheral Data Register

UART

UDR

UART Data Register

USR

UART Status Register

UCR

UART Control Register

UBRR

UART Baud Rate Register

Analog Comparator

ACSR

Analog Comparator Control and Status Register

I/O-Ports

PORTx

Port Output Register

DDRx

Port Direction Register

PINx

Port Input Register

The status register as the most used port

By far the most often used port is the status register with its 8 bits. Usually access to this port is only by automatic setting and clearing bits by the CPU or accumulator, some access is by reading or branching on certain bits in that port, in a few cases it is possible to manipulate these bits directly (using the assembler command SEx or CLx, where x is the bit abbreviation). Most of these bits are set or cleared by the accumulator through bit-test, compareor calculation-operations. The following list has all assembler commands that set or clear status bits depending on the result of the execution.


Avr-Asm-Tutorial

14

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

Bit

Calculation

Logic

Compare

Bits

Shift

Other

Z

ADD, ADC, ADIW, DEC,

AND, ANDI, OR,

CP, CPC,

BCLR Z,

ASR, LSL,

CLR

INC, SUB, SUBI, SBC,

ORI, EOR, COM,

CPI

BSET Z, CLZ,

LSR, ROL,

SBCI, SBIW

NEG, SBR, CBR

SEZ, TST

ROR

C

ADD, ADC, ADIW, SUB,

COM, NEG

CP, CPC,

BCLR C,

ASR, LSL,

-

SUBI, SBC, SBCI, SBIW

CPI

BSET C,

LSR, ROL,

CLC, SEC

ROR

N

ADD, ADC, ADIW, DEC,

AND, ANDI, OR,

CP, CPC,

BCLR N,

ASR, LSL,

CLR

INC, SUB, SUBI, SBC,

ORI, EOR, COM,

CPI

BSET N, CLN,

LSR, ROL,

SBCI, SBIW

NEG, SBR, CBR

SEN, TST

ROR

V

ADD, ADC, ADIW, DEC,

AND, ANDI, OR,

CP, CPC,

BCLR V,

ASR, LSL,

CLR

INC, SUB, SUBI, SBC,

ORI, EOR, COM,

CPI

BSET V, CLV,

LSR, ROL,

SBCI, SBIW

NEG, SBR, CBR

SEV, TST

ROR

S

SBIW

-

-

BCLR S,

-

-

BSET S, CLS,

SES

H

ADD, ADC, SUB, SUBI,

NEG

CP, CPC,

BCLR H,

-

-

SBC, SBCI

CPI

BSET H, CLH,

SEH

T

-

-

-

BCLR T,

-

-

BSET T, BST,

CLT, SET

I

-

-

-

BCLR I, BSET

-

RETI

I, CLI, SEI

Port details

Port details of the most common ports are shown in an extra table (see annex).


Avr-Asm-Tutorial

15

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

SRAM

Using SRAM in AVR assembler language

Nearly all AT90S-AVR-type MCUs have static RAM (SRAM) on board (some don't). Only very simple assembler programs can avoid using this memory space by putting all info into registers. If you run out of registers you should be able to program the SRAM to utilize more space.

What is SRAM?

SRAM are memories that are not directly accessable to the central processing unit (Arithmetic and Logical Unit ALU, sometimes called accumulator) like the registers are. If you access these memory locations you usually use a register as interim storage. In the following example a value in SRAM will be copied to the register R2 (1st command), a calculation with the value in R3 is made and the result is written to R3 (command 2). After that this value is written back to the SRAM location (command 3, not shown here).

So it is clear that operations with values stored in the SRAM are slower to perform than those using registers alone. On the other hand: the smallest AVR type has 128 bytes of SRAM available, much more than the 32 registers can hold.

The types from AT90S8515 upwards offer the additional opportunity to connect additional external RAM, expanding the internal 512 bytes. From the assembler point-of-view, external SRAM is accessed like internal SRAM. No extra commands must be used for that external SRAM.

For what purposes can I use SRAM?

Besides simple storage of values, SRAM offers additional opportunities for its use. Not only access with fixed addresses is possible, but also the use of pointers, so that floating access to subsequent locations can be programmed. This way you can build up ring buffers for interim storage of values or calculated tables. This is not very often used with registers, because they are too few and prefer fixed access.

Even more relative is the access using an offset to a fixed starting address in one of the pointer registers. In that case a fixed address is stored in a pointer register, a constant value is added to this address and read/write access is made to that address with an offset. With that kind of access tables are better used.

The most relevant use for SRAM is the so-called stack. You can push values to that stack, be it the content of a register, a return address prior to calling a subroutine, or the return address prior to an hardwaretriggered interrupt.

How to use SRAM?

To copy a value to a memory location in SRAM you have to define the address. The SRAM addresses you can use reach from 0x0060 (hex notation) to the end of the physical SRAM on the chip (in the AT90S8515 the highest accessable internal SRAM location is 0x025F). With the command

STS 0x0060, R1

the content of register R1 is copied to the first SRAM location. With

LDS R1, 0x0060

the SRAM content at address 0x0060 is copied to the register. This is the direct access with an address that has to be defined by the programmer.

Symbolic names can be used to avoid handling fixed addresses, that require a lot of work, if you later want to change the structure of your data in the SRAM. These names are easier to handle than hex numbers, so give that address a name like:

.EQU MyPreferredStorageCell = 0x0060

Avr-Asm-Tutorial

16

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

STS MyPreferredStorageCell, R1

Yes, it isn't shorter, but easier to remember. Use whatever name that you find to be convenient.

Another kind of access to SRAM is the use of pointers. You need two registers for that purpose, that hold the 16-bit address of the location. As we learned in the Pointer-Register-Division pointer registers are the pairs X (XH:XL, R27:R26), Y (YH:YL, R29:R28) and Z (ZH:ZL, R31:R30). They allow access to the location they point to directly (e.g. with ST X, R1), after prior decrementing the address by one (e.g. ST -X, R1) or with subsequent incrementation of the address (e.g. ST X+, R1). A complete access to three cells in a row looks like this:

.EQU MyPreferredStorageCell = 0x0060

.DEF MyPreferredRegister = R1

.DEF AnotherRegister = R2

.DEF AndAnotherRegister = R3

LDI XH, HIGH(MyPreferredStorageCell)

LDI XL, LOW(MyPreferredStorageCell) LD MyPreferredRegister, X+

LD AnotherRegister, X+ LD AndAnotherRegister, X

Easy to operate, those pointers. And as easy as in other languages than assembler, that claim to be easier to learn.

The third construction is a little bit more exotic and only experienced programmers use this. Let's assume we very often in our program need to access three SRAM locations. Let's futher assume that we have a spare pointer register pair, so we can afford to use it exclusively for our purpose. If we would use the ST/LD instructions we always have to change the pointer if we access another location. Not very convenient.

To avoid this, and to confuse the beginner, the access with offset was invented. During that access the register value isn't changed. The address is calculated by temporarly adding the fixed offset. In the above example the access to location 0x0062 would look like this. First, the pointer register is set to our central location 0x0060:

.EQU MyPreferredStorageCell = 0x0060

.DEF MyPreferredRegister = R1

LDI YH, HIGH(MyPreferredStorageCell)

LDI YL, LOW(MyPreferredStorageCell)

Somewhere later in the program I'd like to access cell 0x0062:

STD Y+2, MyPreferredRegister

Note that 2 is not really added to Y, just temporarly. To confuse you further, this can only be done with the Y- and Z-register-pair, not with the X-pointer!

The corresponding instruction for reading from SRAM with an offset

LDD MyPreferredRegister, Y+2

is also possible.

That's it with the SRAM, but wait: the most relevant use as stack is still to be learned.

Use of SRAM as stack

The most common use of SRAM is its use as stack. The stack is a tower of wooden blocks. Each additional block goes onto the top of the tower, each recall of a value removes the upmost block from the tower. This structure is called Last-In-First-Out (LIFO) or easier: the last to go on top will be the first coming down.

Defining SRAM as stack

To use SRAM as stack requires the setting of the stack pointer first. The stack pointer is a 16-bit-pointer, accessable like a port. The double register is named SPH:SPL. SPH holds the most significant address byte, SPL the least significant. This is only true, if the AVR type has more than 256 byte SRAM. If not, SPH is undefined and must not and cannot be used. We assume we have more than 256 bytes in the following examples.

To construct the stack the stack pointer is loaded with the highest available SRAM address. (In our case the tower grows downwards, towards lower addresses!).

.DEF MyPreferredRegister = R16

LDI MyPreferredRegister, HIGH(RAMEND) ; Upper byte OUT SPH,MyPreferredRegister ; to stack pointer

LDI MyPreferredRegister, LOW(RAMEND) ; Lower byte OUT SPL,MyPreferredRegister ; to stack pointer