ataradov / mcu-starter-projects

Simple starter projects for bare-metal MCU development
307 stars 41 forks source link

g++ (C++) support #5

Open pedro-javierf opened 3 years ago

pedro-javierf commented 3 years ago

Hi,

I've been using the projects, and they work very well. I'm specifically using /tree/master/samd21 , and I was wondering what would be necessary to have C++ support, using for example the arm-none-eabi-g++ compiler.

When changing to that compiler I get the following errors in the vector table definitions:

makefile

##############################################################################
BUILD = build
BIN = Demo

##############################################################################
.PHONY: all directory clean size

CC = arm-none-eabi-g++
OBJCOPY = arm-none-eabi-objcopy
SIZE = arm-none-eabi-size
...

errors:

$ make
CC build/main.o
cc1plus: warning: command line option '-std=gnu99' is valid for C/ObjC but not for C++
../main.c: In function 'int main()':
../main.c:142:13: warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
CC build/startup_samd21.o
cc1plus: warning: command line option '-std=gnu99' is valid for C/ObjC but not for C++
../startup_samd21.c:37:12: error: 'void irq_handler_nmi()' aliased to undefined symbol 'irq_handler_dummy'
../startup_samd21.c:70:12: error: 'void irq_handler_i2s()' aliased to undefined symbol 'irq_handler_dummy'
../startup_samd21.c:69:12: error: 'void irq_handler_ptc()' aliased to undefined symbol 'irq_handler_dummy'
../startup_samd21.c:68:12: error: 'void irq_handler_dac()' aliased to undefined symbol 'irq_handler_dummy'
../startup_samd21.c:67:12: error: 'void irq_handler_ac()' aliased to undefined symbol 'irq_handler_dummy'
../startup_samd21.c:66:12: error: 'void irq_handler_adc()' aliased to undefined symbol 'irq_handler_dummy'
../startup_samd21.c:65:12: error: 'void irq_handler_tc7()' aliased to undefined symbol 'irq_handler_dummy'
../startup_samd21.c:64:12: error: 'void irq_handler_tc6()' aliased to undefined symbol 'irq_handler_dummy'
../startup_samd21.c:63:12: error: 'void irq_handler_tc5()' aliased to undefined symbol 'irq_handler_dummy'
../startup_samd21.c:62:12: error: 'void irq_handler_tc4()' aliased to undefined symbol 'irq_handler_dummy'
../startup_samd21.c:61:12: error: 'void irq_handler_tc3()' aliased to undefined symbol 'irq_handler_dummy'
../startup_samd21.c:60:12: error: 'void irq_handler_tcc2()' aliased to undefined symbol 'irq_handler_dummy'
../startup_samd21.c:59:12: error: 'void irq_handler_tcc1()' aliased to undefined symbol 'irq_handler_dummy'
...

Thank you

ataradov commented 3 years ago

The minimal changes necessary to make it compile are:

Change "CFLAGS += -W -Wall --std=gnu++11 -Os" And add "CFLAGS += -fno-exceptions"

Then wrap entire code of the startup file in extern "C" { .... }

This would compile, but will not support exceptions. If you want exceptions, then you need to remove the corresponding CFLAG and modify the linker script to incide exidx definitions. You can look at more complete linker script examples to see how it is done there.

pedro-javierf commented 3 years ago

The minimal changes necessary to make it compile are:

Change "CFLAGS += -W -Wall --std=gnu++11 -Os" And add "CFLAGS += -fno-exceptions"

Then wrap entire code of the startup file in extern "C" { .... }

This would compile, but will not support exceptions. If you want exceptions, then you need to remove the corresponding CFLAG and modify the linker script to incide exidx definitions. You can look at more complete linker script examples to see how it is done there.

Thanks. That worked.

Should the

%.o:
    @echo CC $@
    @$(CC) $(CFLAGS) $(filter %/$(subst .o,.c,$(notdir $@)), $(SRCS)) -c -o $@

rule be modified to work with .cpp file extension as well?

ataradov commented 3 years ago

That depends on your file extensions. If you use cpp, then yes.

pedro-javierf commented 3 years ago

I've changed the makefile accordingly (gnu++11, no exceptions, etc) and adjusted the code for my specific samd21 and board.

EDIT: I've just added extern "C" to the functions in main.cpp except main (that is, sys_init(), uart_puts(), uart_init(), timer_init(), etc.). I understand that the C++ compiler was mangling their names, and surely main() was not being able to find them and thus the program was not working. Thus the extern "C" directive fixes it. But this confuses me a little bit, will I need to add extern "C" to every single function and file with code I create? Why is main(), or rather the linker, not being able to find the mangled names?

The original blink works fine in C once I set up the correct port and pin number. However as soon as I make the changes to support C++, even though the compilation completes, the code is broken and does not run on the board. It only yields this warning:

$ make
CC build/main.o
../main.cpp: In function 'int main()':
../main.cpp:142:13: warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
CC build/startup_samd21.o
LD build/Demo.elf
OBJCOPY build/Demo.hex
OBJCOPY build/Demo.bin
size:
   text    data     bss     dec     hex filename
    720       0       0     720     2d0 build/Demo.elf
    720       0       0     720     2d0 (TOTALS)

Flashing is correct but the program doesn't work. I should start looking to the vector table not being set up properly. This is my startup_samd21.cpp file:

/*
 * Copyright (c) 2015, Alex Taradov <alex@taradov.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

//-----------------------------------------------------------------------------
#include "samd21.h"

extern "C" {
//-----------------------------------------------------------------------------
#define DUMMY __attribute__ ((weak, alias ("irq_handler_dummy")))

//-----------------------------------------------------------------------------
void irq_handler_reset(void);
DUMMY void irq_handler_nmi(void);
DUMMY void irq_handler_hard_fault(void);
DUMMY void irq_handler_sv_call(void);
DUMMY void irq_handler_pend_sv(void);
DUMMY void irq_handler_sys_tick(void);

DUMMY void irq_handler_pm(void);
DUMMY void irq_handler_sysctrl(void);
DUMMY void irq_handler_wdt(void);
DUMMY void irq_handler_rtc(void);
DUMMY void irq_handler_eic(void);
DUMMY void irq_handler_nvmctrl(void);
DUMMY void irq_handler_dmac(void);
DUMMY void irq_handler_usb(void);
DUMMY void irq_handler_evsys(void);
DUMMY void irq_handler_sercom0(void);
DUMMY void irq_handler_sercom1(void);
DUMMY void irq_handler_sercom2(void);
DUMMY void irq_handler_sercom3(void);
DUMMY void irq_handler_sercom4(void);
DUMMY void irq_handler_sercom5(void);
DUMMY void irq_handler_tcc0(void);
DUMMY void irq_handler_tcc1(void);
DUMMY void irq_handler_tcc2(void);
DUMMY void irq_handler_tc3(void);
DUMMY void irq_handler_tc4(void);
DUMMY void irq_handler_tc5(void);
DUMMY void irq_handler_tc6(void);
DUMMY void irq_handler_tc7(void);
DUMMY void irq_handler_adc(void);
DUMMY void irq_handler_ac(void);
DUMMY void irq_handler_dac(void);
DUMMY void irq_handler_ptc(void);
DUMMY void irq_handler_i2s(void);

extern int main(void);

extern void _stack_top(void);
extern unsigned int _etext;
extern unsigned int _data;
extern unsigned int _edata;
extern unsigned int _bss;
extern unsigned int _ebss;

//-----------------------------------------------------------------------------
__attribute__ ((used, section(".vectors")))
void (* const vectors[])(void) =
{
  &_stack_top,                   // 0 - Initial Stack Pointer Value

  // Cortex-M0+ handlers
  irq_handler_reset,             // 1 - Reset
  irq_handler_nmi,               // 2 - NMI
  irq_handler_hard_fault,        // 3 - Hard Fault
  0,                             // 4 - Reserved
  0,                             // 5 - Reserved
  0,                             // 6 - Reserved
  0,                             // 7 - Reserved
  0,                             // 8 - Reserved
  0,                             // 9 - Reserved
  0,                             // 10 - Reserved
  irq_handler_sv_call,           // 11 - SVCall
  0,                             // 12 - Reserved
  0,                             // 13 - Reserved
  irq_handler_pend_sv,           // 14 - PendSV
  irq_handler_sys_tick,          // 15 - SysTick

  // Peripheral handlers
  irq_handler_pm,                // 0 - Power Manager
  irq_handler_sysctrl,           // 1 - System Controller
  irq_handler_wdt,               // 2 - Watchdog Timer
  irq_handler_rtc,               // 3 - Real Time Counter
  irq_handler_eic,               // 4 - External Interrupt Controller
  irq_handler_nvmctrl,           // 5 - Non-Volatile Memory Controller
  irq_handler_dmac,              // 6 - Direct Memory Access Controller
  irq_handler_usb,               // 7 - Universal Serial Bus Controller
  irq_handler_evsys,             // 8 - Event System
  irq_handler_sercom0,           // 9 - Serial Communication Interface 0
  irq_handler_sercom1,           // 10 - Serial Communication Interface 1
  irq_handler_sercom2,           // 11 - Serial Communication Interface 2
  irq_handler_sercom3,           // 12 - Serial Communication Interface 3
  irq_handler_sercom4,           // 13 - Serial Communication Interface 4
  irq_handler_sercom5,           // 14 - Serial Communication Interface 5
  irq_handler_tcc0,              // 15 - Timer/Counter for Control 0
  irq_handler_tcc1,              // 16 - Timer/Counter for Control 1
  irq_handler_tcc2,              // 17 - Timer/Counter for Control 2
  irq_handler_tc3,               // 18 - Timer/Counter 3
  irq_handler_tc4,               // 19 - Timer/Counter 4
  irq_handler_tc5,               // 20 - Timer/Counter 5
  irq_handler_tc6,               // 21 - Timer/Counter 6
  irq_handler_tc7,               // 22 - Timer/Counter 7
  irq_handler_adc,               // 23 - Analog-to-Digital Converter
  irq_handler_ac,                // 24 - Analog Comparator
  irq_handler_dac,               // 25 - Digital-to-Analog Converter
  irq_handler_ptc,               // 26 - Peripheral Touch Controller
  irq_handler_i2s,               // 27 - Inter-IC Sound Interface
};

//-----------------------------------------------------------------------------
void irq_handler_reset(void)
{
  unsigned int *src, *dst;

  src = &_etext;
  dst = &_data;
  while (dst < &_edata)
    *dst++ = *src++;

  dst = &_bss;
  while (dst < &_ebss)
    *dst++ = 0;

  SCB->VTOR = (uint32_t)vectors;

  main();

  while (1);
}

//-----------------------------------------------------------------------------
void irq_handler_dummy(void)
{
  while (1);
}

//-----------------------------------------------------------------------------
void _exit(int status)
{
  (void)status;
  while (1);
}

}
ataradov commented 3 years ago

The issue here is that irq_handler_tc1 is not recognized as a strong substitute for a weak alias.

So it looks like your only solution is to define actual dummy implementations for all the handlers, and then you can remove extern stuff.

ataradov commented 3 years ago

The minimal fix is to add extern "C" around irq_handler_tc1() alone.

pedro-javierf commented 3 years ago

The minimal fix is to add extern "C" around irq_handler_tc1() alone.

That seems to work as a minimal fix and everything compiles and runs fine. I assume that wrapping will be required for all future interrupt handlers that I implement as well, but for now, there's no need to define dummy implementations for all the handlers as the compiler doesn't complain because it finds irq_handler_dummy() on the startup.c file right?

ataradov commented 3 years ago

Compiler does not complain because weak aliases are not implemented in C++ mode. So when an interrupt handler like this is compiled in C++ mode, it is just removed as an unused function.

If I were doing this for a real project, I would make a dummy version of each handler and remove the weak alias attributes. This would let you compile everything in C++ mode and will generate errors when you redefine the existing dummy handler. So you won't have to spend as much time debugging when you inevitably forget extern around one of the handlers.