Linux I2C 设备驱动开发实践
1. I2C 基础知识
I2C 总线是一种两线式串行总线,用于连接微控制器及其外围设备,多用于主控制器和从器件间的主从通信,在小数据量场合使用,传输距离短。每个设备都有自己的唯一地址,且I2C为半双工,任意时刻只能有一个主机并进行单行通信。IIC一共有只有两个总线: 一条是双向的串行数据线 SDA,一条是串行时钟线 SCL 。
I2C 总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。IIC 总线时序如下图:
-
开始信号:SCL 为高电平时,SDA 由高电平向低电平跳变,开始传送数据。
-
结束信号:SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据。
-
应答信号:接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,表示已收到数据。CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU 接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。
2. Linux I2C 体系结构
Linux 中 I2C 体系结构上图所示,共分成了三个层次:
-
用户空间,包括所有使用I2C 设备的应用程序;
-
内核,也就是驱动部分;
-
硬件,指实际物理设备,包括了 I2C 控制器和 I2C 外设。
其中,Linux 内核中的 I2C 驱动程序从逻辑上主要实现:
-
I2C framework 提供一种 “访问 I2C slave devices” 的方法。由于这些 slave devices 由 I2C controller 控制,因而主要由 I2C controller 驱动实现这一目标。
-
在 I2C framework 内部,有 I2C core、I2C busses、I2C algos 和 I2C muxes 四个模块。
-
I2C core 使用 I2C adapter 和 I2C algorithm 两个子模块抽象 I2C controller 的功能。
-
I2C busses 是各个 I2C controller drivers 的集合,位于 drivers/i2c/busses/目录下, i2c-k1.c。
-
I2C algos 包含了一些通用的 I2C algorithm,所谓的 algorithm,是指 I2C 协议的通信方法,用于实现 I2C 的 read/write 指令。
-
3. Linux I2C 驱动架构概述
Linux I2C 驱动分为两大类:
I2C 总线驱动(适配器驱动)
-
负责 SoC 芯片上的 I2C 控制器(适配器)的初始化和管理
-
实现 I2C 总线的底层读写、时序控制等
-
常见驱动如
i2c-designware、i2c-imx等
I2C 设备驱动
-
负责挂载在 I2C 总线上的具体外设(如 EEPROM、传感器、触摸屏等)
-
通过 I2C 总线驱动提供的接口与硬件通信
I2C 总线驱动和设备驱动通过内核 I2C 框架解耦,便于不同硬件平台和多种 I2C 设备的灵活组合。
4. 关键特性
-
支持9组I2C接口
-
主机模式下支持DMA数据传输
-
支持100k / 400k/ 1.5M三种速度模式
-
多主机模式下支持总线仲裁
5. I2C 总线驱动开发
5.1 核心数据结构
5.1.1. i2c_adapter 结构体
i2c_adapter 抽象表示一个 I2C 控制器,定义在 path/to/your/linux-6.6/include/linux/i2c.h 中:
struct i2c_adapter {
struct module *owner;
unsigned int class; /* 设备类别,允许探测的类型 */
const struct i2c_algorithm *algo; /* 总线访问算法 */
void *algo_data;
/* 所有设备共用的数据字段 */
struct rt_mutex bus_lock;
int timeout; /* 超时时间,单位为jiffies */
int retries; /* 重试次数 */
struct device dev; /* 适配器设备 */
int nr; /* 总线编号 */
char name[48]; /* 适配器名称 */
// ...其他字段
};
5.1.2. i2c_algorithm 结构体
i2c_algorithm 定义了 I2C 总线的数据传输方法,也在 path/to/your/linux-6.6/include/linux/i2c.h 中:
struct i2c_algorithm {
/* 标准I2C传输函数 */
int (*master_xfer)(struct i2c_adapter *adap,
struct i2c_msg *msgs,
int num);
/* SMBus传输函数 */
int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
/* 确定适配器支持的功能 */
u32 (*functionality)(struct i2c_adapter *adap);
// ...其他函数
};
其中 master_xfer 是最核心的函数,实现与 I2C 设备的通信。
5.2 注册与注销 I2C 适配器
/* 注册I2C适配器 */
int i2c_add_adapter(struct i2c_adapter *adapter); // 使用动态总线号
int i2c_add_numbered_adapter(struct i2c_adapter *adap); // 使用静态总线号
/* 注销I2C适配器 */
void i2c_del_adapter(struct i2c_adapter *adap);
5.3 I2C 控制器的设备树表示
在 path/to/your/linux-6.6/arch/riscv/boot/dts/spacemit/k1-x.dtsi 中定义了9个 I2C 控制器,分别从 i2c0 ~ i2c8,此外还有一个ri2c0,它是 RCPU 的 I2C 控制器。以 i2c0 为例:
i2c0: i2c@d4010800 {
compatible = "spacemit,k1x-i2c";
spacemit,adapter-id = <0>;
reg = <0x0 0xd4010800 0x0 0x38>;
#address-cells = <1>;
#size-cells = <0>;
interrupt-parent = <&intc>;
interrupts = <36>;
clocks = <&ccu CLK_TWSI0>;
resets = <&reset RESET_TWSI0>;
spacemit,dma-disable;
spacemit,i2c-master-code = /bits/ 8 <0x0e>;
spacemit,i2c-clk-rate = <32000000>;
spacemit,i2c-lcr = <0x82c469f>;
spacemit,i2c-wcr = <0x142a>;
/* apb clock: 26MHz or 52MHz */
spacemit,apb_clock = <52000000>;
power-domains = <&power K1X_PMU_BUS_PWR_DOMAIN>;
cpuidle,pm-runtime,sleep;
interconnects = <&dram_range4>;
interconnect-names = "dma-mem";
status = "disabled";
};
通过 compatible 属性值 spacemit,k1x-i2c 可以在Linux 源码中搜索这两个字符串即可找到对应的驱动文件。MUSE Pi Pro 的 I2C 适配器驱动文件为 path/to/your/linux-6.6/drivers/i2c/busses/i2c-k1x.c,在文件中有如下内容:
static const struct of_device_id spacemit_i2c_dt_match[] = {
{
.compatible = "spacemit,k1x-i2c",
},
{}
};
MODULE_DEVICE_TABLE(of, spacemit_i2c_dt_match);
static struct platform_driver spacemit_i2c_driver = {
.probe = spacemit_i2c_probe,
.remove = spacemit_i2c_remove,
.shutdown = spacemit_i2c_shutdown,
.driver = {
.name = "i2c-spacemit-k1x",
/* .pm = &spacemit_i2c_pm_ops, */
.of_match_table = spacemit_i2c_dt_match,
},
};
static int __init spacemit_i2c_init(void)
{
register_restart_handler(&spacemit_i2c_sys_nb);
i2c_poweroff_handler = register_sys_off_handler(SYS_OFF_MODE_POWER_OFF,
SYS_OFF_PRIO_HIGH,
spacemit_i2c_notifier_poweroff_call,
NULL);
return platform_driver_register(&spacemit_i2c_driver);
}
static void __exit spacemit_i2c_exit(void)
{
platform_driver_unregister(&spacemit_i2c_driver);
unregister_restart_handler(&spacemit_i2c_sys_nb);
unregister_sys_off_handler(i2c_poweroff_handler);
}
subsys_initcall(spacemit_i2c_init);
module_exit(spacemit_i2c_exit);
从上述代码可以看出,MUSE Pi Pro 的 I2C 适配器驱动是个标准的 platform 驱动,也就是说,虽然 I2C 总线为别的设备提供了一种总线驱动框架,但是 I2C 适配器却是 platform 驱动。
在设备和驱动匹配成功以后 spacemit_i2c_probe 函数就会执行,spacemit_i2c_probe 函数就会完成 I2C 适配器初始化工作。
spacemit_i2c_probe 函数内容如下 (有省略):
static int spacemit_i2c_probe(struct platform_device *pdev)
{
struct spacemit_i2c_dev *spacemit_i2c;
struct device_node *dnode = pdev->dev.of_node;
int ret = 0;
// 1. 申请内存并初始化私有数据结构
spacemit_i2c = devm_kzalloc(&pdev->dev, sizeof(struct spacemit_i2c_dev), GFP_KERNEL);
if (!spacemit_i2c)
return -ENOMEM;
spacemit_i2c->dev = &pdev->dev;
platform_set_drvdata(pdev, spacemit_i2c);
mutex_init(&spacemit_i2c->mtx);
// 2. 复位控制器(可选,部分平台需要)
spacemit_i2c->resets = devm_reset_control_get_optional(&pdev->dev, NULL);
reset_control_assert(spacemit_i2c->resets);
udelay(200);
reset_control_deassert(spacemit_i2c->resets);
// 3. 解析设备树参数(如地址、中断、DMA等)
ret = spacemit_i2c_parse_dt(pdev, spacemit_i2c);
// 4. 获取寄存器资源并映射
ret = of_address_to_resource(dnode, 0, &spacemit_i2c->resrc);
spacemit_i2c->mapbase = devm_ioremap_resource(spacemit_i2c->dev, &spacemit_i2c->resrc);
// 5. 获取中断号并注册中断处理函数
spacemit_i2c->irq = platform_get_irq(pdev, 0);
ret = devm_request_irq(spacemit_i2c->dev, spacemit_i2c->irq, spacemit_i2c_int_handler,
IRQF_NO_SUSPEND, dev_name(spacemit_i2c->dev), spacemit_i2c);
// 6. 获取并使能时钟
spacemit_i2c->clk = devm_clk_get(spacemit_i2c->dev, NULL);
clk_prepare_enable(spacemit_i2c->clk);
// 7. 初始化 i2c_adapter 结构体
i2c_set_adapdata(&spacemit_i2c->adapt, spacemit_i2c);
spacemit_i2c->adapt.owner = THIS_MODULE;
spacemit_i2c->adapt.algo = &spacemit_i2c_algrtm; // 指定传输算法
spacemit_i2c->adapt.dev.parent = spacemit_i2c->dev;
spacemit_i2c->adapt.nr = pdev->id;
spacemit_i2c->adapt.dev.of_node = dnode;
strlcpy(spacemit_i2c->adapt.name, "spacemit-i2c-adapter", sizeof(spacemit_i2c->adapt.name));
// 8. 注册 i2c_adapter 到内核
ret = i2c_add_numbered_adapter(&spacemit_i2c->adapt);
}
5.4. I2C 传输函数分析
在 probe 函数中初始化 i2c_adapter,设置 i2c_algorithm 为 spacemit_i2c_algrtm,最后向 Linux 内核注册 i2c_adapter。spacemit_i2c_algrtm 包含 I2C 适配器与 I2C 设备的通信函数 master_xfer,spacemit_i2c_algrtm 结构体定义如下:
static const struct i2c_algorithm spacemit_i2c_algrtm = {
.master_xfer = spacemit_i2c_xfer,
.functionality = spacemit_i2c_func,
};
重点来看 spacemit_i2c_xfer 函数,因为最终就是通过此函数来完成与 I2C 设备通信的。内容如下 (有省略):
static int spacemit_i2c_xfer(struct i2c_adapter *adapt, struct i2c_msg msgs[], int num)
{
struct spacemit_i2c_dev *spacemit_i2c = i2c_get_adapdata(adapt);
int ret = 0, xfer_try = 0;
bool clk_directly = false;
// 1. 上锁,准备传输参数
mutex_lock(&spacemit_i2c->mtx);
spacemit_i2c->msgs = msgs;
spacemit_i2c->num = num;
// 2. 电源管理:确保时钟可用
if (!spacemit_i2c->clk_always_on) {
ret = pm_runtime_get_sync(spacemit_i2c->dev);
if (ret < 0) {
// 若PM不可用,直接使能时钟
if (ret == -EACCES) {
clk_directly = true;
clk_enable(spacemit_i2c->clk);
} else {
goto err_runtime;
}
}
}
xfer_retry:
// 3. 控制器状态检查与初始化
if (spacemit_i2c_read_reg(spacemit_i2c, REG_CR) != spacemit_i2c->i2c_ctrl_reg_value)
spacemit_i2c_reset(spacemit_i2c);
spacemit_i2c_choose_xfer_mode(spacemit_i2c); // 选择传输模式
spacemit_i2c_unit_init(spacemit_i2c); // 控制器初始化
spacemit_i2c_clear_int_status(spacemit_i2c, SPACEMIT_I2C_INT_STATUS_MASK); // 清中断
spacemit_i2c_init_xfer_params(spacemit_i2c); // 初始化传输参数
spacemit_i2c_mark_rw_flag(spacemit_i2c); // 标记读写方向
reinit_completion(&spacemit_i2c->complete); // 重新初始化完成量
spacemit_i2c_enable(spacemit_i2c); // 使能I2C控制器
// 4. 检查总线状态
ret = spacemit_i2c_recover_bus_busy(spacemit_i2c);
if (ret)
goto timeout_xfex;
// 5. 数据传输(根据模式选择)
if (spacemit_i2c->xfer_mode == SPACEMIT_I2C_MODE_INTERRUPT)
ret = spacemit_i2c_byte_xfer(spacemit_i2c);
else if (spacemit_i2c->xfer_mode == SPACEMIT_I2C_MODE_FIFO)
ret = spacemit_i2c_fifo_xfer(spacemit_i2c);
else
ret = spacemit_i2c_dma_xfer(spacemit_i2c);
// 6. 等待传输完成(中断模式下)
if (spacemit_i2c->xfer_mode == SPACEMIT_I2C_MODE_INTERRUPT) {
unsigned long time_left = wait_for_completion_timeout(&spacemit_i2c->complete, spacemit_i2c->timeout);
if (time_left == 0) {
spacemit_i2c_bus_reset(spacemit_i2c);
spacemit_i2c_reset(spacemit_i2c);
ret = -ETIMEDOUT;
goto timeout_xfex;
}
}
timeout_xfex:
// 7. 关闭I2C控制器
spacemit_i2c_disable(spacemit_i2c);
// 8. 错误处理与重试
if (ret < 0 && (ret == -ETIMEDOUT || ret == -EAGAIN) && ++xfer_try <= spacemit_i2c->drv_retries) {
usleep_range(150, 200);
ret = 0;
goto xfer_retry;
}
err_runtime:
// 9. 时钟与电源管理收尾
if (clk_directly)
clk_disable(spacemit_i2c->clk);
if (!spacemit_i2c->clk_always_on) {
pm_runtime_mark_last_busy(spacemit_i2c->dev);
pm_runtime_put_autosuspend(spacemit_i2c->dev);
}
mutex_unlock(&spacemit_i2c->mtx);
// 返回成功传输的消息数或错误码
return ret < 0 ? ret : num;
}
6. I2C 设备驱动开发
6.1 核心数据结构
6.1.1. i2c_client 结构体
i2c_client 结构体描述 I2C 设备的信息,每检测到一个 I2C 设备就会为其分配一个 i2c_client 实例。定义在 path/to/your/linux-6.6/include/linux/i2c.h 中:
struct i2c_client {
unsigned short flags; /* 设备标志 */
unsigned short addr; /* 芯片地址,7位,存储在低7位 */
char name[I2C_NAME_SIZE]; /* 设备名称 */
struct i2c_adapter *adapter; /* 设备连接的I2C适配器 */
struct device dev; /* 设备结构体 */
int irq; /* 中断号 */
struct list_head detected;
// ...其他字段
};
6.1.2. i2c_driver 结构体
i2c_driver 结构体描述 I2C 设备驱动的信息,类似于platform_driver,定义在 path/to/your/linux-6.6/include/linux/i2c.h 中:
struct i2c_driver {
unsigned int class;
/* 标准驱动模型接口 */
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
/* 其他回调函数 */
void (*shutdown)(struct i2c_client *);
void (*alert)(struct i2c_client *, unsigned int data);
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver; /* 设备驱动结构体 */
const struct i2c_device_id *id_table; /* 传统匹配ID表 */
// ...其他字段
};
6.2 I2C 设备驱动注册与注销
对于I2C 设备驱动编写,重点工作就是构建 i2c_driver,构建完之后需要向内核注册这个 i2c_driver,注册 I2C 设备驱动使用以下函数:
/* 注册I2C驱动 */
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
/* 简化版注册宏 */
#define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver)
owner:一般为 THIS_MODULE。
driver:要注册的 i2c_driver。
/* 注销I2C驱动 */
void i2c_del_driver(struct i2c_driver *driver);
6.3 I2C 设备驱动开发流程
此处结合 GY6500 六轴传感器驱动程序进行说明。
6.3.1. 读写函数
// 读函数
static int gy6500_read_regs(struct gy6500_dev *dev, u8 reg, void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)dev->private_data;
/* msg[0]为发送要读取的首地址 */
msg[0].addr = client->addr; /* gy6500地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = ® /* 读取的首地址 */
msg[0].len = 1; /* reg长度*/
/* msg[1]读取数据 */
msg[1].addr = client->addr; /* gy6500地址 */
msg[1].flags = I2C_M_RD; /* 标记为读取数据*/
msg[1].buf = val; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度*/
ret = i2c_transfer(client->adapter, msg, 2);
if(ret == 2) {
ret = 0;
} else {
printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
ret = -EREMOTEIO;
}
return ret;
}
// 写函数
static s32 gy6500_write_regs(struct gy6500_dev *dev, u8 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->private_data;
b[0] = reg; /* 寄存器首地址 */
memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组b里面 */
msg.addr = client->addr; /* gy6500地址 */
msg.flags = 0; /* 标记为写数据 */
msg.buf = b; /* 要写入的数据缓冲区 */
msg.len = len + 1; /* 要写入的数据长度 */
return i2c_transfer(client->adapter, &msg, 1);
}
6.3.2. 打开设备函数
static int gy6500_open(struct inode *inode, struct file *filp)
{
filp->private_data = &gy6500dev;
/* 初始化GY6500 */
gy6500_write_reg(&gy6500dev, MPU_PWR_MGMT1_REG, 0x80); /* 复位GY6500 */
mdelay(100); /* 等待复位完成 */
gy6500_write_reg(&gy6500dev, MPU_PWR_MGMT1_REG, 0x00); /* 唤醒GY6500 */
/* 设置陀螺仪和加速度范围 */
gy6500_write_reg(&gy6500dev, MPU_GYRO_CFG_REG, 0x18); /* 陀螺仪量程:±2000dps */
gy6500_write_reg(&gy6500dev, MPU_ACCEL_CFG_REG, 0x18); /* 加速度量程:±16g */
/* 设置采样率 */
gy6500_write_reg(&gy6500dev, MPU_SAMPLE_RATE_REG, 0x07); /* 采样率:1000/(1+7)=125Hz */
/* 配置低通滤波器 */
gy6500_write_reg(&gy6500dev, MPU_CFG_REG, 0x06); /* 低通滤波器:5Hz */
return 0;
}
6.3.3. 定义匹配表
/* 传统匹配方式ID列表 */
static const struct i2c_device_id gy6500_id[] = {
{"bianbusense,gy6500", 0},
{}
};
/* 设备树匹配列表 */
static const struct of_device_id gy6500_of_match[] = {
{ .compatible = "bianbusense,gy6500" },
{ /* Sentinel */ }
};
6.3.4. 实现 probe/remove 等回调函数
static int gy6500_probe(struct i2c_client *client)
{
/* 1、构建设备号 */
if (gy6500dev.major) {
gy6500dev.devid = MKDEV(gy6500dev.major, 0);
register_chrdev_region(gy6500dev.devid, GY6500_CNT, GY6500_NAME);
} else {
alloc_chrdev_region(&gy6500dev.devid, 0, GY6500_CNT, GY6500_NAME);
gy6500dev.major = MAJOR(gy6500dev.devid);
}
/* 2、注册设备 */
cdev_init(&gy6500dev.cdev, &gy6500_ops);
cdev_add(&gy6500dev.cdev, gy6500dev.devid, GY6500_CNT);
/* 3、创建类 */
gy6500dev.class = class_create(GY6500_NAME);
if (IS_ERR(gy6500dev.class)) {
return PTR_ERR(gy6500dev.class);
}
/* 4、创建设备 */
gy6500dev.device = device_create(gy6500dev.class, NULL, gy6500dev.devid, NULL, GY6500_NAME);
if (IS_ERR(gy6500dev.device)) {
return PTR_ERR(gy6500dev.device);
}
/* 5、保存I2C客户端数据 */
gy6500dev.private_data = client;
return 0;
}
static void gy6500_remove(struct i2c_client *client)
{
/* 删除设备 */
cdev_del(&gy6500dev.cdev);
unregister_chrdev_region(gy6500dev.devid, GY6500_CNT);
/* 注销掉类和设备 */
device_destroy(gy6500dev.class, gy6500dev.devid);
class_destroy(gy6500dev.class);
}
6.3.5. 构建 i2c_driver 结构体
/* i2c驱动结构体 */
static struct i2c_driver gy6500_driver = {
.probe = gy6500_probe,
.remove = gy6500_remove,
.driver = {
.owner = THIS_MODULE,
.name = "gy6500",
.of_match_table = gy6500_of_match,
},
.id_table = gy6500_id,
};
7. I2C 通信常用 API
7.1 I2C 设备通信函数
probe 函数里面初始化 I2C 设备,要初始化 I2C 设备就必须能够对 I2C 设备寄存器进行读写操作,这里就要用到 i2c_transfer 函数了。i2c_transfer 函数最终会调用 I2C 适配器中 i2c_algorithm 里面的 master_xfer 函数
/* 原始传输 */
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
函数参数和返回值含义如下:
adap:所使用的 I2C 适配器,i2c_client 会保存其对应的 i2c_adapter。
msgs:I2C 要发送的一个或多个消息。
num:消息数量,也就是 msgs 的数量。
返回值:负值,失败,其他非负值,发送的 msgs 数量。
7.2 I2C 消息结构体
struct i2c_msg {
__u16 addr; /* 从机地址 */
__u16 flags; /* 读/写标志等 */
__u16 len; /* 消息长度 */
__u8 *buf; /* 数据缓冲区 */
};
8. I2C 设备驱动开发实例
说明:本次实验采用 GPIO40、GPIO41 (即i2c4) 与六轴传感器 (GY6500) 进行数据读写。
8.1. 查看模块地址
下载 i2c 工具包:i2ctools.zip
执行下面命令解压
unzip i2ctools.zip -d ~/
执行检测工具
sudo ./i2cdetect -yr 4 # 其中 4 是总线编号(比如 i2c-4)
输出如下图:
表示我们的模块地址是 0x68 。
8.2. 设备树配置
打开设备树 path/to/your/linux-6.6/arch/riscv/boot/dts/spacemit/k1-x_pinctrl.dtsi 查看引脚复用功能配置,默认已复用 I2C 功能,内容如下:
pinctrl_i2c4_0: i2c4_0_grp {
pinctrl-single,pins =<
K1X_PADCONF(GPIO_40, MUX_MODE2, (EDGE_NONE | PULL_UP | PAD_1V8_DS2)) /* i2c4_scl */
K1X_PADCONF(GPIO_41, MUX_MODE2, (EDGE_NONE | PULL_UP | PAD_1V8_DS2)) /* i2c4_sda */
>;
};
使用设备树的时候 I2C 设备信息通过创建相应的节点就行了,所以我们在 i2c4 节点下创建子节点,然后在这个子节点内描述 GY6500 这个芯片的相关信息。打开 path/to/your/linux-6.6/arch/riscv/boot/dts/spacemit/k1-x_MUSE-Pi-Pro.dts 这个设备树文件,然后找到如下内容:
&i2c4 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c4_0>;
status = "okay";
};
在 i2c4 节点下进行配置,如下:
&i2c4 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c4_0>;
status = "okay";
gy6500@68 {
compatible = "bianbusense,gy6500";
reg = <0x68>;
spacemit,i2c-fast-mode;
spacemit,i2c-addr-7bit;
status = "okay";
};
};
启动 Docker。
docker start cross-build-user
docker exec -it cross-build-user bash
进入 linux-6.6 目录重新编译 dtb 文件。
cd cross-builder/linux-6.6
make dtbs
在 MUSE Pi Pro 上执行下面命令查看 ip:
ifconfig
如下图所示,ip 为 10.0.91.67 。
执行下面命令将编译好的 k1-x_MUSE-Pi-Pro.dtb 文件传输到 MUSE Pi Pro 板子上。
注:下面的
bianbu@10.0.91.67根据上面结果替换成对应的 ip 。
sudo scp arch/riscv/boot/dts/spacemit/k1-x_MUSE-Pi-Pro.dtb bianbu@10.0.91.67:/home/bianbu
首次通过 ssh 连接 MUSE Pi Pro 会弹出连接确认,键盘输入 yes ,接着再输入密码回车即可。
在 MUSE Pi Pro 上执行以下命令进行替换:
sudo cp ~/k1-x_MUSE-Pi-Pro.dtb /boot/spacemit/6.6.63
将 MUSE Pi Pro 进行重启:
sudo reboot
使用新的设备树启动 Linux 内核之后,在 /sys/bus/i2c/devices 目录下存放着所有 I2C 设备,如果设备树修改正确的话,会在 /sys/bus/i2c/devices 目录下看到一个名为 4-0068 的子目录,如图所示:
上图的 4-0068 就是 GY6500 的设备目录, 68 就是 GY6500 器件地址。进入 4-0068 目录,可以看到 name 文件,name 就是此设备名字,在这里就是 gy6500 ,如图所示:
8.3. 运行和测试
8.3.1. 编译驱动程序
下载源码压缩包
补充:源码文件具体作用请看压缩包里的 README.md 文件。
解压
unzip 06_linux_iic.zip -d ~/
编译
驱动文件需要在Ubuntu上进行编译,因为SDK在Ubuntu上。修改Makefile里的内核路径为实际路径。
cd ~/06_linux_iic/
vim Makefile
修改KERN_DIR 的路径,如图所示:
执行以下命令,编译驱动
make
编译成功以后就会生成一个名为 gy6500_drv.ko 的驱动模块文件。
8.3.2. 加载驱动
将上一小节编译好的 06_linux_iic 文件夹拷贝到 MUSE Pi Pro 开发板上,执行下面命令。
注:下面的
bianbu@10.0.91.67根据上面结果替换成对应的 ip 。
scp -r ~/06_linux_iic/ bianbu@10.0.91.67:/home/bianbu
在开发板上进入该目录并输入如下命令加载 gy6500_drv.ko 这个驱动模块。
cd 06_linux_iic/
sudo insmod gy6500_drv.ko
8.3.3. 硬件连接
将 MUSE Pi Pro 与 gy6500 模块进行硬件连接。
8.3.4. 运行测试
当驱动模块加载成功以后使用 gy6500_test 来测试,输入如下命令:
sudo ./gy6500 /dev/gy6500
应用程序会不断的从 gy6500 中读取数据,然后输出到终端上,如图所示:
可以移动旋转六轴模块观察打印数据是否变化。