备注:部分知识参考百问网手册
一、overview 1.1 特性① 兼容标准I2C总线,多主机运行。
② 64种不同的串行时钟频率之一的软件可编程性。
③ 软件可选择的应答位。
④ 中断驱动,逐字节数据传输。
⑤ 仲裁丢失中断与自动模式切换从主到从。
⑥ 启动和停止信号生成/检测。
⑦ 重复启动信号生成。
⑧ 应答位生成和检测。
⑨ 总线忙检测。
支持两种模式:标准模式和快速模式,标准模式下I2C数据传输速率最高是100Kbits/s,在快速模式下数据传输速率最高为400Kbits/s。
两种模式配置上没有区别只是数据传输速率不同来区分。
当相应的PADs被设置为I2C功能后,I2Cn_SCL 和 I2Cn_SDA 的输入需要通过设置 IOMUX里面的SION bit 来使能。
可查看对应寄存器了解:IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO02
I2C 时钟频率 = (PERCLK_ROOT frequency)/(division factor corresponding to IFDR),默认情况下,IPG_CLK_ROOT 和 PERCLK_ROOT被设置为49.5Mhz,选择从PLL2 的PFD2作为根时钟源,时钟路径计算如下(时钟路径可参考CCM章节):
PLL2 = 528 MHz
PLL2_PFD2 = 528 MHz * 18 / 24 = 396 MHz
IPG_CLK_ROOT = (PLL2_PFD2 / ahb_podf )/ ipg_podf = (396 MHz/4)/2 = 49.5MHz
PER_CLK_ROOT = IPG_CLK_ROOT/perclk_podf = 49.5 MHz/1 = 49.5 MHz
要设置I2C的波特率为100K, I2C分频值 = 49500000/100000 = 495,参考I2Cx_IFDR寄存器对应的表格可知 该寄存器设为0x37比较接近。
二、I2C 寄存器 2.1 I2C Address Register (I2Cx_IADR)| 位域 | 名 | 读写 | 描述 |
|---|---|---|---|
| [15:8] | reserved | R | 总是为0 |
| [7:1] | ADR | R/W | 作为I2C从设备时的地址;使用软件复位时,这个寄存器不受影响 |
| [0] | reserved | R | 总是为0 |
| 位域 | 名 | 读写 | 描述 |
|---|---|---|---|
| [15:6] | reserved | R | 总是为0 |
| [5:0] | IC | R/W | I2C clock rate,用来设置SCL时钟,IC值对应的分频系数要查表。在数据传输过程中,IC值不能改变;但是在发出S信号之前,可以改变IC值。 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BRV8Ghl2-1632816362805)(E:u-bootpici2c_ic_taible.jpg)]
2.3 I2C Control Register (I2Cx_I2CR)| 位域 | 名 | 读写 | 描述 |
|---|---|---|---|
| [15:8] | reserved | R | 总是0 |
| [7] | IEN | R/W | I2C使能,0:I2C控制器被禁止,但是还可以访问它的寄存器;1:I2C控制器使能,要想让本寄存器中其他位起效,此位必须先置1 |
| [6] | IIEN | R/W | I2C中断使能,0:I2C中断禁止,但是中断状态位(I2C_I2SR[IIF])还是可以使用的;1:I2C中断使能,发生中断时,中断状态位(I2C_I2SR[IIF])也会被设置 |
| [5] | MSTA | R/W | 主从模式选择,0:从设备模式,MSTA从1变0时,会发出STOP信号,并变为从设备模式;1:主机模式,MSTA从0变1时,会发即STAT信号,并变为主机模式。注意1:I2C主设备失去总线时,硬件会清除此位,但是不会发出STOP信号注意2:要修改此位时,要先提供I2C控制器时钟注意3:软件清除此位时,会发出STOP信号;如果失去总线,硬件会清除此位 |
| [4] | MTX | R/W | 发送/接收模式,0:接收模式,1:发送模式。作为主机时,应该根据数据传输的方向设置MTX位,当然,发送I2C设备地址时MTX总是1。 |
| [3] | TXAK | R/W | 发送响应使能,当I2C设备处于接收状态时,此位才有效,0:在第9个时钟,发送响应信号,即把SDA拉低;1:在第9个时钟,不发送响应信号 |
| [2] | RSTA | R/W | Repeat start,读该位时总得到0,写:0:不发送repeat start信号;1:发送repeat start信号 |
| [1:0] | reserved | R | 总是0 |
| 位域 | 名 | 读写 | 描述 |
|---|---|---|---|
| [15:8] | reserved | R | 总是0 |
| [7] | ICF | R/W | Data transferring bit,数据正在传输标志,0:正在传输;1:传输结束,当第9个时钟发出后,硬件置位 |
| [6] | IAAS | R/W | I2C address as a slave bit,I2C控制器作用从设备时,通过此位来判断是否有人来寻址它:0:没人找我;1:有别的I2C主机在找我了 |
| [5] | IBB | R/W | I2C bus busy bit,用来显示总线状态,0:总线空闲,发现STOP信号时,IBB就被清0;1:总线忙,发现START信号时,IBB就被置1 |
| [4] | IAL | R/W | Arbitration lost,此位由硬件设置,软件要写入0来清零,0:没有丢失总线,1:总线丢失了 |
| [2] | SRW | R/W | Slave read/write,作为从设备时,主机发送设备地址时会带有读写位,读写位的内容就写入SRW中,0:slave receive,主设备要写数据;1:slave transmit,主设备要读数据 |
| [1] | IIF | I2C中断状态,写0清中断,0:没有I2C中断发生;1:I2C中断发生了,如果IIEN=1则会产生中断,有这些中断:一个字节传输完毕; 作为从设备,接收到了一个吻合的地址; 总线丢失。 | |
| [0] | RXAK | Received acknowledge,是否接收到回应信号,0:收到了:在一个字节传完后第9个时钟周期,收到了回应信号;1:没收到 |
| 位域 | 名 | 读写 | 描述 |
|---|---|---|---|
| [15:8] | reserved | R | 总是0 |
| [7:0] | DATA | R/W | Data Byte。对于接收:存有接收到的最新数据,软件把数据读出来;对于发送:存放下一个即将要发送的数据,这是软件写进去的。 |
对于user application 根据业务需求来进行编写即可。
void i2c_init(I2C_REGISTERS *I2C_base)
{
I2C_base->I2CR &= ~(1 << 7);
I2C_base->IFDR = 0x37;
I2C_base->I2CR |= (1<<7);
}
3.2.2 i2c_check
uint8_t i2c_check(I2C_REGISTERS *I2C_base, uint32_t status)
{
if(status & (1<<4))
{
I2C_base->I2SR &= ~(1<<4);
I2C_base->I2CR &= ~(1 << 7);
I2C_base->I2CR |= (1 << 7);
return I2C_ARBITRATIONLOST;
}
else if(status & (1 << 0))
{
return I2C_NAK;
}
return I2C_OK;
}
3.3 i2c_start
uint8_t i2c_start(I2C_REGISTERS *I2C_base, uint8_t ucSlaveAddr, uint32_t ulOpcode)
{
if(I2C_base->I2SR & (1 << 5))
return 1;
I2C_base->I2CR |= (1 << 5) | (1 << 4);
//设置数据寄存器I2DR: bit[7:0] : 要发送的数据, START信号后第一个数据是从设备地址
I2C_base->I2DR = ((uint32_t)ucSlaveAddr << 1) | ((I2C_READ == ulOpcode)? 1 : 0);
return 0;
}
3.4 i2c_stop
uint8_t i2c_stop(I2C_REGISTERS *I2C_base)
{
uint16_t usTimeout = 0xffff;
I2C_base->I2CR &= ~((1 << 5) | (1 << 4) | (1 << 3));
while((I2C_base->I2SR & (1 << 5)))
{
usTimeout--;
if(usTimeout == 0)
return I2C_TIMEOUT;
}
return I2C_OK;
}
3.5 i2c_restart
uint8_t i2c_restart(I2C_REGISTERS *I2C_base, uint8_t ucSlaveAddr, uint32_t ulOpcode)
{
if(I2C_base->I2SR & (1 << 5) && (((I2C_base->I2CR) & (1 << 5)) == 0))
return 6;
//设置控制寄存器I2CR: bit[4]: 1 发送(transmit) bit[2]: 1 产生重新开始信号(Repeat start)
I2C_base->I2CR |= (1 << 4) | (1 << 2);
//设置数据寄存器I2DR: bit[7:0] : 要发送的数据, START信号后第一个数据是从设备地址
I2C_base->I2DR = ((uint32_t)ucSlaveAddr << 1) | ((I2C_READ == ulOpcode)? 1 : 0);
return 0;
}
3.6 i2c_read
void i2c_read(I2C_REGISTERS *I2C_base, uint8_t *pbuf, uint32_t len)
{
volatile uint8_t dummy = 0;
dummy++;
while(!(I2C_base->I2SR & (1 << 7)));
I2C_base->I2SR &= ~(1 << 1);
I2C_base->I2CR &= ~((1 << 4) | (1 << 3));
if(len == 1)
I2C_base->I2CR |= (1 << 3);
dummy = I2C_base->I2DR;
while(len--)
{
while(!(I2C_base->I2SR & (1 << 1)));
I2C_base->I2SR &= ~(1 << 1);
if(len == 0)
{
i2c_stop(I2C_base);
}
if(len == 1)
{
I2C_base->I2CR |= (1 << 3);
}
*pbuf++ = I2C_base->I2DR;
}
}
3.7 i2c_write
void i2c_write(I2C_REGISTERS *I2C_base, const uint8_t *pbuf, uint32_t len)
{
while(!(I2C_base->I2SR & (1 << 7)));
I2C_base->I2SR &= ~(1 << 1);
I2C_base->I2CR |= 1 << 4;
while(len--)
{
I2C_base->I2DR = *pbuf++;
while(!(I2C_base->I2SR & (1 << 1)));
I2C_base->I2SR &= ~(1 << 1);
if(i2c_check(I2C_base, I2C_base->I2SR))
break;
}
I2C_base->I2SR &= ~(1 << 1);
i2c_stop(I2C_base);
}
3.8 i2c_transfer
uint8_t i2c_transfer(I2C_REGISTERS *I2C_base, I2C_TRANSFER *transfer)
{
uint32_t ulRet = 0;
uint32_t ulOpcode = transfer->ulOpcode;
//开始前准备工作,清除标志位*bit-4 IAL 仲裁位,bit-1 IIF 中断标志位
I2C_base->I2SR &= ~((1 << 1) | (1 << 4));
while(!((I2C_base->I2SR >> 7) & 0X1)){};
//如果要读某个寄存区,寄存器地址要先"写"给从设备 所以方向要"先写","后读"
if ((transfer->ulSubAddressLen > 0) && (transfer->ulOpcode == I2C_READ))
{
ulOpcode = I2C_WRITE;
}
ulRet = i2c_start(I2C_base, transfer->ucSlaveAddress, ulOpcode);
if (ulRet)
{
return ulRet;
}
while(!(I2C_base->I2SR & (1 << 1))){};
ulRet = i2c_check(I2C_base, I2C_base->I2SR);
if (ulRet)
{
i2c_stop(I2C_base);
return ulRet;
}
if (transfer->ulSubAddressLen)
{
do
{
I2C_base->I2SR &= ~(1 << 1);
transfer->ulSubAddressLen--;
I2C_base->I2DR = ((transfer->ulSubAddress) >> (8 * transfer->ulSubAddressLen));
while(!(I2C_base->I2SR & (1 << 1)));
ulRet = i2c_check(I2C_base, I2C_base->I2SR);
if(ulRet)
{
i2c_stop(I2C_base);
return ulRet;
}
}
while ((transfer->ulSubAddressLen > 0) && (ulRet == I2C_OK));
if (I2C_READ == transfer->ulOpcode)
{
I2C_base->I2SR &= ~(1 << 1);
i2c_restart(I2C_base, transfer->ucSlaveAddress, I2C_READ);
while(!(I2C_base->I2SR & (1 << 1))){};
ulRet = i2c_check(I2C_base, I2C_base->I2SR);
if(ulRet)
{
ulRet = I2C_ADDRNAK;
i2c_stop(I2C_base);
return ulRet;
}
}
}
if ((I2C_WRITE == transfer->ulOpcode) && (transfer->ulLenth > 0))
{
i2c_write(I2C_base, transfer->pbuf, transfer->ulLenth);
}
if ((I2C_READ == transfer->ulOpcode) && (transfer->ulLenth > 0))
{
i2c_read(I2C_base, transfer->pbuf, transfer->ulLenth);
}
return 0;
}
3.3 挂载的从机驱动程序如eeprom
1.初始化I2C 控制器对于的引脚
2.调用I2C 控制器驱动接口去读写eeprom 里面的寄存器的内容



