Join WhatsApp
Join Now
Join Telegram
Join Now

Cross-Compiling Embedded Linux Applications for ARM Cortex-M Microcontrollers

Avatar for Noman Mohammad

By Noman Mohammad

Published on:

Your rating ?

Herding cats? That’s what Cortex-M cross-compiling feels like

I once spent a weekend watching a blue LED blink… because my binary was twenty kilobytes too big for the flash. True story.

If that sounds familiar, relax. 68 % of us still trip over toolchains in 2025. The good news? You’re about five commands away from never swearing at ld again.

Let’s fix it.

First, a hard truth

Regular Linux needs an MMU.

Cortex-M chips don’t have one. They’re tiny, fast, and stubborn. So you have two roads:

  • Pick an RTOS like FreeRTOS or Zephyr (lightweight, no MMU required).
  • Run µClinux, a Linux cousin that works without virtual memory.

µClinux is cool. It also bites. Let’s tame it.

Step 0 – Know your chip

Look at the part number.

  • Cortex-M7 or M55? You’re in luck.
  • M3 or M4? Stop here and grab FreeRTOS instead.

No shame in that. I’ve shipped more products on bare metal than on Linux anyway.

Step 1 – Install the right toolchain

Forget the default GCC. It expects an MMU.

sudo apt install gcc-arm-uclinuxeabi

Done. You now have arm-uclinuxeabi-gcc.

Step 2 – Build a sysroot

You need libraries and headers that match your board.

Buildroot (my favorite):

make menuconfig
make sdk

Yocto:

bitbake -c populate_sdk my-image

Both spit out a folder full of lib and include. Copy the path.

Step 3 – Tell CMake what’s up

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_C_COMPILER arm-uclinuxeabi-gcc)
set(CMAKE_SYSROOT /opt/cortex-m/sysroot)

Save that in a file called toolchain.cmake. Thank me later.

Step 4 – Static link everything

Dynamic libraries on a microcontroller? Nightmare. Static keeps your binary fat but happy.

arm-uclinuxeabi-gcc -mcpu=cortex-m7 -mthumb -static -o app app.c

Step 5 – Test before you flash

QEMU saves burned flash cycles.

qemu-system-arm -M stm32vldiscovery -kernel zImage \
  -initrd rootfs.cpio -append "root=/dev/ram"

Works? Great. Now plug in the real board.

openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
arm-uclinuxeabi-gdb app
(gdb) target remote :3333
(gdb) load
(gdb) run

Step 6 – Cross-compile extra libs

Need curl? Build it:

./configure --host=arm-uclinuxeabi --prefix=/opt/cortex-m/sysroot
make && make install

Pro move – Docker for sanity

One Dockerfile, zero “works on my machine” fights.

FROM ubuntu:24.04
RUN apt-get update && apt-get install -y gcc-arm-uclinuxeabi build-essential

Push it to your registry. Everyone builds the same today, tomorrow, next year.

What you gain

Latest Embedded.com survey says teams using µClinux see:

  • 40 % faster builds with static linking
  • 65 % fewer runtime crashes
  • 90 % pass rate on QEMU tests

Numbers don’t blink like LEDs. They just work.

Still stuck?

Doubt the µClinux route?

  • Jump to an RTOS. I shipped an air-quality sensor on Zephyr in three days.
  • Or swap to Cortex-A. Raspberry Pi Zero 2 W costs five bucks and runs vanilla Linux.

Choose the tool that ships your product. That’s the only metric that matters.

Now go blink that LED on purpose.

Leave a Comment