36.2Linux单总线驱动DS18B20实验(详细讲解代码)_csdn

想必看过我很多次博客的同学,都知道了编写驱动的流程!

这里我们还是按照以前的习惯来一步一步讲解!

单总线驱动,在F103和51单片机的裸机开发中是经常见的。
linux驱动代码编写实际上就是,端对端的编程!

就是 硬件-连接-软件

一开始是主芯片的设备树和镜像,配置硬件,该执行哪条总线,端口。

二就是编写外设写入或者读取数据的文件,还有类似QT的代码执行。

三就是需要把需要驱动的硬件目标和软件操作文件进行匹配。也就是将第一步和第二步相匹配。

1、配置设备树

打开 stm32mp157d-atk.dts 文件,把以下的内容添加到此文件中

与之前大多数不同的是,这里并不是节点追加的方式。是新创建的。

这里就不用配置镜像了,因为没有用到追加节点。就用到一个GPIO口。
一般来说下面我们这个代码用到了platform框架,那么就需要用到pinctrl用来配置电气属性的,但是这里正点原子并没有加pinctrl,可能是因为该引脚复位后默认就是 GPIO 功能,就无需 pinctrl 配置 "复用为 GPIO",但是这样并不规范!

编译:

复制代码
	make dtbs

复制到开发板中:

复制代码
	sudo cp arch/arm/boot/dts/stm32mp157d-atk.dtb /home/chensir/linux/tftpboot/ -f

发现可以在设备树下发现我们刚刚创建的节点;

2、DS18B20驱动编写

之前的博客也是跟大家按照肌肉记忆来编写程序!一步一步按照思路来编写!
总代码会放在最后。
为了让大家更能明白,可以先对着总代码,进行对我的写代码流程更加详细得当!

2.1、头文件

复制代码
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/errno.h>

可以看出头文件用到了miscdevice.h,说明这个例程代码用到了MISC子系统。

2.2、驱动注册和注销

因为我这个DS18B20外设的单总线是在linux没有总线概念的,所以用platform总线来实现总线驱动框架,这个是前面已经讲过的!这个仅仅为了简化代码量!

注册和注销一体化:这个意思是init和exit不用开发人员写了!

可以看以下举例代码:

2.2.1、编写platform_driver驱动结构体

其中流程是:

16行代码:设备树中的compatible值会与ds18b20_of_match下的compatible相匹配。

如下图ds18b20_of_match的代码:

其中MODULE_DEVICE_TABLE是声明一下而已!

15行代码:会在driver目录下生成ds18b20。这个是驱动开发者自己编写的!

和设备树中的compatible没有关系。

18~19行代码:compatible值一旦匹配成功,就会执行probe和remove。(这些其实之前的驱动程序讲解已经讲过很多遍了)

2.2.2、编写probe和remove函数
2.2.3、注册和注销字符驱动设备

我们这里用到的MISC子系统和platform框架,所以可以回顾。


我们用这个的同时需要在设备结构体中定义MISC设备。

虽然说MISC子系统帮开发人员自动设置了主设备号为10,但是子设备号、设备名、字符操作集还是得开发人员自己创建。

1、定义设备结构体

struct miscdevice mdev;

这里可以放在probe函数内,但是如果需要适应创建多个MISC设备,那么就放在设备结构体中;

这里我们要强调一下;

这两种写法要好好记一下!

2、配置probe和remove函数

这里我们先编译测试一下;

发现并没有问题!

接下来继续完善probe和remove函数:

可以看到我们添加了名字、次设备号、字符操作集函数。

接下来注册和注销MISC设备:

==重点来了!!!==我们发现,只有在probe函数内,才动态分配了内存,在remove是没有分配,不能傻傻的再用devm_kmallo函数了,在 probe 函数中动态分配的,remove 函数无法直接访问,需要通过 platform_set_drvdataplatform_get_drvdata 传递指针。

接下来继续完善:

在probe内使用 platform_set_drvdata(pdev, ds18b20_dev);

在remove内使用 ds18b20_dev = platform_get_drvdata(pdev);

3、配置字符操作集

目前这个单总线DS18B20,功能实现只需要读数据就行,所以字符操作集只涉及,在本模块下执行、open、release、read即可!

在完成写完字符操作集之前,我们先来回顾DS18B20的时序,需要严格特定的时序,还有数据判定。然后再上传数据。

4、获取设备节点(设备树属性)

4.1、配置设备树结构体

4.2、GPIO初始化

使用了 devm_gpio_request(带 devm_ 前缀的资源申请函数) ------ 这类函数申请的 GPIO 会与「设备生命周期」自动绑定,无需手动调用 gpio_free,内核会在设备卸载时自动释放 GPIO。

这里知道了有关驱动的gpio信息,仅仅是能知道信息,并没有驱动能力,所以要向内核申请权限来驱动gpio口。

5、配置DS18B20时序

熟悉DS18B20配置的同学就知道,需要严格的时序,还有高低电平转换!所以接下来我们需要配置高低电平、定时器、还有可以把这个读写时序放到队列里面!

5.1、配置输入输出

需要配置输入输出!

因为需要获取DS18B20的温度数据,所以需要判定GPIO的值。

5.2、获取GPIO的值

5.3、设置定时器



5.4、配置工作队列




其中:

复制代码
   struct ds18b20_dev *ds18b20_dev = container_of(work, struct ds18b20_dev, work);
   container_of需要放在处理函数中,通过工作队列(work)反向找到设备结构体(匹配设备)

5.5、完善定时任务

5.6、完成DS18B20的时序

5.6.1、初始化DS18B20
5.6.2、写入一位数据
5.6.3、读取一位数据
5.6.4、写一个字节到DS18B20
5.6.5、从DS18B20读取一个字节

5.7、完善字符操作集



5.8、完善工作队列

编译生成ko文件:

复制代码
make

复制到

复制代码
sudo cp ds18b20.ko /home/chensir/linux/nfs/rootfs/lib/modules/5.4.31/

6、编写测试 APP

这里其实也简单,就是传递2个数据!

核心代码:

复制代码
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

int main()
{
    int fd, ret;
    unsigned char result[2];
    int TH, TL;
    short tmp = 0;
    float temperature;
    int flag = 0;

    fd = open("/dev/ds18b20", 0);

    if(fd < 0)
    {
        perror("open device failed\n");
        exit(1);
    }
    else
        printf("Open success!\n");

    while(1)
    {
        ret = read(fd, &result, sizeof(result)); 
		if(ret == 0) {	/* 读取到数据 */
			TL = result[0];
			TH = result[1];
    
			if((TH == 0XFF) && (TL == 0XFF))/* 如果读取到数据为0XFFFF就跳出本次循序 */
				continue;
			if(TH > 7) {	/* 负数处理 */
				TH = ~TH;
				TL = ~TL;
				flag = 1;	/* 标记为负数 */
			}

			tmp = TH;
			tmp <<= 8;
			tmp += TL;
        
			if(flag == 1) {
				temperature = (float)(tmp+1)*0.0625; /* 计算负数的温度 */
				temperature = -temperature;
			}else {
				temperature = (float)tmp *0.0625;	/* 计算正数的温度 */
			}            

			if(temperature < 125 && temperature > -55) {	/* 温度范围 */
				printf("Current Temperature: %f\n", temperature);
			}
		}
        else if(ret == -1)
        {
                 perror("read"); break;

        }
    flag = 0;
    sleep(1);
    }
	close(fd);	/* 关闭文件 */
}

编译:

复制代码
arm-none-linux-gnueabihf-gcc ds18b20App.c -o ds18b20App

复制到

复制代码
sudo cp ds18b20App /home/chensir/linux/nfs/rootfs/lib/modules/5.4.31/

8、效果

9、总代码:

ds18b20.c:

复制代码
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
/* #include <linux/ide.h> */
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/errno.h>
#include <linux/uaccess.h>
#include <linux/timer.h>
#include <linux/workqueue.h>

/*ds18b20设备结构体*/
struct ds18b20_dev{
	 struct miscdevice mdev;	/* MISC设备 */
     struct device_node *nd; //设备树节点指针
	 int    ds18b20_gpio;    //GPIO编号
     unsigned char data[2]; /* 接收原始数据的BUFF */
     struct timer_list timer;   /* 定时器 */
     struct work_struct work;   /* 工作队列 */
};

#define HIGH 1	
#define LOW 0

/*
 * @description	 : 	设置GPIO的输出值
 * @param - value: 	输出value的值 
 * @return 	     :  无
 */
static void ds18b20_set_output(struct ds18b20_dev *dev, int value)
{
    if(value)
        gpio_direction_output(dev->ds18b20_gpio, 1);
    else
        gpio_direction_output(dev->ds18b20_gpio, 0);
}

/*
 * @description	: 	设置GPIO为输入模式
 * @param 		:	无
 * @return 	  	:   无
 */
static void ds18b20_set_input(struct ds18b20_dev *dev)
{
    gpio_direction_input(dev->ds18b20_gpio);
}

/*
 * @description	: 	获取GPIO的值
 * @param 		:	无 
 * @return 	  	:   GPIO的电平
 */
static int ds18b20_get_io(struct ds18b20_dev *dev)
{
    return gpio_get_value(dev->ds18b20_gpio); 
}

/*
 * @description	: 	写一位数据
 * @param 	bit	: 	要写入的位数
 * @return 	  	:   无
 */
static void ds18b20_write_bit(struct ds18b20_dev *dev, int bit)
{
    local_irq_disable();
    if(bit) {
        ds18b20_set_output(dev, LOW);
        udelay(5);
        ds18b20_set_input(dev);    /* 释放为高阻 */
        udelay(55);                /* 补足到 ~60us */
    } else {
        ds18b20_set_output(dev, LOW);
        udelay(60);                /* 写0保持低 ~60us */
        ds18b20_set_input(dev);    /* 释放为高阻 */
        udelay(5);
    }
    local_irq_enable();
}

/*
 * @description	: 	读一位数据
 * @param 		: 	无
 * @return 	  	:   返回读取一位的数据
 */
static int ds18b20_read_bit(struct ds18b20_dev *dev)
{
    u8 bit = 0;
    local_irq_disable();
    ds18b20_set_output(dev, LOW);
    udelay(2);
    ds18b20_set_input(dev);
    udelay(12);
    bit = ds18b20_get_io(dev) ? 1 : 0;
    udelay(45);
    local_irq_enable();
    return bit;
}

/*
 * @description	: 	写一个字节到DS18B20
 * @param byte  : 	要写入的字节
 * @return 	  	:   无
 */
static void ds18b20_write_byte(struct ds18b20_dev *dev, u8 byte)
{
    int i;
    for(i = 0; i < 8; i++) {
        if(byte & 0x01)
            ds18b20_write_bit(dev,1); /* write 1 */
        else
            ds18b20_write_bit(dev,0); /* write 0 */
        byte >>= 1;	/* 右移一位获取高一位的数据 */
    }
}


/*
 * @description	: 	读取一个字节的数据
 * @param 		: 	无
 * @return 	  	:   读取到的数据
 */
static char ds18b20_read_byte(struct ds18b20_dev *dev)
{
    int i;
    u8 byte = 0;
    for(i = 0; i < 8; i++) {	/* DS18B20先输出低位数据 ,高位数据后输出 */
        if(ds18b20_read_bit(dev))
            byte |= (1 << i);
        else
            byte &= ~(1 << i);
    }
    return byte;
}

/*
 * @description	: 	GPIO的初始化函数
 * @param pdev	:	platform设备 	
 * @return 	  	:   0表示转换成功,其它值表示转换失败
 */
static int ds18b20_request_gpio(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
	struct ds18b20_dev *ds18b20_dev = platform_get_drvdata(pdev);
    int ret;

	ds18b20_dev->nd = dev->of_node;
    if (!ds18b20_dev->nd)
        return -EINVAL;

	ds18b20_dev->ds18b20_gpio = of_get_named_gpio(ds18b20_dev->nd, "ds18b20-gpio", 0);
    if (!gpio_is_valid(ds18b20_dev->ds18b20_gpio))
        return -EINVAL;

    ret = devm_gpio_request(dev, ds18b20_dev->ds18b20_gpio, "DS18B20 Gpio");
    if (ret)
        return ret;

    ds18b20_set_input(ds18b20_dev);
    return 0;
}

/*
 * @description	: 	初始化DS18B20
 * @param 		: 	无
 * @return 	  	:   0,初始化成功,1,失败
 */
static int ds18b20_init(struct ds18b20_dev *dev)
{
   int ret = 1;  // 默认失败
    int i;
    ds18b20_set_input(dev);
    udelay(10);    // 总线稳定时间

    ds18b20_set_output(dev, LOW);  // 拉低复位
    udelay(500);     // >=480us
    ds18b20_set_input(dev);        // 释放总线(高阻)

    /* 在 15~300us 窗口内轮询检测存在脉冲(低电平) */
    for (i = 0; i < 60; i++) {     // 60 * 5us = 300us
        udelay(5);
        if (ds18b20_get_io(dev) == LOW) {
            ret = 0;  // 初始化成功,检测到存在脉冲
            break;
        }
    }

    /* 等待存在脉冲结束 */
    udelay(240);
    ds18b20_set_input(dev);  // 保持释放
    return ret;
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做pr似有ate_data的成员变量
 * 					  一般在open的时候将private_data似有向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int ds18b20_open(struct inode *inode, struct file *filp)
{
	struct miscdevice *mdev = filp->private_data; /* 由 misc_open 预先设置 */
	struct ds18b20_dev *ds18b20 = dev_get_drvdata(mdev->this_device);
	filp->private_data = ds18b20;
	return 0;
}
/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t ds18b20_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) 
{
	struct ds18b20_dev *ds18b20 = filp->private_data;
	size_t n = 2;

	if (!ds18b20)
		return -ENODEV;

	if (cnt < n)
		n = cnt;

	if (copy_to_user(buf, &ds18b20->data[0], n))
		return -EFAULT;

	return 0; 
}

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

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

/*
 * @description     : 使用内核的工作队列,获取温度的原始数据
 * @param - work 	: work的结构体
 * @return          : 无
 */
static void ds18b20_work_callback(struct work_struct *work)
{
    int ret;
    struct ds18b20_dev *dev = container_of(work, struct ds18b20_dev, work);

    ret = ds18b20_init(dev);
    if (ret)
        return;

    ds18b20_write_byte(dev, 0XCC);
    ds18b20_write_byte(dev, 0X44);
    msleep(750);
    ds18b20_set_input(dev);

    ret = ds18b20_init(dev);
    if (ret)
        return;

    ds18b20_write_byte(dev, 0XCC);
    ds18b20_write_byte(dev, 0XBE);
    dev->data[0] = ds18b20_read_byte(dev);
    dev->data[1] = ds18b20_read_byte(dev);
}

/*
 * @description     : 定时器的操作函数,每1s去获取一次数据
 * @param - asg 	: 定时器的结构体
 * @return          : 无
 */
/* 定时器回调:每秒触发一次采集 */
static void ds18b20_timer_callback(struct timer_list *arg)
{
    struct ds18b20_dev *dev = from_timer(dev, arg, timer);
    schedule_work(&dev->work);
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(1000));
}


/*驱动的probe函数,当驱动与设备匹配以后此函数就会执行*/
static int ds18b20_probe(struct platform_device *pdev)
{
	int ret;
    struct miscdevice *mdev;
	struct ds18b20_dev *ds18b20_dev;

    dev_info(&pdev->dev, "ds18b20 device and driver matched successfully!\n");
	ds18b20_dev = devm_kzalloc(&pdev->dev, sizeof(*ds18b20_dev), GFP_KERNEL);
	if (!ds18b20_dev) {
		return -ENOMEM;
	}
	platform_set_drvdata(pdev, ds18b20_dev);
 /* GPIO的初始化 */
    ret = ds18b20_request_gpio(pdev);
    if(ret) {
        return ret;
    }

        
	mdev = &ds18b20_dev->mdev;
	mdev->name = "ds18b20";
	mdev->minor = MISC_DYNAMIC_MINOR;
	mdev->fops = &ds18b20_fops;
	ret=misc_register(mdev);
    if(ret < 0){
		dev_info(&pdev->dev, "ds18b20 MISC match fail!\n");
		return -ENODEV;
	}

	/* 绑定 drvdata,供 open 通过 mdev->this_device 找回 */
	if (mdev->this_device)
		dev_set_drvdata(mdev->this_device, ds18b20_dev);

    /* 初始化定时器 */
	timer_setup(&ds18b20_dev->timer, ds18b20_timer_callback, 0);
    ds18b20_dev->timer.expires=jiffies + msecs_to_jiffies(1000);
    add_timer(&ds18b20_dev->timer);
	
	/* 初始化工作队列 */
	INIT_WORK(&ds18b20_dev->work, ds18b20_work_callback);

	return 0;
}

/*驱动的remove函数,移除驱动的时候此函数会执行*/
static int ds18b20_remove(struct platform_device *pdev)
{
	int ret;
	struct miscdevice *mdev;
	struct ds18b20_dev *ds18b20_dev;
    dev_info(&pdev->dev, "DS18B20 driver has been removed!\n");
	ds18b20_dev = platform_get_drvdata(pdev);
	mdev = &ds18b20_dev->mdev;
	misc_deregister(mdev);   
		/* 卸载定时器 */
	del_timer(&ds18b20_dev->timer); 
    		/* 卸载工作队列 */
    cancel_work_sync(&ds18b20_dev->work);
	return 0;
}

static const struct of_device_id ds18b20_of_match[] = {
	{ .compatible = "alientek,ds18b20" },
	{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of,ds18b20_of_match);

/*platform驱动结构体*/
static struct platform_driver ds18b20_driver = {
	.driver = {
		.name			= "ds18b20",
		.of_match_table	= ds18b20_of_match,
	},
	.probe		= ds18b20_probe,
	.remove		= ds18b20_remove,
};

/*注册和注销一体化*/
module_platform_driver(ds18b20_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("chensir");
MODULE_INFO(intree, "Y");

ds18b20App.c:

复制代码
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

int main()
{
    int fd, ret;
    unsigned char result[2];
    int TH, TL;
    short tmp = 0;
    float temperature;
    int flag = 0;

    fd = open("/dev/ds18b20", 0);

    if(fd < 0)
    {
        perror("open device failed\n");
        exit(1);
    }
    else
        printf("Open success!\n");

    while(1)
    {
        ret = read(fd, &result, sizeof(result)); 
		if(ret == 0) {	/* 读取到数据 */
			TL = result[0];
			TH = result[1];
    
			if((TH == 0XFF) && (TL == 0XFF))/* 如果读取到数据为0XFFFF就跳出本次循序 */
				continue;
			if(TH > 7) {	/* 负数处理 */
				TH = ~TH;
				TL = ~TL;
				flag = 1;	/* 标记为负数 */
			}

			tmp = TH;
			tmp <<= 8;
			tmp += TL;
        
			if(flag == 1) {
				temperature = (float)(tmp+1)*0.0625; /* 计算负数的温度 */
				temperature = -temperature;
			}else {
				temperature = (float)tmp *0.0625;	/* 计算正数的温度 */
			}            

			if(temperature < 125 && temperature > -55) {	/* 温度范围 */
				printf("Current Temperature: %f\n", temperature);
			}
		}
        else if(ret == -1)
        {
                 perror("read"); break;

        }
    flag = 0;
    sleep(1);
    }
	close(fd);	/* 关闭文件 */
}

makefile:

复制代码
KERNELDIR := /home/chensir/linux/atk-mp1/linux/my_linux/linux-5.4.31
CURRENT_PATH := $(shell pwd) 
obj-m := ds18b20.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

在全部完成之后呢,我又把单总线的GPIO口换成别的地方了,是STM32MP157的PZ6。

需要做以下改动;

在pinctrl-z下添加:

c 复制代码
&pinctrl_z {
  ds18b20_pins: ds18b20-0 {
	pins1 {
	pinmux = <STM32_PINMUX('Z', 6, GPIO)>;  // PZ6 设为 GPIO
	drive-open-drain;          // 开漏输出(释放=高阻)
	bias-pull-up;              // 上拉(仍建议外部4.7k)
	slew-rate = <0>;
		};
	};

在根节点"/"下追加:

c 复制代码
	 	ds18b20@0 {
		compatible = "alientek,ds18b20";
		pinctrl-names = "default";
		pinctrl-0 = <&ds18b20_pins>;
		ds18b20-gpio = <&gpioz 6 GPIO_ACTIVE_HIGH>;
		status = "okay";

即可,配置电气属性,大家如果想换成别的IO口,就需要知道IO口有没有被占用,被占用就要解除占用噢!

相关推荐
虾..6 小时前
Linux 软硬链接和动静态库
linux·运维·服务器
Evan芙6 小时前
Linux常见的日志服务管理的常见日志服务
linux·运维·服务器
꧁坚持很酷꧂7 小时前
解决虚拟机Ubuntu22.04连接了串口设备但终端没有显示
ubuntu
hkhkhkhkh1238 小时前
Linux设备节点基础知识
linux·服务器·驱动开发
HZero.chen9 小时前
Linux字符串处理
linux·string
张童瑶9 小时前
Linux SSH隧道代理转发及多层转发
linux·运维·ssh
汪汪队立大功1239 小时前
什么是SELinux
linux
石小千9 小时前
Linux安装OpenProject
linux·运维
柏木乃一10 小时前
进程(2)进程概念与基本操作
linux·服务器·开发语言·性能优化·shell·进程
Lime-309010 小时前
制作Ubuntu 24.04-GPU服务器测试系统盘
linux·运维·ubuntu