Author Topic: Setting up C++ on a microcontroller  (Read 6482 times)

0 Members and 4 Guests are viewing this topic.

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18911
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Setting up C++ on a microcontroller
« on: February 10, 2026, 05:07:11 pm »
So after the woes of MPLABX I have now setup to run on my own with VSCode the ARM GCC compiler and make.

So this all starts with a warning I am now getting: '*this.io_board_1_class::node_id' is used uninitialized [-Wuninitialized]

node_id is simply an U32 value being passed to the object on instantiation. All other variables passed also have the same warning.

So I found this: https://stackoverflow.com/questions/52737706/static-c-members-in-a-micocontroller

Sounds like my problem but no one replied and he seems to have fixed it by making the startup file a C++ file (at least it's extension). Should I also be so bold?

This gets me to thinking, if the g++ compiler handles C as well do I need any C flags etyc in my make file?

From somewhere my make file ended up with this:

Code: [Select]

CC = arm-none-eabi-gcc
CXX = arm-none-eabi-g++
OBJCOPY = arm-none-eabi-objcopy
SIZE = arm-none-eabi-size
MKDIR = mkdir -p

CFLAGS += -W -Wall --std=gnu11 -Os -g
CFLAGS += -fno-diagnostics-show-caret
CFLAGS += -fdata-sections -ffunction-sections
CFLAGS += -funsigned-char -funsigned-bitfields
CFLAGS += -mcpu=cortex-m0plus -mthumb
CFLAGS += -MD -MP -MT $(BUILD)/$(*F).o -MF $(BUILD)/$(@F).d

CXXFLAGS += -W -Wall --std=gnu++17 -Os -g
CXXFLAGS += -fno-diagnostics-show-caret
CXXFLAGS += -fdata-sections -ffunction-sections
CXXFLAGS += -funsigned-char -funsigned-bitfields
CXXFLAGS += -mcpu=cortex-m0plus -mthumb
CXXFLAGS += -fno-exceptions -fno-rtti
CXXFLAGS += -MD -MP -MT $(BUILD)/$(*F).o -MF $(BUILD)/$(@F).d

LDFLAGS += --specs=nano.specs --specs=nosys.specs
LDFLAGS += -mcpu=cortex-m0plus -mthumb
LDFLAGS += -Wl,--gc-sections
LDFLAGS += -Wl,-wrap=_malloc_r -Wl,-wrap,_free_r
LDFLAGS += -Wl,--script=linker_script.ld
LDFLAGS += -Wl,-Map=$(BUILD)/$(BIN).map,--cref


I'm essentially calling both compilers, should I? or should I ditch the C stuff and just have it treated as C++?
 

Online negativ3

  • Frequent Contributor
  • **
  • Posts: 620
  • Country: th
Re: Setting up C++ on a microcontroller
« Reply #1 on: February 10, 2026, 05:18:07 pm »
Are you initializing node_id in the member initializer list, not the constructor body?
Is the warning about the parameter or the member variable itself?
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 11333
  • Country: fi
Re: Setting up C++ on a microcontroller
« Reply #2 on: February 10, 2026, 06:41:12 pm »
Show the offending code. Most likely you are really using it uninitialized, i.e. the code is broken and nothing wrong with the compiler flags.
 
The following users thanked this post: nctnico

Offline ejeffrey

  • Super Contributor
  • ***
  • Posts: 4852
  • Country: us
Re: Setting up C++ on a microcontroller
« Reply #3 on: February 10, 2026, 08:47:41 pm »
The person on stack exchange has a different problem than you have.

Your problem, because you have a compiler warning, almost certainly means that you are actually using an uninitialized variable according to the C++ standard.

Their problem in the stack exchange post is that their code is correct (and had no warnings) but had the incorrect runtime library configuration.  Their runtime startup code (i.e., before main() was not properly running initializer methods.  C++ requires special hooks in the startup code to run non-trivial constructors for any global or static objects, which C doesn't support.  g++ emits calls to the constructors into a special object file section for constructors, and then startup code runs them.  Compiling the startup.cc with g++ caused the compiler to emit the correct calls.

Show the code and it will be easy to find.
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18911
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: Setting up C++ on a microcontroller
« Reply #4 on: February 10, 2026, 09:32:30 pm »
I am statically instantiating class objects at the start with an initializer. Node_id is a variable in the class.

do initializers not work for statically instantiated objects?

I'll post code tomorrow.

 

Offline cv007

  • Super Contributor
  • ***
  • Posts: 1061
Re: Setting up C++ on a microcontroller
« Reply #5 on: February 10, 2026, 09:34:35 pm »
Its not hard to produce this error-

https://godbolt.org/z/K8ehnKacj

nor is it hard to find when c++ points to the lines in question.

edit- the above example probably not similar, but does show the errors/warning are usually helpful. As long as you troubleshoot the error output from top to bottom (most of which may disappear after the initial fix).


*this.io_board_1_class::node_id' is used uninitialized [-Wuninitialized]

The use of *this may also be a clue. Seems like an odd use. The online compiler is a good place to test code- create simple examples in the online compiler where code is reduced to the problem at hand. Its a good place to figure out the language.

I would also add -Werror to your flags. There is no reason to allow code to compile with warnings. Either the code is 'good' as far as the compiler is concerned, or it needs to be fixed. Whether the problem is minor or of no consequence doesn't matter- fix it right away or you risk accumulating warnings that you have let slide.
« Last Edit: February 10, 2026, 11:14:24 pm by cv007 »
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 6464
  • Country: nz
Re: Setting up C++ on a microcontroller
« Reply #6 on: February 10, 2026, 11:42:02 pm »
C++ on a microcontroller should Just Work, if you don't go out of your way to make things hard for yourself.

After all, all those millions of non-technical people using Arduinos are using C++ on a microcontroller.

    void setup() {
      Serial.begin(9600);  // C++, not C
    }
   
    void loop() {
      Serial.println("Hello World!"); // C++, not C
      delay(1000);
    }
 

Offline az1

  • Regular Contributor
  • *
  • Posts: 61
  • Country: us
Re: Setting up C++ on a microcontroller
« Reply #7 on: February 11, 2026, 12:42:59 am »
It's a lot easier to help if you can post your project (both source and build stuff).  If you're getting compile time warnings you have problems before you need to worry about whether you're using the C or C++ compiler to do the linking.  If you don't want to post your project then sharing some sort of minimum viable example would be extremely helpful.

From what you've chosen for your compiler flags I think (but it's been ages since I've had to mess with gcc) CFLAGS and CXXFLAGS should be identical (or CXXFLAGS should otherwise be a superset of CFLAGS) except for the --std argument as that's specifying a C or C++ standard.  Your CXXFLAGS are not instructing g++ to generate thumb instructions so that might cause problems?  There's a lot of bravado around writing your own stuff with handwritten makefiles and linker scripts composed with the blood of GUI users while walking uphill both ways in the snow, but there's also some utility in just using a higher level abstraction like cmake.
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 6464
  • Country: nz
Re: Setting up C++ on a microcontroller
« Reply #8 on: February 11, 2026, 12:48:11 am »
I'm essentially calling both compilers, should I? or should I ditch the C stuff and just have it treated as C++?

You're not CALLING *anything* there.

You're just setting up what happens IF a C or C++ file is compiled. If there aren't any of one or the other or both then it's harmless.
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 6464
  • Country: nz
Re: Setting up C++ on a microcontroller
« Reply #9 on: February 11, 2026, 12:49:03 am »
From what you've chosen for your compiler flags I think (but it's been ages since I've had to mess with gcc) CFLAGS and CXXFLAGS should be identical (or CXXFLAGS should otherwise be a superset of CFLAGS) except for the --std argument as that's specifying a C or C++ standard.  Your CXXFLAGS are not instructing g++ to generate thumb instructions so that might cause problems?

You perhaps didn't scroll down to see the rest of the CXX flags?
 

Offline cv007

  • Super Contributor
  • ***
  • Posts: 1061
Re: Setting up C++ on a microcontroller
« Reply #10 on: February 11, 2026, 03:55:00 am »
Quote
C++ on a microcontroller should Just Work, if you don't go out of your way to make things hard for yourself.
No different than C. The C++ compiler is more opinionated about your code, so by the time you satisfy the C++ compiler its possible you may have something better just from the extra complaints you were getting.

Quote
After all, all those millions of non-technical people using Arduinos are using C++ on a microcontroller.
The non-technical are typically working with the upper layer api, so I guess the definition of 'using C++' may have different meanings- the non-technical vs the one who has to write an i2c driver.

The only way Arduino works as well as is does, is because of C++. You can also end up at the same place in your own code, where you are both the low level developer and the high level user. The language allows for a very small distance between low level and high level- you can get as low as any C user and a few lines later be at a very high level. Not to say the C user cannot do the same, but I think you end up with a nicer high level and its easier to get there.

Quote
There's a lot of bravado around writing your own stuff with handwritten makefiles and linker scripts composed with the blood of GUI users while walking uphill both ways in the snow, but there's also some utility in just using a higher level abstraction like cmake.
The linker script and makefile are not complex or large, and once created there is very little that needs changing and can be copied from project to project with minimal changes. Since you took a little time to understand these things, you will also understand how to get what you want- for example a gui is typically not going to let you specify where you want the stack, and if for some reason you would like it to crash into a bad address (causing an exception) rather than having your variables clobbered its not a problem if you understand the linker script. Some of the makefile syntax is not much fun, but once created any changes are usually only in the easier syntax, such as compiler options.

You can create C++ code in the online compiler, without any other help-
https://godbolt.org/z/ePWMWev1Y

The startup file is not shown (not complex), and the makefile is not used in the online compiler (and the one uploaded to the project probably doesn't match the project), but all the code is otherwise done in the compiler with no manufacturer header available. This is generally not a great way to go about a project, but I use the online compiler to test ideas and when I bounce around from pc to pc I can grab a link to the code and work on it anywhere. The project can be downloaded as a zip file, extracted to a project folder on the pc where the mcu has its home, and then uploaded/tested on the mcu. You can also upload a zip file so can sync back up from a local project.

For testing C++ ideas, the x64-86 compiler can also be used in Compiler Explorer so you can use printf and view output (printf debugging).
 

Offline ejeffrey

  • Super Contributor
  • ***
  • Posts: 4852
  • Country: us
Re: Setting up C++ on a microcontroller
« Reply #11 on: February 11, 2026, 04:30:20 am »
ere's a lot of bravado around writing your own stuff with handwritten makefiles and linker scripts composed with the blood of GUI users while walking uphill both ways in the snow, but there's also some utility in just using a higher level abstraction like cmake.

I don't have a problem with cmake or other build systems.  I just have a problem with IDEs that use 10 megabytes of opaque XML "project" files that randomly change and reformat every time you click your mouse and that don't show what compiler commands it's actually running or let you change them as you need. 

Case in point, the reason we are here is that the IDE Simon was using before was injecting a flag into the compiler command line to force it to treat everything as a C source instead of C++.  Why? No idea, but whatever reason was obviously bad and wrong.  Was there a way to fix it? In the gui? Probably but who knows?
 
The following users thanked this post: pardo-bsso

Offline az1

  • Regular Contributor
  • *
  • Posts: 61
  • Country: us
Re: Setting up C++ on a microcontroller
« Reply #12 on: February 11, 2026, 05:06:58 am »
ere's a lot of bravado around writing your own stuff with handwritten makefiles and linker scripts composed with the blood of GUI users while walking uphill both ways in the snow, but there's also some utility in just using a higher level abstraction like cmake.

I don't have a problem with cmake or other build systems.  I just have a problem with IDEs that use 10 megabytes of opaque XML "project" files that randomly change and reformat every time you click your mouse and that don't show what compiler commands it's actually running or let you change them as you need. 

Case in point, the reason we are here is that the IDE Simon was using before was injecting a flag into the compiler command line to force it to treat everything as a C source instead of C++.  Why? No idea, but whatever reason was obviously bad and wrong.  Was there a way to fix it? In the gui? Probably but who knows?

Right, which is why I said cmake and not something higher level with more moving parts.  The likelihood that someone's got a cmake module written for Simon's MCU is non-zero and that would solve any potential compiler configuration issues.  Sure, you can do similar things with whatever brand of make but it's more error prone.  And sure there are other command line alternatives to cmake, I just picked the one I assumed would be most entrenched.

That said as Simon's getting a compile-time warning I think the issue lies in his code not compiler configuration.
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 6464
  • Country: nz
Re: Setting up C++ on a microcontroller
« Reply #13 on: February 11, 2026, 05:28:18 am »
Quote
After all, all those millions of non-technical people using Arduinos are using C++ on a microcontroller.
The non-technical are typically working with the upper layer api, so I guess the definition of 'using C++' may have different meanings- the non-technical vs the one who has to write an i2c driver.

The potential issues with C++ have virtually nothing to do with what the user writes in their own code. Even the simplest "Hello world" is going to need the linker to do non-C things to e.g. get constructors for static objects in libraries initialised correctly.
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18911
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: Setting up C++ on a microcontroller
« Reply #14 on: February 11, 2026, 07:46:18 am »
Case in point, the reason we are here is that the IDE Simon was using before was injecting a flag into the compiler command line to force it to treat everything as a C source instead of C++.  Why? No idea, but whatever reason was obviously bad and wrong.  Was there a way to fix it? In the gui? Probably but who knows?

You must have left the party early ;). it got worse after that, I then found I never had enough RAM for the program. I was also trying to change the stack size but it always changed by twice the amount I changed it and 2 linker scripts were being put into the project as well as being hoofed in from the background.

The make file for the project was empty as it called in other make files from the, "toolchain", so clearly I would never have any control over anything and decided to jettison the "IDE" and do it the "hard way".
 

Offline cv007

  • Super Contributor
  • ***
  • Posts: 1061
Re: Setting up C++ on a microcontroller
« Reply #15 on: February 11, 2026, 07:54:09 am »
Quote
The potential issues with C++ have virtually nothing to do with what the user writes in their own code
Unless the user is writing C++? The non-technical persons using an api like Arduino, with their own created code (not touching the api) mostly consisting of C (int led = 2;), just means the api layer has already been thoroughly debugged and they will most likely not see errors related to C++ other than misuse of the api. If they are creating their own C++ code, then they are not immune to errors unique to C++.

Quote
going to need the linker to do non-C things to e.g. get constructors for static objects in libraries initialised correctly.
The linker has no say in whether you call __libc_init_array() or not, and will not care either way. For C++ of course it would be a major problem if not called in startup code (after data/bss init). Linker does nothing extraordinary for C++.
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18911
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: Setting up C++ on a microcontroller
« Reply #16 on: February 11, 2026, 08:41:28 am »
This is main:

Code: [Select]
/*
 * User Input with 8 buttons and 2 encoders.
 *
 * Created: 03/02/2026 10:00:00
 * Author : Simon
 */

#include "nano_lib.h"

#include "sam.h"

#include "project_settings.hpp"

#include "clock.hpp"

#include "rtc.hpp"

#include "class_gpio.hpp"

#include "class_user_encoder.hpp"

#include "can_open_class.hpp"

#include "can_regs.hpp"

#include "ioboard1_canopen_profile.hpp"

uint32_t last_btn_read_time = 0;

gpioCls btn[10];

encoder_class encoder1(PA10, PA11, 30, pull_up );
encoder_class encoder2(PA12, PA13, 30, pull_up );

can_open_class bus(CAN0);

io_board_1_class io( SERVER, &bus, 4UL, &encoder1, &encoder2 );


int main(void)
{
    clock_setup();
    rtc_initialize();

    /* SOFTWARE SYSTEM SETUP */
   
        encoder1.initialize( );
        encoder2.initialize( );

        btn[0].initializeInput( PB00, pullEnable, active_low );
        btn[1].initializeInput( PB01, pullEnable, active_low );
        btn[2].initializeInput( PB02, pullEnable, active_low );
        btn[3].initializeInput( PB03, pullEnable, active_low );
        btn[4].initializeInput( PB08, pullEnable, active_low );
        btn[5].initializeInput( PB09, pullEnable, active_low );
        btn[6].initializeInput( PB04, pullEnable, active_low );
        btn[7].initializeInput( PB05, pullEnable, active_low );
        btn[8].initializeInput( PB07, pullEnable, active_low );
        btn[9].initializeInput( PB06, pullEnable, active_low );

        encoder1.enable( );
        encoder2.enable( );

        bus.initialize( );

    while (1)
    {
        io.encoder_config_update( ); // checks for new encoder commands and updates accordingly
                if (( ms_time( ) - last_btn_read_time ) >= 2) {

                    last_btn_read_time = ms_time( );

                    io.short_press = 0;
                    io.long_press = 0;

                    for (uint32_t i = 0; i < 10; i++) {
                        btn[i].buttonCycle( );
                        io.short_press |= btn[i].short_press( ) << i;
                        io.long_press |= btn[i].long_press( ) << i;
                    }

                    if (( io.short_press != 0 ) || ( io.long_press != 0 )) {
                        bus.transmit_pdo( io.node_id, tpdo2 );
                    }

                }
         

        bus.heartBeatsTransmit( 500 );
    }
}


void RTC_Handler(void)
{
    RTC_REGS->MODE0.RTC_INTFLAG = 0xFFFF; // clear all status bits
}

void CAN0_Handler(void)
{
    bus.interrupt_routine( );
}

void EIC_Handler( ) {
     io.interrupt_routine( );   
}


The warnings are all around the instantiation of io_board_1_class io( SERVER, &bus, 4UL, &encoder1, &encoder2 );

The constructor is:

Code: [Select]

io_board_1_class::io_board_1_class(device_type deviceType, can_open_class *new_bus, uint32_t new_node_n,
                                   encoder_class * encoder0ptr, encoder_class * encoder1ptr)
{

    bus->addNode(deviceType, node_id, object);

    encoder[0] = encoder0ptr;
    encoder[1] = encoder1ptr;
    bus = new_bus;
    node_id = new_node_n;
    node_status.od_size = sizeof(object) / sizeof(od_type);
    node_status.NMT_state = HB_OPERATIONAL;
    node_status.heartbeat_time_stamp = 0;

    // RPDO 1
    node_status.pdo[0].cob_id.bit.not_valid = 0;
    node_status.pdo[0].mapped_objects_n = 2; // set to the actual number of mapped objects but check the slave data sheet to ensure max is not exceeded
    node_status.pdo[0].map[0] = ENCODER_0_HIGH_LIMIT;
    node_status.pdo[0].map[1] = ENCODER_0_LOW_LIMIT;
    node_status.pdo[0].map[2] = 0;
    node_status.pdo[0].map[3] = 0;
    node_status.pdo[0].highest_sub_index_supported = 5;
    node_status.pdo[0].transmission_type = 1;
    node_status.pdo[0].start_sync = 1;
    node_status.pdo[0].cob_id.bit.cob_id = 0x200 + node_id;

    // RPDO 2
    node_status.pdo[1].cob_id.bit.not_valid = 0;
    node_status.pdo[1].mapped_objects_n = 1;       // set to the actual number of mapped objects but check the slave data sheet to ensure max is not exceeded
    node_status.pdo[1].map[0] = ENCODER_0_COMMAND; //
    node_status.pdo[1].map[1] = 0;                 //
    node_status.pdo[1].map[2] = 0;                 //
    node_status.pdo[1].map[3] = 0;                 //
    node_status.pdo[1].highest_sub_index_supported = 5;
    node_status.pdo[1].transmission_type = 1;
    node_status.pdo[1].start_sync = 1;
    node_status.pdo[1].cob_id.bit.cob_id = 0x300 + node_id;

    // RPDO 3
    node_status.pdo[2].cob_id.bit.not_valid = 0;
    node_status.pdo[2].mapped_objects_n = 2;          // set to the actual number of mapped objects but check the slave data sheet to ensure max is not exceeded
    node_status.pdo[2].map[0] = ENCODER_1_HIGH_LIMIT; //
    node_status.pdo[2].map[1] = ENCODER_1_LOW_LIMIT;  //
    node_status.pdo[2].map[2] = 0;                    //
    node_status.pdo[2].map[3] = 0;
    node_status.pdo[2].highest_sub_index_supported = 5;
    node_status.pdo[2].transmission_type = 1;
    node_status.pdo[2].start_sync = 1;
    node_status.pdo[2].cob_id.bit.cob_id = 0x400 + node_id;

    // RPDO 4
    node_status.pdo[3].cob_id.bit.not_valid = 0;
    node_status.pdo[3].mapped_objects_n = 1;       // set to the actual number of mapped objects but check the slave data sheet to ensure max is not exceeded
    node_status.pdo[3].map[0] = ENCODER_1_COMMAND; //
    node_status.pdo[3].map[1] = 0;                 //
    node_status.pdo[3].map[2] = 0;                 //
    node_status.pdo[3].map[3] = 0;
    node_status.pdo[3].highest_sub_index_supported = 5;
    node_status.pdo[3].transmission_type = 1;
    node_status.pdo[3].start_sync = 1;
    node_status.pdo[3].cob_id.bit.cob_id = 0x500 + node_id;

    // TPDO 1
    node_status.pdo[4].cob_id.bit.not_valid = 0;
    node_status.pdo[4].mapped_objects_n = 2;     // set to the actual number of mapped objects but check the slave data sheet to ensure max is not exceeded
    node_status.pdo[4].map[0] = ENCODER_0_COUNT; // number of dictionary object
    node_status.pdo[4].map[1] = ENCODER_1_COUNT; // number of dictionary object
    node_status.pdo[4].map[2] = 0;
    node_status.pdo[4].map[3] = 0;
    node_status.pdo[4].highest_sub_index_supported = 5;
    node_status.pdo[4].transmission_type = 1;
    node_status.pdo[4].start_sync = 1;
    node_status.pdo[4].cob_id.bit.cob_id = 0x180 + node_id;

    // TPDO 2
    node_status.pdo[5].cob_id.bit.not_valid = 0;
    node_status.pdo[5].mapped_objects_n = 4;       // set to the actual number of mapped objects but check the slave data sheet to ensure max is not exceeded
    node_status.pdo[5].map[0] = SHORT_PRESS_STATE; // number of dictionary object
    node_status.pdo[5].map[1] = LONG_PRESS_STATE;  // number of dictionary object
    node_status.pdo[5].map[2] = 0;                 // number of dictionary object
    node_status.pdo[5].map[3] = 0;                 // number of dictionary object
    node_status.pdo[5].highest_sub_index_supported = 5;
    node_status.pdo[5].transmission_type = 1;
    node_status.pdo[5].start_sync = 1;
    node_status.pdo[5].cob_id.bit.cob_id = 0x280 + node_id;

    // TPDO 3
    node_status.pdo[6].cob_id.bit.not_valid = 1;
    node_status.pdo[6].mapped_objects_n = 1; // set to the actual number of mapped objects but check the slave data sheet to ensure max is not exceeded
    node_status.pdo[6].map[0] = 0;           // number of dictionary object
    node_status.pdo[6].map[1] = 0;           // number of dictionary object
    node_status.pdo[6].map[2] = 0;           // number of dictionary object
    node_status.pdo[6].map[3] = 0;           // number of dictionary object
    node_status.pdo[6].highest_sub_index_supported = 5;
    node_status.pdo[6].transmission_type = 1;
    node_status.pdo[6].start_sync = 1;
    node_status.pdo[6].cob_id.bit.cob_id = 0x380 + node_id;

    // TPDO 4
    node_status.pdo[7].cob_id.bit.not_valid = 1;
    node_status.pdo[7].mapped_objects_n = 0; // set to the actual number of mapped objects but check the slave data sheet to ensure max is not exceeded
    node_status.pdo[7].map[0] = 0;           // number of dictionary object
    node_status.pdo[7].map[1] = 0;           // number of dictionary object
    node_status.pdo[7].map[2] = 0;           // number of dictionary object
    node_status.pdo[7].map[3] = 0;           // number of dictionary object
    node_status.pdo[7].highest_sub_index_supported = 5;
    node_status.pdo[7].transmission_type = 1;
    node_status.pdo[7].start_sync = 1;
    node_status.pdo[7].cob_id.bit.cob_id = 0x480 + node_id;

    node_status.pdo_setup_state[0] = PDO_DEFAULT;
    node_status.pdo_setup_state[1] = PDO_DEFAULT;
    node_status.pdo_setup_state[2] = PDO_DEFAULT;
    node_status.pdo_setup_state[3] = PDO_DEFAULT;
    node_status.pdo_setup_state[4] = PDO_DEFAULT;
    node_status.pdo_setup_state[5] = PDO_DEFAULT;
    node_status.pdo_setup_state[6] = PDO_DEFAULT;
    node_status.pdo_setup_state[7] = PDO_DEFAULT;
}


The make file started life as one of Ataradov's but by the time copilot has had it's way with it I don't know how much is left:

Code: [Select]

##############################################################################

BUILD = build
BIN = main

COMPILER = gcc

SYSTEM = ../../system

LIB = $(SYSTEM)/LIBS

MCU_SYSTEM = $(SYSTEM)/SAMC21

MCU_HW_LIB = $(MCU_SYSTEM)/lib_cpp

STARTUP = $(MCU_SYSTEM)/$(COMPILER)/$(COMPILER)/startup_samc21j17a.c

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

CC = arm-none-eabi-gcc
CXX = arm-none-eabi-g++
OBJCOPY = arm-none-eabi-objcopy
SIZE = arm-none-eabi-size
MKDIR = mkdir -p

CFLAGS += -W -Wall --std=gnu11 -Os -g
CFLAGS += -fno-diagnostics-show-caret
CFLAGS += -fdata-sections -ffunction-sections
CFLAGS += -funsigned-char -funsigned-bitfields
CFLAGS += -mcpu=cortex-m0plus -mthumb
CFLAGS += -MD -MP -MT $(BUILD)/$(*F).o -MF $(BUILD)/$(@F).d

CXXFLAGS += -W -Wall --std=gnu++17 -Os -g
CXXFLAGS += -fno-diagnostics-show-caret
CXXFLAGS += -fdata-sections -ffunction-sections
CXXFLAGS += -funsigned-char -funsigned-bitfields
CXXFLAGS += -mcpu=cortex-m0plus -mthumb
CXXFLAGS += -fno-exceptions -fno-rtti
CXXFLAGS += -MD -MP -MT $(BUILD)/$(*F).o -MF $(BUILD)/$(@F).d

LDFLAGS += --specs=nano.specs --specs=nosys.specs
LDFLAGS += -mcpu=cortex-m0plus -mthumb
LDFLAGS += -Wl,--gc-sections
LDFLAGS += -Wl,-wrap=_malloc_r -Wl,-wrap,_free_r
LDFLAGS += -Wl,--script=linker_script.ld
LDFLAGS += -Wl,-Map=$(BUILD)/$(BIN).map,--cref

# defines, must be updated in c_cpproperties.json as well
DEFINES += \
-D__m0plus__ \
-D__SAMC__ \
-D__SAMC21__ \
-D__SAMC21J__ \
-D__SAMC21J17A__ \
-DDONT_USE_CMSIS_INIT \
-DF_CPU=48000000UL \

INCLUDES += \
-I$(MCU_SYSTEM)/include \
-I$(MCU_SYSTEM)/include/m-profile \
-I$(MCU_SYSTEM)/include/component \
-I$(MCU_SYSTEM)/include/instance \
-I$(MCU_SYSTEM)/include/pio \
-I$(MCU_SYSTEM)/gcc \
-I$(MCU_HW_LIB) \
-I$(LIB) \
-I../settings \
-I./ \

SRCS += \
../main.cpp \
$(STARTUP)/startup_samc21j17a.c \
$(MCU_HW_LIB)/clock.cpp \
$(MCU_HW_LIB)/rtc.cpp \
$(LIB)/port_control.cpp \
$(LIB)/can_open_class.cpp \
$(LIB)/can_open_class_interrupt.cpp \
$(LIB)/class_gpio.cpp \
$(LIB)/class_user_encoder.cpp \
$(LIB)/ioboard1_canopen_profile.cpp \

CFLAGS += $(INCLUDES) $(DEFINES)
CXXFLAGS += $(INCLUDES) $(DEFINES)

OBJS = $(addprefix $(BUILD)/, $(notdir $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(SRCS)))))

all: directory $(BUILD)/$(BIN).elf $(BUILD)/$(BIN).hex $(BUILD)/$(BIN).bin size

$(BUILD)/$(BIN).elf: $(OBJS)
$(CXX) $(LDFLAGS) $(OBJS) $(LIBS) -o $@

$(BUILD)/$(BIN).hex: $(BUILD)/$(BIN).elf
$(OBJCOPY) -O ihex $^ $@

$(BUILD)/$(BIN).bin: $(BUILD)/$(BIN).elf
$(OBJCOPY) -O binary $^ $@

# Build C++ files in main directory
$(BUILD)/%.o: ../%.cpp
$(CXX) $(CXXFLAGS) $< -c -o $@

# Build C files in main directory
$(BUILD)/%.o: ../%.c
$(CC) $(CFLAGS) $< -c -o $@

# Build C files in system files directory
$(BUILD)/%.o: $(MCU_SYSTEM)/gcc/gcc/%.c
$(CC) $(CFLAGS) $< -c -o $@

# Build C++ files in my library directory
$(BUILD)/%.o: $(LIB)/%.cpp
$(CXX) $(CXXFLAGS) $< -c -o $@

# Build C++ files in mcu hardware library directory
$(BUILD)/%.o: $(MCU_HW_LIB)/%.cpp
$(CXX) $(CXXFLAGS) $< -c -o $@

directory:
-@powershell -Command "if (!(Test-Path '$(BUILD)')) { New-Item -ItemType Directory -Path '$(BUILD)' | Out-Null }"

size: $(BUILD)/$(BIN).elf
$(SIZE) -t $^

clean:
-@powershell -Command "if (Test-Path '$(BUILD)') { Remove-Item -Recurse -Force '$(BUILD)' }"

-include $(wildcard $(BUILD)/*.d)

 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 11333
  • Country: fi
Re: Setting up C++ on a microcontroller
« Reply #17 on: February 11, 2026, 08:52:32 am »
You posted a lot of code, but you failed to post the interesting part where node_id is declared, defined, initialized or not initialized.
 
The following users thanked this post: eutectique

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18911
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: Setting up C++ on a microcontroller
« Reply #18 on: February 11, 2026, 09:16:01 am »
You posted a lot of code, but you failed to post the interesting part where node_id is declared, defined, initialized or not initialized.

This error happens on calling:

io_board_1_class::io_board_1_class(device_type deviceType, can_open_class *new_bus, uint32_t new_node_n,
                                   encoder_class * encoder0ptr, encoder_class * encoder1ptr)

the same error is reported for all arguments to the constructor, the node_id peaked my attention as it is literally "4". It is not a variable so no it won't be initialized as such it is just the number 4 to be stored in "uint32_t node_id" in the class object.
 

Online negativ3

  • Frequent Contributor
  • **
  • Posts: 620
  • Country: th
Re: Setting up C++ on a microcontroller
« Reply #19 on: February 11, 2026, 09:27:09 am »
If node_id_value (the variable you're passing) is itself a global/static variable defined in another translation unit (another .c or .cpp file), the compiler cannot guarantee which one gets initialized first. So when my_board is being constructed, node_id_value might not have been initialized yet, hence the warning.
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18911
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: Setting up C++ on a microcontroller
« Reply #20 on: February 11, 2026, 09:46:43 am »
If node_id_value (the variable you're passing) is itself a global/static variable defined in another translation unit (another .c or .cpp file), the compiler cannot guarantee which one gets initialized first. So when my_board is being constructed, node_id_value might not have been initialized yet, hence the warning.

node_id is given to the class constructor as "4UL" it is not a variable, just a constant, but I get the same error on the other arguments as well and these are addresses of other objects/variables so I sort of get that.

So is it a mistake to pass values (constants or variables) to the constructor at class instantiation? should I instantiate the object with no constructor and then update the values in the object.initialize() class function called in main?

As things stand I won't pass arguments to objects or run code in the static instantiation constructor that affects hardware registers as I assume that the code that statically instantiates the object is never actually run and optimized out by the compiler that simply creates the objects in memory with their class attributes set as per the values passed and processed in the constructor.
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18911
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: Setting up C++ on a microcontroller
« Reply #21 on: February 11, 2026, 09:58:21 am »
Right, so I have just changed the constructor to an init() fuction that I run in main() and the issue has gone away.

Code: [Select]

/*
 * User Input with 8 buttons and 2 encoders.
 *
 * Created: 03/02/2026 10:00:00
 * Author : Simon A. Turner
 */

#include "sam.h"

#include "project_settings.hpp"

#include "clock.hpp"

#include "rtc.hpp"

#include "class_gpio.hpp"

#include "class_user_encoder.hpp"

#include "can_open_class.hpp"

#include "can_regs.hpp"

#include "ioboard1_canopen_profile.hpp"

uint32_t last_btn_read_time = 0;

gpioCls btn[10];

encoder_class encoder1(PA10, PA11, 30, pull_up );
encoder_class encoder2(PA12, PA13, 30, pull_up );

can_open_class bus(CAN0);

io_board_1_class io;


int main(void)
{
    clock_setup();
    rtc_initialize();

    /* SOFTWARE SYSTEM SETUP */
   
        encoder1.initialize( );
        encoder2.initialize( );

        io.init( SERVER, &bus, 4UL, &encoder1, &encoder2 );

        btn[0].initializeInput( PB00, pullEnable, active_low );
        btn[1].initializeInput( PB01, pullEnable, active_low );
        btn[2].initializeInput( PB02, pullEnable, active_low );
        btn[3].initializeInput( PB03, pullEnable, active_low );
        btn[4].initializeInput( PB08, pullEnable, active_low );
        btn[5].initializeInput( PB09, pullEnable, active_low );
        btn[6].initializeInput( PB04, pullEnable, active_low );
        btn[7].initializeInput( PB05, pullEnable, active_low );
        btn[8].initializeInput( PB07, pullEnable, active_low );
        btn[9].initializeInput( PB06, pullEnable, active_low );

        encoder1.enable( );
        encoder2.enable( );

        bus.initialize( );

    while (1)
    {
        io.encoder_config_update( ); // checks for new encoder commands and updates accordingly
                if (( ms_time( ) - last_btn_read_time ) >= 2) {

                    last_btn_read_time = ms_time( );

                    io.short_press = 0;
                    io.long_press = 0;

                    for (uint32_t i = 0; i < 10; i++) {
                        btn[i].buttonCycle( );
                        io.short_press |= btn[i].short_press( ) << i;
                        io.long_press |= btn[i].long_press( ) << i;
                    }

                    if (( io.short_press != 0 ) || ( io.long_press != 0 )) {
                        bus.transmit_pdo( io.node_id, tpdo2 );
                    }

                }
         

        bus.heartBeatsTransmit( 500 );
    }
}

« Last Edit: February 11, 2026, 10:06:27 am by Simon »
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18911
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: Setting up C++ on a microcontroller
« Reply #22 on: February 11, 2026, 10:09:11 am »
just looking at the flags I see that -fno-exceptions means that when compiled the data sections are 0:

arm-none-eabi-size -t build/output.elf
   text    data     bss     dec     hex filename
  19492      0    4776   24348    5f1c build/output.elf
  19492      0    4776   24348    5f1c (TOTALS)

Otherwise data is 80. I cannot find any explanation for this flag. Any ideas?
 

Online negativ3

  • Frequent Contributor
  • **
  • Posts: 620
  • Country: th
Re: Setting up C++ on a microcontroller
« Reply #23 on: February 11, 2026, 10:32:19 am »
If you want to identify what those 80 bytes are...

Compare object files with/without the flag
arm-none-eabi-nm -S build/output.elf | grep -E '\.data|\.rodata'

Or check the map file
grep -A 20 "\.data" build/output.map
 

Offline Doctorandus_P

  • Super Contributor
  • ***
  • Posts: 5389
  • Country: nl
Re: Setting up C++ on a microcontroller
« Reply #24 on: February 11, 2026, 10:40:43 am »
... or should I ditch the C stuff and just have it treated as C++?

I would never attempt to use two different compilers to compile sections and then link them into to a single program. It can be done, but there are some gotchas (which are beyond my level of expertise). But a C++ compiler can handle C code perfectly well, so there is no need to make it more complicated.

And C++ is a different language. You may get different (and probably more) warnings and error messages from the compiler.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf

 

- - -
-->