Simple sketch won't work; interrupt vectors missing?

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

I'm trying to get a Linux-based development environment for Atmega328p to work. I'm using Linux Mint 12 with avr-gcc (version 4.5.3), avr-libc (version 1.7.1) and avrdude (version 5.10).

I'm using an usbtiny for my programmer. I have previously used a similar setup (using different software versions) based on Windows.

However, no matter what I do, I can't seem to get the program I build working. I've pared it down to a simple tutorial program on the web, and it still doesn't work right. When using "avr-objdump --disassemble" the ELF doesn't actually include interrupt vectors -- I'd expect the first 40 bytes or so to be an array of "jmp" instructions for the interrupt vectors, but that's not the case.
In fact, the disassembled program starts with some support function from the library at address 0, which should be the reset vector according to my reading of the data sheet. (Although the rjmp in there seems to actually chain to _main, so maybe that's OK)

Is this the/a problem? How do I fix it?

Here's how I compile the program:

avr-gcc -Wall -Os -mcall-prologues -mmcu=atmega328p -c avr/avrtest/main.cpp -o bld/avrobj/avrtest/main.o -MMD -I/usr/lib/avr/include

Here's how I upload the program:

sudo avrdude -e -u -V -p m328p -c 115200 -B 10 -c usbtiny -U lfuse:w:0xEF:m -U hfuse:w:0xDF:m -U efuse:w:0xFD:m -U lock:w:0xFF:m

And, finally, here's the actual program:

#define F_CPU 16000000UL
#include 
#include 

void delayms(uint16_t millis) {
  while ( millis ) {
    _delay_ms(1);
    millis--;
  }
}

int main(void) {
  DDRB |= (1<<PB5); /* set PB0 to output */
  while(1) {
    PORTB &= ~(1<<PB5); /* LED on */
    delayms(100);
    PORTB |= (1<<PB5); /* LED off */
    delayms(900);
  }
  return 0;
}

Here's the disassembled output:

avr-objdump --disassemble bld/avrbin/avrtest

bld/avrbin/avrtest:     file format elf32-avr


Disassembly of section .text:

00000000 <_Z7delaymsj>:
   0:	07 c0       	rjmp	.+14     	; 0x10 <__zero_reg__+0xf>
   2:	ef e9       	ldi	r30, 0x9F	; 159
   4:	ff e0       	ldi	r31, 0x0F	; 15
   6:	31 97       	sbiw	r30, 0x01	; 1
   8:	f1 f7       	brne	.-4      	; 0x6 <__zero_reg__+0x5>
   a:	00 c0       	rjmp	.+0      	; 0xc <__zero_reg__+0xb>
   c:	00 00       	nop
   e:	01 97       	sbiw	r24, 0x01	; 1
  10:	00 97       	sbiw	r24, 0x00	; 0
  12:	b9 f7       	brne	.-18     	; 0x2 <__zero_reg__+0x1>
  14:	08 95       	ret

00000016 
: 16: 25 9a sbi 0x04, 5 ; 4 18: 2d 9a sbi 0x05, 5 ; 5 1a: 84 e8 ldi r24, 0x84 ; 132 1c: 93 e0 ldi r25, 0x03 ; 3 1e: 0e 94 00 00 call 0 ; 0x0 <_Z7delaymsj> 22: 2d 98 cbi 0x05, 5 ; 5 24: 84 e6 ldi r24, 0x64 ; 100 26: 90 e0 ldi r25, 0x00 ; 0 28: 0e 94 00 00 call 0 ; 0x0 <_Z7delaymsj> 2c: f5 cf rjmp .-22 ; 0x18

Btw: the hardware I'm testing with is an Arduino Uno board, but the end system will be home-grown boards (which I've also had work for me before using a Windows-based setup -- but I don't think the host OS is the problem here.)

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

??? obovou you write the object to bld/avrobj/avrtest/main.o and below you disassemble the directory bld/avrobj/avrtest?

-c does "compile and assemble only". Where is your link command? And the "upload" command lone does not mention an uploaded file at all.

avrfreaks does not support Opera. Profile inactive.

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

Two command lines were missing from my question, my apologies:

Linking:

avr-gcc -o bld/avrbin/avrtest bld/avrobj/avrtest/main.o -mmcu=atmega328p
avr-objcopy -R .eeprom -O ihex --set-section-flags=.eeprom=alloc,load --no-change-warnings --change-section-lma .eeprom=0 bld/avrbin/avrtest bld/avrbin/avrtest.hex
avr-objcopy -R .eeprom -O ihex bld/avrbin/avrtest bld/avrbin/avrtest.hex

(Note that the first objcopy line was cribbed from the Arduino IDE in desperation, because it does two objcopies to the same file when building; I am under the belief that I should only need the second objcopy line when building this sample)

Uploading:

sudo avrdude -p m328p -b 115200 -B 10 -c usbtiny -U eeprom:w:bld/avrbin/avrtest.hex:i

The actual question comes from wondering how I can generate the appropriate interrupt vector at the start of the image?

What, if anything, is the required prologue for a "real" program? (apparently the chip sets up the stack pointer for me, which is nice, but what else does the C library require?)

For example: If I use ISR(whatever) { code }, how does that translate to object files, and then the linked image?

I can find all the flip-one-bit examples on the web I want, and none of them help answer that question.

I can read the Atmega datasheet from front to back (and have done so,) but it (understandably) only talks about the chip/registers, not about the build environment.

What I really need is documentation for the "integration layer" between the compiler/linker, and the microcontroller.

How do I address particular code to particular sections? How do I locate particular code at particular addresses? (ISRs, boot loader, ...) Are there any examples of this anywhere?

I have googled around a fair bit, and this "intermediate" level of documentation seems to be quite elusive.

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

Actually, the upload part is now working -- I had copied the upload command from one particular reference, but it's using "eeprom" where it should use "flash." Changing it to upload to the "flash" section means writing the program memory, and the program uploads correctly.

However, that doesn't answer the question of: where is the "intermediate" documentation to be found?

Or is it the case that it's "magicly" done for me?
For example, if I define an ISR(), that originates the function at a particular address, then for any ISR I haven't defined, the linker will attempt to put any of my code in the blank space? (My expectation is that there's actually an interrupt vector with default empty interrupts somewhere, but that's clearly wrong)
If so, is there a handy-dandy source of empty interrupt handler declarations for a particular chip (such as the 328p) that I can get from somewhere?

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

It looks to me that the code disassembly you presented is the unlinked copy of main.o. IOW "avrtest" appears to have not linked with the C run time (CRT).

You can save yourself a LOT of hassle by not attempting to type all these tortuous compiler/linker/objcopy invocations but simply put the right rules in a Makefile or better yet take a pre-existing Makefile for AVR and adpat that. The ultimate form of this is a utility called "Mfile" which is a Tk/Tcl utility that will read in the makefile_template that comes with it, you answer some simple questions from a menu in the GUI interface and it then outputs a complete Makefile to be used for your project. You then simply "make" and everything happens automatically.

As for ISRs. When you succeed in linking to crtm328p.o successfully it will provide code that sets the stack, clears R1, clears SREG, does the _do_copy_data and _do_clear_bss if necessary (these are linked from a different .a in fact). It also links in a fully populated vector table. As you can see from the source:

http://svn.savannah.nongnu.org/v...

it gives each vector a name __vector_N and makes a weak link to __bad_interrupt so that by default all the entries make a JMP to that routine (in case any gets used and now ISR() has been provided). When you use something like:

ISR(USART_RXC_vect) {
}

This is really just "eye candy" for something like:

void __vector_11 (void) __attribute__ ((signal,used, externally_visible)) ; void __vector_11 (void) {
}

So the human friendly name has mapped to __vector_11 and this means that the JMP at that location in the vector table that was weakly made to __bad_interrupt will now be strongly linked to this routine you provide and therefore used in instead. So the vector entry does a "JMP __vector_11" and this code then provides that symbol. Amongst the attributes there the "signal" one is the thing that turns this from a "normal" function that ends RET to an interrupt handler that ends RETI instead.

There is nothing "hidden" about any of this. The CRT is all visible as source at the location I linked above (that is the HEAD of SVN so may be slightly ahead of what you are using) and the entire CRT is pretty much that whole gcrt1.S file, nothing else. Coming at ISR() from the other direction one can easily read the contents of avr/include/avr/interrupt.h to see how that is done. Or do what I did and write a 5 line test program, compile it with --save-temps then look at the filename.i that is produced.

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

Thanks for the response. I'm already using a make file, with fancy dependency decuction and implicit build rules. However, I don't want to have the reader wade through my make rules when a simple copy-and-paste of the make command output is a lot clearer.

Thanks for the reference to the source code. That helps, although it's not the same thing as documentation, because documentation answers "why" whereas source code answers "what." In that vein, I still have a few questions, opened by your response (isn't that always the case? :-)

Quote:
It looks to me that the code disassembly you presented is the unlinked copy of main.o

That may be the case, but what I posted was the output of "avr-gcc -o mumble -mmcu=mumble mumble.o" just like shown in the above code (for apropriate symbolic substitutions of 'mumble.') I imagine that, because I don't use any symbol from the standard C library, the compiler is smart enough to not include it, in the interest of code compactness, perhaps?

Quote:
When you succeed in linking to crtm328p.o successfully

How, exactly, am I supposed to do this? Is there a "-l" that will do it for me? Do I find it in an archive somewhere and "ar x" it to find it, and specify it manually? I'm happy to help myself by reading documentation, but reading source code is not a particularly efficient way of figuring out the "how" and the "why," which is what I'm really after.

Finally: [url]http://svn.savannah.nongnu.org/v...

\

That file (written in assembly, not C, btw) sets up the malloc() heap, which is something I'm not interested in including. If I "manage to link" the standard C library, will it waste my 2 kB of RAM on internal malloc()-ed structures, even if I don't use obviously bad functions like strtok() or strdup()?

Again, I appreciate your answer, but if there's any kind of documentation answering "why" and "how" questions, rather than "what" questions, that would probably be what I'm really looking for.

Quote:
read avr/include/avr/interrupt.h

In fact, I did that. However, that didn't answer why only the ISR I declared was put in place by the linker, and all my other code was slathered all over the rest of the interrupt vectors. Even your answer doesn't actually answer that question -- I compiled and linked as expressed in most references for GCC for AVR I can find online (as well as how I usually compile and link with GCC,) but that didn't do what I (or you) expected it to do. Why?

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

Quote:

How, exactly, am I supposed to do this? Is there a "-l" that will do it for me?

It happens automatically when yuou invoke avr-gcc without -c.
Quote:

That file (written in assembly, not C, btw) sets up the malloc() heap, which is something I'm not interested in including.

It was the diagram and positioning of .bss, .data and the stack that I thought you'd take from it.
Quote:

Again, I appreciate your answer, but if there's any kind of documentation answering "why" and "how" questions, rather than "what" questions, that would probably be what I'm really looking for.

For almost all aspects of AVR-LibC there is an article here in the tutorial forum (many written by abcminiuser (aka Dean Camera)).

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

Can you include (or post somewhere) an actual log ("script" command or equiv") of the whole build and disassembly?

This line in particular is a sign that you're somehow looking at un-linked binary rather than post-link 'executable' code:

    a:   00 c0          rjmp   .+0         ; 0xc <__zero_reg__+0xb> 

(If the code was linked, the actual value of of the jump target address would be in the jump, rather than a zero. (When I compile the same code (different version of gcc, different OS, etc), I get the same ".+0" target in the .o file, but the executable contains by comparison:

;;; .o file
00000000 <_Z7delaymsj>:
   0:   20 ea           ldi     r18, 0xA0       ; 160
   2:   3f e0           ldi     r19, 0x0F       ; 15
   4:   00 c0           rjmp    .+0             ; 0x6 

;;;executable
000000a6 <_Z7delaymsj>:
  a6:   20 ea           ldi     r18, 0xA0       ; 160
  a8:   3f e0           ldi     r19, 0x0F       ; 15
  aa:   04 c0           rjmp    .+8             ; 0xb4 

Note that (as far as I can tell) .o files for avr objects are the same elf format as the final executable; they just contain additional information about which locations contain symbols that have to be resolved and perhaps relocated.

Quote:
if there's any kind of documentation answering "why" and "how" questions, rather than "what" questions, that would probably be what I'm really looking for.

Here, you'll need to dig into some of the gcc documentation in all it's compiler-guru-obscurity and massively incomprehensible non-device-dependence, because some of what you're asking about is not at all specific to the avr.
When you create an executable program, the compiler includes on your behalf several bits of code in addition to the code that you have written. Basically, there is the standard library "libc", the internal library "gcclib", and the startup code that initializes things to the state that main() expects ( "crt*") In the case of avr-gcc, it is this last part that normally contains the interrupt vectors, code to initialize the stack pointer, code to copy initialized data from flash to RAM, code to make sure r1 contains zero, and etc. gcc is pretty good about not including code that isn't actually relvant to the program. There are ways to omit this code entirely (-nostdlib -nostartfiles), but you usually have to go out of your way to do so.
Quote:
why only the ISR I declared was put in place by the linker, and all my other code was slathered all over the rest of the interrupt vectors.

It didn't. A defined ISR function compiles to a normal function in the .o files, plus "instructions" to the linker ("weak symbols") to put the address of that function in the interrupt vector table that comes from crtxx.o The disassembly you posted just had all your functions, located sequentially...

I'm not familiar with "Linux Mint", but I suppose that it is possible that the avr-gcc package for it has been built improperly. Some CPUs (ARM being the obvious example) can be built to work in several different binary environments (linux, bare metal, raw), and perhaps someone built the Mint avr-gcc to produce "raw" binaries (no startup code), rather than the normal executables (AFAIK, avr-gcc is not supposed to HAVE alternate binary formats, but a gcc build is a mysterious thing!)

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

Huh. OK, virtual machines make this relatively easy, and I was curious. I installed a Linux MINT 12 system and loaded up the gcc-avr and avr-libc packages, and it behaves just the way you (OP) describe - link produces an output file that contains none of the "startup" code (includes no interrupt vector table, either.)

Conclusion: the package is built wrong.

SprinterSB: is there a way to get gcc to tell us how it was compiled, as a way of maybe determining what's wrong with it?

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

I guess I was looking for the output you get when you give the compiler "-v" for verbose output. I don't see anything terribly wrong with this:

billw@bill-mint ~ $ avr-gcc -v -o test main.o
Using built-in specs.
COLLECT_GCC=avr-gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/avr/4.5.3/lto-wrapper
Target: avr
Configured with: ../src/configure -v --enable-languages=c,c++ --prefix=/usr/lib --infodir=/usr/share/info --mandir=/usr/share/man --bindir=/usr/bin --libexecdir=/usr/lib --libdir=/usr/lib --enable-shared --with-system-zlib --enable-long-long --enable-nls --without-included-gettext --disable-checking --disable-libssp --build=i686-linux-gnu --host=i686-linux-gnu --target=avr
Thread model: single
gcc version 4.5.3 (GCC) 
COMPILER_PATH=/usr/lib/gcc/avr/4.5.3/:/usr/lib/gcc/avr/4.5.3/:/usr/lib/gcc/avr/:/usr/lib/gcc/avr/4.5.3/:/usr/lib/gcc/avr/:/usr/lib/gcc/avr/4.5.3/../../../avr/bin/
LIBRARY_PATH=/usr/lib/gcc/avr/4.5.3/:/usr/lib/gcc/avr/4.5.3/../../../avr/lib/
COLLECT_GCC_OPTIONS='-v' '-o' 'test'
 /usr/lib/gcc/avr/4.5.3/collect2 -o test -L/usr/lib/gcc/avr/4.5.3 -L/usr/lib/gcc/avr/4.5.3/../../../avr/lib main.o -lgcc -lc -lgcc

billw@bill-mint ~ $ find /usr/lib/gcc/avr/4.5.3/../../../avr/lib/ -name \*328\*
/usr/lib/gcc/avr/4.5.3/../../../avr/lib/avr5/crtm328.o
/usr/lib/gcc/avr/4.5.3/../../../avr/lib/avr5/crtm328p.o

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

I'd say avr-gcc is misconfigured - it should pass a "-m avrSOMETHING" to the linker (or collect2, a fancy "linker caller"), together with the crtSOMETHING.o to link with.

Try

avr-gcc test.c -mmcu=x

It should list the mcu names avr-gcc knows of.

JW

Last Edited: Tue. May 8, 2012 - 07:47 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

jwatte wrote:
I'm already using a make file, with fancy dependency decuction and implicit build rules. However, I don't want to have the reader wade through my make rules when a simple copy-and-paste of the make command output is a lot clearer.
Thanks for that. The command emit by a makefile are always more enlighning than cryptic and/or incomplete makefile snippets (except in the case there is an issue with the makefile itself).

Quote:
Quote:
It looks to me that the code disassembly you presented is the unlinked copy of main.o
That may be the case, but what I posted was the output of "avr-gcc -o mumble -mmcu=mumble mumble.o" just like shown in the above code (for apropriate symbolic substitutions of 'mumble.') I imagine that, because I don't use any symbol from the standard C library, the compiler is smart enough to not include it, in the interest of code compactness, perhaps?
Than depends on what you use for "mumble" if it's a mumbling device, the compiler driver (avr-gcc is just a driver that adds/removes options as it calls the sub-tools) will add respective options and files to the linker invocation, amonst them what crt.o (C Run Time initialization, i.e. startup code) to link and what library flavour to use.

This is not the case of you have mumbling architecture like avr25 or avrxmega4.

A common error is that the link process is not started through the compiler driver avr-gcc but by a direct linker invokation of avr-ld. If you call avr-ld by hand, you will have to add all command options by hand that otherwise would have been added by the driver: Startup code, architecture, RAM start, etc.

The C essentials are not supposed to be removed: initializing RAM, calling constructors, linking vector table, call of main, setting up core registers, ...

To see if the process is as it should be, add a -v -Wl,Map,mumble.map to the driver's link command to get verbose output how the linker is called and have a mapfile (ascii) produced that tells about what modules are dragged in from where and what objects are located where.

Quote:
When you succeed in linking to crtm328p.o successfully
How, exactly, am I supposed to do this?
See above explanation. To do a correct link and to track if a particular link runs as expected.

That file (written in assembly, not C, btw) sets up the malloc() heap, which is something I'm not interested in including.[/code]the crt does not "set up a heap"; it ust defines a symbol, see also __heap* definitions in the linker script. These can be used in a program. If you do not use malloc et al. not a single byte will be used for them.

Quote:
If I "manage to link" the standard C library, will it waste my 2 kB of RAM on internal malloc()-ed structures, even if I don't use obviously bad functions like strtok() or strdup()?
No. This is the case for AVR-LibC which you are using obviously.

Quote:
Again, I appreciate your answer, but if there's any kind of documentation answering "why" and "how" questions, rather than "what" questions, that would probably be what I'm really looking for.
avr-gcc ist just a flavour of GCC and avr-as, avr-ld, ave-objcopy, avr-size, avr-nm are just flavours of gas, ld, objcopy, size, nm, etc. That is:

The anatomy of a build process with help of the GNU tools is very much the same whatever platform you are on and whatever target architecture you are using. Becasue this is so, not all of the usage of GNU tools is repeated again and again, and if you read the avr-tools documentation it's likely you miss many things if you restrict to the avr part only.

Quote:
Quote:
read avr/include/avr/interrupt.h
In fact, I did that. However, that didn't answer why only the ISR I declared was put in place by the linker, and all my other code was slathered all over the rest of the interrupt vectors.
This should not be like that. It gives raise to the assumption that something fundamentally is going wrong like bad installation, misleading path names, etc.

avrfreaks does not support Opera. Profile inactive.

Last Edited: Tue. May 8, 2012 - 06:25 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hmm. I can now get proper elf executables (with interrupt vectors) if I specify an explicit "-mmcu=atmega328p" on the link command, but not if I have it only on the compile command. In an older version (4.3.2) on a different OS (Mac), the linker seems to default to "-m avr2" and includes SOME crtxxx even with no -mmcu command, so it at least SEEMS to work.

(Try adding "-v -Wl,-v" to your compile and link commands. verbose logging output.)

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

The -mmcu= flag should be used on every invocation of avr-gcc, whether you're using it to compile a single module, or whether you're using it to link the entire application.