0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

基于Linux驱动的I2C总线的深度分析

h1654155971.7688 2017-12-18 16:31 次阅读

1、 I2C总线的硬件特性:两线式串行总线.用于连接CPU和外设之间的通信接口需要2根信号线,时钟控制线SCL和数据传输信号线SDA.串行:CPU和外设之间传输是一个周期传输一个BIT位, 如果需要写入0X55,需要两个时钟周期才能完成.CPU又称master,外设又称slave。

“一个时钟周期传输一个bit”:CPU和外设之间传输一个bit位,必须要通过时钟控制信号来实现双方的数据收和发!比如CPU在时钟高电平像数据线写入数据,设备在同一个周期低电平从数据线上接收数据。

“总线”:两根信号线上可以挂接很多外设,也可以挂接很多CPU.一般来说总线上只有一个CPU,如果有多个CPU,I2C总线具有仲裁机制来实现同步访问。

SCL和SDA分别会接上一个上拉电阻,这两根信号线的默认状态为高电平状态!一般如果CU或者外设配置GPIO为输出口,就等于CPU或者外设控制了GPIO获取控制权;如果配置成输入口,就等于释放控制权。
基于Linux驱动的I2C总线的深度分析

2、问:CPU如何通过两根线找到需要访问的具体设备?CPU如果找到某个具有的外设,那么CPU和外设是如何通过两根信号新完成数据交互? SDA和SCL如何搭配的?

以上答案在I2C总线协议中(芯片手册)

3、总线协议相关内容:

(1) START信号,起始信号:CPU如果要访问总线,必须CU首先向总线上发送一个START起始信号;此信号由CPU发起;SCL为高电平。SDL由高电平向低电平跳变,产生START信号‘

(2) 停止信号,结束信号:CPU结束访问总线,需要向总线发送一个STOP信号;此信号由CPU发起;SCL为高电平,SDA由低电平向高电平跳变,产生STOP信号;

(3) 设备地址:用于标识外设在I2C总线上的唯一性!同一个I2C总线上外设,每一个外设都有唯一的设备地址;如果CPU要访问某个外设,CPU只需要在总线上发送某个外设的设备地址即可,发送完毕,如果外设存在于总线上,外设会给CPU一个反馈信号,就可以进行后续的数据访问;

外设的设备地址的确定:一般有芯片厂家和原理图共同决定,以三个外设为例,电可檫除存储器AT24C02、温度传感器LM77,背光灯控制芯片ADP8860。

AT24C02,EEPROM的设备地址:1010A2A1A0R/W:1010:高4bit,芯片厂家定义;A2A1A0:原理图上都接GNDèA2A1A01=000 R=1,表示CPU读取外设,W=0,表示CPU写外设。

WP:写保护

通过以上信息,得到读设备地址:10100001=>0Xa1 写设备地址:10100000=>0Xa0 设备地址(7位,不算读写位,地址右移1位,高位补0)=》最终设备地址=01010000=》0X50

ADP8860背光灯控制芯片设备地址0101010X:读设备地址:01010101=>0X55 写设备地址:01010100=>0X54

设备地址:00101010=>0X2A

LM77设备地址100100A1AA1:如果A1A0都接地:A1A0=00 读设备地址:10010001=>0X91 写设备地址:10010000=>0X90 设备地址:01001000=>0X48。

(4)ACK信号:反馈应答信号。如果CPU发送完设备地址,,并且外设进行响应,此时给CPU发送一个ACK应答信号,告诉CPU,外设存在于总线上;如果在数据读写过程中,也可以通过ACK信号指示数据的读写过程是否正常!有效的ACK信号为低电平(数据线),无效的ACK信号为高电平!

4.CPU与外设的数据交互:I2C的数据传输,从高位开始发送,一次传输一个字节!

1.LM77温度传感器的数据传输

(1)CPU发送START信号;(2)COU发送设备地址 3.CPU发送读写位4.设备如果正常存在总线上,设备给CPU发送一个ACK信号 5.根据芯片手册进行数据的读写操作,其中涉及到ACK信号,这个ACK与4步骤ACK信号意义不太一样!6.CPU发送STOP信号,结束此次数据交互。

2.ADP8860背光灯芯片:芯片内部有一组寄存器,但是这些寄存器CPU不能像访问GPIO一样直接去访问寄存器地址,原因ADP8860并没有直接连接到CPU的4G地址空间中,需要间接的利用I2C总线访问芯片内部的寄存器地址;例如把数据0XAA写入芯片内部寄存器0X34这个地址。

(1)CPU发送START信号 (2)CPU发送设备地址

3.CPU发送读写位,如果是写,这个bit为0

4.ADP8860如果正常存在于总线上,设备返回一个ACK信号给CPU,低电平有效。

5.CPU发送访问的寄存器以后,设备给CPU发送一个ACK信号,告诉CPU可以继续访问;

7.CPU发送要写入的数据0XAA;

8.设备将数据0XAA写入到内部寄存器0X34,然后设备给CPU一个ACK信号,告诉CPU写入成功;

9.CPU发送STOP信号,结束此次的寄存器写入操作

3.电可檫除存储器EEPROM(at24c02)访问过程:

AT24C01的容量为256字节,地址编址:0—255MSN:高位LSB:低位。

将数据0XAA 写入到内部储存地址0X55,CPU要访问的内部地址的操作过程和ADP8860相似!

(1)CPU发送START信号 (2)CPU发送设备地址 (3)CPU发送写:0 (4)设备存在总线上,那么设备给CPU发送一个ACK应答信号,告诉CPU,我在总线上! (4)CPU发送要操作的地址:0X55 (5)设备接收到这个要操作的地址,设备给CPU发送一个ACK信号,告诉CPU,可以访问这个地址!(6)CPU发送要写入的数据0XAA (7)设备接收这个要写入的数据,并将数据写入到对应的地址0X55中,设备给CPU发送一个ACK信号,告诉CPU写入数据成功! (8)CPU发送一个STOP信号,停止数据的此次访问。

随机读:就是读任何一个地址里的数据即可,例如读取0X55地址空间中的数据信息

SDA和SCL如何搭配使用? 如果CPU或者设备向数据线上写入数据,应该在SCL为低电平写入数据;如果CPU或者设备从数据线上获取数据,应该在同周期的高电平去读数据!

I2C外设的具体如何操作,关键看芯片手册即可!

4.AT24C02的硬件特性:EEPROM,电可檫除存储器; 容量:2K ,256字节 传输速度:100khz’,400KHZ

写周期:5ms 分页:1页为8字节,如果按页写,最多一次只能写8字节,如果写9字节,第9字节数据会把第一字节的数据进行覆盖; 地址编码:0—255 设备地址:0X50

5.案例:存储软件和硬件版本号到EEPROM中:软件版本号:SYYMMDDXY->S14101700 硬件版本号:HYYMMDDXY->H14101700EEROM存储器地址规划:软件版本号的地址范围:0X0—0X9硬件版本哈的地址范围:0X10—0X19

驱动设计:1.采用GPIO模拟I2C时序来实现I2C总线硬件操作!2.采用操作I2C控制器来实现I2C总线硬件操作! 3.采用linux内核I2C驱动框架来实现I2C硬件操作

5.1 Linux内核的I2C驱动框架:

App:open,read,write,ioctl

Eeprom.ADDR = 0;设备地址 Eeprom.data = ‘s’;//数据

Ioctl(fd,I2C_WRITE_CMD,&eeprom)

I2C设备驱动:eeprom_ioctl, eeprom_read,………;

1.只关注硬件外设; 2.只关注硬件外设操作的数据信息; 设备地址、设备片内地址 设备的数据

2.I2C设备驱动利用内核提供的相关操作方法,将以上数据信息发送给I2C的总线驱动,由I2C总线驱动来实现硬件上面的数据传输!

内核提供的操作接口:1.i2c_transfer();//老式接口 SMBUS接口;//新式接口,兼容老式接口

作用:上一层的I2C设备驱动利用这些接口函数,将I2C设备驱动要访问的数据信息(设备地址、片内地址、数据)丢给I2C总线驱动来实现硬件的总线传输!

I2C总线驱动:1.管理的设备对象仅仅I2C控制器 I2C硬件控制器集成在CPU的内容,访问I2C控制器就通过寄存器来进行,类似串口控制器,nandflash控制器,由硬件帮你发时序!

3.I2C总线驱动启动硬件的时序,然后根据I2C设备驱动发来的数据信息(设备地址、片内地址、数据)最终完成I2C的硬件传输。

利用I2C实现I2C外设的驱动重点涉及两个驱动:I2C总线驱动和I2C设备驱动。

1.I2C总线驱动:管理的对象是I2C控制器,只负责硬件的传输数据,这个驱动一般都是芯片公司在提供的linux内核源码中,只需配置内核添加I2C总线驱动即可:

DEVICE DRIVERàI2C SupportàI2C hardware bus support-><*>s3c2410 i2c drivers

I2C总线驱动:drivers/i2c/buses/i2c-s3c.c

2.i2C设备驱动:实际开发只需关注I2C外设的驱动,就是I2C设备驱动,只需关注外设的操作的数据信息(设备地址、片内地址、数据)

5.2利用I2C驱动框架实现I2C设备驱动。

I2C设备驱动实现利用了内核分离的思想,采用虚拟总线的形式来管理I2C设备驱动,具体如下:

(1)首先linux内核已经帮你定义好了一个I2C的虚拟总线(i2c_bus_type),在这个总线上维护者两个链表:dev链表和drv链表

(2)dev链表存放硬件信息,每一个节点的对应的数据类型是struct i2c_client,用这个结构体来装载硬件信息。每当向内核添加一个硬件节点时,内核帮你遍历drv链表,取出drv链表每一个软件节点,根据硬件节点的name和软件节点的id_table中的name进行匹配,如果匹配成功,说明硬件找到了软件,然后调用软件节点的probe函数,然后将匹配成功的硬件节点的首地址传递给probe函数,供probe函数获取硬件信息;

(3)drv链表存放软件信息,每一个节点的对应的数据类型是struct_i2c_driver,用这个结构体来装载软件信息(fops)。每当向内核添加一个软件节点时,内核会帮你遍历dev链表,取出dev链表每一个硬件节点,根据硬件节点的name和软件节点的id_table中的name进行匹配,如果匹配成功,说明软件找到了硬件,然后调用软件节点的probe函数,然后将匹配成功的硬件节点首地址传递给probe函数,供probe函数获取硬件信息;

(4)i2c硬件的信息:关键是i2c设备地址,因为I2C设备地址有可能由原理图决定。

总结;实现一个I2C设备驱动关键围绕着i2c_client和i2c_driver。

[c]view plaincopy

structi2c_client{

unsignedshortflags;/*div.,seebelow读写标志*/

unsignedshortaddr;/*chipaddress-NOTE:7bit设备地址7bit*//*addressesare

charname[I2C_NAME_SIZE];//用于匹配

structi2c_adapter*adapter;/*theadapterwesiton适配器,4个总线*/

structi2c_driver*driver;/*andouraccessroutines软件节点*/

structdevicedev;/*thedevicestructureplatform_data来存自己定义的硬件信息*/

intirq;/*irqissuedbydevice中断号*/

structlist_headdetected;//链表

这个结构体不会像platform_device显示的需要自己去分配,初始化和注册,这个工作linux内核已经帮你实现,甚至注册的时候进行匹配,都是linux内核来帮你实现!

问:如果linux内核帮你实现分配初始化i2c_client,内核如何知道我要的操作的设备地址,自己的私有硬件信息? Linux内核提供了另外一个结构体struct i2c_board_info,程序员根据这个结构体来进行对I2C外设的硬件分配初始化和注册,内核会根据i2c_board_info的信息来实现对i2c_client的一系列操作。

问:驱动如何使用i2c_board_info呢?

Struct i2c_board_info{

Char type[I2C_NAME_SIZE];//用于匹配,最终会赋值给i2c_client的name

Unsigned short flags; //读写标志

Unsigned short addrd; //设备地址。最终赋值给i2c_client的addr

Void *platform_data; //装载自己定义的硬件信息,最终赋值给i2c_client的dev.platform_data

Int irq; //中断号,最终赋值给i2c_client的irq

}

明确:struct i2c_board_info的分配,初始化和注册三个步骤必须在平台代码中完成,不能以模块加载的形式来实现! 对struct i2c_board_info的操作本质就是间接的在操作i2c_client。

实现在平台代码中初始化分配i2c_board_info

  1. Vimarch/arm/mach-s5pv210/mach-cw21.c 在头文件的后面添加分配i2c_board_info的分配初始化。

Static structi2c_board_info eeprom[] = {I2C_BOARD_INFO(“at24c02”,0x50)};

说明:I2C_BOARD_INFO:用于初始化i2c_board_info的type和addrat24c02:用于匹配,跟i2c_driver的id_table的name匹配,最终会赋值给i2c_client的name。

0x50:设备地址,最终会赋值给i2c_client.addr。

2.同样在平台代码的初始化函数中(.init_machine =smdkc110_machine_init),所在函数smdkc110_machine_init中调用I2c_register_board_info注册i2c_board_info信息到内核中!i2c_regster_board_info(int busnum, struct i2c_board_info const*info, unsigned n)

函数功能:注册分配初始化好的i2c_board_info对象到内核中,内核根据这个信息初始化注册i2c_client;

参数:busnum:i2c外设所在的I2C总线编号,cw210开发板的at’24c02通过原理图可知连接到CPU的第一个I2C总线上,所以这个参数指定为0,info:执行分配初始化的i2_board_info对象数组(=eeprom)

N:对象数组的个数RRAY_SIZE(eeprom)

注意:一旦向内核注册I2c_board_info设备信息,内核在初始化时会根据此信息帮你分配初始化和注册一个i2c_client.

2177i2c_register_board_info(0, i2c-devs0, ARRAY_SIZE(i2c_devs0));

2178i2c_register_board_info(1, i2c-devs1, ARRAY_SIZE(i2c_devs1));

2179i2c_register_board_info(2, i2c-devs2, ARRAY_SIZE(i2c_devs2));

2180i2c_register_board_info(5, i2c-devs3, ARRAY_SIZE(i2c_devs5));

2181 //注册atc24c02的硬件对象i2c_board_info

2182i2c_register_board_info(0, eeprom, ARRAY_SIZE(eeprom));

Struct i2c_driver怎样使用?

1.分配初始化struct i2c_driver

Struct i2c_driver eeprom_drv = {

.driver = {

.name= “tarena”//不重要

},

.probe = at24c02_probe, //匹配成功调用

.remove = at24c02, //卸载软件节点调用

.id_table = 其中的name用于匹配

}

2.调用i2c_add_driver注册 3.调用i2c_del_driver卸载。

案例:SMBUS接口作用:I2C设备驱动利用SMBUS相关的函数,能够将I2C设备驱动涉及的数据信息丢给I2C总线驱动,然后I2C硬件传输!本质上就是I2C设备驱动和I2C总线驱动的一个数据交互的桥梁!

SMBUS接口说明文档:内核源码\Documentation\i2c\smbus-protocol找到对应的SMBUS接口函数。

SMBUS接口函数的使用。

1.找到打开smbus-protol说明文档 2.打开芯片的时序图 3.根据时序图在smbus-protol文档中找到对应的操作函数 4.想尽一切办法看这个函数的说明或者参考别人的代码。

//写数据到EEPROM中

// addr,data

App:ioctl---àaddr,data

I2C设备驱动:at24c02_i2c_write---à设备地址,addr,data

内核:SMBUS--à设备地址,addr,data

总线驱动--àSTART> 设备地址写ACK addr ACK data ACK STOP

1.使用SMBUS接口将数据(地址、数据、设备地址(G_CLIENT->addr)),丢给I2C总线驱动,启动I2C总线驱动的硬件传输。

1.1 打开SMUBS文档:内核源码\Documentation\i2c\smbus-protocol找到对应的SMBUS接口函数。

1.2打开芯片操作时序图

1.3根据时序图找到对应的SMBUS操作函数

1.4将ADDR,data和匹配成功的i2c_client通过函数丢给I2C总线驱动然后启动I2C总线的硬件传输

i2c_smbus_write_byte_data(g_client, addr, data);

从EEPROM读取数据

// addr

// app:ioctlà data

I2C设备驱动:at24c02_i2c_readà data 设备地组织:addr

内核:SMBUE—》data设备地址,addr

总线驱动-àSTART设备地址写ACK addr ACK START 设备地址 读ACK返回数据data

NOACK STOP -àdata


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    10988

    浏览量

    206725
  • I2C
    I2C
    +关注

    关注

    28

    文章

    1346

    浏览量

    120808

原文标题:Linux驱动之I2C总线

文章出处:【微信号:weixin21ic,微信公众号:21ic电子网】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    I2C规范,I2C总线原理

    I2C规范,I2C总线原理1 序言
    发表于 04-09 18:34

    浅谈I2C总线

    一个上拉电阻接到正电源,因此在不使用的时候扔保持高电平。使用 I2C 总线进行通信的设备驱动这两根线变为低电平,在不使用的时候就让它们保持高电平。每个连到 I2C 的设备都有一个唯一地
    发表于 11-30 11:50

    LinuxI2C体系结构

     在Linux 系统中,I2C 驱动由3 部分组成,即I2C 核心、I2C 总线
    发表于 08-06 06:53

    什么是基于嵌入式LinuxI2C设备驱动程序?

    由于I2C总线的通用性,Linux作为一款优秀的嵌入式操作系统,也必须要对其要有很好的支持。在Linux内核源码中对I2C
    发表于 09-02 08:04

    如何驱动I2C总线

    KC705用户指南,IIC_MUX_RESET_B在低电平时有效,因此我必须将其驱动为高电平以启用FPGA U1与I2C总线上其他组件之间的I2C串行转换。所以我把那段代码:IIC_M
    发表于 09-20 07:25

    怎样去设计I2C总线底层驱动

    I2C通信协议是什么?Windows CE系统驱动的特点有哪些?怎样去设计I2C总线底层驱动
    发表于 04-27 07:12

    I2C总线是什么

    I2C总线I2C总线(读做“IC”或“I2C
    发表于 07-21 09:03

    如何仿照Linux内核去编写I2C驱动

    的设计思想很值得借鉴,本文将刨除设备、总线等复杂概念,单从I2C驱动实现角度仿照Linux内核编写I2C
    发表于 08-23 08:03

    I2C总线驱动和设备驱动

    为400kbit/s常见iic设备eeprom触摸芯片温湿度传感器mpu6050(姿态传感器)…框架图I2C核心提供I2C总线驱动和设备驱动
    发表于 12-13 06:18

    I2C总线的原理是什么

    采用串行总线技术可以大大简化硬件的设计,体积减少,可靠性高。常见的总线I2C(inter IC bus),单总线,SPI总线
    发表于 01-18 07:36

    I2C总线的学习资料分享

    一、概述  1、I2C总线只有两根双向信号线。一根是数据线SDA,另一根是时钟线SCL。   SCL:上升沿将数据输入到每个器件中;下降沿驱动EEPROM器件输出数据。(边沿触发)  SDA:双向
    发表于 01-19 08:05

    I2C设备驱动的相关资料下载

    文章目录1、简介2I2C总线、设备和驱动的结构体定义2.1 结构体定义--I2C总线2.2 结
    发表于 02-10 06:39

    linux移植MPU6050的I2C驱动过程分享

    有MPU6050的例程,测试后很好用,如下图(梦幻联动一下:树莓派PICO+freeRTOS)因为pico上有现成的能用的例程,所以改一改之后直接移植到linux上首先介绍一下linux下的I2C框架①、
    发表于 02-10 06:48

    I2C总线简介

    I2C 简介I2C 是双线双向的串行总线,它为设备之间数据交换提供了一种简单高效的方法。I2C 标准是一个具有冲突检测机制和仲裁机制的真正意义上的多主机
    发表于 02-22 06:55

    Linux驱动程序支持通过I2C和SPI总线进行通信吗

    Linux驱动程序支持哪些设备呢?Linux驱动程序支持通过I2C和SPI总线进行通信吗?
    发表于 03-09 06:33