Pages: [1] 2   Go Down
Author Topic: Mixing C with Assembler: Assembly code before push instructions in a function  (Read 1668 times)
0 Members and 1 Guest are viewing this topic.
0
Offline Offline
Jr. Member
**
Karma: 4
Posts: 94
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hello,
I am calling a function from an interrupt, and due to timing issues I would like to preserve the state of a register very quickly when the interrupt takes place.

The C compiler generates a bunch of code to preserve registers when the ISR (or any function) is called.  I want to insert some assembler which is to be run before those registers are preserved.  Is this possible?  Is there some directive I can add to my asm line to tell it exactly where to inject itself in the C code?  Thanks.

The code looks iike this.  I know- the push instructions are there to preserve registers.  Consider my asm to be pseudo-code; I will figure out how to use __tmp_reg__ if I decide that my efforts are worth it.  Thanks:
Code:
ISR(PCINT2_vect) {
    uint8_t value;
    // I want this to take place before the compiler-created push instructions.
    asm volatile("in %0, %1" : "=r" (value) : "I" (_SFR_IO_ADDR(PORTD)));
    portD.PCint();
}
Logged

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 212
Posts: 13085
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset


Do you understand the purpose of the push instructions?

Quote
I want to insert some assembler which is to be run before those registers are preserved.

Does the "some assembler" include an in instruction (like your pseudo-code)?
Logged

Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 653
Posts: 50881
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
I want to insert some assembler which is to be run before those registers are preserved.
Why? The registers are copied, not altered. You can still access the register afterwards, and get the same value.
Logged

UK
Offline Offline
Shannon Member
****
Karma: 223
Posts: 12630
-
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I would like to preserve the state of a register very quickly when the interrupt takes place.

I guess you mean you want to preserve the state of an I/O register at the earliest possible point in an ISR.

Have you thought of a way to do that without using any processor registers? Since you can't know what processor registers are in use you mustn't use them until you have preserved their state so that they can be restored afterwards.
Logged

I only provide help via the forum - please do not contact me for private consultancy.

0
Offline Offline
Jr. Member
**
Karma: 4
Posts: 94
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset


Do you understand the purpose of the push instructions?

Quote
I want to insert some assembler which is to be run before those registers are preserved.

Does the "some assembler" include an in instruction (like your pseudo-code)?


Yes, and yes.  I plan on taking advantage of this:

Quote
Register r0 may be freely used by your assembler code and need not be restored at the end of your code. It's a good idea to use __tmp_reg__ ... instead of r0 or r1, just in case a new compiler version changes the register usage definitions.
Logged

0
Offline Offline
Jr. Member
**
Karma: 4
Posts: 94
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I would like to preserve the state of a register very quickly when the interrupt takes place.

I guess you mean you want to preserve the state of an I/O register at the earliest possible point in an ISR.
Exactly!  You hit the nail on the head.  I am writing a library and I am preserving the state of an I/O register- ostensibly at the time of the interrupt- but with all the push instructions, it takes place some 4 microseconds (I figure) after the actual interrupt.

Quote
Have you thought of a way to do that without using any processor registers? Since you can't know what processor registers are in use you mustn't use them until you have preserved their state so that they can be restored afterwards.
Not really.  I am aware of this:
Quote
Register r0 may be freely used by your assembler code and need not be restored at the end of your code. It's a good idea to use __tmp_reg__ and __zero_reg__ instead of r0 or r1, just in case a new compiler version changes the register usage definitions.
So I believe I can safely use r0.  All I need is to use the in instruction, then save that register to RAM, and I should be good to go.
Logged

UK
Offline Offline
Shannon Member
****
Karma: 223
Posts: 12630
-
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

That sounds viable, although I don't know enough about the behaviour of those temp registers to know how long their contents will be preserved. For example, if they're trampled by a push operation then using one of these as a scratch area while you do the push would not work very well. smiley

The only way I can think of to achieve that solution is to write your ISR handler in assembler, have this save your I/O register in your temp register and then call a 'C'/C++ function for whatever logic you want to code in higher level languages. You sound as if you're comfortable writing in assembler, but if you aren't you could get a starting point by listing the assembler generated for your existing handler.
Logged

I only provide help via the forum - please do not contact me for private consultancy.

0
Offline Offline
Jr. Member
**
Karma: 4
Posts: 94
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

... if they're trampled by a push operation then using one of these as a scratch area while you do the push would not work very well.

... write your ISR handler in assembler, have this save your I/O register in your temp register and then call a 'C'/C++ function for whatever logic you want to code in higher level languages. You sound as if you're comfortable writing in assembler, but if you aren't you could get a starting point by listing the assembler generated for your existing handler.
Excellent suggestion, thanks for the tips!  I am not comfortable writing in assembler, so I am going to list the assembler generated and work from there :-) .  But, I am comfortable living life on the edge!  So I'm going to try it.

My intention is to get the heck out of the way of the compiler as quickly as possible, so I'm thinking I might need 2 assembly instructions:  1 to read the register, the other to save it to a C variable (ie, RAM).

I notice that my idea is not so far fetched, as a matter of fact I found this link: http://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html .  The ISR_NAKED is what I want.  So, my idea is the following, pseudo-code:

Code:
ISR(PCINT0_vect, ISR_NAKED)
{
    asm (read_in_register_to_r0)
    asm (store_to_ram)
    // The following function call will preserve the registers on the stack.  Since I haven't
    // mucked with any but r0 thus far in my ISR, I should be ok.  The function call will
    // also restore the necessary registers.
    C_function_call();
    asm (reti);
}

I notice that the ISR pushes a whole bunch of registers on the stack.  Function calls do not necessarily save the same large set.  I assume that's because the compiler is smart enough to know that a function may not stomp on all the registers, so it's judicious in its saving.  Thus, it seems to me the ISR is storing such a large set because it doesn't have any foreknowledge of what it should or should not preserve, so it paints with a large brush.

But if the *only* thing my ISR does is as listed:  1. my assembly, 2. call a C function, 3. return, then I assume that the heavy lifting of pushing and popping the necessary registers, if handled solely by the C function call in the ISR, should be sufficient. There are no other dragons lying about out, are there?  Again, assuming my ISR is just that lean.

Right now my ISR looks like this:
Code:
ISR(PCINT0_vect) {
        portD.PCint();
}
...so it's doing two bunches of push/pop operations:  The entry into the ISR, and the entry into the function call, which is overkill.

...Just checking my assumptions...
Logged

Offline Offline
God Member
*****
Karma: 32
Posts: 507
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset


Quote
Register r0 may be freely used by your assembler code and need not be restored at the end of your code. It's a good idea to use __tmp_reg__ and __zero_reg__ instead of r0 or r1, just in case a new compiler version changes the register usage definitions.
So I believe I can safely use r0.  All I need is to use the in instruction, then save that register to RAM, and I should be good to go.

No, you are in an ISR. The main code might be using r0 temporarily when your ISR gets called - if you touch r0,  you mess up the main code.

I once tried doing a similar thing with r1, thinking that because it should always be 0 I could quickly use it in an ISR and zero it again afterward - and it didn't work either. Turns out the zero register wasn't always zero!
« Last Edit: February 07, 2012, 01:07:22 pm by stimmer » Logged


0
Offline Offline
Jr. Member
**
Karma: 4
Posts: 94
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Good point.  I may use r0, but I'll push/pop it.  Thanks- 1 push instruction won't hurt.
Logged

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 212
Posts: 13085
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset


An addendum to @stimmer's post...  Before your interrupt service routine or any function called by your ISR uses any register (R0 - R31), the register value must be preserved.  Before your interrupt service routine or any function called by your ISR performs any math operation, the status register value (SREG) must be preserved.

Quote
But if the *only* thing my ISR does is as listed:  1. my assembly, 2. call a C function, 3. return, then I assume that the heavy lifting of pushing and popping the necessary registers, if handled solely by the C function call in the ISR, should be sufficient.

Wrong.
Logged

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 212
Posts: 13085
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Good point.  I may use r0, but I'll push/pop it.  Thanks- 1 push instruction won't hurt.

Not good enough.  You have to preserve the registers used by your function before the function is called.
Logged

0
Offline Offline
Jr. Member
**
Karma: 4
Posts: 94
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Good point.  I may use r0, but I'll push/pop it.  Thanks- 1 push instruction won't hurt.

Not good enough.  You have to preserve the registers used by your function before the function is called.

I understand about r0 (which I'll use) and SREG, but doesn't the compiler push all registers it's concerned with?  I've been rummaging around in avr-objdump and I observe that the compiler preserves registers upon entry into the function, not before functions are called.

I am setting ISR_NAKED for the ISR, but when the ISR calls another function that will not be naked.
Logged

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 212
Posts: 13085
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
but doesn't the compiler push all registers it's concerned with?

Yes.

But, there is an assumption that whoever called the ISR preserved certain registers.  That's the purpose of all those pushes; to do what a caller would have done if the ISR had been called like a normal function.
Logged

Global Moderator
Melbourne, Australia
Offline Offline
Brattain Member
*****
Karma: 511
Posts: 19350
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I am calling a function from an interrupt, and due to timing issues I would like to preserve the state of a register very quickly when the interrupt takes place.

Why do you want to do this?

Also, I don't know if you can do much better than the compiler. For example:

Code:
ISR (SPI_STC_vect)
{
// do nothing 
}

Generates:

Code:
ISR (SPI_STC_vect)
 100: 1f 92        push r1   // save the "zero" register
 102: 0f 92        push r0   // save R0
 104: 0f b6        in r0, 0x3f ; 63  // get SREG into R0
 106: 0f 92        push r0   // save SREG
 108: 11 24        eor r1, r1  // make sure R1 is zero
{
// do nothing 
}
 10a: 0f 90        pop r0   // get SREG back
 10c: 0f be        out 0x3f, r0 ; 63 
 10e: 0f 90        pop r0   // get the old R0 back
 110: 1f 90        pop r1   // get the "zero" register back
 112: 18 95        reti

You can't afford to do much less than that, except perhaps the R1 stuff. Leaving out the R1 stuff would save 3 clock cycles. I don't think you can omit the rest and have it work.

What is it, exactly, that you need to save so quickly?
Logged

http://www.gammon.com.au/electronics

Please post technical questions on the forum - not to me by personal message. Thanks a lot.

Pages: [1] 2   Go Up
Jump to: