Synchonizing RAM locations between .C and .S in AVR GCC

Go To Last Post
19 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello,

I have a project in which I want to handle one specific time and process critical interrupt entirely in assembler, but which on the whole is so complex I prefer C for the rest of the programming.
For clarity (lots of asm code!) and full control of the resulting asm code I put this routine in a .S file rather than inline-assembler in the C code.

I have one problem which I cannot find a usable answer to on this forum or through google, I have spent many an hour searching and probably it's my search terms, but I have found only posts about external RAM and other such foolish issues ;-).

I want to store a set of 16 variables, each 16 bit in RAM from the C code, which will be used by the assembler routine if necessary. I cannot however figure out how to make AVR GCC compile my program so that my array as defined in the main C file ends up in one specific part of the (internal!) RAM, so that I can be sure where to retrieve it in assembler. I process them as single bytes in assembler, so it's no problem to use constants in an LDS command (no runtime variation of which byte is processed where)

Is there an AVR GCC wizard out there with the answer? I'm pretty sure making all 32 registers volatile and storing my variables there is the worst possible solution! For many reasons... :-P

Many cheeses!,

Asmyldof!

Embedded design is as much a life choice as any other and I demand the right to legally marry my work.
-------
If it helps, I can PM you my answer in a number of different languages. Ask if you have trouble reading my high-speed-babble in English.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

But the assembler just accesses the variables symbolically and the linker will fix it up?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Just write your ISR() in C. In its own independent C source file. This way, the compiler checks that you have all necessary external references.

Check that it all works ok with your project.

Now compile it to an .S file. I would rename this to ISR_asm.S or similar.

Lo and behold you have a real ASM file with no GCC inline gobbledygook.

Strip out most of the garbage relating to debugging.
Hand optimise the single ISR().

Replace ISR_C.c with ISR_asm.S in your project. Re-build and you are done.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Here's an example that works:

#include  

void copy_var(void);

uint16_t one_var;
uint16_t another_var;

int main(void) 
{ 
	one_var = 0x1234;

	while(1) { 
		copy_var();
	} 
}
#include  

	.global copy_var

copy_var:
	ldi r30,lo8(one_var)
	ldi r31,hi8(one_var)
	ldi r28,lo8(another_var)
	ldi r29,hi8(another_var)
	ld r24, Z+
	st Y+, r24
	ld r24, Z
	st Y, r24
	ret

Which copies the 0x34 in location 0x0060 and 0x12 in 0x0061 (where the linker chose to locate 'one_var' on a mega16) into locations 0x0062/0x0063 (where it put 'another_var')

Nothing was done to give the variables an absolute address - the linker just starts (on a mega16) placing .bss from 0x0060 onwards (as there are no .data entries)

Cliff

PS I've done something very naughty in this code and not read:

http://www.nongnu.org/avr-libc/u...

and just corrupted r24,28,29,30,31 without bothering to consider if I should have PUSH/POP preserved them within copy_var or not.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Actually you can even write this:

This

#include 

extern uint16_t one_var;
extern uint16_t another_var;

void copy_var(void)
{
   one_var = another_var;
}

generates:

	.text

	.global	copy_var

copy_var:
	lds r24,another_var
	lds r25,(another_var)+1
	sts (one_var)+1,r25
	sts one_var,r24
	ret

You are allowed to trash r24, r25 in a regular function. Of course if you had started with an ISR(), avr-gcc would have written your prolog and epilog for you.

Most of the stuff in the generated .S file was 'debug' information. What you really want is a nice clean ASM file. (as you can see above)

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
PS I've done something very naughty in this code and not read:

http://www.nongnu.org/avr-libc/u...

and just corrupted r24,28,29,30,31 without bothering to consider if I should have PUSH/POP preserved them within copy_var or not.

This is not pertinent for Asmyldof's case, as he wants to write ISRs which have to store/restore all registers (including SREG).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm currently at work, so I cannot test it on the actual hardware, but all of the above tricks seem to compile properly, so I'm pretty sure I can take it from here.

weird thing is that last time I tried using a variable name from a C file in my assembler file (first thing I tried) it puffed out a heap of errors like there was no tomorrow. (what?! - sorry, I liked the sound of it, even if it makes no sense.)
But now it seems to be fine, probably something I changed in the makefile to fix other errors or some such, so all is well now!

*hugs all of you for being so lightning fast in responding!*

BTW as a nerd born and raised I would like to say to the lot of you that I utterly love all the babble about semantics! ^.^ (done something naughty... not pertinent to my problem...)

Not to worry, with over 10 years of assembler under my belt the pre-process pushing of dangerous registers is second nature. Before even thinking of what my interrupt routine will do it always already has

ISRLabel:
 push register_a ; usually a more descriptive name
 GetIO register_a, SREG ; macro GetIO uses in or lds depending on IO address
 push register_a
 ; think of something smart to do later when not hung over or drunk 
 ;....
 ; (5% of my waking time)
 
 pop register_a
 PutIO SREG, register_a
 pop register_a
 reti

Embedded design is as much a life choice as any other and I demand the right to legally marry my work.
-------
If it helps, I can PM you my answer in a number of different languages. Ask if you have trouble reading my high-speed-babble in English.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

By the way, a relevant page from the user manual:

http://www.nongnu.org/avr-libc/u...

though personally I don't like the fact that it defines the variables in the project.h:

/*
 * Global register variables.
 */
#ifdef __ASSEMBLER__

#  define sreg_save	r2
#  define flags		r16
#  define counter_hi    r4

#else  /* !ASSEMBLER */

#include 

register uint8_t sreg_save asm("r2");
register uint8_t flags     asm("r16");
register uint8_t counter_hi asm("r4");

#endif /* ASSEMBLER */

but it's doing this to also bind them to registers and make both the .c (!__ASSEMBLER__) and .S (__ASSEMBLER__) aware of the registers used.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Of course you can do it the hard way. i.e. read all the GCC documentation.

The 'easy' way means that the Compiler has already done what it thinks it needs. You just have to glance over the code and apply a little human intuition.

The efficiency of your ISR() is always going to come back to your algorithm. Reading and writing a few special function registers does not take long in ANY language.

So I would always seek the best algorithm in the HLL first. Then the tweaking in ASM is trivial.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Asmyldof wrote:
Not to worry, with over 10 years of assembler under my belt [...]

That actually might be a drawback when it comes to avr-as.

Beware, with section-dependent labels - which are those to be fixed up by the linker - you cannot perform any arithmetics, only those for which the assembler emits special "flags" understood by the linker.

Unfortunately, the documentation is truely GNU, i.e. you are mostly on your own. See e.g. https://www.avrfreaks.net/index.p...

JW

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:

The efficiency of your ISR() is always going to come back to your algorithm.

[...]

So I would always seek the best algorithm in the HLL first. Then the tweaking in ASM is trivial.


True, very true, and I'm sure that if I started in C, had it compiled, then trimmed the code down it would work and probably be easier for most.
But even though I may be relatively young I grew up with propagation delays, skew on anything above 1MHz, and thus also the step by step mental maths that make it easier for me to write something quick and efficient in assembler. It's a personal preference mostly and I cannot deny that chances are it might be outdated, but I know I can do a big chunk of good, efficient and fast assembler in a very decent time.
It's just the compilers that sometimes baffle me ;-)

@wek and basically all of you: You have been very valuable to me and my "little" experimenting board. I now feel a tiny bit less afraid of the scariness that is AVR GCC ;-).

Oh and, just for those of you who are still in doubt: Atmel AVR is far from the weirdest architecture out there *points yonder over the horizon*.
:-P
May just be a personal preference again and I freely admit that I too sometimes prefer an accumulator or even substructures and parallel busses in 8bit devices, but for 9 out of 10 of my embedded ideas, Atmel's pretty amazing. Specially when a controller doesn't have to cost more than €0,70 - restrictions apply ;-).

Embedded design is as much a life choice as any other and I demand the right to legally marry my work.
-------
If it helps, I can PM you my answer in a number of different languages. Ask if you have trouble reading my high-speed-babble in English.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I would strongly recommend the "Write ISR() in C" approach. Your algorithm will be clear to both you and anyone who maintains your code.

You are clearly experienced, and will be writing lean ISR()'s in the first place. So they will physically consist of very few lines of code, and hence easy to follow.

Your system will 'work' with the sub-optimal C ISR().
Hand optimising a C generated .S file is still a good approach.

I know that you may be very happy with writing the ASM from scratch. I am just suggesting that the 'habit of a lifetime' is not always the easiest to change.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:
Your system will 'work' with the sub-optimal C ISR(). Hand optimising a C generated .S file is still a good approach.
I frequently do this (for short, time-critical ISRs and the like) and then I leave the C version in the assembler source file as a comment. The C source then constitutes a very concise description of the algorithm (that is supposed to be) implemented by the assembler code.

CH
==

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

cmhicks wrote:
I leave the C version in the assembler source file as a comment. The C source then constitutes a very concise description of the algorithm (that is supposed to be) implemented by the assembler code.
Remind me to avoid a job where I would need to work with your sources.

C is no less gibberish than asm.

JW

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

wek wrote:
cmhicks wrote:
I leave the C version in the assembler source file as a comment. The C source then constitutes a very concise description of the algorithm (that is supposed to be) implemented by the assembler code.
Remind me to avoid a job where I would need to work with your sources.

C is no less gibberish than asm.

JW

OK. I should have been more precise.

For a colleague who is not an expert for the assembler or microprocessor architecture in question, the C source makes a useful component of the complete documentation.

I agree that in a purist sense, C can be ambiguous, but ASM is not. Therefore, for a given algorithm, a correct ASM implementation may be a better formal description of the algorithm than a correct C implementation (which may make implicit assumptions of behaviour outside the scope of the C standard - eg sizeof(int) or endian-ness). However...

1. I strive, as far as possible, to write my C in as unambiguous and clear a way as possible, and such that it does not depend on compiler- or architecture-specific behaviour.

2. What I am trying to achieve is that a jobbing programmer used to high-level languages can glance at the source and see quickly from the documentation and comments in the source file what it does and how to use it. Where a function is implemented in assembler, I find that it is often useful to include a C equivalent. This is a small extension to the widespread practice of including a C-prototype for C-callable ASM functions.

3. I thought *I* was a pedant :-)

CH
==

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I use comments.

JW

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Haha, this is becoming a wonderful discussion!

To y'all: I will probably be writing everything in C in a few months, but time-critical is not only something that lives in my code. Since I very often work 14 hours a day, I always choose the thing I think fastest with for my spare time projects.
Once I have used AVR GCC and all it's little weird 'features' more I will probably write C code as fast as assembler and be more optimistic about optimizing a generated ASM file.

Just one thing, where and/or how does AVR Studio/AVR GCC create an assembler file for me to edit? I think this would be some compiler option in either the makefile or in avr studio, but I'm not entirely sure what where and how.

*lick*

Robert!

P.S.: Just did a meaningless test on an AtTiny13 and with a difference of only 6 words of PGMspace (on of course a not too big project) I'm reasonably impressed with AVR GCC. I haven't tested execution speed of course, it might just beat me there.

Embedded design is as much a life choice as any other and I demand the right to legally marry my work.
-------
If it helps, I can PM you my answer in a number of different languages. Ask if you have trouble reading my high-speed-babble in English.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Just one thing, where and/or how does AVR Studio/AVR GCC create an assembler file for me to edit? I think this would be some compiler option in either the makefile or in avr studio, but I'm not entirely sure what where and how.

Under Project Configuration go to "Custom Options" and for [All Files] enter "-save-temps" then click the [Add] button.

When you next build if you look in the project/default output directory for each foo.c there will now be a foo.i (text after preprocessing) and foo.s (asm generated by C compiler).

If you want to use that as a starting point for hand optimised assembler then copy the file.s back to the project directory and VERY IMPORTANTLY change the extension from .s to .S (because "clean" has a nasty habit of deleting *.s but not *.S). Now simply add file.S to the list of source files in place of the previous file.c and the project should build identically but with the Asm being assembled by avr-as

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks Cliff!
You iz awesomenezz.

Embedded design is as much a life choice as any other and I demand the right to legally marry my work.
-------
If it helps, I can PM you my answer in a number of different languages. Ask if you have trouble reading my high-speed-babble in English.