Atmega Assembler

Hello guys... I want to write this simple arduino code in assembler:

int pin = 13;
volatile int state = LOW;

void setup()
{
  pinMode(pin, OUTPUT);
  attachInterrupt(0, blink, RISING);
}

void loop()
{
  digitalWrite(pin, state);
}

void blink()
{
  state = HIGH;
}

This code simply light on pin 13 when there is a rising edge on pin 2
I write with avr studio this code:

.nolist			.
.include "m328pdef.inc"

.org	0x0002
	rjmp	button_on
	

button_on:
	out		PORTB,r16
	
.list

	rjmp	main

main:
	ldi		r16,0x20
	out		DDRB,r16

If I put the instruction "out PORTB,r16" under the main label the output pin 13 correctly light on, so I suppose the error is in the interrupt...

I also try this code but it doesn't work neither:

.nolist			.
.include "m328pdef.inc"	

	
.list
.org	0x0000
	rjmp	main
.org	0x0002
	rjmp	button_on

main:
	ldi		r16,0x20
	out		DDRB,r16
	sei
loop:
	rjmp	loop

button_on:
	out		PORTB,r16
	reti

Thanks in advance and sorry for my bad english!

xelendilx:

main:
ldi		r16,0x20
out		DDRB,r16

What happens next?

xelendilx:
I also try this code but it doesn't work neither:

Better read the Atmega datasheet. Interrupts don't just turn themselves on.

Maybe in the second code I forgot to set the interrupt mask?

So I've to add this instructions in the main program

ldi r17, 0x01 ; any logical change, better for me then rising edge
out EICRA, r17
out EIMSK, r17

Might one ask, what you expect to gain by writing it in assembler? If it's just because digitalWrite() is slow, do direct port manipulations in C, this is just as fast as writing it in assembler. In most cases, the code produced by the compiler is as good or better than what all but the most experiences assembler programmers produce. You just need to make sure you define your variables of the proper type (like int8_t instead of int for small values) to give the compiler a chance to optimise it.

And if you really have a small piece of code where you have an improved version in assembly, use the C inline assembler. For any real world project this is a far more sensible approach.

Korman

  1. the functions in the Arduino core can be somewhat inefficient, but with the code you're using there, it can't get much more streamlined.
  2. that code you wrote (in C) won't work. it'll turn on the LED and just keep turning it on, and on, and on, and on, and on, every time that interrupt goes off... it'll stay on.
  3. that assembly code... umm... /coff. Dude, OK, avoiding trashing the noobs, but seriously? It won't even run. It'll set interrupts then inside "loop" the only thing it'll do is continuously loop back to itself... "main" will never run, and "sei" just sets... nothing, because no interrupts were set up. :confused:

I dunno... until you start tearing into the temp\build*.* folders with "avr-objdump -S" and stepping through the assembly in your head to find places to optimize the code... probably best-off keeping to the standard Arduino code :slight_smile:

I'm not trying to code better than the compiler. I know we are in 2011 and writing code in assembler is not a good choice. I'm not stupid, thanks for asking.

I simply want to understand how interrupt works for an university project. And the C code must do exactly what it does: a stupid thing to understand how interrupt works.

Ah... these are the things you say in your first post to dismiss the glaring issue of "why on earth is this guy trying to do this in assembly?" :wink:

Alrighty then. To answer your question, take a look at Arduino\hardware\cores\arduino\WInterrupts.c. That's a load of C code that gets added to your program when you use attachInterrupt. Now take a look at wiring.c... that gets added to every program too. And wiring_digital.c while you're at it. All those files hold the actual code behind those simple pinMode, attachInterrupt, digitalWrite, etc functions.

Now here's how it's done.

  1. A segment of ASM is written, to perform the function you want the interrupt to do.
  2. The address of that segment (remember: AVR uses "word" addresses, so a byte address of 0x40 is really AVR address 0x20 - at least as far as I've found) is loaded into an interrupt vector of your choice - I'm guessing INT0. That way the CPU knows where to send itself when that interrupt is triggered.
  3. Then, you set the interrupt registers to enable the interrupt in the mode you desire (enable INT0 on rising edge; enable INT0 interrupt code).
  4. Then, you just have your "main loop" consist of reading that value from RAM - which the interrupt will modify when it's triggered - and act accordingly. Your program loop will be COMPLETELY unaware of when the interrupt is triggered, so you always need to read a fresh value into a register then compare it.

That's about as basic as it gets... I'm probably even leaving a lot out there too. The key is that you create a function, point that function into the interrupt vector table (I'm not 100% clear on that myself), then tell the CPU that "Hey, I have an interrupt set up here, so use it like this" (using a couple bit-flags). And you flip the "big master breaker" switch - the "global interrupts flag" that turns all registered interrupts on or off - using "sei" (SEt Interrupts) or "cli" (CLear Interrupts). That just turns one bit to 1 or 0, doesn't erase all your interrupts or anything, just "pauses" or "resumes" them.

This document may be your best friend... trust me, to learn all this stuff I just blabbed about over the last 2 weeks, I've spent about 6 hours grinding over this PDF religiously: http://www.atmel.com/dyn/resources/prod_documents/doc8271.pdf - the full spec/info document on how to use these chips :slight_smile:

xelendilx:
I simply want to understand how interrupt works for an university project. And the C code must do exactly what it does: a stupid thing to understand how interrupt works.

Oh, why didn't you say so in the first place? In this case, take a look at the datasheet of the ATmega328 by Atmel, more specifically chapters 6.7, 11 and 12. You'll find there working sample code how to things in C and in Assembly, C being first. Most things are explained there, for the rest look into the Arduino boot-loader source or avr-dump output of your program.

Doing this would have prevented that you waste your and our time with a wild goose chase.

Korman

May I suggest, "Programming Microcontrollers using Assembly Language" by Chuck Baird.

It works with the simple modification I suggest in post #3

@FalconFour: I know the theory of interrupts, I only want to see how it works in practise with the atmega. I think atmega is an example of auto vectored interrupts (I'm not sure of the exact term in english). So the number of the interrupt line raised automatically define the vector of the interrupts.

xelendilx:
I think atmega is an example of [...]

Don't think, be certain. Read it all in the data-sheet, that's why Atmel publishes it. Anything else is just messing around, which you shouldn't do if you take your work seriously.

Korman

xelendilx:
It works with the simple modification I suggest in post #3 ...

I'm glad my comment nudged you in the right direction. I thought your code could potentially work, except that interrupts are off after a reset.

xelendilx:
I think atmega is an example of auto vectored interrupts (I'm not sure of the exact term in english). So the number of the interrupt line raised automatically define the vector of the interrupts.

This may be losing something in the translation, but I don't know about "the number of the interrupt". Each interrupt feature (eg. brownout) is assigned an interrupt in the interrupt vector table, which itself starts at a location dependent on the fuse settings.

FalconFour:
2. The address of that segment (remember: AVR uses "word" addresses, so a byte address of 0x40 is really AVR address 0x20 - at least as far as I've found) is loaded into an interrupt vector of your choice - I'm guessing INT0.

I'm not sure about this either. Maybe a terminology issue. Address 0x20 is address 0x20 as far as I am concerned. According to the Atmega328 docs (page 65 et al.) the interrupt vectors 1, 2, 3 etc. are at addresses 0x0000, 0x0002, 0x0004 etc. It also says (page 57):

Each Interrupt Vector occupies two instruction words in ATmega168PA and ATmega328P, and one instruction word in ATmega48PA and ATmega88PA.

Thus the vectors are numbered consecutively, but the actual addresses go up by two (on some processors at least) simply because it take a JMP xxx to implement the vector, and that is two bytes.

@Nick Gammon: I mean each interrupt line that the mcu has directly to an interrupt vector (for example 0x0002 for INT0)... But I'm not sure about this! Thanks you for the suggestion!

@Korman: This isn't an homework, i'm trying to understand atmega for a better comprension of the argument!!

Now I want to modify the code for reading the value at the int0 pin (pd2, arduino digital pin 2) and light on or off the led on pb5 (arduino pin 13)...

I don't understand well how to read a value from a pin... I suppose I must read the PIND value with the in command and i write this code:

.nolist			.
.include "m328pdef.inc"	

	
.list
.org	0x0000
	rjmp	main
.org	0x0002
	rjmp	button_on

.org	0x003A	
main:
	ldi 	r16, 0x01
	sts 	EICRA, r16
	out 	EIMSK, r16
	ldi		r16,0x20
	out		DDRB,r16
	ldi		r16,0x00
	out		DDRD,r16
	//ldi		r16,0xFF
	//out		PORTD,r16   ;I read somewhere to add this, but I don't understand those instructions...
	rcall	set_light
	sei
loop:
	rjmp	loop

button_on:
	rcall	set_light	
	reti

set_light:
	ldi		r16,LOW(RAMEND) ;Initiate Stackpointer
	out		SPL,r16
	ldi		r16,HIGH(RAMEND)
	out		SPH,r16
	in		r16,PIND
	andi	r16,0x04
	breq	light_off
	//sbi		PORTB,5
	ldi		r16,0x20
	out		PORTB,r16
	rjmp	end_set_light
light_off:
	//cbi		PORTB,5
	ldi		r16,0x00
	out		PORTB,r16
end_set_light:	
	ret

But the branch in the set_light routine goes ever in the light_off label.... Why??

        ldi 	r16, 0x01
	sts 	EICRA, r16
	out 	EIMSK, r16

Why do you "out" to one register but "sts" to another one?

And is this really wise?

button_on:
	rcall	set_light	
	reti

set_light:
	ldi		r16,LOW(RAMEND) ;Initiate Stackpointer
	out		SPL,r16
	ldi		r16,HIGH(RAMEND)

On an interrupt you call a function, which then changes the stack pointer! Surely the chances of that returning correctly are slim.

Hmm... STS and OUT is definitely going to be a problem (OUT uses different addresses in a restricted range of I/O ports, while STS uses RAM addresses), but I dunno about the stack pointer. It looks like it just reads the stack pointer and sets it back to the same value for some reason... not sure of the reason though =P

Actually, hell, the entire set_light routine makes my head hurt. Why are you even dicking with the stack pointer anyway? That's all autonomous, there's no reason to even read/write/look at the stack pointer as far as I figure... :expressionless:

edit: Wait, what the...? It's setting SPL and SPH to the end of the RAM area, now I'm confused. It can't execute from RAM (all code is executed from Flash ROM directly), so... yeah, I'm lost, and my head hurts even more now.

@ Nick: out doesn't work with the first register (I recieve an error with avr). Only addresses up to 0x69 can be used with out...

@ Nick and FalconFour: I'm really both stupid and too tired to program in assembler the night. I read that stack pointer must be initialized on avr-freaks and I copy the code in the wrong function... Of course stack pointer must be initialized in the main function, so that the main function can save the return address on the stack...

With this simple mod all works fine :slight_smile:

Hey, you're even teaching me things! XD Didn't know the SP had to be initialized, so that's good to know. I've only ever dabbed in ASM as parts of a C project (to get things done more efficiently than the compiler would optimize it). Great to hear it works out now! :smiley:

xelendilx:
@ Nick: out doesn't work with the first register (I recieve an error with avr). Only addresses up to 0x69 can be used with out...

Ah OK. However, 63 (decimal, not hex) I think:

  __asm__ __volatile__ (
    "  ldi r16,2\n"
    "  out 64,r16\n"
    :::);

Gives this error:

/var/folders/1A/1AUycq24Ev4PKBlyDOFIG++++TI/-Tmp-//cckTEn4G.s: Assembler messages:
/var/folders/1A/1AUycq24Ev4PKBlyDOFIG++++TI/-Tmp-//cckTEn4G.s:30: Error: number must be positive and less than 64