019——IIC模块驱动开发(基于EEPROM【AT24C02】和I.MX6uLL)
作者:mmseoamin日期:2024-04-29

目录

一、 IIC基础知识

二、Linux中的IIC(韦东山老师的学习笔记)

1. I2C驱动程序的层次

2. I2C总线-设备-驱动模型

2.1 i2c_driver

2.2 i2c_client

三、 AT24C02 介绍

四、 AT24C02驱动开发

实验 

驱动程序

应用程序


一、 IIC基础知识

总线类设备驱动——IIC_iic设备驱动-CSDN博客

Exynos_4412——IIC总线概述_.若使用iic总线让从机给主机发送一个字节的数据0xa2,画出scl和sda上的时序图-CSDN博客

STM32——IIC总线(MPU6050应用)_mpu6050例程-CSDN博客

写过好多次啦不打算重复写了。

二、Linux中的IIC(韦东山老师的学习笔记)

参考资料:

  • Linux内核文档:

    • Documentation\i2c\instantiating-devices.rst

    • Documentation\i2c\writing-clients.rst

  • Linux内核驱动程序示例:

    • drivers/eeprom/at24.c

    1. I2C驱动程序的层次

    019——IIC模块驱动开发(基于EEPROM【AT24C02】和I.MX6uLL),第1张

    I2C Core就是I2C核心层,它的作用:

    • 提供统一的访问函数,比如i2c_transfer、i2c_smbus_xfer等

    • 实现I2C总线-设备-驱动模型,管理:I2C设备(i2c_client)、I2C设备驱动(i2c_driver)、I2C控制器(i2c_adapter)

      2. I2C总线-设备-驱动模型

      019——IIC模块驱动开发(基于EEPROM【AT24C02】和I.MX6uLL),第2张

      2.1 i2c_driver

      i2c_driver表明能支持哪些设备:

      • 使用of_match_table来判断

        • 设备树中,某个I2C控制器节点下可以创建I2C设备的节点

          • 如果I2C设备节点的compatible属性跟of_match_table的某项兼容,则匹配成功

        • i2c_client.name跟某个of_match_table[i].compatible值相同,则匹配成功

      • 使用id_table来判断

        • i2c_client.name跟某个id_table[i].name值相同,则匹配成功

        i2c_driver跟i2c_client匹配成功后,就调用i2c_driver.probe函数。

        2.2 i2c_client

        i2c_client表示一个I2C设备,创建i2c_client的方法有4种:

        方法1

        • 通过I2C bus number来创建

          int i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len);
        • 通过设备树来创建

          	i2c1: i2c@400a0000 {
          		/* ... master properties skipped ... */
          		clock-frequency = <100000>;
          		flash@50 {
          			compatible = "atmel,24c256";
          			reg = <0x50>;
          		};
          		pca9532: gpio@60 {
          			compatible = "nxp,pca9532";
          			gpio-controller;
          			#gpio-cells = <2>;
          			reg = <0x60>;
          		};
          	};

          方法2

                  有时候无法知道该设备挂载哪个I2C bus下,无法知道它对应的I2C bus number。 但是可以通过其他方法知道对应的i2c_adapter结构体。 可以使用下面两个函数来创建i2c_client:

          • i2c_new_device

              static struct i2c_board_info sfe4001_hwmon_info = {
            	I2C_BOARD_INFO("max6647", 0x4e),
              };
              int sfe4001_init(struct efx_nic *efx)
              {
            	(...)
            	efx->board_info.hwmon_client =
            		i2c_new_device(&efx->i2c_adap, &sfe4001_hwmon_info);
            	(...)
              }

            i2c_new_probed_device

              static const unsigned short normal_i2c[] = { 0x2c, 0x2d, I2C_CLIENT_END };
              static int usb_hcd_nxp_probe(struct platform_device *pdev)
              {
            	(...)
            	struct i2c_adapter *i2c_adap;
            	struct i2c_board_info i2c_info;
            	(...)
            	i2c_adap = i2c_get_adapter(2);
            	memset(&i2c_info, 0, sizeof(struct i2c_board_info));
            	strscpy(i2c_info.type, "isp1301_nxp", sizeof(i2c_info.type));
            	isp1301_i2c_client = i2c_new_probed_device(i2c_adap, &i2c_info,
            						   normal_i2c, NULL);
            	i2c_put_adapter(i2c_adap);
            	(...)
              }

            差别:

            • i2c_new_device:会创建i2c_client,即使该设备并不存在

            • i2c_new_probed_device:

              • 它成功的话,会创建i2c_client,并且表示这个设备肯定存在

              • I2C设备的地址可能发生变化,比如AT24C02的引脚A2A1A0电平不一样时,设备地址就不一样

              • 可以罗列出可能的地址

              • i2c_new_probed_device使用这些地址判断设备是否存在

              • 方法3(不推荐):由i2c_driver.detect函数来判断是否有对应的I2C设备并生成i2c_client

              • 方法4:通过用户空间(user-space)生成 调试时、或者不方便通过代码明确地生成i2c_client时,可以通过用户空间来生成。

                  // 创建一个i2c_client, .name = "eeprom", .addr=0x50, .adapter是i2c-3
                  # echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-3/new_device
                  
                  // 删除一个i2c_client
                  # echo 0x50 > /sys/bus/i2c/devices/i2c-3/delete_device

                三、 AT24C02 介绍

                        AT24C02 是基于 I2C 总线的存储器件,由于接口方便,体积小,数据掉电不丢失等特点,在仪器仪表及工业自动化控制中得到大量的应用。百问网提供的 EEPROM 模块使用的就是 AT24C02,使用 8 位地址,存储容量为 2K bit,即 2048bit = 256*8bit = 256 Byte, 其中它被分为 32 页,每页 8Byte。

                019——IIC模块驱动开发(基于EEPROM【AT24C02】和I.MX6uLL),第3张

                        基于 I2C 接口的设备操作,我们主要关心的是 I2C 数据格式。 AT24C02 作为 EEPROM 存储设备,显然我们关心的是:怎么读写某个地址,即怎么发地址、怎么读写数据。 查阅《AT24CXX.pdf》手册,要写入一个字节,可如下操作:先发出设备地址, 再发出 WORD 地址(即存储地址),再发出数据。

                019——IIC模块驱动开发(基于EEPROM【AT24C02】和I.MX6uLL),第4张

                        反之,要读 AT24C02,如下图:涉及 2 次 I2C 传输。 先发出设备地址, WORD地址;再次发出设备地址,读到数据。

                019——IIC模块驱动开发(基于EEPROM【AT24C02】和I.MX6uLL),第5张

                019——IIC模块驱动开发(基于EEPROM【AT24C02】和I.MX6uLL),第6张

                四、 AT24C02驱动开发

                实验 

                019——IIC模块驱动开发(基于EEPROM【AT24C02】和I.MX6uLL),第7张 先添加设备树

                019——IIC模块驱动开发(基于EEPROM【AT24C02】和I.MX6uLL),第8张

                019——IIC模块驱动开发(基于EEPROM【AT24C02】和I.MX6uLL),第9张

                019——IIC模块驱动开发(基于EEPROM【AT24C02】和I.MX6uLL),第10张

                019——IIC模块驱动开发(基于EEPROM【AT24C02】和I.MX6uLL),第11张

                这是我们的设备树目录

                019——IIC模块驱动开发(基于EEPROM【AT24C02】和I.MX6uLL),第12张

                可以用find找到我们刚添加的设备目录 

                019——IIC模块驱动开发(基于EEPROM【AT24C02】和I.MX6uLL),第13张

                019——IIC模块驱动开发(基于EEPROM【AT24C02】和I.MX6uLL),第14张

                咱们用的这个小工具有个配置项可以设置一下很方便

                019——IIC模块驱动开发(基于EEPROM【AT24C02】和I.MX6uLL),第15张

                019——IIC模块驱动开发(基于EEPROM【AT24C02】和I.MX6uLL),第16张

                reg这个可能是特殊的加密文件需要用hexdump去看

                019——IIC模块驱动开发(基于EEPROM【AT24C02】和I.MX6uLL),第17张

                019——IIC模块驱动开发(基于EEPROM【AT24C02】和I.MX6uLL),第18张

                019——IIC模块驱动开发(基于EEPROM【AT24C02】和I.MX6uLL),第19张

                虽然我们是iic-1但是内核是从0开始的所以这个就是我们的设备目录

                019——IIC模块驱动开发(基于EEPROM【AT24C02】和I.MX6uLL),第20张

                插入模块后我们的驱动就出来了,没写的时候读出来是一堆乱码

                019——IIC模块驱动开发(基于EEPROM【AT24C02】和I.MX6uLL),第21张

                019——IIC模块驱动开发(基于EEPROM【AT24C02】和I.MX6uLL),第22张

                验证一下它的非易失性

                019——IIC模块驱动开发(基于EEPROM【AT24C02】和I.MX6uLL),第23张

                没毛病

                驱动程序

                #include "asm/uaccess.h"
                #include "linux/delay.h"
                #include "linux/i2c.h"
                #include 
                #include 
                #include 
                #include 
                #include 
                #include 
                #include 
                #include 
                #include 
                #include 
                #include 
                #include 
                #include 
                #include 
                #include 
                #include 
                #include 
                #include 
                #include 
                #include 
                #include 
                #include 
                #include 
                #include 
                #include 
                /* 主设备号                  */
                static int major = 0;
                static struct class *my_i2c_class;
                static struct i2c_client *g_client;
                static DECLARE_WAIT_QUEUE_HEAD(gpio_wait);
                struct fasync_struct *i2c_fasync;
                /* 实现对应的open/read/write等函数,填入file_operations结构体                   */
                static ssize_t i2c_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
                {
                	int err;
                	unsigned char *kern_buf;
                	struct i2c_msg msgs[2];
                	/* 从0读取size字节 */
                	kern_buf = kmalloc(size, GFP_KERNEL);
                	/* 初始化i2c_msg 
                	 * 1. 发起一次写操作: 把0发给AT24C02, 表示要从0地址读数据
                	 * 2. 发起一次读操作: 得到数据
                	 */
                	msgs[0].addr  = g_client->addr;
                	msgs[0].flags = 0;  /* 写操作 */
                	msgs[0].buf   = kern_buf;
                	kern_buf[0]   = 0; /* 把数据0发给设备 */
                	msgs[0].len   = 1;
                	msgs[1].addr  = g_client->addr;
                	msgs[1].flags = I2C_M_RD;  /* 写操作 */
                	msgs[1].buf   = kern_buf;
                	msgs[1].len   = size;
                	err = i2c_transfer(g_client->adapter, msgs, 2);
                	/* copy_to_user  */
                	err = copy_to_user(buf, kern_buf, size);
                	kfree(kern_buf);
                	
                	return size;
                }
                static ssize_t i2c_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
                {
                	int err;
                	unsigned char kern_buf[9];
                	struct i2c_msg msgs[1];
                	int len;
                	unsigned char addr = 0;
                	/* 把size字节的数据写入地址0 */
                	//kern_buf = kmalloc(size+1, GFP_KERNEL);
                	while (size > 0)
                	{
                		if (size > 8)
                			len = 8;
                		else
                			len = size;
                		size -= len;
                		/* copy_from_user  */
                		err = copy_from_user(kern_buf+1, buf, len);
                		buf += len;
                		/* 初始化i2c_msg 
                		* 1. 发起一次写操作: 把0发给AT24C02, 表示要从0地址读数据
                		* 2. 发起一次读操作: 得到数据
                		*/
                		msgs[0].addr  = g_client->addr;
                		msgs[0].flags = 0;  /* 写操作 */
                		msgs[0].buf   = kern_buf;
                		kern_buf[0]   = addr;  /* 写AT24C02的地址 */
                		msgs[0].len   = len+1;
                		addr += len;
                		err = i2c_transfer(g_client->adapter, msgs, 1);
                		mdelay(20);
                	}
                	//kfree(kern_buf);
                	
                	return size;    
                }
                static unsigned int i2c_drv_poll(struct file *fp, poll_table * wait)
                {
                	//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
                	poll_wait(fp, &gpio_wait, wait);
                	//return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
                	return 0;
                }
                static int i2c_drv_fasync(int fd, struct file *file, int on)
                {
                	if (fasync_helper(fd, file, on, &i2c_fasync) >= 0)
                		return 0;
                	else
                		return -EIO;
                }
                /* 定义自己的file_operations结构体                                              */
                static struct file_operations i2c_drv_fops = {
                	.owner	 = THIS_MODULE,
                	.read    = i2c_drv_read,
                	.write   = i2c_drv_write,
                	.poll    = i2c_drv_poll,
                	.fasync  = i2c_drv_fasync,
                };
                static int i2c_drv_probe(struct i2c_client *client,
                			const struct i2c_device_id *id)
                {
                	// struct device_node *np = client->dev.of_node;
                	// struct i2c_adapter *adapter = client->adapter;
                	/* 记录client */
                	g_client = client;
                	/* 注册字符设备 */
                	/* 注册file_operations 	*/
                	major = register_chrdev(0, "100ask_i2c", &i2c_drv_fops);  /* /dev/gpio_desc */
                	my_i2c_class = class_create(THIS_MODULE, "100ask_i2c_class");
                	if (IS_ERR(my_i2c_class)) {
                		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
                		unregister_chrdev(major, "100ask_i2c");
                		return PTR_ERR(my_i2c_class);
                	}
                	device_create(my_i2c_class, NULL, MKDEV(major, 0), NULL, "myi2c"); /* /dev/myi2c */
                	
                	return 0;
                }
                static int i2c_drv_remove(struct i2c_client *client)
                {
                	/* 反注册字符设备 */
                	device_destroy(my_i2c_class, MKDEV(major, 0));
                	class_destroy(my_i2c_class);
                	unregister_chrdev(major, "100ask_i2c");
                	return 0;
                }
                static const struct of_device_id myi2c_dt_match[] = {
                	{ .compatible = "100ask,i2cdev" },
                	{},
                };
                static const struct i2c_device_id at24c02_ids[] = {
                	{ "xxxxyyy",	(kernel_ulong_t)NULL },
                	{ /* END OF LIST */ }
                };
                static struct i2c_driver my_i2c_driver = {
                	.driver = {
                		   .name = "100ask_i2c_drv",
                		   .owner = THIS_MODULE,
                		   .of_match_table = myi2c_dt_match,
                	},
                	.probe = i2c_drv_probe,
                	.remove = i2c_drv_remove,
                	.id_table = at24c02_ids,
                };
                static int __init i2c_drv_init(void)
                {
                	/* 注册i2c_driver */
                	return i2c_add_driver(&my_i2c_driver);
                }
                static void __exit i2c_drv_exit(void)
                {
                	/* 反注册i2c_driver */
                	i2c_del_driver(&my_i2c_driver);
                }
                /* 7. 其他完善:提供设备信息,自动创建设备节点                                     */
                module_init(i2c_drv_init);
                module_exit(i2c_drv_exit);
                MODULE_LICENSE("GPL");
                

                应用程序

                #include 
                #include 
                #include 
                #include 
                #include 
                #include 
                #include 
                #include 
                static int fd;
                /*
                 * ./i2c_test /dev/myi2c string 
                 * ./i2c_test /dev/myi2c
                 *
                 */
                int main(int argc, char **argv)
                {
                	int ret;
                	char buf[100];
                	
                	/* 1. 判断参数 */
                	if (argc < 2) 
                	{
                		printf("Usage:\n", argv[0]);
                		printf("      %s , read at24c02\n", argv[0]);
                		printf("      %s  , write at24c02\n", argv[0]);
                		return -1;
                	}
                	/* 2. 打开文件 */
                	fd = open(argv[1], O_RDWR | O_NONBLOCK);
                	if (fd == -1)
                	{
                		printf("can not open file %s\n", argv[1]);
                		return -1;
                	}
                	if (argc == 3)
                	{
                		ret = write(fd, argv[2], strlen(argv[2]) + 1);
                	}
                	else
                	{
                		ret = read(fd, buf, 100);
                		printf("read: %s\n", buf);
                	}
                	close(fd);
                	
                	return 0;
                }