onewire单总线:
一. 基础知识:
单总线:串行模式,一条线,包括时钟线和数据线,根据设备地址,像IIC一样可实现一主多从。
常见引脚:三根线,信号线,vcc,GND。
DS18B20温度传感器:9到12bit分辨率,对应数据后小数点的后1到4位,即最高精确到后四位小数。分辨率越高,采集数据所需要的时间越长。
二. 驱动编写:
- 字符驱动框架:
#include <linux/init.h>
#include <linux/module.h>
static int __init ds18b20_init(){
return 0;
}
//static静态函数,改变作用域,表示只能在本文件里被调用
static void __exit ds18b20_exit(){
}
module_init(ds18b20_init);
module_exit(ds18b20_exit);
MODULE_LICENSE("GPL");
- makefile文件:
obj-m += ds18b20.o
KDIR:=内核源码目录
PWD?=$(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.order
- 编写driver驱动:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
int ds18b20_probe(struct platform_device *dev){
printk("This is probe\n");
return 0;
}
//const 修饰被初始化后就不可改变的常量,匹配表要用数组格式
const struct of_device_id ds18b20_match_table[] = {
{.compatible = "ds18b20"},
//最后一组空{}表示结束
{},
};
struct platform_driver ds18b20_driver = {
.driver = {
.owner = THIS_MODULES,
//驱动名字
.name = "ds18b20",
//设备树的匹配表
.of_match_table = ds18b20_match_table,
},
//当设备和驱动匹配成功后,就会执行probe函数
.probe = ds18b20_probe,
}
static int __init ds18b20_init(){
//添加一个driver
int ret = platform_driver_register(&ds18b20_driver);
if(ret < 0){
printk("register faile\n");
}
return 0;
}
//static静态函数,改变作用域,表示只能在本文件里被调用
static void __exit ds18b20_exit(){
platform_deiver_unregister(&ds18b20_driver);
}
module_init(ds18b20_init);
module_exit(ds18b20_exit);
MODULE_LICENSE("GPL");
- 补全platform_driver和设备树匹配成功后的device注册:创建字符设备节点,作为外设设备的控制对象,用于与应用层的APP做数据交互(open/read/write…操作集)
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev.h>
#include <linux/slab.h>
// 定义一个结构体,用于存储设备信息
struct ds18b20_data
{
dev_t dev_num;
struct cdev ds18b20_cdev;
struct class *ds18b20_class;
struct device *ds18b20_device;
};
struct ds18b20_data *ds18b20;
int ds18b20_open(struct inode *node, struct file *file){
return 0;
}
ssize_t ds18b20_read(struct file *file, char __user *buf, size_t size, loff_t *offset){
return 0;
}
int ds18b20_release(struct inode *node, struct file *file){
return 0;
}
// 定义一个操作集
static struct file_operations ds18b20_fops = {
.owner = THIS_MODULES,
.open = ds18b20_open,
.read = ds18b20_read,
.release = ds18b20_release,
}
int ds18b20_probe(struct platform_device * dev)
{
printk("This is probe\n");
// 动态申请内存
// GFP_KERNEL是一个宏,表示使用内核级分配器分配内存,这对于在Linux内核环境中使用硬件驱动程序非常有用,因为它可以确保内存分配的一致性和高效性。
ds18b20 = kzalloc(sizeof(struct ds18b20_data), GFP_KERNEL);
if (ds18b20 == NULL)
{
printk("kzalloc failed\n");
return -1;
}
// 动态申请字符设备号:设备号起始值、要分配的设备数量和设备号名称,最后赋值给dev_num
int ret = alloc_chrdev_region(&ds18b20->dev_num, 0, 1, "myds18b20");
if (ret < 0)
{
printk("alloc_chrdev_region failed\n");
kfree(ds18b20);
return -1;
}
// 绑定字符设备和操作集
cdev_init(&ds18b20->ds18b20_cdev, &ds18b20_fops);
// 设置设备的所有者(THIS_MODULES)为当前模块
ds18b20->ds18b20_cdev.owner = THIS_MODULES;
// 添加一个字符设备到内核,将字符设备和设备号做绑定
cdev_add(&ds18b20->ds18b20_cdev, ds18b20->dev_num, 1);
// 动态申请设备类,(目录名)
ds18b20->ds18b20_class = class_create(THIS_MODULES, "Sensors");
if (IS_ERR(ds18b20->ds18b20_class))
{
printk("class_create failed\n");
cdev_del(&ds18b20->ds18b20_cdev);
unregister_chrdev_region(ds18b20->dev_num, 1);
kfree(ds18b20);
return -1;
}
// 动态申请设备节点,(文件名)
ds18b20->ds18b20_device = device_create(ds18b20->ds18b20_class, NULL, ds18b20->dev_num, NULL, "myds18b20");
if(IS_ERR(ds18b20->ds18b20_device){
printk("device_create failed\n");
class_destroy(ds18b20->ds18b20_class);
cdev_del(&ds18b20->ds18b20_cdev);
unregister_chrdev_region(ds18b20->dev_num, 1);
kfree(ds18b20);
return -1;
}
return 0;
}
// const 修饰被初始化后就不可改变的常量,匹配表要用数组格式
const struct of_device_id ds18b20_match_table[] = {
{.compatible = "ds18b20"},
// 最后一组空{}表示结束
{},
};
struct platform_driver ds18b20_driver = {
.driver = {
.owner = THIS_MODULES,
// 驱动名字
.name = "ds18b20",
// 设备树的匹配表
.of_match_table = ds18b20_match_table,
},
// 当设备和驱动匹配成功后,就会执行probe函数
.probe = ds18b20_probe,
}
static int __init
ds18b20_init()
{
// 添加一个driver
int ret = platform_driver_register(&ds18b20_driver);
if (ret < 0)
{
printk("register faile\n");
}
return 0;
}
// static静态函数,改变作用域,表示只能在本文件里被调用
static void __exit ds18b20_exit()
{
// 删除一个driver
device_destroy(ds18b20->ds18b20_class, ds18b20->dev_num);
// 删除一个class
class_destroy(ds18b20->ds18b20_class);
// 删除字符设备
cdev_del(&ds18b20->ds18b20_cdev);
// 释放设备号
unregister_chrdev_region(ds18b20->dev_num, 1);
// 释放内存
kfree(ds18b20);
platform_deiver_unregister(&ds18b20_driver);
}
module_init(ds18b20_init);
module_exit(ds18b20_exit);
MODULE_LICENSE("GPL");
设备树:
{
ds18b20_gpio:gpio0_b0{
//匹配名
compatible = "ds18b20";
//引脚信息
ds18b20-gpios = <&gpio0 PK_PB0 GPIO_ACTIVE_HIGH>;
//设置复用为GPIO
pinctrl-names = "default";
pinctrl-0 = <&ds18b20_gpio_ctrl>;
}
}
在pinctrl节点添加:
ds18b20_gpio{
ds18b20_gpio_ctrl:ds18b20-gpio-ctrl{
rockchip,pins = <0 PK_PB0 1 &pcfg_pull_none>;
};
}
解析设备树,获取信息:
在probe函数中添加节点获取信息:
//获取设备树中gpio信息
ds18b20->ds18b20_gpio = gpiod_get_optional(&dev->dev, "ds18b20", 0);
if(ds18b20->ds18b20_gpio == NULL){
device_destroy(ds18b20->ds18b20_class, ds18b20->dev_num);
class_destroy(ds18b20->ds18b20_class);
cdev_del(&ds18b20->ds18b20_cdev);
unregister_chrdev_region(ds18b20->dev_num, 1);
kfree(ds18b20);
printk("gpiod_get_optional failed\n");
return -1;
}
// 设置gpio为输出模式
gpiod_direction_output(ds18b20->ds18b20_gpio, 1);
驱动时序:
启动:复位信号 -> 发送0xcc跳过寻址 -> 发送0x44启动转换
读温度:复位信号 -> 跳过寻址 -> 发送0xbe读数据
设置:复位 -> 跳 -> 发送0x4e写数据 -> 数据
编写复位时序:
// 复位ds18b20
void ds18b20_reset(void)
{
// 设置gpio为输出模式
gpiod_direction_output(ds18b20->ds18b20_gpio, 1);
// 拉低gpio
gpiod_set_value(ds18b20->ds18b20_gpio, 0);
// 延时480us
udelay(480);
// 拉高gpio
gpiod_set_value(ds18b20->ds18b20_gpio, 1);
// 设置输入
gpiod_direction_input(ds18b20->ds18b20_gpio);
// 获取gpio状态
while (gpiod_get_value(ds18b20->ds18b20_gpio);
// 等待gpio状态被拉高
while(!gpiod_get_value(ds18b20->ds18b20_gpio);
udelay(480);
}
写时序:
// 写入一个位
void ds18b20_writebit(unsigned char bit)
{
// 设置gpio为输出模式
gpiod_direction_output(ds18b20->ds18b20_gpio, 1);
// 拉低gpio
gpiod_set_value(ds18b20->ds18b20_gpio, 0);
//从机会在主机拉低后的15us后,开始采样数据,15u后数据线是低电平便是0,是高电平便是1
if(bit){
udelay(10);
// 拉高gpio
gpiod_set_value(ds18b20->ds18b20_gpio, 1);
}
udelay(65);
//释放
gpiod_direction_output(ds18b20->ds18b20_gpio, 1);
udelay(2);
}
//写一个字节
void ds18b20_writebyte(unsigned char byte){
for(int i=0;i<8;i++){
ds18b20_writebit(byte&0x01);
byte>>=1;
}
}
读时序:
//读一个位
unsigned char ds18b20_readbit(void){
// 设置gpio为输出模式
gpiod_direction_output(ds18b20->ds18b20_gpio, 1);
// 拉低gpio
gpiod_set_value(ds18b20->ds18b20_gpio, 0);
//延时2us
udelay(2);
// 设置输入
gpiod_direction_input(ds18b20->ds18b20_gpio);
// 延时10us
udelay(10);
// 获取gpio状态
unsigned char bit = gpiod_get_value(ds18b20->ds18b20_gpio);
// 延时60us
udelay(60);
// 释放
gpiod_direction_output(ds18b20->ds18b20_gpio, 1);
return bit;
}
//读一个字节
unsigned char ds18b20_readbyte(void){
unsigned char byte=0;
for(int i=0;i<8;i++){
byte>>=1;
if(ds18b20_readbit()){
byte|=0x80;
}
return byte;
}
DS1820驱动:
复位
跳过rom
启动转换
延时750ms
复位
跳过rom
读数据
接收数据
/// @brief 读取温度数据
/// @param void
/// @return temp 温度值
int ds18b20_readtemp(void){
int temp_l = 0;
int temp_h = 0;
// 复位ds18b20
ds18b20_reset();
// 写入命令0xCC
ds18b20_writebyte(0xCC);
// 写入命令0x44
ds18b20_writebyte(0x44);
// 延时750ms
msleep(750);
// 复位ds18b20
ds18b20_reset();
// 写入命令0xCC
ds18b20_writebyte(0xCC);
// 写入命令0xBE
ds18b20_writebyte(0xBE);
// 读取温度低字节
temp_l = ds18b20_readbyte();
// 读取温度高字节
temp_h = ds18b20_readbyte();
// 计算温度值
int temp = (temp_h << 8) | temp_l;
// 返回温度值
return temp;
}
补全用户与内核的接口函数:
ssize_t ds18b20_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
int temp = ds18b20_readtemp();
if(copy_to_user(buf, &temp, sizeof(temp))){
return -EFAULT;
}
return 0;
}
三.编写用户程序:
转换规则:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
/// @brief 根据获取温度值转换位温度实际数值
/// @param value 获取的温度值
void ds18b20_get_temp(int value) {
char sig;
float temp;
// 判断温度正负,前5位是符号位,0表示正,1表示负
if((value >> 11)& 0x01){
sig = '-';
// 如果是负数,需要将高8位取反,然后加上1
value = ~value + 1;
//高5位是符号位,需要去掉,清零即可
value &= ~(0xf8<<8);
}else{
sig = '+';
}
//精度转换,默认精度是12位,即小数点后四位,精度0.0625
temp = (float)(value* 0.0625);
printf("Temperature: %c%.4f\n", sig, temp);
}
int main() {
int fd;
int temp_value;
fd = open("/dev/ds18b20", O_RDWR);
if(fd < 0) {
printf("Error opening file\n");
return -1;
}
// Perform read/write operations on the device file
while(1){
read(fd, &temp_value, sizeof(temp_value));
ds18b20_get_temp(temp_value);
}
close(fd);
return 0;
}
四. 使用IOctrl修改分辨率
定义命令
_IO(type, nr):用来定义不带参数的ioctl命令。
_IOR(type,nr,size):用来定义用户程序向驱动程序写参数的ioctl命令。
_IOW(type,nr,size):用来定义用户程序从驱动程序读参数的ioctl命令。
_IOWR(type,nr,size):用来定义带读写参数的驱动命令。
-
dir,即direction,表示ioctl命令的访问模式,分为无数据(_IO)、读数据(_IOR)、写数据(_IOW)、读写数据(_IOWR)四种模式。
-
type,即device type,表示设备类型,也可翻译成“幻数”或“魔数”,可以是任意一个char型字符,如’a’、‘b’、‘c’等,其主要作用是使ioctl命令具有唯一的设备标识。不过在内核中’w’、‘y’、'z’三个字符已经被使用了。
-
nr,即number,命令编号/序数,取值范围0~255,在定义了多个ioctl命令的时候,通常从0开始顺次往下编号。
-
size,涉及到ioctl的参数arg,占据13bit或14bit,这个与体系有关,arm使用14bit。用来传递arg的数据类型的长度,比如如果arg是int型,我们就将这个参数填入int,系统会检查数据类型和长度的正确性。
完整的存储结构
#define SET_RESOLUTION _IOW('A', 0, int)
// ds18b20的ioctrl
long ds18b20_ioctl(struct inode *node, struct file *file, unsigned int cmd, unsigned long arg)
{
if (cmd == SET_RESOLUTION)
{
if (arg >= 9 && arg <= 12)
{
ds18b20_set_resolution(arg);
// 读取分辨率,检验是否成功
if (check_resolution(arg))
{
return -EINVAL;
printk("set resolution failed\n");
}
else
{
printk("set resolution successful\n");
}
}
}
return 0;
}
// 设置温度精度
void ds18b20_set_resolution(int resolution)
{
// 复位ds18b20
ds18b20_reset();
// 写入命令0xCC
ds18b20_writebyte(0xCC);
// 写入命令0x4E
ds18b20_writebyte(0x4E);
// 需要先写入温度上下限报警值
ds18b20_writebyte(60);
ds18b20_writebyte(10);
switch (resolution)
{
case /* constant-expression */ 9:
/* code */
ds18b20_writebyte(0x1f);
break;
case /* constant-expression */ 10:
/* code */
ds18b20_writebyte(0x3f);
break;
case /* constant-expression */ 11:
/* code */
ds18b20_writebyte(0x5f);
break;
case /* constant-expression */ 12:
/* code */
ds18b20_writebyte(0x7f);
break;
default:
break;
}
}
// 检验精度
int check_resolution(int resolution)
{
int ret = 1;
ds18b20_reset();
ds18b20_writebyte(0xCC);
ds18b20_writebyte(0xBE);
//按顺序读取,第五个字节为温度精度
ds18b20_readbyte();
ds18b20_readbyte();
ds18b20_readbyte();
ds18b20_readbyte();
switch (resolution)
{
case 9:
if (ds18b20_readbyte == 0x1f)
{
ret = 0;
}
break;
case 10:
if (ds18b20_readbyte == 0x3f)
{
ret = 0;
}
break;
case 11:
if (ds18b20_readbyte == 0x5f)
{
ret = 0;
}
break;
case 12:
if (ds18b20_readbyte == 0x7f)
{
ret = 0;
}
break;
default:
break;
}
}
编写用户程序添加IO功能:
定义命令
#define SET_RESOLUTION _IOW('A', 0, int)
// Set the resolution to 12 bits
ioctl(fd, SET_RESOLUTION, 12);
五.完整驱动和APP
driver
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h >
#include <linux/delay.h>
#include <linux/uaccess.h>
#define SET_RESOLUTION _IOW('A', 0, int)
// 定义一个结构体,用于存储设备信息
struct ds18b20_data
{
dev_t dev_num;
struct cdev ds18b20_cdev;
struct class *ds18b20_class;
struct device *ds18b20_device;
struct gpio_desc *ds18b20_gpio;
};
struct ds18b20_data *ds18b20;
// 复位ds18b20
void ds18b20_reset(void)
{
// 设置gpio为输出模式
gpiod_direction_output(ds18b20->ds18b20_gpio, 1);
// 拉低gpio
gpiod_set_value(ds18b20->ds18b20_gpio, 0);
// 延时480us
udelay(480);
// 拉高gpio
gpiod_set_value(ds18b20->ds18b20_gpio, 1);
// 设置输入
gpiod_direction_input(ds18b20->ds18b20_gpio);
// 获取gpio状态
while (gpiod_get_value(ds18b20->ds18b20_gpio);
// 等待gpio状态被拉高
while(!gpiod_get_value(ds18b20->ds18b20_gpio);
udelay(480);
}
// 写入一个位
void ds18b20_writebit(unsigned char bit)
{
// 设置gpio为输出模式
gpiod_direction_output(ds18b20->ds18b20_gpio, 1);
// 拉低gpio
gpiod_set_value(ds18b20->ds18b20_gpio, 0);
// 从机会在主机拉低后的15us后,开始采样数据,15u后数据线是低电平便是0,是高电平便是1
if (bit)
{
udelay(10);
// 拉高gpio
gpiod_set_value(ds18b20->ds18b20_gpio, 1);
}
udelay(65);
// 释放
gpiod_direction_output(ds18b20->ds18b20_gpio, 1);
udelay(2);
}
// 写一个字节
void ds18b20_writebyte(unsigned char byte)
{
for (int i = 0; i < 8; i++)
{
ds18b20_writebit(byte & 0x01);
byte >>= 1;
}
}
// 读一个位
unsigned char ds18b20_readbit(void)
{
// 设置gpio为输出模式
gpiod_direction_output(ds18b20->ds18b20_gpio, 1);
// 拉低gpio
gpiod_set_value(ds18b20->ds18b20_gpio, 0);
// 延时2us
udelay(2);
// 设置输入
gpiod_direction_input(ds18b20->ds18b20_gpio);
// 延时10us
udelay(10);
// 获取gpio状态
unsigned char bit = gpiod_get_value(ds18b20->ds18b20_gpio);
// 延时60us
udelay(60);
// 释放
gpiod_direction_output(ds18b20->ds18b20_gpio, 1);
return bit;
}
// 读一个字节
unsigned char ds18b20_readbyte(void)
{
unsigned char byte = 0;
for (int i = 0; i < 8; i++)
{
byte >>= 1;
if (ds18b20_readbit())
{
byte |= 0x80;
}
return byte;
}
}
/// @brief 读取温度数据
/// @param void
/// @return temp 温度值
int ds18b20_readtemp(void)
{
int temp_l = 0;
int temp_h = 0;
// 复位ds18b20
ds18b20_reset();
// 写入命令0xCC
ds18b20_writebyte(0xCC);
// 写入命令0x44启动温度转换
ds18b20_writebyte(0x44);
// 延时750ms
msleep(750);
// 复位ds18b20
ds18b20_reset();
// 写入命令0xCC
ds18b20_writebyte(0xCC);
// 写入命令0xBE读取温度值
ds18b20_writebyte(0xBE);
// 读取温度低字节
temp_l = ds18b20_readbyte();
// 读取温度高字节
temp_h = ds18b20_readbyte();
// 计算温度值
int temp = (temp_h << 8) | temp_l;
// 返回温度值
return temp;
}
// 设置温度精度
void ds18b20_set_resolution(int resolution)
{
// 复位ds18b20
ds18b20_reset();
// 写入命令0xCC
ds18b20_writebyte(0xCC);
// 写入命令0x4E
ds18b20_writebyte(0x4E);
// 需要先写入温度上下限报警值
ds18b20_writebyte(60);
ds18b20_writebyte(10);
switch (resolution)
{
case /* constant-expression */ 9:
/* code */
ds18b20_writebyte(0x1f);
break;
case /* constant-expression */ 10:
/* code */
ds18b20_writebyte(0x3f);
break;
case /* constant-expression */ 11:
/* code */
ds18b20_writebyte(0x5f);
break;
case /* constant-expression */ 12:
/* code */
ds18b20_writebyte(0x7f);
break;
default:
break;
}
}
// 检验精度
int check_resolution(int resolution)
{
int ret = 1;
ds18b20_reset();
ds18b20_writebyte(0xCC);
ds18b20_writebyte(0xBE);
//按顺序读取,第五个字节为温度精度
ds18b20_readbyte();
ds18b20_readbyte();
ds18b20_readbyte();
ds18b20_readbyte();
switch (resolution)
{
case 9:
if (ds18b20_readbyte == 0x1f)
{
ret = 0;
}
break;
case 10:
if (ds18b20_readbyte == 0x3f)
{
ret = 0;
}
break;
case 11:
if (ds18b20_readbyte == 0x5f)
{
ret = 0;
}
break;
case 12:
if (ds18b20_readbyte == 0x7f)
{
ret = 0;
}
break;
default:
break;
}
}
// 打开设备
int ds18b20_open(struct inode *node, struct file *file)
{
return 0;
}
ssize_t ds18b20_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
int temp = ds18b20_readtemp();
if (copy_to_user(buf, &temp, sizeof(temp)))
{
return -EFAULT;
}
return 0;
}
int ds18b20_release(struct inode *node, struct file *file)
{
return 0;
}
// ds18b20的ioctrl
long ds18b20_ioctl(struct inode *node, struct file *file, unsigned int cmd, unsigned long arg)
{
if (cmd == SET_RESOLUTION)
{
if (arg >= 9 && arg <= 12)
{
ds18b20_set_resolution(arg);
// 读取分辨率,检验是否成功
if (check_resolution(arg))
{
return -EINVAL;
printk("set resolution failed\n");
}
else
{
printk("set resolution successful\n");
}
}
}
return 0;
}
// 定义一个操作集
static struct file_operations ds18b20_fops = {
.owner = THIS_MODULES,
.open = ds18b20_open,
.read = ds18b20_read,
.release = ds18b20_release,
.unlocked_ioctl = ds18b20_ioctl,
}
int ds18b20_probe(struct platform_device * dev)
{
printk("This is probe\n");
// 动态申请内存
// GFP_KERNEL是一个宏,表示使用内核级分配器分配内存,这对于在Linux内核环境中使用硬件驱动程序非常有用,因为它可以确保内存分配的一致性和高效性。
ds18b20 = kzalloc(sizeof(struct ds18b20_data), GFP_KERNEL);
if (ds18b20 == NULL)
{
printk("kzalloc failed\n");
return -1;
}
// 动态申请字符设备号:设备号起始值、要分配的设备数量和设备号名称,最后赋值给dev_num
int ret = alloc_chrdev_region(&ds18b20->dev_num, 0, 1, "myds18b20");
if (ret < 0)
{
printk("alloc_chrdev_region failed\n");
kfree(ds18b20);
return -1;
}
// 绑定字符设备和操作集
cdev_init(&ds18b20->ds18b20_cdev, &ds18b20_fops);
// 设置设备的所有者(THIS_MODULES)为当前模块
ds18b20->ds18b20_cdev.owner = THIS_MODULES;
// 添加一个字符设备到内核,将字符设备和设备号做绑定
cdev_add(&ds18b20->ds18b20_cdev, ds18b20->dev_num, 1);
// 动态申请设备类,(目录名)
ds18b20->ds18b20_class = class_create(THIS_MODULES, "Sensors");
if (IS_ERR(ds18b20->ds18b20_class))
{
printk("class_create failed\n");
cdev_del(&ds18b20->ds18b20_cdev);
unregister_chrdev_region(ds18b20->dev_num, 1);
kfree(ds18b20);
return -1;
}
// 动态申请设备节点,(文件名)
ds18b20->ds18b20_device = device_create(ds18b20->ds18b20_class, NULL, ds18b20->dev_num, NULL, "myds18b20");
if(IS_ERR(ds18b20->ds18b20_device){
printk("device_create failed\n");
class_destroy(ds18b20->ds18b20_class);
cdev_del(&ds18b20->ds18b20_cdev);
unregister_chrdev_region(ds18b20->dev_num, 1);
kfree(ds18b20);
return -1;
}
//获取设备树中gpio信息
ds18b20->ds18b20_gpio = gpiod_get_optional(&dev->dev, "ds18b20", 0);
if(ds18b20->ds18b20_gpio == NULL){
device_destroy(ds18b20->ds18b20_class, ds18b20->dev_num);
class_destroy(ds18b20->ds18b20_class);
cdev_del(&ds18b20->ds18b20_cdev);
unregister_chrdev_region(ds18b20->dev_num, 1);
kfree(ds18b20);
printk("gpiod_get_optional failed\n");
return -1;
}
// 设置gpio为输出模式
gpiod_direction_output(ds18b20->ds18b20_gpio, 1);
return 0;
}
// const 修饰被初始化后就不可改变的常量,匹配表要用数组格式
const struct of_device_id ds18b20_match_table[] = {
{.compatible = "ds18b20"},
// 最后一组空{}表示结束
{},
};
struct platform_driver ds18b20_driver =
{
.driver = {
.owner = THIS_MODULES,
// 驱动名字
.name = "ds18b20",
// 设备树的匹配表
.of_match_table = ds18b20_match_table,
},
// 当设备和驱动匹配成功后,就会执行probe函数
.probe = ds18b20_probe,
}
static int __init
ds18b20_init()
{
// 添加一个driver
int ret = platform_driver_register(&ds18b20_driver);
if (ret < 0)
{
printk("register faile\n");
}
return 0;
}
// static静态函数,改变作用域,表示只能在本文件里被调用
static void __exit ds18b20_exit()
{
// 删除一个driver
device_destroy(ds18b20->ds18b20_class, ds18b20->dev_num);
// 删除一个class
class_destroy(ds18b20->ds18b20_class);
// 删除字符设备
cdev_del(&ds18b20->ds18b20_cdev);
// 释放设备号
unregister_chrdev_region(ds18b20->dev_num, 1);
// 释放内存
kfree(ds18b20);
platform_deiver_unregister(&ds18b20_driver);
}
module_init(ds18b20_init);
module_exit(ds18b20_exit);
MODULE_LICENSE("GPL");
APP:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define SET_RESOLUTION _IOW('A', 0, int)
/// @brief 根据获取温度值转换位温度实际数值
/// @param value 获取的温度值
void ds18b20_get_temp(int value) {
char sig;
float temp;
// 判断温度正负,前5位是符号位,0表示正,1表示负
if((value >> 11)& 0x01){
sig = '-';
// 如果是负数,需要将高8位取反,然后加上1
value = ~value + 1;
//高5位是符号位,需要去掉,清零即可
value &= ~(0xf8<<8);
}else{
sig = '+';
}
//精度转换,默认精度是12位,即小数点后四位,精度0.0625
temp = (float)(value* 0.0625);
printf("Temperature: %c%.4f\n", sig, temp);
}
int main() {
int fd;
int temp_value;
fd = open("/dev/ds18b20", O_RDWR);
if(fd < 0) {
printf("Error opening file\n");
return -1;
}
// Set the resolution to 12 bits
ioctl(fd, SET_RESOLUTION, 12);
// Perform read/write operations on the device file
while(1){
read(fd, &temp_value, sizeof(temp_value));
ds18b20_get_temp(temp_value);
}
close(fd);
return 0;
}