目录
一、 IIC基础知识
二、Linux中的IIC(韦东山老师的学习笔记)
1. I2C驱动程序的层次
2.1 i2c_driver
2.2 i2c_client
三、 AT24C02 介绍
四、 AT24C02驱动开发
实验
驱动程序
应用程序
总线类设备驱动——IIC_iic设备驱动-CSDN博客
Exynos_4412——IIC总线概述_.若使用iic总线让从机给主机发送一个字节的数据0xa2,画出scl和sda上的时序图-CSDN博客
STM32——IIC总线(MPU6050应用)_mpu6050例程-CSDN博客
写过好多次啦不打算重复写了。
参考资料:
Linux内核文档:
Documentation\i2c\instantiating-devices.rst
Documentation\i2c\writing-clients.rst
Linux内核驱动程序示例:
drivers/eeprom/at24.c
I2C Core就是I2C核心层,它的作用:
提供统一的访问函数,比如i2c_transfer、i2c_smbus_xfer等
实现I2C总线-设备-驱动模型,管理:I2C设备(i2c_client)、I2C设备驱动(i2c_driver)、I2C控制器(i2c_adapter)
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函数。
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 是基于 I2C 总线的存储器件,由于接口方便,体积小,数据掉电不丢失等特点,在仪器仪表及工业自动化控制中得到大量的应用。百问网提供的 EEPROM 模块使用的就是 AT24C02,使用 8 位地址,存储容量为 2K bit,即 2048bit = 256*8bit = 256 Byte, 其中它被分为 32 页,每页 8Byte。
基于 I2C 接口的设备操作,我们主要关心的是 I2C 数据格式。 AT24C02 作为 EEPROM 存储设备,显然我们关心的是:怎么读写某个地址,即怎么发地址、怎么读写数据。 查阅《AT24CXX.pdf》手册,要写入一个字节,可如下操作:先发出设备地址, 再发出 WORD 地址(即存储地址),再发出数据。
反之,要读 AT24C02,如下图:涉及 2 次 I2C 传输。 先发出设备地址, WORD地址;再次发出设备地址,读到数据。
先添加设备树
这是我们的设备树目录
可以用find找到我们刚添加的设备目录
咱们用的这个小工具有个配置项可以设置一下很方便
reg这个可能是特殊的加密文件需要用hexdump去看
虽然我们是iic-1但是内核是从0开始的所以这个就是我们的设备目录
插入模块后我们的驱动就出来了,没写的时候读出来是一堆乱码
验证一下它的非易失性
没毛病
#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; }
上一篇:HiveSQL基础Day03