Файл: 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