打印
[开发资料]

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

[复制链接]
48|3
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
powerantone|  楼主 | 2025-4-14 16:39 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 powerantone 于 2025-4-14 16:40 编辑

嵌入式软件代码,由于其运行在资源受限的硬件平台上,因此,对嵌入式软件工程师的编程能力和细节把控能力提出了更高的要求。
以下列举一些嵌入式初学者常见的错误类型和具体示例,并提供一些改进方法,希望可以帮助各位老铁在嵌入式软件设计时,更好地进行规避。

使用特权

评论回复
沙发
powerantone|  楼主 | 2025-4-14 16:40 | 只看该作者
一、内存管理不当
1、栈溢出
初学者经常容易忽视嵌入式系统有限的栈空间,函数层级调用过深或者在函数内部定义过大的局部变量,容易导致栈溢出。//错误示例void process_data() {    uint8_t large_buffer[4096]; // 在只有2KB栈空间的MCU上    // ... 使用large_buffer}
//改进方法void process_data() {    static uint8_t large_buffer[4096]; // 改为静态存储    // 或者使用动态分配(如果支持)    // uint8_t* large_buffer = malloc(4096);    // ... 使用large_buffer    // free(large_buffer);}2、内存泄漏
在支持动态内存分配的嵌入式软件系统中,使用malloc分配内存之后,忘记调用free释放内存,导致内存泄漏。//错误示例void create_packet() {    Packet* pkt = (Packet*)malloc(sizeof(Packet));    // ... 使用pkt    // 忘记free(pkt);}
//改进方法void create_packet() {    Packet* pkt = (Packet*)malloc(sizeof(Packet));    if(pkt == NULL) {        // 错误处理        return;    }    // ... 使用pkt    free(pkt); // 确保释放}二、未考虑并发和中断问题
1、共享资源未进行保护
在中断服务程序和主程序之间共享全局变量的时候未进行变量保护,导致两者可能对全局变量同时进行修改和引用。//错误示例volatile uint32_t counter; // 主程序和ISR都会修改void ISR() {    counter++; // 中断中修改}void main() {    while(1) {        if(counter > 100) { // 主程序读取            // ...        }    }}
//改进方法volatile uint32_t counter;void ISR() {    counter++; // 简单变量在大多数架构上是原子操作}void main() {    while(1) {        uint32_t local_counter = counter; // 先读取到局部变量        if(local_counter > 100) {            // ...        }    }}2、中断函数执行耗时操作
在中断服务函数里面执行非常耗时的操作,影响了嵌入式系统的实时性。//错误示例void UART_ISR() {    uint8_t data = UART->DR;    process_data(data); // 复杂的数据处理    send_response();    // 可能阻塞的操作}
//改进方法#define BUF_SIZE 64volatile uint8_t uart_buffer[BUF_SIZE];volatile uint8_t uart_idx = 0;
void UART_ISR() {    if(uart_idx < BUF_SIZE) {        uart_buffer[uart_idx++] = UART->DR; // 仅快速存储数据    }}
void main() {    while(1) {            if(uart_idx > 0) {            uint8_t data = uart_buffer[--uart_idx];            process_data(data); // 在主循环中处理        }    }}

使用特权

评论回复
板凳
powerantone|  楼主 | 2025-4-14 16:40 | 只看该作者
三、硬件相关错误
1、硬件外设未进行初始化
在代码中直接使用未进行初始化的外设设备,导致程序出现莫名其妙的异常。//错误示例void read_sensor() {    uint16_t value = ADC->DR; // 直接读取ADC数据寄存器    // ...}
//改进方法void init_adc() {    // 启用ADC时钟    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
    // 配置ADC    ADC1->CR2 = ADC_CR2_ADON; // 开启ADC    // ... 其他配置    delay_ms(1); // 等待稳定}
void read_sensor() {    ADC1->CR2 |= ADC_CR2_SWSTART; // 开始转换    while(!(ADC1->SR & ADC_SR_EOC)); // 等待转换完成    uint16_t value = ADC1->DR; // 读取结果    // ...}2、忽略寄存器位操作
直接对寄存器赋值,而不是采用位操作修改寄存器值。//错误示例void set_gpio() {    GPIOA->ODR = 0x0001; // 直接设置输出数据寄存器}
//改进方法void set_gpio() {    GPIOA->ODR |= GPIO_ODR_OD0; // 置位PA0    // 或者    GPIOA->BSRR = GPIO_BSRR_BS0; // 原子操作置位PA0}
void clear_gpio() {    GPIOA->ODR &= ~GPIO_ODR_OD0; // 清零PA0    // 或者    GPIOA->BSRR = GPIO_BSRR_BR0; // 原子操作清零PA0}四、实时性和效率问题
1、阻塞性延时
使用忙等待或空操作实现延时,浪费CPU的运算资源。//错误示例void delay_ms(uint32_t ms) {    for(uint32_t i = 0; i < ms*1000; i++) {        __NOP(); // 空操作    }}
//改进方法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) < ms);}// 初始化时配置SysTick定时器void init_systick() {    SysTick_Config(SystemCoreClock / 1000); // 1ms中断}2、滥用浮点数运算
在没有FPU运算单元的MCU上频繁使用浮点数运算,大大增加MCU的运算压力。//错误示例float calculate_pid(float error) {    static float integral = 0;    integral += error * 0.1; // 浮点运算    return error * 2.5 + integral;}
//改进方法#define SCALE_FACTOR 1000
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}五、代码结构和可维护性问题
1、滥用全局变量
编写嵌入式代码时,过度使用全局变量进行参数传递,增加了模块之间的耦合度,导致可维护性变差。//错误示例uint32_t temperature;uint32_t humidity;uint32_t pressure;
void read_sensors() {    temperature = read_temp();    humidity = read_humidity();    pressure = read_pressure();}
void process_data() {    if(temperature > 30) {        // ...    }}//改进方法typedef struct {    uint32_t temperature;    uint32_t humidity;    uint32_t pressure;} SensorData;
void read_sensors(SensorData* data) {    data->temperature = read_temp();    data->humidity = read_humidity();    data->pressure = read_pressure();}
void process_data(const SensorData* data) {    if(data->temperature > 30) {        // ...    }}2、缺乏模块化概念
在进行嵌入式软件设计时,缺乏模块化的编程概念,把所有的功能都写在单个源代码文件里面。//错误示例project/└── main.c (包含所有外设驱动、业务逻辑)
//改进方法project/├── drivers/│   ├── gpio.c│   ├── uart.c│   └── adc.c├── modules/│   ├── sensor.c│   └── controller.c└── main.c (仅包含高层逻辑)六、调试和测试不足
1、缺乏断言assert()检查
传入的参数和假设条件总是成立的,没有对参数合法性进行检查。//错误示例void set_pwm(uint8_t duty) {    TIM1->CCR1 = duty; // 直接赋值,不检查边界}
//改进方法#include <assert.h>
void set_pwm(uint8_t duty) {    assert(duty <= 100); // 调试时检查    TIM1->CCR1 = (TIM1->ARR * duty) / 100; // 转换为实际计数值}2、忽略编译器警告
直接忽略编译时出现的警告,导致软件里面包含潜在风险。//错误示例int read_value() {    uint16_t value = read_adc();    return value; // 警告: 从'uint16_t'转换为'int'可能改变值}
//改进方法uint16_t read_value() { // 修改返回类型匹配    uint16_t value = read_adc();    return value;}

使用特权

评论回复
地板
powerantone|  楼主 | 2025-4-14 16:41 | 只看该作者
总结和实践建议
嵌入式软件开发的时候,需要特别注意硬件资源限制,系统实时性要求,硬件交互特性等等,建议初学者养成以下习惯,用以规避上述错误,从而写出更加高效可靠的嵌入式软件代码。
防御性编程:对所有指针解引用、内存分配、外设操作添加有效性检查。
工具链利用:使用静态分析工具(如 Coverity)、调试器(JTAG)和示波器辅助开发。
硬件理解:深入研读芯片数据手册,掌握寄存器位定义和外设时序要求。
代码规范:遵循 MISRA-C 标准,避免未定义行为。
测试策略:编写单元测试覆盖边界条件,使用硬件仿真验证实时性。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

607

主题

3157

帖子

4

粉丝