一 环境

本来想写读E2PROM的,不过这几天做充电管理的时候,另外一颗I2C代码做好了,实在不想再去写代码,反正也只是学习,再加上精力有限,就直接用了。

I2C的基础可以看看我之前写的:总线学习1--I2C_ssd1315 修改i2c从机地址-CSDN博客

这次用的树莓派3B,I2C设备是一颗INA219,读取电压电流的一颗IC。树莓派的I2C总线,用raspi-config打开之后生成/dev/i2c-1节点,此时可以直接读取。

二 应用层读取

#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

#define I2C_DEVICE "/dev/i2c-1"
#define I2C_ADDR 0x40  // INA219的地址

int main() {
    int file;
    char buf[2];

    // 打开I2C设备
    if ((file = open(I2C_DEVICE, O_RDWR)) < 0) {
        perror("无法打开I2C设备");
        return 1;
    }

    // 设置I2C从设备地址
    if (ioctl(file, I2C_SLAVE, I2C_ADDR) < 0) {
        perror("无法设置I2C从设备地址");
        return 1;
    }

    // 写入要读取的寄存器地址(例如电压寄存器)
    buf[0] = 0x02;  // INA219电压寄存器地址
    if (write(file, buf, 1) != 1) {
        perror("无法写入寄存器地址");
        return 1;
    }

    // 读取寄存器的内容(2字节)
    if (read(file, buf, 2) != 2) {
        perror("无法读取数据");
        return 1;
    }

    // 处理读取到的电压数据
    int voltage = (buf[0] << 8) | buf[1];
    voltage >>= 3;  // 去掉3位无效位
    printf("读取到的电压: %d mV\n", voltage * 4);  // 转换为毫伏

    close(file);
    return 0;
}

运行

gcc -o i2c_read i2c_read.c
sudo ./i2c_read

结果如下:

三 内核读取

battery.dts

/dts-v1/;
/plugin/;
 
/ {
    compatible = "brcm,bcm2835";
 
    fragment@0 {
        target = <&i2c1>;  // 确认I2C接口,可能需要根据实际情况调整
        __overlay__ {
            #address-cells = <1>;
            #size-cells = <0>;
 
            ina219@40 {
                compatible = "ti,ina219";
                reg = <0x40>;  // INA219设备的I2C地址
                shunt-resistor = <10000>;  // 分流电阻的值,单位微欧姆
            };
        };
    };
};

battery.c

#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/slab.h>
 
#define INA219_ADDR 0x40 // INA219 I2C地址
#define INA219_REG_BUS_VOLTAGE 0x02 // INA219电压寄存器地址
 
static struct i2c_client *ina219_client;
 
// 读取电压函数
static int read_battery_voltage(void)
{
    int ret;
    u16 voltage_raw;
    struct i2c_msg msgs[2];
    u8 reg = INA219_REG_BUS_VOLTAGE;
    u8 buf[2];
 
    // 向INA219的电压寄存器发送读取请求
    msgs[0].addr = ina219_client->addr;
    msgs[0].flags = 0; // 写
    msgs[0].len = 1;
    msgs[0].buf = &reg;
 
    // 读取返回的数据
    msgs[1].addr = ina219_client->addr;
    msgs[1].flags = I2C_M_RD; // 读
    msgs[1].len = 2;
    msgs[1].buf = buf;
 
    ret = i2c_transfer(ina219_client->adapter, msgs, 2);
    if (ret < 0) {
        pr_err("read INA219 Voltage failed.\n");
        return ret;
    }
 
    // 计算电压值
    voltage_raw = (buf[0] << 8) | buf[1];
    voltage_raw >>= 3; // 去掉3位右移
    // 返回以毫伏为单位的电压
    return voltage_raw * 4;
}
 
struct ina219_data {
    struct i2c_client *client;
    int shunt_resistor;
};
 
static int ina219_probe(struct i2c_client *client) {
    /*struct ina219_data *data;
    printk(KERN_CRIT "ina219_probe.\n");
    data = kzalloc(sizeof(struct ina219_data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;
 
    data->client = client;
    // Assuming device property read is already correct
    if (device_property_read_u32(&client->dev, "shunt-resistor", &data->shunt_resistor)) {
        data->shunt_resistor = 10000;  // default value
    }
 
    i2c_set_clientdata(client, data);
    printk(KERN_INFO "INA219 at address %x initialized\n", client->addr); */
    ina219_client = client;
    pr_info("INA219 ina219_probe\n");
 
    // 读取并打印电压
    int voltage_mv = read_battery_voltage();
    if (voltage_mv >= 0) {
        pr_info("Bus Voltage: %d mV\n", voltage_mv);
    }
 
    return 0;
}
 
static void ina219_remove(struct i2c_client *client) {
    struct ina219_data *data = i2c_get_clientdata(client);
    kfree(data);
    printk(KERN_INFO "INA219 at address %x removed\n", client->addr);
}
 
static const struct of_device_id ina219_of_match[] = {
    { .compatible = "ti,ina219" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, ina219_of_match);
 
static const struct i2c_device_id ina219_id[] = {
    { "ina219", 0 },
    { }
};
MODULE_DEVICE_TABLE(i2c, ina219_id);
 
static struct i2c_driver ina219_driver = {
    .driver = {
        .name = "ina219",
        .of_match_table = of_match_ptr(ina219_of_match),
        .owner = THIS_MODULE,
    },
    .probe = ina219_probe,
    .remove = ina219_remove,
    .id_table = ina219_id,
};
 
module_i2c_driver(ina219_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("fanged");
MODULE_DESCRIPTION("INA219 Driver for Raspberry Pi");

Makefile

obj-m += battery.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

运行:

make
sudo dtc -I dts battery.dts -o battery.dtbo
sudo dtoverlay battery.dtbo
sudo insmod battery.ko
sudo rmmod battery.ko

就可以在内核中看到打印。

四 树莓派3B对于I2C的配置

i2c1的原始定义是在:arch\arm\boot\dts\broadcom\bcm-cygnus.dtsi

		i2c1: i2c@1800b000 {
			compatible = "brcm,cygnus-iproc-i2c", "brcm,iproc-i2c";
			reg = <0x1800b000 0x100>;
			#address-cells = <1>;
			#size-cells = <0>;
			interrupts = <GIC_SPI 86 IRQ_TYPE_LEVEL_HIGH>;
			clock-frequency = <100000>;
			status = "disabled";
		};

这部分对应的驱动在内核中drivers\i2c\busses\i2c-bcm-iproc.c

static const struct of_device_id bcm_iproc_i2c_of_match[] = {
	{
		.compatible = "brcm,iproc-i2c",
		.data = (int *)IPROC_I2C,
	}, {
		.compatible = "brcm,iproc-nic-i2c",
		.data = (int *)IPROC_I2C_NIC,
	},
	{ /* sentinel */ }
};

树莓派3B的修改定义在arch\arm\boot\dts\broadcom\bcm2835-rpi.dtsi

&i2c0 {
	pinctrl-names = "default";
	pinctrl-0 = <&i2c0_gpio0>;
	status = "okay";
	clock-frequency = <100000>;
};

&i2c1 {
	pinctrl-names = "default";
	pinctrl-0 = <&i2c1_gpio2>;
	status = "okay";
	clock-frequency = <100000>;
};

i2c0据说是内部用的,i2c1是正常一般用的。看得出来都是用pinctrl来定义的。

i2c1_gpio2定义是在arch\arm\boot\dts\broadcom\bcm283x.dtsi

			i2c1_gpio2: i2c1-gpio2 {
				brcm,pins = <2 3>;
				brcm,function = <BCM2835_FSEL_ALT0>;
			};

这里定义了引脚和寄存器。

#define BCM2835_FSEL_ALT0   4

pinctrl也是一个内核子系统,虽然写过一篇,但是对这个部分一直是太清楚。Pinctrl学习_pinctrl-single-CSDN博客

看了一下代码,这里的pinctrl,并不是pinctrl子系统。而是i2c子系统的一个属性。

想想如果没有pinctrl,i2c是怎么用?

from machine import Pin, I2C
 
# 初始化I2C接口 (GP0 - SDA, GP1 - SCL)
i2c = I2C(0, scl=Pin(17), sda=Pin(16), freq=400000)

感觉i2c里面的pinctrl就是把第四行的事给干了一次。

对内核来说,只要配置了target = <&i2c1>,找着static int ina219_probe(struct i2c_client *client)直接怼就行了。

对了应用就更简单,直接用/dev/i2c-1完事。

所以,目前我对pinctrl的理解就是初始系统的时候,自动配置好i2c,spi这些内容,而且方便后面使用。

五 内核中的I2C子模块

内核中I2C的代码位于drivers\i2c,结果如下:

瞅了一下这部分很大,还是后面再专门写一下吧。。。

六 参考

https://www.kernel.org/doc/Documentation/i2c/writing-clients

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐