从单片机到Linux内核:手把手教你将DS18B20驱动移植到野火i.MX6ULL开发板

对于习惯了单片机裸机开发的工程师来说,第一次接触Linux驱动开发往往会感到无所适从。本文将带你完成一次完整的知识迁移——将一个常见的DS18B20温度传感器驱动从单片机环境移植到Linux平台。我们会重点对比两种开发模式的差异,特别是如何将裸机时序操作代码封装进Linux的platform driver框架。

1. 开发环境与硬件准备

在开始之前,我们需要准备好开发环境。野火i.MX6ULL开发板是一款基于Cortex-A7内核的嵌入式Linux开发平台,它提供了完整的Linux BSP支持。与单片机开发不同,Linux驱动开发需要以下工具链:

  • 交叉编译工具链 :arm-linux-gnueabihf-
  • 内核源码树 :需要与目标板运行的内核版本一致
  • 设备树编译器 (dtc):用于编译设备树源文件

硬件连接方面,DS18B20与i.MX6ULL的连接非常简单,只需要一个GPIO引脚:

信号线 连接说明
DQ 连接到GPIO1_IO02
VCC 3.3V电源
GND 地线

提示:DS18B20是开漏输出,需要接上拉电阻(通常4.7KΩ)

2. 设备树配置:硬件描述的革新

设备树是Linux驱动开发与单片机开发最大的区别之一。在单片机中,我们通常在代码中直接写死硬件配置,而在Linux中,这些信息应该放在设备树中。

对于DS18B20,我们需要在设备树中添加以下节点:

/dts-v1/;

/ {
    ds18b20 {
        compatible = "xgj,ds18b20";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_ds18b20>;
        ds18b20-gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>;
        status = "okay";
    };
};

&iomuxc {
    pinctrl_ds18b20: ds18b20grp {
        fsl,pins = <
            MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0x10b0
        >;
    };
};

设备树编译命令:

make ARCH=arm dtbs

设备树的优势在于:

  • 硬件与驱动分离 :同一驱动可以支持不同硬件配置
  • 运行时配置 :无需重新编译内核即可修改硬件参数
  • 自我描述 :系统可以动态发现硬件配置

3. 驱动框架:从裸机到Linux的思维转变

在单片机环境下,我们通常直接操作寄存器或库函数来控制硬件。而在Linux中,驱动需要遵循内核提供的框架。对于DS18B20这样的设备,最适合使用platform驱动框架。

3.1 platform驱动框架组成

一个完整的platform驱动包含以下部分:

  1. platform_driver结构体 :定义probe/remove等回调函数
  2. of_device_id表 :用于设备树匹配
  3. file_operations :定义设备文件操作接口
  4. 字符设备注册 :创建/dev节点

关键数据结构示例:

static struct platform_driver ds18b20_driver = {
    .probe = ds18b20_probe,
    .remove = ds18b20_remove,
    .driver = {
        .name = "ds18b20",
        .of_match_table = ds18b20_of_match,
    },
};

3.2 驱动初始化流程对比

步骤 单片机实现 Linux驱动实现
GPIO初始化 直接配置寄存器 通过设备树获取GPIO编号
硬件访问 直接操作寄存器 使用gpiolib接口
时序控制 直接延时循环 使用内核延时函数
并发保护 通常不考虑 需要自旋锁/互斥锁

4. 核心时序操作的实现与优化

DS18B20的通信依赖于精确的时序,这在Linux环境中面临新的挑战:

4.1 复位时序的实现

static int ds18b20_reset(void)
{
    int ret = 1;
    unsigned long flags;
    
    spin_lock_irqsave(&ds18b20_dev.lock, flags);
    gpio_direction_output(ds18b20_dev.gpio, 0);
    udelay(480);
    gpio_direction_output(ds18b20_dev.gpio, 1);
    udelay(75);
    gpio_direction_input(ds18b20_dev.gpio);
    ret = gpio_get_value(ds18b20_dev.gpio);
    udelay(10);
    gpio_direction_output(ds18b20_dev.gpio, 1);
    spin_unlock_irqrestore(&ds18b20_dev.lock, flags);
    
    return ret;
}

注意:Linux不是实时系统,udelay的精度有限,对于特别严格的时序可能需要其他方案

4.2 位读写操作的优化

读一位的典型实现:

static uint8_t ds18b20_read_bit(void)
{
    uint8_t bit;
    gpio_direction_output(ds18b20_dev.gpio, 0);
    udelay(1);
    gpio_direction_input(ds18b20_dev.gpio);
    udelay(5);
    bit = gpio_get_value(ds18b20_dev.gpio);
    udelay(55);
    return bit;
}

为了提高性能,可以考虑以下优化:

  1. 使用内核hrtimer实现更精确的定时
  2. 预编译时序模板减少运行时计算
  3. 实现DMA-based位操作(对于某些SoC)

5. 温度数据的读取与用户接口

在Linux驱动中,我们需要通过文件接口向用户空间暴露温度数据:

5.1 文件操作接口实现

static ssize_t ds18b20_read(struct file *filp, char __user *buf, 
                          size_t size, loff_t *offset)
{
    uint8_t temp_L, temp_H;
    short temp;
    int ret;
    
    if (ds18b20_reset() != 0)
        return -EIO;
        
    ds18b20_write_byte(CMD_SKIP_ROM);
    ds18b20_write_byte(CMD_CONVERT_TEMP);
    msleep(750);
    
    if (ds18b20_reset() != 0)
        return -EIO;
        
    ds18b20_write_byte(CMD_SKIP_ROM);
    ds18b20_write_byte(CMD_READ_SCRATCHPAD);
    temp_L = ds18b20_read_byte();
    temp_H = ds18b20_read_byte();
    
    temp = (temp_H << 8) | temp_L;
    
    if (copy_to_user(buf, &temp, sizeof(temp)))
        return -EFAULT;
        
    return sizeof(temp);
}

5.2 用户空间测试程序

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

#define DEV_PATH "/dev/ds18b20"

int main()
{
    int fd = open(DEV_PATH, O_RDONLY);
    short raw_temp;
    float temperature;
    
    while(1) {
        if(read(fd, &raw_temp, sizeof(raw_temp)) == sizeof(raw_temp)) {
            temperature = raw_temp * 0.0625f;
            printf("Temperature: %.2f°C\n", temperature);
        }
        sleep(1);
    }
    
    close(fd);
    return 0;
}

6. 调试技巧与性能考量

Linux驱动调试比单片机复杂得多,以下是一些实用技巧:

调试方法对比表 :

调试需求 单片机方案 Linux方案
打印调试 串口printf printk + dmesg
信号测量 逻辑分析仪 ftrace/systrace
内存检查 单步调试 kdump/coredump
性能分析 周期计数 perf工具

对于时序敏感型设备,还需要特别注意:

  1. 禁止内核抢占 : preempt_disable()
  2. 关闭本地中断 : local_irq_save()
  3. 内存屏障 : rmb()/wmb()
  4. 缓存一致性 : dma_cache_sync()

驱动加载与测试流程:

# 编译驱动
make -C /path/to/kernel M=$PWD modules

# 加载驱动
insmod ds18b20.ko

# 查看内核日志
dmesg | tail

# 运行测试程序
./ds18b20_test
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐