Calling C++ from Arm assembly

I'm trying to set up a Raspberry Pi Pico as a teaching platform for learning ARM assembly language. I'd rather not use the Pico SDK as the development environment because of the multi-gigabyte install and having to learn Cmake before you can even compile a program.

I have the Arduino IDE set up with the Mbed RP2040 board installed. I have no problem with writing, compiling and running ARM assembly on the Pico with that setup. I can even call functions written in C. That's great when things work perfectly. It's not so great when there's a problem. Since the debug facility in the Arduino IDE doesn't appear to support the Pico yet, debugging is primitive to say the least. I can turn an LED on and off.

What I would like to be able to do is send characters from the Pico to the host computer using the Serial() functions. The problem is that those are C++ functions, not C functions. Does anyone have any example code of calling C++ functions from assembler. Arm would be great, but even AVR code would give me a starting point for figuring this out.

Have you seen this and this?

They use a different compiler, but perhaps it will get you started.

Thank you. I had seen the first one, but not the second one. Time to get busy writing test programs :slight_smile:

One way to do this that you might not have thought of is to use Python on your RP2040 board.
There are packages for AdaFruit's Circuit Python that let you run and compile machine code. I have used them to fill in the gaps for devices missing from the Normal Python libraries.

So you program it from an editor like Thonny and when you run the file you have created it runs in ARM machine code.

Just test it - fully unpacked Raspberry Pico package has 390 Mbytes on my harddrive. And I don't use cmake for it...

how do you manage that, without a makefile that includes ~100 "-I" arguments?

Presumably the "several gigabytes" is the version that includes the Microsoft VC++ Community Edition "needed" to compile some of the host-side utilities. If I use the (newish) windows Pico SDK Installer utility, I get about 1.3G, which includes gcc, cmake, git, python, and etc.

westfw@ww-gaming:/mnt/c/RPi_Pico_v1.5.0$ du -sh *
8.0K    ReadMe.txt
107M    cmake
708M    gcc-arm-none-eabi
68M     git
2.1M    install.log
572K    ninja
21M     openocd
4.0K    pico-code.ps1
4.0K    pico-env.cmd
0       pico-env.ps1
11M     pico-examples.zip
315M    pico-sdk
3.8M    pico-sdk-tools
4.0K    pico-setup.cmd
4.0K    pico-setup.lnk
3.4M    picotool
16M     python
212K    resources
272K    uninstall.exe
0       version.ini

A lot of that (gcc, pico-sdk include files) would also be installed by the Arduino utilities (Wee! Multiple copies, everywhere!)

I have use Earl Pheelhower RP2040 arduino package, that make me able to use SDK methods directly from Arduino.
It contains a source of SDK files (24 Mbytes) and cmake (13 Kbytes)

....
Although you may be right - only make files are in the cmake directory and there is no cmake itself. I also don't see where my gcc-arm-eabi compiler is. Perhaps it was installed earlier with some other board

Just as a status update, I am getting there, slowly.
The test program I am using is this:

void setup()
{
  Serial.begin(9600);
}
void loop()
{
  while (true)
  { 
    Serial.println("Hello World!  ");
    delay(1000);
  }
}

The compiler produces the following assembler code:

	.cpu cortex-m0plus
	.eabi_attribute 20, 1
	.eabi_attribute 21, 1
	.eabi_attribute 23, 3
	.eabi_attribute 24, 1
	.eabi_attribute 25, 1
	.eabi_attribute 26, 1
	.eabi_attribute 30, 4
	.eabi_attribute 34, 0
	.eabi_attribute 18, 4
	.file	"HelloWorld_Picotest.ino.cpp"
	.text
.Ltext0:
	.cfi_sections	.debug_frame
	.section	.text.loop,"ax",%progbits
	.align	1
	.global	loop
	.syntax unified
	.code	16
	.thumb_func
	.fpu softvfp
	.type	loop, %function
loop:
.LFB3082:
	.file 1 "/home/kelly/Arduino/HelloWorld_Picotest/HelloWorld_Picotest.ino"
	.loc 1 8 0
	.cfi_startproc
	@ Volatile: function does not return.
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 0, uses_anonymous_args = 0
	push	{r4, lr}
	.cfi_def_cfa_offset 8
	.cfi_offset 4, -8
	.cfi_offset 14, -4
	.loc 1 12 0
	movs	r4, #250
	lsls	r4, r4, #2
.L2:
	.loc 1 11 0 discriminator 1
	ldr	r1, .L3
	ldr	r0, .L3+4
	bl	_ZN7arduino5Print7printlnEPKc
.LVL0:
	.loc 1 12 0 discriminator 1
	movs	r0, r4
	bl	delay
.LVL1:
	b	.L2
.L4:
	.align	2
.L3:
	.word	.LC0
	.word	_UART_USB_
	.cfi_endproc
.LFE3082:
	.size	loop, .-loop
	.section	.text.setup,"ax",%progbits
	.align	1
	.global	setup
	.syntax unified
	.code	16
	.thumb_func
	.fpu softvfp
	.type	setup, %function
setup:
.LFB3081:
	.loc 1 4 0
	.cfi_startproc
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 0, uses_anonymous_args = 0
	.loc 1 5 0
	movs	r1, #150
	.loc 1 4 0
	push	{r4, lr}
	.cfi_def_cfa_offset 8
	.cfi_offset 4, -8
	.cfi_offset 14, -4
	.loc 1 5 0
	lsls	r1, r1, #6
	ldr	r0, .L6
	bl	_ZN7arduino4UART5beginEm
.LVL2:
	.loc 1 6 0
	@ sp needed
	pop	{r4, pc}
.L7:
	.align	2
.L6:
	.word	_UART_USB_
	.cfi_endproc
.LFE3081:
	.size	setup, .-setup
	.section	.rodata.loop.str1.1,"aMS",%progbits,1
.LC0:
	.ascii	"Hello World!  \000"
	.text
.Letext0:
	.file 2 "/home/kelly/.arduino15/packages/arduino/tools/arm-none-eabi-gcc/7-2017q4/arm-none-eabi/include/machine/_default_types.h"
	.file 3 "/home/kelly/.arduino15/packages/arduino/tools/arm-none-eabi-gcc/7-2017q4/arm-none-eabi/include/sys/_stdint.h"
	.file 4 "/home/kelly/.arduino15/packages/arduino/tools/arm-none-eabi-gcc/7-2017q4/arm-none-eabi/include/stdint.h"

Most of that is debugging information, and I have trimmed off several hundred lines similar to the last couple of lines in that output.

My assembler code is this:

.thumb_func                        // let the compiler know this code must be entered in thumb mode
.thumb                             // this code is 16 bit thumb code
.global main                       // make the label 'main' available to the linker
.syntax unified
.extern _UART_USB_                 // constant from Arduino IDE
.extern _ZN7arduino4UART5beginEm   // Serial.begin( int baud_rate ) as mangled by C++ compiler
.extern _ZN7arduino5Print7printlnEPKc // Serial.println("")
.extern delay                      // delay(int)

main:     
      bl   setup                      // initialize USB port
loop:
      ldr  r0,=_UART_USB_             // data to go via USB port
      ldr  r1,=message                // data is at this address
      bl   _ZN7arduino5Print7printlnEPKc   // go print message
      ldr  R0,=1000        // delay is 1 second
      bl   delay
      b    loop
      .align 2
message:
      .asciz "Hello World! "

setup:
      push {r4,lr}
      ldr  r0,=_UART_USB_             // set up Serial port
      ldr  r1,=9600                   // baud rate is 9600
      bl   _ZN7arduino4UART5beginEm   // call USB initialization
      pop {r4,pc}

It compiles and links without error, so all the labels I am using exist. The problem is that the serial port does not show up to the host computer. It's as if the Serial.begin is not being called. When I add in code to blink an LED, I can verify that the code does return from the Serial.begin and the Serial.println. I can also confirm that the delay function works as intended. I'm obviously missing something somewhere.

I found my soluion. The problem was a couple of initialization calls that main makes before calling setup. I ended up creating a C wrapper function to do that initialization, and call it from my assembly language program.

extern "C"  void Serial_Init( void );
/*  Initialise hardware and USB serial port */
void Serial_Init( void ) {
    init();
    initVariant();
#if defined(SERIAL_CDC)
  PluggableUSBD().begin();
  _SerialUSB.begin(115200);
#endif
  return;
}

It was the calls to initVariant() and PluggableUSBD() that I was missing.

I've also decided to skip trying to use the mangled names as created by the compiler. Calling _ZN7arduino5Print5printEmi is much less understandable than calling Serial.Print( int number)
Instead I've created a "C" function as a wrapper around the C++ calls.

extern "C" void Print_Integer( int number );
void Print_Integer( int number ) {
  Serial.print( number );
  return;
}

It's a bit of a pain having to create a new wrapper function for each set of parameters to Serial.print(), but it's good enough to use for debugging, which was my main goal.

Thanks for the help, it got me started in the right direction.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.