Linux使用单总线驱动DS18b20

onewire单总线:


一. 基础知识:

单总线:串行模式,一条线,包括时钟线和数据线,根据设备地址,像IIC一样可实现一主多从

常见引脚:三根线,信号线,vcc,GND。

DS18B20温度传感器:9到12bit分辨率,对应数据后小数点的后1到4位,即最高精确到后四位小数。分辨率越高,采集数据所需要的时间越长。

二. 驱动编写:

  1. 字符驱动框架:
#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");
  1. 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
  1. 编写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");
  1. 补全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):用来定义带读写参数的驱动命令。

  1. dir,即direction,表示ioctl命令的访问模式,分为无数据(_IO)、读数据(_IOR)、写数据(_IOW)、读写数据(_IOWR)四种模式。

  2. type,即device type,表示设备类型,也可翻译成“幻数”或“魔数”,可以是任意一个char型字符,如’a’、‘b’、‘c’等,其主要作用是使ioctl命令具有唯一的设备标识。不过在内核中’w’、‘y’、'z’三个字符已经被使用了。

  3. nr,即number,命令编号/序数,取值范围0~255,在定义了多个ioctl命令的时候,通常从0开始顺次往下编号。

  4. 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;
}
  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
单总线是一种串行通信协议,它只需要一条数据线即可实现通信,这种协议非常适合与温度传感器等低速设备进行通信。 DS18B20温度传感器是一种具有数字温度传感器,它采用单总线接口,可以直接与STM32F103C8T6单片机进行通信。 下面是STM32F103C8T6单总线DS18B20温度传感器通讯的基本原理: 1. 初始化 在通信开始前,需要先初始化DS18B20温度传感器,初始化的具体步骤如下: a. 将总线拉低,保持至少480us以上,用于复位DS18B20。 b. 将总线拉高,等待15-60us,用于DS18B20发出存在脉冲。 c. 读取总线电平,判断DS18B20是否存在,如果存在,则通讯可以开始。 2. 发送指令 在DS18B20与STM32F103C8T6单片机建立通信后,STM32F103C8T6单片机需要向DS18B20发送指令,以获取温度数据。 发送指令的具体步骤如下: a. 将总线拉低,保持至少1us。 b. 发送指令字节,指令字节由3位地址码和5位功能码组成。 c. 等待DS18B20的响应,DS18B20会发送一个存在脉冲,然后发送应答信号。 3. 读取温度数据 在DS18B20接收到指令并响应后,STM32F103C8T6单片机就可以读取温度数据了。 读取温度数据的具体步骤如下: a. 将总线拉低,保持至少1us。 b. 发送读取温度指令字节。 c. DS18B20发送温度数据,STM32F103C8T6单片机接收并处理温度数据。 以上是STM32F103C8T6单总线DS18B20温度传感器通讯的基本原理。在实际应用中,还需要根据具体的需求进行相应的配置和调试。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

想和我重名?

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值