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.
| Core | Architecture | Pipeline | Main additions | Example parts |
|---|---|---|---|---|
| Cortex-M0 | ARMv6-M | 3-stage | Smallest 32-bit core, reduced Thumb instruction set | NXP LPC1114, STM32F030 |
| Cortex-M0+ | ARMv6-M | 2-stage | Lower power, optional single-cycle I/O port and MPU | Raspberry Pi RP2040, Microchip SAMD21 |
| Cortex-M3 | ARMv7-M | 3-stage | Full Thumb-2, hardware divide, optional bit-banding and MPU | NXP LPC1768, STM32F103 |
| Cortex-M4 | ARMv7E-M | 3-stage | DSP and SIMD instructions, optional single-precision FPU | TI TM4C123, STM32F407, NXP LPC4088 |
| Cortex-M7 | ARMv7E-M | 6-stage, dual-issue | Instruction and data caches, TCM, optional single or double precision FPU | NXP 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.
| Feature | M0 / M0+ | M3 | M4 | M7 |
|---|---|---|---|---|
| Hardware integer divide | No (library call) | Yes (SDIV, UDIV) | Yes | Yes |
| External interrupts (NVIC) | Up to 32 | Up to 240 | Up to 240 | Up to 240 |
| Priority bits | 2 (4 levels) | 3 to 8, vendor choice | 3 to 8 | 3 to 8 |
| Saturation (SSAT, USAT) | No | Yes | Yes | Yes |
| DSP and SIMD instructions | No | No | Yes | Yes |
| Floating point unit | No | No | Optional single precision | Optional single or double |
| Unaligned word access | No, causes HardFault | Yes, for LDR and STR | Yes | Yes |
| Bit-banding | No | Optional | Optional | No |
| Separate fault handlers | HardFault only | MemManage, BusFault, UsageFault | Same as M3 | Same 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
* 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.
| Core | Typical 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.