platfrom tree架构下实现单总线驱动(HS0038)

目录

概述

1 理论分析

1.1 红外编码简介

1.2 NEC协议编码

1.3 编码波形分析      

2 实现节点参数

2.1 添加节点数据至.dts

2.2 编译.dts文件

2.3 板卡更新.dtb文件

3. 代码实现

3.1 驱动代码实现

3.2 测试代码实现

4 测试

4.1 开发环境

4.1.1 硬件系统参数

4.1.2 编译环境:Ubuntu

4.2 验证驱动代码功能

5  使用逻辑分析仪分析波形


概述

        本文介绍在linux架构下,如何实现一个单总线驱动程序的详细过程,以HS0038为例,实现该芯片的解码功能,并把它加载在linux驱动框架下。然后编写测试程序验证驱动程序的功能。

        在完成驱动程序验证后,又使用逻辑分析仪工具,验证波形数据和驱动代码采集到的数据是否一致。

1 理论分析

1.1 红外编码简介

       红外线遥控是利用近红外光传送遥控指令的,波长为 0.76um~1.5um。用近红外作为遥控光源,是因为目前红外发射器件(红外发光管)与红外接收器件(光敏二极管、三极管及光电池)的发光与受光峰值波长一般为0.8um~0.94um, 在近红外光波段内,二者的光谱正好重合,能够很好地匹配,可以获得较高的传输效率及较高的可靠性。

       红外遥控的编码协议种类繁多,如: NEC、 Philips RC-5、 Philips RC-6、 Sony SIRC等,而使用最多的是 NEC 协议,下面介绍NEC 协议的编码格式。

1.2 NEC协议编码

       NEC 协议采用的是 PPM(Pulse Position Modulation,脉冲位置调制)进行编码。当我们按下遥控器的一个按键时,会发送一帧的数据。这一帧数据由引导码、地址码、地址反码、数据码、数据反码以及一位结束位(可忽略)组成。

platfrom tree架构下实现单总线驱动(HS0038)_第1张图片

 1.3 编码波形分析      

       由上图所示: 9ms 的高电平脉冲和之后的 4.5ms 的低电平组成了引导码。接着后面是地址码及数据码,低位在前,高位在后。最后是 562.5um 脉冲突发以表示消息传输的结束。 其中地址码加上地址反码的时间和数据码加上数据反码的时间是固定不变的(如数据码为 00000111,则数据反码则为 11111000),总是由 8 个“1”和 8 个“0”组成。地址码和数据码都是由“0”、“1”组成的,下面介绍逻辑“0”和逻辑“1”的编码方式,如图 :

platfrom tree架构下实现单总线驱动(HS0038)_第2张图片

红外遥控按下时发送的数据顺序如下:

那如果一只按着同一个按键不放,发送完一帧数据之后又会发送什么呢?

platfrom tree架构下实现单总线驱动(HS0038)_第3张图片

2 实现节点参数

2.1 添加节点数据至.dts

1) 使用 i.MX Pins Tool v6 配置IO Pin

platfrom tree架构下实现单总线驱动(HS0038)_第4张图片

2) 在板卡相关的.dts文件中,添加接口数据和配置参数。该文件地址:

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

step-1: 更新MUX_CTL参数。代码如下:

    // mftang: user's hs0038 pin 
        pinctrl_gpio_mftanghs0038: gpio-mftanghs0038 {
            fsl,pins = <
                MX6UL_PAD_CSI_MCLK__GPIO4_IO17   0x000090B0
            >;
        };

该部分代码在.dts文件中的位置:

platfrom tree架构下实现单总线驱动(HS0038)_第5张图片

step-2: 添加节点数据,代码如下:

	//mftang: user's HS0038, 2024-1-30
	// IO: GPIO-4-PIN17
	mftanghs0038 {
			compatible = "atk-dl6y2c,hs0038";
			pinctrl-names = "default";
			pinctrl-0 = <&pinctrl_gpio_mftanghs0038>;
			gpios = <&gpio4 17 GPIO_ACTIVE_HIGH>;
			status = "okay";
	};

该部分代码在.dts文件中的位置:

platfrom tree架构下实现单总线驱动(HS0038)_第6张图片

2.2 编译.dts文件

编译.dts文件,并把编译生成的.dtb文件发送到NFS共享目录下,便于在板卡中操作该文件。

1)在内核根目录下使用如下命令编译.dts文件

make dtbs

platfrom tree架构下实现单总线驱动(HS0038)_第7张图片

2) 复制 .dtb 文件至NFS共享目录

cp arch/arm/boot/dts/imx6ull-14x14-emmc-4.3-480x272-c.dtb  /home/mftang/nfs/atk_dl6y2c/

2.3 板卡更新.dtb文件

复制.dtb文件到相应的运行目录,然后重新板卡

在/proc/device-tree中可以看见device节点,然后可以在driver中使用该节点。

platfrom tree架构下实现单总线驱动(HS0038)_第8张图片

3. 代码实现

3.1 驱动代码实现

1)编写驱动代码

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

使用方法:
1) 在.dts文件中定义节点信息
    //mftang: user's HS0038, 2024-1-30
    // IO: GPIO-4-PIN17
    mftanghs0038 {
        compatible = "atk-dl6y2c,hs0038";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_gpio_mftanghs0038>;
        gpios = <&gpio4 17 GPIO_ACTIVE_HIGH>;
        status = "okay";
    };
    
2) 在驱动匹配列表 
static const struct of_device_id hs0038_of_match[] = {
    { .compatible = "atk-dl6y2c,hs0038" },
    { } // Sentinel
};

驱动程序用法:

typedef struct{
    int code; 
    int sign;     // -1: error, 0: success 
}HS0038Stru;

HS0038Stru stru_hs0038;

if (read(fd, &stru_hs0038, sizeof( HS0038Stru )) == sizeof( HS0038Stru ))
{
    if( !stru_hs0038.sign )
    {
        printf("get hs0038 : 0x%x \r\n", stru_hs0038.code);
    }
}

***************************************************************/
#include 
#include 
#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      "treehs0038"     // dev/treehs0038
#define OFF              0
#define ON               1

/* hs0038dev设备结构体 */
struct hs0038stru_dev{
    dev_t   devid;                 /* 设备号         */
    struct  cdev cdev;             /* cdev           */
    struct  class *class;          /* 类             */
    struct  device *device;        /* 设备           */
    int     major;                 /* 主设备号       */
    struct  device_node *node;     /* hs0038设备节点 */
    int     userhs0038;            /* hs0038 GPIO标号*/
    struct  gpio_desc *pin;
};

struct hs0038stru_dev hs0038dev;   /* hs0038设备 */

typedef struct{
    int code; 
    int sign;     // -1: error, 0: sucess 
}HS0038Stru;

HS0038Stru stru_hs0038;

static u64 hs0038_edge_time[100];
static wait_queue_head_t hs0038_wq;
static int irq;
static int hs0038_edge_cnt = 0;
static unsigned int hs0038_data = 0; 

static unsigned int hs0038_data_buf[8];
static int r, w;

static void put_data(unsigned int val)
{
    if (((w+1) & 7) != r)
    {
        hs0038_data_buf[w] = val;
        w = (w + 1) & 7;
    }
}

static int get_data(unsigned int *val)
{
    if (r == w)
    {
        return -1;
    }
    else
    {
        *val = hs0038_data_buf[r];
        r = (r + 1) & 7;        
        return 0;
    }
}

static int has_data(void)
{
    if (r == w)
        return 0;
    else
        return 1;
}

int hs0038_parse_data(unsigned int *val)
{
    u64 tmp;
    unsigned char data[4];
    int i, j, m;
    
    /* 判断是否重复码 */
    if (hs0038_edge_cnt == 4)
    {
        tmp = hs0038_edge_time[1] - hs0038_edge_time[0];
        if (tmp > 8000000 && tmp < 10000000)
        {
            tmp = hs0038_edge_time[2] - hs0038_edge_time[1];
            if (tmp < 3000000)
            {
                /* 获得了重复码 */
                *val = hs0038_data;
                return 0;
            }
        }
    }

    /* 接收到了68次中断 */
    m = 3;
    if (hs0038_edge_cnt >= 68)
    {
        /* 解析到了数据 */
        for (i = 0; i < 4; i++)
        {
            data[i] = 0;
            /* 先接收到bit0 */
            for (j = 0; j < 8; j++)
            {
                /* 数值: 1 */    
                if (hs0038_edge_time[m+1] - hs0038_edge_time[m] > 1000000)
                    data[i] |= (1<= 2)
    {
        if (hs0038_edge_time[hs0038_edge_cnt-1] - hs0038_edge_time[hs0038_edge_cnt-2] > 60000000)
        {
            /* 超时 */
            hs0038_edge_time[0] = hs0038_edge_time[hs0038_edge_cnt-1];
            hs0038_edge_cnt = 1;
            return IRQ_HANDLED; // IRQ_WAKE_THREAD;
        }
    }

    ret = hs0038_parse_data(&val);
    if (!ret)
    {
        /* 解析成功 */
        hs0038_edge_cnt = 0;
        printk("%s line %d :  get ir code = 0x%x  \r\n",  __FUNCTION__, __LINE__, val);
        put_data(val);
        wake_up(&hs0038_wq);   

        stru_hs0038.code =  val;
        stru_hs0038.sign = 0;
    }
    else if (ret == -2)
    {
        /* 解析失败 */
        stru_hs0038.sign = -1;
        hs0038_edge_cnt = 0;
    }
    
    return IRQ_HANDLED; // IRQ_WAKE_THREAD;
}


/*
    linux driver 驱动接口: 
    实现对应的open/read/write等函数,填入file_operations结构体
*/
static ssize_t hs0038_drv_read (struct file *file, char __user *buf, 
                                 size_t size, loff_t *offset)
{
    unsigned int val;
    int err;
    
    printk(" %s line %d \r\n",  __FUNCTION__, __LINE__);

    wait_event_interruptible(hs0038_wq, has_data());

    get_data(&val);

    err = copy_to_user(buf, &stru_hs0038, sizeof(HS0038Stru));
    
    return sizeof(HS0038Stru);
}

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

    return 0;
}

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

/* 
    定义driver的file_operations结构体
*/
static struct file_operations hs0038_fops = {
    .owner   = THIS_MODULE,
    .read    = hs0038_drv_read,
    .open    = hs0038_drv_open,

    .release = hs0038_drv_close,
};


/* 1. 从platform_device获得GPIO
 * 2. gpio=>irq
 * 3. request_irq
 */
static int hs0038_probe(struct platform_device *pdev)
{
    int result;
    
    printk("hs0038 driver and device was matched!\r\n");
    
    /* 1. 获得硬件信息 */
    hs0038dev.pin = gpiod_get(&pdev->dev, NULL, 0);
    if (IS_ERR(hs0038dev.pin))
    {
        printk("%s line %d get pin parameter error! \n", __FUNCTION__, __LINE__);
    }
    
    /* register irq*/
    irq = gpiod_to_irq(hs0038dev.pin);

    result = request_irq(irq, hs0038_isr, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "hs0038", NULL);

    
    /* 2. device_create */
    device_create( hs0038dev.class, NULL, 
                   MKDEV( hs0038dev.major, 0 ), NULL, 
                   DEVICE_NAME);        // device name 
    
    return 0;
}

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


static const struct of_device_id atk_dl6y2c_hs0038[] = {
    { .compatible = "atk-dl6y2c,hs0038" },
    { },
};

/* 1. 定义platform_driver */
static struct platform_driver hs0038_driver = {
    .probe      = hs0038_probe,
    .remove     = hs0038_remove,
    .driver     = {
        .name   = "atk_hs0038",
        .of_match_table = atk_dl6y2c_hs0038,
    },
};

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

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

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

    platform_driver_unregister(&hs0038_driver);
    class_destroy(hs0038dev.class);
    unregister_chrdev(hs0038dev.major, DEVICE_NAME);
}

/*
 4. 驱动入口和出口函数
*/
module_init(hs0038_init);
module_exit(hs0038_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_09_tree_hs0038.o

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


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

3)编译代码,并将生成的.ko复制到NFS共享目录中,然后装载到板卡中

platfrom tree架构下实现单总线驱动(HS0038)_第9张图片

3.2 测试代码实现

1)编写测试代码

/***************************************************************
Copyright  2024-2029. All rights reserved.
文件名  : test_09_tree_hs0038.c
作者    : [email protected]
版本    : V1.0
描述    : hs0038 测试程序,用于测试 drv_09_tree_hs0038
日志    : 初版V1.0 2024/1/29

typedef struct{
    int code; 
    int sign;     // -1: error, 0: success 
}HS0038Stru;

HS0038Stru stru_hs0038;

if (read(fd, &stru_hs0038, sizeof( HS0038Stru )) == sizeof( HS0038Stru ))
{
    if( !stru_hs0038.sign )
    {
        printf("get hs0038 : 0x%x \r\n", stru_hs0038.code);
    }
}

***************************************************************/
#include 
#include 
#include 
#include 
#include 
#include 

#define DEV_NAME    "/dev/treehs0038"


typedef struct{
    int code; 
    int sign;     // -1: error, 0: sucess 
}HS0038Stru;

HS0038Stru stru_hs0038;

int main(int argc, char **argv)
{
    int fd;
    int len = sizeof(HS0038Stru);

    
    fd = open(DEV_NAME, O_RDWR);
    if (fd < 0){
        printf("can not open file %s \r\n", DEV_NAME);
        return -1;
    }

    while(1){
        if (read(fd, &stru_hs0038, len) == len)
        {
            if( !stru_hs0038.sign )
            {
                printf("get hs0038 : 0x%x \r\n", stru_hs0038.code);
            }
        }
    }

    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_09_tree_hs0038: test_09_tree_hs0038.o
	$(CC) $(CFLAGS) -o test_09_tree_hs0038 test_09_tree_hs0038.o
	$(STRIP) -s test_09_tree_hs0038

clean:
	rm -f test_09_tree_hs0038 test_09_tree_hs0038.o

3) 编译测试代码,并复制到NFS挂载目录下

platfrom tree架构下实现单总线驱动(HS0038)_第10张图片

4 测试

4.1 开发环境

4.1.1 硬件系统参数

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

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

内核启动位置: eMMC

4.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

4.2 验证驱动代码功能

运行代码,使用遥控器按下不同的按键,可以得到不同的键值。

platfrom tree架构下实现单总线驱动(HS0038)_第11张图片

5  使用逻辑分析仪分析波形

遥控器一次发码的完整波形 

platfrom tree架构下实现单总线驱动(HS0038)_第12张图片

数据码分析,注意:低位在前,高位在后,可得,当前发送的遥控码是:0x0c

platfrom tree架构下实现单总线驱动(HS0038)_第13张图片

.查看repeat code:

platfrom tree架构下实现单总线驱动(HS0038)_第14张图片

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