13
\$\begingroup\$

I am using avr-gcc and avrdude to write programs onto an ATMega8515 micro-controller. I have got the blinking LED program working without a hitch. However, when I started to use interrupts and introduce functions other than main() to the code, I came across bugs. Though I could compile the program and write it onto the chip, it did not run as expected - the LEDs that should have been blinking were completely unresponsive. With tweaking I found out that the introduction of any function, not just an interrupt service routine, caused this issue.

I came across near identical issues here:

https://stackoverflow.com/questions/65427371/why-does-my-isr-declaration-break-my-program

"Now, none of the LEDs blink. Even weirder, none of them blink when I remove the sei(). The only way I've found to make the first LED blink again is to comment out the ISR declaration"

And here:

AVR ATmega32 - C - Gets stuck in function call

"it's just the same statements as before, moved to the pulse_init() function. When I upload this code the timer is set up correctly and the output pulse is OK, but the program never starts blinking the LEDs on PC0..1."

To be clear, these are the commands I am using to compile and write the code to the ATMega:

avr-gcc main.c -O2 -o main.elf
avr-objcopy main.elf -O ihex main.hex
avrdude -c usbasp -p m8515 -U flash:w:"main.hex":a

And this is a sample code snippet that does not work:

#ifndef F_CPU
#define F_CPU 1000000UL // 16 MHz clock speed
#endif

#define __AVR_ATmega8515__
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

volatile uint8_t toggle_blinking = 1;


int main()
{
    sei(); /* set global interrupt enable */
    MCUCR = 0b00000100; /* Set the INT1 to trigger on any logic change*/
    GICR = 0b10000000;
    DDRC = 0xFF; // Makes PORTC as output
    DDRD=0xe0;

    
    while(1)
    {
        if(toggle_blinking)
        {
            PORTC = 0xFF; //Turns ON All LEDs
            // _delay_ms(500); //1 second delay
            // _delay_ms(500); //1 second delay

        }
        else
        {
            PORTC= 0x00; //Turns OFF All LEDs
        }
    }
}

ISR(INT1_vect)
{
    toggle_blinking ^= 1;
}

The common denominator between all of these programs (mine, and the two linked above) is the use of the avr-gcc optimization flag (-O) which is there to ensure that the util/delay.h functions run correctly. I compiled a program which did not work with the optimization flag again, this time without the optimization flag. And sure enough, the program worked.

To clarify, I am not sure whether other function definitions are "breaking" the program, or if they are just being run instead of main(). Here is the object dump when running running avr-objdump -d main.elf on the .elf file that was generated by compiling the above program with optimization enabled:

    00000000 <__ctors_end>:
    0:   10 e0           ldi     r17, 0x00       ; 0
    2:   a0 e6           ldi     r26, 0x60       ; 96
    4:   b0 e0           ldi     r27, 0x00       ; 0
    6:   ea e6           ldi     r30, 0x6A       ; 106
    8:   f0 e0           ldi     r31, 0x00       ; 0
    a:   03 c0           rjmp    .+6             ; 0x12 <__zero_reg__+0x11>
    c:   c8 95           lpm
    e:   31 96           adiw    r30, 0x01       ; 1
    10:   0d 92           st      X+, r0
    12:   a2 36           cpi     r26, 0x62       ; 98
    14:   b1 07           cpc     r27, r17
    16:   d1 f7           brne    .-12            ; 0xc <__zero_reg__+0xb>

      00000018 <__vector_2>:
    18:   1f 92           push    r1
    1a:   0f 92           push    r0
    1c:   0f b6           in      r0, 0x3f        ; 63
    1e:   0f 92           push    r0
    20:   11 24           eor     r1, r1
    22:   8f 93           push    r24
    24:   9f 93           push    r25
    26:   80 91 60 00     lds     r24, 0x0060     ; 0x800060 <__data_start>
    2a:   91 e0           ldi     r25, 0x01       ; 1
    2c:   89 27           eor     r24, r25
    2e:   80 93 60 00     sts     0x0060, r24     ; 0x800060 <__data_start>
    32:   9f 91           pop     r25
    34:   8f 91           pop     r24
    36:   0f 90           pop     r0
    38:   0f be           out     0x3f, r0        ; 63
    3a:   0f 90           pop     r0
    3c:   1f 90           pop     r1
    3e:   18 95           reti

      00000040 <main>:
    40:   78 94           sei
    42:   84 e0           ldi     r24, 0x04       ; 4
    44:   85 bf           out     0x35, r24       ; 53
    46:   80 e8           ldi     r24, 0x80       ; 128
    48:   8b bf           out     0x3b, r24       ; 59
    4a:   8f ef           ldi     r24, 0xFF       ; 255
    4c:   84 bb           out     0x14, r24       ; 20
    4e:   80 ee           ldi     r24, 0xE0       ; 224
    50:   81 bb           out     0x11, r24       ; 17
    52:   9f ef           ldi     r25, 0xFF       ; 255
    54:   80 91 60 00     lds     r24, 0x0060     ; 0x800060 <__data_start>
    58:   88 23           and     r24, r24
    5a:   29 f0           breq    .+10            ; 0x66 <main+0x26>
    5c:   95 bb           out     0x15, r25       ; 21
    5e:   80 91 60 00     lds     r24, 0x0060     ; 0x800060 <__data_start>
    62:   81 11           cpse    r24, r1
    64:   fb cf           rjmp    .-10            ; 0x5c <main+0x1c>
    66:   15 ba           out     0x15, r1        ; 21
    68:   f5 cf           rjmp    .-22            ; 0x54 <main+0x14>

And here is the dump from the compiled program without optimization enabled:

  00000000 <__ctors_end>:
    0:   10 e0           ldi     r17, 0x00       ; 0
    2:   a0 e6           ldi     r26, 0x60       ; 96
    4:   b0 e0           ldi     r27, 0x00       ; 0
    6:   ea ea           ldi     r30, 0xAA       ; 170
    8:   f0 e0           ldi     r31, 0x00       ; 0
    a:   03 c0           rjmp    .+6             ; 0x12 <__zero_reg__+0x11>
    c:   c8 95           lpm
    e:   31 96           adiw    r30, 0x01       ; 1
    10:   0d 92           st      X+, r0
    12:   a2 36           cpi     r26, 0x62       ; 98
    14:   b1 07           cpc     r27, r17
    16:   d1 f7           brne    .-12            ; 0xc <__zero_reg__+0xb>

  00000018 <main>:
    18:   cf 93           push    r28
    1a:   df 93           push    r29
    1c:   cd b7           in      r28, 0x3d       ; 61
    1e:   de b7           in      r29, 0x3e       ; 62
    20:   78 94           sei
    22:   85 e5           ldi     r24, 0x55       ; 85
    24:   90 e0           ldi     r25, 0x00       ; 0
    26:   24 e0           ldi     r18, 0x04       ; 4
    28:   e8 2f           mov     r30, r24
    2a:   f9 2f           mov     r31, r25
    2c:   20 83           st      Z, r18
    2e:   8b e5           ldi     r24, 0x5B       ; 91
    30:   90 e0           ldi     r25, 0x00       ; 0
    32:   20 e8           ldi     r18, 0x80       ; 128
    34:   e8 2f           mov     r30, r24
    36:   f9 2f           mov     r31, r25
    38:   20 83           st      Z, r18
    3a:   84 e3           ldi     r24, 0x34       ; 52
    3c:   90 e0           ldi     r25, 0x00       ; 0
    3e:   2f ef           ldi     r18, 0xFF       ; 255
    40:   e8 2f           mov     r30, r24
    42:   f9 2f           mov     r31, r25
    44:   20 83           st      Z, r18
    46:   81 e3           ldi     r24, 0x31       ; 49
    48:   90 e0           ldi     r25, 0x00       ; 0
    4a:   20 ee           ldi     r18, 0xE0       ; 224
    4c:   e8 2f           mov     r30, r24
    4e:   f9 2f           mov     r31, r25
    50:   20 83           st      Z, r18
    52:   80 91 60 00     lds     r24, 0x0060     ; 0x800060 <__data_start>
    56:   88 23           and     r24, r24
    58:   39 f0           breq    .+14            ; 0x68 <__SREG__+0x29>
    5a:   85 e3           ldi     r24, 0x35       ; 53
    5c:   90 e0           ldi     r25, 0x00       ; 0
    5e:   2f ef           ldi     r18, 0xFF       ; 255
    60:   e8 2f           mov     r30, r24
    62:   f9 2f           mov     r31, r25
    64:   20 83           st      Z, r18
    66:   f5 cf           rjmp    .-22            ; 0x52 <__SREG__+0x13>
    68:   85 e3           ldi     r24, 0x35       ; 53
    6a:   90 e0           ldi     r25, 0x00       ; 0
    6c:   e8 2f           mov     r30, r24
    6e:   f9 2f           mov     r31, r25
    70:   10 82           st      Z, r1
    72:   ef cf           rjmp    .-34            ; 0x52 <__SREG__+0x13>

  00000074 <__vector_2>:
    74:   1f 92           push    r1
    76:   0f 92           push    r0
    78:   0f b6           in      r0, 0x3f        ; 63
    7a:   0f 92           push    r0
    7c:   11 24           eor     r1, r1
    7e:   8f 93           push    r24
    80:   9f 93           push    r25
    82:   cf 93           push    r28
    84:   df 93           push    r29
    86:   cd b7           in      r28, 0x3d       ; 61
    88:   de b7           in      r29, 0x3e       ; 62
    8a:   90 91 60 00     lds     r25, 0x0060     ; 0x800060 <__data_start>
    8e:   81 e0           ldi     r24, 0x01       ; 1
    90:   89 27           eor     r24, r25
    92:   80 93 60 00     sts     0x0060, r24     ; 0x800060 <__data_start>
    96:   00 00           nop
    98:   df 91           pop     r29
    9a:   cf 91           pop     r28
    9c:   9f 91           pop     r25
    9e:   8f 91           pop     r24
    a0:   0f 90           pop     r0
    a2:   0f be           out     0x3f, r0        ; 63
    a4:   0f 90           pop     r0
    a6:   1f 90           pop     r1
    a8:   18 95           reti

It seems as though the memory address location of main() is being affected by compiler optimizations which is causing the issues. Does anyone have a proper explanation for this and/or a fix that would allow me to use compiler optimizations with interrupt routines?

\$\endgroup\$
7
  • 1
    \$\begingroup\$ I think it would be easier to read the assembler output ( -S) of the compiler. \$\endgroup\$
    – greybeard
    Commented Nov 29, 2023 at 6:20
  • 1
    \$\begingroup\$ The second linked question reveals that it was a faulty chip. It does not contribute to your issue. \$\endgroup\$ Commented Nov 29, 2023 at 7:10
  • \$\begingroup\$ The first linked question seems to have errors in setting up the interrupt, and this seems to be one of your problems, too. Unfortunately I'm currently not in the situation to check this. \$\endgroup\$ Commented Nov 29, 2023 at 7:17
  • \$\begingroup\$ Compiler and linker play well together to put reset and ISR vectors at the right location, and therefore the order of the functions does not matter. However, I'm missing options to tell compiler and linker about the target in your command lines. Therefore, I assume a layer-8 error (your command line is wrong), but as I said, I cannot check this right now. \$\endgroup\$ Commented Nov 29, 2023 at 7:17
  • \$\begingroup\$ @thebusybee as a user in one of the comments pointed out, that 'solution' is contentious. I'm referring to the second linked question \$\endgroup\$
    – cornelius
    Commented Nov 29, 2023 at 7:18

3 Answers 3

24
\$\begingroup\$

These are not bugs that you are encountering. The problem goes deeper than just compiler optimization flags. It is a pure coincidence it happens to work at all.

The code isn't compiled properly according to instructions and examples of avr-libc/avr-gcc. Reading the manual will solve the issue.

The command used to compile the code will not work and generates something you can't use, at least not on a Mega8515.

You haven't told to the compiler and the linker which MCU you are using, so that the compiler could pick the right architecture, right startup code, and right libraries to use when compiling and linking code, as there are about 15-20 possible variants of things called AVR.

By default the compiler generates code for "avr2" architecture and Mega8515 is an "avr4" architecture chip.

Also the way you are defining the IC you use in the code for the io header is not the intended way of defining the MCU.

All this is explained on avr-libc website and there are also example projects and more importantly, examples how to compile them properly.

\$\endgroup\$
7
  • \$\begingroup\$ Yea I'm trying to do the setup using windows but there's a few hurdles to say the least. I'll try it with Linux \$\endgroup\$
    – cornelius
    Commented Nov 30, 2023 at 15:51
  • 2
    \$\begingroup\$ @cornelius It really does not depend on if you do it under Windows or Linux. \$\endgroup\$
    – Justme
    Commented Nov 30, 2023 at 16:29
  • 1
    \$\begingroup\$ in theory yes. But for example, the installation guide for the avr toolchain wants you to install the latest version of MinGW. That comes with gcc version 6.3.0. Then when it wants you to configure and build avr-gcc, it wants you to use LDFLAGS='-L /usr/local/lib -R /usr/local/lib' - but the -R flag doesn't exist for gcc 6.3.0... Anyway, that's for a different question \$\endgroup\$
    – cornelius
    Commented Dec 1, 2023 at 1:35
  • 1
    \$\begingroup\$ @cornelius That's about building avr-gcc yourself? I bet using it from ready made binaries does not require MinGW, or even building it yourself, so how to execute avr-gcc with correct parameters should be identical under all OSes, Windows, Linux, MacOS, BSDs etc. \$\endgroup\$
    – Justme
    Commented Dec 1, 2023 at 5:43
  • \$\begingroup\$ yes, I agree once you have the correct binaries the exceution of avr-gcc should be the same under all OSes. So case closed with regards to the original question. But in order to use the example projects on the avr-libc website like you suggested, one ought to set-up the toolchain using their guide as well, and that involves MinGW. \$\endgroup\$
    – cornelius
    Commented Dec 1, 2023 at 6:56
13
\$\begingroup\$

You know that something goes really wrong when at address 0 there is no vector table, which would be an array of RJMPs or JMPs, but there is ordinary code:

    00000000 <...>:`
    0:   10 e0           ldi     r17, 0x00       ; 0
    2:   a0 e6           ldi     r26, 0x60       ; 96

Vectab is device specific, thus something with device selection is wrong, and indeed:

Missing -mmcu=atmega8515 when Compiling and Linking

avr-gcc main.c -O2 -o main.elf

Always (like in ALWAYS) specify the MCU for which you are generating code! Both for compiling and linking. You are performing both steps by means of one command, so -mmcu= is missing in both build steps.

The outcome of missing -mmcu= will be:

  • The compiler is using the wrong command line options (as per wrong specs file). With -mmcu=atmega8515 the compiler will use specs-atmega8515 which provides options for many sub-processes like compiler proper (cc1 for C or cc1plus for C++), assembler (as) and linker (ld).

  • One outcome of missing specs-atmega8515 is that built-in macro __AVR_ATmega8515__ is not defined which is required so AVR-LibC can pick the right sub-header in #include <avr/io.h>. You hacked around that by hand-defining __AVR_ATmega8515__. Don't to that! The macro is defined in specs-atmega8515 which will be used with -mmcu=atmega8515. Without that option, the default is -mmcu=avr2, and the specs file specs-avr2 does not provide any device-specific options and defines.

  • Linking without -mmcu=atmega8515 will have the effect that the compiler driver (avr-gcc) will call the linker without providing AVR-LibC's startup-code and vector table for ATmega8515 in crtatmega8515.o. The startup-code will initialize SP, set R1 aka. __zero_reg__ to zero, etc. When your code is missing all these relevant bits, it will malfunction.

  • Linking without -mmcu=atmega8515 will have the effect that the compiler driver (avr-gcc) will not link against libatmega8515.a. Functions like EEPROM routines eeprom_read_byte from avr/eeprom.h are missing.

  • Wrong or sub-optimal code: Not all AVR devices share the same instruction set. For example, ATmega8515 supports the MUL instruction, but you are compiling for device family avr2 (the default) which doest support MUL. Similar for MOVW instruction.

To get an overview what commands avr-gcc is issuing, add -v to the command line options and compare the output with[out] -mmcu=atmega8515.

\$\endgroup\$
2
  • \$\begingroup\$ There is no problem with sei() being the first instruction of a sensible program. All interrupts will be properly initialized to disabled at CPU reset before it executes any code. \$\endgroup\$
    – Justme
    Commented Nov 29, 2023 at 17:54
  • \$\begingroup\$ @Justme: Ok, I'll remove that part. \$\endgroup\$ Commented Nov 29, 2023 at 18:28
11
\$\begingroup\$

Thank you for providing the dump output! You've captured the smoking gun:

See ATmega8515 Datasheet | Microchip page 54:

enter image description here

The first instruction must be rjmp .init (or equivalent label), and the instruction at 0x04 must be rjmp __vector_2 (and so on; usually, unused vectors are rjmp __reset or something like that). Instead, it has completely stomped over the whole interrupt vector region and just placed regular code there.

When you change the pin state, you've essentially done a program reset, minus the two instructions at the top that is. Which, in these cases, r17 and r26 are not used after initialization, so it will indeed act "normal" (not that "normal" does anything from reset, heh), rather than corrupt itself or crash. Cute!

Normally, vectors are allocated with an object containing a list of jump targets; it's forced to base address 0, and tagged used, meaning the linker will not omit it from the output, even if no reference is made to it by other objects, and even if -lto is used. Other objects (vectors, init, main(), etc.) are then placed after it.

These are missing because the compiler needs to know what exact MCU core is being used. This will also obviate the #define __AVR_ATmega8515__ declaration, which is generated automatically by the specs file.

When -mmcu=atmega8515 is added to the command line, GCC will read from a "specs" file, which provides a variety of parameters: which libraries to use, important addresses if different from linked symbols, compile target (CPU), etc. Defines, and other command-line options, can also be included. For example, on my path, %GCC_root%\lib\gcc\avr\8.1.0\device-specs\specs-atmega8515 contains these lines towards the end:

*cpp:
    -D__AVR_ATmega8515__ -D__AVR_DEVICE_NAME__=atmega8515

the define is handled automatically by these command line options.


Alternative smoking gun: you should never have to touch double-underscore defines in program code. io.h reads such a platform define, and presumably you patched that in to make it work; but this won't affect the linker, or anything else in the compiler back-end. This is kind of your clue that -mmcu is missing; though it can be hard to tell what exact invocation you happen to be missing.

The recommended start, here, I suppose, is to look at an existing project, or build tool, and see how they do it. For my part, I use Code::Blocks, which provides built-in scripts / invocations for avr-gcc, which I've mutated slightly to suit my own purposes. For a more shell-oriented environment, you'd probably want to look at makefiles.

\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.