嵌入式C开发编程原则完全指南

嵌入式C开发编程原则完全指南

从基础原则到工程实践,帮助你写出简洁、可靠、易维护的嵌入式代码


前言

在嵌入式软件开发中,我们面临着独特的挑战:资源受限、实时性要求、硬件直接操作、长期维护等。这些特点使得编程原则的应用变得尤为重要。本文将系统介绍经典的软件工程原则,并结合嵌入式C语言的特点,提供实用的指导建议。

适用范围: 单片机开发、RTOS应用、嵌入式Linux、物联网设备、工控系统


目录

一、核心设计原则

  1. 过早优化是万恶之源
  2. KISS原则:保持简单
  3. 奥卡姆剃刀定律:如无必要勿增实体
  4. YAGNI原则:你不会需要它
  5. DRY原则:不要重复自己

二、代码组织原则

  1. 单一职责原则(SRP)
  2. 高内聚低耦合
  3. 面向接口编程

三、可读性与可维护性

  1. 自解释的代码
  2. 防御性编程
  3. 错误处理原则

四、嵌入式专属考虑

  1. 资源受限下的设计权衡
  2. 实时性与确定性
  3. 可移植性设计

一、过早优化是万恶之源

1.1 原则起源

“Premature optimization is the root of all evil.”
—— Donald Knuth, 1974

这是计算机科学泰斗高德纳(Donald Knuth)在1974年发表的经典论文《Structured Programming with go to Statements》中提出的观点。完整的原文是:

“We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%.”

翻译: 在97%的情况下,我们应该忘记那些微小的效率改进:过早优化是万恶之源。但我们不应该错过那关键的3%的优化机会。

1.2 核心思想

开发三步法则(Kent Beck):

  1. Make it work(先让它工作)

    • 实现功能,保证正确性
    • 通过测试验证
    • 不考虑性能细节
  2. Make it right(再让它正确)

    • 重构代码结构
    • 提高可读性
    • 消除明显问题
  3. Make it fast(最后让它快速)

    • 基于性能测试数据
    • 只优化真正的瓶颈
    • 保持代码可维护性

1.3 嵌入式开发中的陷阱

❌ 常见的过早优化陷阱:

/* 陷阱1:为了"节省内存"使用位域 */
typedef struct {
    uint8_t flag1 : 1;      // 节省了7位,但...
    uint8_t flag2 : 1;      // 增加了位操作开销
    uint8_t flag3 : 1;      // 降低了可读性
    uint8_t mode  : 2;      // 难以调试
    uint8_t state : 3;      // 可移植性差
} OverOptimizedFlags_t;     // 省了3字节,值得吗?

/* 陷阱2:手动优化循环 */
for (int i = 0; i < n; i++)
{
    array[i] = value;       // 清晰直观
}

// 过度优化为:
int i = n;
while (i--) {               // 稍快,但可读性差
    array[i] = value;
}

/* 陷阱3:宏替代函数 */
#define MAX(a, b) ((a) > (b) ? (a) : (b))
// 问题:参数可能被多次求值
int x = MAX(foo(), bar());  // foo()和bar()可能被调用多次!

✅ 正确的做法:

/* 方案1:先保持简单 */
typedef struct {
    bool flag1;             // 清晰
    bool flag2;             // 易读
    bool flag3;             // 易调试
    uint8_t mode;           // 可移植
    uint8_t state;          // 如果内存真的不够,再优化
} SimpleFlags_t;

/* 方案2:使用标准库 */
memset(array, value, n * sizeof(array[0]));  // 编译器会优化

/* 方案3:使用内联函数 */
static inline int Max(int a, int b)
{
    return (a > b) ? a : b;  // 类型安全,无副作用
}

1.4 何时应该优化?

优化的前提条件:

应该优化:

  1. 有明确的性能问题(实测数据)
  2. 找到了真正的瓶颈(profiling工具)
  3. 优化后仍保持可读性
  4. 有性能测试对比数据

不应该优化:

  1. 没有性能问题就优化
  2. 基于猜测而非测量
  3. 牺牲可读性换取微小性能提升
  4. 为了"可能的未来需求"而优化

嵌入式场景下的判断标准:

场景是否优化理由
中断服务函数(ISR)✅ 是实时性要求高
1ms定时任务⚠️ 测量后决定看CPU占用率
初始化函数❌ 否只执行一次,无需优化
每秒调用一次的函数❌ 否不是瓶颈
DMA传输设置✅ 是硬件相关,影响系统性能
UI显示更新❌ 否100ms刷新一次,足够快

二、KISS原则:保持简单

2.1 原则定义

KISS = Keep It Simple, Stupid

最早由美国海军在1960年提出,核心思想是:大多数系统如果保持简单而不是复杂,会工作得更好。

相关格言:

  • “Simplicity is the ultimate sophistication.” —— Leonardo da Vinci
    简单是终极的复杂

  • “Everything should be made as simple as possible, but not simpler.” —— Albert Einstein
    凡事应尽可能简单,但不能过于简单

  • “Less is more.” —— Ludwig Mies van der Rohe
    少即是多

2.2 函数设计的简单之道

2.2.1 参数数量

规则: 函数参数不超过3-5个

/* ❌ 参数过多 */
void ConfigSensor(
    uint8_t addr,
    uint8_t mode,
    uint16_t threshold,
    uint16_t hysteresis,
    bool enable_interrupt,
    bool enable_filter,
    uint8_t filter_level,
    uint32_t timeout)        // 8个参数,难以记忆!
{
    // ...
}

/* ✅ 使用结构体封装 */
typedef struct {
    uint16_t threshold;
    uint16_t hysteresis;
    uint8_t filter_level;
    bool enable_interrupt;
    bool enable_filter;
} SensorConfig_t;

void ConfigSensor(uint8_t addr, const SensorConfig_t *config)
{
    // 只有2个参数,清晰明了
}

嵌入式应用: HAL库就是这样设计的

// STM32 HAL库的设计
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);  // 2个参数
HAL_UART_Transmit(&huart1, data, size, timeout);  // 4个参数
2.2.2 函数长度

规则: 一个函数不超过50行(最多100行)

/* ❌ 函数过长 */
void ProcessData(void)
{
    // 200行代码
    // 做了10件不同的事情
    // 难以理解和维护
}

/* ✅ 拆分为多个函数 */
void ProcessData(void)
{
    if (!ValidateInput())
    {
        HandleError();
        return;
    }
    
    uint8_t *buffer = AllocateBuffer();
    if (buffer == NULL)
    {
        return;
    }
    
    ReadData(buffer);
    TransformData(buffer);
    SendData(buffer);
    FreeBuffer(buffer);
}

2.3 控制流的简化

2.3.1 减少嵌套层次

规则: 嵌套不超过3层

/* ❌ 深层嵌套 */
void CheckStatus(void)
{
    if (device_ready)
    {
        if (data_available)
        {
            if (buffer_not_full)
            {
                if (checksum_valid)
                {
                    // 终于到达这里!
                    ProcessData();
                }
            }
        }
    }
}

/* ✅ 提前返回(Guard Clauses)*/
void CheckStatus(void)
{
    if (!device_ready) return;
    if (!data_available) return;
    if (buffer_full) return;
    if (!checksum_valid) return;
    
    // 主逻辑清晰可见
    ProcessData();
}

嵌入式应用:中断服务函数

/* ISR中使用提前返回 */
void EXTI0_IRQHandler(void)
{
    /* 检查标志位 */
    if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) == RESET)
    {
        return;  // 不是我的中断,直接返回
    }
    
    /* 清除中断标志 */
    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
    
    /* 处理中断 */
    g_button_pressed = true;
}

三、奥卡姆剃刀定律:如无必要勿增实体

3.1 原则起源

奥卡姆剃刀(Occam’s Razor)

“Entities should not be multiplied without necessity.”
如无必要,勿增实体。

14世纪哲学家奥卡姆的威廉(William of Occam)提出,意思是:在众多解释中,最简单的往往是正确的。

在编程中的含义:

  • 不要添加不必要的抽象层
  • 不要引入不必要的复杂性
  • 选择最简单的解决方案

3.2 避免过度设计

3.2.1 不必要的抽象层
/* ❌ 过度抽象:3层封装只为读一个寄存器 */
typedef struct {
    void (*read)(void*);
    void (*write)(void*, uint32_t);
    void *context;
} GenericRegister_t;

typedef struct {
    GenericRegister_t base;
    uint32_t offset;
} SpecificRegister_t;

uint32_t ReadRegister(SpecificRegister_t *reg)
{
    return reg->base.read(reg->base.context);
}

/* ✅ 直接访问 */
#define LED_GPIO_PORT    GPIOA
#define LED_GPIO_PIN     GPIO_PIN_5

void LED_On(void)
{
    HAL_GPIO_WritePin(LED_GPIO_PORT, LED_GPIO_PIN, GPIO_PIN_SET);
}

嵌入式原则: 除非需要支持多个硬件平台,否则直接访问更清晰

3.2.2 不必要的设计模式
/* ❌ 过度使用设计模式:单例模式 */
typedef struct {
    uint8_t data;
    bool initialized;
} Sensor_t;

static Sensor_t *s_instance = NULL;

Sensor_t* Sensor_GetInstance(void)
{
    if (s_instance == NULL)
    {
        s_instance = malloc(sizeof(Sensor_t));  // 嵌入式中避免malloc!
        s_instance->initialized = false;
    }
    return s_instance;
}

/* ✅ 简单的全局变量 */
static Sensor_t s_sensor = {0};

void Sensor_Init(void)
{
    s_sensor.data = 0;
    // 初始化传感器
}

uint8_t Sensor_Read(void)
{
    return s_sensor.data;
}

嵌入式原则: 单片机通常只有一个实例,使用静态全局变量即可

3.3 简化数据结构

/* ❌ 过度设计:为了"灵活性" */
typedef struct Node {
    void *data;                    // 泛型指针
    size_t data_size;              // 数据大小
    int type;                      // 类型标记
    struct Node *next;             // 链表
    struct Node *prev;             // 双向链表
    void (*destructor)(void*);     // 析构函数
    void *user_data;               // 用户数据
} GenericNode_t;

/* ✅ 简单直接:只保留需要的 */
typedef struct {
    uint8_t data[32];              // 固定大小
    uint8_t length;                // 实际长度
    bool valid;                    // 有效标志
} DataPacket_t;

四、YAGNI原则:你不会需要它

4.1 原则定义

YAGNI = You Aren’t Gonna Need It

来源于极限编程(XP),核心思想:

不要实现当前不需要的功能。

4.2 常见的YAGNI违反

4.2.1 "将来可能需要"的功能
/* ❌ 提前实现未来功能 */
typedef struct {
    uint8_t sensor1_data;          // 当前使用
    uint8_t sensor2_data;          // 当前使用
    uint8_t sensor3_data;          // 预留,可能以后用
    uint8_t sensor4_data;          // 预留,可能以后用
    uint8_t sensor5_data;          // 预留,可能以后用
    uint16_t reserved[10];         // 保留字段
} SensorData_t;                    // 浪费内存!

void ProcessAllSensors(SensorData_t *data)
{
    ProcessSensor1(data->sensor1_data);
    ProcessSensor2(data->sensor2_data);
    // sensor3-5的处理函数还没写,但结构体已经占用了内存
}

/* ✅ 只实现当前需要的 */
typedef struct {
    uint8_t sensor1_data;
    uint8_t sensor2_data;
} SensorData_t;  // 需要时再扩展
4.2.2 "通用化"的陷阱
/* ❌ 过度通用化:支持所有可能的情况 */
void ConfigurePort(
    GPIO_TypeDef *port,
    uint16_t pin,
    uint32_t mode,
    uint32_t pull,
    uint32_t speed,
    uint32_t alternate,  // 当前项目根本不用alternate功能
    uint32_t options)    // 什么options?没人知道
{
    // 一大堆复杂的配置逻辑
}

/* ✅ 只实现需要的功能 */
void LED_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = GPIO_PIN_5;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

void Button_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = GPIO_PIN_0;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}

4.3 何时违反YAGNI

可以提前设计的情况:

硬件接口设计

/* PCB已经设计好,预留了4个LED,虽然当前只用2个 */
#define LED1_PIN    GPIO_PIN_0
#define LED2_PIN    GPIO_PIN_1
#define LED3_PIN    GPIO_PIN_2  // 预留
#define LED4_PIN    GPIO_PIN_3  // 预留

// 可以定义宏,但不实现不用的函数

协议预留字段

/* 通信协议需要考虑扩展性 */
typedef struct {
    uint8_t header;        // 0xAA
    uint8_t version;       // 版本号,为将来升级预留
    uint8_t length;
    uint8_t data[64];
    uint8_t checksum;
} Protocol_t;

五、DRY原则:不要重复自己

5.1 原则定义

DRY = Don’t Repeat Yourself

来源于《程序员修炼之道》,核心思想:

每个知识点在系统中应该有且只有一个明确的、权威的表示。

5.2 代码重复的类型

5.2.1 完全重复(应该消除)
/* ❌ 相同的业务逻辑重复 */
float ReadSensor1(void)
{
    uint16_t adc = HAL_ADC_GetValue(&hadc1);
    float voltage = adc * 3.3f / 4096.0f;
    return voltage * 100.0f;  // 转换为温度
}

float ReadSensor2(void)
{
    uint16_t adc = HAL_ADC_GetValue(&hadc2);
    float voltage = adc * 3.3f / 4096.0f;  // 完全相同的公式
    return voltage * 100.0f;  // 完全相同的转换
}

/* ✅ 提取公共逻辑 */
float ADC_ToVoltage(uint16_t adc)
{
    return adc * 3.3f / 4096.0f;
}

float Voltage_ToTemperature(float voltage)
{
    return voltage * 100.0f;
}

float ReadSensor(ADC_HandleTypeDef *hadc)
{
    uint16_t adc = HAL_ADC_GetValue(hadc);
    float voltage = ADC_ToVoltage(adc);
    return Voltage_ToTemperature(voltage);
}
5.2.2 结构相似(权衡是否消除)
/* ⚠️ 结构相似,但上下文不同 */
void Init_UART1(void)
{
    __HAL_RCC_USART1_CLK_ENABLE();
    
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

void Init_UART2(void)
{
    __HAL_RCC_USART2_CLK_ENABLE();
    
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = GPIO_PIN_2 | GPIO_PIN_3;  // 不同引脚
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF7_USART2;   // 不同alternate
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

// 这种情况可以保持分开,因为:
// 1. 代码量小(10行左右)
// 2. 硬件配置不同
// 3. 合并会增加参数复杂度
// 4. 分开更清晰

5.3 配置数据的复用

/* ✅ 使用配置表 */
typedef struct {
    GPIO_TypeDef *port;
    uint16_t pin;
    uint8_t active_level;  // 0=低电平有效,1=高电平有效
} LED_Config_t;

static const LED_Config_t LED_Configs[] = {
    {GPIOA, GPIO_PIN_0, 1},  // LED1
    {GPIOA, GPIO_PIN_1, 1},  // LED2
    {GPIOB, GPIO_PIN_5, 0},  // LED3,低电平有效
};

void LED_On(uint8_t led_id)
{
    const LED_Config_t *cfg = &LED_Configs[led_id];
    GPIO_PinState state = cfg->active_level ? GPIO_PIN_SET : GPIO_PIN_RESET;
    HAL_GPIO_WritePin(cfg->port, cfg->pin, state);
}

5.4 魔数的消除

/* ❌ 魔数散布在代码中 */
void DelayMs(uint32_t ms)
{
    for (uint32_t i = 0; i < ms * 8000; i++)  // 8000是什么?
    {
        __NOP();
    }
}

if (temperature > 50)  // 50是什么意思?
{
    // ...
}

/* ✅ 使用常量 */
#define CPU_FREQ_MHZ        72
#define LOOPS_PER_MS        (CPU_FREQ_MHZ * 1000 / 8)
#define TEMP_WARNING_LEVEL  50  // 摄氏度

void DelayMs(uint32_t ms)
{
    for (uint32_t i = 0; i < ms * LOOPS_PER_MS; i++)
    {
        __NOP();
    }
}

if (temperature > TEMP_WARNING_LEVEL)
{
    TriggerWarning();
}

六、单一职责原则(SRP)

6.1 原则定义

SRP = Single Responsibility Principle

一个模块/函数应该只有一个引起它变化的原因。

Robert C. Martin(Uncle Bob)在SOLID原则中提出。

6.2 函数级别的SRP

/* ❌ 一个函数做太多事情 */
void ProcessSensorData(void)
{
    // 1. 读取传感器
    uint16_t adc = ADC_Read();
    
    // 2. 转换数据
    float value = adc * 3.3f / 4096.0f;
    
    // 3. 滤波
    static float buffer[10];
    float filtered = MovingAverage(value, buffer, 10);
    
    // 4. 判断范围
    if (filtered > 100.0f) {
        // 处理超限
    }
    
    // 5. 保存到EEPROM
    EEPROM_Write(0x1000, (uint8_t*)&filtered, sizeof(filtered));
    
    // 6. 显示
    LCD_Display(filtered);
    
    // 7. 发送到上位机
    UART_Send((uint8_t*)&filtered, sizeof(filtered));
}

/* ✅ 拆分职责 */
float ReadSensor(void)
{
    uint16_t adc = ADC_Read();
    return adc * 3.3f / 4096.0f;
}

float FilterValue(float value)
{
    static float buffer[10];
    return MovingAverage(value, buffer, 10);
}

bool CheckRange(float value)
{
    return (value >= 0.0f && value <= 100.0f);
}

void LogValue(float value)
{
    EEPROM_Write(0x1000, (uint8_t*)&value, sizeof(value));
}

void DisplayValue(float value)
{
    LCD_Display(value);
}

void SendValue(float value)
{
    UART_Send((uint8_t*)&value, sizeof(value));
}

// 主流程清晰
void ProcessSensorData(void)
{
    float raw = ReadSensor();
    float filtered = FilterValue(raw);
    
    if (CheckRange(filtered))
    {
        LogValue(filtered);
        DisplayValue(filtered);
        SendValue(filtered);
    }
}

6.3 模块级别的SRP

嵌入式应用:驱动模块设计

/* ✅ 每个驱动模块只负责一个硬件 */

// led.c - 只负责LED控制
void LED_Init(void);
void LED_On(uint8_t id);
void LED_Off(uint8_t id);
void LED_Toggle(uint8_t id);

// button.c - 只负责按键检测
void Button_Init(void);
bool Button_IsPressed(uint8_t id);

// uart.c - 只负责串口通信
void UART_Init(uint32_t baudrate);
void UART_Send(const uint8_t *data, uint16_t len);
uint16_t UART_Receive(uint8_t *buffer, uint16_t size);

// sensor.c - 只负责传感器数据采集
void Sensor_Init(void);
float Sensor_Read(void);

七、高内聚低耦合

7.1 原则定义

内聚(Cohesion): 模块内部元素的相关性
耦合(Coupling): 模块之间的依赖程度

目标:

  • 高内聚:模块内功能紧密相关
  • 低耦合:模块间依赖尽可能少

7.2 降低耦合的方法

7.2.1 避免全局变量
/* ❌ 过度使用全局变量 */
float g_temperature;  // 全局变量
float g_humidity;
bool g_sensor_error;

void ReadTemperature(void)
{
    g_temperature = ADC_Read() * 0.1f;
}

void ProcessData(void)
{
    if (g_temperature > 50.0f)  // 依赖全局变量
    {
        g_sensor_error = true;
    }
}

/* ✅ 使用参数和返回值 */
typedef struct {
    float temperature;
    float humidity;
    bool error;
} SensorData_t;

void ReadSensorData(SensorData_t *data)
{
    if (data == NULL) return;
    
    data->temperature = ADC_Read() * 0.1f;
    data->humidity = ADC_Read() * 0.2f;
    data->error = false;
}

void ProcessData(const SensorData_t *data)
{
    if (data == NULL) return;
    
    if (data->temperature > 50.0f)
    {
        TriggerAlarm();
    }
}
7.2.2 使用接口隔离
/* ✅ 通过接口访问,而非直接访问全局变量 */

// sensor.c
static SensorData_t s_sensor_data = {0};  // 私有数据

void Sensor_Init(void)
{
    memset(&s_sensor_data, 0, sizeof(s_sensor_data));
}

// 只暴露必要的接口
float Sensor_GetTemperature(void)
{
    return s_sensor_data.temperature;
}

float Sensor_GetHumidity(void)
{
    return s_sensor_data.humidity;
}

bool Sensor_HasError(void)
{
    return s_sensor_data.error;
}

7.3 提高内聚的方法

将相关功能组织在一起:

/* ✅ 按功能模块组织 */

// pwm_output.c - PWM输出相关的所有功能
typedef struct {
    uint32_t frequency;
    uint8_t duty_cycle;
    bool enabled;
} PWM_Config_t;

static PWM_Config_t s_pwm_config = {0};

void PWM_Init(void);
void PWM_SetFrequency(uint32_t freq);
void PWM_SetDutyCycle(uint8_t duty);
void PWM_Start(void);
void PWM_Stop(void);
uint32_t PWM_GetFrequency(void);
uint8_t PWM_GetDutyCycle(void);

八、面向接口编程

8.1 C语言中的接口

C语言没有interface关键字,但可以使用:

  • 函数指针
  • 结构体+函数指针
  • 头文件定义接口

8.2 使用函数指针实现多态

/* ✅ 传感器接口定义 */
typedef struct {
    void (*init)(void);
    float (*read)(void);
    const char* name;
} Sensor_Interface_t;

// 温度传感器实现
void TempSensor_Init(void)
{
    // 初始化温度传感器
}

float TempSensor_Read(void)
{
    return ADC_Read() * 0.1f;  // 温度转换
}

const Sensor_Interface_t TempSensor = {
    .init = TempSensor_Init,
    .read = TempSensor_Read,
    .name = "Temperature"
};

// 湿度传感器实现
void HumiSensor_Init(void)
{
    // 初始化湿度传感器
}

float HumiSensor_Read(void)
{
    return ADC_Read() * 0.2f;  // 湿度转换
}

const Sensor_Interface_t HumiSensor = {
    .init = HumiSensor_Init,
    .read = HumiSensor_Read,
    .name = "Humidity"
};

// 通用处理函数
void ProcessSensor(const Sensor_Interface_t *sensor)
{
    if (sensor == NULL) return;
    
    sensor->init();
    float value = sensor->read();
    printf("%s: %.2f\n", sensor->name, value);
}

// 使用
void main(void)
{
    ProcessSensor(&TempSensor);
    ProcessSensor(&HumiSensor);
}

8.3 HAL抽象层示例

STM32 HAL库就是面向接口编程的典范:

// 所有UART都实现相同的接口
HAL_UART_Transmit(&huart1, data, len, timeout);
HAL_UART_Transmit(&huart2, data, len, timeout);
HAL_UART_Transmit(&huart3, data, len, timeout);

// 所有GPIO都实现相同的接口
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);

九、自解释的代码

9.1 命名的重要性

好的命名 > 注释

/* ❌ 需要注释才能理解 */
int d;  // 延时时间(毫秒)
int t;  // 温度
int f;  // 标志位

void p(int x) {  // 处理数据
    // ...
}

/* ✅ 自解释的命名 */
int delay_ms;  // 或 delayInMilliseconds
int temperature_celsius;
bool is_sensor_ready;

void ProcessSensorData(int raw_value) {
    // ...
}

9.2 魔数的替代

/* ❌ 魔数 */
if (status & 0x08) {  // 0x08是什么意思?
    // ...
}

for (int i = 0; i < 100; i++) {  // 100是什么?
    buffer[i] = 0;
}

/* ✅ 使用常量 */
#define STATUS_READY_BIT    0x08
#define BUFFER_SIZE         100

if (status & STATUS_READY_BIT) {
    // 清晰明了
}

for (int i = 0; i < BUFFER_SIZE; i++) {
    buffer[i] = 0;
}

9.3 避免缩写

/* ❌ 过度缩写 */
void cfg_tmp_sns(void);  // 什么意思?
int calc_avg_val(int *arr, int n);  // calc? avg? val?

/* ✅ 完整单词 */
void ConfigureTemperatureSensor(void);
int CalculateAverageValue(int *array, int count);

/* ✅ 公认的缩写可以使用 */
int adc_value;  // ADC是公认缩写
int uart_baudrate;  // UART是公认缩写
int max_temperature;  // max/min是常见缩写

十、防御性编程

10.1 输入参数检查

/* ✅ 检查所有指针参数 */
void ProcessData(const uint8_t *data, uint16_t length)
{
    // 参数校验
    if (data == NULL)
    {
        return;  // 或返回错误码
    }
    
    if (length == 0 || length > MAX_DATA_SIZE)
    {
        return;
    }
    
    // 正常处理
    for (uint16_t i = 0; i < length; i++)
    {
        ProcessByte(data[i]);
    }
}

10.2 数组边界检查

/* ❌ 没有边界检查 */
void SetValue(int index, int value)
{
    g_array[index] = value;  // 危险!可能越界
}

/* ✅ 边界检查 */
#define ARRAY_SIZE 10

void SetValue(int index, int value)
{
    if (index < 0 || index >= ARRAY_SIZE)
    {
        // 记录错误
        return;
    }
    
    g_array[index] = value;
}

10.3 断言的使用

/* ✅ 使用断言检查前置条件 */
#include <assert.h>

void ConfigureClock(uint32_t frequency)
{
    // 开发阶段的断言
    assert(frequency >= 1000000);   // 至少1MHz
    assert(frequency <= 72000000);  // 不超过72MHz
    
    // 生产环境的检查
    if (frequency < 1000000 || frequency > 72000000)
    {
        // 使用默认值
        frequency = 8000000;
    }
    
    // 配置时钟
    RCC_SetFrequency(frequency);
}

十一、错误处理原则

11.1 返回值vs错误码

/* ✅ 方案1:返回错误码 */
typedef enum {
    SENSOR_OK = 0,
    SENSOR_ERROR_TIMEOUT,
    SENSOR_ERROR_INVALID_DATA,
    SENSOR_ERROR_NOT_READY
} SensorError_t;

SensorError_t Sensor_Read(float *out_value)
{
    if (out_value == NULL)
    {
        return SENSOR_ERROR_INVALID_DATA;
    }
    
    if (!Sensor_IsReady())
    {
        return SENSOR_ERROR_NOT_READY;
    }
    
    *out_value = ADC_Read() * 0.1f;
    return SENSOR_OK;
}

// 使用
float temperature;
SensorError_t err = Sensor_Read(&temperature);
if (err != SENSOR_OK)
{
    // 处理错误
    HandleError(err);
}

11.2 统一的错误处理

/* ✅ 全局错误码定义 */
typedef enum {
    ERR_OK = 0,
    ERR_NULL_POINTER,
    ERR_INVALID_PARAM,
    ERR_TIMEOUT,
    ERR_BUSY,
    ERR_NOT_READY,
    ERR_HARDWARE_FAULT
} ErrorCode_t;

/* 错误处理函数 */
void ReportError(ErrorCode_t error, const char *module)
{
    // 记录错误日志
    LogError(error, module);
    
    // 根据严重程度处理
    if (error == ERR_HARDWARE_FAULT)
    {
        SystemReset();  // 硬件故障,重启
    }
}

11.3 嵌入式错误恢复策略

/* ✅ 超时重试机制 */
#define MAX_RETRY_COUNT 3

ErrorCode_t SendDataWithRetry(const uint8_t *data, uint16_t len)
{
    int retry = 0;
    ErrorCode_t err;
    
    while (retry < MAX_RETRY_COUNT)
    {
        err = UART_Transmit(data, len, 1000);  // 1秒超时
        
        if (err == ERR_OK)
        {
            return ERR_OK;  // 成功
        }
        
        retry++;
        Delay(100);  // 重试前延时
    }
    
    return ERR_TIMEOUT;  // 重试失败
}

十二、资源受限下的设计权衡

12.1 内存优化

栈vs堆的选择:

/* ❌ 嵌入式中避免频繁malloc */
void ProcessData(void)
{
    uint8_t *buffer = malloc(1024);  // 危险!
    if (buffer == NULL) return;
    
    // 处理数据
    free(buffer);
}

/* ✅ 使用静态内存 */
#define BUFFER_SIZE 1024
static uint8_t s_buffer[BUFFER_SIZE];

void ProcessData(void)
{
    memset(s_buffer, 0, BUFFER_SIZE);
    // 处理数据
}

12.2 代码空间优化

/* ⚠️ 函数vs宏的权衡 */

// 方案1:函数(推荐)- 可读但占用代码空间
static inline uint16_t GetMax(uint16_t a, uint16_t b)
{
    return (a > b) ? a : b;
}

// 方案2:宏 - 节省空间但不安全
#define GET_MAX(a, b) ((a) > (b) ? (a) : (b))

// 嵌入式建议:优先使用inline函数,编译器会优化

十三、实时性与确定性

13.1 避免不确定的时间开销

/* ❌ 中断中执行耗时操作 */
void UART_RxIRQHandler(void)
{
    uint8_t data = UART_ReadByte();
    
    // 错误:在中断中处理复杂逻辑
    ProcessComplexProtocol(data);  // 耗时未知
    SaveToFlash(data);            // 耗时长
}

/* ✅ 中断只做最小工作 */
#define RX_BUFFER_SIZE 64
static uint8_t s_rx_buffer[RX_BUFFER_SIZE];
static volatile uint16_t s_rx_head = 0;
static volatile uint16_t s_rx_tail = 0;

void UART_RxIRQHandler(void)
{
    uint8_t data = UART_ReadByte();
    
    // 只接收数据放入缓冲区
    uint16_t next = (s_rx_head + 1) % RX_BUFFER_SIZE;
    if (next != s_rx_tail)
    {
        s_rx_buffer[s_rx_head] = data;
        s_rx_head = next;
    }
}

// 主循环处理
void main_loop(void)
{
    while (s_rx_tail != s_rx_head)
    {
        uint8_t data = s_rx_buffer[s_rx_tail];
        s_rx_tail = (s_rx_tail + 1) % RX_BUFFER_SIZE;
        
        ProcessComplexProtocol(data);
    }
}

十四、可移植性设计

14.1 类型的可移植性

/* ❌ 依赖平台的类型 */
int value;      // 16位还是32位?
long counter;   // 32位还是64位?
char status;    // 有符号还是无符号?

/* ✅ 使用标准固定宽度类型 */
#include <stdint.h>

uint32_t value;   // 明确32位
int16_t offset;   // 明确16位有符号
uint8_t status;   // 明确8位无符号

14.2 HAL层抽象

/* ✅ 硬件抽象层设计 */

// hal_gpio.h - 接口定义
typedef enum {
    GPIO_PIN_RESET = 0,
    GPIO_PIN_SET = 1
} GPIO_PinState_t;

void HAL_GPIO_WritePin(uint8_t pin, GPIO_PinState_t state);
GPIO_PinState_t HAL_GPIO_ReadPin(uint8_t pin);

// hal_gpio_stm32.c - STM32实现
void HAL_GPIO_WritePin(uint8_t pin, GPIO_PinState_t state)
{
    if (state == GPIO_PIN_SET)
        GPIOA->BSRR = (1 << pin);
    else
        GPIOA->BRR = (1 << pin);
}

// hal_gpio_esp32.c - ESP32实现
void HAL_GPIO_WritePin(uint8_t pin, GPIO_PinState_t state)
{
    gpio_set_level(pin, state);
}

总结与建议

编程原则的优先级

  1. 首要原则(必须遵守)

    • 防御性编程(安全第一)
    • 错误处理(可靠性)
    • 自解释的代码(可维护性)
  2. 核心原则(强烈推荐)

    • KISS原则
    • 单一职责
    • 避免过早优化
  3. 高级原则(视情况应用)

    • DRY原则(权衡复杂度)
    • 面向接口编程
    • 奥卡姆剃刀

嵌入式开发的特殊考虑

方面通用软件嵌入式软件
内存充足受限,慎用动态分配
性能优化次要关键路径必须优化
实时性无严格要求必须保证确定性
可移植性Nice to have常需要跨平台
代码重复尽量避免适度接受(简单优先)

实践建议

  1. 从简单开始

    • 先让代码工作
    • 再追求优雅
    • 最后优化性能
  2. 持续重构

    • 小步迭代
    • 保持测试
    • 及时清理
  3. 团队协作

    • 统一编码规范
    • 代码审查
    • 知识分享
  4. 工具辅助

    • 静态分析(Cppcheck)
    • 代码格式化(clang-format)
    • 版本控制(Git)

延伸阅读

经典书籍

  • 《代码大全》(Code Complete)- Steve McConnell
  • 《程序员修炼之道》(The Pragmatic Programmer)
  • 《重构》(Refactoring)- Martin Fowler
  • 《嵌入式C语言设计模式》

编码规范

  • MISRA C(汽车行业标准)
  • BARR-C(嵌入式C编码标准)
  • Linux内核编码规范
  • Google C++风格指南

在线资源

  • Embedded Artistry Blog
  • Interrupt by Memfault
  • Stack Overflow嵌入式标签

本文完

记住:简单、可靠、可维护的代码比聪明的代码更有价值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值