当前位置: 首页 > news >正文

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

目录

前言

一、DHT11模块

1.通信协议

2.数据格式

3.编程思路

①入口函数

②实现read函数

③编写中断处理函数

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

⑤应用程序

二、DS18B20模块

1. 通信时序 

 ① 初始化时序

② 写时序

③ 读时序

2. 常用命令

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. 常用命令

CCHSkip ROM 

忽略ROM表示后续发出的命令将会给发给所有设备程序

如果总线上只有ds18b20这一个设备,则适合用这个命令

55HMatchRMO匹配RMO发出此命令后,接着发出64位ROM编码,用于选中某个设备
44H

Convert

Temperation

启动温度转化,注意不同精度需要不同转化时间

结果存入内部RAM

BEHread读取整个内部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源码

#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, 唤醒APPparse_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);// 唤醒APPwake_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++;}}// 放入环形buffercrc = 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;// 唤醒APPwake_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

#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;elsereturn -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");

课程链接 

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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 如何在 Android 智能手机上恢复已删除的图片
  • C++前向声明简介
  • 【Dash】Dash中@callback() 回调函数的Output() 参数
  • 【OpenCV】SIFT(尺度不变特征变换)算法?
  • [000-01-022].第03节:RabbitMQ环境搭建
  • RPC 运行时,第二次:发现新的漏洞
  • 从“云、边、端”的统一管理,为传统工厂数字化转型赋能的智慧地产开源了
  • UI测试使用webdriver-manager免安装浏览器驱动
  • 源码构建LAMP
  • 《区块链:能源市场变革的引领者》
  • 自来水厂供水监控的串口服务器应用
  • 企业中需要哪些告警Rules
  • 【Gaussian splatting系列学习】(二)
  • 【一起学Rust | 框架篇 | Tauri2.0框架】tauri中rust和前端的相互调用(rust调用前端)
  • [已解决]mac远程连接windows桌面:parallels client连接遇到的问题
  • 【个人向】《HTTP图解》阅后小结
  • hadoop集群管理系统搭建规划说明
  • IndexedDB
  • Js实现点击查看全文(类似今日头条、知乎日报效果)
  • Python学习之路16-使用API
  • Vue小说阅读器(仿追书神器)
  • windows下使用nginx调试简介
  • 订阅Forge Viewer所有的事件
  • 机器学习中为什么要做归一化normalization
  • 每天一个设计模式之命令模式
  • 前端攻城师
  • 如何使用Mybatis第三方插件--PageHelper实现分页操作
  • 实现简单的正则表达式引擎
  • 使用 @font-face
  • 源码之下无秘密 ── 做最好的 Netty 源码分析教程
  • 7行Python代码的人脸识别
  • ​必胜客礼品卡回收多少钱,回收平台哪家好
  • ‌分布式计算技术与复杂算法优化:‌现代数据处理的基石
  • #VERDI# 关于如何查看FSM状态机的方法
  • #基础#使用Jupyter进行Notebook的转换 .ipynb文件导出为.md文件
  • #知识分享#笔记#学习方法
  • (06)Hive——正则表达式
  • (2/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (c语言)strcpy函数用法
  • (不用互三)AI绘画工具应该如何选择
  • (二)PySpark3:SparkSQL编程
  • (附源码)小程序 交通违法举报系统 毕业设计 242045
  • (蓝桥杯每日一题)love
  • (亲测成功)在centos7.5上安装kvm,通过VNC远程连接并创建多台ubuntu虚拟机(ubuntu server版本)...
  • (三)elasticsearch 源码之启动流程分析
  • (学习日记)2024.03.25:UCOSIII第二十二节:系统启动流程详解
  • (原创) cocos2dx使用Curl连接网络(客户端)
  • ***详解账号泄露:全球约1亿用户已泄露
  • .NET Conf 2023 回顾 – 庆祝社区、创新和 .NET 8 的发布
  • .NET 动态调用WebService + WSE + UsernameToken
  • .Net的DataSet直接与SQL2005交互
  • .net获取当前url各种属性(文件名、参数、域名 等)的方法
  • .NET设计模式(11):组合模式(Composite Pattern)
  • .Net下的签名与混淆
  • .NET周刊【7月第4期 2024-07-28】