1. 引言
- 在嵌入式开发中,我们常会遇到这样的需求:
- 同一款产品可能会使用不同型号的传感器(比如 AHT 20 或 SHT 30),希望在编译甚至运行时切换驱动。
- 系统需要同时支持多个同类型设备(例如两个温湿度传感器),但上层逻辑希望统一调用。
- 这些场景都指向同一个设计需求:上层代码依赖“接口”而非具体实现,而这就是面向对象中多态解决的问题。
- 虽然 C 语言没有
class 和 interface 关键字,但我们可以用结构体封装函数指针来模拟多态,从而实现底层驱动与上层应用的解耦。
2. 以温湿度传感器的例子 :AHT20、SHT30
2.1 公共层:创建一个 api_typedef.h
- 该头文件用于存放各类传感器的 API 结构体定义
- 代码如下:
#ifndef __API_TYPEDEF_H
#define __API_TYPEDEF_H
#include <stdint.h>
/* 温湿度类传感器API结构体定义 */
typedef struct
{
uint8_t (*Init)(void); //初始化
uint8_t (*GetData)(float* p_temp,float* p_humi);//获取数据
}SensorWS_Driver_t;
/* 其它类外设API结构体定义 */
/* ... */
#endif
2.2 驱动层:编写 AHT20 、SHT30底层驱动
2.2.1 AHT20 底层驱动
- 创建 dev_aht20.h 和 dev_aht20.c
- dev_aht20.h 内容如下:
#ifndef __DEV_AHT20_H
#define __DEV_AHT20_H
/***
* @brief 初始化AHT20
* @return 1为初始化成功,0为初始化失败
*/
uint8_t Dev_AHT20_Init(void);
/***
* @brief 读取AHT20的温湿度数据
* @param p_temp 以指针形式返回的温度数据
* @param p_humi 以指针形式返回的湿度数据
* @return 1为读取数据成功,0为读取数据失败
*/
uint8_t Dev_AHT20_GetData(float* p_temp,float* p_humi);
#endif
2.2.2 SHT30 底层驱动
- 创建 dev_sht30.h 和 dev_sht30.c
- dev_sht30.h 内容如下:
#ifndef __DEV_SHT30_H
#define __DEV_SHT30_H
/***
* @brief 初始化SHT30
* @return 1为初始化成功,0为初始化失败
*/
uint8_t Dev_SHT30_Init(void);
/***
* @brief 读取SHT30的温湿度数据
* @param p_temp 以指针形式返回的温度数据
* @param p_humi 以指针形式返回的湿度数据
* @return 1为读取数据成功,0为读取数据失败
*/
uint8_t Dev_SHT30_GetData(float* p_temp,float* p_humi);
#endif
2.3 main.c 使用案例
#include <stdio.h>
#include <stdint.h>
#include "api_typedef.h"
#include "dev_aht20.h"
#include "dev_sht30.h"
#define USE_SENSOR_WS_NUM 2
static SensorWS_Driver_t AHT20 = {
.Init = Dev_AHT20_Init,
.GetData = Dev_AHT20_GetData
};
static SensorWS_Driver_t SHT30 = {
.Init = Dev_SHT30_Init,
.GetData = Dev_SHT30_GetData
};
static SensorWS_Driver_t* pDev[USE_SENSOR_WS_NUM] = { &AHT20, &SHT30 };
int main()
{
for (uint8_t i = 0; i < USE_SENSOR_WS_NUM; i++)
{
if (pDev[i]->Init() != 1)
{
// 初始化失败处理
}
}
while (1)
{
float wendu = 0.0f;
float shidu = 0.0f;
for (uint8_t i = 0; i < USE_SENSOR_WS_NUM; i++)
{
if (pDev[i]->GetData(&wendu, &shidu) == 1)
{
// 实际使用时请替换为嵌入式串口打印函数
printf("温度:%.1f,湿度:%.1f \n", wendu, shidu);
}
}
}
}
3. 总结
- 这样,我们就可以轻松更改底层驱动,而不影响相关业务的实现
- 如果只是单纯为了方便随时更换底层驱动,对于资源极度紧张的场景,也可以使用宏来实现静态接口,但本文重点展示多态设计