实现platform tree下的单总线协议驱动(Linux)

目录

1 开发环境

1.1 硬件系统参数

1.2 编译环境:Ubuntu

2 单总线协议驱动的实现

2.1 在内核的.dts文件

2.2 编写驱动代码

2.3 编写测试App

3 测试


1 开发环境

1.1 硬件系统参数

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

硬件: ATK-DL6Y2C开发板(芯片型号: IMX6LL)

内核启动位置: eMMC

1.2 编译环境:Ubuntu

版本信息: 20.04.2

Linux version 5.15.0-84-generic (buildd@lcy02-amd64-005) 
(gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0, GNU ld (GNU Binutils for Ubuntu) 2.34) 
#93~20.04.1-Ubuntu SMP Wed Sep 6 16:15:40 UTC 2023

linux kernel 版本信息: 4.1.15

交叉编译器版本信息: gcc version 4.9.4

2 单总线协议驱动的实现

       本文以DS18B20芯片为例,实现platform tree类型的驱动程序。其具体实现步骤,参看以下章节内容。

2.1 在内核的.dts文件

        在内核中的找到和板卡相关的.dts文件,并在该文件的根节点,添加和IO相关的配置。本人使用的板卡关联.dts文件地址为:

/home/mftang/linux_workspace/study_atk_dl6y2c/kernel/atk-dl6u2c/arch/arm/boot/dts/imx6ull-14x14-evk.dts

节点参数代码为:

    // mftang: user's ds18b20, 2024-1-29
	// IO: GPIO-4-PIN19
	mftangds18b20s {
		compatible = "atk-dl6y2c,testds18b20";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_gpio_mftangds18b20>;
		gpios = <&gpio4 19 GPIO_ACTIVE_HIGH>;
		status = "okay";
	};	

在.dts文件中可以看见:

实现platform tree下的单总线协议驱动(Linux)_第1张图片

       编译该文件后,通过NFS将生成的.dtb文件共享给开发板。.dtb在开发版中怎么应用?请参看文章:

如何在内核中.dts文件添加用户节点(Linux kernel)-CSDN博客

2.2 编写驱动代码

1) 编写驱动代码,本例程采用platform 驱动架构,其具体代码如下:

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

使用方法:
    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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


#define DEVICE_NAME      "tree_ds18b20"     // dev/treeds18b20


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


/* ds18b20dev设备结构体 */
struct ds18b20stru_dev{
    dev_t   devid;                 /* 设备号    */
    struct  cdev cdev;             /* cdev        */
    struct  class *class;          /* 类         */
    struct  device *device;        /* 设备        */
    int     major;                 /* 主设备号    */    
    struct  device_node *node;     /* 设备节点 */
    struct  gpio_desc *pin;
};

static wait_queue_head_t ds18b20_wq;
struct ds18b20stru_dev ds18b20dev;         /* ds18b20设备 */


/* ds18b20 dirver */

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;

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

    if (!timeout_count)
        return -1;

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

    if (!timeout_count)
        return -1;

    return 0;
}


static int ds18b20_check( void )
{
    gpiod_direction_output(ds18b20dev.pin, 0);

    tempudelay(480);
    gpiod_direction_input(ds18b20dev.pin);
   
    if (ds18b20_wait_for_ack())
    {
        return -1;
    }
    else
        return 0;

}

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

    // write data bit
    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;
} 


/*
    linux driver 驱动接口: 
    实现对应的open/read/write等函数,填入file_operations结构体
*/
static ssize_t ds18b20_drv_read (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_close(struct inode *node, struct file *file)
{
    printk(" %s line %d \r\n",  __FUNCTION__, __LINE__);

    return 0;
}

static int ds18b20_drv_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &ds18b20dev; /* 设置私有数据  */
    return 0;
}

/* 
    定义driver的file_operations结构体
*/
static struct file_operations ds18b20_fops = {
    .owner   = THIS_MODULE,
    .read    = ds18b20_drv_read,
    .open    = ds18b20_drv_open,
    .release = ds18b20_drv_close,
};

/* 
     从platform_device获得GPIO
 */
static int ds18b20_probe(struct platform_device *pdev)
{
    printk("ds18b20 driver and device was matched!\r\n");
    
    /* 1. 获得硬件信息 */
    ds18b20dev.pin = gpiod_get(&pdev->dev, NULL, GPIOD_OUT_HIGH);
    if (IS_ERR(ds18b20dev.pin))
    {
        printk("%s line %d\n", __FUNCTION__, __LINE__);
        return -1;
    }
    
    /* 2. device_create */
    device_create( ds18b20dev.class, NULL, 
                   MKDEV( ds18b20dev.major, 0 ), NULL, 
                   DEVICE_NAME);    // device name 
    
    return 0;
}

static int ds18b20_remove(struct platform_device *pdev)
{
    printk("%s line %d\n", __FUNCTION__, __LINE__);
    
    device_destroy( ds18b20dev.class, MKDEV( ds18b20dev.major, 0));
    gpiod_put(ds18b20dev.pin);
    
    return 0;
}

/*
  match the gpio in .dtb
  
    //mftang: user's ds18b20, 2024-1-26
    // IO: GPIO-4-PIN19
    userds18b20s {
        compatible = "atk-dl6y2c,userds18b20s";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_gpio_ds18b20>;
        gpios = <&gpio4 19 GPIO_ACTIVE_LOW>;
        status = "okay";
    };    
*/
static const struct of_device_id atk_dl6y2c_ds18b20[] = {
    { .compatible =  "atk-dl6y2c,testds18b20" },
    { },
};

/* 1. 定义platform_driver */
static struct platform_driver ds18b20_driver = {
    .probe      = ds18b20_probe,
    .remove     = ds18b20_remove,
    .driver     = {
        .name   = "atk_ds18b20",
        .of_match_table = atk_dl6y2c_ds18b20,
    },
};

/* 2. 在入口函数注册platform_driver */
static int __init ds18b20_init(void)
{
    printk("%s line %d\n",__FUNCTION__, __LINE__);
     
    /* register file_operations  */
    ds18b20dev.major = register_chrdev(0, 
                                       DEVICE_NAME,     /* device name */
                                       &ds18b20_fops);  

    /* create the device class  */
    ds18b20dev.class = class_create(THIS_MODULE, "ds18b20_class");
    
    if (IS_ERR(ds18b20dev.class)) {
        printk("%s line %d\n", __FUNCTION__, __LINE__);
        unregister_chrdev( ds18b20dev.major, DEVICE_NAME);
        return PTR_ERR( ds18b20dev.class );
    }
     
    init_waitqueue_head(&ds18b20_wq);
    
    return platform_driver_register(&ds18b20_driver); 
}

/* 3. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 *    卸载platform_driver
 */
static void __exit ds18b20_exit(void)
{
    printk("%s line %d\n", __FUNCTION__, __LINE__);

    platform_driver_unregister(&ds18b20_driver);
    class_destroy(ds18b20dev.class);
    unregister_chrdev(ds18b20dev.major, DEVICE_NAME);
}


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

module_init(ds18b20_init);
module_exit(ds18b20_exit);

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

2)编写驱动代码的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_07_tree_ds18b20.o

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


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

2.3 编写测试App

1)编写测试代码,读取ds18b20温度数据,然后通过终端打印出来

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

#define devices   "/dev/tree_ds18b20"

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


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


int main(void)
{
    int fd;
    int count_run = 0;
    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 < 10000)
    {
        len = read(fd, &ds_struc, sizeof(Ds18b20Struc));
        count_run++;
        if (len > 0) {
           convert_temp(&ds_struc, count_run);
        } 
        else {
           perror("read ds18b20 device fail!\n");
        }
        sleep(1);
    }

    close(fd);

    return 0;
}

2) 编写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_07_tree_ds18b20: test_07_tree_ds18b20.o
	$(CC) $(CFLAGS) -o test_07_tree_ds18b20 test_07_tree_ds18b20.o
	$(STRIP) -s test_07_tree_ds18b20

clean:
	rm -f test_07_tree_ds18b20 test_07_tree_ds18b20.o

3 测试

        编译测试代码和驱动代码,然后把生成的文件copy到NFS共享目录下,登录开发板后,安装驱动,然后运行测试程序。

step-1: 安装驱动

实现platform tree下的单总线协议驱动(Linux)_第2张图片

step-2: 运行测试App

可以看App能正确的调用驱动程序,且能正确的去取DS18B20的值

实现platform tree下的单总线协议驱动(Linux)_第3张图片

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