i2c接口常见于各种慢速ic,特别是传感器。本文以I2C的CO2 sensor为例,
如上可以看到该i2c设备的从机地址,最大通信速率以及通信时序。因只要读取co2值,因此只需关注读的时许即可。
i2c设备接到实际硬件上后可以通过i2ctool进行侦测硬件接线是否有误。
root@TinaLinux:/# i2cdetect -y -r 1 //查询接到i2c总线1的从设备,如下可以看到接了一个从机地址为0x41的设备,即准备驱动co2设备
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- 41 -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
root@TinaLinux:/# i2cdump -f -y 1 0x41 //把co2 设备的寄存器内容都dump出来
0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
00: 01 78 04 3f 10 ff 2f 3f 36 3f 01 3f 01 3f 00 3f ?x???./?6?????.?
10: 00 3f 01 3f 00 3f 00 3f 00 3f 13 3f 00 3f 20 3f .???.?.?.???.? ?
20: 78 3f 78 3f 78 3f 00 3f 3f 3f 10 3f 00 3f 00 3f x?x?x?.?????.?.?
30: 7f 3f 7b 3f 04 3f 28 3f 28 3f 68 3f 68 3f 7b 3f ??{???(?(?h?h?{?
40: 7b 3f 7f 3f 7c 3f 7b 3f 06 3f 00 3f 00 3f 00 3f {???|?{???.?.?.?
50: 55 3f 01 3f 3f 3f 04 3f 3f 3f 00 3f 70 3f 3f 3f U?????????.?p???
60: 00 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 00 3f .?????????????.?
70: 3f 3f b2 3f 00 3f 03 3f 00 3f 00 3f 00 3f 7b 3f ????.???.?.?.?{?
80: 7b 3f 04 3f 3f 3f 00 3f 00 3f 00 3f 3f 3f 3f 3f {?????.?.?.?????
90: 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f ????????????????
a0: 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f ????????????????
b0: 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f ????????????????
c0: 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f ????????????????
d0: 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f ????????????????
e0: 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f ????????????????
f0: 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f ????????????????
root@TinaLinux:/# i2cget -f -y 1 0x41 0x00 //读取寄存器00的值
0x01
root@TinaLinux:/# i2cget -f -y 1 0x41 0x02 //读取寄存器02的值
0x04
root@TinaLinux:/#
通过i2ctool确认可以识别到硬件上没问题后既可以开始进行驱动开发。
1、用户态驱动
即在用户层直接驱动iic从机,不需要涉及到编写内核驱动,好处是不用涉及到编译内核,只需要用户态驱动
//直接在用户态上通过read write 标准接口去读取
#include <sys/ioctl.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <stdio.h>
#define CHIP "/dev/i2c-1"
#define CHIP_ADDR 0x41
int main()
{
unsigned char rddata[4];
unsigned char rdnofilterregaddr = 0x02;
unsigned char rdfilterregaddr = 0x34;
int co2ppm;
printf("hello, this is i2c tester\n");
int fd = open(CHIP, O_RDWR);
if (fd < 0)
{
printf("open "CHIP"failed\n");
goto exit;
}
if (ioctl(fd, I2C_SLAVE_FORCE, CHIP_ADDR) < 0)
{
/* 璁剧疆鑺墖鍦板潃 */
printf("oictl:set slave address failed\n");
goto closelabel;
}
while(1)
{
printf("write nofilteraddress return: %d\n",write(fd, &rdnofilterregaddr, 1));
if(read(fd, &rddata, 3))
{
printf("rawdata:%x %x %x\r\n",rddata[0],rddata[1],rddata[2]);
co2ppm = rddata[0]*256 + rddata[1];
printf("co2ppm:%d\n",co2ppm);
}
else
{
printf("noneread\n");
}
sleep(3);
printf("write filteraddress return: %d\n",write(fd, &rdfilterregaddr, 1));
if(read(fd, &rddata, 2))
{
printf("rawdata:%x %x\r\n",rddata[0],rddata[1],rddata[2]);
co2ppm = rddata[0]*256 + rddata[1];
printf("co2ppmfilter:%d\n",co2ppm);
}
else
{
printf("noneread\n");
}
sleep(3);
}
closelabel:
close(fd);
exit:
return 0;
}
//编译用户态程序
arm-openwrt-linux-gcc i2cuser.c
//拷贝到板子上运行
root@TinaLinux:/# ./a.out
hello, this is i2c tester
write nofilteraddress return: 1
rawdata:3 a8 55
co2ppm:936
write filteraddress return: 1
rawdata:3 78
co2ppmfilter:888
write nofilteraddress return: 1
rawdata:3 9e 55
co2ppm:926
write filteraddress return: 1
rawdata:a 22
co2ppmfilter:2594
write nofilteraddress return: 1
rawdata:5 d1 55
co2ppm:1489
write filteraddress return: 1
rawdata:8 6
co2ppmfilter:2054
2、内核态驱动
编写设备树,依据平台设备驱动模型去编写sensor driver驱动。
设备树
&twi1 {
clock-frequency = <100000>;//按co2 sensor支持的最大100khz填写
pinctrl-0 = <&twi1_pins_a>;
pinctrl-1 = <&twi1_pins_b>;
pinctrl-names = "default", "sleep";
status = "okay";
co2lp3@41{ //co2子设备定义
compatible = "co2lp3";//用于drive probe
reg = <0x41>;//设备地址
};
};
驱动代码
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/mod_devicetable.h>
#include <linux/bitops.h>
#include <linux/jiffies.h>
#include <linux/property.h>
#include <linux/acpi.h>
#include <linux/i2c.h>
#include <linux/nvmem-provider.h>
#include <linux/regmap.h>
#include <linux/pm_runtime.h>
#include <linux/gpio/consumer.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
static int major = 0;
static struct class *lp3co2_class;
static struct i2c_client *lp3co2_client;
static int lp3co2_read_regs( void *val, int len)
{
int ret;
struct i2c_msg msg;
/* msg 为读取数据 */
msg.addr = lp3co2_client->addr;
msg.flags = I2C_M_RD;
msg.buf = val;
msg.len = len;
ret = i2c_transfer(lp3co2_client->adapter, &msg, 1);
if(ret == 1){
ret = 0;
}else{
printk("i2c rd failed=%d len=%d \n",ret, len);
ret = -EREMOTEIO;
}
return ret;
}
static unsigned int lp3co2_write_regs( unsigned char reg, unsigned char*buf, unsigned char len)
{
unsigned char *b;
unsigned char i;
struct i2c_msg msg;
b = kzalloc(len+1,GFP_KERNEL);
b[0] = reg;
msg.addr = lp3co2_client->addr;//i;
msg.flags = 0;//write
msg.buf = b;
msg.len = len+1;
return i2c_transfer(lp3co2_client->adapter, &msg, 1);
}
static ssize_t lp3co2_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
int err;
char kernel_buf[4];
lp3co2_write_regs(0x02,NULL,0);
lp3co2_read_regs(kernel_buf,2);
lp3co2_write_regs(0x34,NULL,0);
lp3co2_read_regs(&kernel_buf[2],2);
err = copy_to_user(buf, kernel_buf, size);
return size;
}
static int lp3co2_open (struct inode *node, struct file *file)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static struct file_operations lp3co2_ops = {
.owner = THIS_MODULE,
.open = lp3co2_open,
.read = lp3co2_read,
};
static const struct of_device_id of_match_ids_lp3co2[] = {
{ .compatible = "co2lp3", .data = NULL },
{ /* END OF LIST */ },
};
static const struct i2c_device_id lp3co2_ids[] = {
{ "lp3co2", (kernel_ulong_t)NULL },
{ /* END OF LIST */ }
};
static int lp3co2_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
lp3co2_client = client;
/* register_chrdev */
major = register_chrdev(0, "lp3co2", &lp3co2_ops);
lp3co2_class = class_create(THIS_MODULE, "lp3co2_class");
device_create(lp3co2_class, NULL, MKDEV(major, 0), NULL, "lp3co2"); /* /dev/lp3co2 */
return 0;
}
static int lp3co2_remove(struct i2c_client *client)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
device_destroy(lp3co2_class, MKDEV(major, 0));
class_destroy(lp3co2_class);
/* unregister_chrdev */
unregister_chrdev(major, "lp3co2");
return 0;
}
static struct i2c_driver i2c_lp3co2_driver = {
.driver = {
.name = "co2lp3",
.of_match_table = of_match_ids_lp3co2,
},
.probe = lp3co2_probe,
.remove = lp3co2_remove,
.id_table = lp3co2_ids,
};
static int __init i2c_driver_lp3co2_init(void)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
return i2c_add_driver(&i2c_lp3co2_driver);
}
module_init(i2c_driver_lp3co2_init);
static void __exit i2c_driver_lp3co2_exit(void)
{
i2c_del_driver(&i2c_lp3co2_driver);
}
module_exit(i2c_driver_lp3co2_exit);
MODULE_AUTHOR("www.100ask.net");
MODULE_LICENSE("GPL");
应用层测试代码
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
int main(int argc, char **argv)
{
int fd,ret;
unsigned char databuf[4] = {0};
int co2ppm;
fd = open("/dev/lp3co2", O_RDWR);
if (fd < 0)
{
perror("open device lp3co2 error");
}
while (1)
{
ret = read(fd, databuf, 4);
printf("rawdata:%x %x %x %x\r\n",databuf[0],databuf[1],databuf[2],databuf[3]);
co2ppm = databuf[0]*256 + databuf[1];
printf("co2ppm:%d\n",co2ppm);
co2ppm = databuf[2]*256 + databuf[3];
printf("co2filterppm:%d\n",co2ppm);
sleep(3);
}
return 0;
}
linux i2c既可以通过i2c硬件总线驱动,也可以通过模拟io驱动,模拟io驱动linux内核也有做好了对应的驱动,只要设备树按要求的定义后就可以直接使用。不需要自己像mcu级别额外去控制模拟scl和sda时序。
gpio模拟iic驱动需要内核开启相应配置
同时添加对应的设备树
i2cgpio{
compatible = "i2c-gpio";//必须为该名称才可以让内核的驱动识别到
gpios = < &pio PB 6 0 /*sda*/
&pio PB 7 0 /*scl*/
>;
i2c-gpio,delay-us = <30>;
#address-cells = <1>;
#size-cells = <0>;
co2lp3@41{
compatible = "co2lp3";
reg = <0x41>;
};
};
root@TinaLinux:/# dmesg | grep i2c
[ 2.361954] i2c /dev entries driver
[ 3.059727] sunxi-i2c sunxi-i2c1: sunxi-i2c1 supply twi not found, using dummy regulator
[ 3.069550] sunxi-i2c sunxi-i2c1: probe success
[ 3.075112] sunxi-i2c sunxi-i2c2: sunxi-i2c2 supply twi not found, using dummy regulator
[ 3.084907] sunxi-i2c sunxi-i2c2: probe success
[ 3.091982] gpio-38 (i2cgpio): enforced open drain please flag it properly in DT/ACPI DSDT/board file
[ 3.102457] gpio-39 (i2cgpio): enforced open drain please flag it properly in DT/ACPI DSDT/board file
[ 3.113282] i2c-gpio i2cgpio: using lines 38 (SDA) and 39 (SCL)
i2c-1 i2c sunxi-i2c1 I2C adapter
i2c-2 i2c sunxi-i2c2 I2C adapter
i2c-0 i2c i2cgpio I2C adapter
//i2c-0即为模拟iic的adapter
root@TinaLinux:/# i2cdump -y -f 0
root@TinaLinux:/# i2cdetect -y -r 0
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- 41 -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
root@TinaLinux:/# i2cdump -y -f 0x41
root@TinaLinux:/# i2cdump -y -f 0 0x41
0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
00: 01 78 04 3f 10 ff 2f 3f 36 3f 01 3f 01 3f 00 3f ?x???./?6?????.?
10: 00 3f 01 3f 00 3f 00 3f 00 3f 13 3f 00 3f 20 3f .???.?.?.???.? ?
20: 78 3f 78 3f 78 3f 00 3f 3f 3f 10 3f 00 3f 00 3f x?x?x?.?????.?.?
30: 7f 3f 7a 3f 05 3f 28 3f 28 3f 67 3f 67 3f 7a 3f ??z???(?(?g?g?z?
40: 7a 3f 7f 3f 7b 3f 7a 3f 06 3f 00 3f 00 3f 00 3f z???{?z???.?.?.?
50: 55 3f 01 3f 3f 3f 04 3f 3f 3f 00 3f 70 3f 3f 3f U?????????.?p???
60: 00 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 00 3f .?????????????.?
70: 3f 3f b2 3f 00 3f 03 3f 00 3f 00 3f 00 3f 7b 3f ????.???.?.?.?{?
80: 7b 3f 05 3f 3f 3f 00 3f 00 3f 00 3f 3f 3f 3f 3f {?????.?.?.?????
90: 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f ????????????????
a0: 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f ????????????????
b0: 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f ????????????????
c0: 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f ????????????????
d0: 3f 3f 3f 3f 3f 3f 3f 3f XX 3f 3f 3f 3f 3f 3f 3f ????????X???????
e0: 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f ????????????????
f0: 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f ????????????????
root@TinaLinux:/#
i2c更换成模拟io后内核会生成一个新的adapter,用户态驱动只需打开的设备文件名改为生成的i2c-0后就可以直接读取,
root@TinaLinux:/# ./a.out
hello, this is i2c tester
write nofilteraddress return: 1
rawdata:4 6a 55
co2ppm:1130
write filteraddress return: 1
rawdata:4 8e
co2ppmfilter:1166
write nofilteraddress return: 1
rawdata:4 62 55
co2ppm:1122