How do I specify the address of a variable in GCC???

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

Hello all,
I am working on a project where there are some very tight timing parameters. In one portion of the code, I would like to declare an array of unsigned chars, and specify the address at which they are located. This is needed because later in the code, I access this array from an assembly routine using the Z index registers to point to the array. I'd like the array to be located where the ZLow byte can be set to 0x00, and thus directly loading a value into ZLow will provide the proper offset into the array (NOTE: I'm actually reading a 4-bit value off of PORTB directly into the ZLow register, which should provide the index...this saves a few critical instruction cycles, and this is why I need the array to be placed at, say, 0x0100, or 0x0200, etc).

Any way I can do this? The only two ways I could think of were:
1) Manually declare my variables up until 0x0100, and then declare my arrays (but this is prone to tons of potential problems)

or

2) Tell gcc to start the .data section at a "better" location, like 0x0100, for this application, and place my arrays as the first thing in memory. However, since the .data is supposed to be static, I would be breaking the rules by accessing static data from another file (I guess I could figure out how much static data I had, subtract this from 0x0100, which would then place my .bss section at 0x0100, but this is dangerous for the same reasons as 1). It would also be potentially wasteful of precious RAM...

Any other ideas out there?

Thanks in advance for the help!

John O.

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

Solution #2 doesn't sound all that bad. Just declare the storage for
the array in an assembler file, and make it go to a different section.
Create a modified linker script that changes the order (and offset) of
the data sections.

When accessing the data from C, you can still declare the array to be
extern.

While this might sound like a lot of effort at first, it'll be a very
clean solution.

Another option would be to ensure that a particular order of the
linked files will always be achieved in the Makefile, and to use the
very first of the source files to take the declaration of that array.
(This can be the standard way in a C file then.) Since the linker
will link all files in the order they are given on the command-line,
it will also allocate static storage in that order, so you can ce sure
that the very first global variable defined will always be at the
bottom of RAM (actually: at the beginning of .data). However, in
order to make it go into .data (and not into .bss), you need to
initialize the array to something that is not zero, or again, you need
to modify the linker script to have .bss in front of .data.

Also, you should certainly add a check of the resulting symbol table
to your Makefile that causes the build to fail with some error message
in case this special array does not appear at your intented address
for whatever reason.

Jörg Wunsch

Please don't send me PMs, use email if you want to approach me personally.

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

Hello,

If I can understand your message, you want to allocate an array at a fixed data memory position with a memory "page" boundary alignment, no?

The "easiest" solution I can imagine is to allocate the array in a special section (not .data, .bss, etc) defined with the exact size of your array. Then you edit your makefile to set the start position of that section. In this way you should complain with the above restrictions. The code is similar to

static volatile char foo[0x100] __attribute__ ((section (".foosec")));

Remove static if you need global access. The size can be lower than 0x100. You'll possibly need volatile access. I don't recommend you to use long section names but I can't remember why.

The problem is now to avoid overlap this section with the others in your program and coexist in peace. You can then

1) Allocate the array section at the beginning of the data memory and move upwards the start of the other sections (.data, .bss, and .noinit). This positions are device dependent and remember that most AVRs address registers, SFRs and I/O addresses, are stated at low memory positions. Maybe the first internal SRAM address is not at a "page" boundary (for example, in ATmega8515 is 0x60). You can waste the space gap to the next "page" boundary or create another section and fill it with any other static data (global or local).

2) You may instead allocate the array section at the end of the data memory and move downwards the top of the stack. Edit the makefile accordingly or (not so elegant way) change the stack pointer manually in your "main" function ensuring your code never returns from it. Once again, last SRAM memory position can be far from a "page" boundary. Same problem, same solution.

Remember you can't commit the compiler to reserve registers ZH and ZL for exclusive access in your assembler code and read the documentation (Memory sections, malloc()-dynamic memory) in avr-libc documentation to see the tipical memory map in avr-gcc.

Quote:
However, since the .data is supposed to be static, I would be breaking the rules by accessing static data from another file

You are mixing several concepts. Section .data is about static data variables allocated at compile time. The opposite is heap data, dynamically allocated data (with malloc() and similar) at execution time. This is a bit confusing in C because the reserved word "static" usually means local access vs global access, instead of static allocation vs dynamic allocation.

Bye. Carlos.

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

ISTR that defining arbitrary data (i. e. SRAM) sections without
modifying the linker script does not work. Sections unknown to the
linker script are IMHO treated as ROM sections.

Jörg Wunsch

Please don't send me PMs, use email if you want to approach me personally.

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

Thanks for the ideas....I'm going to give them a try over the next couple of days.....

Stay tuned...I'm sure I'll have more questions! :D

John O.

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

Ok...I'm back with a working solution I think...I re-read the avrlibc reference (along with the generous replies from this thread) and ended up doing the following:
-declared the array that I want aligned on a 256 byte boundary (i.e., located at 0x0100, or 0x0200, etc.) as follows:

unsigned char redLookup[16] __attribute__ ((section (".noinit")));

This puts the redLookup table in the .noinit section. The redLookup table is actually declared at the top of the first C file listed in my makefile, which should guarantee that the redLookup table will occupy the first 16 bytes of the .noinit section of RAM.

I then modified the standard Makefile that comes with WinAvr to tell it where to place the .noinit section in memory (in this case, I located it at 0x0200), using the following line in my makefile:

LDFLAGS = -Wl,-Map=$(TARGET).map,--cref,--section-start=.noinit=0x800200

I then rebuilt my code, and used avr-nm to double check where my redLookup[] array was placed, and it is in fact located at 0x00800200 (remember the addition of 0x00800000 is normal with this architecture).

So I think this works...I built it and simulated it with AvrStudio this morning (initializing the redLookup table with a small loop, and it looks like the data is being written to 0x200 as expected).

So....am I missing anything here? I hope to fully test this later, but I have to go to work now :evil: Let me know if this jives with the GCC experts in the group....thanks!

John O.

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

Hello,

Sorry, no expert. It's just me.

In my opinion, there are a few potencially dangerous aspects in your solution to consider it appropiate in a general context. Well,... it's working, well suited to your needs and then, perfect!

You are very lucky, more than 50 billion of names combining 6 alphanumeric characters and you choose ".noinit". There are 3 magical sections for storing static application data (.data, .bss, and .noninit). The magic is in the fact they run together in the memory and nobody else but the compiler has to worry about this 3 pieces puzzle. Section .nonit should be used as the ordinary place to store all globals that don't require initialization, and very specially long arrays, buffers. Even when the application start can only save a few cycles by putting data in .noinit the real problem is the insertion of a dependency with the linking order of the files because you want your array aligned at 0x200. Any other name for the section in the C code and in the makefile and you'll get identical results but without linking order dependencies and you'll still have available the section .noinit for no-need-to-initialize data. Anyway, It seems to me that any non standard data section is also unitialized.

The position 0x200 is over the standard data sections and below the stack, that is, in the middle of the heap used in dynamic memory allocation. That will make unusable the function malloc() unless you change the start position of the heap. This positioning in-the-middle is an option I wasn't considering because it's the candidate to maximize memory fragmentation, but this depends on many factors (device, data sizes,...) and in some cases could be better than the other two proposals (first and last data blocks respectively).

If you convert object code from EFF to COFF to use AVR Studio you can also change the shifting of the sections accordingly to the new memory map, even when AVR Studio uses to ignore most of this information, specially with code sections.

Bye. Carlos.

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

Hello,
I have a similar requirement. In a sample code I declared global variables (not just a character array, but includes int, float etc). I needed to clear these variables and in the sample coed I found the addresses to be contigious. So I initialised a pointer and cleared the mem! But in my actual application, the variables do not appear in the order in which they are declared. To get around the problem I declared a structure. Here the members seem to be in contigious memory. Please advice me if there are going to be any pit falls in this approach.

Will not GCC assign global variables in the order thay are declared in? I worked on Keil compiler where there is an option by which variables are assigned memory in the order they are declared in. Is there any way I can do that in GCC too?

Regards

Parthasaradhi Nayani

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

GCC usually assigns the variables the way the are declared, but of
course, there's no guarantee. In particular, remember that
initialized variables go into .data, and non-initialized ones (i. e.
those that are cleared to 0 at startup) go into .bss.

Thus,

double bar = 3.1415926;
int foo;

will not be in contiguous memory.

Jörg Wunsch

Please don't send me PMs, use email if you want to approach me personally.

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

Thanks jorg for the reply, but if I decare a struct, will the members be in sequential order?
Thank you.
regards

Parthasaradhi Nayani

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

Hello,

I think this is a standard C requirement or for example the access to fields in "union" typed structures wouldn't be deterministic. The limitation in several platforms (not in the AVR) is the data packing of the fields. Some microprocessors & microcontrollers with data 16-bit data buses or wider read word size data faster if it's aligned in word or double word boundary addresses. This forces the introduction of gaps of unused bytes between variables and even between fields inside of structures to align everything (variables and fields). Most compilers present an option to force the compact byte alignment (normally at the cost of speed). In gcc you can add __attribute__ ((packed)). Anyway, the AVR data bus is just 8 bits wide. Then all data, including structures, is byte aligned by default.

Also remember that AVR data coding is little endian.

Regards. Carlos.

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

Hi Carlos,
Thanks for the update. I am accessing the members in the structure by their name. So there I do not see any problem even if the processor is a 16 bit processor and data is stored in 16 bit boundaries. Essentially I wanted to clear all members of the structure. It is for this purpose that I wanted to know if the members are going to be in sequence. Second reason is I have provided external RAM to my Mega128 and this memory is battery backed. Once the data in the structure is filled (by using the correct 'C' notation) I want to transfer this structure contents to Mem. by using a pointer. Hence I needed the structure memebers to be in sequence. As of now the sequence seems to be OK. But in future if I add/delete other variables the members of the structure must remain in order. I did not want surprises! So I needed confirmation.

Do I assume that the sequence remains unchanged?

Thank you.

Parthasaradhi Nayani

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

Hello,

I can't understand why do you need to know the exact placement of fields in the structure to duplicate it in some other place in the memory.

If your external memory is an ordinary parallel address/data bus SRAM you can directly place your structure using static data access creating a section as explained in this thread and creating the variables in that section. The section is not initialized in the reset and you can store there any information. If the data address is a priori undetermined you may use a pointer to the structure and standard C field access with the operator "->". Another option is using library block memory copy functions with the address of the source and target structures and the length given by the operator sizeof().

If your memory is serial access (I2C, SPI,...) or any other non-standard access you can implement read and write functions for byte, word, (double word,) and block access (similar to EEPROM library funcs). Block transfer functions again will solve easily your problem.

Appart from the mentioned problems with data aligment and byte ordering, C structures are very predictable in most of the compilers. Structures always occupy a contiguous block of memory and the filling bytes for aligned when needed are unused (never interlaced with any other data).

On the other way, in C++ the structures are derived into classes and the compilers can also store instance data into them. Yet still persists the classic C simple structure by declaring the data with attribute extern "C".

Regards. Carlos.