[牛人杂谈]

I2C 使用方法

[复制链接]
4285|22
手机看帖
扫描二维码
随时随地手机跟帖
yiy|  楼主 | 2017-4-18 22:22 | 显示全部楼层 |阅读模式
I2c, AC, ck, ip
2C相信大家都不陌生,很多人都用GPIO模拟过I2C。但是模拟的太占CPU资源,并且一般只能模拟Master,模拟slave还是挺困难的。
下面介绍一下I2C IP。
这个是I2CON寄存器,I2C的控制寄存器
1111111111.jpg
从左往右依次为:中断使能位,状态变化指示位,发送START信号,发送STOP信号,回ACK,使能I2C IP
²  I2C_STS:I2C 的状态发生变化该位就会置 1
²  START:请求 I2C IP 发送 START 信号。一旦发送成功,该 I2C就作为 I2CMaster
²  STOP:请求 I2C IP 发送 STOP 信号
²  ACK:如果收到数据的时候该位为 1,I2C IP 将回 ACK 给对方,否则回 NACK
其实从I2C的波形就可以看出,I2C协议完全是状态驱动的,从START信号开始,发送/接收地址字节之后收到ACK/NACK状态,发送/接收数据之后收到ACK/NACK状态,发送STOP
222222222222.jpg

yiy|  楼主 | 2017-4-18 22:24 | 显示全部楼层
这个就是状态寄存器,状态一旦发生改变,就会发生I2C中断,然后读该寄存器就知道发生了何事。
下图就是I2C作为Master和Slave时各个状态的含义表格:
33333333.png
0xF8是总线空闲的状态值,也是STATUS寄存器的缺省值。
下面详细介绍一下上表的各个状态。
Master状态介绍:
1)      发送 START 信号成功,发生 I2C 中断,STATUS 寄存器的值=0x08
2)      作为 I2C Master 没有发送 STOP 又发送 START 信号成功,发生 I2C 中断,STATUS 寄存器的值=0x10
3)      发送地址+W 成功并收到 ACK,发生 I2C 中断,STATUS 寄存器的值=0x18
4)      发送地址+W 成功并收到 NACK,发生 I2C 中断,STATUS 寄存器的值=0x20
5)      发送数据成功并收到 ACK,发生 I2C 中断,STATUS 寄存器的值=0x28
6)      发送数据成功并收到 NACK,发生 I2C 中断,STATUS 寄存器的值=0x30
7)      Master发生仲裁失败,发生 I2C中断,STATUS寄存器的值=0x38
8)      发送地址+R 成功并收到 ACK,发生 I2C 中断,STATUS 寄存器的值=0x40
9)      发送地址+R 成功并收到 NACK,发生 I2C 中断,STATUS 寄存器的值=0x48
10)   收到数据并返回 ACK,发生 I2C 中断,STATUS 寄存器的值=0x50
11)   收到数据并返回 NACK,发生 I2C 中断,STATUS 寄存器的值=0x58
12)   总线错误,发生 I2C 中断,STATUS 寄存器的值=0x00
Slave状态介绍:
1)      收到 RE-START 信号或者 STOP 信号,发生 I2C 中断,STATUS 寄存器的值=0xA0
2)      收到 SLA+R 信号并返回 ACK,发生 I2C 中断,STATUS 寄存器的值=0xA8
3)      作为 Master 仲裁失败 HW 会自动转为 Slave,之后收到 SLA+R 信号,发生 I2C 中断, STATUS 寄存器的值=0xB0
4)      发送数据并收到 ACK,发生 I2C 中断,STATUS 寄存器的值=0xB8
5)      发送数据并收到 NACK,发生 I2C 中断,STATUS 寄存器的值=0xC0
6)      从接发送最后一个数据,但是居然收到的是 ACK,发生 I2C 中断,STATUS 寄存器的值
=0xC8
7)      从接收到 SLA+W 并返回 ACK,发生 I2C 中断,STATUS 寄存器的值=0x60
8)      作为 Master 仲裁失败 HW 会自动转为 Slave,之后收到 SLA+W 信号,发生 I2C 中断, STATUS 寄存器的值=0x68
9)      收到数据并返回 ACK,发生 I2C 中断,STATUS 寄存器的值=0x80
10)   收到数据并返回 NACK,发生 I2C 中断,STATUS 寄存器的值=0x88
11)   广播模式收到 SLA+W 并返回 ACK,发生 I2C 中断,STATUS 寄存器的值=0x70
12)   广播模式仲裁失败,发生 I2C中断,STATUS寄存器的值=0x78

13)   广播模式收到数据并返回 ACK,发生 I2C 中断,STATUS 寄存器的值=0x90

使用特权

评论回复
yiy|  楼主 | 2017-4-18 22:25 | 显示全部楼层
知道所有的状态之后,配置好I2C,然后在中断里面查看STATUS寄存器,处理状态就可以了。超时中断是用户设定时间内没有发生状态改变,就会发生超时中断,这个可以防止软件被hold住。
下面是一个读/写EEPROM的例子。照样是先选择用到的IP的时钟源,使能各个IP的时钟,然后配置多功能引脚,然后是I2C IP功能初始化。另外是I2C 的中断处理函数。中断处理函数中根据 STATUS寄存器的值,进行状态处理。只要调用函数I2C_SET_CONTROL_REG(I2C0,I2C_SI);清除状态改变标志,I2CIP就会自动开始下一个状态。

void I2C0_IRQHandler(void)
{

    uint32_t u32Status;

    /* 清除中断标志 */
    I2C0->INTSTS |= I2C_INTSTS_INTSTS_Msk;
                 /*取得STATUS寄存器的值*/
    u32Status = I2C_GET_STATUS(I2C0);
         
                 /*检查中断标志*/
    if (I2C_GET_TIMEOUT_FLAG(I2C0)) {/*发生超时中断*/
        /* 清除超时中断标志 */
        I2C_ClearTimeoutFlag(I2C0);     } else {/*状态改变中断*/
        if (s_I2C0HandlerFn != NULL)             s_I2C0HandlerFn(u32Status);
    }
}

/*  I2C 接收回调函数                                                                              
*/ void I2C_MasterRx(uint32_t u32Status)
{
    if (u32Status == 0x08) {        /* START 被发送,准备SLA+W */
        I2C_SET_DATA(I2C0, (g_u8DeviceAddr << 1)); /* 写 SLA+W 到数据寄存器 I2CDAT */
        I2C_SET_CONTROL_REG(I2C0, I2C_SI); /*清除状态改变标志,I2C IP开始发送SLA+W*/
    } else if (u32Status == 0x18) {             /* SLA+W 已经发送并且收到ACK */
        I2C_SET_DATA(I2C0, g_au8TxData[g_u8DataLen++]);/*将要发送的数据写到I2CDAT寄存器*/
        I2C_SET_CONTROL_REG(I2C0, I2C_SI); /* 清除状态改变标志, I2C IP开始发送数据 */
    } else if (u32Status == 0x20) {             /* SLA+W 已经被发送并且收到NACK */
        I2C_SET_CONTROL_REG(I2C0, I2C_STA | I2C_STO | I2C_SI); /*发送STOP并重新发送START信号*/
    } else if (u32Status == 0x28) {             /* DATA 已经被发送并且收到ACK */         if (g_u8DataLen != 2) {
            I2C_SET_DATA(I2C0, g_au8TxData[g_u8DataLen++]);/*继续写数据到I2CDAT寄存器*/
            I2C_SET_CONTROL_REG(I2C0, I2C_SI); /*清除状态改变标志,I2C IP开始发送数据*/         } else {
            I2C_SET_CONTROL_REG(I2C0, I2C_STA|I2C_SI);/*清除状态改变标志并再次发送START信号*/         }
    } else if (u32Status == 0x10) {             /* Repeat START 已经被发送,准备SLA+R */
        I2C_SET_DATA(I2C0, (g_u8DeviceAddr << 1) | 0x01);  /* 写 SLA+R 到I2CDAT寄存器 */
        I2C_SET_CONTROL_REG(I2C0, I2C_SI); /*清除状态改变标志,I2C IP开始发送SLA+R*/
    } else if (u32Status == 0x40) {             /* SLA+R 已经发送并且收到ACK信号 */

        I2C_SET_CONTROL_REG(I2C0, I2C_SI); /*清除状态改变标志,I2C IP准备接收数据*/     } else if (u32Status == 0x58) {             /* DATA 收到并且返回NACK */         g_u8RxData = I2C_GET_DATA(I2C0); /*读取数据*/
        I2C_SET_CONTROL_REG(I2C0, I2C_STO | I2C_SI); /*清除状态改变标志并发送STOP信号*/         g_u8EndFlag = 1;
    } else {         /* TO DO */         printf("Status 0x%x is NOT processed\n", u32Status);     }
}

/*  I2C 发送回调函数                                                                              
*/ void I2C_MasterTx(uint32_t u32Status)
{
    if (u32Status == 0x08) {                    /* START 已经发送,准备SLA+W */         I2C_SET_DATA(I2C0, g_u8DeviceAddr << 1);  /* 写 SLA+W 到寄存器 I2CDAT */
        I2C_SET_CONTROL_REG(I2C0, I2C_SI); /*清除状态改变标志,I2C IP开始发送SLA+W */
    } else if (u32Status == 0x18) {             /* SLA+W 已经发送并且收到ACK信号 */
        I2C_SET_DATA(I2C0, g_au8TxData[g_u8DataLen++]);/*将要发送的数据写到I2CDAT寄存器*/
        I2C_SET_CONTROL_REG(I2C0, I2C_SI); /*清除状态改变标志,I2C IP开始发送数据*/
    } else if (u32Status == 0x20) {             /* SLA+W 已经发送但是收到NACK */
        I2C_SET_CONTROL_REG(I2C0, I2C_STA | I2C_STO | I2C_SI); /*发送STOP并重新发送START信号*/
    } else if (u32Status == 0x28) {             /* DATA已经发送并收到ACK */         if (g_u8DataLen != 3) {
            I2C_SET_DATA(I2C0, g_au8TxData[g_u8DataLen++]);/*将要发送的数据写到I2CDAT寄存器*/
            I2C_SET_CONTROL_REG(I2C0, I2C_SI); /*清除状态改变标志,I2C IP开始发送数据*/         } else {
            I2C_SET_CONTROL_REG(I2C0, I2C_STO | I2C_SI); /*清除状态改变标志并发送STOP信号*/             g_u8EndFlag = 1;
        }
    } else {         /* TO DO */         printf("Status 0x%x is NOT processed\n", u32Status);     }
}  void SYS_Init(void)
{
    /* Init System Clock */

    /* 解锁保护寄存器 */
    SYS_UnlockReg();

    /* 使能 12MHz HXT, 32KHz LXT 和 HIRC */
    CLK_EnableXtalRC(CLK_PWRCTL_HXT_EN_Msk | CLK_PWRCTL_LXT_EN_Msk | CLK_PWRCTL_HIRC_EN_Msk);

    /* 等待晶振稳定 */
    CLK_WaitClockReady(CLK_CLKSTATUS_HXT_STB_Msk | CLK_CLKSTATUS_LXT_STB_Msk | CLK_CLKSTATUS_HIRC_STB_Msk);

    /*  使能PLL,并且 HCLK 时钟源切为PLL */
    CLK_SetCoreClock(32000000);

    /* 选择IP 时钟源,I2C时钟源只能是HCLK */
    CLK_SetModuleClock(UART0_MODULE, CLK_CLKSEL1_UART_S_HIRC, CLK_UART_CLK_DIVIDER(1));
    CLK_SetModuleClock(I2C0_MODULE, 0, 0);

    /* 使能 IP 时钟 */
    CLK_EnableModuleClock(UART0_MODULE);
    CLK_EnableModuleClock(I2C0_MODULE);

    /* 重新计算变量PllClock, SystemCoreClock and CycylesPerUs */
    SystemCoreClockUpdate();

    /* Init I/O Multi-function */
    /* 配置 PB 作为 UART0 RXD and TXD  */
    SYS->PB_L_MFP &= ~(SYS_PB_L_MFP_PB0_MFP_Msk | SYS_PB_L_MFP_PB1_MFP_Msk);
    SYS->PB_L_MFP |= (SYS_PB_L_MFP_PB0_MFP_UART0_TX | SYS_PB_L_MFP_PB1_MFP_UART0_RX);

    /* 配置PC0和PC1用作 I2C0 */
    SYS->PC_L_MFP = (SYS_PC_L_MFP_PC0_MFP_I2C0_SCL | SYS_PC_L_MFP_PC1_MFP_I2C0_SDA);  
    /* 重新加锁 */
    SYS_LockReg();
}  
void I2C0_Init(void)
{
    /* 打开 I2C0 并设置波特率为100k */

    I2C_Open(I2C0, 100000);

    /* 打印I2C0 总线时钟 */
    printf("I2C clock %d Hz\n", I2C_GetBusClockFreq(I2C0));

    /* 设置2个 I2C0 从机地址,如果做从机的话,就用这个函数设置从接地址 */
    I2C_SetSlaveAddr(I2C0, 0, 0x15, I2C_GCMODE_DISABLE);   /* Slave Address : 0x15 */     I2C_SetSlaveAddr(I2C0, 1, 0x35, I2C_GCMODE_DISABLE);   /* Slave Address : 0x35 */

    /* 使能 I2C0 中断 */
    I2C_EnableInt(I2C0);
    NVIC_EnableIRQ(I2C0_IRQn);
}
/*  Main Function                                                                                    
*/ int32_t main (void)
{     uint32_t i;

    /* 初始化系统并配置多功能引脚 */
    SYS_Init();

    /* Init UART to 115200-8n1 for print message */
    UART_Open(UART0, 115200);

    /*         该例程设置I2C 总线时钟为100kHz. 然后, 访问 EEPROM 24LC64 进行Byte Write         和Byte Read 操作, 并检查读到的数据是否等于写入的数据.
    */      printf("+-------------------------------------------------------+\n");     printf("|    Nano1x2 Series I2C Sample Code with EEPROM 24LC64  |\n");     printf("+-------------------------------------------------------+\n");  
    /* 初始化 I2C0 访问 EEPROM */
    I2C0_Init();
         
/* EEPROM从机地址为0x50 */     g_u8DeviceAddr = 0x50;
         

/*地址0写入3,地址1写入4,然后读回比较*/
    for (i = 0; i < 2; i++) {         g_au8TxData[0] = (uint8_t)((i & 0xFF00) >> 8);         g_au8TxData[1] = (uint8_t)(i & 0x00FF);         g_au8TxData[2] = (uint8_t)(g_au8TxData[1] + 3);
         g_u8DataLen = 0;         g_u8EndFlag = 0;

        /* 写数据到EEPROM */         s_I2C0HandlerFn = (I2C_FUNC)I2C_MasterTx;

        /* I2C 作为Master发送 START信号 */
        I2C_SET_CONTROL_REG(I2C0, I2C_STA);

        /* 等待 I2C 发送完成 */         while (g_u8EndFlag == 0);         g_u8EndFlag = 0;

        /* 从EEPROM读数据 */         s_I2C0HandlerFn = (I2C_FUNC)I2C_MasterRx;
         g_u8DataLen = 0;         g_u8DeviceAddr = 0x50;
                  
/* I2C 作为Master发送 START信号 */
        I2C_SET_CONTROL_REG(I2C0, I2C_STA);

        /* 等待I2C 接收完成 */         while (g_u8EndFlag == 0);

        /* 比较数据 */
        if (g_u8RxData != g_au8TxData[2]) {             printf("I2C Byte Write/Read Failed, Data 0x%x\n", g_u8RxData);             return -1;
        }     }     printf("I2C Access EEPROM Test OK\n");

    return 0;
}
  



使用特权

评论回复
yiy|  楼主 | 2017-4-18 22:27 | 显示全部楼层
总结上面I2C_MasterRx的流程为:
1)      I2C_SET_CONTROL_REG(I2C0,I2C_STA); 发送 START信号,之后该 I2CIP 就作为 Master
2)      发生 I2C 中断,STATUS=0x08,表示 START 信号发送成功,然后软件写 I2CDAT寄存器,发送 I2C地址+W给 EEPROM
3)      发生 I2C 中断,STATUS=0x18,表示 SLA+W 发送成功并收到 ACK,然后发送高位地址给
EEPROM
4)      发生 I2C 中断,STATUS=0x28,表示高位地址发送成功并收到 ACK,然后发送低位地址给
EEPROM
5)      发生 I2C 中断,STATUS=0x28,表示低位地址发送成功并收到 ACK,然后再次发送 START 信号——RepeatSTART
6)      发生 I2C 中断,STATUS=0x10,表示 Repeat START发送成功,然后发送 I2C地址+R给 EEPROM
7)      发生 I2C 中断,STATUS=0x40,表示 SLA+R 发送成功,并收到 ACK,然后不设 ACK bit,将回 NACK 给 EEPROM
8)      发生 I2C 中断,STATUS=0x58,收到 EEPROM 返回的数据并回 NACK 给 EEPROM
把这个流程对应I2C_MasterRx函数仔细看一下,相信不难懂。发送的流程也类似,在此就不再赘述。

使用特权

评论回复
xixi2017| | 2017-4-18 22:41 | 显示全部楼层
这个协议一直没有学好,保存下来慢慢看。

使用特权

评论回复
玛尼玛尼哄| | 2017-4-19 11:06 | 显示全部楼层
I2C属于那种硬件结构简单,但是软件结构复杂的类型。

使用特权

评论回复
玛尼玛尼哄| | 2017-4-19 14:50 | 显示全部楼层
请求 I2C IP 发送 START 信号。一旦发送成功,该 I2C就作为 I2CMaster
看来这个Master是谁先发开始信号谁就是master

使用特权

评论回复
huangcunxiake| | 2017-4-19 15:37 | 显示全部楼层
两根线的操作当然需要精细的打算,毕竟不用那个按照同样约定的波特率传,这个比232优秀,算法复杂点而已。

使用特权

评论回复
简单happy| | 2017-4-19 20:18 | 显示全部楼层

这个协议一直没有学好,保存下来慢慢看。

使用特权

评论回复
yiy|  楼主 | 2017-4-19 22:16 | 显示全部楼层
我发这个贴绝对的好东西,收藏了很久,认真读过的。

使用特权

评论回复
wahahaheihei| | 2017-4-19 23:34 | 显示全部楼层
用寄存器操作,那就要掌握熟练整个过程。

使用特权

评论回复
zhangwan9301| | 2017-9-6 17:28 | 显示全部楼层
mark

使用特权

评论回复
zhuotuzi| | 2017-9-6 21:01 | 显示全部楼层
时序研究一下就好了。

使用特权

评论回复
wahahaheihei| | 2017-9-6 21:02 | 显示全部楼层
时序图是GPIO 模拟的时候用的多,不过学会了这个,寄存器操作上也方便

使用特权

评论回复
a1024159443| | 2017-9-30 11:19 | 显示全部楼层
为什么那个发送数组里有3个数字?第0个和第1个数据起什么作用?

使用特权

评论回复
wanduzi| | 2017-9-30 20:34 | 显示全部楼层
I2C和CAN是我认为最那个难搞的接口。

使用特权

评论回复
wahahaheihei| | 2017-10-1 14:02 | 显示全部楼层
教程很详细,不知道这个怎么处理中断和同时发送接收的。

使用特权

评论回复
xinpian101| | 2017-10-2 22:49 | 显示全部楼层
虽占用,但是比自带的好用,总之,整个过程在自己控制范围内。

使用特权

评论回复
heisexingqisi| | 2017-10-3 09:57 | 显示全部楼层
第一步,电路不能搞错了,要把电路搞好了,再研究代码

使用特权

评论回复
heisexingqisi| | 2017-10-3 09:57 | 显示全部楼层
不少人查问题,代码找不到毛病,最后就是硬件没弄对。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

yiy

91

主题

1687

帖子

4

粉丝