目录
前言
一、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. 常用命令
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 使用权,进入睡眠状态,直到指定的时间过去或者被其他事件唤醒。在用户空间一般不直接使用这个函数,而是使用用户空间的睡眠函数,如
usleep
、sleep
等。
- 模块发出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, 唤醒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
#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