单片机仿Linux驱动开发(一)

单片机仿Linux驱动开发思路:标准化接口+分层设计+实战源码

很多嵌入式开发者写驱动都有痛点:接口混乱、参数难管、换项目重写、调用麻烦,想做出Linux驱动那样模块化、易移植的代码却没有清晰思路。


一、先明确:驱动的核心职责与边界

1. 驱动只做4件事(Linux驱动极简思想)

不管是传感器、屏、存储、通信模块,驱动只负责和硬件交互:

  1. 初始化:硬件上电、配置、自检
  2. 读数据:从硬件读取原始值
  3. 写命令:下发控制指令/配置参数
  4. 控制行为:复位、休眠、唤醒、模式切换

2. 驱动绝对不做5件事(分层解耦关键)

  • 业务逻辑
  • 数据滤波/算法处理
  • 界面显示
  • 定时任务
  • 业务判断

一句话原则:驱动只管硬件,应用层只管业务,彻底分离。


二、仿Linux标准接口:应用层一看就会

Linux驱动的 open/read/write/ioctl/close 是工业级标准接口,单片机可以直接简化套用。

任何模块都按这套接口设计,不用看底层代码,调用一目了然:

接口类型 函数风格 功能
初始化 XXX_Init 硬件初始化、自检、配置
读数据 XXX_Read 读取硬件原始数据
写命令 XXX_Write 发送指令/参数
控制 XXX_Ctrl 休眠/唤醒/复位/模式设置
反初始化 XXX_DeInit 关闭硬件、释放资源

统一接口后,所有驱动调用方式完全一致,团队协作、项目移植效率大幅提升。


三、7条核心规范

1. 禁止魔法值,全部用枚举

❌ 坏写法:AHT10_Ctrl(0);(没人知道0是什么)

✅ 好写法:

c 复制代码
typedef enum {
    AHT10_CMD_SLEEP,
    AHT10_CMD_WAKEUP,
    AHT10_CMD_RESET
} AHT10_CmdTypeDef;

AHT10_Ctrl(AHT10_CMD_SLEEP);

2. 返回值只表示状态,数据用指针传出

❌ 坏写法:float AHT10_ReadTemp()(失败无法判断)

✅ 好写法:

c 复制代码
uint8_t AHT10_Read(float *temp, float *humi);

返回值 = 成功/失败;数据 = 指针输出。

3. 数据指针必须带长度,防止越界

❌ 坏写法:void Write(uint8_t *data)

✅ 好写法:uint8_t Write(const uint8_t *data, uint16_t len)

4. 全局统一状态码,所有驱动通用

c 复制代码
typedef enum {
    DRV_OK = 0,
    DRV_ERROR,
    DRV_TIMEOUT,
    DRV_PARAM_ERR,
    DRV_HW_ERR
} DRV_StatusTypeDef;

5. 控制函数统一用:cmd + void*

一个接口搞定所有控制,不新增函数:

c 复制代码
uint8_t XXX_Ctrl(uint8_t cmd, void *param);

6. 必须做参数校验(空指针、非法值)

c 复制代码
if(temp == NULL || humi == NULL)
    return DRV_PARAM_ERR;

7. 头文件只暴露接口,隐藏底层实现

  • .h:只放函数声明、枚举、结构体
  • .c:底层操作、内部变量、私有函数全部隐藏

四、参数太多?用结构体打包

初始化参数 ≥3 个时,结构体打包是最清晰、最规范的方式。

示例:

c 复制代码
typedef struct {
    I2C_HandleTypeDef *hi2c;
    uint16_t dev_addr;
    uint8_t reset_pin;
    GPIO_TypeDef *reset_port;
    uint8_t auto_calib;
} AHT10_ConfigTypeDef;

调用时:

c 复制代码
AHT10_ConfigTypeDef cfg = {
    .hi2c = &hi2c1,
    .dev_addr = 0x70<<1,
    .reset_pin = GPIO_PIN_5,
    .reset_port = GPIOA,
    .auto_calib = 1
};
AHT10_Init(&cfg);

五、static / const 正确使用

1. static:隐藏内部实现

  • 对外接口:不加 static
  • 内部函数/变量:必须加 static(外部无法访问)
c 复制代码
// 对外
DRV_StatusTypeDef AHT10_Init(...);

// 内部私有
static uint8_t sensor_ready;
static DRV_StatusTypeDef CheckBusy(void);

2. const:只读、防篡改、放Flash

用于固定地址、命令表、驱动结构体、配置参数:

c 复制代码
const uint16_t AHT10_ADDR = 0x70<<1;

六、仿Linux file_operations 抽象驱动

这是Linux驱动最核心的思想:把所有驱动接口封装成统一结构体,实现"驱动抽象化、平台无关化"。

1. 定义抽象接口结构体

c 复制代码
typedef struct {
    DRV_StatusTypeDef (*Init)(void *config);
    DRV_StatusTypeDef (*Read)(float *t, float *h);
    DRV_StatusTypeDef (*Write)(const uint8_t *data, uint16_t len);
    DRV_StatusTypeDef (*Ctrl)(uint8_t cmd, void *param);
    DRV_StatusTypeDef (*DeInit)(void);
} DRV_OperationsTypeDef;

2. 实例化驱动(类似注册驱动)

c 复制代码
const DRV_OperationsTypeDef AHT10_Drv = {
    .Init = AHT10_Init,
    .Read = AHT10_Read,
    .Write = AHT10_Write,
    .Ctrl = AHT10_Ctrl,
    .DeInit = AHT10_DeInit
};

3. 应用层统一调用

c 复制代码
AHT10_Drv.Init(&cfg);
AHT10_Drv.Read(&temp, &humi);
AHT10_Drv.Ctrl(AHT10_CMD_RESET, NULL);

优势:换传感器、换MCU,应用层代码几乎不用改。


七、AHT10温湿度传感器示例

aht10.h(对外接口)

c 复制代码
#ifndef __AHT10_H
#define __AHT10_H

#include "stm32hal.h"

// 统一状态码
typedef enum {
    DRV_OK = 0,
    DRV_ERROR,
    DRV_PARAM_ERR,
    DRV_HW_ERR
} DRV_StatusTypeDef;

// 控制命令枚举
typedef enum {
    AHT10_CMD_SLEEP,
    AHT10_CMD_WAKEUP,
    AHT10_CMD_RESET
} AHT10_CmdTypeDef;

// 配置结构体
typedef struct {
    I2C_HandleTypeDef *hi2c;
    uint16_t dev_addr;
    uint8_t auto_calib;
} AHT10_ConfigTypeDef;

// 抽象驱动结构体
typedef struct {
    DRV_StatusTypeDef (*Init)(void *config);
    DRV_StatusTypeDef (*Read)(float *temp, float *humi);
    DRV_StatusTypeDef (*Ctrl)(uint8_t cmd, void *param);
} DRV_OperationsTypeDef;

// 获取驱动实例
const DRV_OperationsTypeDef *AHT10_GetDrv(void);

#endif

aht10.c(底层实现,对外隐藏)

c 复制代码
#include "aht10.h"

static AHT10_ConfigTypeDef cfg;
static uint8_t is_ready = 0;

// 内部函数
static DRV_StatusTypeDef AHT10_CheckReady(void)
{
    is_ready = 1;
    return DRV_OK;
}

// 初始化
static DRV_StatusTypeDef AHT10_Init(void *config)
{
    if(config == NULL) return DRV_PARAM_ERR;
    cfg = *(AHT10_ConfigTypeDef*)config;

    if(AHT10_CheckReady() != DRV_OK)
        return DRV_HW_ERR;

    return DRV_OK;
}

// 读取数据
static DRV_StatusTypeDef AHT10_Read(float *temp, float *humi)
{
    if(temp == NULL || humi == NULL)
        return DRV_PARAM_ERR;
    if(!is_ready) return DRV_HW_ERR;

    *temp = 25.5f;
    *humi = 52.3f;
    return DRV_OK;
}

// 控制接口
static DRV_StatusTypeDef AHT10_Ctrl(uint8_t cmd, void *param)
{
    (void)param;
    if(!is_ready) return DRV_HW_ERR;

    switch(cmd) {
        case AHT10_CMD_SLEEP: is_ready = 0; break;
        case AHT10_CMD_WAKEUP: is_ready = 1; break;
        case AHT10_CMD_RESET: AHT10_Init(&cfg); break;
        default: return DRV_PARAM_ERR;
    }
    return DRV_OK;
}

// 驱动实例
static const DRV_OperationsTypeDef AHT10_Drv = {
    .Init = AHT10_Init,
    .Read = AHT10_Read,
    .Ctrl = AHT10_Ctrl
};

// 返回驱动实例
const DRV_OperationsTypeDef *AHT10_GetDrv(void)
{
    return &AHT10_Drv;
}

main.c(应用层调用示例)

c 复制代码
int main(void)
{
    HAL_Init();

    // 配置
    AHT10_ConfigTypeDef cfg = {
        .hi2c = &hi2c1,
        .dev_addr = 0x70<<1,
        .auto_calib = 1
    };

    // 获取驱动
    const DRV_OperationsTypeDef *drv = AHT10_GetDrv();
    drv->Init(&cfg);

    float temp, humi;

    while(1) {
        drv->Read(&temp, &humi);
        HAL_Delay(500);
    }
}

总结:仿Linux驱动的3个核心

  1. 分层解耦:驱动只操作硬件,应用层只做业务
  2. 接口标准化:统一使用 Init / Read / Write / Ctrl / DeInit
  3. 代码规范化:枚举代替魔法值、结构体打包参数、static/const合理使用
相关推荐
waves浪游2 小时前
进程间通信(上)
linux·运维·服务器·开发语言·c++
6190083362 小时前
win wsl2 指定目录安装Ubuntu-24.04开启ssh sftp
linux·ubuntu·ssh
实在太懒于是不想取名2 小时前
STM32N6的开发日记(5):数字摄像头接口像素流水线DCMIPP让MCU拥有高性能摄像头资源
stm32·单片机·嵌入式硬件
一直跑2 小时前
同一台服务器上(同局域网)的其他账号访问自己的数据(没有sudo权限和无 ACL和无共同组)
java·linux·服务器
Shingmc32 小时前
【Linux】进程间关系与守护进程
linux·服务器
天涯铭2 小时前
深入浅出:单片机I/O模式与上拉电阻
单片机·上拉电阻·gpio输出
Amnesia0_02 小时前
文件和fd,文件的内核级缓冲区,重定向
linux·运维·服务器
wwyyxx262 小时前
Linux 下 .NET 程序 CPU 异常占用排查记录
linux·.net·调试
.千余2 小时前
【Linux】开发工具1
linux·运维·服务器·c语言·学习