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

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


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

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

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

内聚类型 说明 嵌入式中的坏味/好味
偶然内聚 模块内元素毫无关联,只是凑在一起 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(板级支持包)抽象。

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

相关推荐
科芯创展2 小时前
1A,1MHz,30VIN,XZ4115,降压恒流LED驱动芯片
单片机·嵌入式硬件
集芯微电科技有限公司2 小时前
四通道2A输出集成功率电感降压模块专为紧凑型方案设计
人工智能·单片机·嵌入式硬件·生成对抗网络·计算机外设
踏着七彩祥云的小丑2 小时前
嵌入式测试学习第 37 天:异常场景测试:断电、拔插、干扰、非法指令
单片机·嵌入式硬件·学习
QK_002 小时前
C语言 static 关键字三大作用
c语言·开发语言
隔窗听雨眠2 小时前
C语言函数递归从入门到精通(下):性能优化与工程实践
c语言·算法·性能优化
意法半导体STM323 小时前
【官方原创】如何为STM32CubeMX2配置Visual Studio Code配置方案
vscode·stm32·单片机·嵌入式硬件·策略模式·stm32cubemx·嵌入式开发
雾削木4 小时前
B语言经典教程现代化重构
java·前端·stm32·单片机·嵌入式硬件
Hello-FPGA4 小时前
Camera Link 与 CoaXPress 技术对比 如何选择你的相机接口
单片机·嵌入式硬件
项目題供诗5 小时前
STM32-USART串口协议(二十二)
stm32·单片机·嵌入式硬件