在嵌入式程序开发过程中,如果逻辑直接依赖于某传感器或某计算函数的返回值,极易造成代码高耦合,不利于我们对一个程序进行维护与修改,所以通过合理的解耦降低耦合度是开发中非常重要的一环,我个人在开发中常用的解耦方式如下。

在裸机编程(Bare-Metal)下,最常用的方式是状态码+返回值的处理形式,通过函数返回状态码,传入输出类型变量指针的形式获取数据,示例如下:

typedef enum {  
    Sensor_RET_OK = 0,  
    Sensor_RET_ERR,  
    Sensor_RET_TIMEOUT,  
    ...  
} Sensor_RET_Typedef;  
  
// 读取传感器数据  
Sensor_RET_Typedef sensor_read(float* value){  
  
    if (value == NULL){  
        return Sensor_RET_ERR;  
    }  
      
    if (/*超时逻辑*/){  
        return Sensor_RET_TIMEOUT;  
    }  
      
    /* 传感器读取逻辑 */    *value = 7.26f;  
    return Sensor_RET_OK;  
}  
  
// 调用数据  
float temperature;  
  
if (sensor_read(&temperature) == Sensor_RET_OK) {  
    std::cout << temperature << std::endl;  
}

适用场景:

  • 同步调用

  • 驱动接口

  • 返回多个状态类型

优点是状态与业务数据分离,接口清晰,当函数需要返回多个数据时,可以使用结构体对数据进行包装,便于读取数据。

typedef struct {
    float temperature;
    float humidity;
    uint32_t timestamp;
} SensorData;

RetCode sensor_read(SensorData *data)
{
    if (data == NULL) {
        return RET_ERROR;
    }

    data->temperature = 25.5f;
    data->humidity = 60.0f;
    data->timestamp = get_tick();

    return RET_OK;
}

在使用实时操作系统(RTOS)的情况下,为了防止多任务可能产生的变量竞争等不可靠情况,我们大多使用消息队列形式进行返回值的处理,以使用FreeRTOS的API演示如下:


typedef struct {
    uint8_t id;
    int value;
} Message;

QueueHandle_t message_queue;

/* 生产者任务 */
void producer_task(void *parameter)
{
    Message message;

    for (;;) {
        message.id = 1;
        message.value = 100;

        /* 向队列发送消息 */
        xQueueSend(
            message_queue,
            &message,
            portMAX_DELAY
        );

        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

/* 消费者任务 */
void consumer_task(void *parameter)
{
    Message message;

    for (;;) {
        /* 等待并读取消息 */
        if (xQueueReceive(
                message_queue,
                &message,
                portMAX_DELAY) == pdPASS) {

            /* 消费消息 */
            process_message(message.id, message.value);
        }
    }
}

除了裸机常用的状态码+返回值与RTOS常用的消息队列方式,还有一种同时适用于两者,但需要更多项目工程化构建的方法——发布/订阅模式,在此不做论述。