编程基础 --高内聚,低耦合

在嵌入式系统里,硬件错综复杂、时序要求严格,高内聚低耦合是让你的代码从"能跑"进化到"能维护、可移植、可测试"的关键设计准则。我们把它拆开来看。


一、高内聚:一个模块就做好一件事

内聚衡量一个模块内部各个元素之间的关联紧密程度。高内聚意味着模块内部的函数和数据都是为了完成一个清晰、单一的任务而存在,就像一个"专业团队"。

从差到好,内聚大致分这几个层次(你可以在自己的代码里对号入座):

内聚类型 说明 嵌入式中的坏味/好味
偶然内聚 模块内元素毫无关联,只是凑在一起 utils.c 里塞了毫秒延时、CRC校验、字符串转换、引脚翻转...
逻辑内聚 逻辑上类似但功能不同,靠参数选择 一个 Peripheral_Write(p, addr, data) 同时处理I2C、SPI、UART,内部 switch-case
时间内聚 在同一时间段执行的动作放在一起 把所有"初始化"代码扔进 System_Init() 里,不分模块
通信内聚 操作同一块数据 串口环形队列的入队、出队、清空都在 ringbuf.c
顺序内聚 前一步的输出是后一步的输入 传感器数据滤波 → 校准补偿 → 工程单位转换,组成一个数据处理流水线
功能内聚 模块所有元素为完成单一功能而存在 温湿度传感器模块只负责与温湿度传感器相关的一切

嵌入式高内聚的体现:

  • 一个传感器驱动模块内部,包含了该传感器的识别、初始化、读写寄存器、校准计算、自检,而且只包含这些
  • 它的 .h 文件中不会暴露内部用到的 I2C 地址、寄存器地址等细节。
  • 所有外部需要的功能都通过明确的 API 提供,如 Humidity_Init()Humidity_Read(float *rh)

二、低耦合:模块之间尽量不"亲密"

耦合 衡量模块之间的依赖程度。低耦合意味着模块之间只需要知道最少的"公共约定"(接口),修改一个模块不会导致连锁反应。在嵌入式里,最大的耦合源往往是硬件依赖全局数据

从坏到好,耦合也分几等:

耦合类型 说明 嵌入式举例
内容耦合 直接访问对方模块内部数据或代码 应用代码直接改写串口驱动的发送缓冲区指针
公共耦合 共享全局变量 多个任务读写同一个全局 g_sensor_data,没有保护
控制耦合 通过控制标志影响对方逻辑 Motor_Control(STOP, 0) 然后对方根据 cmd 执行不同行为
数据耦合 通过参数传递简单数据 Temperature_Get(float *value),只交换数据值
非直接耦合 模块间基本无关系 按键模块和显示屏驱动完全独立

嵌入式低耦合的典型做法:

  • 硬件抽象层 :应用逻辑不直接操作 I2C 寄存器,而是调用 TempSensor_Read(),这个函数内部再去调 I2C 驱动。换一个同功能传感器,只需替换这个模块的实现。
  • 消息通信:在 RTOS 中,任务之间传递数据使用队列、邮箱,而不是裸用全局变量。发送方和接收方只依赖队列句柄,双方内部实现可以独立变化。
  • 依赖倒置 :高层的报警逻辑不依赖"LED是GPIO PA8",而是依赖一个 AlertIndicator_On() 接口。LED、蜂鸣器、闪屏都可以实现该接口。

三、正反对比:一个温湿度监控的例子

假设你要做一个小设备:采集 SHT30 温湿度,每 1 秒打印到串口,当温度超过 30°C 时点亮红色 LED。

❌ 低内聚高耦合的设计(常见初学者写法)
c 复制代码
// main.c 里一锅粥
float temp, hum;
uint8_t buf[6];

void Task_Sensor(void *pvParameters) {
    while(1) {
        // 直接操作 I2C,写死了 SHT30 地址和寄存器
        buf[0] = 0x2C << 1;  // 器件地址,假设是I2C1
        // ... 一堆 I2C 读写代码 ...
        temp = -45 + 175 * (rawT / 65535.0);
        hum = 100 * (rawH / 65535.0);

        // 串口输出写在这里
        printf("Temp: %.2f, Hum: %.2f\r\n", temp, hum);

        // LED 控制直接操作 GPIO
        if (temp > 30.0) {
            HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_SET);
        } else {
            HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_RESET);
        }
        vTaskDelay(1000);
    }
}

问题:传感器、输出、报警全粘在一起。换传感器型号 → 要改这个任务;改用屏幕显示 → 要改任务;LED 改蜂鸣器 → 还是要改。而且无法单独测试传感器驱动。

✅ 高内聚低耦合的设计

1. 传感器模块(高内聚)

c 复制代码
// sht30.h - 对外的干净接口
bool SHT30_Init(I2C_HandleTypeDef *hi2c);
bool SHT30_Read(float *temperature, float *humidity);

// sht30.c - 内部实现所有细节
static I2C_HandleTypeDef *sht30_i2c;
static uint8_t sht30_addr = 0x44;   // 内聚在这里

bool SHT30_Read(float *t, float *h) {
    uint8_t cmd[2] = {0x2C, 0x06};
    uint8_t data[6];
    // 所有 I2C 操作、CRC 校验、公式转换,都封装在此
    // ...
    return true;
}

2. 报警模块(高内聚)

c 复制代码
// alert.h
typedef enum { ALERT_OFF, ALERT_ON } AlertState;
void Alert_Init(void);
void Alert_Set(AlertState state);   // 外部只需调用这个

// alert.c - 当前实现是LED,但内聚在模块内部
void Alert_Set(AlertState state) {
    HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, 
                      state == ALERT_ON ? GPIO_PIN_SET : GPIO_PIN_RESET);
}

3. 应用层(低耦合地把它们组合起来)

c 复制代码
// 只依赖接口,不依赖内部实现
void Task_Sensor(void *pvParameters) {
    float temp, hum;
    while(1) {
        if (SHT30_Read(&temp, &hum)) {
            Log_Printf("Temp: %.2f, Hum: %.2f", temp, hum);
            Alert_Set(temp > 30.0 ? ALERT_ON : ALERT_OFF);
        }
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

现在,把 SHT30 换成 SHT40,只换 sht30.c;把 LED 报警换成蜂鸣器,只换 alert.c。应用层一行不改。传感器驱动可以脱离硬件,在 PC 上用 Mock 的 I2C 进行单元测试。


四、嵌入式中的特殊解耦陷阱

  • 时序耦合 :任务A必须等任务B执行完某一步,通常用信号量或事件组解耦,而不是用固定的 vTaskDelay 赌顺序。
  • 中断与任务耦合:ISR 只做必要的事(如清标志、发信号量、压入数据到队列),把数据处理"推"给任务。这是低耦合的核心实践。
  • 跨平台耦合:不要让业务逻辑包含任何特定芯片的寄存器操作,通过 BSP(板级支持包)抽象。

一句话总结:高内聚让每个模块成为一个"责任明确的专家",低耦合让这些专家通过"标准化的合同(接口)"协作,而不是互相干涉内部事务。从你手头的项目挑一个耦合最严重的"上帝文件",用这个原则重构一次,体会会特别深。

相关推荐
风雨中的蜜蜂5 小时前
CMT2310A 超低功耗高性能射频收发器概述
stm32·单片机·嵌入式硬件
麻瓜老宋6 小时前
AI开发C语言应用按步走,表达式计算器calc的第二十步,魔法数修复、测试分隔符、位运算截断检查、进制溢出检查
c语言·开发语言·atomcode
冻柠檬飞冰走茶6 小时前
PTA基础编程题目集 7-11 分段计算居民水费(C语言实现)
c语言·开发语言·算法
国科安芯7 小时前
基于MOSFET工作电压、电流特性的选型设计
科技·stm32·单片机·嵌入式硬件·系统架构
wuyk5557 小时前
68.嵌入式 C 语言避坑指南:volatile 关键字 —— 单片机中断、寄存器访问的核心技巧
c语言·开发语言·stm32·单片机·嵌入式硬件
qz_Serene7 小时前
C语言:编译和链接
c语言·开发语言
麻瓜老宋8 小时前
AI开发C语言应用按步走,表达式计算器calc的第十九步,注释、表达式分隔符、long double 精度
c语言·开发语言·atomcode
Zevalin爱灰灰20 小时前
能源与电源管理电路——防倒灌电路
单片机·嵌入式硬件·电源管理
jianqiang.xue1 天前
ESP-IDF保姆级入门02|Ubuntu24.04搭建EIM-GUI开发环境,打通Windows_Linux双平台统一开发
stm32·单片机·物联网
ziguo11221 天前
深入浅出 C/C++ 数据类型:从入门到踩坑
linux·c语言·c++·windows·visual studio