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.
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.
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.
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.
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
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?"
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.
A segment of ASM is written, to perform the function you want the interrupt to do.
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.
Then, you set the interrupt registers to enable the interrupt in the mode you desire (enable INT0 on rising edge; enable INT0 interrupt code).
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
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.
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.
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.
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??
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...
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...
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!
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