I want to build and simulate a C++ project for Cortex-M4, using the g++ arm compiler, in Arm Dev Studio.
Which example would be suitable as a template please?
Hi,My name is Stephen and I work at Arm.As a starting point, I suggest you take a look at the ready-made example for Cortex-M4, provided in the Arm DS Examples, named "startup_Cortex-M4_AC6". That example is intended for use with Arm Compiler 6 ("AC6").To import this example into the workspace:
To run the example, follow the instructions in the readme.Sorry, there is no a GCC-specific example for Cortex-M4 in the Arm DS examples, though the command-lines are similar, so it should be straightforward to convert from AC6 to GCC.C code for Cortex-M4 would normally be compiled using Arm Compiler 6 with, e.g.:armclang --target=arm-arm-none-eabi -mcpu=cortex-m4 -g -O1 ...C++ code for Cortex-M4 would normally be compiled using g++ with, e.g.:arm-none-eabi-g++ -c -mcpu=cortex-m4 -g -01 ...The "startup_Cortex-M4_AC6" example uses some AC6 compiler intrinsics such as __arm_wsr(), though these can be converted to inline assembler if needed for g++.Alternatively, take a look at the CMSIS Pack examples for the Cortex-M family. For example, the RTX RTOS can be compiled with either AC6 or GCC. To explore these, go to the CMSIS-Pack Manager perspective (button the top-right of the IDE). Then, in the Devices tab, select your M-class device (or "All Devices"), then select the Examples tab. The CMSIS-RTOS2 Blinky example might be a good one to start with.Hope this helps,Stephen
Hi Stephen
Thanks very much for your answer (which I will accept). I've chosen to use "startup_Cortex-M4_AC6", because my project is bare-metal. I've built and run it successfully.
My plan is to convert it to C++ and then to convert it to use the GCC toolset.
Do you have an opinion about whether I should change main.c to main.cpp and compile it with the C++ compiler, or whether to keep it as main.c and call a C++ module from it? (I'm guessing that the former would be correct).Best regards
David
Hi David,
Yes, I think I'd go for the former too - that is, change main.c to main.cpp and compile it with the C++ compiler.
Suggest try compiling with "-std=gnu++11". You'll need to comment-out the SCS.CPACR line temporarily to compile that file successfully.
Hope this helps to get you started.
Stephen
Thanks, I'm getting there!
I now only see this error:Error: L6218E: Undefined symbol $Super$$__rt_lib_init() (referred from main.o).
I also get a lot of messages about: "Removing Unused input sections from the image.".
Any thoughts on these please? Do I need that macro?Best regards
Regarding my previous comment, __rt_lib_init() is a library initialization function. It is used in the example's main.c:/* Enable the FPU if required */ #ifdef __ARM_FP extern void $Super$$__rt_lib_init(void); void $Sub$$__rt_lib_init(void) { /* Enable the FPU in both privileged and user modes by setting bits 20-23 to enable CP10 and CP11 */ #if 0 SCS.CPACR = SCS.CPACR | (0xF << 20); #endif $Super$$__rt_lib_init(); } #endif
/* Enable the FPU if required */ #ifdef __ARM_FP extern void $Super$$__rt_lib_init(void); void $Sub$$__rt_lib_init(void) { /* Enable the FPU in both privileged and user modes by setting bits 20-23 to enable CP10 and CP11 */ #if 0 SCS.CPACR = SCS.CPACR | (0xF << 20); #endif $Super$$__rt_lib_init(); } #endif
I don't know what to do with this when converting to main.cpp. Any thoughts please?
Best regards
My new main.cpp builds without error but when I run the code the printf's don't appear. A breakpoint at the first printf is never reached. My current main.cpp is shown below. Any thoughts please?Best regards
#if 0 /* Enable the FPU if required */ #ifdef __ARM_FP extern void $Super$$__rt_lib_init(void); void $Sub$$__rt_lib_init(void) { /* Enable the FPU in both privileged and user modes by setting bits 20-23 to enable CP10 and CP11 */ #if 0 SCS.CPACR = SCS.CPACR | (0xF << 20); #endif $Super$$__rt_lib_init(); } #endif #endif __attribute__((noreturn)) int main(void) { /* Processor starts-up in Privileged Thread Mode using Main Stack */ /* Tell the processor the location of the vector table, obtained from the scatter file */ *(volatile unsigned int *)(VectorTableOffsetRegister) = (unsigned int) &Image$$VECTORS$$Base; /* Display a welcome message via semihosting */ printf("Cortex-M4 bare-metal startup example\n"); #if 0 /* Initialize MPU */ SCS_init(); #endif /* Perform a float calculation */ #ifdef __ARM_FP printf("Calculating using the hardware floating point unit (FPU)\n"); #else printf("Calculating using the software floating point library (no FPU)\n"); #endif printf("Float result should be 80.406250\n"); printf("Float result is %f\n", calculate(1.0f, 2.5f)); /* Initialize SysTick Timer */ SysTick_init(); /* Initialize Process Stack Pointer */ __arm_wsr("PSP", (unsigned int) &Image$$PROCESS_STACK$$ZI$$Limit); /* Change Thread mode to Unprivileged and to use the Process Stack */ unsigned int read_ctrl = __arm_rsr("CONTROL"); __arm_wsr("CONTROL", read_ctrl | 3); /* Flush and refill pipeline with unprivileged permissions */ __isb(0xf); /* Run the main application (sorts) */ compare_sorts(); while( 1 ) { /* Loop forever */ } } /* Float calculation to demonstrate the Cortex-M4's FPU, including float Fused Multiply Add (fma) */ float calculate( float a, float b) { a = a + 3.25f; b = b * 4.75f; return fmaf(a + b, a, b); // result should be (4.25+11.875)*4.25 + 11.875 = 80.40625 }
Hello David,
Is the issue specifically that printfs are not working? Have you enabled semihosting in your code? The below article (not written by Arm) may be useful:
https://interrupt.memfault.com/blog/arm-semihosting
Hi Ronan,
Thanks for your reply. To be honest, I'm not sure that the problem is as simple as enabling semi-hosting, there is the issue of C++ startup to consider.
I'm new to ARM and am rather to surprised to find that there appear to be no examples in ARM Dev Studio of developing with C++; everything seems to be for 'C'. I've tried selecting C++ in the new project wizard but that seems to generate very simple 'Hello world' examples with no C++ startup code.
Do you have any suggestions for a starting point please?Best regards
I'm not overly familiar with g++, but using armclang, there is little difference between using C and C++. The initialization code should take care of everything.
Note that C++ has a significantly larger init code, which may be an issue if using a contrained MCU, which is why C is used more often than C++, but that is a side issue.
Looking again at the history above, I note that you have '#if 0'd out the code to enable the FPU, though your code may assume it is enabled. Set a breakpoint on your code for _fp_init and single step through that function. Is that what is causing the error?
Looking at the structure of the example you are using to build from, copying this $Sub#_lib_rt_init code into the scs.c file should bring that in. Alternatively, disable FP by compiling with:
-mcpu=cortex-m4 -mfpu=none -mfloat-abi=soft
Stephen suggested the use of CMSIS. This contains all the necessary init code, including device specific code if working with an MCU. I created a simple example (attached), that builds and runs a C++ example I found online on the supplied FVP.
CMSIS_cpp.zip
Hi Ronan and StephenThanks very much for your answer Ronan. In line with your suggestion, I am now trying to use CMSIS and thought it best to try to run the CMSIS-RTOS2 Blinky example that Stephen suggested.
I have successfully imported it and built it. Please will you explain how I can run it in ARM Dev Studio?
I assume you imported the uVision Simulator version of the Blinky example. This is configured by default for Cortex-M3. If you wish to change it for Cortex-M4, open the .rteconfig file. and go to the Device Tab, and click Change, and select ARMCM4(_FP) (if floating point desired). The example I attached yesterday may be easier to consume.To run, select Fiile > New > Model Connection, and give it a meaningful name. Also recommend associating with the project for ease of file management. In next pane, select MPS2_Cortex-M4 from the FVPs (Fixed Virtual Platforms) installed with Arm DS. Finally, in the Files tab, navigate to the .axf built in the Workspace, and in Debugger tab, Debug from main (or entry point), Click debug, and the model will launch and load the code (to repeat, you will be able to just double click on this connection in the Debug Connections pane).
Thanks, I have successfully built the Blinky example and created a debug connection for it. Please will you remind me what I need to do to see the printf output in the Target Console?
If you have time, I would be grateful for steps to add C++ source file.Best regards
This one had me scratching my head for a moment...
I found the default build setting is to send printf (stdout) to Event Recorder, which is a Keil MDK feature for instrumentation.
Open the .rteconfig file, and disable this setting in Compiler > I/O, to use the default (semihosting) stdout.
Rebuilt, and I do indeed see the output in the Target Console:
Iris server started listening to port 7100 telnetterminal0: Listening for serial connection on port 5000 telnetterminal1: Listening for serial connection on port 5001 telnetterminal2: Listening for serial connection on port 5002 Iris server is reported on port 7100 LED On: #0 LED On: #1 LED Off: #0 LED On: #2 LED Off: #1 ...
To add a C++ file, this is not straight forward in this case. I don't think there is an option to change a C project to a C/C++ project. If you are just adding a single C++ source file, you can right click on that file, and in Properties > Tool Chain Editor, select the C++ compiler. You then need to go to Settings and enter compatible build options for the C builds. This will only apply to this source file, so isn't very scalable :(
The easiest solution (sorry) is to create a New > Project > C/C++ > C++ Project > CMSIS C/C++ Project, and then copy over the Blinky.c source file. You will need to setup the generated .rteconfig file the same way as it was for the previous project, but that is fairly straight forward from the GUI (and is likely do-once operation).