Linux驱动入门实验班——DHT11、DS18B20模块驱动(附百问网视频链接)

目录

前言

一、DHT11模块

1.通信协议

2.数据格式

3.编程思路

①入口函数

②实现read函数

③编写中断处理函数

④***编写数据解析函数

⑤应用程序

二、DS18B20模块

[1. 通信时序](#1. 通信时序)

[① 初始化时序](#① 初始化时序)

[② 写时序](#② 写时序)

[③ 读时序](#③ 读时序)

[2. 常用命令](#2. 常用命令)

[3. 编程思路](#3. 编程思路)

1.启动温度转换

2.读取温度

三、源码

1.dht11源码

2.ds18b20

课程链接


前言

在这里主要记录学习韦东山老师 Linux驱动课程的笔记,韦东山老师的驱动课程讲的非常好,想要学习驱动的小伙伴可以去b站学习他的课程。

一、DHT11模块

1.通信协议

与IRDA的通信不同,需要先发一个开始信号给DHT11,才能接收数据。下图为一次完整传输示例:

其中深黑色信号表示由主机驱动,即主机向 DHT11 发信号,浅灰色信号表示 DHT11 驱动,即DHT11发向主机发信号。

⚫ 当主机没有与DHT11通信时,总线处于空闲状态,此时总线电平由于上拉 电阻的作用处于高电平。

⚫ 当主机与DHT11正在通信时,总线处于通信状态,一次完整的通信过程如 下: 开始信号 = 一个低脉冲 + 一个高脉冲。低脉冲至少持续18ms,高脉冲持续20-40us。

a) 主机将对应的GPIO管脚配置为输出,准备向DHT11发送数据;

b) 主机发送一个开始信号。

c) 主机将对应的GPIO管脚配置为输入,准备接受DHT11传来的数据,这 时信号由上拉电阻拉高; d) DHT11发出响应信号: 响应信号 = 一个低脉冲 + 一个高脉冲。低脉冲持续80us,高脉冲持续80us。

e) DHT11发出数据信号: ◼ 数据为0的一位信号 = 一个低脉冲 + 一个高脉冲。低脉冲持续 50us,高脉冲持续26~28us。 ◼ 数据为1的一位信号 = 一个低脉冲 + 一个高脉冲。低脉冲持续 50us,高脉冲持续70us。

f) DHT11发出结束信号: 最后1bit数据传送完毕后,DHT11拉低总线50us,然后释放总线,总线由 上拉电阻拉高进入空闲状态。

2.数据格式

数据格式 = 8bit 湿度整数数据+8bit湿度小数数据 +8bi 温度整数数据+8bit温度小数数据 +8bit 校验和。

数据传送正确时,校验和等于"8bit湿度整数数据+8bit湿度小数数据+8bi 温度整数数据+8bit温度小数数据"所得结果的末8位。

3.编程思路

①入口函数
  • 叫gpio引脚编号转换为中断号
  • 申请GPIO
  • 设置DHT11的GPIO引脚的初始状态输出高电平
  • 释放这个引脚
  • 设置DHT11的GPIO引脚为输入模式
②实现read函数
  • 发送18ms的低脉冲
  • 注册中断
  • 设置定时器
  • 休眠等待数据(等待温湿度的整数位)
  • 释放中断
  • 判断数据是否有效
  • 拷贝回用户空间
③编写中断处理函数
  • 记录中断发生的实践
  • 累加次数84次
  • 次数足够解析数据放入环形缓冲区
  • 唤醒read
  • u64 ktime_get_ns
④***编写数据解析函数
  • 计算高低脉冲持续时间
  • 如果接收到约等于50微秒低电平,就判断数据是1还是0
  • 当接收到八个数据说明接收完一位,将计数值都恢复到初始状态
  • 将接收到的数据与校验码做对比
  • 如果相等将整数部分放入环形缓冲区
  • 唤醒read
⑤应用程序
  • 打开设备节点
  • 读取数据放入buf
  • gcc 和 arm-gcc的区别

二、DS18B20模块

1. 通信时序

① 初始化时序

类似前面的DHT11,主机要跟DS18B20通信,首先需要发出一个开始信号。 深黑色线表示由主机驱动信号,浅灰色线表示由DS18B20驱动信号。 最开始时引脚是高电平,想要开始传输信号。

a) 必须要拉低至少480us,这是复位信号;

b) 然后拉高释放总线,等待15~60us之后,

c) 如果GPIO上连有DS18B20芯片,它会拉低60~240us。 如果主机在最后检查到60~240us的低脉冲,则表示DS18B20初始化成功。

② 写时序

⚫ 如果写0,拉低至少60us(写周期为60-120us)即可;

⚫ 如果写1,先拉低至少1us,然后拉高,整个写周期至少为60us即可。

如何读数据

③ 读时序

⚫ 主机先拉低至少1us,随后读取电平,如果为0,即读到的数据是0,如果 为1,即可读到的数据是1。

⚫ 整个过程必须在15us内完成,15us后引脚都会被拉高。

2. 常用命令

|-----|---------------------|--------------------------------------------------------|
| CCH | Skip ROM | 忽略ROM表示后续发出的命令将会给发给所有设备程序 如果总线上只有ds18b20这一个设备,则适合用这个命令 |
| 55H | MatchRMO | 匹配RMO发出此命令后,接着发出64位ROM编码,用于选中某个设备 |
| 44H | Convert Temperation | 启动温度转化,注意不同精度需要不同转化时间 结果存入内部RAM |
| BEH | read | 读取整个内部RAM,9字节 |

3. 编程思路

1.启动温度转换

  • 关中断

在 Linux 内核中,spin_lock_irqsave 函数用于获取自旋锁并保存中断状态。

函数原型

void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);

参数

  • lock:指向要获取的自旋锁的指针。
  • flags:用于保存中断状态的变量。

返回值:该函数没有返回值。

例如,在多线程环境中,当需要保护一段关键代码不被并发访问时,可以使用 spin_lock_irqsave 来获取自旋锁并保存中断状态。

这样可以确保在执行关键代码段时,不会被其他线程或中断干扰,保证数据的一致性和正确性。

  • 主控芯片发出480us的低脉冲
  • 等待回应

在 Linux 内核中,set_current_state函数用于设置当前进程的状态。

一、函数原型

void set_current_state(state_t state)

其中state_t是一个枚举类型,代表进程的状态。

二、参数说明

  • state:进程的新状态,可以是以下几种值之一:
    • TASK_RUNNING:表示进程正在运行或可运行状态。
    • TASK_INTERRUPTIBLE:可中断的睡眠状态,等待某个条件满足时被唤醒,唤醒后可以被信号中断。
    • TASK_UNINTERRUPTIBLE:不可中断的睡眠状态,等待某个条件满足时被唤醒,唤醒后不能被信号中断。
    • 其他特定于内核的进程状态值。

三、返回值

这个函数没有返回值。

使用这个函数可以改变当前进程的状态,从而影响内核的调度行为。例如,当一个进程需要等待某个资源时,可以将其状态设置为可中断或不可中断的睡眠状态,以便在资源可用时被唤醒并继续执行。
在 Linux 内核中,schedule_timeout是一个用于实现任务睡眠一段时间的函数。

一、函数原型

long schedule_timeout(long timeout)

二、参数说明

  • timeout:指定任务睡眠的时间间隔,以 jiffies(内核时钟滴答数)为单位。通常通过HZ(内核时钟频率)将时间转换为 jiffies。例如,如果要睡眠 2 秒,在HZ为 100 的情况下,timeout的值为 2 * HZ = 200。

三、返回值

返回值表示剩余的睡眠时间,如果返回值为 0,表示睡眠时间已到,任务被唤醒。如果在睡眠过程中被提前唤醒(例如通过信号),则返回剩余的睡眠时间。

这个函数通常用于内核空间中需要让当前任务睡眠一段时间的场景。需要注意的是,在使用这个函数时,任务会放弃 CPU 使用权,进入睡眠状态,直到指定的时间过去或者被其他事件唤醒。在用户空间一般不直接使用这个函数,而是使用用户空间的睡眠函数,如usleepsleep等。

  • 模块发出60-240us的应答信号
  • 发送rom命令
  • 接着主控芯片发出选择模块的ROM地址
  • 如果只有一个模块就发出忽略这个rom地址的信号(CCh)
  • 发送功能命令
  • 转换温度(44h)
  • 恢复中断
  • 等待一会

2.读取温度

  • 再重复上面的步骤,但是发出的功能命令为读取温度(BEh)
  • 然后读取九个字节,前八个为数据为,最后一个为检验码
  • 恢复中断
  • 计算CRC验证数据

三、源码

1.dht11源码

cpp 复制代码
#include "asm-generic/errno-base.h"
#include "asm-generic/gpio.h"
#include "linux/jiffies.h"
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>

struct gpio_desc{
	int gpio;
	int irq;
    char *name;
    int key;
	struct timer_list key_timer;
} ;

static struct gpio_desc gpios[] = {
    {115, 0, "dht11", },
};

/* 主设备号                                                                 */
static int major = 0;
static struct class *gpio_class;

static u64 g_dht11_irq_time[84];
static int g_dht11_irq_cnt = 0;

/* 环形缓冲区 */
#define BUF_LEN 128
static char g_keys[BUF_LEN];
static int r, w;

struct fasync_struct *button_fasync;

static irqreturn_t dht11_isr(int irq, void *dev_id);
static void parse_dht11_datas(void);

#define NEXT_POS(x) ((x+1) % BUF_LEN)

static int is_key_buf_empty(void)
{
	return (r == w);
}

static int is_key_buf_full(void)
{
	return (r == NEXT_POS(w));
}

static void put_key(char key)
{
	if (!is_key_buf_full())
	{
		g_keys[w] = key;
		w = NEXT_POS(w);
	}
}

static char get_key(void)
{
	char key = 0;
	if (!is_key_buf_empty())
	{
		key = g_keys[r];
		r = NEXT_POS(r);
	}
	return key;
}


static DECLARE_WAIT_QUEUE_HEAD(gpio_wait);

// static void key_timer_expire(struct timer_list *t)
static void key_timer_expire(unsigned long data)
{
	// 解析数据, 放入环形buffer, 唤醒APP
	parse_dht11_datas();
}


/* 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t dht11_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char kern_buf[2];

	if (size != 2)
		return -EINVAL;

	g_dht11_irq_cnt = 0;

	/* 1. 发送18ms的低脉冲 */
	err = gpio_request(gpios[0].gpio, gpios[0].name);
	gpio_direction_output(gpios[0].gpio, 0);
	gpio_free(gpios[0].gpio);

	mdelay(18);
	gpio_direction_input(gpios[0].gpio);  /* 引脚变为输入方向, 由上拉电阻拉为1 */

	/* 2. 注册中断 */
	err = request_irq(gpios[0].irq, dht11_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, gpios[0].name, &gpios[0]);
	mod_timer(&gpios[0].key_timer, jiffies + 10);	

	/* 3. 休眠等待数据 */
	wait_event_interruptible(gpio_wait, !is_key_buf_empty());

	free_irq(gpios[0].irq, &gpios[0]);

	//printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	/* 设置DHT11 GPIO引脚的初始状态: output 1 */
	err = gpio_request(gpios[0].gpio, gpios[0].name);
	if (err)
	{
		printk("%s %s %d, gpio_request err\n", __FILE__, __FUNCTION__, __LINE__);
	}
	gpio_direction_output(gpios[0].gpio, 1);
	gpio_free(gpios[0].gpio);


	/* 4. copy_to_user */
	kern_buf[0] = get_key();
	kern_buf[1] = get_key();

	printk("get val : 0x%x, 0x%x\n", kern_buf[0], kern_buf[1]);
	if ((kern_buf[0] == (char)-1) && (kern_buf[1] == (char)-1))
	{
		printk("get err val\n");
		return -EIO;
	}

	err = copy_to_user(buf, kern_buf, 2);
	
	return 2;
}

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


/* 定义自己的file_operations结构体                                              */
static struct file_operations dht11_drv = {
	.owner	 = THIS_MODULE,
	.read    = dht11_read,
	.release = dht11_release,
};

static void parse_dht11_datas(void)
{
	int i;
	u64 high_time;
	unsigned char data = 0;
	int bits = 0;
	unsigned char datas[5];
	int byte = 0;
	unsigned char crc;

	/* 数据个数: 可能是81、82、83、84 */
	if (g_dht11_irq_cnt < 81)
	{
		/* 出错 */
		put_key(-1);
		put_key(-1);

		// 唤醒APP
		wake_up_interruptible(&gpio_wait);
		g_dht11_irq_cnt = 0;
		return;
	}

	// 解析数据
	for (i = g_dht11_irq_cnt - 80; i < g_dht11_irq_cnt; i+=2)
	{
		high_time = g_dht11_irq_time[i] - g_dht11_irq_time[i-1];

		data <<= 1;

		if (high_time > 50000) /* data 1 */
		{
			data |= 1;
		}

		bits++;

		if (bits == 8)
		{
			datas[byte] = data;
			data = 0;
			bits = 0;
			byte++;
		}
	}

	// 放入环形buffer
	crc = datas[0] + datas[1] + datas[2] + datas[3];
	if (crc == datas[4])
	{
		put_key(datas[0]);
		put_key(datas[2]);
	}
	else
	{
		put_key(-1);
		put_key(-1);
	}

	g_dht11_irq_cnt = 0;
	// 唤醒APP
	wake_up_interruptible(&gpio_wait);
}

static irqreturn_t dht11_isr(int irq, void *dev_id)
{
	struct gpio_desc *gpio_desc = dev_id;
	u64 time;
	
	/* 1. 记录中断发生的时间 */
	time = ktime_get_ns();
	g_dht11_irq_time[g_dht11_irq_cnt] = time;

	/* 2. 累计次数 */
	g_dht11_irq_cnt++;

	/* 3. 次数足够: 解析数据, 放入环形buffer, 唤醒APP */
	if (g_dht11_irq_cnt == 84)
	{
		del_timer(&gpio_desc->key_timer);
		parse_dht11_datas();
	}

	return IRQ_HANDLED;
}


/* 在入口函数 */
static int __init dht11_init(void)
{
    int err;
    int i;
    int count = sizeof(gpios)/sizeof(gpios[0]);
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
	for (i = 0; i < count; i++)
	{		
		gpios[i].irq  = gpio_to_irq(gpios[i].gpio);

		/* 设置DHT11 GPIO引脚的初始状态: output 1 */
		err = gpio_request(gpios[i].gpio, gpios[i].name);
		gpio_direction_output(gpios[i].gpio, 1);
		gpio_free(gpios[i].gpio);

		setup_timer(&gpios[i].key_timer, key_timer_expire, (unsigned long)&gpios[i]);
	 	//timer_setup(&gpios[i].key_timer, key_timer_expire, 0);
		//gpios[i].key_timer.expires = ~0;
		//add_timer(&gpios[i].key_timer);
		//err = request_irq(gpios[i].irq, dht11_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "100ask_gpio_key", &gpios[i]);
	}

	/* 注册file_operations 	*/
	major = register_chrdev(0, "100ask_dht11", &dht11_drv);  /* /dev/gpio_desc */

	gpio_class = class_create(THIS_MODULE, "100ask_dht11_class");
	if (IS_ERR(gpio_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "100ask_dht11");
		return PTR_ERR(gpio_class);
	}

	device_create(gpio_class, NULL, MKDEV(major, 0), NULL, "mydht11"); /* /dev/mydht11 */
	
	return err;
}

/* 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 */
static void __exit dht11_exit(void)
{
    int i;
    int count = sizeof(gpios)/sizeof(gpios[0]);
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	device_destroy(gpio_class, MKDEV(major, 0));
	class_destroy(gpio_class);
	unregister_chrdev(major, "100ask_dht11");

	for (i = 0; i < count; i++)
	{
		//free_irq(gpios[i].irq, &gpios[i]);
		//del_timer(&gpios[i].key_timer);
	}
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(dht11_init);
module_exit(dht11_exit);

MODULE_LICENSE("GPL");

2.ds18b20

cpp 复制代码
#include "acpi/acoutput.h"
#include "asm-generic/errno-base.h"
#include "asm-generic/gpio.h"
#include "asm/gpio.h"
#include "asm/uaccess.h"
#include <linux/module.h>
#include <linux/poll.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>

struct gpio_desc{
	int gpio;
	int irq;
    char *name;
    int key;
	struct timer_list key_timer;
} ;

static struct gpio_desc gpios[] = {
    {115, 0, "ds18b20", },
};

/* 主设备号                                                                 */
static int major = 0;
static struct class *gpio_class;

static spinlock_t ds18b20_spinlock;


static void ds18b20_udelay(int us)
{
	u64 time = ktime_get_ns();
	while (ktime_get_ns() - time < us*1000);
}

static int ds18b20_reset_and_wait_ack(void)
{
	int timeout = 100;

	gpio_set_value(gpios[0].gpio, 0);
	ds18b20_udelay(480);

	gpio_direction_input(gpios[0].gpio);

	/* 等待ACK */
	while (gpio_get_value(gpios[0].gpio) && timeout--)
	{
		ds18b20_udelay(1);
	}

	if (timeout == 0)
		return -EIO;

	/* 等待ACK结束 */
	timeout = 300;
	while (!gpio_get_value(gpios[0].gpio) && timeout--)
	{
		ds18b20_udelay(1);
	}
	if (timeout == 0)
		return -EIO;
	
	return 0;

}

static void ds18b20_send_cmd(unsigned char cmd)
{
	int i;
	
	gpio_direction_output(gpios[0].gpio, 1);

	for (i = 0; i < 8; i++)
	{
		if (cmd & (1<<i)) 
		{
			/* 发送1 */
			gpio_direction_output(gpios[0].gpio, 0);
			ds18b20_udelay(2);
			gpio_direction_output(gpios[0].gpio, 1);
			ds18b20_udelay(60);
		}
		else
		{
			/* 发送0 */
			gpio_direction_output(gpios[0].gpio, 0);
			ds18b20_udelay(60);		
			gpio_direction_output(gpios[0].gpio, 1);
		}
	}
}

static void ds18b20_read_data(unsigned char *buf)
{
	int i;
	unsigned char data = 0;

	gpio_direction_output(gpios[0].gpio, 1);
	for (i = 0; i < 8; i++)
	{
		gpio_direction_output(gpios[0].gpio, 0);
		ds18b20_udelay(2);
		gpio_direction_input(gpios[0].gpio);
		ds18b20_udelay(15);
		if (gpio_get_value(gpios[0].gpio))
		{
			data |= (1<<i);
		}
		ds18b20_udelay(50);
		gpio_direction_output(gpios[0].gpio, 1);
	}

	buf[0] = data;
}

/********************************************************/  
/*DS18B20的CRC8校验程序*/  
/********************************************************/   
/* 参考: https://www.cnblogs.com/yuanguanghui/p/12737740.html */   
static unsigned char calcrc_1byte(unsigned char abyte)   
{   
	unsigned char i,crc_1byte;     
	crc_1byte=0;                //设定crc_1byte初值为0  
	for(i = 0; i < 8; i++)   
	{   
		if(((crc_1byte^abyte)&0x01))   
		{   
			crc_1byte^=0x18;     
			crc_1byte>>=1;   
			crc_1byte|=0x80;   
		}         
		else     
			crc_1byte>>=1;   

		abyte>>=1;         
	}   
	return crc_1byte;   
}

/* 参考: https://www.cnblogs.com/yuanguanghui/p/12737740.html */   
static unsigned char calcrc_bytes(unsigned char *p,unsigned char len)  
{  
	unsigned char crc=0;  
	while(len--) //len为总共要校验的字节数  
	{  
		crc=calcrc_1byte(crc^*p++);  
	}  
	return crc;  //若最终返回的crc为0,则数据传输正确  
}  


static int ds18b20_verify_crc(unsigned char *buf)
{
    unsigned char crc;

	crc = calcrc_bytes(buf, 8);

    if (crc == buf[8])
		return 0;
	else
		return -1;
}

static void ds18b20_calc_val(unsigned char ds18b20_buf[], int result[])
{
	unsigned char tempL=0,tempH=0;
	unsigned int integer;
	unsigned char decimal1,decimal2,decimal;

	tempL = ds18b20_buf[0]; //读温度低8位
	tempH = ds18b20_buf[1]; //读温度高8位

	if (tempH > 0x7f)      							//最高位为1时温度是负
	{
		tempL    = ~tempL;         				    //补码转换,取反加一
		tempH    = ~tempH+1;      
		integer  = tempL/16+tempH*16;      			//整数部分
		decimal1 = (tempL&0x0f)*10/16; 			//小数第一位
		decimal2 = (tempL&0x0f)*100/16%10;			//小数第二位
		decimal  = decimal1*10+decimal2; 			//小数两位
	}
	else
	{
		integer  = tempL/16+tempH*16;      				//整数部分
		decimal1 = (tempL&0x0f)*10/16; 					//小数第一位
		decimal2 = (tempL&0x0f)*100/16%10;				//小数第二位
		decimal  = decimal1*10+decimal2; 				//小数两位
	}
	result[0] = integer;
	result[1] = decimal;
}

/* 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t ds18b20_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	unsigned long flags;
	int err;
	unsigned char kern_buf[9];
	int i;
	int result_buf[2];

	if (size != 8)
		return -EINVAL;

	/* 1. 启动温度转换 */
	/* 1.1 关中断 */
	spin_lock_irqsave(&ds18b20_spinlock, flags);

	/* 1.2 发出reset信号并等待回应 */
	err = ds18b20_reset_and_wait_ack();
	if (err)
	{
		spin_unlock_irqrestore(&ds18b20_spinlock, flags);
		printk("ds18b20_reset_and_wait_ack err\n");
		return err;
	}

	/* 1.3 发出命令: skip rom, 0xcc */
	ds18b20_send_cmd(0xcc);

	/* 1.4 发出命令: 启动温度转换, 0x44 */
	ds18b20_send_cmd(0x44);

	/* 1.5 恢复中断 */
	spin_unlock_irqrestore(&ds18b20_spinlock, flags);

	/* 2. 等待温度转换成功 : 可能长达1s */
	set_current_state(TASK_INTERRUPTIBLE);
	schedule_timeout(msecs_to_jiffies(1000));

	/* 3. 读取温度 */
	/* 3.1 关中断 */
	spin_lock_irqsave(&ds18b20_spinlock, flags);

	/* 3.2 发出reset信号并等待回应 */
	err = ds18b20_reset_and_wait_ack();
	if (err)
	{
		spin_unlock_irqrestore(&ds18b20_spinlock, flags);
		printk("ds18b20_reset_and_wait_ack second err\n");
		return err;
	}
	/* 3.3 发出命令: skip rom, 0xcc */
	ds18b20_send_cmd(0xcc);

	/* 3.4 发出命令: read scratchpad, 0xbe */
	ds18b20_send_cmd(0xbe);

	/* 3.5 读9字节数据 */
	for (i = 0; i < 9; i++)
	{
		ds18b20_read_data(&kern_buf[i]);
	}

	/* 3.6 恢复中断 */
	spin_unlock_irqrestore(&ds18b20_spinlock, flags);

	/* 3.7 计算CRC验证数据 */
	err = ds18b20_verify_crc(kern_buf);
	if (err)
	{
		printk("ds18b20_verify_crc err\n");
		return err;
	}

	/* 4. copy_to_user */
	ds18b20_calc_val(kern_buf, result_buf);
	
	err = copy_to_user(buf, result_buf, 8);
	return 8;
}


/* 定义自己的file_operations结构体                                              */
static struct file_operations gpio_key_drv = {
	.owner	 = THIS_MODULE,
	.read    = ds18b20_read,
};


/* 在入口函数 */
static int __init ds18b20_init(void)
{
    int err;
    int i;
    int count = sizeof(gpios)/sizeof(gpios[0]);
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	spin_lock_init(&ds18b20_spinlock);
	
	for (i = 0; i < count; i++)
	{		
		err = gpio_request(gpios[i].gpio, gpios[i].name);
		gpio_direction_output(gpios[i].gpio, 1);
	}

	/* 注册file_operations 	*/
	major = register_chrdev(0, "100ask_ds18b20", &gpio_key_drv);  /* /dev/gpio_desc */

	gpio_class = class_create(THIS_MODULE, "100ask_ds18b20_class");
	if (IS_ERR(gpio_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "100ask_ds18b20");
		return PTR_ERR(gpio_class);
	}

	device_create(gpio_class, NULL, MKDEV(major, 0), NULL, "myds18b20"); /* /dev/myds18b20 */
	
	return err;
}

/* 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 */
static void __exit ds18b20_exit(void)
{
    int i;
    int count = sizeof(gpios)/sizeof(gpios[0]);
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	device_destroy(gpio_class, MKDEV(major, 0));
	class_destroy(gpio_class);
	unregister_chrdev(major, "100ask_ds18b20");

	for (i = 0; i < count; i++)
	{
		gpio_free(gpios[i].gpio);
	}
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(ds18b20_init);
module_exit(ds18b20_exit);

MODULE_LICENSE("GPL");

课程链接

百问网韦老师的驱动入门实验班https://video.100ask.net/p/t_pc/course_pc_detail/video/v_637cc4a7e4b068e9c3dc6633?product_id=p_634cbce4e4b00a4f37500252&content_app_id=&type=6

相关推荐
gywl19 分钟前
openEuler VM虚拟机操作(期末考试)
linux·服务器·网络·windows·http·centos
森旺电子1 小时前
51单片机仿真摇号抽奖机源程序 12864液晶显示
单片机·嵌入式硬件·51单片机
hhhhhhh_hhhhhh_2 小时前
rk3568制冷项目驱动开发流程汇总(只适用于部分模块CIF DVP等,自用)
驱动开发
日记跟新中2 小时前
Ubuntu20.04 修改root密码
linux·运维·服务器
码农君莫笑2 小时前
信管通低代码信息管理系统应用平台
linux·数据库·windows·低代码·c#·.net·visual studio
BUG 4042 小时前
Linux——Shell
linux·运维·服务器
大霞上仙2 小时前
Linux 多命令执行
linux·运维·服务器
晨欣2 小时前
Kibana:LINUX_X86_64 和 DEB_X86_64两种可选下载方式的区别
linux·运维·服务器
AI青年志3 小时前
【服务器】linux服务器管理员查看用户使用内存情况
linux·运维·服务器
dessler3 小时前
Docker-run命令详细讲解
linux·运维·后端·docker