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口有没有被占用,被占用就要解除占用噢!

相关推荐
Yana.nice3 小时前
yum list 和 repoquery的区别
linux
码出钞能力4 小时前
更换libc.so导致linux变砖,通过LD_PRELOAD挽救
linux·服务器
小马学嵌入式~4 小时前
嵌入式 SQLite 数据库开发笔记
linux·c语言·数据库·笔记·sql·学习·sqlite
小猪咪piggy4 小时前
【JavaEE】(24) Linux 基础使用和程序部署
linux·运维·服务器
Haven-5 小时前
Linux常见命令
linux·基本指令
IT 小阿姨(数据库)5 小时前
PgSQL中pg_stat_user_tables 和 pg_stat_user_objects参数详解
linux·运维·数据库·sql·postgresql·oracle
MChine慕青5 小时前
顺序表与单链表:核心原理与实战应用
linux·c语言·开发语言·数据结构·c++·算法·链表
朱自清的诗.5 小时前
使用虚拟机Ubuntu搭建mosquito服务器 使esp32、上位机通信
ubuntu·esp32·mosquito
虎头金猫5 小时前
如何在Linux上使用Docker在本地部署开源PDF工具Stirling PDF:StirlingPDF+cpolar让专业操作像在线文档一样简单
linux·运维·ubuntu·docker·pdf·开源·centos