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

相关推荐
望获linux7 小时前
【实时Linux实战系列】实时系统的可观测性:Prometheus 与 Grafana 集成
大数据·linux·服务器·开发语言·网络·操作系统
hweiyu007 小时前
Linux 命令:mount
linux·运维·服务器
zhmy_0067 小时前
linux 多服务器下目录数据文件实时同步
linux·文件实时同步
AI视觉网奇7 小时前
redis 配置学习笔记
linux·服务器
红尘客栈28 小时前
K8S基本命令操作
linux·容器·kubernetes
阑梦清川8 小时前
linux网络基础
linux
月墨江山10 小时前
Ubuntu 20.04 使用 Issac Gym 进行宇树G1人形机器人进行强化学习训练(Linux仿真)
linux·ubuntu·机器人
future141211 小时前
单片机学习日记
单片机·嵌入式硬件·学习
天朝八阿哥11 小时前
关于xfce4-pulseaudio-plugin中文翻译的bug
linux·debian
z2023050811 小时前
linux之 remoteproc 内核实现源码分析
linux·运维·服务器