Linux下的DS18B20 驱动设计

目录

1 DS18B20 特性介绍

2 IO属性配置

3 和linux相关的驱动代码实现

4 驱动程序Makefile

5 测试代码实现

6 测试代码Makefile

7 测试驱动


系统环境:

使用Linux内核: linux-imx-4.1.15-2.1.0-g3dc0a4b-v2.7.tar.bz2

硬件:正点原子ATK-DL6Y2C开发板

内核启动位置:eMMC

使用GPIO4_PIN19作为DS18B20 DQ的IO

1 DS18B20 特性介绍

硬件接口:

Linux下的DS18B20 驱动设计_第1张图片

实物连接图:

Linux下的DS18B20 驱动设计_第2张图片

操控DS18B20注意点 :

1) 在一条数据线上,可以连接多个DS18B20。每个DS18B20都内嵌不同的ID,所以需要先选择某个 DS18B20。如果只有一个DS18B20,就不需要选择。

2) 访问DS18B20的流程为:启动温度转换、读取温度。 怎么启动温度转换?方法如下:

Linux下的DS18B20 驱动设计_第3张图片

3) 时序操作注意点:

深黑色线表示由主机驱动信号,浅灰色线表示由DS18B20驱动信号。最开始时引脚是高电平,想要开始传输信号:必须要拉低至少480us,这是复位信号;然后拉高释放总线,等待15~60us之后,如果GPIO上连有DS18B20芯片,它会拉低60~240us:这就是回应。如果主机在最后检查到60~240us的低脉冲回应信号,则表示DS18B20初始化成功。

初始化设备步骤:

step-1: Master 拉低电平时间 480us < TX < 960 us

step-2:MCU IO设置为输入,此时IO会自动拉高(DS18B20 IO和上拉电阻连接,会置高IO), MCU等待ds18b20响应是否在线。

step-3: DS18B20发送在线信息(MCU IO读到低电平),该电平持续时间 60us < Tx < 240 us

Linux下的DS18B20 驱动设计_第4张图片

写命令操作:

bit-0: 拉低至少60us(写周期为60-120us) bit-1: 先拉低至少1us,然后拉高,整个写周期至少为60us

Linux下的DS18B20 驱动设计_第5张图片

读数据操作:

step-1: 主机先拉低IO电平,其只持续至少1us(建议10us)

step-2: 配置IO输入,MCU读取DS18B20 DQ上的电平

Linux下的DS18B20 驱动设计_第6张图片

2 IO属性配置

1)定义IO相关寄存器的地址

选择GPIO4_PIN19作为DS18B20的数据控制引脚,现在对该引脚进行配置

/***************************************************************
Copyright  2024-2029. All rights reserved.
文件名     : drv_03_ds18b20.c
作者       : [email protected]
版本       : V1.0
描述       : ds18b20 驱动程序, GPIO4_PIN19-----ds18b20 port
其他       : 无
日志       : 初版V1.0 2024/1/24 

使用方法:
    typedef struct{
        unsigned short  temperatureVal; //温度数据, 真实值val = temperatureVal *0.0625
        int sign;                       //符号位,1: 负数, 0: 正数
    }Ds18b20Struc;

    Ds18b20Struc ds_struc;
    
    void convert_temp()
    {
        read(fd, &ds_struc, sizeof(Ds18b20Struc));
        printf("ds18b20 Value: %.02f, sign: %d \n", ds_struc.temperatureVal*0.0625, 
                                                    ds_struc.sign);
    }
***************************************************************/
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MODE_NAME      "ds18b20"     // dev/ds18b20
#define DEVICE_CNT     1

#define CCM_CCGR3_BASE                          (0x20C4074)   // GPIO端口时钟配置
#define IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC_BASE    (0x20E01DC)   // IO_PIN 功能属性配置
#define IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC_BASE    (0x20E0468)   // IO_PIN 电平属性配置

#define GPIO4_GDIR_BASE                         (0x20A8004)   // GPIO 方向属性配置
#define GPIO4_DR_BASE                           (0x20A8000)   // GPIO 数据寄存器

#define PIN_SEC                                 19            // GPIO - 19 


static void __iomem *CCM_CCGR;
static void __iomem *IOMUXC_SW_MUX_CTL ;
static void __iomem *IOMUXC_SW_PAD_CTL ;

static void __iomem *GPIO_GDIR;
static void __iomem *GPIO_DR;

typedef struct{
    unsigned short   temperatureVal;    // 温度数据, 真实值val = temperatureVal *0.0625
    int sign;                           // 符号位,1: 负数, 0: 正数
}Ds18b20Struc;

/* 1.  struds18b20设备结构体 */
struct struds18b20_dev
{
    dev_t devid;            /* 设备号      */
    struct cdev cdev;       /* cdev     */
    struct class *class;    /* 类        */
    struct device *device;  /* 设备 	    */
    int major;              /* 主设备号     */
    int minor;              /* 次设备号     */
    struct device_node *nd; /* 设备节点 */
};


static struct struds18b20_dev struds18b20; /* structure ds18b20 设备 */

2)初始化IO的属性

static void gpio_init( void )
{
    unsigned int temp;

    /*
        gpio port: GPIO4-PIN-19
    */

    // remap the register address to MCU
    CCM_CCGR = ioremap(CCM_CCGR3_BASE, 4);  
    IOMUXC_SW_MUX_CTL = ioremap(IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC_BASE, 4);
    IOMUXC_SW_PAD_CTL = ioremap(IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC_BASE, 4); 

    GPIO_GDIR = ioremap(GPIO4_GDIR_BASE, 4);
    GPIO_DR = ioremap(GPIO4_DR_BASE, 4);

    /*
    step-1:
        使能GPIO-4 CLOCK
        register CCM_CCGR3  
        Address: 20C_4000h base + 74h offset = 20C_4074h
        CG6: bit[13:12] = 0b11
    */
    temp = readl(CCM_CCGR);
    temp &= ~(3 << 12);             /* 清除以前的设置 */
    temp |= (3<<12);                /* 设置新值 */
    writel(temp, CCM_CCGR);

    /*
     step-2:
       设置GPIO4_PIN20为IO
       register IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC
       Address: 20E_0000h base + 1E0h offset = 20E_01E0h
       MUX_MODE: bit[3:0] = 0b0101
       */
    temp = readl(IOMUXC_SW_MUX_CTL); 
    temp &=~(0xf);
    temp |= 0x05;
    writel(temp, IOMUXC_SW_MUX_CTL);

    /*
    step-3:
        设置GPIO_GDIR,配置该IO为 in/out
        Address: Base address + 4h offset
        0 INPUT — GPIO is configured as input.
        1 OUTPUT — GPIO is configured as output.
    */
    temp = readl(GPIO_GDIR); 
    temp |= (1< 0 ? true:false;
}

2) DS18B20硬件驱动代码实现

static void tempudelay(int usecs) 
{
    int pre,last;

    pre = ktime_get_boot_ns();
    
    while(1){
        last = ktime_get_boot_ns();
        if( (last - pre) >= (usecs*1000))
            break;
    }
}

static int ds18b20_wait_for_ack(void)
{
    int timeout_count = 500;

    gpio_set_input();

    /* 如果是高电平,等待 */
    while (get_pin_data() && --timeout_count)
    {
        udelay(1);
    }

    if (!timeout_count)
    return -1;

    /* 现在是低电平 */
    timeout_count = 500;
    while (!get_pin_data() && --timeout_count)
    {
        udelay(1);
    }

    if (!timeout_count)
        return -1;

    return 0;
}


static int ds18b20_check( void )
{
    gpio_set_output();
    set_pin_data(0);

    tempudelay(480);

    if (ds18b20_wait_for_ack())
        return -1;
    else
        return 0;

}

static void ds18b20_write_byte( unsigned char byte)
{
    unsigned char k;

    // write data bit
    gpio_set_output(); 

    for ( k = 0; k < 8; k++ )
    {
        if (byte & (1<sign = 0;
    if (temp2 > 0x7f)           //最高位为1时温度是负
    {
        temp1    = ~temp1;      //补码转换,取反加一
        temp2    = ~temp2+1; 
        pdsStruc->sign = 1;
    }

    //read tempeture value 
    tempval =  ((temp2 << 8) | temp1);
    printk(" [%s line %d ] read value: 0x%04x \r\n",
           __FUNCTION__, __LINE__, (u16)tempval);

    pdsStruc->temperatureVal = tempval;

    local_irq_restore(flags);

    return true;
}

3 和linux相关的驱动代码实现

static ssize_t ds18b20_read_val (struct file *file, char __user *buf, 
                                          size_t size, loff_t *offset)
{
    Ds18b20Struc dsStruc;
    bool result;

    result = ds18b20_process( &dsStruc );
    if( result ){
        result = copy_to_user(buf, &dsStruc, sizeof(Ds18b20Struc));
    }

    return sizeof(Ds18b20Struc);
}


static int ds18b20_drv_open(struct inode *node, struct file *file)
{
    //init device hardware 
    printk(" [%s line %d ] open the devices! \r\n",__FUNCTION__, __LINE__);

    return 0;
}

static int ds18b20_drv_close(struct inode *node, struct file *file)
{
    printk(" %s line %d \r\n",  __FUNCTION__, __LINE__);

    return 0;
}

/* 2. 定义自己的 file_operations 结构体 */
static struct file_operations ds18b20drv_drv = {
    .owner = THIS_MODULE,
    .open  = ds18b20_drv_open,
    .read = ds18b20_read_val,
    .release = ds18b20_drv_close,
};

/* 4. 把 file_operations 结构体告诉内核:注册驱动程序 */
/* 5. 安装驱动程序时,就会去调用这个入口函数 */
static int __init ds18b20_drv_init(void)
{
    printk("[%s line %d ] initial ds18b20 devices! \r\n",__FUNCTION__, __LINE__);

    /* 初始化硬件资源 */
    gpio_init();

    /* 注册字符设备驱动 */

    /* 1、创建设备号 */
    if (struds18b20.major)
    {
        /*  定义了设备号 */
        struds18b20.devid = MKDEV(struds18b20.major, 0);
        register_chrdev_region(struds18b20.devid, DEVICE_CNT, MODE_NAME);
    }
    else
    {
        /* 没有定义设备号,自动申请设备号 */
        alloc_chrdev_region(&struds18b20.devid, 0, DEVICE_CNT, MODE_NAME);

        struds18b20.major = MAJOR(struds18b20.devid); /* 获取分配号的主设备号 */
        struds18b20.minor = MINOR(struds18b20.devid); /* 获取分配号的次设备号 */
    }

    printk("struds18b20 major=%d,minor=%d \r\n", struds18b20.major, struds18b20.minor);

    /* 2、初始化cdev */
    struds18b20.cdev.owner = THIS_MODULE;
    cdev_init(&struds18b20.cdev, &ds18b20drv_drv);

    /* 3、添加一个cdev */
    cdev_add(&struds18b20.cdev, struds18b20.devid, DEVICE_CNT);

    /* 4、创建类 */
    struds18b20.class = class_create(THIS_MODULE, MODE_NAME);
    if (IS_ERR(struds18b20.class))
    {
        return PTR_ERR(struds18b20.class);
    }

    /* 5、创建设备 */
    struds18b20.device = device_create(struds18b20.class, NULL, 
                                       struds18b20.devid, NULL,
                                       MODE_NAME);
    if (IS_ERR(struds18b20.device))
    {
        return PTR_ERR(struds18b20.device);
    }

    return 0;
}

/* 6. 有入口函数就有出口函数:卸载驱动程序时就会去调用这个出口函数 */
static void __exit ds18b20_drv_exit(void)
{
    printk("[%s line %d ] exit ds18b20 drvices driver!\r\n",__FUNCTION__, __LINE__);

    gpio_uninit();

    /* 注销字符设备驱动 */
    cdev_del(&struds18b20.cdev);                             /*  删除cdev */
    unregister_chrdev_region(struds18b20.devid, DEVICE_CNT); /* 注销设备号 */

    device_destroy(struds18b20.class, struds18b20.devid);
    class_destroy(struds18b20.class);
}

/* 7. 其他完善:提供设备信息,自动创建设备节点 */

module_init(ds18b20_drv_init);
module_exit(ds18b20_drv_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("[email protected]");

4 驱动程序Makefile

PWD := $(shell pwd)

KERNEL_DIR=/home/mftang/linux_workspace/study_atk_dl6y2c/kernel/atk-dl6u2c
ARCH=arm
CROSS_COMPILE=/home/ctools/gcc-linaro-4.9.4-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-

export  ARCH  CROSS_COMPILE

obj-m:=drv_03_ds18b20.o

all:
	$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules


clean:
	rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions *.order *.symvers

5 测试代码实现

/***************************************************************
Copyright  2024-2029. All rights reserved.
文件名      : test_03_ds18b20.c
作者        : [email protected]
版本        : V1.0
描述        : ds18b20 driver 测试程序,用于测试 drv_03_ds18b20
日志        : 初版V1.0 2024/1/24 
使用方法    : ./test_03_ds18b20
***************************************************************/
#include 
#include 
#include 
#include 
#include 
#include 

#define devices   "/dev/ds18b20"

typedef struct{
    unsigned short  temperatureVal;     //温度数据, 真实值val = temperatureVal *0.0625
    int sign;                           //符号位,1: 负数, 0: 正数
}Ds18b20Struc;


void convert_temp( Ds18b20Struc *pStru )
{
    printf("ds18b20 Value: %.02f, sign: %d \n", pStru->temperatureVal*0.0625, 
                                                pStru->sign);
}


int main(void)
{
    int fd;
    int count_run = 10000;
    int len; 
    Ds18b20Struc ds_struc;

    fd = open(devices, 0);
    if (fd == -1){
        printf("can not open file: %s \n", devices);
        return -1;
    }

    while( count_run > 0)
    {
        len = read(fd, &ds_struc, sizeof(Ds18b20Struc));
        count_run--;
        if (len > 0) {
           convert_temp(&ds_struc);
        } 
        else {
           perror("read ds18b20 device fail!\n");
        }
        sleep(1);
    }

    close(fd);

    return 0;
}

6 测试代码Makefile

CFLAGS= -Wall -O2
CC=/home/ctools/gcc-linaro-4.9.4-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc
STRIP=/home/ctools/gcc-linaro-4.9.4-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip


test_03_ds18b20: test_03_ds18b20.o
	$(CC) $(CFLAGS) -o test_03_ds18b20 test_03_ds18b20.o
	$(STRIP) -s test_03_ds18b20

clean:
	rm -f test_03_ds18b20 test_03_ds18b20.o

7 测试驱动

1)使用NFS服务器,将驱动程序和测试发送到板卡中

Linux下的DS18B20 驱动设计_第7张图片

2)装载驱动程序,运行测试代码

Linux下的DS18B20 驱动设计_第8张图片

你可能感兴趣的:(linux,驱动开发,芯片驱动分析,linux)