嵌入式 C 语言程序性能优化

1 嵌入式 C 语言程序性能优化

嵌入式系统的资源极度受限:几十 KB 的 Flash、几 KB 的 RAM、几十 MHz 的 CPU、严苛的实时要求。 本文系统梳理嵌入式 C 程序的性能优化技术,从编译器行为、内存模型、指令级优化到架构设计, 帮你建立完整的优化思维体系,真正掌握"让代码在寸土寸金的硬件上跑得又快又省"的核心能力。


1.1 优化思维与方法论

1.1.1 优化的黄金铁律

"不要过早优化"(Premature optimization is the root of all evil)------Knuth 但在嵌入式领域,你必须从一开始就理解硬件限制。

正确的优化流程:

复制代码
第一步:让代码正确运行(Correctness First)
第二步:测量,找到真正的瓶颈(Measure, Don't Guess)
第三步:针对性优化热点(Optimize the Hot Path)
第四步:验证优化效果(Benchmark After)
第五步:确保功能未被破坏(Regression Test)

1.1.2 嵌入式优化的三个维度

复制代码
┌─────────────────────────────────────────────────────────┐
│                   嵌入式优化三维度                        │
│                                                         │
│   执行速度         内存占用          功耗消耗             │
│   (Speed)         (Memory)         (Power)              │
│                                                         │
│   ↗ 提高 CPU 效率  ↗ 减少 RAM 使用  ↗ 降低活跃电流       │
│   ↗ 减少指令数     ↗ 压缩 Flash 占用 ↗ 延长休眠时间       │
│   ↗ 利用 DMA/硬件  ↗ 避免内存碎片   ↗ 关闭不用外设        │
│                                                         │
│   ⚠️  三者往往互相制约,需要根据业务优先级取舍             │
└─────────────────────────────────────────────────────────┘

1.1.3 性能分析工具链

复制代码
┌─────────────────────────────────────────────────────────┐
│  工具类型          工具名称              适用场景          │
│──────────────────────────────────────────────────────── │
│  编译器分析    GCC -fstack-usage       栈使用分析         │
│              GCC -S (汇编输出)        查看生成汇编        │
│              arm-none-eabi-objdump    反汇编分析          │
│              arm-none-eabi-size       段大小统计          │
│                                                         │
│  硬件调试     J-Link RTT Viewer       实时日志            │
│              ITM/SWO Trace            PC 采样分析         │
│              Logic Analyzer           时序分析            │
│              Oscilloscope             执行时间测量         │
│                                                         │
│  软件工具     Percepio Tracealyzer    RTOS 任务分析        │
│              gprof (仿真环境)          函数级耗时           │
│              Keil uVision Analyzer    指令级 Profile       │
└─────────────────────────────────────────────────────────┘

1.2 编译器优化技术

1.2.1 优化级别详解

bash 复制代码
# GCC 优化级别
-O0   # 不优化(默认),调试最友好,代码最慢
-O1   # 基础优化,减少代码大小和执行时间,不影响调试
-O2   # 标准优化(推荐生产),包含大多数安全优化
-O3   # 激进优化,可能增大代码体积,适合计算密集型
-Os   # 优化代码大小(Flash 紧张时首选)
-Oz   # 最小化代码大小(Clang 支持,比 -Os 更激进)
-Og   # 优化调试体验(GCC 4.8+,调试阶段的最优选择)

各级别优化效果对比(Cortex-M4,矩阵乘法):

复制代码
优化级别  执行时间   代码大小   调试体验
  -O0     100%      100%      最好
  -O1      65%       80%      较好
  -O2      42%       75%      一般
  -O3      38%       85%      较差(内联膨胀)
  -Os      55%       68%      一般
makefile 复制代码
# 开启全程序优化(跨编译单元内联)
CFLAGS  += -flto
LDFLAGS += -flto

# 效果:函数调用跨文件内联,去除未使用代码
# 典型收益:代码体积减少 10~20%,速度提升 5~15%
c 复制代码
/* 没有 LTO:编译单元隔离,无法跨文件内联 */
// file_a.c
int add(int a, int b) { return a + b; }

// file_b.c  
extern int add(int a, int b);
int result = add(3, 4);  // ← 产生函数调用指令

/* 有 LTO:链接期内联 */
int result = 7;  // ← 编译期直接计算!

1.2.3 关键编译选项

makefile 复制代码
# ARM Cortex-M 典型优化配置
CFLAGS += -mcpu=cortex-m4          # 指定目标 CPU
CFLAGS += -mfpu=fpv4-sp-d16        # 启用硬件浮点单元
CFLAGS += -mfloat-abi=hard         # 使用硬件浮点 ABI(而非软浮点)
CFLAGS += -mthumb                  # Thumb-2 指令集(16/32 位混合,更紧凑)
CFLAGS += -ffunction-sections      # 每个函数放独立 section
CFLAGS += -fdata-sections          # 每个变量放独立 section
LDFLAGS += -Wl,--gc-sections       # 链接时删除未使用的 section(Dead Code Elimination)
CFLAGS += -fno-common              # 禁止公共符号(更好的 DCE)

# 软浮点 vs 硬浮点 性能对比(Cortex-M4F)
# 软浮点:一次 float 乘法 ≈ 20~100 条指令
# 硬浮点:一次 float 乘法 ≈ 1 条指令(VMUL)

1.2.4 理解编译器生成的汇编

c 复制代码
/* 原始 C 代码 */
uint32_t calc(uint32_t x) {
    return x * 3 + x / 4;
}
asm 复制代码
; -O0 生成(未优化,慢)
calc:
    push    {r7, lr}
    sub     sp, sp, #8
    str     r0, [r7, #4]
    ldr     r3, [r7, #4]
    mov     r2, r3
    add     r2, r2, r2, lsl #1   ; x*3
    ldr     r3, [r7, #4]
    lsr     r3, r3, #2           ; x/4(移位代替除法)
    add     r3, r2, r3
    mov     r0, r3
    pop     {r7, pc}

; -O2 生成(优化后)
calc:
    add     r3, r0, r0, lsl #1   ; r3 = x*3(单指令)
    add     r0, r3, r0, lsr #2   ; r0 = x*3 + x/4(单指令)
    bx      lr                   ; 直接返回,无栈操作!

💡 经验 :养成查看关键函数汇编的习惯,arm-none-eabi-objdump -d firmware.elf 或在 Compiler Explorer (godbolt.org) 实时查看。


1.3 数据类型与内存布局优化

1.3.1 选择正确的数据类型

c 复制代码
/* ❌ 错误示范:在 32 位 MCU 上使用 char/short 做循环变量 */
void bad_loop(void) {
    uint8_t i;
    for (i = 0; i < 100; i++) {
        /* 
         * 问题:32 位 CPU 寄存器是 32 位的
         * 每次操作 uint8_t 需要额外的位掩码指令:
         * AND r0, r0, #0xFF  ← 强制截断到 8 位,多余指令!
         */
    }
}

/* ✅ 正确做法:循环变量用 uint_fast8_t 或直接用 uint32_t */
void good_loop(void) {
    uint_fast8_t i;   /* 编译器选择最快的能容纳 8 位值的类型 */
    for (i = 0; i < 100; i++) {
        /* 在 32 位 CPU 上,uint_fast8_t 通常是 uint32_t */
    }
}

类型选择指南:

复制代码
用途                    推荐类型              原因
─────────────────────────────────────────────────────────
循环计数器(小范围)    uint_fast8_t/16/32_t  CPU 原生宽度最快
存储/传输的精确大小     uint8_t/16/32/64_t    精确控制字节数
布尔值                 bool / uint_fast8_t   避免 _Bool 的隐式转换
硬件寄存器映射          volatile uint32_t     精确宽度 + 防优化
数组索引               size_t / uint32_t     足够大,避免符号扩展
时间戳/计数器           uint32_t / uint64_t   根据最大值选择
浮点运算               float(有 FPU 时)    double 在无 FPU 时极慢

1.3.2 结构体对齐与填充

c 复制代码
/* ❌ 糟糕的结构体布局 ------ 浪费 5 字节 */
typedef struct {
    uint8_t  flag;    /* 1 字节 */
                      /* [3 字节填充] */
    uint32_t value;   /* 4 字节,需要 4 字节对齐 */
    uint8_t  status;  /* 1 字节 */
                      /* [1 字节填充] */
    uint16_t count;   /* 2 字节,需要 2 字节对齐 */
} BadStruct;          /* 总计:12 字节(实际数据 8 字节)*/

/* ✅ 优化布局:按大小从大到小排列 */
typedef struct {
    uint32_t value;   /* 4 字节 */
    uint16_t count;   /* 2 字节 */
    uint8_t  flag;    /* 1 字节 */
    uint8_t  status;  /* 1 字节 */
} GoodStruct;         /* 总计:8 字节(零填充!)*/

/* 验证结构体大小 */
_Static_assert(sizeof(GoodStruct) == 8, "结构体大小异常,检查对齐");

对齐规则可视化:

复制代码
BadStruct 内存布局:
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│flag│PAD │PAD │PAD │   value(4B)   │stat│PAD │ count(2B) │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
  0    1    2    3    4    5    6    7    8    9   10   11

GoodStruct 内存布局:
┌────┬────┬────┬────┬────┬────┬────┬────┐
│      value(4B)    │ count │flag│stat│
└────┴────┴────┴────┴────┴────┴────┴────┘
  0    1    2    3    4    5    6    7

1.3.3 位域的正确使用

c 复制代码
/* 用于硬件寄存器映射(节省 RAM,但访问需要移位操作) */
typedef union {
    uint32_t raw;
    struct {
        uint32_t enable    : 1;   /* bit 0 */
        uint32_t direction : 2;   /* bit 1-2 */
        uint32_t speed     : 3;   /* bit 3-5 */
        uint32_t reserved  : 26;  /* bit 6-31 */
    } bits;
} GpioConfig;

/* 使用 */
GpioConfig gpio = {.raw = 0};
gpio.bits.enable    = 1;
gpio.bits.direction = 2;
gpio.bits.speed     = 7;

/* ⚠️ 位域注意事项:
   1. 跨编译器/平台的位序(bit order)不保证相同
   2. 不要用位域做跨机器通信的协议字段
   3. 位域访问可能产生 read-modify-write,在并发场景需要保护 */

1.4 指针与内存访问优化

1.4.1 restrict 关键字:消除指针别名

c 复制代码
/*
 * 问题:编译器不知道 dst 和 src 是否指向同一块内存(指针别名)
 * 因此每次循环都要重新从内存读 *src,无法优化为寄存器缓存
 */
void memcpy_slow(uint8_t *dst, const uint8_t *src, size_t n) {
    for (size_t i = 0; i < n; i++) {
        dst[i] = src[i];  /* 编译器担心 dst==src,不敢缓存 */
    }
}

/*
 * ✅ 使用 restrict 告诉编译器:dst 和 src 不重叠
 * 编译器可以使用 SIMD/LDM/STM 批量指令优化
 */
void memcpy_fast(uint8_t * restrict dst,
                 const uint8_t * restrict src, size_t n) {
    for (size_t i = 0; i < n; i++) {
        dst[i] = src[i];  /* 编译器可以激进优化 */
    }
}

/* 在数学运算中的巨大收益 */
void vector_add(float * restrict result,
                const float * restrict a,
                const float * restrict b,
                uint32_t len) {
    for (uint32_t i = 0; i < len; i++) {
        result[i] = a[i] + b[i];
        /* 有 restrict:编译器使用 VLDM/VSTM,批量浮点操作 */
        /* 无 restrict:每次循环单独 LDR/VLDR,慢 4~8 倍 */
    }
}

1.4.2 对齐访问 vs 非对齐访问

c 复制代码
/* 
 * ARM Cortex-M0/M0+ 不支持非对齐访问(直接硬件 fault)
 * Cortex-M3/M4 支持但非对齐访问比对齐慢 2~4 倍
 */

/* ❌ 危险:从任意地址读取 uint32_t(可能非对齐) */
uint32_t read_u32_bad(uint8_t *ptr) {
    return *(uint32_t *)ptr;  /* 如果 ptr 不是 4 字节对齐 → FAULT */
}

/* ✅ 安全:使用 memcpy 或 __packed 处理非对齐数据 */
uint32_t read_u32_safe(uint8_t *ptr) {
    uint32_t val;
    memcpy(&val, ptr, sizeof(val));  /* 编译器生成安全的字节操作 */
    return val;
}

/* ✅ 指定对齐属性 */
uint32_t buffer[256] __attribute__((aligned(32)));  /* 32 字节对齐(DMA 要求) */

/* ✅ 打包结构体(用于协议解析,牺牲速度换紧凑) */
typedef struct __attribute__((packed)) {
    uint8_t  header;
    uint32_t payload;   /* 不对齐,但 packed 确保不插入填充 */
    uint16_t checksum;
} ProtocolFrame;  /* 7 字节,无填充 */

1.4.3 局部性原理与数组访问

c 复制代码
/* 二维数组:C 语言行主序(Row-Major Order) */

/* ❌ 列访问:Cache Miss 极多,对 64KB Cache 的 MCU 慢 10 倍 */
void column_access(int mat[N][N]) {
    int sum = 0;
    for (int j = 0; j < N; j++)      /* 列优先 */
        for (int i = 0; i < N; i++)
            sum += mat[i][j];         /* 跳跃访问,破坏空间局部性 */
}

/* ✅ 行访问:顺序访问,Cache 友好,预取有效 */
void row_access(int mat[N][N]) {
    int sum = 0;
    for (int i = 0; i < N; i++)      /* 行优先 */
        for (int j = 0; j < N; j++)
            sum += mat[i][j];         /* 连续地址,Cache 命中率高 */
}

/*
 * 内存访问模式可视化(4x4 矩阵):
 *
 * 行访问(好):→→→→ →→→→ →→→→ →→→→  (连续)
 * 列访问(差):↓   ↓   ↓   ↓
 *               ↓   ↓   ↓   ↓           (跳跃)
 *               ↓   ↓   ↓   ↓
 *               ↓   ↓   ↓   ↓
 */

1.5 循环优化技术

1.5.1 循环展开(Loop Unrolling)

c 复制代码
/* 原始循环:每次迭代都有循环开销(比较、跳转、计数) */
void sum_original(const int32_t *arr, int32_t *result, uint32_t n) {
    int32_t sum = 0;
    for (uint32_t i = 0; i < n; i++) {
        sum += arr[i];
    }
    *result = sum;
}

/* 手动展开 4 倍:减少 75% 的循环控制开销,允许编译器流水线调度 */
void sum_unrolled(const int32_t *arr, int32_t *result, uint32_t n) {
    int32_t sum0 = 0, sum1 = 0, sum2 = 0, sum3 = 0;
    uint32_t i = 0;
    
    /* 主循环:每次处理 4 个元素 */
    for (; i + 3 < n; i += 4) {
        sum0 += arr[i];
        sum1 += arr[i + 1];
        sum2 += arr[i + 2];
        sum3 += arr[i + 3];
    }
    
    /* 尾部处理:处理剩余元素 */
    for (; i < n; i++) {
        sum0 += arr[i];
    }
    
    *result = sum0 + sum1 + sum2 + sum3;
}

/* 或者使用 GCC 编译器属性自动展开 */
void sum_auto_unroll(const int32_t *arr, int32_t *result, uint32_t n) {
    int32_t sum = 0;
    #pragma GCC unroll 4
    for (uint32_t i = 0; i < n; i++) {
        sum += arr[i];
    }
    *result = sum;
}

1.5.2 Duff's Device(经典展开技巧)

c 复制代码
/*
 * Duff's Device:结合 switch-case 和循环,处理任意长度数据的展开
 * 经典技巧,适用于 memcpy 类操作
 */
void duff_copy(char *dst, const char *src, int count) {
    int n = (count + 7) / 8;  /* 向上取整到 8 的倍数 */
    
    switch (count % 8) {      /* 计算余数,跳到对应位置 */
        case 0: do { *dst++ = *src++;  /* fall through */
        case 7:      *dst++ = *src++;
        case 6:      *dst++ = *src++;
        case 5:      *dst++ = *src++;
        case 4:      *dst++ = *src++;
        case 3:      *dst++ = *src++;
        case 2:      *dst++ = *src++;
        case 1:      *dst++ = *src++;
                } while (--n > 0);
    }
}
/* 注:现代 MCU 上优先使用 memcpy(编译器会生成 LDM/STM 多字传输) */

1.5.3 循环不变量外提

c 复制代码
/* ❌ 低效:每次循环都调用 strlen(O(n²) 复杂度!) */
void bad_loop(char *str, char *buf) {
    for (int i = 0; i < strlen(str); i++) {  /* strlen 每次都扫描! */
        buf[i] = str[i] | 0x20;  /* 转小写 */
    }
}

/* ✅ 优化:将不变量提取到循环外 */
void good_loop(char *str, char *buf) {
    int len = strlen(str);        /* 只计算一次! */
    for (int i = 0; i < len; i++) {
        buf[i] = str[i] | 0x20;
    }
    buf[len] = '\0';
}

/* ❌ 另一个常见问题:循环内部重复计算 */
void bad_calc(float *arr, uint32_t n, float freq, float time) {
    for (uint32_t i = 0; i < n; i++) {
        arr[i] = sinf(2.0f * 3.14159f * freq * time * i); /* 每次都算前缀 */
    }
}

/* ✅ 预计算常量部分 */
void good_calc(float *arr, uint32_t n, float freq, float time) {
    float phase_step = 2.0f * 3.14159f * freq * time;  /* 提出循环 */
    for (uint32_t i = 0; i < n; i++) {
        arr[i] = sinf(phase_step * i);
    }
}

1.5.4 查表法替代复杂计算

c 复制代码
/* 三角函数/对数等超越函数在无 FPU 的 MCU 上极慢 */

/* ❌ 实时计算 sin(软浮点:≈ 100~200 条指令) */
float get_sin_slow(uint16_t angle_deg) {
    return sinf(angle_deg * 3.14159f / 180.0f);
}

/* ✅ 查表法:1 次数组访问(≈ 2~4 条指令) */
/* 预先计算并存储在 Flash 中 */
static const int16_t SIN_TABLE[360] = {
    0,      175,    349,    523,    698,    /* 0°~4°,Q15 格式(×32767) */
    /* ... 完整 360 个值 ... */
    -175,   /* 359° */
};

int16_t get_sin_fast(uint16_t angle_deg) {
    return SIN_TABLE[angle_deg % 360];  /* 直接查表 */
}

/* 更精细:双线性插值查表,以更小的表获得更高精度 */
int16_t get_sin_interpolated(float angle_deg) {
    uint16_t idx = (uint16_t)angle_deg % 360;
    float frac = angle_deg - (uint16_t)angle_deg;
    return (int16_t)(SIN_TABLE[idx] * (1.0f - frac) + 
                     SIN_TABLE[(idx + 1) % 360] * frac);
}

/* CRC 查表法(另一个经典场景) */
static const uint8_t CRC8_TABLE[256] = { /* 预计算 */ };

uint8_t crc8_fast(const uint8_t *data, uint32_t len) {
    uint8_t crc = 0xFF;
    while (len--) {
        crc = CRC8_TABLE[crc ^ *data++];  /* 1次查表 vs 8次位运算 */
    }
    return crc;
}

1.6 函数调用优化

1.6.1 inline 函数

c 复制代码
/*
 * 函数调用开销分析(ARM Cortex-M):
 * - PUSH/POP 寄存器:2~10 条指令
 * - 参数传递(超过 4 个需要压栈):额外开销
 * - 分支预测失败:3~5 周期损失
 * 对于短小函数,inline 消除所有这些开销
 */

/* static inline:编译器通常会内联(优先于 #define) */
static inline uint32_t max_u32(uint32_t a, uint32_t b) {
    return (a > b) ? a : b;
}

/* __attribute__((always_inline)):强制内联(无论优化级别) */
static __attribute__((always_inline)) inline 
uint16_t byte_swap_16(uint16_t val) {
    return (val << 8) | (val >> 8);
}

/* __attribute__((noinline)):禁止内联(用于调试或减小代码体积) */
__attribute__((noinline)) 
void debug_log(const char *msg) {
    /* 调试函数:不内联以节省 Flash */
    uart_send_string(msg);
}

/* ⚠️ 注意:过度内联会导致代码膨胀(I-Cache 压力增大),适得其反 */
/* 规则:只对 ≤ 10 行、调用频繁的函数使用 always_inline */

1.6.2 函数参数优化

c 复制代码
/*
 * ARM AAPCS 调用约定:
 * - r0~r3:前 4 个参数(通过寄存器传递,免费)
 * - r4 以后:通过栈传递(有开销)
 * - 返回值:r0(32位)或 r0+r1(64位)
 */

/* ❌ 参数过多,需要压栈 */
int bad_function(int a, int b, int c, int d, int e, int f) {
    /* e, f 通过栈传递,有 PUSH/POP 开销 */
    return a + b + c + d + e + f;
}

/* ✅ 超过 4 个参数时,用结构体指针打包 */
typedef struct {
    int a, b, c, d, e, f;
} Params;

int good_function(const Params *p) {
    /* 只有 1 个指针参数(寄存器传递),结构体在调用方栈上 */
    return p->a + p->b + p->c + p->d + p->e + p->f;
}

/* ✅ 小结构体(≤ 4 字节)可以直接按值传递 */
typedef struct { int16_t x; int16_t y; } Point;

Point move_point(Point p, int16_t dx, int16_t dy) {
    /* Point 打包进单个寄存器,无需指针间接访问 */
    return (Point){p.x + dx, p.y + dy};
}

1.6.3 尾调用优化(Tail Call Optimization)

c 复制代码
/* 尾调用:函数的最后一个操作是调用另一个函数,可优化为跳转(节省栈帧) */

/* ❌ 非尾调用:调用后还有操作 */
int factorial_bad(int n) {
    if (n <= 1) return 1;
    return n * factorial_bad(n - 1);  /* 调用后还要乘,不是尾调用 */
    /* 每次递归都需要保存 n 在栈上,n=100 需要 100 个栈帧 → 栈溢出! */
}

/* ✅ 尾递归版本(开-O2 编译器自动转为循环) */
int factorial_tail(int n, int acc) {
    if (n <= 1) return acc;
    return factorial_tail(n - 1, n * acc);  /* 尾调用,编译器优化为 B 指令 */
}
/* 调用:factorial_tail(10, 1) */

/* 对于嵌入式,通常直接改成循环更保险 */
int factorial_loop(int n) {
    int result = 1;
    while (n > 1) result *= n--;
    return result;
}

1.7 位操作与算术优化

1.7.1 用移位替代乘除法

c 复制代码
/*
 * ARM 没有硬件整数除法指令(Cortex-M0/M3)
 * 或除法较慢(Cortex-M4 的 SDIV 指令需要 2~12 周期)
 * 编译器会自动将"除以常数"转换为乘法+移位(magic number 技术)
 * 但对变量除法,必须手动优化
 */

/* 2 的幂次方:直接用移位 */
static inline uint32_t div_by_8(uint32_t x)  { return x >> 3; }
static inline uint32_t mul_by_4(uint32_t x)  { return x << 2; }
static inline uint32_t mod_by_16(uint32_t x) { return x & 15; } /* x % 16 */

/* 非 2 的幂次方:使用 Barrett Reduction 技术 */
/* 除以 10(嵌入式常用,如电压换算)*/
static inline uint32_t div10(uint32_t n) {
    /* (n * 0xCCCCCCCD) >> 35 等价于 n / 10,编译器通常自动生成 */
    return (uint32_t)(((uint64_t)n * 0xCCCCCCCDULL) >> 35);
}

/* 实际对比(Cortex-M4,1000 次操作):
   n / 10(UDIV):  2~12 周期/次
   div10():         3 周期/次(UMULL + 移位)
*/

/* 乘以 3 的快速实现 */
static inline uint32_t mul3(uint32_t x) { return x + (x << 1); } /* x + 2x */
static inline uint32_t mul5(uint32_t x) { return x + (x << 2); } /* x + 4x */
static inline uint32_t mul6(uint32_t x) { return (x << 1) + (x << 2); }

1.7.2 位操作技巧大全

c 复制代码
/* ==================== 标志位操作 ==================== */
#define BIT(n)           (1UL << (n))
#define SET_BIT(x, n)    ((x) |=  BIT(n))
#define CLR_BIT(x, n)    ((x) &= ~BIT(n))
#define TOG_BIT(x, n)    ((x) ^=  BIT(n))
#define TST_BIT(x, n)    (((x) >> (n)) & 1U)

/* ==================== 区间操作 ==================== */
#define MASK(hi, lo)       (((1UL << ((hi)-(lo)+1)) - 1) << (lo))
#define GET_FIELD(x,hi,lo) (((x) & MASK(hi,lo)) >> (lo))
#define SET_FIELD(x,hi,lo,v) \
    ((x) = ((x) & ~MASK(hi,lo)) | (((v) << (lo)) & MASK(hi,lo)))

/* 使用示例:从 32 位寄存器读取 bit[11:8] */
uint32_t reg_val = 0xABC0;
uint32_t field = GET_FIELD(reg_val, 11, 8);  /* = 0xB */

/* ==================== 实用技巧 ==================== */

/* 判断是否为 2 的幂次方(O(1)) */
static inline bool is_power_of_2(uint32_t x) {
    return x != 0 && (x & (x - 1)) == 0;
}

/* 向上对齐到 2 的幂次方(常用于内存分配) */
static inline uint32_t align_up(uint32_t x, uint32_t align) {
    /* align 必须是 2 的幂次方 */
    return (x + align - 1) & ~(align - 1);
}
/* align_up(13, 4) = 16, align_up(16, 4) = 16 */

/* 计算前导零数(可用于快速 log2) */
static inline uint32_t count_leading_zeros(uint32_t x) {
    return __builtin_clz(x);  /* ARM: CLZ 指令,单周期 */
}

static inline uint32_t floor_log2(uint32_t x) {
    return 31 - __builtin_clz(x);  /* 等价于 (int)log2(x) */
}

/* 整数绝对值(无分支) */
static inline int32_t abs_no_branch(int32_t x) {
    int32_t mask = x >> 31;          /* 全 0 或全 1(算术右移) */
    return (x + mask) ^ mask;
}

/* 整数 min/max(可能避免分支预测失败) */
static inline int32_t min_no_branch(int32_t a, int32_t b) {
    return b + ((a - b) & ((a - b) >> 31));
}

/* 字节序转换 */
static inline uint32_t bswap32(uint32_t x) {
    return __builtin_bswap32(x);  /* ARM: REV 指令,单周期 */
}
static inline uint16_t bswap16(uint16_t x) {
    return __builtin_bswap16(x);  /* ARM: REV16 指令 */
}

1.7.3 定点数运算(Fixed-Point Arithmetic)

c 复制代码
/*
 * 在无 FPU 的 MCU(如 Cortex-M0)上,float 运算需要软件模拟
 * 一次浮点乘法 ≈ 20~60 条指令
 * 定点数用整数运算模拟小数,通常只需 1~3 条指令
 */

/* Q15 格式:1位符号 + 15位小数(范围 -1.0 ~ +0.9999...) */
#define Q15_SCALE     32768   /* 2^15 */
#define FLOAT_TO_Q15(x)  ((int16_t)((x) * Q15_SCALE))
#define Q15_TO_FLOAT(x)  ((float)(x) / Q15_SCALE)

/* Q15 乘法:结果需要右移 15 位 */
static inline int16_t q15_mul(int16_t a, int16_t b) {
    return (int16_t)(((int32_t)a * b) >> 15);
}

/* Q15 MAC(Multiply-Accumulate,FIR 滤波器核心) */
int16_t fir_filter(const int16_t *input, const int16_t *coeff, 
                   uint16_t len) {
    int32_t acc = 0;
    for (uint16_t i = 0; i < len; i++) {
        acc += (int32_t)input[i] * coeff[i];  /* 32位累加器防溢出 */
    }
    return (int16_t)(acc >> 15);  /* 转回 Q15 */
}

/* ARM Cortex-M DSP 扩展指令(SMULBB 等)会自动用于此类计算 */

/* Q16.16 格式:16位整数 + 16位小数(范围 -32768.0 ~ +32767.9999) */
typedef int32_t q16_t;
#define Q16_SCALE       65536   /* 2^16 */
#define FLOAT_TO_Q16(x) ((q16_t)((x) * Q16_SCALE))

static inline q16_t q16_mul(q16_t a, q16_t b) {
    return (q16_t)(((int64_t)a * b) >> 16);
}

1.8 Cache 优化

1.8.1 Cache 基础(有 Cache 的 MCU)

复制代码
典型嵌入式 Cache 配置(Cortex-M7):
┌─────────────────────────────────────────────────────────┐
│  I-Cache(指令缓存):16KB / 32KB                        │
│  D-Cache(数据缓存):16KB / 32KB                        │
│  Cache Line 大小:32 字节(8 个 uint32_t)               │
│                                                         │
│  访问延迟对比:                                          │
│    Cache Hit(命中):  1~2 周期                         │
│    Cache Miss(失效): 10~100 周期(取决于总线速度)      │
│    Flash(无 Cache):  3~10 周期(等待状态)             │
└─────────────────────────────────────────────────────────┘

1.8.2 代码 Cache 优化(I-Cache)

c 复制代码
/* 
 * 热路径代码:确保关键函数在同一 Cache Line(32字节)内
 * 避免在中断服务程序末尾跳转到冷代码
 */

/* ✅ 将频繁调用的函数放入 ITCM(Instruction Tightly Coupled Memory) */
/* ITCM 是 CPU 直连内存,0 等待周期,比 Cache 更快且确定性更强 */

__attribute__((section(".itcm_code")))
void critical_isr_handler(void) {
    /* 时间敏感的中断处理代码,放入 ITCM */
}

/* 链接脚本(.ld 文件)中定义 ITCM 段 */
/*
SECTIONS {
    .itcm_code : {
        *(.itcm_code)
    } > ITCM AT > FLASH
}
*/

1.8.3 数据 Cache 与 DMA 一致性

c 复制代码
/*
 * ⚠️ 最危险的 Cache 问题:DMA 传输时的 Cache 不一致性
 *
 * 场景:CPU 写数据到 buffer → D-Cache 中有新数据,RAM 中是旧数据
 *       DMA 从 RAM 读取数据 → 读到的是旧数据!(Cache 脏数据问题)
 */

/* DMA 发送前:必须将 Cache 写回到 RAM(Clean) */
SCB_CleanDCache_by_Addr((uint32_t *)tx_buf, sizeof(tx_buf));
HAL_SPI_Transmit_DMA(&hspi1, tx_buf, sizeof(tx_buf));

/* DMA 接收后:必须使 Cache 失效,强制从 RAM 重新读取(Invalidate) */
HAL_SPI_Receive_DMA(&hspi1, rx_buf, sizeof(rx_buf));
/* 等待 DMA 完成... */
SCB_InvalidateDCache_by_Addr((uint32_t *)rx_buf, sizeof(rx_buf));
/* 现在 CPU 读 rx_buf 才是 DMA 写入的新数据 */

/* ✅ 最佳实践:DMA Buffer 放在 Non-Cacheable 内存区域 */
__attribute__((section(".dma_buffer")))
uint8_t dma_rx_buf[256];  /* 在 MPU 中配置此区域为 Non-Cacheable */

1.8.4 MPU 配置优化 Cache 策略

c 复制代码
/* Cortex-M7 MPU 配置:为不同内存区域设置 Cache 策略 */
void mpu_configure(void) {
    ARM_MPU_Disable();
    
    /* Region 0:Flash → Cacheable(只读,不需要写回) */
    ARM_MPU_SetRegionEx(0,
        0x08000000,              /* 起始地址 */
        ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 3, 0, 1, 1, 0,
                     ARM_MPU_REGION_SIZE_1MB));  /* Write-Through */
    
    /* Region 1:SRAM(普通数据) → Write-Back, Write-Allocate(最快) */
    ARM_MPU_SetRegionEx(1,
        0x20000000,
        ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 3, 0, 1, 1, 0,
                     ARM_MPU_REGION_SIZE_512KB));
    
    /* Region 2:DMA Buffer → Non-Cacheable(保证一致性) */
    ARM_MPU_SetRegionEx(2,
        DMA_BUFFER_BASE,
        ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 3, 1, 0, 0, 0,
                     ARM_MPU_REGION_SIZE_4KB));  /* Strongly Ordered */
    
    ARM_MPU_Enable(MPU_CTRL_PRIVDEFENA_Msk);
}

1.9 中断与实时性优化

1.9.1 中断延迟的来源

复制代码
中断延迟 = 硬件响应时间 + 软件响应时间

硬件响应时间(Cortex-M):
  最短:12 周期(Tail-Chain 连续中断:6 周期)
  最长:12 + 最长不可中断指令(LDM/STM:可达 20+ 周期)

软件响应时间来源:
  1. 中断向量跳转:1~3 周期
  2. ISR 头部 PUSH 寄存器:N 周期
  3. 等待更高优先级中断处理完成
  4. Cache Miss(代码/数据不在 Cache 中)
  5. Flash 等待周期

1.9.2 ISR 最佳实践

c 复制代码
/* ✅ 最小化 ISR:只做最必要的操作 */

/* 错误示范:ISR 中做大量工作 */
void UART_IRQHandler_BAD(void) {
    char buf[256];
    uint16_t len = 0;
    
    while (uart_data_ready()) {
        buf[len++] = uart_read_byte();
    }
    
    /* ❌ ISR 中做协议解析(耗时!) */
    parse_protocol(buf, len);
    process_command(buf, len);
    send_response(buf, len);
}

/* ✅ 正确示范:ISR 只搬运数据,主循环处理业务 */

/* 环形缓冲区(无需动态内存,无需加锁在单核场景) */
#define RX_RING_SIZE 256
typedef struct {
    volatile uint8_t  buf[RX_RING_SIZE];
    volatile uint16_t head;
    volatile uint16_t tail;
} RingBuffer;

static RingBuffer uart_rx;
static volatile bool data_ready = false;

void UART_IRQHandler(void) {
    /* ISR:只做一件事------把数据放入环形缓冲区 */
    uint8_t byte = USART1->DR;  /* 读取硬件寄存器(同时清除中断标志) */
    
    uint16_t next = (uart_rx.head + 1) & (RX_RING_SIZE - 1);
    if (next != uart_rx.tail) {  /* 非满 */
        uart_rx.buf[uart_rx.head] = byte;
        uart_rx.head = next;
    }
    
    if (byte == '\n') data_ready = true;  /* 通知主循环 */
}

/* 主循环:处理业务逻辑 */
void main_loop(void) {
    if (data_ready) {
        data_ready = false;
        process_uart_data(&uart_rx);  /* 在主循环处理,不影响中断响应 */
    }
}

1.9.3 中断优先级分组策略

c 复制代码
/* ARM Cortex-M NVIC 优先级设置 */

/* 优先级规划示例(8个优先级,0最高) */
#define IRQ_PRIO_CRITICAL    0   /* 安全停机、过流保护 */
#define IRQ_PRIO_HIGH        2   /* 电机 PWM、编码器 */
#define IRQ_PRIO_NORMAL      4   /* 通信(UART、SPI) */
#define IRQ_PRIO_LOW         6   /* 用户输入、LED */
#define IRQ_PRIO_SYSTICK     7   /* SysTick(RTOS 时钟) */

/* 设置优先级分组(4位抢占,0位子优先级)*/
NVIC_SetPriorityGrouping(4);

/* 配置各中断优先级 */
NVIC_SetPriority(TIM1_UP_IRQn,  IRQ_PRIO_HIGH);
NVIC_SetPriority(USART1_IRQn,   IRQ_PRIO_NORMAL);
NVIC_SetPriority(SysTick_IRQn,  IRQ_PRIO_SYSTICK);

/* ⚠️ 关键:FreeRTOS API 只能在 configMAX_SYSCALL_INTERRUPT_PRIORITY
   以下优先级的 ISR 中调用(通常 configMAX_SYSCALL_INTERRUPT_PRIORITY=5)
   优先级 0~4 的 ISR 不能调用任何 FreeRTOS API! */

1.9.4 关中断临界区优化

c 复制代码
/* ❌ 错误:关中断时间过长 */
void bad_critical(void) {
    __disable_irq();          /* 关所有中断 */
    
    do_complex_calculation(); /* 耗时操作,期间中断全部丢失! */
    update_shared_data();
    send_uart_packet();       /* 更不应该在关中断期间做IO */
    
    __enable_irq();
}

/* ✅ 正确:最小化临界区 */
void good_critical(void) {
    /* 1. 在关中断之外做耗时准备工作 */
    uint32_t new_value = do_complex_calculation();
    
    /* 2. 只在修改共享数据时关中断(微秒级) */
    uint32_t primask = __get_PRIMASK();
    __disable_irq();
    shared_data = new_value;   /* 极短的临界区 */
    __set_PRIMASK(primask);    /* 恢复中断状态(而非直接 enable,保持嵌套正确性) */
    
    /* 3. 关中断之外做后续工作 */
    send_uart_packet();
}

/* ✅ 更精细:只屏蔽低于特定优先级的中断(保留高优先级中断) */
void basepri_critical(void) {
    uint32_t old_basepri = __get_BASEPRI();
    __set_BASEPRI(IRQ_PRIO_HIGH << (8 - __NVIC_PRIO_BITS));  /* 只屏蔽低优先级 */
    
    /* 临界区:高优先级中断仍可抢占 */
    shared_data = new_value;
    
    __set_BASEPRI(old_basepri);  /* 恢复 */
}

1.10 内存管理优化

1.10.1 静态分配优先

c 复制代码
/*
 * 嵌入式系统应尽量避免动态内存分配(malloc/free):
 * 1. 内存碎片:长期运行后可能无法分配连续内存
 * 2. 不确定性:分配时间不固定,破坏实时性
 * 3. 堆溢出:malloc 失败可能返回 NULL(需要检查)
 * 4. 线程安全:需要加锁,影响中断延迟
 */

/* ✅ 静态数组预分配 */
#define MAX_PACKETS   16
#define PACKET_SIZE   128

static uint8_t packet_pool[MAX_PACKETS][PACKET_SIZE]; /* 全局静态 */
static uint8_t packet_used[MAX_PACKETS] = {0};        /* 使用标记 */

uint8_t *alloc_packet(void) {
    for (int i = 0; i < MAX_PACKETS; i++) {
        if (!packet_used[i]) {
            packet_used[i] = 1;
            return packet_pool[i];
        }
    }
    return NULL;  /* 池满,返回失败(可预测!) */
}

void free_packet(uint8_t *pkt) {
    int idx = (pkt - packet_pool[0]) / PACKET_SIZE;
    if (idx >= 0 && idx < MAX_PACKETS) {
        packet_used[idx] = 0;
    }
}

1.10.2 内存池实现

c 复制代码
/* 固定大小内存池:O(1) 分配/释放,无碎片 */
typedef struct MemPoolBlock {
    struct MemPoolBlock *next;  /* 空闲链表指针(复用数据区存储) */
} MemPoolBlock;

typedef struct {
    MemPoolBlock *free_list;    /* 空闲块链表头 */
    uint8_t      *pool_buf;     /* 内存池缓冲区 */
    uint32_t      block_size;   /* 块大小 */
    uint32_t      block_count;  /* 总块数 */
} MemPool;

/* 初始化内存池 */
void mempool_init(MemPool *pool, void *buf, 
                  uint32_t block_size, uint32_t count) {
    pool->pool_buf    = (uint8_t *)buf;
    pool->block_size  = block_size;
    pool->block_count = count;
    pool->free_list   = NULL;
    
    /* 将所有块串成空闲链表 */
    for (uint32_t i = 0; i < count; i++) {
        MemPoolBlock *block = (MemPoolBlock *)(buf + i * block_size);
        block->next = pool->free_list;
        pool->free_list = block;
    }
}

/* O(1) 分配 */
void *mempool_alloc(MemPool *pool) {
    if (pool->free_list == NULL) return NULL;
    MemPoolBlock *block = pool->free_list;
    pool->free_list = block->next;
    return (void *)block;
}

/* O(1) 释放 */
void mempool_free(MemPool *pool, void *ptr) {
    MemPoolBlock *block = (MemPoolBlock *)ptr;
    block->next = pool->free_list;
    pool->free_list = block;
}

/* 使用示例 */
#define SENSOR_BLOCK_SIZE  sizeof(SensorData)
#define SENSOR_POOL_COUNT  8

static uint8_t sensor_buf[SENSOR_BLOCK_SIZE * SENSOR_POOL_COUNT];
static MemPool sensor_pool;

void init(void) {
    mempool_init(&sensor_pool, sensor_buf, SENSOR_BLOCK_SIZE, SENSOR_POOL_COUNT);
}

1.10.3 栈使用分析与防溢出

c 复制代码
/*
 * 栈溢出是嵌入式最危险的 Bug 之一:
 * - 覆盖相邻内存,导致随机崩溃
 * - 现象可能在溢出发生后很久才出现
 */

/* ✅ 栈着色法(Stack Painting):检测最大栈使用量 */
#define STACK_FILL_PATTERN  0xDEADBEEF
#define STACK_SIZE          1024  /* 字节 */

/* 在 main 最开始调用,用特定值填充栈 */
void stack_paint(void) {
    extern uint32_t _stack_start;  /* 链接脚本定义 */
    uint32_t *p = &_stack_start;
    while (p < (uint32_t *)__get_MSP()) {
        *p++ = STACK_FILL_PATTERN;
    }
}

/* 统计实际最大栈使用量 */
uint32_t stack_get_max_usage(void) {
    extern uint32_t _stack_start, _stack_end;
    uint32_t *p = &_stack_start;
    while (*p == STACK_FILL_PATTERN) p++;
    return ((uint32_t)&_stack_end - (uint32_t)p);
}

/* ✅ 硬件 MPU 栈保护:在栈底设置 MPU Guard Region */
void enable_stack_guard(void) {
    extern uint32_t _stack_start;
    ARM_MPU_SetRegionEx(7,           /* 最低优先级 Region */
        (uint32_t)&_stack_start,     /* 栈底 */
        ARM_MPU_RASR(1, /* 禁止访问 */ ARM_MPU_AP_NONE,
                     0, 0, 0, 0, 0, ARM_MPU_REGION_SIZE_32B));
    /* 访问 Guard Region 会触发 MemManage Fault,立即捕获溢出 */
}

/* 使用 GCC 编译选项生成栈使用报告 */
/* CFLAGS += -fstack-usage  → 生成 .su 文件,列出每个函数的栈使用 */

1.11 Flash 与 ROM 优化

1.11.1 代码体积优化策略

c 复制代码
/* 1. 消除重复代码(DRY 原则) */

/* ❌ 重复代码(浪费 Flash) */
void init_gpio_pa5(void) {
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
    GPIOA->MODER |= (1 << 10);
    GPIOA->OSPEEDR |= (3 << 10);
}
void init_gpio_pb3(void) {
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;
    GPIOB->MODER |= (1 << 6);
    GPIOB->OSPEEDR |= (3 << 6);
}

/* ✅ 参数化函数(复用代码) */
void init_gpio(GPIO_TypeDef *port, uint8_t pin, uint8_t mode) {
    /* 启用时钟 */
    uint32_t port_idx = ((uint32_t)port - GPIOA_BASE) / 0x400;
    RCC->AHB1ENR |= (1 << port_idx);
    
    port->MODER   = (port->MODER   & ~(3U << (pin*2))) | (mode << (pin*2));
    port->OSPEEDR = (port->OSPEEDR & ~(3U << (pin*2))) | (3U  << (pin*2));
}
makefile 复制代码
# 2. 编译器代码体积优化选项
CFLAGS += -Os                    # 优化代码大小
CFLAGS += -fno-unwind-tables     # 去除异常展开表(C++ 有用)
CFLAGS += -fno-asynchronous-unwind-tables
CFLAGS += -fomit-frame-pointer   # 省略帧指针(释放 r7/r11 寄存器)
CFLAGS += -fno-common            # 公共符号单独分配(更好 DCE)

# 3. 去除未使用代码(Dead Code Elimination)
CFLAGS  += -ffunction-sections -fdata-sections
LDFLAGS += -Wl,--gc-sections     # 链接时删除未引用的 section

# 效果验证
arm-none-eabi-size --format=sysv firmware.elf

1.11.2 常量数据存放在 Flash

c 复制代码
/*
 * 常量表放在 Flash(ROM)而非 RAM,节省宝贵的 RAM 空间
 * 注意:Flash 访问可能有等待周期,但对于查表可以接受
 */

/* ✅ const 修饰的全局数组:编译器放入 .rodata 段(Flash) */
const uint16_t ADC_CALIBRATION_TABLE[4096] = { /* 12位ADC校准数据 */ };
const float    TEMP_CURVE_TABLE[256] = { /* 温度曲线 */ };

/* ⚠️ 注意:const 局部变量可能放入 RAM(取决于编译器),
   最好使用 static const 确保放入 Flash */
void func(void) {
    static const uint8_t lut[16] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4};
    /* static 确保链接到 .rodata,而非每次调用都在栈上构建 */
}

/* 使用 PROGMEM(AVR)或 __attribute__((section(".rodata")))(ARM)强制 */
const uint8_t FONT_DATA[] __attribute__((section(".rodata"))) = { /* 字体数据 */ };

1.12 低功耗优化

1.12.1 电源模式管理

c 复制代码
/*
 * STM32 低功耗模式(以 STM32L4 为例):
 * 
 * Run Mode     → 全速运行,所有外设开启
 * Sleep Mode   → CPU 停止,外设继续运行(等待中断唤醒)
 * Stop 0/1/2   → 大部分时钟停止,RAM 保持,外设关闭
 * Standby      → 只有 RTC 和备份寄存器运行,唤醒后复位
 * Shutdown     → 最低功耗,类似断电
 */

/* 低功耗设计模式:事件驱动 + WFI */
void main_loop_low_power(void) {
    while (1) {
        /* 处理所有待处理事件 */
        process_pending_events();
        
        /* 没有待处理工作,进入 Sleep */
        if (!has_pending_work()) {
            /* 关闭不需要的外设 */
            disable_unused_peripherals();
            
            /* 等待中断(Wait For Interrupt)--- CPU 停止,外设继续 */
            __WFI();
            
            /* 中断发生后从这里继续 */
            enable_required_peripherals();
        }
    }
}

/* 深度睡眠(Stop Mode)*/
void enter_stop_mode(uint32_t sleep_ms) {
    /* 配置 RTC 唤醒定时器 */
    rtc_set_wakeup(sleep_ms);
    
    /* 关闭所有不必要的时钟 */
    HAL_RCC_DisableCSS();
    
    /* 进入 Stop 2 模式(最低功耗 Stop,RAM 保持) */
    HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);
    
    /* 从 Stop 唤醒后,时钟可能需要重新配置 */
    system_clock_restore();
}

1.12.2 外设时钟门控

c 复制代码
/* 动态关闭不使用的外设时钟(每个外设节省 μA ~ mA) */

/* 用完 ADC 立即关闭 */
void read_adc_low_power(void) {
    /* 开启 ADC 时钟 */
    __HAL_RCC_ADC_CLK_ENABLE();
    
    /* 等待 ADC 稳定(启动时间) */
    HAL_Delay(1);
    
    /* 读取数据 */
    uint16_t value = adc_read_channel(ADC_CHANNEL_1);
    
    /* 立即关闭 ADC 时钟 */
    __HAL_RCC_ADC_CLK_DISABLE();
    
    return value;
}

/* 批量处理:一次唤醒做多件事,减少唤醒次数 */
void sensor_task(void) {
    /* 唤醒一次,采集所有传感器数据 */
    float temp     = read_temperature();
    float humidity = read_humidity();
    float pressure = read_pressure();
    
    /* 一次性发送所有数据 */
    transmit_sensor_data(temp, humidity, pressure);
    
    /* 进入深睡,等待下次采集周期(60秒后) */
    enter_stop_mode(60000);
}

1.12.3 CPU 频率动态调整

c 复制代码
/* 根据任务负载动态调整 CPU 频率(DVFS 思想) */

typedef enum {
    CPU_SPEED_LOW    =  4,   /* MHz,待机/简单计算 */
    CPU_SPEED_MEDIUM = 16,   /* MHz,正常业务 */
    CPU_SPEED_HIGH   = 80,   /* MHz,计算密集/通信 */
} CpuSpeed;

void set_cpu_speed(CpuSpeed speed) {
    /* 调整 PLL 倍频/分频系数 */
    /* 注意:降频前先降压(DVFS),升频前先升压 */
    switch (speed) {
        case CPU_SPEED_LOW:
            HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE2);
            set_pll_to_4mhz();
            break;
        case CPU_SPEED_HIGH:
            HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);
            set_pll_to_80mhz();
            break;
    }
}

/* 功耗对比(STM32L4,@3.3V):
   80MHz Run  ≈ 28 mA
   16MHz Run  ≈  7 mA
   4MHz Run   ≈  2 mA
   Stop 2     ≈  1 μA
*/

1.13 编译器关键字与 Pragma 技巧

1.13.1 volatile 的正确使用

c 复制代码
/*
 * volatile 告诉编译器:这个变量可能被编译器不知道的方式改变
 * 禁止编译器对该变量做缓存(寄存器缓存)或重排序
 */

/* 场景1:硬件寄存器 ------ 必须使用 volatile */
#define GPIOA_IDR  (*(volatile uint32_t *)0x40020010)
uint8_t pin_state = (GPIOA_IDR >> 5) & 1;  /* 每次都从寄存器读 */

/* 场景2:中断与主循环共享变量 */
volatile bool uart_rx_complete = false;  /* ISR 设置,主循环读取 */

void UART_ISR(void) {
    receive_data();
    uart_rx_complete = true;   /* 写操作立即反映到内存 */
}

void main_loop(void) {
    while (!uart_rx_complete) {  /* 每次循环都重新读取内存 */
        __WFI();
    }
    uart_rx_complete = false;
    process_data();
}

/* ❌ 常见误用:volatile 不能保证原子性! */
volatile uint32_t counter = 0;

void ISR(void) { counter++; }  /* 读-改-写,非原子操作! */

/* ✅ 对于需要原子操作的场景,使用关中断或原子内置函数 */
void ISR_safe(void) {
    __atomic_fetch_add(&counter, 1, __ATOMIC_RELAXED);  /* GCC 原子操作 */
}

1.13.2 内存屏障

c 复制代码
/*
 * 在乱序执行 CPU 和 DMA 操作中,需要内存屏障确保操作顺序
 * Cortex-M7 有乱序执行,Cortex-M3/M4 有写缓冲
 */

/* DMB(Data Memory Barrier):确保前面所有内存访问完成 */
/* DSB(Data Synchronization Barrier):确保前面所有指令完成 */
/* ISB(Instruction Synchronization Barrier):清空流水线 */

void dma_start_transfer(void) {
    /* 确保数据写入内存(不在写缓冲中)再启动 DMA */
    memcpy(dma_buf, source, len);
    __DMB();                          /* 写屏障:确保 memcpy 的写入已到内存 */
    DMA1_Channel1->CCR |= DMA_CCR_EN; /* 启动 DMA */
}

void update_vector_table(void) {
    /* 重定向中断向量表后,必须刷新流水线 */
    SCB->VTOR = NEW_VECTOR_TABLE_ADDR;
    __DSB();  /* 等待 SCB 写入完成 */
    __ISB();  /* 刷新流水线,使新向量表生效 */
}

1.13.3 实用 GCC 属性

c 复制代码
/* 函数属性 */
void system_reset(void) __attribute__((noreturn));   /* 不会返回,编译器优化调用点 */
int  pure_func(int x)   __attribute__((pure));       /* 无副作用,相同输入相同输出,可缓存 */
int  const_func(int x)  __attribute__((const));      /* 比 pure 更严格,不读全局变量 */

/* 错误处理标记 */
void* safe_malloc(size_t n) __attribute__((malloc, returns_nonnull, warn_unused_result));

/* 结构/变量属性 */
typedef struct {
    uint8_t a;
    uint32_t b;
} __attribute__((packed)) PackedStruct;    /* 禁用对齐填充 */

uint8_t big_array[1024] __attribute__((aligned(64)));   /* 64 字节对齐 */

/* 弱符号(方便 HAL 层覆盖默认实现) */
__attribute__((weak)) void HAL_Delay(uint32_t ms) {
    /* 默认实现:用 SysTick */
    volatile uint32_t count = ms * (SystemCoreClock / 1000);
    while (count--);
}
/* 用户可在自己的文件中重新定义 HAL_Delay,会覆盖弱符号版本 */

/* 分支预测提示(GCC) */
#define LIKELY(x)    __builtin_expect(!!(x), 1)
#define UNLIKELY(x)  __builtin_expect(!!(x), 0)

void process_packet(uint8_t *data, uint16_t len) {
    if (UNLIKELY(data == NULL)) {  /* 提示:这个分支极少发生 */
        handle_error();
        return;
    }
    /* 正常路径:编译器优化为不跳转(直通) */
    parse_and_dispatch(data, len);
}

1.14 性能剖析与测量方法

1.14.1 GPIO 翻转计时法(最简单、最精确)

c 复制代码
/*
 * 最简单精确的计时方法:
 * 用示波器测量 GPIO 翻转之间的时间
 * 精度:纳秒级(受 GPIO 驱动能力和示波器带宽限制)
 */

#define PERF_PIN_SET()   GPIOA->BSRR = GPIO_PIN_0        /* PA0 置高 */
#define PERF_PIN_CLR()   GPIOA->BSRR = GPIO_PIN_0 << 16  /* PA0 置低 */
#define PERF_PIN_TOG()   GPIOA->ODR  ^= GPIO_PIN_0       /* PA0 翻转 */

void benchmark_function(void) {
    PERF_PIN_SET();
    
    target_function_to_measure();
    
    PERF_PIN_CLR();
    /* 示波器测量高电平持续时间 = 函数执行时间 */
}

1.14.2 DWT 周期计数器

c 复制代码
/*
 * ARM Cortex-M3/M4/M7 内置 DWT(Data Watchpoint and Trace)模块
 * CYCCNT 寄存器:每个 CPU 周期加 1,精度极高
 */

/* 初始化 DWT */
void dwt_init(void) {
    CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;  /* 使能 DWT */
    DWT->CYCCNT = 0;                                   /* 清零计数 */
    DWT->CTRL  |= DWT_CTRL_CYCCNTENA_Msk;             /* 启动计数 */
}

/* 计时宏 */
#define DWT_GET_CYCLES()  (DWT->CYCCNT)
#define CYCLES_TO_US(c)   ((c) / (SystemCoreClock / 1000000U))
#define CYCLES_TO_NS(c)   ((c) * 1000U / (SystemCoreClock / 1000000U))

/* 使用示例 */
void benchmark_with_dwt(void) {
    uint32_t start, end, cycles;
    
    start = DWT_GET_CYCLES();
    
    /* 被测函数 */
    for (int i = 0; i < 1000; i++) {
        target_function();
    }
    
    end = DWT_GET_CYCLES();
    cycles = end - start;
    
    printf("1000 次调用:%lu 周期,平均 %lu 周期/次,%lu ns/次\n",
           cycles, cycles / 1000, CYCLES_TO_NS(cycles / 1000));
}

/* 统计最坏情况执行时间(WCET) */
#define BENCHMARK_RUNS  10000
uint32_t wcet = 0, total = 0;

for (int i = 0; i < BENCHMARK_RUNS; i++) {
    uint32_t t0 = DWT_GET_CYCLES();
    target_function();
    uint32_t t1 = DWT_GET_CYCLES();
    uint32_t elapsed = t1 - t0;
    
    if (elapsed > wcet) wcet = elapsed;
    total += elapsed;
}
printf("平均: %lu 周期,WCET: %lu 周期\n", total/BENCHMARK_RUNS, wcet);

1.14.3 ITM 软件跟踪

c 复制代码
/* ARM ITM(Instrumentation Trace Macrocell):通过 SWO 引脚输出调试信息 */
/* 不影响程序执行时序(相比 printf 通过 UART) */

void itm_send_char(uint32_t ch) {
    while (ITM->PORT[0].u32 == 0);  /* 等待 FIFO 可用 */
    ITM->PORT[0].u8 = (uint8_t)ch;
}

/* 用于 printf 重定向到 ITM */
int __io_putchar(int ch) {
    ITM_SendChar(ch);
    return ch;
}

/* 发送 32 位数值用于实时绘图(配合 Segger SystemView 或 Ozone) */
void log_value(uint32_t channel, uint32_t value) {
    while (ITM->PORT[channel].u32 == 0);
    ITM->PORT[channel].u32 = value;
}

1.15 实战案例:从慢到快的完整重构

1.15.1 场景:8通道 ADC 采集 + 数字滤波器(Cortex-M4,无优化基线)

原始代码(未优化版本):

c 复制代码
/* 版本 1:功能正确,但性能很差 */

#define ADC_CHANNELS  8
#define FILTER_ORDER  16

float filter_coeffs[FILTER_ORDER] = { /* FIR 系数 */ };
float sample_buffer[ADC_CHANNELS][FILTER_ORDER];  /* 环形缓冲 */
float output[ADC_CHANNELS];

/* 问题:double 运算(无 FPU 用软浮点),频繁 malloc,cache 不友好 */
void process_adc_v1(void) {
    for (int ch = 0; ch < ADC_CHANNELS; ch++) {
        /* 获取 ADC 值(调用开销大) */
        uint16_t raw = HAL_ADC_GetValue(&hadc1);
        
        /* 存入缓冲 */
        double sample = (double)raw / 4095.0 * 3.3;  /* ❌ double! */
        
        /* 移位缓冲(低效) */
        for (int i = FILTER_ORDER - 1; i > 0; i--) {
            sample_buffer[ch][i] = sample_buffer[ch][i-1]; /* ❌ 频繁移位 */
        }
        sample_buffer[ch][0] = sample;
        
        /* FIR 滤波(列访问,cache 不友好) */
        double sum = 0.0;  /* ❌ double 累加 */
        for (int i = 0; i < FILTER_ORDER; i++) {
            sum += sample_buffer[ch][i] * filter_coeffs[i]; /* ❌ cache miss */
        }
        output[ch] = (float)sum;
    }
}

逐步优化过程:

c 复制代码
/* 版本 2:float 替代 double,使用硬件 FPU */
/* 收益:软浮点 → 硬件 FPU,速度提升 10~20x */

// CFLAGS += -mfpu=fpv4-sp-d16 -mfloat-abi=hard

float filter_coeffs_f[FILTER_ORDER] = { /* FIR 系数,float */ };
float sample_buffer_f[ADC_CHANNELS][FILTER_ORDER];

void process_adc_v2(void) {
    for (int ch = 0; ch < ADC_CHANNELS; ch++) {
        uint16_t raw = HAL_ADC_GetValue(&hadc1);
        float sample = (float)raw * (3.3f / 4095.0f);  /* ✅ float 常量 */
        
        memmove(&sample_buffer_f[ch][1], &sample_buffer_f[ch][0],
                (FILTER_ORDER - 1) * sizeof(float));  /* ✅ memmove 比循环快 */
        sample_buffer_f[ch][0] = sample;
        
        float sum = 0.0f;  /* ✅ float */
        for (int i = 0; i < FILTER_ORDER; i++) {
            sum += sample_buffer_f[ch][i] * filter_coeffs_f[i];
        }
        output[ch] = sum;
    }
}
c 复制代码
/* 版本 3:改用索引型环形缓冲,消除 memmove */
/* 收益:消除 O(N) 的数据移动,变为 O(1) 索引更新 */

uint16_t ring_index[ADC_CHANNELS] = {0};  /* 每个通道的当前写入位置 */
float ring_buffer[ADC_CHANNELS][FILTER_ORDER];

void process_adc_v3(void) {
    for (int ch = 0; ch < ADC_CHANNELS; ch++) {
        float sample = (float)HAL_ADC_GetValue(&hadc1) * (3.3f / 4095.0f);
        
        /* O(1) 写入,无数据移动 */
        ring_index[ch] = (ring_index[ch] + 1) & (FILTER_ORDER - 1);
        ring_buffer[ch][ring_index[ch]] = sample;
        
        /* FIR:从环形缓冲读取(顺序访问) */
        float sum = 0.0f;
        uint16_t idx = ring_index[ch];
        for (int i = 0; i < FILTER_ORDER; i++) {
            sum += ring_buffer[ch][idx] * filter_coeffs_f[i];
            idx = (idx == 0) ? (FILTER_ORDER - 1) : (idx - 1);
        }
        output[ch] = sum;
    }
}
c 复制代码
/* 版本 4:内存布局重组 + restrict + DMA 采集 */
/* 收益:Cache 友好访问,DMA 解放 CPU */

/* 将 FIR 数据改为通道优先布局(访问同一通道时连续) */
/* buffer[sample_idx][channel_idx] → buffer[channel_idx][sample_idx] 已经是最优 */
/* 关键:系数和数据交叉访问时的 cache 行为 */

/* 使用 CMSIS DSP 库(ARM 汇编级优化的 FIR 实现) */
#include "arm_math.h"

arm_fir_instance_f32 fir_inst[ADC_CHANNELS];
float fir_state[ADC_CHANNELS][FILTER_ORDER + BLOCK_SIZE - 1];

void fir_init(void) {
    for (int ch = 0; ch < ADC_CHANNELS; ch++) {
        arm_fir_init_f32(&fir_inst[ch], FILTER_ORDER,
                         filter_coeffs_f, fir_state[ch], BLOCK_SIZE);
    }
}

/* DMA 采集 ADC,CPU 同时处理上一批数据(双缓冲) */
uint16_t adc_dma_buf[2][ADC_CHANNELS];  /* Ping-Pong 双缓冲 */
volatile uint8_t dma_ready_buf = 0;

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) {
    dma_ready_buf ^= 1;  /* 切换缓冲 */
}

void process_adc_v4(void) {
    /* 使用 CMSIS DSP 的 ARM 汇编优化 FIR(利用 SIMD/MAC 指令) */
    float input_f[BLOCK_SIZE];
    float output_f[BLOCK_SIZE];
    
    /* 等待 DMA 完成(实际应改为事件驱动) */
    while (!dma_ready_buf);
    
    for (int ch = 0; ch < ADC_CHANNELS; ch++) {
        /* 类型转换 */
        for (int i = 0; i < BLOCK_SIZE; i++) {
            input_f[i] = adc_dma_buf[dma_ready_buf][ch] * (3.3f / 4095.0f);
        }
        
        /* CMSIS DSP FIR:使用 VMLA/VMUL SIMD 指令,比手写快 4x */
        arm_fir_f32(&fir_inst[ch], input_f, output_f, BLOCK_SIZE);
        output[ch] = output_f[BLOCK_SIZE - 1];
    }
}

优化结果汇总:

复制代码
版本    执行时间(μs)  CPU占用率  Flash(KB)   说明
────────────────────────────────────────────────────────
V1       8540         85%        12.4       软浮点 double
V2        620          6%        11.8       硬件 FPU float
V3        480          4.8%      12.0       消除 memmove
V4        95           0.95%     14.2       CMSIS DSP + DMA
────────────────────────────────────────────────────────
总提升:  90x                              

1.16 总结:嵌入式优化的优先级路线图

复制代码
Level 1(必做,收益最大):
  ✅ 开启正确的编译器优化级别(-O2 或 -Os)
  ✅ 使能硬件 FPU(-mfpu=fpv4-sp-d16 -mfloat-abi=hard)
  ✅ 消除 double,统一使用 float(有 FPU 时)
  ✅ 开启 -ffunction-sections/-fdata-sections + --gc-sections

Level 2(重要,中等收益):
  ✅ 用 DWT CYCCNT 找到真正的性能热点
  ✅ 优化结构体内存布局(消除填充字节)
  ✅ 热路径函数使用 static inline
  ✅ ISR 最小化:只搬运数据,不做业务处理
  ✅ 使用 restrict 消除指针别名

Level 3(进阶,架构级优化):
  ✅ 使用 DMA 解放 CPU(Ping-Pong 双缓冲)
  ✅ 热路径代码放入 ITCM(零等待内存)
  ✅ 使用 CMSIS DSP / 厂商 HAL 中的汇编优化函数
  ✅ 查表法替代复杂数学运算
  ✅ Cache 一致性管理(DMA + D-Cache)

Level 4(极致优化):
  ✅ 手写关键汇编(SIMD/NEON/DSP 扩展指令)
  ✅ 定点数替代浮点数(无 FPU 平台)
  ✅ 流水线级别的指令调度优化
  ✅ 内存带宽优化(访问模式、预取)

⚠️  永远记住:先测量,再优化。直觉往往是错的。

本文覆盖了嵌入式 C 性能优化从入门到进阶的完整体系。建议结合具体硬件平台(STM32/NXP/TI 等)动手实践,在实际测量中加深理解。

相关推荐
逻辑驱动的ken1 小时前
Java高频面试考点场景题28
java·开发语言·面试·职场和发展·求职招聘
fly_over1 小时前
AI Agent 开发实战教程(二):Prompt 工程与工具调用
开发语言·python·langchain·prompt·ai编程·ai agent
csbysj20201 小时前
并查集基础
开发语言
雨落在了我的手上1 小时前
初识java(四):程序逻辑控制
java·开发语言·前端
jllllyuz1 小时前
VC++ 读写 Excel 文件实现
开发语言·c++·excel
Lucky_ldy1 小时前
C语言学习:字符函数和字符串函数(内容丰富且易懂)
c语言·开发语言·学习
小小编程能手1 小时前
C++文件从操作:
开发语言·c++
ellis19701 小时前
Unity性能优化之检测工具Profiler
unity·性能优化
czxyvX1 小时前
5-Qt系统相关
开发语言·qt