-1

In short, a global const variable (array) defined in a .cpp file disappeared from the .o file, causing "undefined reference ... " error. The problem can be imperfectly solved by add 'volatile' to the definition of variable. It's useless to turn off compiler optimization or '-flto'.

Compiler: AVR-GCC 11.10 from https://github.com/ZakKemble/avr-gcc-build/releases.

Enviroment: PlatformIO.

OS: Windows 11 x64.

The definition is like:

// file: oled_basic_init_sequence.cpp

#include <Arduino.h>

namespace  oled_basic {

    const uint8_t _init_sequence_for_ssd1316_4_wire_spi[25] PROGMEM = {
        0xae, /*display off*/
        0x00, /*set lower column address*/
        // ...
    };


    const uint8_t _init_sequence_for_ssd1306_2_wire_i2c[23] PROGMEM = {
        0xAE,
        0xD5,
        // ...
    };

}  // namespace oled_basic

With or without PROGMEM doesn't matter. In .h file:

// file: oled_basic_init_sequence.h

#pragma once


#include <stdint.h>

namespace oled_basic {

    extern const uint8_t _init_sequence_for_ssd1316_4_wire_spi[25];

    extern const uint8_t _init_sequence_for_ssd1306_2_wire_i2c[23];

}  // namespace oled_basic

In main.cpp file:


#include <Arduino.h>


#include "oled_basic_init_sequence.h"


volatile uint8_t * test = 0;


void setup() {
    *test = oled_basic::_init_sequence_for_ssd1316_4_wire_spi[0];
    
    // this doesn't help
    *test = pgm_read_byte(&(oled_basic::_init_sequence_for_ssd1316_4_wire_spi[0]));
}

void loop() {

}


compilation failed with:

Building in release mode
Compiling .pio\build\ATmega128\src\oled_basic_init_sequence.cpp.o
Linking .pio\build\ATmega128\firmware.elf
c:/users/xxxxx/.platformio/packages/toolchain-atmelavr/bin/../lib/gcc/avr/11.1.0/../../../../avr/bin/ld.exe: C:\Users\xxxxx\AppData\Local\Temp\ccelvOYl.ltrans0.ltrans.o: in function `main':
<artificial>:(.text.startup+0x5a): undefined reference to `_ZN10oled_basic37_init_sequence_for_ssd1316_4_wire_spiE'
collect2.exe: error: ld returned 1 exit status
*** [.pio\build\ATmega128\firmware.elf] Error 1
========================================================= [FAILED] Took 3.42 seconds =========================================================

Verbose build info:


avr-g++ -o .pio\build\ATmega128\firmware.elf -mmcu=atmega128 -Os -Wl,--gc-sections -flto -fuse-linker-plugin .pio\build\ATmega128\src\main.cpp.o .pio\build\ATmega128\src\oled_basic_font.cpp.o .pio\build\ATmega128\src\oled_basic_init_sequence.cpp.o -L.pio\build\ATmega128 -Wl,--start-group .pio\build\ATmega128\lib81f\libi2c.a .pio\build\ATmega128\lib685\libSPI.a .pio\build\ATmega128\libe29\libWire.a .pio\build\ATmega128\libFrameworkArduinoVariant.a .pio\build\ATmega128\libFrameworkArduino.a -lm -Wl,--end-group

Maybe I just made some low level syntax mistakes and still can't realize.

ke_bitter
  • 9
  • 1
  • Don't put the definition in the header file. Declare it in the header file, define it in one `.cpp` file. – Ted Lyngmo Aug 20 '23 at 05:55
  • In fact, the problem can also be solved by just put the array to header file. – ke_bitter Aug 20 '23 at 06:03
  • That doesn't sound like a good solution, but please provide an answer to your question. It's ok so answer your own question since it may help other people with similar issues in the future. – Ted Lyngmo Aug 20 '23 at 06:16
  • 1
    `uint8_t *ttest = 0` and `*test = ` - why are you assigning to NULL? – KamilCuk Aug 20 '23 at 08:14
  • @KamilCuk It's just "test". `test` is decorated with `volatile` so that compiler should generate code referencing that array. – ke_bitter Aug 20 '23 at 08:57
  • 1
    it should be `test =` not `*test =` – Juraj Aug 20 '23 at 09:05
  • `test` is a pointer to the memory-address 0 and you are trying to assign the value 0xAE to that address - not a good idea. – ABaumstumpf Aug 20 '23 at 10:04

1 Answers1

2

Possible fix

Include oled_basic_init_sequence.h from oled_basic_init_sequence.cpp.

Explanation

namespace  oled_basic {
    const uint8_t _init_sequence_for_ssd1316_4_wire_spi[25] = {
        0xae, /*display off*/
        0x00, /*set lower column address*/
        // ...
    };
}

Without the forward declaration of the variable with the extern keyword, this code defines _init_sequence_for_ssd1316_4_wire_spi with internal linkage: it can't be used in other translation units.

With the forward declaration, the variable will be defined with external linkage.

namespace  oled_basic {
    extern const uint8_t _init_sequence_for_ssd1316_4_wire_spi[25];

    const uint8_t _init_sequence_for_ssd1316_4_wire_spi[25] = {
        0xae, /*display off*/
        0x00, /*set lower column address*/
        // ...
    };
}

We can see on compiler explorer that the compiler emits a global symbol for the variable in the second snippet, but not with the first snippet:

.global oled_basic::_init_sequence_for_ssd1316_4_wire_spi
        .section        .rodata
        .type   oled_basic::_init_sequence_for_ssd1316_4_wire_spi, @object
        .size   oled_basic::_init_sequence_for_ssd1316_4_wire_spi, 25
oled_basic::_init_sequence_for_ssd1316_4_wire_spi:
        .string "\256"
        .string ""
        .zero   22
  • wow, it works, thanks. I always remember that symbols are "public" by default. Maybe I should read some more basics, some time. – ke_bitter Aug 20 '23 at 13:22