项目任务:1. i2c控制摄像头马达升降及查询升降状态;2. i2c控制usb hub复位
一、i2c总线协议简介:
①i2c硬件结构:i2c
总线在物理连接上非常简单,分别由SDA
(串行数据线)和SCL
(串行时钟线)及上拉电阻组成。通信原理是通过对SCL
和SDA
线高低电平时序的控制,来产生I2C
总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。I2C
通信方式为半双工,只有一根SDA
线,同一时间只可以单向通信。
②i2c主从设备:I2C
总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址(地址通过物理接地或者拉高,主从设备之间就通过这个地址来确定与哪个器件进行通信,在通常的应用中,我们把CPU
端带有I2C
总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。
③i2c总线协议:总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。
起始和结束信号产生条件:总线在空闲状态时,SCL
和SDA
都保持着高电平;
当SCL
为高电平而SDA
由高到低的跳变,表示产生一个起始条件;
当SCL
为高而SDA
由低到高的跳变,表示产生一个停止条件。
在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C
器件无法访问总线;而在停止条件产生后,本次数据传输的主从设备将释放总线,总线再次处于空闲状态。 主设备在SCL
线上产生每个时钟脉冲的过程中将在SDA
线上传输一个数据位,当一个字节按数据位从高位到低位的顺序传输完后,紧接着从设备将拉低SDA
线,回传给主设备一个应答位,此时才认为一个字节真正的被传输完成。
i2c
总线上的每一个设备都对应一个唯一的地址,主从设备之间的数据传输是建立在地址的基础上,地址的传输如上所述的数据传输时一致,只不过在最低位添加读写标志位,如1表示读,0表示写,所以呢,每一最小包数据是由9bit组成,其中8bit内容+1bit ACK,如果该数据是地址数据,则8bit中有1bit是读写位。
接下来,我们来使用i2c控制摄像头马达升降及查询升降状态。
第一步:通过dts增加一个i2c设备总线,对应的节点名称为i2c-3,
i2c3: i2c@3 {
compatible = "mstar,swi2c";
iic-mode = <1>; /* i2c-mode: 0:swi2c; 1:hwi2c */
bus-index = <3>; /* swi2c setting: bus index */
sda-gpio = <10>; /* swi2c setting: sda pad */
scl-gpio = <9>; /* swi2c setting: scl pad */
def-delay = <100>; /* swi2c setting: clk */
hw-port = <2>; /* hwi2c setting: port */
pad-mux = <18>; /* hwi2c setting: pad mux */
speed-khz = <100>; /* hwi2c setting: clk */
retries = <5>; /* Unused */
status = "okay"; /* Unused */
};
然后通过控制该设备节点,我们可以与摄像机进行通信(只需要填上要访问的寄存器地址)。具体的协议如下所示:
地址 |
定义 |
数据 |
读写 |
备注 |
0x01 |
查询软件版本 |
版本格式为Vx.y bit7 bit6 bit5 bit4:x bit3 bit2 bit1 bit0:y |
只读 |
|
0x02 |
查询硬件信息 |
bit3 bit2 bit1 bit0:结构 0x01:一段式 0x02:二段式 bit7 bit6 bit5 bit4:保留 |
只读 |
|
0x03 |
查询升降状态 |
0x00:原始状态 0x01:原始状态和第一段之间 0x02:第一段就位 0x03:第一段和第二段之间 0x04:第二段就位 |
只读 |
|
0x04 |
查询升降时间 |
全程时间(单位秒) |
只读 |
仅供参考,断电前收起功能,请查询状态,确保回到原始状态,再断电 |
0x10 |
控制升降 |
0x00:全部收起 0x01:第一段露出 0x02:第二段露出 |
只写 |
高段升起低段也升起,不用单独控制低段 |
根据上面的协议,我将上述功能封装成两个接口,一个是控制接口,另一个是查询接口,这两个接口实际上也只是ioctl中的i2c_rdwr_ioctl_data结构体内容不同而已(i2cFD为open i2c-3节点时返回的句柄)。
// 查询命令集(读的时候要先将地址写进去,然后再读)
int query_motor_updown(int i2cFD, unsigned char addr, unsigned char* cmd) {
LOGE("MotorUpDown query_motor_updown start");
int ret = -1;
i2c_rdwr_ioctl_data ioctl_data;
i2c_msg msg[2];
msg[0].addr = I2C_ADDRESS_SLAVE;
msg[0].len = 1;
msg[0].flags = 0;
msg[0].buf = &addr;
msg[1].addr = I2C_ADDRESS_SLAVE;
msg[1].flags = I2C_M_RD; // Read
msg[1].len = 1;
msg[1].buf = cmd;
ioctl_data.msgs = msg;
ioctl_data.nmsgs = 2;
ret = ioctl(i2cFD, I2C_RDWR, &ioctl_data);
if (ret < 0) {
LOGE("MotorUpDown query_motor_updown I2C_RDWR is failed(error: %s)", strerror(errno));
return QUERY_MOTOR_ERROR;
}
LOGI("MotorUpDown query_motor_updown ret = %d, cmd = 0x%x end", ret, *cmd);
return CMD_SUCCESS;
}
// 控制命令集(写的时候,直接写就行了)
int ctl_motor_updown(int i2cFD, unsigned char addr, unsigned char cmd) {
LOGE("MotorUpDown ctl_motor_updown start");
int ret = -1;
int bufSize = 2;
i2c_rdwr_ioctl_data ioctl_data;
i2c_msg msg;
msg.addr = I2C_ADDRESS_SLAVE;
msg.len = bufSize;
msg.flags = 0;
msg.buf = (unsigned char *)malloc(bufSize);
if (nullptr == msg.buf) {
LOGI("MotorUpDown ctl_motor_updown malloc is error");
return MALLOC_ERROR;
}
msg.buf[0] = addr;
msg.buf[1] = cmd;
ioctl_data.msgs = &msg;
ioctl_data.nmsgs = 1;
ret = ioctl(i2cFD, I2C_RDWR, &ioctl_data);
if (ret < 0) {
LOGE("MotorUpDown ctl_motor_updown I2C_RDWR is failed(error: %s)", strerror(errno));
return CTL_MOTOR_ERROR;
}
LOGI("MotorUpDown ctl_motor_updown I2C_RDWR ret = %d, cmd = 0x%x end", ret, cmd);
if(nullptr != msg.buf) {
free(msg.buf);
msg.buf = nullptr;
}
return CMD_SUCCESS;
}
目前已经能够实现i2c来控制摄像机马达升降和查询升降状态。
二、i2c驱动简介
①i2c核心(i2c-core)
2C核心维护了i2c_bus结构体,提供了I2C总线驱动和设备驱动的注册、注销方法,维护了I2C总线的驱动、设备链表,实现了设备、驱动的匹配探测。贴心的Linux内核已经为我们提供这了部分代码;
其中的核心函数如下:
a. 增加/删除i2c_adapter
int i2c_add_adapter(struct i2c_adapter *adapter);
int i2c_del_adapter(struct i2c_adapter *adapter);
b. 增加/删除i2c_driver
int i2c_add_driver(struct i2c_driver *driver);
int i2c_del_driver(struct i2c_driver *driver);
c. i2c传输
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msg, int num);
i2c_transfer这个函数会经常用到,主要用于进行i2c适配器和i2c设备之间的一组消息交互,该组消息就放在结构体msg中。i2c_transfer最后是调用i2c_algorithm的master_xfer函数驱动硬件。
②i2c驱动总线
I2C总线驱动维护了I2C适配器数据结构(i2c_adapter)和适配器的通信方法数据结构(i2c_algorithm)。所以I2C总线驱动可控制I2C适配器产生start、stop、ACK等。该部分代码由芯片厂商提供;
③i2c设备驱动
I2C设备驱动主要维护两个结构体:i2c_driver和i2c_client,实现和用户交互的文件操作集合fops、cdev等。此部分代码就是驱动开发者需要完成的。
综上,i2c-core是i2c驱动总线和i2c设备驱动的中间枢纽,它以通用的方式实现i2c设备与适配器的沟通。接下来,我们手动写一个i2c设备驱动,来完成i2c访问hub寄存器的任务:
(1)前置条件:创建一个i2c-client,即一个真实的i2c物理设备,在linux 内核3.0版本以后,开始使用设备树来进行创建i2c-client,也就是在上面提到的dts中配置相关属性:
i2c3: i2c@3 {
compatible = "mstar,swi2c";
iic-mode = <1>; /* i2c-mode: 0:swi2c; 1:hwi2c */
bus-index = <3>; /* swi2c setting: bus index */
sda-gpio = <10>; /* swi2c setting: sda pad */
scl-gpio = <9>; /* swi2c setting: scl pad */
def-delay = <100>; /* swi2c setting: clk */
hw-port = <2>; /* hwi2c setting: port */
pad-mux = <18>; /* hwi2c setting: pad mux */
speed-khz = <100>; /* hwi2c setting: clk */
retries = <5>; /* Unused */
status = "okay"; /* Unused */
myi2c1226@2C {
compatible = "invensense,myi2c1226";
reg = <0x2C>;
};
};
(2)在linux内核源码中的driver目录下添加tcl_2c文件夹,并书写相应的tcl_i2c.c文件,如下所示:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#define HUB_ADDR 0x00
static int myi2c_probe(struct i2c_client *client, const struct i2c_device_id *id) {
int ret = -1;
i2c_rdwr_ioctl_data ioctl_data;
i2c_msg msg;
// 读取hub的寄存器地址 HUB_ADDR
unsigned char addr = HUB_ADDR;
// 接收从hub中读取到的数据;
unsigned char val = 0xff;
msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].len = 1;
msg[0].buf = &addr;
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].len = 1;
msg[1].buf = &val;
ioctl_data.msgs = msg;
ioctl_data.nmsgs = 2;
ret = i2c_transfer(client->adapter, msg, 2);
if (ret < 0) {
printk(KERN_ERR "myi2c_probe i2c_transfer ret = %d is failed", ret);
return ret;
}
printk(KERN_ERR "myi2c_probe i2c_transfer ret = %d, val = 0x%x end", ret, val);
return 0;
}
// 用来匹配设备my-i2c设备树节点
static const struct of_device_id myi2c1226_of_match[] = {
{.compatible = "invensense,myi2c1226"},
{},
};
static struct i2c_driver myi2c1226_driver = {
.driver = {
.name = "invensense,myi2c1226",
.owner = THIS_MODULE,
.of_match_table = myi2c1226_of_match,
},
.probe = myi2c_probe,
};
static int __init myi2c1226_driver_init(void) {
printk(KERN_ERR "myi2c_probe myi2c1226_driver_init start\n");
return i2c_add_driver(&myi2c1226_driver);
}
static void __exit myi2c1226_exit(void) {
printk(KERN_ERR "myi2c_probe myi2c1226_driver_init start\n");
i2c_del_driver(&myi2c1226_driver);
}
module_init(myi2c1226_driver_init);
module_exit(myi2c1226_exit);
其中上述加粗部分是用于设备和驱动相连接的标志,该部分与dts中对应一致后,才算探测成功,这样才会调用到上述中的probe函数,在该函数中就执行了一件事,通过hub寄存器地址查看hub状态。
在tcl_i2c.c同级目录下还有相应的Kconfig文件和Makefile文件,非常简单,这里就不再赘述
关于i2c相关知识我也是学习了皮毛,以后可以利用空闲时间去系统地学习下。bye~
版权声明:本文为CSDN博主「haodada1226」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/haodada1226/article/details/121905378
暂无评论