ARM Cortex-M Families Compared: M0, M0+, M3, M4 and M7 Explained

ARM does not manufacture microcontrollers. It designs processor cores and licenses them to chip makers such as NXP, STMicroelectronics, Microchip, Texas Instruments, Nordic and Raspberry Pi. Each vendor adds its own flash, RAM, clocks and peripherals around the core. That is why an LPC1768 and an STM32F103 have completely different GPIO registers but run the same instructions and handle interrupts in exactly the same way: both use a Cortex-M3 core.

Knowing which core sits inside a chip tells you a lot before you open its datasheet: whether it can divide in hardware, whether floating point is fast or emulated in software, how many interrupts the controller supports and which compiler flags to use. This article compares the five most common Cortex-M cores and shows how the differences appear in real code.

Architecture Version Versus Core

Two names are involved. The architecture (ARMv6-M, ARMv7-M, ARMv7E-M, ARMv8-M) defines the instruction set, the exception model and the programmer's view. The core (Cortex-M0, M3, M4 and so on) is one particular hardware implementation of an architecture, with its own pipeline, bus interfaces and optional blocks. Code compiled for an older, smaller architecture generally runs on a larger one, but not the other way round.

All Cortex-M cores share some important features. They execute only the Thumb instruction set (there is no 32-bit ARM state as in the older ARM7TDMI), they have a fixed memory map, a Nested Vectored Interrupt Controller (NVIC), and a vector table that holds addresses rather than branch instructions. Interrupt handlers can be ordinary C functions because the hardware saves the caller-saved registers automatically.

CoreArchitecturePipelineMain additionsExample parts
Cortex-M0ARMv6-M3-stageSmallest 32-bit core, reduced Thumb instruction setNXP LPC1114, STM32F030
Cortex-M0+ARMv6-M2-stageLower power, optional single-cycle I/O port and MPURaspberry Pi RP2040, Microchip SAMD21
Cortex-M3ARMv7-M3-stageFull Thumb-2, hardware divide, optional bit-banding and MPUNXP LPC1768, STM32F103
Cortex-M4ARMv7E-M3-stageDSP and SIMD instructions, optional single-precision FPUTI TM4C123, STM32F407, NXP LPC4088
Cortex-M7ARMv7E-M6-stage, dual-issueInstruction and data caches, TCM, optional single or double precision FPUNXP i.MX RT1062, STM32F767, Microchip SAME70

Feature Differences That Affect Your Code

The table below lists the differences that most often change how you write or optimise firmware. Optional features depend on the chip vendor, so always confirm them in the device datasheet.

FeatureM0 / M0+M3M4M7
Hardware integer divideNo (library call)Yes (SDIV, UDIV)YesYes
External interrupts (NVIC)Up to 32Up to 240Up to 240Up to 240
Priority bits2 (4 levels)3 to 8, vendor choice3 to 83 to 8
Saturation (SSAT, USAT)NoYesYesYes
DSP and SIMD instructionsNoNoYesYes
Floating point unitNoNoOptional single precisionOptional single or double
Unaligned word accessNo, causes HardFaultYes, for LDR and STRYesYes
Bit-bandingNoOptionalOptionalNo
Separate fault handlersHardFault onlyMemManage, BusFault, UsageFaultSame as M3Same as M3

Two rows deserve attention. Without hardware divide, every / and % on an M0 becomes a call to a run-time library routine such as __aeabi_uidiv, which takes many cycles. Without an FPU, every float multiply becomes a call such as __aeabi_fmul. On an M4 with its FPU enabled, the same multiply is a single VMUL.F32 instruction. If your application does filtering, motor control or audio, this difference alone can decide the part.

Seeing the Differences in Code

The GNU Arm compiler (arm-none-eabi-gcc) defines predefined macros for the target architecture and its features. The file below uses them to report the architecture and to choose the fastest way to clamp a value. Compile it once for each core and compare the disassembly with arm-none-eabi-objdump -d.

core_info.c: architecture detection and feature-dependent code
/* core_info.c
 * Build examples:
 *   arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -O2 -c core_info.c
 *   arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -O2 -c core_info.c
 *   arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -O2 -c core_info.c
 */
#include <stdint.h>

/* Report which architecture the code was compiled for */
const char *arch_name(void)
{
#if defined(__ARM_ARCH_7EM__)
    return "ARMv7E-M (Cortex-M4 or M7)";
#elif defined(__ARM_ARCH_7M__)
    return "ARMv7-M (Cortex-M3)";
#elif defined(__ARM_ARCH_6M__)
    return "ARMv6-M (Cortex-M0 or M0+)";
#else
    return "unknown";
#endif
}

/* Clamp a 32-bit value to the signed 16-bit range */
int16_t clamp16(int32_t x)
{
#if defined(__ARM_FEATURE_SAT)
    int32_t r;
    /* One SSAT instruction on M3, M4 and M7 */
    __asm volatile ("ssat %0, #16, %1" : "=r" (r) : "r" (x));
    return (int16_t)r;
#else
    /* Compare and branch on M0 and M0+ */
    if (x > 32767)  return 32767;
    if (x < -32768) return -32768;
    return (int16_t)x;
#endif
}

/* Average of n samples: UDIV on M3/M4/M7, __aeabi_uidiv call on M0 */
uint32_t average(const uint32_t *buf, uint32_t n)
{
    uint32_t sum = 0;
    for (uint32_t i = 0; i < n; i++)
        sum += buf[i];
    return n ? sum / n : 0;
}

/* Convert a 12-bit ADC count to volts (3.3 V reference).
 * VMUL.F32 with an FPU, __aeabi_fmul call without one. */
float adc_to_volts(uint32_t count)
{
    return (float)count * (3.3f / 4095.0f);
}

When you build for cortex-m0, objdump shows bl __aeabi_uidiv inside average and a short compare sequence inside clamp16. For cortex-m3 the divide becomes a single udiv and the clamp becomes ssat. For cortex-m4 with -mfloat-abi=hard, adc_to_volts uses vcvt and vmul on the FPU registers instead of calling soft-float routines.

CoreTypical gcc flags
Cortex-M0 / M0+-mcpu=cortex-m0 or -mcpu=cortex-m0plus -mthumb
Cortex-M3-mcpu=cortex-m3 -mthumb
Cortex-M4F-mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16
Cortex-M7 (double precision FPU)-mcpu=cortex-m7 -mthumb -mfloat-abi=hard -mfpu=fpv5-d16

Where ARM7 and the Newer Cores Fit

Many Indian university syllabi still teach the LPC2148, which uses the older ARM7TDMI-S core (ARMv4T architecture). ARM7 has two instruction sets (32-bit ARM and 16-bit Thumb), separate processor modes such as IRQ, FIQ and Supervisor with banked registers, and a vectored interrupt controller (VIC) that is a vendor peripheral rather than part of the core. Interrupt handlers need special compiler keywords like __irq in Keil. Cortex-M removed most of this complexity, which is why new designs use Cortex-M.

Newer cores follow the ARMv8-M architecture. The Cortex-M23 is the ARMv8-M Baseline successor to the M0+, and the Cortex-M33 is the Mainline successor to the M4; both can include TrustZone security extensions. The Cortex-M55 and M85 add the Helium vector extension for machine learning and signal processing. The programming model you learn on an M3 or M4 carries over to all of them.

How to Choose a Core for a Project

  • Battery sensor node, simple control, lowest cost: Cortex-M0+ parts such as SAMD21 or STM32L0.
  • General-purpose learning board with plenty of peripherals: Cortex-M3 parts such as the LPC1768.
  • Digital filters, motor control with float maths, audio: Cortex-M4F parts such as TM4C123 or STM32F407.
  • Graphics, high-speed data processing, large code size: Cortex-M7 parts such as i.MX RT1062.
  • Security features like secure boot and isolated key storage: Cortex-M33 parts with TrustZone.

Common Mistakes

  • Building M4F code with -mfloat-abi=soft and wondering why float maths is slow; the FPU is never used.
  • Enabling the FPU instructions but forgetting to turn on the FPU in the CPACR register at startup, which causes a UsageFault (CMSIS SystemInit usually does this).
  • Casting a byte buffer to a uint32_t pointer on an M0; the unaligned access triggers a HardFault.
  • Assuming bit-banding exists on every core; it is absent on M0, M0+ and M7.

Summary

Cortex-M0 and M0+ implement the compact ARMv6-M architecture with no divide, no FPU and at most 32 interrupts. The M3 brings full Thumb-2 and hardware divide, the M4 adds DSP instructions and an optional single-precision FPU, and the M7 adds a longer dual-issue pipeline, caches and an optional double-precision FPU. Check the core first when you pick a chip, set the matching compiler flags, and verify optional features in the vendor datasheet.