基于RK3399Pro的TM1650键盘读取-IIC总线

目录

原理图

IIC总线简介

tm1650的特性

按键读写时序图

 数据命令设置

程序代码编写

添加设备树

驱动编写

匹配设备节点

文件探索

杂项设备

文件操作集

键值读取接口

IIC读取接口

上层应用代码

编写Makefile文件

测试步骤

编译源码

加载驱动

执行测试程序

实验现象


重要提示:

博客评审专家需要一定的粉丝,所以之后写的文档设置权限粉丝可见,还望谅解。

原理图

TM1650采用的是IIC接口。

IIC总线简介

IIC 即Inter-Integrated Circuit(集成电路总线),这种总线类型是由飞利浦半导体公司在八十年代初设计出来的一种简单、双向、二线制、同步串行总线

  • 起始状态:当SCL保持“高”时,SDA由“高”变为“低”;

  • 结束状态:当SCL保持“高”时,SDA由“低”变为“高”时;

  • 有效数据位传输:SDA线上的数据在SCL“高”期间必须是稳定的,只有当SCL线上的时钟信号为低时,数据线上的“高”或“低”状态才可以改变。SCL高电平期间为传输的数据位;

  • 空闲状态:IC 总线的 SDA 和 SCL 两条信号线同时处于高电平时,规定为总线的空闲状态;

应答信号和非应带信号:I2C 总线上的所有数据都是以 8 位字节传送的,发送器(主机)每发送一个字节,就在第9个时钟脉冲期间释放数据线,由接收器(从机)反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。对于反馈有效应答位 ACK 的要求是,接收器在第 9 个时钟脉冲之前的低电平期间将 SDA 线拉低,并且确保在该时钟的高电平期间为稳定的低电平。//第9位为从机反馈的应答信号,若为高电平则没有接受成功。

  • IIC读时序

  • IIC写时序

tm1650的特性

TM1650 是一种带键盘扫描接口的 LED(发光二极管显示器) 驱动控制专用电路。 内部集成有 MCU输入输出控制数字接口、数据锁存器、LED 驱动、键盘扫描、辉度调节等电路。TM1650 性能稳定、质量可靠、抗干扰能力强,可适用于 24 小时长期连续工作的应用场合。

按键读写时序图

读写的键盘值对应表格如下所示:

 数据命令设置

B7 B6 B5 B4 B3 B2 B1 B0 说明
0 1 0 0 1 0 0 0 模式命令
0 1 0 0 1 x x 1 读取按键数据命令

读取键盘的命令位0x49

程序代码编写

添加设备树

在设备树

arch/arm64/boot/dts/rockchip/rk3399pro-toybrick-prop-linux.dts 

中添加

&i2c6 {
        status = "okay";
        //i2c-scl-rising-time-ns = <800>;
        //i2c-scl-falling-time-ns = <200>;
        i2c-scl-rising-time-ns = <140>;
        i2c-scl-falling-time-ns = <30>;
        clock-frequency=<400000>;
        
        tm1650:tm1650@24{
                status = "okay";
                compatible = "tm1650";
                reg = <0x24>;
        };
};

驱动编写

匹配设备节点

static const struct i2c_device_id i2c_id[] = {
    { "tm1650", 0 },
    { }, /* Terminating entry */
};
​
static struct of_device_id i2c_match[] = {
    {.compatible = "tm1650" },
    { },
};
​
MODULE_DEVICE_TABLE(i2c, i2c_id);
​​
static struct i2c_driver i2c_driver = {
    .driver = {
        .name = "tm1650",
        .owner = THIS_MODULE,
        .of_match_table = of_match_ptr(i2c_match),
    },
    .probe = i2c_probe,
    .remove = i2c_remove,
    .id_table = i2c_id, 
};

文件探索

static int i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int ret;
    struct device_node *tm1650_node = NULL; 
    tm1650_client = client;
    
    printk("The match successful!\n");
    
    printk("client->addr = %x\n",client->addr);
    ret = misc_register(&gec3399_tm1650_misc);   //杂项设备
    if(ret < 0){
        printk("misc register error\n");
        goto err_register_error;        
    }
    
    tm1650_node = of_find_compatible_node(NULL, NULL,"tm1650"); 
    if(tm1650_node == NULL){
        printk("not node of compatible is tm1650\n");
        ret = -ENODEV;
        goto err_gpio_request;
    }
    
    printk("tm1650 dirve install succee\n");
    return 0;
​
err_gpio_request:
    misc_deregister(&gec3399_tm1650_misc);
err_register_error:
    return 0;
}

杂项设备

static struct miscdevice gec3399_tm1650_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "tm1650_drv",
    .fops = &gec3399_tm_fops,
};//杂项设备初始化

文件操作集

static const struct file_operations gec3399_tm_fops = {
    .owner = THIS_MODULE,
    .read = gec3399_tm_read,
};  //文件操作集结构体

键值读取接口

static ssize_t gec3399_tm_read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
    int ret;
    char data_buf = 0;
    
    //printk("<0>""sizeof(data_buf) = %d\nsizeof(tm() = %d\n",sizeof(data_buf),sizeof(value));
    msleep(250);
    //读取tm1650的值
    ret = i2c_read_byte(tm1650_client,RDADDR,&data_buf,1);
    if(ret != 1){
        printk("%s %d i2c read byte data err\n",__FUNCTION__,__LINE__);
        //return -1;
    }
    //value = (data_buf[0]<<8) | (data_buf[1]<<0);
    //printk("<0>""value = %d\n",data_buf);
    //按键值上报给用户空间
    ret = copy_to_user(buf, &data_buf, sizeof(data_buf));
    if(ret < 0){
        printk("<0>""%s %d err copy tm value to user\n",__FUNCTION__,__LINE__);
        return -EFAULT;
    }
    return ret;
}
​

IIC读取接口

static int i2c_read_byte(struct i2c_client * client, uint8_t reg, uint8_t * buf, int len)
{
    struct i2c_msg msgs[2];
​
    msgs[0].addr = client->addr;
    msgs[0].flags = 0;
    msgs[0].len = 1;
    msgs[0].buf = &reg;
​
    msgs[1].addr = client->addr;
    msgs[1].flags = I2C_M_RD;
    msgs[1].len = len;
    msgs[1].buf = buf;
​
    if(i2c_transfer(client->adapter, msgs, 2) != 2)
        return 0;
    return 1;
}

上层应用代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <poll.h>
​
int fd_tm = 0;
short tm_value = 0;  
​
int main(void)
{
    int ret;
    
    //打开设备节点 
    fd_tm = open("/dev/tm1650_drv", O_RDWR);
    if(fd_tm < 0)
    {
        perror("open tm_drv driver");
        return -1;      
    }

    while(1)
    {
        //读取键盘的按键值
        ret = read(fd_tm,&tm_value,1);
        if(ret<0)
        {
            perror("read error\n");
            return -1;
        }
        //打印出按键的按键值
        printf("tm_value = %xH \n",tm_value);   
        sleep(1);
    }
    close(fd_tm);
    
    return 0;
}

编写Makefile文件

obj-m += tm1650_drv.o
KERNELDIR:=/file/RK3399Pro/rk3399pro_git_repo/kernel
PWD:=$(shell pwd)
​
default:
    $(MAKE)  -C $(KERNELDIR) M=$(PWD) modules
test:
    aarch64-linux-gnu-gcc tm1650_test.c -o tm1650_test  
​
clean:
    rm -rf *.o *.order .*.cmd *.ko *.mod.c *.symvers *.tmp_versions

测试步骤

编译源码

在ubuntu中输入:

make

得到驱动目标文件tm1650_drv.ko

输入:

make test

得到测试目标文件:tm1650_test

加载驱动

在开发板命令终端输入:

insmod tm1650_drv.ko

执行测试程序

在开发板命令终端输入:

chmod 777 tm1650_test
./tm1650_test

实验现象

读取到键盘的按键值

root@linaro-alip:~/test# ./tm1650_test 
tm_value = 44H 
tm_value = 45H 
tm_value = 46H 

Guess you like

Origin blog.csdn.net/ZOROE123/article/details/121352625