I2C的使用讲解

1、I2C
本文以韦东山视频为基础,作如下笔记。
I2C硬件上的接法:
如下图所示,主控芯片引出两条线SCL,SDA线,在一条I2C总线上可以接很多I2C设备。数据可以从主设备传到从设备上,从设备也能传数据到主设备上,即双向传输。
I2C的使用讲解_第1张图片
读写操作:
刚开始主芯片要发出一个start信号,然后发出一个设备地址(用来确定是往哪一个芯片写数据),读/写(0表示写,1表示读)。
回应(用来确定这个设备是否存在),然后就可以传输数据,传输数据之后,要有一个回应信号(确定数据是否接受完成),然后再传输下一个数据。
每传输一个数据,接受方都会有一个回应信号,数据发送完之后,主芯片就会发送一个停止信号。如下图:白色为主设备到从设备传输,灰色为从设备传输到主设备。
在这里插入图片描述
传输是以8位为单元数据传输的,先传输最高位(MSB),主芯片发出start信号之后,然后发出9个时钟传输数据。
(1)开始信号(S):SCL为高电平时,SDA高电平向低电平跳变,开始传送数据。
(2)结束信号(P):SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。
(3)响应信号(ACK):接收器在接收到8位数据后,在第9个时钟周期,拉低SDA
SDA上传输的数据必须在SCL为高电平期间保持稳定,SDA上的数据只能在SCL为低电平期间变化。如图
I2C的使用讲解_第2张图片
双向传输原理:
SDA上一根线实现双向传输,还不互相影响各自的发送?
主芯片通过一根SDA线既可以把数据发给从设备,也可以从SDA上读取数据,连接SDA线的引脚里面有两个引脚(发送引脚/接受引脚),使用开极(极电集开发出去作为输出)电路选择功能,如下图
I2C的使用讲解_第3张图片

当某一个芯片不想影响SDA线时,那就不驱动这个三极管。
想输出高电平时(读);都不驱动(高电平就由上拉电阻决定)。
想输出低电平(写),就驱动三极管。

对于IIC协议它只能规定怎么传输数据,数据什么含义它完全不能够控制,数据的含义有从设备决定。
S3C2440的I2C控制器:
在嵌入式系统里面的主控芯片一般都会有I2C控制器,要是没有可以根据I2C协议用GPIO管脚模拟,但是非常麻烦,我们要发送数据时,可以把数据放到某个寄存器,它就会自动的发出时钟,并且把数据发送给从设备,同时会等待从设备会返回回应信号。
当我们想发送一个数据的时候,要设置某个寄存器启动传输,它也一样会产生时钟,然后从设备就会把数据通过SDA传到I2C控制器里面,组装进某个寄存器里面,最终寄存器会把接收到的8位数据返回给我们的程序,从这里可以看到I2C控制器简化了I2C的操作。简短电路连接图,如图:
I2C的使用讲解_第4张图片
I2C的使用讲解_第5张图片
根据上图,我们首先设置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中断,并标识中断是否发生。它的各位含义如表:
I2C的使用讲解_第6张图片
使用IICCON寄存器时,有如下注意事项。
1.发送模式的时钟频率由位[6]、位[3:0]联合决定,另外,llCCON[6]=0,IICCON[3:0]
不能取0或10
2.I2c中断在以下3种情况下发生:当发出地址信息或接收到一个从机地址并且吻合时,当总线仲裁失败时,当发送/接收完一个字节的数据(包括响应位)时。

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

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

(2)IICSTAT寄存器(Multi-masterIIC-buscontrol/status)
IICSTAT寄存器用于选择i2c接口的工作模式,发出S信号、P信号,使能接收/发送功能,并标识各种状态,比如总线仲裁是否成功、作为从机时是否被寻址、是否接收到0地址、是否接收到ACK信号等。IICSTAT寄存器的各位如表:
I2C的使用讲解_第7张图片
(3)IICADD寄存器(Multi-masterIlC-busaddress)
用到IICADD寄存器的位[7:11],表示从机地址。IICADD寄存器在串行输出使能位IICSTAT[4]为0时,才可以写入:在任何时间都可以读出。IICADD寄存器的各位如表:
I2C的使用讲解_第8张图片

(4)IICDS寄存器(Multi-masterIIC-busTx/Rxdatashift)
用到IICDS寄存器的位丨7:0],其中保存的是要发送或己经接收的数据。IICDS寄存器在串行输出使能位IICSTAT()1为1时,就可以写入;在任何时间都可以读出。IICDS寄存器的各位如表:
I2C的使用讲解_第9张图片
读写操作流程图

主机发送器模式操作:
I2C的使用讲解_第10张图片
主机接收器模式操作:
I2C的使用讲解_第11张图片

编程:

/*  FILE: i2c.c
 * 用于主机发送/接收
 */
#include 
#include "s3c24xx.h"
#include "i2c.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;       // 禁止内部上拉
    GPECON |= 0xa0000000;   // 选择引脚功能:GPE15:IICSDA, GPE14:IICSCL

    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)
}

/* 主机发送
 * slvAddr : 从机地址,buf : 数据存放的缓冲区,len : 数据长度 
 */
void i2c_write(unsigned int slvAddr, unsigned char *buf, int len)
{
    g_tS3C24xx_I2C.Mode = WRDATA;   // 写操作
    g_tS3C24xx_I2C.Pt   = 0;        // 索引值初始为0
    g_tS3C24xx_I2C.pData = buf;     // 保存缓冲区地址
    g_tS3C24xx_I2C.DataCount = len; // 传输长度
    
    IICDS   = slvAddr;
    IICSTAT = 0xf0;         // 主机发送,启动
    
    /* 等待直至数据传输完毕 */    
    while (g_tS3C24xx_I2C.DataCount != -1);
}
        
/*
 * 主机接收
 * slvAddr : 从机地址,buf : 数据存放的缓冲区,len : 数据长度 
 */
void i2c_read(unsigned int slvAddr, unsigned char *buf, int len)
{
    g_tS3C24xx_I2C.Mode = RDDATA;   // 读操作
    g_tS3C24xx_I2C.Pt   = -1;       // 索引值初始化为-1,表示第1个中断时不接收数据(地址中断)
    g_tS3C24xx_I2C.pData = buf;     // 保存缓冲区地址
    g_tS3C24xx_I2C.DataCount = len; // 传输长度
    
    IICDS        = slvAddr;
    IICSTAT      = 0xb0;    // 主机接收,启动
    
    /* 等待直至数据传输完毕 */    
    while (g_tS3C24xx_I2C.DataCount != -1);
}

/*
 * I2C中断服务程序
 * 根据剩余的数据长度选择继续传输或者结束
 */
void I2CIntHandle(void)
{
    unsigned int iicSt,i;

    // 清中断
    SRCPND = BIT_IIC;
    INTPND = BIT_IIC;
    
    iicSt  = IICSTAT; 

    if(iicSt & 0x8){ printf("Bus arbitration failed\n\r"); }

    switch (g_tS3C24xx_I2C.Mode)
    {    
        case WRDATA:
        {
            if((g_tS3C24xx_I2C.DataCount--) == 0)
            {
                // 下面两行用来恢复I2C操作,发出P信号
                IICSTAT = 0xd0;
                IICCON  = 0xaf;
                Delay(10000);  // 等待一段时间以便P信号已经发出
                break;    
            }

      IICDS = g_tS3C24xx_I2C.pData[g_tS3C24xx_I2C.Pt++];
            
            // 将数据写入IICDS后,需要一段时间才能出现在SDA线上
            for (i = 0; i < 10; i++);   

            IICCON = 0xaf;      // 恢复I2C传输
            break;
        }

        case RDDATA:
        {
            if (g_tS3C24xx_I2C.Pt == -1)
            {
                // 这次中断是发送I2C设备地址后发生的,没有数据
                // 只接收一个数据时,不要发出ACK信号
                g_tS3C24xx_I2C.Pt = 0;
                if(g_tS3C24xx_I2C.DataCount == 1)
                   IICCON = 0x2f;   // 恢复I2C传输,开始接收数据,接收到数据时不发出ACK
                else 
                   IICCON = 0xaf;   // 恢复I2C传输,开始接收数据
                break;
            }
            
            if ((g_tS3C24xx_I2C.DataCount--) == 0)
            {
                g_tS3C24xx_I2C.pData[g_tS3C24xx_I2C.Pt++] = IICDS;

                // 下面两行恢复I2C操作,发出P信号
                IICSTAT = 0x90;
                IICCON  = 0xaf;
                Delay(10000);  // 等待一段时间以便P信号已经发出
                break;    
            }      
           
           g_tS3C24xx_I2C.pData[g_tS3C24xx_I2C.Pt++] = IICDS;

           // 接收最后一个数据时,不要发出ACK信号
           if(g_tS3C24xx_I2C.DataCount == 0)
               IICCON = 0x2f;   // 恢复I2C传输,接收到下一数据时无ACK
           else 
               IICCON = 0xaf;   // 恢复I2C传输,接收到下一数据时发出ACK
           break;
        }
       
        default:
            break;      
    }
}

/*
 * 延时函数
 */
void Delay(int time)
{
    for (; time > 0; time--);
}


调用i2c设置m41t11实时时钟:
/*
 * FILE: m41t11.c
 * 调用I2C读写函数,设置、读取RTC芯片m41t11
 */
#include 
#include "m41t11.h"
#include "i2c.h"

struct rtc_registers {
    unsigned char   secs;
    unsigned char   mins;
    unsigned char   hours;
    unsigned char   wday;
    unsigned char   mday;
    unsigned char   mon;
    unsigned char   year;
    unsigned char   cs;
};

#define BCD_TO_BIN(val) (((val)&15) + ((val)>>4)*10)
#define BIN_TO_BCD(val) ((((val)/10)<<4) + (val)%10)                     


static unsigned long epoch = 2000;  /* 芯片中”年”为0x00时,对应2000年 */

static const unsigned char days_in_mo[] = 
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

/*
 * 写m41t11,设置日期与时间
 */
int m41t11_set_datetime(struct rtc_time *dt)
{
    unsigned char leap_yr;
    struct {
        unsigned char addr;
        struct rtc_registers rtc;
    } __attribute__ ((packed)) addr_and_regs;

    memset(&addr_and_regs, 0, sizeof(addr_and_regs));

    leap_yr = ((!(dt->tm_year % 4) && (dt->tm_year % 100))
            || !(dt->tm_year % 400));

    if ((dt->tm_mon < 1) || (dt->tm_mon > 12) || (dt->tm_mday == 0)) {
        return -1;
    }

    if (dt->tm_mday > (days_in_mo[dt->tm_mon] + ((dt->tm_mon == 2)
                && leap_yr))) {
        return -2;
    }

    if ((dt->tm_hour >= 24) || (dt->tm_min >= 60) || (dt->tm_sec >= 60)) {
        return -3;
    }

    if ((dt->tm_year -= epoch) > 255) {
        /* They are unsigned */
        return -4;
    }

    if (dt->tm_year >= 100) {
        dt->tm_year -= 100;
    }

    addr_and_regs.rtc.secs  = BIN_TO_BCD(dt->tm_sec);
    addr_and_regs.rtc.mins  = BIN_TO_BCD(dt->tm_min);
    addr_and_regs.rtc.hours = BIN_TO_BCD(dt->tm_hour);
    addr_and_regs.rtc.mday  = BIN_TO_BCD(dt->tm_mday);
    addr_and_regs.rtc.mon   = BIN_TO_BCD(dt->tm_mon);
    addr_and_regs.rtc.year  = BIN_TO_BCD(dt->tm_year);
    addr_and_regs.rtc.wday  = BIN_TO_BCD(dt->tm_wday);
    addr_and_regs.rtc.cs    = 0;

    i2c_write(0xD0, (unsigned char *)&addr_and_regs, sizeof(addr_and_regs));

    return 0;
}

/*
 * 读取m41t11,获得日期与时间
 */
int m41t11_get_datetime(struct rtc_time *dt)
{
    unsigned char addr[1] = { 0 };
    struct rtc_registers rtc;

    memset(&rtc, 0, sizeof(rtc));

    i2c_write(0xD0, addr, 1);
    i2c_read(0xD0, (unsigned char *)&rtc, sizeof(rtc));

    dt->tm_year = epoch;
    rtc.secs  &= 0x7f;   /* seconds */
    rtc.mins  &= 0x7f;   /* minutes */
    rtc.hours &= 0x3f;   /* hours */
    rtc.wday  &= 0x07;   /* day-of-week */
    rtc.mday  &= 0x3f;   /* day-of-month */
    rtc.mon   &= 0x1f;   /* month */
    rtc.year  &= 0xff;   /* year */

    dt->tm_sec     = BCD_TO_BIN(rtc.secs);
    dt->tm_min     = BCD_TO_BIN(rtc.mins);
    dt->tm_hour    = BCD_TO_BIN(rtc.hours);
    dt->tm_wday    = BCD_TO_BIN(rtc.wday);
    dt->tm_mday    = BCD_TO_BIN(rtc.mday);
    dt->tm_mon     = BCD_TO_BIN(rtc.mon);
    dt->tm_year    += BCD_TO_BIN(rtc.year);

    return 0;
}

你可能感兴趣的:(裸机开发)