Skip to main content
TLeaf
Associate
May 27, 2019
Question

How to configure STM32CubeIDE to support C++ development?

  • May 27, 2019
  • 26 replies
  • 40319 views

1. How to configure the IDE to use g++ compiler to compile all the files includes "*.c" files in the project?

Current it compiles the .c files by using gcc and .cpp by using g++ ...

2. If I rename main.c to main.cpp, the cube code generator will create a new main.c file instead of using main.cpp. Any sulotions for this?

Becasue main.c is .c file, so the IDE use gcc to compile this file, and this caused that I can't use any objects in it. But if I changed the main.c to main.cpp, the cube can't generate code into it...:face_screaming_in_fear:

26 replies

TMaia.1
Associate
November 13, 2020

@Harvey White​ , can you provide some example? I'm not sure if I understand you correctly.

Harvey White
Senior III
November 13, 2020

The example is earlier in this thread. Here's the setup, and why I did it like that:

1) I have a main program loop in C++ in file application.cpp. That's what I want to run

2) I have a main.c generated by CubeMX. I don't want to rename main.c to main.cpp

3) I've determined that I will always call the application in my programs application.cpp, even though it may be a different project ( I have a program that writes main application loops for certain types of programs, just to mention it).

4) the main program (main.c) calls the bridge program before it goes into the loop. In main.c it looks like this:

/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void const * argument)
{
 /* USER CODE BEGIN 5 */
 /* Infinite loop */
	 cpp_link();
	 // NEVER GETS HERE, cpp_link goes to application init and then loop
 for(;;)
 {
 osDelay(10000);
 }
 /* USER CODE END 5 */
}
 

5) note that the main program loop does nothing, and I call my bridge program (C to C++) before the main loop. The main loop executes, but that's a "who cares" and it's going to spend most of its time in a delay. My bridge program is called cpp_link.

CPP_link.hpp looks like:

#ifndef CPP_LINK_HPP_
#define CPP_LINK_HPP_
 
	#ifdef __cplusplus
		extern "C"
		{
	#endif
 
		void cpp_link (void);
 
	#ifdef __cplusplus
		}
	#endif
#endif /* CPP_LINK_HPP_ */

since it's identical for all applications I write, it never needs to change, and the code in main.c can just be copied over.

CPP_LINK.cpp looks like this:

#include "CPP_LINK.hpp"
 
#include "APPLICATION.hpp"
 
#ifdef __cplusplus
	extern "C"
	{
#endif
 
	void cpp_link (void)
		{
			APPLICATION_init();
		}
 
#ifdef __cplusplus
	}
#endif

CPP link never changes because the call to my program (application.cpp) never changes because the main executable routine is always called APPLICATION_init();

Since I'm using FreeRTOS, APPLICATION_init looks like this:

// ********************************************************************************************************************
// ******************************************************* APPLICATION INIT *******************************************
// ********************************************************************************************************************
 
 
 void APPLICATION_init(void)
 {
 #ifdef _FREERTOS
 xTaskCreate((TaskFunction_t) APPLICATION_TASK,
 "APPLICATION",
 24000,
 NULL, 3,
 &APPLICATION_task_handle);
 #endif
 }

Your settings for FreeRTOS will vary, of course.

The first few lines of the application itself look like this....

 #ifdef _FREERTOS
 void APPLICATION_TASK(void const * argument)
 {
 struct address_list_type A;
 int display_number = 0; // incrementing display number
// ********************************************************************************************************************
// ******************************************************* APPLICATION VARIABLES **************************************
// ********************************************************************************************************************
 
 
 struct SYSTEM_INSTALL_response install_response[40];
 uint16_t which_install = 0;
 int16_t i;
 int x = 0, y = 0;
 #ifdef _GRAPHICS
 string main_caption;
 #endif
 
// (more code follows.........)

So:

main.c (regenerated by CubeMX) calls cpp_link. Once the code is inserted in main.c, CubeMX doesn't change that code. CPP_link calls your application code.

That CPP_LINK calls the application_init code in my programs is just how I do things. CPP_link can call your C++ program loop directly. As you see, Application_init sets up my main program task (it then returns, and you get the very long main program loop with the delay of 10000). What you call from CPP_LINK is up to you, or even if it never comes back.

The reason for this design is that:

1) I don't have to change or rename main.c

2) I have a very small program (CPP_LINK) that does exactly what I need and was the experiment to see if it compiled

3) for me (and this may be unique), I have a program that builds application.cpp for me, depending on what project options I pick. So having fixed names (but variable content) makes sense, but that's only for what cpp_link calls.

All the other parts of my code are written to call C routines (if FreeRTOS) or if HAL layer routines. My main code is C++ which can just call C++. No C code calls C++ with the exception of the one call in main.c to cpp_link.

TMaia.1
Associate
November 15, 2020

Thanks. Its clear now. However, I got this dumb newbie question, since I have trouble handle it. How do you create global variables without editing main.c?

Harvey White
Senior III
November 15, 2020

I'm going to assume that you have main.c, which then calls another function in a CPP program, lets call it a(), in a file app.cpp, and you have another file called other.cpp with routines in it.

First: any routine in other.cpp has a corresponding definition in other.hpp, so you get to the routines like that.

Second: any variable in other.cpp that isn't in a function, ex: int mine; can be declared external in app.cpp, and declared outside any function: example: extern int mine; It can be accessed from app.cpp. You do this with classes, too, so if you have a class MYCLASS declared in other.hpp, you can have an instance of it in other.cpp example: MYCLASS myclass; and that can be accessed in app.cpp by doing: #include "other.hpp" and on the next line: extern MYCLASS myclass;

Establish a naming convention for yourself so that you can tell the difference between the class declaration and an instance of the class. Since C++ is case sensitive, one way is to have the class declaration in caps, and the instance in lower case. There's lots of different opinions on this, pick whatever you'd like.

A second way would be to establish another file: globals.hpp, put globals in that, and then include that in your files. I do this with structure definitions that have to be in many different files (example: graphics objects to be drawn need the basic definitions for graphics for each) Again, there are many different program structures and ideas about what you put where, whether or not you should ever have a global variable, and so on.

TMaia.1
Associate
November 15, 2020

Thank you. I saw your previous code, but I was afraid that I was missing something. I will try again.

Let me ask you a newbie question. Using HAL, many examples work with global variables. How do you handle that without touching main.c?

Harvey White
Senior III
November 15, 2020

You might find the example I just gave to be more comprehensive, I'd hope so. Feel free to ask questions, of course.

The more complex the program, the (somewhat) trickier the answer is to your question.

Main.c calls CPP_LINK, which then calls APPLICATION_init, which sets up and starts the application task. The application task needs to find stuff. If main.c never calls it, then main.c doesn't need to know where it is. The global variables are in the application part that's called by CPP_LINK.

I had almost gone on to a more complex explanation, but it's dependent on the rest of the structure of your program.

The program I have is rather complex, lots of tasks, and is graphics touch-screen driven, which is generally not the average example program. It might be doing things you don't need to do when you're working with the HAL example programs.

I could give you an example with I2C (which I use a lot), but it may not fit your needs. Do feel free to ask.

TMaia.1
Associate
November 26, 2020

I did run some tests and it works. I replaced the init function with an application class.

Application app;
app.init();
//APPLICATION_init();

But still, I have many doubts.

1- I was sure that I was about to get compilation error only calling

cpp_link();

as you mentioned. However, I only got a warning which is gone after inserting

#include "CPP_LINK.hpp"

How does the compiler found cpp_link function without the header?

2- I also need to add

#include "main.h"

inside Application.cpp. Otherwise, It can't find HAL functions.

3- The global variables still are my concern. Ex: main.c has

UART_HandleTypeDef huart2;

created by Configuration Tool. I can't use it inside application. Any suggestion?

Harvey White
Senior III
November 26, 2020

The reason for the app_init was that the application wasn't a class, I didn't need one. I generally prefer to have a class when I'm thinking possible derivatives of that class or need inheritance. Didn't see a need for that in the main application. So that's nothing major.

As far as CPP_LINK.hpp is concerned, compilers hate surprises. In any file, when you come across any reference to anything, it either has to be a part of the language or it's already been defined. It's traditional to put those definitions in the .h or .hpp files. If you have a function completely declared in a file, and only use it afterwards, then you don't need it in the .hpp/.h file. If that same function is ever called from outside the file, then you have to have the definition available to the file in which it is called. Most people include the .h or the .hpp file. CPP_LINK.hpp was a separate file. CPP_LINK.cpp was a separate file. They never changed and never needed to change as long as your application had the same name.

I didn't give you the whole application file. Not only is it too big, but you have pretty much the parts you need to see how the C and C++ parts go.

Inside your application.cpp file, you declare the following:

// this is for another board, you'll need to put your own board definitions in here. Look to main.c for the proper includes

 #include "stm32f7xx.h" // 144 pin processor, NUCLEO-144 board, $23.00
 
 #include "stm32f7xx_hal.h" // HAL drivers
 
 

// then add:

extern UART_HandleTypeDef huart2;
 

You've already put in main.h which copies pin definitions from the CubeMX program.

You shouldn't need much more.

You can always go to the main.c program, look to see what's included, then see where it's defined. That may help a bit.

But with the right files (check the contents of the equivalent files

LOliv.2
Associate
December 6, 2020

I may understand the workaround proposed, but I am still not sure if implementing it.

I am currently exploring the STM32 universe to see if it can suit our needs and the fact that C++ is not natively supported seems very strange. To be honest, I don't understand where is the problem, it's in CubeMX that doesn't check if the user renamed main.c into main.cpp?

Or the problem is deeper into the project structure?

I'm asking because we will need to integrate C++ code in our projects and I am not comfortable to push the company on a new ecosystem that doesn't support C++. For this reason I'm asking, trying to understand if it's something that is a matter of a point release to fix or it's a deliberate choice by ST

Thanks, any opinion is appreciated

Harvey White
Senior III
December 6, 2020

My experiences are with CubeMX, then CubeMX with FreeRTOS added, then CubeMX with C++ and with FreeRTOS.

While FreeRTOS is not C++, and may never be, CubeMX may eventually be, but since I do not and cannot speak for ST, I can't say anything about that.

Remember that while a C++ program can call C programs directly, the reverse is not true without help. There are others who have put C++ wrappers around C programming, but the particular approach I used requires that to be done once. Since CubeMX does not see or respect a main.cpp file (would be easy enough to do), you need a workaround. Renaming the main.c to main.cpp is bothersome, and easy to forget to do, since you must rename main.cpp back to main.c if you want CubeMX to deal nicely with it. (solution had been suggested)

What I have is not an ideal structure, but it does work. There are limits to how the code interacts. For instance, callback routines from .c programs are not happy with trying to access .cpp routines, so there's a limit to what you can do with callbacks. Fortunately, if you use FreeRTOS, the FreeRTOS functions are in C, can be called, and you can use a semaphore as a signal flag when needed (or equivalent). If you're writing a single byte/word/uint32_t variable, something the processor does in one write cycle, then you need not worry. Pointers are likely ok by the same reasoning.

You can (and I do and have) generate C++ programs that will work within the CubeMX ecosystem. They may contain some workaround, especially with some class instances, but they do work. The code will not likely satisfy a C++ purist, though.

I've no experience with any other microcontroller operating system other than FreeRTOS, other than one I wrote for the AVR 8 bit series. That was in C++, so with no CubeMX, everything was in C++.

From what I understand, FreeRTOS does things in a way that C++ would break, if used. ST has C++ compatibility wrappers for all the functions, but there are still problems, as I mentioned. Will they ever go to C++? no idea.

The code I have is considerably more complex than the average example program, so yes, you can do it. Gracefully? for certain values of grace....

RFlec.1
Associate III
January 28, 2021

I'm a bit surprised, since this toppic seems to be so old and CubeMX still does not respect your CubeIDE Project settings. I'm currently struggling with exactly the same problem. I created a C++ Project with CubeIDE and for me the only reason to use Eclipse is the ST Integration of all those tools that make STM32 Development so much easier. But CubeMX stoically insists on creating a c-File and this is obviously not compiled by a C++ Compiler since I get the error that it doesn't know the class keyword. Is it really so hard to implement this properly? Is there any setting to just compile everything with the C++ Compiler regardless of the file extension? All those solutions where you try to call C++ from C and renaming the main.c file to cpp are nothing more than ugly hacks.

Harvey White
Senior III
January 28, 2021

CubeMX always creates a C file. FreeRTOS is a C program, the HAL drivers (and LL drivers) are C programs. I'll bet all of the provided routines are C.

CubeMX is C.

Renaming main.c to main.cpp does work, but you eventually have to automate it to keep from getting mixed up with the latest version (.c) and the version you tweaked (.cpp). Note also that CubeMX has to read your .c file to keep the user sections.

I'll disagree that calling the C++ from C is an ugly hack. Once done properly (at least through a bridging call), you don't have to worry about it ever again. Period. Your main.c stays main.c, your application code is in C++, and barring a few hiccups (or hiccoughs) with FreeRTOS and some other support routines, you're done.

I doubt that the support software will ever go to C++, there's a lot out there. FreeRTOS, because of design philosophy, may (likely will) never go to C++. You can write the equivalent of FreeRTOS (limited or otherwise) in C++, so that's not the problem.

I have a C++ project with graphics, FreeRTOS, SD cards, HAL drivers, touchscreen, SPI, I2C, serial, NRF networking. As much of it is C++ as possible, but FATFS, FreeRTOS, hal drivers, they're all C. It still works.

I don't think the situation will change with respect to C, C++. I do think that a good solution (at least to the main.c problem) is to allow CubeMX to make changes to main.cpp if it can't find main.c. It could be a special configuration box with all sorts of lawyer induced warnings but it would save the renaming problem.

Pavel A.
Super User
January 28, 2021

Supporting code generation in C++ will double and triple amount of testing with various C++ compilers and emerging new flavors of C++.

While ST still suffers with plain C.

Again - not all C code can be safely compiled in C++ mode.

No, there's no need to rename main.c. No, calling C++ from C isn't ugly hack. It's opinion against an opposite opinion.

>  Is there any setting to just compile everything with the C++ Compiler regardless of the file extension?

Yes there is. Use on your own risk.

We need CLM movement here - "C programmers' lives matter".

-- pa

RFlec.1
Associate III
January 28, 2021

If you know about the config option to always use the c++ compiler, would you please be so kind as to explain where?

Pavel A.
Super User
January 28, 2021

Is google broken today?? The option is -x c++

For example gcc -x c++ foo.c