历史上的今天
返回首页

历史上的今天

今天是:2025年03月17日(星期一)

正在发生

2020年03月17日 | 第019课 I2C协议详解及裸机程序分析

2020-03-17 来源:eefocus

第001节_I2C协议与EEPROM

I2C协议

I2C在硬件上的接法如下(图19-1)所示,主控芯片引出两条线SCL,SDA线,在一条I2C总线上可以接很多I2C设备,我们还会放一个上拉电阻(放一个上拉电阻的原因以后我们再说)。

这里写图片描述

我们怎么传输数据,我们需要发数据从主设备发送到从设备上去,也需要把数据从从设备传送到主设备上去,数据涉及到双向传输。


举个例子:

这里写图片描述

体育老师:可以把球发给学生,也可以把球从学生中接过来。


1.发球:a.老师说:注意了(start) 

b.老师对A学生说我要球发给你(地址)。 

:: c.老师就把球发出去了(传输)。 

:: d.A收到球之后,应该告诉老师一声(回应)。 

:: e.老师说下课(停止)


2.接球: 

:: a.老师说注意了(start), 

:: b.老师说:B把球发给我(地址) 

:: c.B就把球发给老师(传输) 

:: d.老师收到球之后,给B说一声,表示收到球了(回应)。 

:: e.老师说下课(停止)


我们就使用这个简单的例子,来解释一下IIC的传输协议。 

* 老师说注意了,表示开始信号(start) 

* 老师告诉某个学生,表示发送地址(address) 

* 老师发球/接球,表示数据的传输 

* 老师/学生收到球,回应表示:回应信号(ACK) 

* 老师说下课,表示IIC传输接受(P)


IIC传输数据的格式

1.写操作:


刚开始主芯片要发出一个start信号,然后发出一个设备地址(用来确定是往哪一个芯片写数据),方向(读/写,0表示写,1表示读)。


回应(用来确定这个设备是否存在),然后就可以传输数据,传输数据之后,要有一个回应信号(确定数据是否接受完成),然后再传输下一个数据。


每传输一个数据,接受方都会有一个回应信号,数据发送完之后,主芯片就会发送一个停止信号。


:: 白色背景:主→从 

:: 灰色背景:从→主

这里写图片描述

2.读操作:


刚开始主芯片要发出一个start信号,然后发出一个设备地址(用来确定是从哪一个芯片读取数据),方向(读/写,0表示写,1表示读)。


回应(用来确定这个设备是否存在),然后就可以传输数据,传输数据之后,要有一个回应信号(确定数据是否接受完成),然后在传输下一个数据。


每传输一个数据,接受方都会有一个回应信号,数据发送完之后,主芯片就会发送一个停止信号。


:: 白色背景:主→从 

:: 灰色背景:从→主

这里写图片描述

传输是以8位为单元数据传输的,先传输最高位(MSB),主芯片发出start信号之后,然后发出9个时钟传输数据。


(1)开始信号(S):SCL为高电平时,SDA山高电平向低电平跳变,开始传送数据。


(2)结束信号(P):SCL为电平时,sDA由低电平向高电平跳变,结束传送数据。


(3)响应信号(ACK):接收器在接收到8位数据后,在第9个时钟周期,拉低SDA


SDA上传输的数据必须在SCL为高电平期间保持稳定,SDA上的数据只能在SCL为低电平期间变化。如图 

这里写图片描述

1.问题:如何在SDA上实现双向传输?


答:主芯片通过一根SDA线既可以把数据发给从设备,也可以从SDA上读取数据,连接SDA线的引脚里面必然有两个引脚(发送引脚/接受引脚)。


2.问题:主设备(从设备)发送数据时,从设备(主设备)的发送引脚,不影响数据的发送,怎么做到呢?


答:里面放一个三极管,使用开极(极电集开发出去作为输出)电路,如下图 

这里写图片描述

下面画一个真值表: 

这里写图片描述

从真值表和电路图我们可以知道,当某一个芯片不行影响SDA线时,那就不驱动这个三极管。

想输出高电平时;都不驱动(高电平就由上拉电阻决定)。

想输出低电平,就驱动三极管。

从下面的例子可以看看数据是怎么传的(实现双向传输),比如:主设备发送(8bit)给从设备 

1.前8个clk 

* 从设备不要影响,从设备不驱动三极管; 

* 主设备决定数据;


2.第9个clk,由从设备决定数据 

* 主设备不驱动三极管; 

* 从设备决定数据;


从上面的例子,就可以知道,怎样在一条线上实现,双向传输的办法。这就是为什么在SDA,SCL上放上拉电阻的原因。


在第9个时钟之后,如果有某一方处于繁忙状态,它可以一直把SCL拉低当SCL为低电平时候,大家都不应该使用IIC总线,只有当SCL从低电平变为高电平的时候,IIC总线才能被使用。


从前图我们也可以知道ACK信号应该是低电平。主设备不驱动三极管,如果从设备不驱动三极端的化SDA应该是高电平,当从设备接收数据之后,发出回应信号的时候,就会驱动三极管,让SDA变为低电平。所以说:ACK信号是低电平。


对于IIC协议它只能规定怎么传输数据,数据什么含义它完全不能够控制,数据的含义有从设备决定。


第002节_S3C2440的I2C控制器

在嵌入式系统里面的主控芯片一般都会有I2C控制器,要是没有可以根据I2C协议用GPIO管脚模拟,但是非常麻烦,我们要发送数据时,可以把数据放到某个寄存器,它就会自动的发出时钟,并且把数据发送给从设备,同时会等待从设备会返回回应信号。


当我们想发送一个数据的时候,要设置某个寄存器启动传输,它也一样会产生时钟,然后从设备就会把数据通过SDA传到I2C控制器里面,组装进某个寄存器里面,最终寄存器会把接收到的8位数据返回给我们的程序,从这里可以看到I2C控制器简化了I2C的操作。简短电路连接图,如图:

这里写图片描述

这里写图片描述

根据上图,我们首先设置IICCON(来设置时钟),时钟源是PCLK(是50MHZ)太快了我们需要设置这个分频系数,把时钟降低,降低到我们想要的SCL,然后我们要发出start信号,我们需要设置寄存器发出start信号,之后我们需要发出数据啊,我们的程序可以把数据写入到IICDS寄存器,一写入就会自动的发出时钟,并且把这8位数据从SDA发送给从设备,数据发送之后,在第九个时钟会收到回应信号,可以查询IICSTAT是否有ACK(有ACK表示数据发送成功了),可以继续发送数据,等发完数据之后,再来设置IICSTAT让它发出P信号。


在第九个CLK,就会产生一个中断,在中断处理过程中SCL被拉为低电平,谁都不能再使用IIC总线,等待中断处理完成.


怎样处理中断?


写操作: 

若无ACK,出错,然后发出P信号结束, 

:: 若有ACK信号表示上一个字节成功发送出去 

:: 若仍有数据,写入IICDS寄存器,然后清中断,一清中断就会释放SCL信号,继续发出时钟,把数据再次发送出去。 

:: 若没有数据了,发出P信号结束。


读操作: 

读到8位数时,应该回应一个ACK信号。 

:: 还想读数据,清中断,启动传输。等它再次发生中断时,再来读取IICDS寄存器,得到数据。不想读取数据,发出P信号结束。


重点: 发生中断时,我们的IIC控制器会把SCL拉低,阻止任何设备再使用IIC总线,清中断之后才能继续使用,这种机制就给我们中断服务程序的执行提供了时间。


读-写操作


在发送模式: 

:: 1.往寄存器IICDS寄存器放入一个val值。 

:: 2.发完,产生中断,并且会把 SCL拉低。 

:: 3.在中断程序里,判断状态,然后往IICDS里面写入下一个数据,一旦写入下一个数据IIC继续操作,若再次发完,就会再次产生中断。


在接受模式: 

:: 1.我的程序发起传输,接受数据。 

:: 2.接收到数据之后,产生中断,SCL被拉低。 

:: 3.中断程序里,判断数据是否要继续接受等,如果还有继续接受的话,再次设置,设置好之后读IICDS寄存器,一但读出来IIC。 

:: 继续接受下一个数据,收到新数据之后,又会产生一个中断(就是这样循环操作)。


(l)IICCON寄存器(Multi-masterIIC-buscontrol)


IICCON寄存器用于控制是否发出ACK信号、设置发送器的时钟、开启,i2c中断,并标识中断是否发生。它的各位含义如表:

这里写图片描述

使用IICCON寄存器时,有如下注意事项。


1.发送模式的时钟频率由位[6]、位[3:0]联合决定,另外,llCCON[6]=0,IICCON[3:0] 

不能取0或10


2.12c中断在以下3种情况下发生:当发出地址信息或接收到一个从机地址并且吻合时,当总线仲裁失败时,当发送/接收完一个字节的数据(包括响应位)时。


3.基于SDA、SCL线上时间特性的考虑,要发送数据时,先将数据写入IICDS寄存器,然后再清除中断。


4.如果IICCON[5]=0,IICCON14]将不能正常工作。所以,即使不使用12c中断,也要将IICCON[5]设为1。


(2)IICSTAT寄存器(Multi-masterIIC-buscontrol/status)


IICSTAT寄存器用于选择12c接口的工作模式,发出S信号、P信号,使能接收/发送功能,并标识各种状态,比如总线仲裁是否成功、作为从机时是否被寻址、是否接收到0地址、是否接收到ACK信号等。IICSTAT寄存器的各位如表:

这里写图片描述

(3)IICADD寄存器(Multi-masterIlC-busaddress)


用到IICADD寄存器的位[7:11],表示从机地址。IICADD寄存器在串行输出使能位 

IICSTAT[4]为0时,才可以写入:在任何时间都可以读出。IICADD寄存器的各位如表: 

这里写图片描述

(4)IICDS寄存器(Multi-masterIIC-busTx/Rxdatashift)


用到IICDS寄存器的位丨7:0],其中保存的是要发送或己经接收的数据。IICDS寄存器在串行输出使能位IICSTAT()1为1时,叼可以写入;在任何时间都可以读出。IICDS寄存器的各位如表:

这里写图片描述

读写操作流程图


主机发送器模式操作:

这里写图片描述

主机接收器模式操作:

这里写图片描述

第003节_程序框架

写程序之前 考虑好程序的框架,我们想写出一个结构比较好,比较容易扩展的程序


我们先要考虑清楚框架的设计。


IIC控制器的功能

IIC会做什么事情呢?


对于IIC控制器,它负责传输数据,不知道数据的含义,但是它要实现写/读操作


读操作 

这里写图片描述

写操作 

这里写图片描述

IIC设备的功能

很显然,IIC控制器提供了传输数据的能力,至于数据有什么含义,IIC控制器并不知道,数据的含义有外接的IIC芯片决定,我们需要阅读芯片手册,才知道IIC控制器应该发出怎样的数据,


AT24cxx的操作方法 

这里写图片描述 
这里写图片描述

显然我们的程序应该分为两层(IIC设备层,IIC控制器层),框架如下图所示: 

这里写图片描述

我们提供一个统一的接口i2c_transfer,不关使用哪个芯片,他最终都会调用i2c_transfer,来选择某一款I2C控制器,把数据发送出去,或者从I2c设备读到数据,对于每一次传输的数据都可以用一个i2c_msg结构体来表示。但是,读某个地址的数据时,就要用两个i2c_msg结构体来描述它,因为一个i2c_msg结构体只能描述一个传输方向(读/写),我们读取ac24ccxx某个地址上的数据时,要先写出要读取的地址,然后来读取设备地址上的数据。


我们想设计出以一个结构体比较容易扩展的框架,对于I2C控制器我们要抽象出一个结构体i2c_controller,我们构造这个结构体之后,把这个这个结构体,告诉上层(I2C控制器那一层),上层有个管理者i2c_contreller.c文件。


我们在s3c2440_i2c_controller.c这个文件中我们构造出一个i2c_controller结构体,把它放入上层文件中的数组里,以后就根据结构体的名字,把这个结构体取出来使用。


假设我们有一个TI的开发板,在ti_i2c_controller.c文件中,也要构造出一个i2c_controller结构体,同样们也会把这个结构体放入上层的结构体数组(i2c_contreller.c文件中)中,以后根据名字先出来使用。


对于设备层中的at24cxx芯片我们写出at24cxx.c文件在这个文件实现读写函数:


1.at24cxx_write函数


2.at24cxx_read。


函数读写函数都会调用i2c_transfer发起IIC传输,所以我们写程序的时候主要的暂时会涉及到三个文件:


at24cxx.c, s3c2440_i2c_controller.c,i2c_contreller.c。在最上层会写出一个i2c_test.c文件,它会提供菜单供我们选择来测试。


下面我们写一个程序框架,涉及到的文件有:i2c_test.c、at24cxx.c、i2c_controller.c、s3c2440_i2c_controller.c。


i2c_test.c文件


该文件的内容如下:


void i2c_test(void)

{

    /* 初始化: 选择I2C控制器 */


    /* 提供菜单供测试 */

}


这个菜单最终会调用到at24cxx.c里面的函数。


at24cxx.c文件


在里面会使用标准的接口i2c_transfer来启动I2C传输。该文件的内容如下:


int at24cxx_write(unsigned int addr, unsigned char *data, int len)

{

    /* 构造i2c_msg */


    /* 调用i2c_transfer */

}



int at24cxx_read(unsigned int addr, unsigned char *data, int len)

{

    /* 构造i2c_msg */


    /* 调用i2c_transfer */

}


i2c_controller.c文件


该文件的内容如下:


/* 有一个i2c_controller数组用来存放各种不同芯片的操作结构体 */

void register_i2c_controller()

{

}


/* 根据名字来选择某款I2C控制器 */

void select_i2c_controller(char *name)

{

}


/* 实现 i2c_transfer 接口函数 */


int i2c_transfer(i2c_msg msgs, int num)

{


}


select_i2c_controller函数根据名字来选择某款I2C控制器后,以后就会使用被选择的I2C控制器来启动传输。


有数组一定有注册函数register_i2c_controller会把下面实现的I2C控制器结构体i2c_controller放到i2c_controller数组里面。


s3c2440_i2c_controller.c文件


对于具体的芯片,要实现自己的i2c_controller。该文件的内容如下:


/* 实现i2c_controller

          .init

          .master_xfer

          .name

 */


第004节I2C控制器编程框架

我们现在来讲I2C控制器怎么写,它是I2C程序中最核心的地方,我们要先构造几个结构体,这几个结构体放在i2c_controller.h里面。


我们要发出I2c传输时,要构造出i2c_msg,把构造出的i2c_msg扔给下面的i2c_controller.c,i2c_controller.c会选择某一个i2c控制器,使用里面的master_xfer来传输数据, 所以我们需要构造出一个i2c_controller结构体。


i2c_controller.h文件


文件的内容如下所示:


#ifndef _I2C_CONTROLLER_H

#define _I2C_CONTROLLER_H


typedef struct i2c_msg {

    unsigned int addr;  /* 7bits */

    int flags;  /* 0 - write, 1 - read */

    int len;

    int cnt_transferred;

    unsigned char *buf;

}i2c_msg, *p_i2c_msg;


typedef struct i2c_controller {

    int (*int)(void);

    int (*master_xfer)(i2c_msg msgs, int num);

    char *name;

}i2c_controller, *p_i2c_controller;



#endif /* _I2C_CONTROLLER_H */


解析:我们构造这两个结构体,我们要把它放在i2c_controller.c把它用起来,


i2c_controller.c文件


文件的内容如下所示:


include "i2c_controller.h"


#define I2C_CONTROLLER_NUM 10


/* 有一个i2c_controller数组用来存放各种不同芯片的操作结构体 */

static p_i2c_controller p_i2c_controllers[I2C_CONTROLLER_NUM];

static p_i2c_controller p_i2c_con_selected;



void register_i2c_controller(p_i2c_controller *p)

{

    int i;

    for (i = 0; i < I2C_CONTROLLER_NUM; i++)

    {

        if (!p_i2c_controllers[i])

        {

            p_i2c_controllers[i] = p;

            return;

        }

    }

}


解析:register_i2c_controller函数用于把参数中的结构体指针,注册到p_i2c_controllers指针数组中。


/* 根据名字来选择某款I2C控制器 */

int select_i2c_controller(char *name)

{

    int i;

    for (i = 0; i < I2C_CONTROLLER_NUM; i++)

    {

        if (p_i2c_controllers[i] && !strcmp(name, p_i2c_controllers[i]->name))

        {

            p_i2c_con_selected = p_i2c_controllers[i];

            return 0;

        }

    }

    return -1;

}


解析:select_i2c_controller函数根据参数中的名字(name) 从p_i2c_controllers指针数组中取出对应的结构体指针复制给p_i2c_con_selected结构体指针(静态全局变量)。


/* 实现 i2c_transfer 接口函数 */


int i2c_transfer(i2c_msg msgs, int num)

{

    return p_i2c_con_selected->master_xfer(msgs, num);

}


解析:i2c_transfer接口函数,调用选择的p_i2c_con_selected成员中master_xfer函数。


void i2c_init(void)

{

    /* 注册下面的I2C控制器 */

    s3c2440_i2c_con_add();


    /* 选择某款I2C控制器 */


    /* 调用它的init函数 */

}


解析:s3c2440_i2c_con_add()函数,把定义的s3c2440_i2c_con结构体注册到p_i2c_controllers数组中。


s3c2440_i2c_controller.c文件


中断服务函数,当发成中断是,就会调用中断服务函数,代码如下


void i2c_interrupt_func(int irq)

{

    /* 每传输完一个数据将产生一个中断 */


    /* 对于每次传输, 第1个中断是"已经发出了设备地址" */

}


s3c2440_i2c_con_init函数,用来初始化I2C,控制器代码如下:


void s3c2440_i2c_con_init(void)

{

    /* 设置时钟 */

    /* [7] : IIC-bus acknowledge enable bit, 1-enable in rx mode

     * [6] : 时钟源, 0: IICCLK = fPCLK /16; 1: IICCLK = fPCLK /512

     * [5] : 1-enable interrupt

     * [4] : 读出为1时表示中断发生了, 写入0来清除并恢复I2C操作

     * [3:0] : Tx clock = IICCLK/(IICCON[3:0]+1).

     * Tx Clock = 100khz = 50Mhz/16/(IICCON[3:0]+1)

     */

    IICCON = (0<<6) | (1<<5) | (30<<0);


    /* 注册中断处理函数 */

    register_irq(27, i2c_interrupt_func);

}


解析:


1).IICCON = (0<<6) | (1<<5) | (30<<0); 设置IICCON控制寄存器。选择发送时钟,使能中断。


2).register_irq(27, i2c_interrupt_func):注册中断处理函数,当发生I2C中断的时候就会调用i2c_interrupt_func中断处理函数。


初始化完成后,就可以调用do_master_tx写I2C从机了,这个函数仅仅启动I2C传输,然后等待,直到数据在中断服务程序中传输完毕后再返回。函数代码如下:


void do_master_tx(p_i2c_msg msg)

{

    msg->cnt_transferred = 0;


    /* 设置寄存器启动传输 */

    /* 1. 配置为 master tx mode */

推荐阅读

史海拾趣

CANDD公司的发展小趣事

为了进一步提升品牌影响力和市场份额,CANDD公司开始实施国际化战略。公司首先在欧洲市场设立了分公司,并成功打开了欧洲市场的大门。随后,公司又进军亚洲市场,通过与当地企业的合作,逐渐在亚洲市场站稳了脚跟。随着国际化战略的深入实施,CANDD公司的品牌知名度和市场份额不断攀升。

CTS [CTS Corporation]公司的发展小趣事

在追求商业成功的同时,CTS也高度重视可持续发展和社会责任。公司致力于推动绿色生产和环保理念,采用环保材料和节能技术,减少对环境的影响。此外,CTS还积极参与社会公益活动,回馈社会,为社区的繁荣和发展贡献力量。

这些故事框架仅是对CTS Corporation公司可能的发展历程的一种推测和概述,具体的细节和内容需要根据实际情况进行补充和完善。希望这些框架能够为您撰写具体的发展故事提供一些灵感和帮助。

Gemmy Electronics Co Ltd公司的发展小趣事
电子捕鼠器首先通过电源电路将家用交流电(如220V)转换为适合捕鼠器工作的低压直流电(如6V)。
American Technical Ceramics (ATC)公司的发展小趣事
定期清理捕鼠器上的灰尘和污垢,保持其清洁干燥,有助于减少故障发生。
Andersen Laboratories Inc公司的发展小趣事

自1997年成立以来,AnalogicTech便致力于技术创新,不断推动电子行业的发展。公司总部位于硅谷,设计中心则分布在圣塔克拉拉和上海,汇聚了全球顶尖的研发人才。凭借深厚的技术底蕴和创新能力,AnalogicTech成功研发出一系列具有颠覆性的产品,为当今最具创造性的消费品提供了强大的技术支持。

Axon' Cable公司的发展小趣事

Axon' Cable公司自1965年在法国成立起,便致力于通讯连接器的研发与生产。在创业初期,公司面临着资金短缺、技术瓶颈和市场竞争的诸多挑战。然而,Axon' Cable凭借其坚定的信念和不懈的努力,逐步突破了技术难关,并成功开发出了一系列具有竞争力的产品。这些产品在市场上得到了广泛认可,为公司的后续发展奠定了坚实的基础。

问答坊 | AI 解惑

20W的数字功放

只有八个引脚的数字功放,可以达到20W的功率,可惜是单声道的,发上来与大家一同学习…

查看全部问答>

xilinx 和altera两者目前应用情况?

XILINX 给我感觉价格高,应用通信等高端应用。 ALTERA 价格便宜,应用工业和消费电子。…

查看全部问答>

Software-Defined Radio with LabVIEW FPGA

Software-Defined Radio with LabVIEW FPGA…

查看全部问答>

LED晶圆技术发展趋势及LED晶圆制程

replyreload += \',\' + 759475;Timson,如果您要查看本帖隐藏内容请回复…

查看全部问答>

超级终端乱码

[root@(none) /]# ls 1;34mQtopia0m     1;34mdev0m        1;34mhome0m       1;32mlinuxrc0m    1;36mram disk0m    1;32mtestshell0m   ...…

查看全部问答>

c8051F350 定时中断死机?

void Timer0_Init(void){     TH0 = 0x0B;           // Init Timer0 High register   TL0 = 0xDC ;           / ...…

查看全部问答>

对比方案赛+型号或名称相似芯片在webench设计中的对比

本帖最后由 地瓜patch 于 2014-7-29 19:32 编辑 一直一来,对于那些名称类似的芯片一直是以可以相互替换的视角来应用的。 比如TL061CPSR和TL064CNSR两颗芯片。 这两颗芯片在webench中的滤波器设计中能看到他们的身影。 至于两颗芯片的区别有多 ...…

查看全部问答>

CCS单步调试与run的板子运行状况不一样

用单步运行下面的程序,使LED闪烁。直接run却不行,请问大神们怎么解决这个问题? for(;;)    {               GpioDataRegs.GPASET.bit.GPIO6   =1;       &nb ...…

查看全部问答>

关于TTI抽奖活动抽中奖品何时能收到问题?

请问我在TTI 这个抽奖活动 https://www.eeworld.com.cn/huodon ... hibitions_20160218/ 中2月26日抽中了一个三星32G存储卡,那么何时能收到这个奖品呢?到时奖品是按照我论坛中登记的会员地址信息邮寄么?当时抽中了以后,就没有其他任何提示了, ...…

查看全部问答>