通过i2c控制摄像机马达升降

项目任务:1. i2c控制摄像头马达升降及查询升降状态;2. i2c控制usb hub复位

一、i2c总线协议简介:

①i2c硬件结构:i2c总线在物理连接上非常简单,分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成。通信原理是通过对SCLSDA线高低电平时序的控制,来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。I2C通信方式为半双工,只有一根SDA线,同一时间只可以单向通信。

②i2c主从设备:I2C总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址(地址通过物理接地或者拉高,主从设备之间就通过这个地址来确定与哪个器件进行通信,在通常的应用中,我们把CPU端带有I2C总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。

③i2c总线协议:总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。

        起始和结束信号产生条件:总线在空闲状态时,SCLSDA都保持着高电平;
        当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

生成海报
点赞 0

haodada1226

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

多功能智能药盒

多功能智能药盒 本设计选取的主控芯片是 STM32F103C8T6 最小单片机系统,利用蓝牙模块实现智能药盒与手机 APP 之间的通讯,达到数据同步和控制。而 APP 的主要功能是服药时间的设定、管理药盒实时采集的

QM2150平台 配置SPI死机

项目场景: 由于在该平台上需要调试一个spi设备,但是在调试过程发现:一旦在spi总线下挂载设备节点就会出现死机。 问题描述: 首先我们打开spi-log: kernel/msm-4.

MTK平台 mt6771 GPIO控制

MTK平台下GPIO控制分为lk和正常模式控制; lk模式控制GPIO(所用调用必须包含在编译lk的宏定义中) 头文件: #include