[牛人杂谈] I2C 使用方法

[复制链接]
5347|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就会自动开始下一个状态。

  1. void I2C0_IRQHandler(void)
  2. {

  3.     uint32_t u32Status;

  4.     /* 清除中断标志 */
  5.     I2C0->INTSTS |= I2C_INTSTS_INTSTS_Msk;
  6.                  /*取得STATUS寄存器的值*/
  7.     u32Status = I2C_GET_STATUS(I2C0);
  8.          
  9.                  /*检查中断标志*/
  10.     if (I2C_GET_TIMEOUT_FLAG(I2C0)) {/*发生超时中断*/
  11.         /* 清除超时中断标志 */
  12.         I2C_ClearTimeoutFlag(I2C0);     } else {/*状态改变中断*/
  13.         if (s_I2C0HandlerFn != NULL)             s_I2C0HandlerFn(u32Status);
  14.     }
  15. }

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

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

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

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

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

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

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

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

  69.     /* 使能 IP 时钟 */
  70.     CLK_EnableModuleClock(UART0_MODULE);
  71.     CLK_EnableModuleClock(I2C0_MODULE);

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

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

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

  86.     I2C_Open(I2C0, 100000);

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

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

  91.     /* 使能 I2C0 中断 */
  92.     I2C_EnableInt(I2C0);
  93.     NVIC_EnableIRQ(I2C0_IRQn);
  94. }
  95. /*  Main Function                                                                                    
  96. */ int32_t main (void)
  97. {     uint32_t i;

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

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

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

  109. /*地址0写入3,地址1写入4,然后读回比较*/
  110.     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);
  111.          g_u8DataLen = 0;         g_u8EndFlag = 0;

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

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

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

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

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

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

  125.     return 0;
  126. }
  127.   



 楼主| 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 | 显示全部楼层
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

114

主题

1954

帖子

4

粉丝
快速回复 在线客服 返回列表 返回顶部