原理图
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位为从机反馈的应答信号,若为高电平则没有接受成功。
tm1650的特性
TM1650 是一种带键盘扫描接口的 LED(发光二极管显示器) 驱动控制专用电路。 内部集成有 MCU输入输出控制数字接口、数据锁存器、LED 驱动、键盘扫描、辉度调节等电路。TM1650 性能稳定、质量可靠、抗干扰能力强,可适用于 24 小时长期连续工作的应用场合。
按键读写时序图
读写的键盘值对应表格如下所示:
数据命令设置
[tr]B7B6B5B4B3B2B1B0说明[/tr]
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 = %xn",client->addr);
ret = misc_register(&gec3399_tm1650_misc); //杂项设备
if(ret < 0){
printk("misc register errorn");
goto err_register_error;
}
tm1650_node = of_find_compatible_node(NULL, NULL,"tm1650");
if(tm1650_node == NULL){
printk("not node of compatible is tm1650n");
ret = -ENODEV;
goto err_gpio_request;
}
printk("tm1650 dirve install succeen");
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) = %dnsizeof(tm() = %dn",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 errn",__FUNCTION__,__LINE__);
//return -1;
}
//value = (data_buf[0]<<8) | (data_buf[1]<<0);
//printk("<0>""value = %dn",data_buf);
//按键值上报给用户空间
ret = copy_to_user(buf, &data_buf, sizeof(data_buf));
if(ret < 0){
printk("<0>""%s %d err copy tm value to usern",__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 = ®
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
#include
#include
#include
#include
#include
#include
#include
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 errorn");
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
原理图
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位为从机反馈的应答信号,若为高电平则没有接受成功。
tm1650的特性
TM1650 是一种带键盘扫描接口的 LED(发光二极管显示器) 驱动控制专用电路。 内部集成有 MCU输入输出控制数字接口、数据锁存器、LED 驱动、键盘扫描、辉度调节等电路。TM1650 性能稳定、质量可靠、抗干扰能力强,可适用于 24 小时长期连续工作的应用场合。
按键读写时序图
读写的键盘值对应表格如下所示:
数据命令设置
[tr]B7B6B5B4B3B2B1B0说明[/tr]
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 = %xn",client->addr);
ret = misc_register(&gec3399_tm1650_misc); //杂项设备
if(ret < 0){
printk("misc register errorn");
goto err_register_error;
}
tm1650_node = of_find_compatible_node(NULL, NULL,"tm1650");
if(tm1650_node == NULL){
printk("not node of compatible is tm1650n");
ret = -ENODEV;
goto err_gpio_request;
}
printk("tm1650 dirve install succeen");
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) = %dnsizeof(tm() = %dn",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 errn",__FUNCTION__,__LINE__);
//return -1;
}
//value = (data_buf[0]<<8) | (data_buf[1]<<0);
//printk("<0>""value = %dn",data_buf);
//按键值上报给用户空间
ret = copy_to_user(buf, &data_buf, sizeof(data_buf));
if(ret < 0){
printk("<0>""%s %d err copy tm value to usern",__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 = ®
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
#include
#include
#include
#include
#include
#include
#include
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 errorn");
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
举报