RK3568驱动指南|第十四篇 单总线-第160章DS18B20驱动复位时序编写

瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。


【公众号】迅为电子

【粉丝群】824412014(加群获取驱动文档+例程)

【视频观看】嵌入式学习之Linux驱动(第十四篇 单总线_全新升级)_基于RK3568

【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板


第160章DS18B20驱动复位时序编写

在上个章节的基础上,本章节我们继续编写DS18b20驱动,在驱动中实现获取GPIO和复位时序的驱动编写,并且在加载驱动之后,使用逻辑分析仪对复位时序进行分析。

160.1 DS18B20驱动获取GPIO编写

本实验对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\95_ds18b20_02

编写完成的ds18b20.c代码如下所示,添加的代码已加粗表示。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>

struct ds18b20_data
{
    dev_t dev_num;                  // 设备号
    struct cdev ds18b20_cdev;       // 字符设备结构体
    struct class *ds18b20_class;    // 设备类
    struct device *ds18b20_device;  // 设备
    struct gpio_desc *ds18b20_gpio; // GPIO描述符
};

struct ds18b20_data *ds18b20;       // DS18B20数据结构指针

int ds18b20_open(struct inode *inode, struct file *file)
{
    return 0;
}

ssize_t ds18b20_read(struct file *file, char __user *buf, size_t size, loff_t *offs)
{
    return 0;
}

int ds18b20_release(struct inode *inode, struct file *file)
{
    return 0;
}

struct file_operations ds18b20_fops = {
    .open = ds18b20_open,
    .read = ds18b20_read,
    .release = ds18b20_release,
    .owner = THIS_MODULE,
};

// 设备探测函数
int ds18b20_probe(struct platform_device *dev)
{
    int ret;
    printk("This is probe \n");

    // 分配内存给ds18b20_data结构体
    ds18b20 = kzalloc(sizeof(*ds18b20), GFP_KERNEL);
    if (ds18b20 == NULL)
    {
        printk("kzalloc error\n");
        ret = -ENOMEM;
        goto error_0;
    }

    // 分配字符设备号
    ret = alloc_chrdev_region(&ds18b20->dev_num, 0, 1, "myds18b20");
    if (ret < 0)
    {
        printk("alloc_chrdev_region error\n");
        ret = -EAGAIN;
        goto error_1;
    }

    // 初始化字符设备
    cdev_init(&ds18b20->ds18b20_cdev, &ds18b20_fops);
    ds18b20->ds18b20_cdev.owner = THIS_MODULE;
    cdev_add(&ds18b20->ds18b20_cdev, ds18b20->dev_num, 1);

    // 创建设备类
    ds18b20->ds18b20_class = class_create(THIS_MODULE, "sensors");
    if (IS_ERR(ds18b20->ds18b20_class))
    {
        printk("class_create error\n");
        ret = PTR_ERR(ds18b20->ds18b20_class);
        goto error_2;
    }

    // 创建设备
    ds18b20->ds18b20_device = device_create(ds18b20->ds18b20_class, NULL, ds18b20->dev_num, NULL, "ds18b20");
    if (IS_ERR(ds18b20->ds18b20_device))
    {
        printk("device_create error\n");
        ret = PTR_ERR(ds18b20->ds18b20_device);
        goto error_3;
    }

    // 获取GPIO描述符
    ds18b20->ds18b20_gpio = gpiod_get_optional(&dev->dev, "ds18b20", 0);
    if (ds18b20->ds18b20_gpio == NULL)
    {
        ret = -EBUSY;
        goto error_4;
    }

    // 设置GPIO方向为输出
    gpiod_direction_output(ds18b20->ds18b20_gpio, 1);

    return 0;

error_4:
    device_destroy(ds18b20->ds18b20_class, ds18b20->dev_num);

error_3:
    class_destroy(ds18b20->ds18b20_class);

error_2:
    cdev_del(&ds18b20->ds18b20_cdev);
    unregister_chrdev_region(ds18b20->dev_num, 1);

error_1:
    kfree(ds18b20);

error_0:
    return ret;
}

// 设备匹配表
const struct of_device_id ds18b20_match_table[] = {
    { .compatible = "ds18b20" },
    {},
};

struct platform_driver ds18b20_driver = {
    .driver = {
        .owner = THIS_MODULE,
        .name = "ds18b20",
       .of_match_table = ds18b20_match_table,
    },
    .probe = ds18b20_probe,
};

static int __init ds18b20_init(void)
{
    int ret;
    ret = platform_driver_register(&ds18b20_driver);
    if (ret < 0)
    {
        printk("platform_driver_register error\n");
        return -1;
    }

    return 0;
}

static void __exit ds18b20_exit(void)
{
    // 释放GPIO描述符
    gpiod_put(ds18b20->ds18b20_gpio);
    // 销毁设备
    device_destroy(ds18b20->ds18b20_class, ds18b20->dev_num);
    // 销毁设备类
    class_destroy(ds18b20->ds18b20_class);
    // 删除字符设备
    cdev_del(&ds18b20->ds18b20_cdev);
    // 注销字符设备号
    unregister_chrdev_region(ds18b20->dev_num, 1);
    // 释放ds18b20_data内存
    kfree(ds18b20);
    // 注销平台驱动
    platform_driver_unregister(&ds18b20_driver);
}

module_init(ds18b20_init);
module_exit(ds18b20_exit);
MODULE_LICENSE("GPL");

160.2 复位和应答信号时序分析

在ds18b20驱动文件中,复位和应答信号的时序分析是通过控制GPIO引脚的电平和延时来实现的。下面详细解释一下信号时序的分析过程。

如上图所示,复位信号时序分析:

在复位的过程中,首先将GPIO引脚拉低,保持一段时间(通常为480微秒),这是为了确保发送复位信号的低电平持续足够的时间触发DS18b20的复位操作。

接下来,释放GPIO引脚,使其回到高电平状态,此时DS18b20将检测到引脚状态的变化,并开始响应复位信号。

在释放引脚之后需要等待一段时间,以确保ds18b20完成复位操作并准备好接收后续的命令。

如上图所示,应答信号时序分析:

在发送命令或数据之前,需要先向DS18B20发送一个启动信号,然后等待DS18B20的应答信号。启动信号是通过将GPIO引脚拉低一小段时间(通常为1微秒)然后立即拉高来发送的。

接下来,驱动程序需要检查引脚的状态,以确定DS18B20是否发送了应答信号。

DS18B20的应答信号是在启动信号后的一小段时间内由DS18B20将引脚拉低发送的。驱动程序需要检测引脚状态是否被成功拉低,以确认应答信号的接收。

在驱动程序中,可以使用Linux内核提供的GPIO API来控制GPIO引脚的电平状态和延时。例如,在复位信号时序分析中,可以使用以下函数来控制GPIO引脚的状态:

gpiod_direction_output():设置GPIO引脚为输出模式。

gpiod_set_value():将GPIO引脚设置为高电平或低电平。

udelay():延时一定的微秒数。0

160.3 DS18b20驱动复位时序编写

我们在160.1小节驱动的基础上,继续编写复位时序驱动。

本实验对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\95_ds18b20_02

编写完成的ds18b20.c代码如下所示,添加的代码已加粗表示。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h> // 添加此头文件
#include <linux/delay.h>

struct ds18b20_data
{
    dev_t dev_num;
    struct cdev ds18b20_cdev;
    struct class *ds18b20_class;
    struct device *ds18b20_device;
    struct gpio_desc *ds18b20_gpio;
};

struct ds18b20_data *ds18b20;

void ds18b20_reset(void)
{
    // 设置 GPIO 方向为输出,输出低电平
    gpiod_direction_output(ds18b20->ds18b20_gpio, 1);
    gpiod_set_value(ds18b20->ds18b20_gpio, 0);
    udelay(700); // 延迟 700 微秒

    // 设置 GPIO 输出高电平,并将 GPIO 方向设置为输入
    gpiod_set_value(ds18b20->ds18b20_gpio, 1);
    gpiod_direction_input(ds18b20->ds18b20_gpio);

    // 等待直到 GPIO 输入为低电平
    while (gpiod_get_value(ds18b20->ds18b20_gpio))
        ;

    // 等待直到 GPIO 输入为高电平
    while (!gpiod_get_value(ds18b20->ds18b20_gpio))
        ;
    udelay(480); // 延迟 480 微秒
}

int ds18b20_open(struct inode *inode, struct file *file)
{
    return 0;
}

ssize_t ds18b20_read(struct file *file, char __user *buf, size_t size, loff_t *offs)
{
    return 0;
}

int ds18b20_release(struct inode *inode, struct file *file)
{
    return 0;
}

struct file_operations ds18b20_fops = {
    .open = ds18b20_open,
    .read = ds18b20_read,
    .release = ds18b20_release,
    .owner = THIS_MODULE,
};

int ds18b20_probe(struct platform_device *dev)
{
    int ret;
    printk("This is probe \n");

    // 分配并初始化 ds18b20_data 结构体
    ds18b20 = kzalloc(sizeof(*ds18b20), GFP_KERNEL);
    if (ds18b20 == NULL)
    {
        printk("kzalloc error\n");
        ret = -ENOMEM;
        goto error_0;
    }

    // 分配字符设备号
    ret = alloc_chrdev_region(&ds18b20->dev_num, 0, 1, "myds18b20");
    if (ret < 0)
    {
        printk("alloc_chrdev_region error\n");
        ret = -EAGAIN;
        goto error_1;
    }

    // 初始化字符设备结构体
    cdev_init(&ds18b20->ds18b20_cdev, &ds18b20_fops);
    ds18b20->ds18b20_cdev.owner = THIS_MODULE;

    // 将字符设备添加到系统
    cdev_add(&ds18b20->ds18b20_cdev, ds18b20->dev_num, 1);

    // 创建设备类
    ds18b20->ds18b20_class = class_create(THIS_MODULE, "sensors");
    if (IS_ERR(ds18b20->ds18b20_class))
    {
        printk("class_create error\n");
        ret = PTR_ERR(ds18b20->ds18b20_class);
        goto error_2;
    }

    // 创建设备节点
    ds18b20->ds18b20_device = device_create(ds18b20->ds18b20_class, NULL, ds18b20->dev_num, NULL, "ds18b20");
    if (IS_ERR(ds18b20->ds18b20_device))
    {
        printk("device_create error\n");
        ret = PTR_ERR(ds18b20->ds18b20_device);
        goto error_3;
    }

    // 获取 GPIO 描述符
    ds18b20->ds18b20_gpio = gpiod_get_optional(&dev->dev, "ds18b20", 0);
    if (ds18b20->ds18b20_gpio == NULL)
    {
        ret = -EBUS```c
error_4:
    device_destroy(ds18b20->ds18b20_class, ds18b20->dev_num);

error_3:
    class_destroy(ds18b20->ds18b20_class);

error_2:
    cdev_del(&ds18b20->ds18b20_cdev);
    unregister_chrdev_region(ds18b20->dev_num, 1);

error_1:
    kfree(ds18b20);

error_0:
    return ret;
}

const struct of_device_id ds18b20_match_table[] = {
    {.compatible = "ds18b20"},
    {},
};

struct platform_driver ds18b20_driver = {
    .driver = {
        .owner = THIS_MODULE,
        .name = "ds18b20",
        .of_match_table = ds18b20_match_table,
    },
    .probe = ds18b20_probe,
};

static int __init ds18b20_init(void)
{
    int ret;

    // 注册平台驱动
    ret = platform_driver_register(&ds18b20_driver);
    if (ret < 0)
    {
        printk("platform_driver_register error\n");
        return -1;
    }

    ds18b20_reset(); // 调用复位函数

    return 0;
}

static void __exit ds18b20_exit(void)
{
    // 释放资源
    gpiod_put(ds18b20->ds18b20_gpio);
    device_destroy(ds18b20->ds18b20_class, ds18b20->dev_num);
    class_destroy(ds18b20->ds18b20_class);
    cdev_del(&ds18b20->ds18b20_cdev);
    unregister_chrdev_region(ds18b20->dev_num, 1);
    kfree(ds18b20);
    platform_driver_unregister(&ds18b20_driver);
}

module_init(ds18b20_init);
module_exit(ds18b20_exit);
MODULE_LICENSE("GPL");

160.4 编译驱动程序

在上一小节中的ds18b20.c代码同一目录下创建 Makefile 文件,Makefile 文件内容如下所示:

export ARCH=arm64#设置平台架构
export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀
obj-m +=ds18b20.o    #此处要和你的驱动源文件同名
KDIR :=/home/topeet/Linux/linux_sdk/kernel    #这里是你的内核目录                                                                                                                            
PWD ?= $(shell pwd)
all:
    make -C $(KDIR) M=$(PWD) modules    #make操作
clean:
    make -C $(KDIR) M=$(PWD) clean    #make clean操作

对于Makefile的内容注释已在上图添加,保存退出之后,来到存放ds18b20.c和Makefile文件目录下,如下图所示:

然后使用命令“make”进行驱动的编译,编译完成如下图所示:

编译完生成ds18b20.ko目标文件,如下图所示:

至此驱动模块就编译成功了。接下来通过逻辑分析仪验证复位时序。

160.5 通过逻辑分析仪验证复位时序

首先将DS18b20模块和开发板背面的GPIO连接起来,然后将逻辑分析仪连接到ds18b20模块上,连接如下图所示:

硬件连接好之后,安装逻辑分析仪的上位机软件,安装完成之后,打开上位机软件,然后设置参数,如下图所示:

 

设置完毕之后,如下图所示:

设置完毕之后,如下图所示:

上位机上捕捉到时序如下图所示:

将捕捉到的复位时序和下图的时序图对比分析,可以看出驱动复位时序是没问题的。

至此,DS18b20驱动复位时序编写完成。

  • 17
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值