电子产业一站式赋能平台

PCB联盟网

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

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

[复制链接]

554

主题

554

帖子

3753

积分

四级会员

Rank: 4

积分
3753
发表于 前天 17:51 | 显示全部楼层 |阅读模式
嵌入式软件代码,由于其运行在资源受限的硬件平台上,因此,对嵌入式软件工程师的编程能力和细节把控能力提出了更高的要求。7 Q/ k9 L* [! m# Z, J5 Q
以下列举一些嵌入式初学者常见的错误类型和具体示例,并提供一些改进方法,希望可以帮助各位老铁在嵌入式软件设计时,更好地进行规避。
  ~/ u/ q' w( \8 r一、内存管理不当- b2 H+ V* R+ C7 k4 {
1、栈溢出. n5 q2 u* I6 }# [- n8 h3 R
初学者经常容易忽视嵌入式系统有限的栈空间,函数层级调用过深或者在函数内部定义过大的局部变量,容易导致栈溢出。
  • //错误示例void process_data() {    uint8_t large_buffer[4096]; // 在只有2KB栈空间的MCU上    // ... 使用large_buffer}
    0 s. J2 l0 C2 p& S' {//改进方法void process_data() {    static uint8_t large_buffer[4096]; // 改为静态存储    // 或者使用动态分配(如果支持)    // uint8_t* large_buffer = malloc(4096);    // ... 使用large_buffer    // free(large_buffer);}2、内存泄漏
    . T& t( @) W8 \% p. ~' f# K在支持动态内存分配的嵌入式软件系统中,使用malloc分配内存之后,忘记调用free释放内存,导致内存泄漏。
  • //错误示例void create_packet() {    Packet* pkt = (Packet*)malloc(sizeof(Packet));    // ... 使用pkt    // 忘记free(pkt);}
    : T# t3 e0 E5 |) L8 E; s" w# y//改进方法void create_packet() {    Packet* pkt = (Packet*)malloc(sizeof(Packet));    if(pkt == NULL) {        // 错误处理        return;    }    // ... 使用pkt    free(pkt); // 确保释放}二、未考虑并发和中断问题
    . l$ `+ k4 U0 f# @. Q1、共享资源未进行保护
    ) G: B/ P# N0 a8 q+ w: @在中断服务程序和主程序之间共享全局变量的时候未进行变量保护,导致两者可能对全局变量同时进行修改和引用。
  • //错误示例volatile uint32_t counter; // 主程序和ISR都会修改void ISR() {    counter++; // 中断中修改}void main() {    while(1) {        if(counter > 100) { // 主程序读取            // ...        }    }}
    " f/ P; N; F( N//改进方法volatile uint32_t counter;void ISR() {    counter++; // 简单变量在大多数架构上是原子操作}void main() {    while(1) {        uint32_t local_counter = counter; // 先读取到局部变量        if(local_counter > 100) {            // ...        }    }}2、中断函数执行耗时操作1 q4 T! s! p8 h- a. [4 `
    在中断服务函数里面执行非常耗时的操作,影响了嵌入式系统的实时性。
  • //错误示例void UART_ISR() {    uint8_t data = UART->DR;    process_data(data); // 复杂的数据处理    send_response();    // 可能阻塞的操作}
    ( f; Q6 h' M: t$ {; P//改进方法#define BUF_SIZE 64volatile uint8_t uart_buffer[BUF_SIZE];volatile uint8_t uart_idx = 0;
    % A" N  R8 f1 y. E3 Y( mvoid UART_ISR() {    if(uart_idx         uart_buffer[uart_idx++] = UART->DR; // 仅快速存储数据    }}- q) F7 u) V6 X& n( C
    void main() {    while(1) {            if(uart_idx > 0) {            uint8_t data = uart_buffer[--uart_idx];            process_data(data); // 在主循环中处理        }    }}三、硬件相关错误
    ! j' ]( V" l  ~- d5 ^" v1、硬件外设未进行初始化
    ; X' h+ J0 [9 }( ?$ I! g: T  X3 H在代码中直接使用未进行初始化的外设设备,导致程序出现莫名其妙的异常。
  • //错误示例void read_sensor() {    uint16_t value = ADC->DR; // 直接读取ADC数据寄存器    // ...}* y3 \$ [: u. V" C3 q4 Y, t
    //改进方法void init_adc() {    // 启用ADC时钟    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
    ; ^; `* n' c& Y8 e( A    // 配置ADC    ADC1->CR2 = ADC_CR2_ADON; // 开启ADC    // ... 其他配置    delay_ms(1); // 等待稳定}- w8 x- v1 s) k" j
    void read_sensor() {    ADC1->CR2 |= ADC_CR2_SWSTART; // 开始转换    while(!(ADC1->SR & ADC_SR_EOC)); // 等待转换完成    uint16_t value = ADC1->DR; // 读取结果    // ...}2、忽略寄存器位操作4 }$ q# [+ B3 @( ~+ t
    直接对寄存器赋值,而不是采用位操作修改寄存器值。
  • //错误示例void set_gpio() {    GPIOA->ODR = 0x0001; // 直接设置输出数据寄存器}% b6 w6 [7 S+ [; Y0 Z; M4 S! Z- @
    //改进方法void set_gpio() {    GPIOA->ODR |= GPIO_ODR_OD0; // 置位PA0    // 或者    GPIOA->BSRR = GPIO_BSRR_BS0; // 原子操作置位PA0}, f8 M" Q( s: c1 V. L; m
    void clear_gpio() {    GPIOA->ODR &= ~GPIO_ODR_OD0; // 清零PA0    // 或者    GPIOA->BSRR = GPIO_BSRR_BR0; // 原子操作清零PA0}四、实时性和效率问题1 m5 @8 K- m5 d: R3 \
    1、阻塞性延时
    , R+ {) g2 u" {" X( ~' M- C  P使用忙等待或空操作实现延时,浪费CPU的运算资源。
  • //错误示例void delay_ms(uint32_t ms) {    for(uint32_t i = 0; i 1000; i++) {        __NOP(); // 空操作    }}
    ' v6 T- y6 d: `//改进方法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、滥用浮点数运算
    + F+ t& g" n9 C  V4 `在没有FPU运算单元的MCU上频繁使用浮点数运算,大大增加MCU的运算压力。
  • //错误示例float calculate_pid(float error) {    static float integral = 0;    integral += error * 0.1; // 浮点运算    return error * 2.5 + integral;}: a' V/ M$ u1 Z2 U9 T6 U
    //改进方法#define SCALE_FACTOR 1000
    + ~* A+ u; l9 Y  Yint32_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}五、代码结构和可维护性问题6 n8 C+ V1 w' c4 e; Z
    1、滥用全局变量! q" Y% J$ ~% {$ e" P
    编写嵌入式代码时,过度使用全局变量进行参数传递,增加了模块之间的耦合度,导致可维护性变差。
  • //错误示例uint32_t temperature;uint32_t humidity;uint32_t pressure;" e2 j- n7 ^( c
    void read_sensors() {    temperature = read_temp();    humidity = read_humidity();    pressure = read_pressure();}" ?* y/ o' W* r: M
    void process_data() {    if(temperature > 30) {        // ...    }}//改进方法typedef struct {    uint32_t temperature;    uint32_t humidity;    uint32_t pressure;} SensorData;
    0 t& `: C% ?1 _6 ivoid read_sensors(SensorData* data) {    data->temperature = read_temp();    data->humidity = read_humidity();    data->pressure = read_pressure();}- L, z  L; Y6 J0 D& C# |
    void process_data(const SensorData* data) {    if(data->temperature > 30) {        // ...    }}2、缺乏模块化概念* ]+ ?3 j: \3 \* s& y
    在进行嵌入式软件设计时,缺乏模块化的编程概念,把所有的功能都写在单个源代码文件里面。
  • //错误示例project/└── main.c (包含所有外设驱动、业务逻辑)* S7 w' P! X8 x% E
    //改进方法project/├── drivers/│   ├── gpio.c│   ├── uart.c│   └── adc.c├── modules/│   ├── sensor.c│   └── controller.c└── main.c (仅包含高层逻辑)六、调试和测试不足
    2 L3 f$ R' t1 e- S1、缺乏断言assert()检查# }3 v5 W4 O- X9 a) _9 Y8 P0 n
    传入的参数和假设条件总是成立的,没有对参数合法性进行检查。
  • //错误示例void set_pwm(uint8_t duty) {    TIM1->CCR1 = duty; // 直接赋值,不检查边界}
    & Y, j) O, r# a//改进方法#include
    . F$ j& T6 j  k" c* t3 {: r2 c8 Kvoid set_pwm(uint8_t duty) {    assert(duty 100); // 调试时检查    TIM1->CCR1 = (TIM1->ARR * duty) / 100; // 转换为实际计数值}2、忽略编译器警告3 S) l: c" N. Z/ H
    直接忽略编译时出现的警告,导致软件里面包含潜在风险。
  • //错误示例int read_value() {    uint16_t value = read_adc();    return value; // 警告: 从'uint16_t'转换为'int'可能改变值}. H+ [- Z4 M7 b; \8 _
    //改进方法uint16_t read_value() { // 修改返回类型匹配    uint16_t value = read_adc();    return value;}总结和实践建议# Z8 D# j; z% m% L+ i7 d2 w1 R
    嵌入式软件开发的时候,需要特别注意硬件资源限制,系统实时性要求,硬件交互特性等等,建议初学者养成以下习惯,用以规避上述错误,从而写出更加高效可靠的嵌入式软件代码。
    ( D: ?: L/ w& A' q  ^, y0 s防御性编程:对所有指针解引用、内存分配、外设操作添加有效性检查。
    1 s- w' n) O  w( P% w0 `工具链利用:使用静态分析工具(如 Coverity)、调试器(JTAG)和示波器辅助开发。
      ~* k1 n8 A$ e, ^% e# B0 r硬件理解:深入研读芯片数据手册,掌握寄存器位定义和外设时序要求。7 \7 s! ^- S+ t* T
    代码规范:遵循 MISRA-C 标准,避免未定义行为。
    ! B6 G( t3 Y- S  p测试策略:编写单元测试覆盖边界条件,使用硬件仿真验证实时性。4 U; \/ ]! l# X1 Z9 ^( ?1 [
    -END-) {3 t4 a1 v& R) [
    往期推荐:点击图片即可跳转阅读
    * y3 \6 M$ z1 N2 J& X) X
    ( V3 ]" Y$ n3 p' u8 u

    tc3gqia00it64042956608.jpg

    tc3gqia00it64042956608.jpg
    " ~& J1 v/ n" f
    嵌入式C语言基础,如何向函数内部传递结构信息?3 _4 Y, s* Y# f

    xovwcygrctb64042956708.jpg

    xovwcygrctb64042956708.jpg
    7 D# D, O1 h& O( N1 x8 E& @
    嵌入式开发者的效率神器:Serial-Studio实战解析!
    5 |2 }5 `  J. R9 e6 f$ z

    ucsmik2dgoh64042956808.jpg

    ucsmik2dgoh64042956808.jpg

    # R# r8 B' {6 E  H7 Y嵌入式软件工程师,凌晨三点还在Debug,没想到故障竟然是。。。  N/ B5 |# O  {4 N! r( `. F2 d% ]
    星标+置顶,掌握嵌入式AIoT前沿技术资讯1 A+ r+ `$ j" T
    点赞+关注,一起变得更加优秀!
  • 回复

    使用道具 举报

    发表回复

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

    本版积分规则


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