电子产业一站式赋能平台

PCB联盟网

搜索
查看: 99|回复: 0
收起左侧

嵌入式软件初学者,写代码时比较容易犯的错误!

[复制链接]

568

主题

568

帖子

4221

积分

四级会员

Rank: 4

积分
4221
发表于 2025-4-11 17:51:00 | 显示全部楼层 |阅读模式
嵌入式软件代码,由于其运行在资源受限的硬件平台上,因此,对嵌入式软件工程师的编程能力和细节把控能力提出了更高的要求。
$ ^& j. x! w3 \% U3 N" s以下列举一些嵌入式初学者常见的错误类型和具体示例,并提供一些改进方法,希望可以帮助各位老铁在嵌入式软件设计时,更好地进行规避。
5 O, \" @! V/ O% a' Z1 o, _5 `, d1 v一、内存管理不当
7 I' j* z' A! a' [* ~: A  r1、栈溢出, j6 i4 ?4 L! G
初学者经常容易忽视嵌入式系统有限的栈空间,函数层级调用过深或者在函数内部定义过大的局部变量,容易导致栈溢出。
  • //错误示例void process_data() {    uint8_t large_buffer[4096]; // 在只有2KB栈空间的MCU上    // ... 使用large_buffer}, C. Q1 Y/ \: R4 ~( Z3 {' v1 g
    //改进方法void process_data() {    static uint8_t large_buffer[4096]; // 改为静态存储    // 或者使用动态分配(如果支持)    // uint8_t* large_buffer = malloc(4096);    // ... 使用large_buffer    // free(large_buffer);}2、内存泄漏
    . r. x; S& n/ c1 x6 Y在支持动态内存分配的嵌入式软件系统中,使用malloc分配内存之后,忘记调用free释放内存,导致内存泄漏。
  • //错误示例void create_packet() {    Packet* pkt = (Packet*)malloc(sizeof(Packet));    // ... 使用pkt    // 忘记free(pkt);}6 L0 B5 H3 r. ~8 {
    //改进方法void create_packet() {    Packet* pkt = (Packet*)malloc(sizeof(Packet));    if(pkt == NULL) {        // 错误处理        return;    }    // ... 使用pkt    free(pkt); // 确保释放}二、未考虑并发和中断问题0 c/ A) i3 ?! y- n( o" k1 \1 `
    1、共享资源未进行保护7 m6 }. U. w9 Y7 R- n* ]5 E
    在中断服务程序和主程序之间共享全局变量的时候未进行变量保护,导致两者可能对全局变量同时进行修改和引用。
  • //错误示例volatile uint32_t counter; // 主程序和ISR都会修改void ISR() {    counter++; // 中断中修改}void main() {    while(1) {        if(counter > 100) { // 主程序读取            // ...        }    }}% R, ~- u& S6 W" K( p
    //改进方法volatile uint32_t counter;void ISR() {    counter++; // 简单变量在大多数架构上是原子操作}void main() {    while(1) {        uint32_t local_counter = counter; // 先读取到局部变量        if(local_counter > 100) {            // ...        }    }}2、中断函数执行耗时操作
    5 |, B  i: _4 }8 |4 b- R9 Q在中断服务函数里面执行非常耗时的操作,影响了嵌入式系统的实时性。
  • //错误示例void UART_ISR() {    uint8_t data = UART->DR;    process_data(data); // 复杂的数据处理    send_response();    // 可能阻塞的操作}+ s  ~$ N6 O7 o" s1 l
    //改进方法#define BUF_SIZE 64volatile uint8_t uart_buffer[BUF_SIZE];volatile uint8_t uart_idx = 0;& R1 V3 Y! ^7 Q* g" |& f
    void UART_ISR() {    if(uart_idx         uart_buffer[uart_idx++] = UART->DR; // 仅快速存储数据    }}
    " n# G& p# ?* e( Y  r" y6 M# Xvoid main() {    while(1) {            if(uart_idx > 0) {            uint8_t data = uart_buffer[--uart_idx];            process_data(data); // 在主循环中处理        }    }}三、硬件相关错误: L% Q! f* D" X; I. f) D
    1、硬件外设未进行初始化
    + N! p, X! H! B1 m7 O在代码中直接使用未进行初始化的外设设备,导致程序出现莫名其妙的异常。
  • //错误示例void read_sensor() {    uint16_t value = ADC->DR; // 直接读取ADC数据寄存器    // ...}  ?- p% }' E* F! e9 |
    //改进方法void init_adc() {    // 启用ADC时钟    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;. S: \6 w- _. h& a# H; [
        // 配置ADC    ADC1->CR2 = ADC_CR2_ADON; // 开启ADC    // ... 其他配置    delay_ms(1); // 等待稳定}( C. Q9 m. U- }8 @
    void read_sensor() {    ADC1->CR2 |= ADC_CR2_SWSTART; // 开始转换    while(!(ADC1->SR & ADC_SR_EOC)); // 等待转换完成    uint16_t value = ADC1->DR; // 读取结果    // ...}2、忽略寄存器位操作$ q6 X) _' M: e: p! v& `  X
    直接对寄存器赋值,而不是采用位操作修改寄存器值。
  • //错误示例void set_gpio() {    GPIOA->ODR = 0x0001; // 直接设置输出数据寄存器}, s+ F5 K- b% ?3 S8 b
    //改进方法void set_gpio() {    GPIOA->ODR |= GPIO_ODR_OD0; // 置位PA0    // 或者    GPIOA->BSRR = GPIO_BSRR_BS0; // 原子操作置位PA0}# b4 N6 {* Q* n; i  @
    void clear_gpio() {    GPIOA->ODR &= ~GPIO_ODR_OD0; // 清零PA0    // 或者    GPIOA->BSRR = GPIO_BSRR_BR0; // 原子操作清零PA0}四、实时性和效率问题
    / h: k; u" g# c! Y) Z; b1、阻塞性延时
    7 b! ]1 E1 ^/ Q! r) A; ]使用忙等待或空操作实现延时,浪费CPU的运算资源。
  • //错误示例void delay_ms(uint32_t ms) {    for(uint32_t i = 0; i 1000; i++) {        __NOP(); // 空操作    }}; I5 \: n6 z  h+ f
    //改进方法volatile uint32_t systick_count = 0;void SysTick_Handler() {    systick_count++;}void delay_ms(uint32_t ms) {    uint32_t start = systick_count;    while((systick_count - start) }// 初始化时配置SysTick定时器void init_systick() {    SysTick_Config(SystemcoreClock / 1000); // 1ms中断}2、滥用浮点数运算) i# K% f9 ^- ^, G/ ^9 w
    在没有FPU运算单元的MCU上频繁使用浮点数运算,大大增加MCU的运算压力。
  • //错误示例float calculate_pid(float error) {    static float integral = 0;    integral += error * 0.1; // 浮点运算    return error * 2.5 + integral;}
    ) q5 w# k7 D8 B, Y; i& x; E1 x//改进方法#define SCALE_FACTOR 10000 \' i' x4 ?3 I' B  h5 U! K& v" b
    int32_t calculate_pid(int32_t error) {    static int32_t integral = 0;    integral += (error * 100) / 1000; // 相当于error*0.1    return (error * 2500) / 1000 + integral; // 相当于error*2.5 + integral}五、代码结构和可维护性问题% q9 A, r% T! q3 i* u  _- \1 f  J
    1、滥用全局变量
    6 w. U( D) }' }" C4 X编写嵌入式代码时,过度使用全局变量进行参数传递,增加了模块之间的耦合度,导致可维护性变差。
  • //错误示例uint32_t temperature;uint32_t humidity;uint32_t pressure;
    + c4 C, ?# Q6 t/ ?  H1 uvoid read_sensors() {    temperature = read_temp();    humidity = read_humidity();    pressure = read_pressure();}0 P( n3 O/ Y6 [% I' l( F- `
    void process_data() {    if(temperature > 30) {        // ...    }}//改进方法typedef struct {    uint32_t temperature;    uint32_t humidity;    uint32_t pressure;} SensorData;: l0 T' n% E; b! V# f
    void read_sensors(SensorData* data) {    data->temperature = read_temp();    data->humidity = read_humidity();    data->pressure = read_pressure();}: x# d& v: ?, v2 C# e2 w
    void process_data(const SensorData* data) {    if(data->temperature > 30) {        // ...    }}2、缺乏模块化概念
    " n7 W9 j' p0 {7 y" _+ x8 S在进行嵌入式软件设计时,缺乏模块化的编程概念,把所有的功能都写在单个源代码文件里面。
  • //错误示例project/└── main.c (包含所有外设驱动、业务逻辑); a" X: g5 L( G# t+ F
    //改进方法project/├── drivers/│   ├── gpio.c│   ├── uart.c│   └── adc.c├── modules/│   ├── sensor.c│   └── controller.c└── main.c (仅包含高层逻辑)六、调试和测试不足
    0 k5 n/ Z, w) M" [# f9 z1、缺乏断言assert()检查
    + ]1 G$ d, M, d8 @- }* N/ X传入的参数和假设条件总是成立的,没有对参数合法性进行检查。
  • //错误示例void set_pwm(uint8_t duty) {    TIM1->CCR1 = duty; // 直接赋值,不检查边界}
    8 I5 C0 Q# V3 q$ Q//改进方法#include
    : L! l. b6 C  p, S- T3 U& {# b2 ivoid set_pwm(uint8_t duty) {    assert(duty 100); // 调试时检查    TIM1->CCR1 = (TIM1->ARR * duty) / 100; // 转换为实际计数值}2、忽略编译器警告
    / t  z3 v3 J' ]8 Y6 b4 Q  P) E直接忽略编译时出现的警告,导致软件里面包含潜在风险。
  • //错误示例int read_value() {    uint16_t value = read_adc();    return value; // 警告: 从'uint16_t'转换为'int'可能改变值}
    1 D5 x& X2 C0 e% D  {5 t% D2 C//改进方法uint16_t read_value() { // 修改返回类型匹配    uint16_t value = read_adc();    return value;}总结和实践建议
    ( J) m, \6 g2 j嵌入式软件开发的时候,需要特别注意硬件资源限制,系统实时性要求,硬件交互特性等等,建议初学者养成以下习惯,用以规避上述错误,从而写出更加高效可靠的嵌入式软件代码。4 G$ b! S; v  y" g( X& k* I9 d- q
    防御性编程:对所有指针解引用、内存分配、外设操作添加有效性检查。% s) n4 J8 Z9 C9 j
    工具链利用:使用静态分析工具(如 Coverity)、调试器(JTAG)和示波器辅助开发。: w0 ?0 s5 e) @% {/ ^! `
    硬件理解:深入研读芯片数据手册,掌握寄存器位定义和外设时序要求。
    6 p4 B+ p) p4 y3 O  V1 p代码规范:遵循 MISRA-C 标准,避免未定义行为。
    ; Z5 g. J# I' K" Z" d4 y测试策略:编写单元测试覆盖边界条件,使用硬件仿真验证实时性。
    ) b" E1 Y* q( A' u! i-END-* J5 Y- q/ W; W+ n& Y
    往期推荐:点击图片即可跳转阅读
    / Y, h0 }( `( }+ X. S* G% @' |
    : k" R% o8 W, l! g

    tc3gqia00it64042956608.jpg

    tc3gqia00it64042956608.jpg
    7 a: ~% W% T+ h! k6 D
    嵌入式C语言基础,如何向函数内部传递结构信息?
    # v0 _! F6 h+ z# J; y* v

    xovwcygrctb64042956708.jpg

    xovwcygrctb64042956708.jpg
    6 A- B0 g' d$ Q1 J1 i
    嵌入式开发者的效率神器:Serial-Studio实战解析!* B% k& a; i3 y2 J) J  W" g  f1 V

    ucsmik2dgoh64042956808.jpg

    ucsmik2dgoh64042956808.jpg
    4 h# E+ n: v& m* L. Z6 J& M) A' Q7 s
    嵌入式软件工程师,凌晨三点还在Debug,没想到故障竟然是。。。
    ' n% n+ w/ p" y星标+置顶,掌握嵌入式AIoT前沿技术资讯3 B2 T" J3 F( M, V. r4 r+ P2 m
    点赞+关注,一起变得更加优秀!
  • 回复

    使用道具 举报

    发表回复

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则


    联系客服 关注微信 下载APP 返回顶部 返回列表