历史上的今天
返回首页

历史上的今天

今天是:2024年11月24日(星期日)

正在发生

2021年11月24日 | I2C协议->裸机程序->adapter驱动程序分析

2021-11-24

开发板:mini2440


内核  :linux2.6.32.2


参考  :韦东山毕业班I2C视频教程


1、i2c协议简要分析


    i2c中线是一种由 PHILIPS 公司开发的串行总线,用于连接微控制器及其外围设备,它具有以下特点。

        1、只有两条总线线路:一条串行数据线SDA,一条串行时钟线SCL。

        2、每个连接到总线的器件都可以使用软件根据它的唯一的地址来确定。

        3、传输数据的设备之间是简单的主从关系。

        4、主机可以用作主机发送器或者主机接收器。

        5、它是一个真正的多主机总线,两个或多个主机同时发起数据传输时,可以通过冲突检测和仲裁来防止数据被破坏。

        6、串行的8位双向传输,位速在标准模式下可达 100kbit/s,在快速模式下可达400kbit/s,在高速模式下可待3.4Mbit/s。

        7、片上的滤波器可以增加抗干扰能力,保证数据的完整性。

        8、连接到同一总线上的IC数量只受到总线的最大电容400Pf的限制。

如上图所示,启动一个传输时,主机先发送一个S信号,然后发送8位数据。这8位数据的前7位为从机地址,第八位表示传输的方向(0表示写,1表示读),如果有数据则继续发送,最后发出P信号停止。

信号类型:


    注意:正常数据传输时,SDA 在 SCL 为低电平时改变,在 SCL 为高电平时保持稳定。

    开始信号 S 信号:

        SCL 为高电平时,SDA由高电平向低电平跳变,开始传送数据。

    结束信号 P 信号:

        SCL 为高电平时,SDA由低电平向高电平跳变,结束传送数据。

    响应信号 ACK:

        接收器在接收到8位数据后,在第9个时钟周期,拉低 SDA 电平

    注意:在第9个时钟周期,发送器保持SDA为高,如果有ACK,那么第9个时钟周期SDA为低电平,如果没有为高电平,发送器根据电平高低分辨是否有ACK信号。

          如果使能了IIC中断,发送完8bit数据后,主机自动进入中断处理函数,此时SCL被发送器拉低,让接收器被迫等待。恢复传输只需要清除中断挂起。


2、 s3c2440 读写流程

    1、设置传输模式 IICSTAT[7-6],我们做实验与AT24C08通信时,2440作为主机,因此只用到主机发模式和主机收模式。

    2、写入从机地址到 IICDS[7-1],此时IICDS[7-1]位表示从机地址,第0位不关心。如 AT24C08 为 0xA0(最低位写0了,发送到数据线上的7位地址的后边以为才表示收发,这里虽然写0但并不是根据这里的0来真正发送的)。

    3、写 0xF0(写) 或 0xB0(读)到 IICSTAT 寄存器, 高两位表示 传输模式前边设置过了,设置IICSTT[5-4] 为 11,使能传输,发送S信号。

    4、IIC控制器自动将第2步中设置的 IICDS[7-1] 再根据 传输模式 补充 IICDS[0]位,发送出去。

    5、进入第9个时钟周期,此时,从机决定是否发出ACK信号,主机进入中断,判断是否收到ACK信号,以及是否继续传输。

    继续发送:

        1、将数据写入 IICDS 

        2、清除中断挂起,SCL时钟恢复,IICDS的数据被自动发送到 SDA 线上,回到第5步。

    停止发送:

        1、写 0xD0(写) 和 0x90(读) 到 IICATAT ,IICATAT[7-6]还是表示的传输模式,IICATAT[5-4] == 0 1,发送停止信号

        2、清除中断挂起,SCL时钟恢复,发出停止信号

        3、延时,等待停止信号发出

3、 AT24C08 读写分析


    1、写过程

    写过程与2440芯片的里的写流程相一致,按照流程写就OK


    2、读过程

    读过程是由2440芯片里的一个写流程加一个读流程组合而成,其中写流程结束没有发出P信号,而是直接发出了S信号开始读流程,也就是我为什么加了一道红线的原因。


附上一份简单的裸机程序,仅供参考:基于MINI2440



#include

#include "s3c2440.h"

 

void Delay(int time);

 

#define WRDATA      (1)

#define RDDATA      (2)

 

typedef struct tI2C {

    unsigned char *pData;   /* 数据缓冲区 */

    volatile int DataCount; /* 等待传输的数据长度 */

    volatile int Status;    /* 状态 */

    volatile int Mode;      /* 模式:读/写 */

    volatile int Pt;        /* pData中待传输数据的位置 */

}tS3C24xx_I2C, *ptS3C24xx_I2C;

 

static tS3C24xx_I2C g_tS3C24xx_I2C;

 

/*

 * I2C初始化

 */

void i2c_init(void)

{

    GPEUP  |= 0xc000;       // 禁止内部上拉

/*

 * AT24C08 两根线 I2CSCL I2CSDA 与 2440芯片相连

 *  配置2440 GPECON GPE15 GPE14引脚为I2C功能

 */

    GPECON |= 0xa0000000;   // 选择引脚功能:GPE15:IICSDA, GPE14:IICSCL

/* 开INT_IIC中断 */

    //INTMSK &= ~(BIT_IIC);

 

    /* bit[7] = 1, 使能ACK

     * bit[6] = 0, IICCLK = PCLK/16

     * bit[5] = 1, 使能中断

     * bit[3:0] = 0xf, Tx clock = IICCLK/16

     * PCLK = 50MHz, IICCLK = 3.125MHz, Tx Clock = 0.195MHz

     */

    IICCON = (1<<7) | (0<<6) | (1<<5) | (0xf);  // 0xaf

 

    //IICADD  = 0x10;     // S3C24xx slave address = [7:1]

    IICSTAT = 0x10;     // I2C串行输出使能(Rx/Tx)

}

 

void I_Write(unsigned int slvaddr, unsigned char addr, unsigned char data)  

{  

    unsigned int ack;  

// 写从地址

IICSTAT |= 0x1<<6;//主机写模式   

    IICSTAT |= 0x1<<7;  

    IICDS = slvaddr;//0xa0;  //write slave address to IICDS   

    IICCON&=~0x10; //clear pending bit   

    IICSTAT = 0xf0;  //(M/T start)   

    while((IICCON & 1<<4) == 0);//udelay(10);//ack period and then interrupt is pending   

    // 写寄存器地址    

    IICDS = addr;  

    IICCON&=~0x10; //clear pending bit   

    while((IICCON & 1<<4) == 0);//udelay(10);//ack period and then interrupt is pending   

// 写数据

    IICDS = data;  

    IICCON&=~0x10; //clear pending bit   

    while((IICCON & 1<<4) == 0);//udelay(10);//ack period and then interrupt is pending   

// 发出停止信号

    IICSTAT = 0xD0; //write (M/T stop to IICSTAT)   

    IICCON&=~0x10; //clear pending bit   

 

    while((IICSTAT & 1<<5) == 1);  

}  

unsigned char I_Read(unsigned int slvaddr, unsigned char addr)  

{  

    unsigned char data  = 1;  

    int ack;  

// 写从地址

IICSTAT |= 0x1<<6;//主机写模式   

    IICSTAT |= 0x1<<7;  

slvaddr = 0xA0;    

    IICDS = slvaddr;//0xa0;  //write slave address to IICDS   

    IICCON&=~0x10; //clear pending bit   

    IICSTAT = 0xf0;  //(M/T start)   

    while((IICCON & 1<<4) == 0);//udelay(10);//ack period and then interrupt is pending   

  

    // 写寄存器地址

    IICDS = addr;  

    IICCON&=~0x10; //clear pending bit   

    while((IICCON & 1<<4) == 0);//udelay(10);//ack period and then interrupt is pending   

 

    // 写从地址(读模式)

    slvaddr = 0xA1;

    IICSTAT &= ~(0x1<<6);//主机接受模式  

    IICSTAT |= 0x1<<7;  

    IICDS = slvaddr;  

    IICCON&=~0x10; //clear pending bit   

    IICSTAT = 0xb0;  //(M/R Start)   

    while((IICCON & 1<<4) == 0);//udelay(10);//uart_SendByte('o');//ack period and then interrupt is pending::   

 

// 读数据

    data = IICDS;  

    //IICCON&=~0x10; //clear pending bit

IICCON = 0x2f; //清挂起状态,并设置无应答

    while((IICCON & 1<<4) == 0);//udelay(10);//ack period and then interrupt is pending   

    data = IICDS;  

    

//IICCON&=~0x10; //clear pending bit   

IICCON = 0x2f; //清挂起状态,并设置无应答

    while((IICCON & 1<<4) == 0);//udelay(10);//ack period and then interrupt is pending   

 

 

    IICSTAT = 0x90;  

IICCON = 0xaf;

    //IICCON &= ~0x10; //clear pending bit   

 

    while((IICSTAT & 1<<5) == 1);  

 

    return data;  

      



4、adapter驱动程序

    这里,我们主要分析驱动里的发送核心算法,至于注册中断,IO内存映射,设置寄存器不在讨论。

    static int xxx_i2c_xfer(struct i2c_adapter *adpap, struct i2c_msg *msg,int num)

    这个算法函数的作用就是将上层封装好的一些i2c_msg 进行解析,将数据写入寄存器,发送出去。在设备驱动层,我们使用了类似i2c_smbus_write_byte 等函数,类似的函数有很多,它们的作用就是封装i2c_msg 结构(比如读和写的 msg 肯定不一样,读一个字节和读多个字节也不一样),然后调用 i2c_smbus_xfer_emulated->i2c_transfer,最终调用到我们的xxx_i2c_xfer函数进行传输。通过分析i2c_smbus_xfer_emulated函数,我们可以了解i2c_msg是如何封装的。下面,我们简单分析一下,知道最上层想干什么,我们才能知道实现哪些底层的功能不是。

struct i2c_msg {

__u16 addr; //从机地址

__u16 flags;

__u16 len; // buf 里 有多少个字节

__u8 *buf; // 本 msg 含有的数据,可能是1个字节,可有可能是多个字节

};

    此函数,省略了很多内容,举例分析而已~,细节请看源码



static s32 i2c_smbus_xfer_emulated(struct i2c_adapter * adapter, u16 addr,

                                   unsigned short flags,

                                   char read_write, u8 command, int size,

                                   union i2c_smbus_data * data)

{

unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3];

unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2];

int num = read_write == I2C_SMBUS_READ?2:1; // 写操作两个Msg 读操作一个msg 这和我们前面分析AT24c08是一致的

struct i2c_msg msg[2] = { { addr, flags, 1, msgbuf0 },

                          { addr, flags | I2C_M_RD, 0, msgbuf1 }

                        };

msgbuf0[0] = command; // 从机地址右移1位得到的,比如AT24C08  为 0x50

switch(size) {

case I2C_SMBUS_BYTE_DATA: // 单字节读写

if (read_write == I2C_SMBUS_READ)

msg[1].len = 1;

/*

* 读:

* msgbuf0[0] = command

*  msg[1].len = 1 ,数据会读到 msgbuf0[1] 里

*/

else {

msg[0].len = 2;

msgbuf0[1] = data->byte;

/*

* 写:

* msgbuf0[0] = command

* msgbuf0[1] = data->byte

*/

}

break;

}

status = i2c_transfer(adapter, msg, num);

}

    上面代码跟我们分析AT24C08的时候如出一辙,对于一个写操作,我们只需要一个2440的写流程对应于这里的一个Msg,然而对于读操作,我们需要2440的两个流程,对应于这里的两个Msg。那么,我们底层控制器驱动需要做的工作就是,取出所有的Msg,将每一个Msg里buf里的数据发送出去,如果有下一个Msg, 那么再将下一个Msg里的buf发送完毕,最后发出P停止信号。还有一点,每发送一个Msg都要先发出S开始信号。


    在看adapter程序之前,我们先来简单思考一下,发出S开始信号之后,可能有以下3中情况:

        1、当前msg.len == 0 ,如果有ACK直接发出stop信号。这种情况出现在,控制器枚举设备的时候,因为它只发送S信号以及设备地址,不发送数据。

        2、根据msg->flags 为 I2C_M_RD 等信息判断读写,msg->flags 最低位为1表示读,最低位为0表示写。

            #define I2C_M_TEN0x0010          /* this is a ten bit chip address */

            #define I2C_M_RD0x0001           /* read data, from slave to master */

推荐阅读

史海拾趣

First Switchtech公司的发展小趣事

在电子行业的初期,First Switchtech公司(或类似公司)凭借其在开关技术领域的突破性创新,迅速在市场中崭露头角。公司研发出了一种新型低功耗、高可靠性的电子开关,这一创新不仅解决了当时市场上开关设备能耗高、故障率大的问题,还极大地提升了产品的整体性能。随着这一技术的广泛应用,First Switchtech公司逐渐在电子开关领域建立了领先地位,并带动了整个行业的技术进步。

Boundary Devices公司的发展小趣事

为了进一步扩大市场份额,Boundary Devices积极实施国际化战略。公司通过与全球各地的合作伙伴建立合作关系,将产品推向国际市场。同时,公司还积极参加国际电子展会和技术交流活动,与全球同行进行深入的交流与合作,不断提升公司的国际影响力。

C&K Switches公司的发展小趣事

2022年,C&K Switches公司被Littelfuse以5.4亿美元的企业价值收购。Littelfuse是一家工业技术制造公司,致力于打造一个可持续、互联和更安全的世界。两家公司在工业、通讯以及车载领域有着相近的市场布局和业务高度互补。收购完成后,C&K成为Littelfuse电子业务部门的一部分,双方共同为客户提供更全面的解决方案。这一收购不仅加强了C&K的市场地位,还为其未来的发展注入了新的活力和机遇。

BULGIN公司的发展小趣事

在2016年,BULGIN公司宣布与姊妹公司Arcolectric进行战略性合并。这一合并使得两个具有互补性的产品组合合为一体,BULGIN因此成为更加全面和覆盖范围更加广的工程解决方案提供商。合并后的BULGIN不仅扩大了专长范围,特别是在机电开关领域,还进一步提升了在全球市场的竞争力。

BAND-IT公司的发展小趣事

20世纪60年代,人类开始迈入太空探索的新纪元。在这个充满挑战和未知的领域中,紧固件的可靠性和安全性至关重要。1961年,艾伦·谢波德成为第一个乘坐麦克唐纳道格拉斯太空舱在太空旅行的美国人,而太空舱里的数千根软管和扎带卡箍正是由BAND-IT公司提供。这些产品以其出色的耐用性和可靠性,经受住了太空极端环境的考验,为太空探索的安全和成功作出了贡献。

DECON公司的发展小趣事

品质是企业的生命线,DECON公司深知这一点。因此,公司建立了完善的品质管理体系,从原材料采购到生产过程的每一个环节都进行严格的质量监控。公司引进了先进的生产设备和技术,优化了生产流程,提高了生产效率和产品质量。同时,公司还建立了完善的售后服务体系,为客户提供及时、高效的技术支持和解决方案。

问答坊 | AI 解惑

如何在两个进程中使用同一个串口

我想在两个程序中使用同一个串口,由于不能使用CreateFile打开两次串口,所以需要使用同一个HANDLE,而直接使用这个HANDLE又不行,在PC上可以使用DuplicateHandle来进行一个拷贝,但是在CE上这个函数说只能拷贝Event、Mutex、Semaphore这三种类型的 ...…

查看全部问答>

S3C2440板子SDRAM不转,为什么?

     最近做了一块S3C2440的板子,程序从FLASH搬4K到2440内部的SRAM中初始化CPU,SDRAM后,(前4K代码运行正常,有指示灯显示),搬代码到SDRAM中,在SDRAM运行,就运行不了,程序跑飞,不知道为什么?     &n ...…

查看全部问答>

串口发送数据格式

现在要用软件方式模拟方式向单片机的串口 发送数据.   就是我在程序中向一根线写数据(位) 请问串口的数据线的格式定义是怎么. 采用的是波特率是9600. 8位数据位. 1位停止位. 比如说:我一要向该线写数据0xA5B6(10100101)(10110110) ...…

查看全部问答>

求一个基于16F877的数字电压表的汇编语言编写的程序~

测量电压0~5V 测量电压在液晶显示屏上显示 用键盘可以改变测量量程提高测量精度~ 唔,如果有高人能够指点~感激不尽的撒~…

查看全部问答>

CCSv3.3加载程序时,弹出警告

CCSv3.3加载程序时,弹出如下警告…

查看全部问答>

PL2303最新驱动1.8.19 (2013-8-14)

http://www.prolific.com.tw/US/ShowProduct.aspx?p_id=225&pcid=41 Windows Driver Installer Setup Program (For PL2303 HXA, XA, HXD, EA, RA, SA, TA, TB versions) Installer version & Build date: 1.8.19 (2013-8-14) Windows XP (3 ...…

查看全部问答>

【IAR Error】IAR MSP430编译报错:error

无编号警告类型: 1、Sat Jun 23, 2012 17:41:05: The stack pointer for stack \'Stack\' (currently Memory:0xF5336) is 原因:http://blog.sina.com.cn/s/blog_4c0cb1c0010153l9.html     IAR相关设置:Tools->Option->Stack->Wa ...…

查看全部问答>

PIC24HJ128GP204发生非预期的软件复位

我在使用PIC24HJ128GP204,发现程序在休眠状态下会被复位,仿真发现复位后RCON的值是0x48,即发生了软件复位,但我的程序中并没有调用过RESET指令。有大侠遇到过这样的问题的吗?求解…

查看全部问答>