嵌入式开发记录-day31 I2C设备 -- 触摸屏、i2c子系统
1、准备
1、写在前面的:
1、在Linux下IIC设备以及总线‘配置都需要向8051那样需要使用IO产生特定的波形;
2、需要在Linux的API中配置特殊设备地址,不同的设备有不同的地址;
3、需要将IIC设备添加到Linux总线上;以及内核里面;
2、linux内核API
1、注册IIC设备:i2c_board_info // 按照其他格式仿写
驱动注册及驱动卸载函数:i2c_add_driver、i2c_del_driver
读写IIC设备函数:i2c_fransfer、i2c_msg
3、查看已经生成好的设备地址:
1、查看IIC设备地址:ls /sys/bus/i2c/devices/
2、设备地址与原理图对应:3-0038(设备地址)---->I2C_3_SCL(addr:在datasheet中查找0x38)
3、查看i2c设备的名称:cat /sys/bus/i2c/devices/3-0038/name
4、烧写驱动前需要准备的:
本次实验将触摸屏当做IIC设备,因此烧写驱动之前需要先把原来的驱动去掉,重新编译、烧写镜像;
1、make menuconfig:Device Drivers-->input device support-->
Touchscreens-->FT5X0X based touchscreens(去掉)
vim arch/arm/mach-exynos/mach-itop4412.c 平台文件
5、在平台文件下添加I2c设备:在i2c_devs3[]中添加
{
I2C_BOARD_INFO("i2c_test", 0x70>>1),
},
6、烧写镜像后,查看i2c设备名称cat /sys/bus/i2c/devices/3-0038/name 应该是i2c_test
7、i2c驱动注册和卸载
8、module_init()与late_initcall()异同点
1、都是以模块的方式注册驱动
2、module_init()加载的时间更早一点,late_initcall()加载时间更晚一些;
9、本IIC教程支持7寸9寸的电容触摸,所以暂时做不了本实验、可以使用AT24C08存储器实验教程来实验
2、加载触摸屏幕IIC驱动
#include <linux/kernel.h> #include <linux/module.h> #include <linux/i2c.h> #include <linux/input.h> #include <linux/delay.h> #include <linux/slab.h> #include <linux/gpio.h> #include <linux/platform_device.h> #ifdef CONFIG_HAS_EARLYSUSPEND #include <linux/earlysuspend.h> #endif #include <linux/regulator/consumer.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> #include <plat/ft5x0x_touch.h> //i2c_test_probe static int i2c_test_probe(struct i2c_client *client, const struct i2c_device_id *id) { printk("==%s:\n", __FUNCTION__); return 0; } static int __devexit i2c_test_remove(struct i2c_client *client) { i2c_set_clientdata(client, NULL); printk("==%s:\n", __FUNCTION__); return 0; } // static const struct i2c_device_id i2c_test_id[] = { { "i2c_test", 0 }, { } }; // i2c驱动结构体 static struct i2c_driver i2c_test_driver = { .probe = i2c_test_probe, // 初始化加载函数 .remove = __devexit_p(i2c_test_remove), // 驱动卸载函数 .id_table = i2c_test_id, // i2c设备IP地址列表 .driver = { .name = "i2c_test", // 驱动名称 .owner = THIS_MODULE, // 驱动所有者 }, }; void i2c_io_init(void) { // 主要完成触屏上电的一些初始化操作 其他的IIC设备一般不需要这一步骤 int ret; // GPL0_2---->屏幕IIC电平转换芯片使能端TP1_EN ret = gpio_request(EXYNOS4_GPL0(2), "TP1_EN"); if (ret) { printk(KERN_ERR "failed to request TP1_EN for " "I2C control\n"); //return err; } // 将GPL0_2端口设置为输出、并设置为输出模式 /* gpio_set_valuce() :只设置端口的电平 gpio_direction_output:设置输出模式后,还会设置电平 */ gpio_direction_output(EXYNOS4_GPL0(2), 1); s3c_gpio_cfgpin(EXYNOS4_GPL0(2), S3C_GPIO_OUTPUT); // 这句可以不用写 gpio_free(EXYNOS4_GPL0(2)); // 假设释放后维持高电平 mdelay(5); // 延时5ms // GPL0_3---->XEINT3 // 配置与触屏的中断有关 ret = gpio_request(EXYNOS4_GPX0(3), "GPX0_3"); if (ret) { gpio_free(EXYNOS4_GPX0(3)); ret = gpio_request(EXYNOS4_GPX0(3), "GPX0_3"); if(ret) { printk("ft5xox: Failed to request GPX0_3 \n"); } } // GPL0_3 拉低 gpio_direction_output(EXYNOS4_GPX0(3), 0); mdelay(200); // 拉低200ms 忙等待200ms无法操作此GPIO gpio_direction_output(EXYNOS4_GPX0(3), 1); // 拉高 s3c_gpio_cfgpin(EXYNOS4_GPX0(3), S3C_GPIO_OUTPUT); gpio_free(EXYNOS4_GPX0(3)); msleep(300); // } int i2c_test_init(void) { printk("==%s:\n", __FUNCTION__); i2c_io_init(); printk("==%s:\n", __FUNCTION__); return i2c_add_driver(&i2c_test_driver); } void i2c_text_exit(void) { printk("==%s:\n", __FUNCTION__); i2c_del_driver(&i2c_test_driver); } // module_init(keyirq_init); late_initcall(i2c_test_init); module_exit(i2c_text_exit); MODULE_LICENSE("Dual BSD/GPL");
3、编译测试
// 加载驱动之前 首先查看这句驱动名称,若不对则修改有问题,正常继续下面 cat /sys/bus/i2c/devices/3-0038/name 应该是i2c_test // 加载模块 [root@iTOP-4412]# insmod IICTest.ko [ 131.394724] ==i2c_test_init: [ 131.905048] ==i2c_test_init: [ 131.906483] ==i2c_test_probe: [root@iTOP-4412]# rmmod IICTest [ 203.942878] ==i2c_text_exit: [ 203.944427] ==i2c_test_remove:
4、包含的头文件
#include <linux/kernel.h> #include <linux/module.h> #include <linux/i2c.h> #include <linux/input.h> #include <linux/delay.h> #include <linux/slab.h> #include <linux/gpio.h> #include <linux/platform_device.h> #ifdef CONFIG_HAS_EARLYSUSPEND #include <linux/earlysuspend.h> #endif #include <linux/regulator/consumer.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> #include <plat/ft5x0x_touch.h>
5、读取单个寄存器的值
1、在操作从机(slave)的时候,需要说明是读操作还是写操作,并且说明读、些哪一个寄存器,需要传入一个地址;
2、了解上面这个之后再继续
6、读取单个寄存器
1 #include <linux/kernel.h> 2 #include <linux/module.h> 3 #include <linux/i2c.h> 4 #include <linux/input.h> 5 #include <linux/delay.h> 6 #include <linux/slab.h> 7 #include <linux/gpio.h> 8 #include <linux/platform_device.h> 9 #ifdef CONFIG_HAS_EARLYSUSPEND 10 #include <linux/earlysuspend.h> 11 #endif 12 #include <linux/regulator/consumer.h> 13 #include <mach/gpio.h> 14 #include <plat/gpio-cfg.h> 15 #include <plat/ft5x0x_touch.h> 16 17 // 读寄存器数据 18 static int i2c_test_read_reg(struct i2c_client *client,u8 addr, u8 *pdata) 19 { 20 u8 buf1[4] = { 0 }; 21 u8 buf2[4] = { 0 }; 22 // 数据结构体初始化 23 struct i2c_msg msgs[] = { 24 { 25 .addr = client->addr, //0x38 26 .flags = 0, // 写 27 .len = 1, // 要写的数据的长度 28 .buf = buf1, // 发送数据缓冲区 29 }, 30 { 31 .addr = client->addr, 32 .flags = I2C_M_RD,/* read data, from slave to master */ 33 .len = 1, // 接收/读数据长度 34 .buf = buf2, // 接收缓冲区 35 }, 36 }; 37 int ret; 38 buf1[0] = addr; // 设备的固件地址0xa6 主机查找设备 根据i2c设备的固件地址? 39 ret = i2c_transfer(client->adapter, msgs, 2); 40 if (ret < 0) { 41 pr_err("read reg (0x%02x) error, %d\n", addr, ret); 42 } else { 43 *pdata = buf2[0]; // 读取数据成功 将数据返回 44 } 45 return ret; 46 } 47 48 static int i2c_test_read_fw_reg(struct i2c_client *client,unsigned char* val) 49 { 50 int ret; 51 *val = 0xff; // 定义获取数据的临时变量 52 ret = i2c_test_read_reg(client,0xa6, val); 53 // 0xa6 i2c 设备的固件地址firmware address 54 printk("ts reg 0xa6 val is %d\n",*val); 55 return ret; 56 } 57 //i2c_test_probe 58 // 从机地址 i2c设备的ID号 59 static int i2c_test_probe(struct i2c_client *client, const struct i2c_device_id *id) 60 { 61 unsigned char val; 62 printk("==%s:\n", __FUNCTION__); 63 i2c_test_read_fw_reg(client,&val); // 读取数据 64 return 0; 65 } 66 // 传入一个从机地址 67 static int __devexit i2c_test_remove(struct i2c_client *client) 68 { 69 i2c_set_clientdata(client, NULL); // 设置从机的数据为空 70 printk("==%s:\n", __FUNCTION__); 71 return 0; 72 } 73 // 74 static const struct i2c_device_id i2c_test_id[] = { 75 { "i2c_test", 0 }, 76 { } 77 }; 78 79 // i2c驱动结构体 80 static struct i2c_driver i2c_test_driver = { 81 .probe = i2c_test_probe, // 初始化加载函数 82 .remove = __devexit_p(i2c_test_remove), // 驱动卸载函数 83 .id_table = i2c_test_id, // i2c设备IP地址列表 84 .driver = { 85 .name = "i2c_test", // 驱动名称 86 .owner = THIS_MODULE, // 驱动所有者 87 }, 88 }; 89 static void i2c_io_init(void) 90 { 91 // 主要完成触屏上电的一些初始化操作 其他的IIC设备一般不需要这一步骤 92 int ret; // GPL0_2---->屏幕IIC电平转换芯片使能端TP1_EN 93 ret = gpio_request(EXYNOS4_GPL0(2), "TP1_EN"); 94 if (ret) { 95 printk(KERN_ERR "failed to request TP1_EN for " 96 "I2C control\n"); 97 //return err; 98 } 99 // 将GPL0_2端口设置为输出、并设置为输出模式 100 /* gpio_set_valuce() :只设置端口的电平 101 gpio_direction_output:设置输出模式后,还会设置电平 102 */ 103 gpio_direction_output(EXYNOS4_GPL0(2), 1); 104 s3c_gpio_cfgpin(EXYNOS4_GPL0(2), S3C_GPIO_OUTPUT); // 这句可以不用写 105 gpio_free(EXYNOS4_GPL0(2)); // 假设释放后维持高电平 106 mdelay(5); // 延时5ms 107 108 // GPL0_3---->XEINT3 // 配置与触屏的中断有关 109 ret = gpio_request(EXYNOS4_GPX0(3), "GPX0_3"); 110 if (ret) { 111 gpio_free(EXYNOS4_GPX0(3)); 112 ret = gpio_request(EXYNOS4_GPX0(3), "GPX0_3"); 113 if(ret) 114 { 115 printk("ft5xox: Failed to request GPX0_3 \n"); 116 } 117 } 118 // GPL0_3 拉低 119 gpio_direction_output(EXYNOS4_GPX0(3), 0); 120 mdelay(200); // 拉低200ms 忙等待200ms无法操作此GPIO 121 gpio_direction_output(EXYNOS4_GPX0(3), 1); // 拉高 122 s3c_gpio_cfgpin(EXYNOS4_GPX0(3), S3C_GPIO_OUTPUT); 123 gpio_free(EXYNOS4_GPX0(3)); 124 msleep(300); // 125 } 126 127 static int i2c_test_init(void) 128 { 129 printk("==%s:\n", __FUNCTION__); 130 i2c_io_init(); 131 printk("==%s:\n", __FUNCTION__); 132 return i2c_add_driver(&i2c_test_driver); 133 134 } 135 136 void i2c_text_exit(void) 137 { 138 printk("==%s:\n", __FUNCTION__); 139 i2c_del_driver(&i2c_test_driver); 140 } 141 142 // module_init(keyirq_init); 143 late_initcall(i2c_test_init); 144 module_exit(i2c_text_exit); 145 MODULE_LICENSE("Dual BSD/GPL");
7、其中用的结构体注释参考
https://www.cnblogs.com/deng-tao/p/6130080.html
8、对应用层提供访问接口
1 #include <linux/kernel.h> 2 #include <linux/module.h> 3 #include <linux/i2c.h> 4 #include <linux/input.h> 5 #include <linux/delay.h> 6 #include <linux/slab.h> 7 #include <linux/gpio.h> 8 #include <linux/platform_device.h> 9 #ifdef CONFIG_HAS_EARLYSUSPEND 10 #include <linux/earlysuspend.h> 11 #endif 12 #include <linux/regulator/consumer.h> 13 #include <mach/gpio.h> 14 #include <plat/gpio-cfg.h> 15 #include <plat/ft5x0x_touch.h> 16 17 struct i2c_client *this_client; 18 // 读寄存器数据 19 static int i2c_tes_read_reg(struct i2c_client *client,u8 addr, u8 *pdata) 20 { 21 u8 buf1[4] = { 0 }; 22 u8 buf2[4] = { 0 }; 23 // 数据结构体初始化 24 struct i2c_msg msgs[] = { 25 { 26 .addr = client->addr, //0x38 27 .flags = 0, // 写 28 .len = 1, // 要写的数据的长度 29 .buf = buf1, // 发送数据缓冲区 30 }, 31 { 32 .addr = client->addr, 33 .flags = I2C_M_RD,/* read data, from slave to master */ 34 .len = 1, // 接收/读数据长度 35 .buf = buf2, // 接收缓冲区 36 }, 37 }; 38 int ret; 39 buf1[0] = addr; // 设备的固件地址0xa6 主机查找设备 根据i2c设备的固件地址? 40 ret = i2c_transfer(client->adapter, msgs, 2); 41 if (ret < 0) { 42 pr_err("read reg (0x%02x) error, %d\n", addr, ret); 43 } else { 44 *pdata = buf2[0]; // 读取数据成功 将数据返回 45 } 46 return ret; 47 } 48 49 static void i2c_test_read_fw_reg(struct i2c_client *client,unsigned char* val) 50 { 51 int ret; 52 *val = 0xff; // 定义获取数据的临时变量 53 ret = i2c_test_read_reg(client,0xa6, val); 54 // 0xa6 i2c 设备的固件地址firmware address 55 printk("ts reg 0xa6 val is %d\n",*val); 56 return ret; 57 } 58 59 static int i2c_open_func(struct file *filp) 60 { 61 return 0; 62 } 63 64 static int i2c_release_func(struct file *filp) 65 { 66 return 0; 67 } 68 69 static ssize_t i2c_read_func(struct file *filp, char __user *buffer, size_t count, loff_t *ppos) 70 { 71 int ret; 72 u8 reg_data; 73 74 ret = copy_from_user(®_data,buffer,1); // 获取要读那个寄存器的地址 75 76 struct i2c_msg msgs[] = { 77 { 78 .addr = this_client->addr, //0x38 79 .flags = 0, //写 80 .len = 1, //要写的数据的长度 81 .buf = ®_data, // 写入需要读的寄存器的地址 82 }, 83 { 84 .addr = this_client->addr, 85 .flags = I2C_M_RD, // 读操作 86 .len = 1, // 读数据的长度 87 .buf = ®_data, // 读出的数据 88 }, 89 }; 90 ret = i2c_transfer(this_client->adapter, msgs, 2); // 返回应该2 表示2个msg传输成功 91 if (ret < 0) { 92 pr_err("read reg error!\n"); 93 } 94 // 上传数据到用户 95 ret = copy_to_user(buffer,®_data,1); 96 97 return ret; 98 } 99 100 // 写操作 先写一个寄存器的地址 101 // 再一个值 102 static ssize_t i2c_write_func(struct file *filp, char __user *buffer, size_t count, loff_t *ppos) 103 { 104 int ret; 105 u8 buf[2]; 106 struct i2c_msg msgs[1]; 107 108 ret = copy_from_user(&buf,buffer,2); // 从用户段获取写入寄存器的地址 以及数据 109 110 msgs[0].addr = this_client->addr; //0x38 111 msgs[0].flags = 0; //写 112 msgs[0].len = 2; //第一个是要写的寄存器地址,第二个是要写的内容 113 msgs[0].buf = buf; // 要写的地址数据 114 115 ret = i2c_transfer(this_client->adapter, msgs, 1); // ret == 1 表示传输1个msg 成功 116 if (ret < 0) { 117 pr_err("write reg 0x%02x error!\n",buf[0]); 118 } 119 // 猜测这句应该可以不用 待验证 120 ret = copy_to_user(buffer,buf,1); 121 122 return ret; 123 } 124 125 // 文件操作 126 static struct file_operations i2c_ops = { 127 .owner = THIS_MODULE, 128 .open = i2c_open_func, // 打开 129 .release= i2c_release_func, // 释放 130 .write = i2c_write_func, // 写操作 131 .read = i2c_read_func, // 读操作 132 }; 133 134 // i2c杂项设备结构体 135 static struct miscdevice i2c_dev = { 136 .minor = MISC_DYNAMIC_MINOR, 137 .fops = &i2c_ops, 138 .name = "i2c_control", 139 }; 140 141 //i2c_test_probe 142 // 从机地址 i2c设备的ID号 143 static int i2c_test_probe(struct i2c_client *client, const struct i2c_device_id *id) 144 { 145 unsigned char val; 146 this_client = client; 147 printk("==%s:\n", __FUNCTION__); 148 i2c_test_read_fw_reg(client,&val); // 读取数据 149 150 misc_register(&i2c_dev); // 在想设备注册 151 152 return 0; 153 } 154 // 传入一个从机地址 155 static int __devexit i2c_test_remove(struct i2c_client *client) 156 { 157 i2c_set_clientdata(client, NULL); 158 misc_deregister(&i2c_dev); // 杂项设备卸载 159 printk("==%s:\n", __FUNCTION__); 160 return 0; 161 } 162 // 163 static const struct i2c_device_id i2c_test_id[] = { 164 { "i2c_test", 0 }, 165 { } 166 }; 167 168 169 // i2c驱动结构体 170 static struct i2c_driver i2c_test_driver = { 171 .probe = i2c_test_probe, // 初始化加载函数 172 .remove = __devexit_p(i2c_test_remove), // 驱动卸载函数 173 .id_table = i2c_test_id, // i2c设备IP地址列表 174 .driver = { 175 .name = "i2c_test", // 驱动名称 176 .owner = THIS_MODULE, // 驱动所有者 177 }, 178 }; 179 180 181 static void i2c_io_init(void) 182 { 183 // 主要完成触屏上电的一些初始化操作 其他的IIC设备一般不需要这一步骤 184 int ret; // GPL0_2---->屏幕IIC电平转换芯片使能端TP1_EN 185 ret = gpio_request(EXYNOS4_GPL0(2), "TP1_EN"); 186 if (ret) { 187 printk(KERN_ERR "failed to request TP1_EN for " 188 "I2C control\n"); 189 //return err; 190 } 191 // 将GPL0_2端口设置为输出、并设置为输出模式 192 /* gpio_set_valuce() :只设置端口的电平 193 gpio_direction_output:设置输出模式后,还会设置电平 194 */ 195 gpio_direction_output(EXYNOS4_GPL0(2), 1); 196 s3c_gpio_cfgpin(EXYNOS4_GPL0(2), S3C_GPIO_OUTPUT); // 这句可以不用写 197 gpio_free(EXYNOS4_GPL0(2)); // 假设释放后维持高电平 198 mdelay(5); // 延时5ms 199 200 // GPL0_3---->XEINT3 // 配置与触屏的中断有关 201 ret = gpio_request(EXYNOS4_GPX0(3), "GPX0_3"); 202 if (ret) { 203 gpio_free(EXYNOS4_GPX0(3)); 204 ret = gpio_request(EXYNOS4_GPX0(3), "GPX0_3"); 205 if(ret) 206 { 207 printk("ft5xox: Failed to request GPX0_3 \n"); 208 } 209 } 210 // GPL0_3 拉低 211 gpio_direction_output(EXYNOS4_GPX0(3), 0); 212 mdelay(200); // 拉低200ms 忙等待200ms无法操作此GPIO 213 gpio_direction_output(EXYNOS4_GPX0(3), 1); // 拉高 214 s3c_gpio_cfgpin(EXYNOS4_GPX0(3), S3C_GPIO_OUTPUT); 215 gpio_free(EXYNOS4_GPX0(3)); 216 msleep(300); // 217 } 218 219 static int i2c_test_init(void) 220 { 221 printk("==%s:\n", __FUNCTION__); 222 i2c_io_init(); 223 printk("==%s:\n", __FUNCTION__); 224 return i2c_add_driver(&i2c_test_driver); 225 226 } 227 228 static void i2c_text_exit(void) 229 { 230 printk("==%s:\n", __FUNCTION__); 231 i2c_del_driver(&i2c_test_driver); 232 } 233 234 // module_init(keyirq_init); 235 late_initcall(i2c_test_init); 236 module_exit(i2c_text_exit); 237 MODULE_LICENSE("Dual BSD/GPL");
这个主要注意读写操作的逻辑顺序
9、其他注释
给应用提供读取i2c设备的接口:
1、在probe函数中注册杂项设备,连接到文件操作结构体,实现对i2c设备的打开读写操作
2、 int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)
参考这篇文章
https://www.cnblogs.com/jiangzhaowei/p/10906800.html
10、应用读写IIC设备
1 #include <stdio.h> 2 3 #include <sys/types.h> 4 #include <sys/stat.h> 5 #include <fcntl.h> 6 #include <unistd.h> 7 #include <sys/ioctl.h> 8 9 int main(int argc,char **argv) 10 { 11 int fd,ret; 12 char *i2c_device = "/dev/i2c_control"; 13 unsigned char buffer[2]; 14 15 printf("open %s!\n",i2c_device); 16 if((fd = open(i2c_device,O_RDWR|O_NDELAY))<0) 17 printf("APP open %s failed",i2c_device); 18 else{ 19 printf("APP open %s success!\n",i2c_device); 20 } 21 22 //读一个数据0xa6 23 buffer[0] = 0xa6; 24 ret = read(fd,buffer,1); 25 if(ret<0) 26 printf("i2c read failed!\n"); 27 else{ 28 printf("i2c read reg 0xa6 data is 0x%02x!\n",buffer[0]); 29 } 30 31 //01先从0x00读出一个数据,02写一个数据到0x00,03再读出来对比 32 //01 33 buffer[0] = 0x00; 34 read(fd,buffer,1); 35 printf("i2c read reg 0x00 data is 0x%02x!\n",buffer[0]); 36 //02 37 buffer[0] = 0x00; 38 buffer[1] = 0x40; 39 ret = write(fd,buffer,2); 40 if(ret<0){ 41 printf("i2c write failed!\n"); 42 goto exit; 43 } 44 //03 45 buffer[0] = 0x00; 46 read(fd,buffer,1); 47 printf("i2c read reg 0x00 data is 0x%02x!\n",buffer[0]); 48 49 close(fd); 50 51 exit: 52 close(fd); 53 return -1; 54 }
11、I2C子系统:
首先参考:14、i2c子系统 - Lioker - 博客园 (cnblogs.com)