1、IIC通讯需要两条线就可以,SCL、SDA。
2、IIC的数据传输的速率,不同的ic是不同的,根据电平维持的延时函数的时间来确定IIC数据传输的速率.
3、IIC的延时函数可以使用延时函数,延时函数一般使用系统滴答时钟产生延时,也是在Sysclk频率总线的基础上产生的延时。这个延时和“__NOP();”指令产生的延时是一样的,“__NOP();”也是依靠Sysclk频率产生延时。使用场景:“__NOP();”指令是一个汇编指令的运行产生延时,是占用cpu的,短时间且精确的延时是可以使用的;较长时间的精准的延时还是需要使用系统滴答时钟的定时器实现延时的。
4、标准的IIC传输节拍信号是由7种的:起始信号、停止信号、产生ACK应答信号、产生NACK应答信号、等待ACK应答信号、接收1byte字节信号、发送1byte字节信号。
5、在标准IIC信号中分为两种形式:边沿信号,上升沿或者下降沿(起始信号、停止信号)。电平信号,高电平或者低电平(产生ACK应答信号、产生NACK应答信号、等待ACK应答信号、接收1byte字节信号、发送1byte字节信号)。
6、上面的两类信号,也就是7种信号中,SDA的信号必须在SCL为高电平的时候有效。
7、在上面的7种基础信号的基础上,根据不同的芯片封装不同的数据发送和接收的函数,下面将简单介绍一般的数据发送和接收协议形式,大部分ic芯片都是相同的。
ic数据的发送:
(1)发送起始位。
(2)发送写控制字节,写控制字节的最后一位表示“写”,其他的位表示IC的id。
(3)等待IC的ACK回应。
(4)发送地址字节。
(5)等待IC的ACK回应。
(6)发送写入的数据字节。
(7)等待IC的ACK回应。
(8)如果单字节写入,只能写入一次,Pag页的写入,5,6可以进行多次。
(9)最后给IC发送停止位。
ic数据的接收:
(1)发送起始位。
(2)发送写控制字节,写控制字节的最后一位表示“写”,其他的位表示IC的id。
(3)等待IC的ACK回应。
(4)写入地址高字节(如果是16位地址数据)。
(5)等待IC的ACK回应。
(6)写入地址低字节
(7)等待IC的ACK回应。
(8)发送起始位。
(9)发送读控制字节,读控制字节的最后一位表示“读”,其他的位表示IC的id。
(10)等待IC的ACK回应。
(11)接收数据。
(12)数据没有接收完毕,继续接收,发送ACK回应信号。
(13)接收数据。
(14)数据接收完毕,发送NACK回应信号。
(15)发送停止位。
8、停止信号,最后保持SCL为高电平;其他信号,函数结束的最后一定要保持SCL为低电平。
static void i2c_sda_in(void)
{
GPIO_InitTypeDef gpio_cfg;
__HAL_RCC_GPIOB_CLK_ENABLE();
gpio_cfg.Pin = bus_i2c->sda_pin;
gpio_cfg.Mode = GPIO_MODE_INPUT;
// gpio_cfg.Pull = GPIO_PULLUP;
HAL_GPIO_Init((GPIO_TypeDef*)bus_i2c->sda_port, &gpio_cfg);
}
static void i2c_sda_out(void)
{
GPIO_InitTypeDef gpio_cfg;
__HAL_RCC_GPIOB_CLK_ENABLE();
gpio_cfg.Pin = bus_i2c->sda_pin;
gpio_cfg.Mode = GPIO_MODE_OUTPUT_OD;
gpio_cfg.Pull = GPIO_PULLUP;
gpio_cfg.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init((GPIO_TypeDef*)bus_i2c->sda_port, &gpio_cfg);
}
static void i2c_scl_out(void)
{
GPIO_InitTypeDef gpio_cfg;
__HAL_RCC_GPIOB_CLK_ENABLE();
gpio_cfg.Pin = bus_i2c->scl_pin;
gpio_cfg.Mode = GPIO_MODE_OUTPUT_OD;
gpio_cfg.Pull = GPIO_PULLUP;
gpio_cfg.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init((GPIO_TypeDef*)bus_i2c->scl_port, &gpio_cfg);
}
static void i2c_sda_write(unsigned char value)
{
HAL_GPIO_WritePin((GPIO_TypeDef*)bus_i2c->sda_port, bus_i2c->sda_pin, value?GPIO_PIN_SET:GPIO_PIN_RESET);
}
static void i2c_scl_write(unsigned char value)
{
HAL_GPIO_WritePin((GPIO_TypeDef*)bus_i2c->scl_port, bus_i2c->scl_pin, value?GPIO_PIN_SET:GPIO_PIN_RESET);
}
static unsigned int i2c_sda_read(void)
{
return HAL_GPIO_ReadPin((GPIO_TypeDef*)bus_i2c->sda_port, bus_i2c->sda_pin);
}
刚开始的时候SDA和SCL引脚信号应该都是高电平,起始信号之后要保持SCL为低电平。
在SCL高电平的时候,SDA产生下降沿。
(1)SAD拉高+延时函数
(2)SCL拉高+延时函数
(3)SDA拉低+延时函数
(4)SCL拉低+延时函数
static void i2c_start(void)
{
i2c_sda_out();
i2c_sda_write(1);
i2c_delay();
i2c_scl_write(1);
i2c_delay();
i2c_sda_write(0);
i2c_delay();
i2c_scl_write(0);
i2c_delay();
}
结束的时候SCL的电平一定是低电平,但是SDA的电平是不确定的,所以应该先把SDA电平拉低。
在SCL为高电平的时候,SDA产生上升沿。
(1)SDA拉低+延时函数
(2)SCL拉高+延时函数
(3)SDA拉高+延时函数
static void i2c_stop(void)
{
i2c_sda_out();
i2c_sda_write(0);
i2c_delay();
i2c_scl_write(1);
i2c_delay();
i2c_sda_write(1);
i2c_delay();
}
延时函数就使用汇编指令“__NOP()”。具体知识参考其他文章。
static void i2c_delay(void)
{
__NOP();
}
SCL电平一定是低电平,SDA电平未知。SCL为高电平的时候,SDA为低电平,为ACK应答信号。但是这个信号必须在数据接受完之后发送才有效。
(1)SDA拉低+延时函数
(2)SCL拉高+延时函数
(3)SCL拉低+延时函数
static void i2c_write_ack(void)
{
i2c_sda_out();
i2c_sda_write(0);
i2c_delay();
i2c_scl_write(1);
i2c_delay();
i2c_scl_write(0);
i2c_delay();
}
SCL电平一定为低电平,SDA的状态确定。在SCL为高电平的时候,SDA为高电平。这个信号也是只有在读取完数据之后发送才可以。
(1)SDA拉高+延时函数
(2)SCL拉高+延时函数
(3)SCL拉低+延时函数
static void i2c_write_nack(void)
{
i2c_sda_out();
i2c_sda_write(1);
i2c_delay();
i2c_scl_write(1);
i2c_delay();
i2c_scl_write(0);
i2c_delay();
}
SCL一定为高电平,SDA电平不确定。在SCL为高电平的时候,读取SDA电平,当读取到SDA为低电平的时候,就说明接收到了ACK信号。
(1)SCL拉高+延时函数
(2)读取SDA电平+延时函数
(3)SCL拉低+延时函数
static unsigned char i2c_read_ack(void)
{
unsigned char level = 0;
i2c_sda_in();
i2c_scl_write(1);
i2c_delay();
if(i2c_sda_read())
level = 1;
i2c_scl_write(0);
i2c_delay();
return level;
}
只有在SCL为高电平的时候SDA电平才有效,在SCL为高电平的时候,必须保持SDA电平稳定,所以SCL电平变化之前,SDA应该先变化。
(1)SDA电平变化+延时函数(根绝写入数据位设置电平)
(2)SCL拉高+延时函数
(3)SCL拉低+延时函数
static void i2c_write_byte(unsigned short data)
{
int i;
unsigned char temp = (unsigned char)(data & 0xFF);
i2c_sda_out();
for(i=0;i<8;i++)
{
if(temp & 0x80)
i2c_sda_write(1);
else
i2c_sda_write(0);
temp <<= 1;
i2c_delay();
i2c_scl_write(1);
i2c_delay();
i2c_scl_write(0);
i2c_delay();
}
}
数据接收和数据发送是一样的,SDA在SCL为高电平的时候有效,所以SCL为高电平的时候读取SDA引脚的电平状态。
(1)SCL拉高+延时函数
(2)读取SDA电平+延时函数
(3)SCL拉低+延时函数
static unsigned char i2c_read_byte(void)
{
int i;
unsigned char temp = 0;
i2c_sda_in();
for(i=0;i<8;i++)
{
i2c_scl_write(1);
i2c_delay();
temp <<= 1;
if(i2c_sda_read())
temp |= 0x01;
i2c_scl_write(0);
i2c_delay();
}
return temp;
}
下面是针对IC的一般情况的数据写入和读出的操作流程。
下图所示的就是IIC对IC芯片的数据写入的基本逻辑。
(1)写入启动。
(2)等待ACK响应。
(3)写入“ic写控制字节”。
(4)等待ACK响应。
(5)写入寄存器地址。
(6)等待ACK响应。(如果没等到就写入stop位并返回)
(7)写入要写入的数据(可以使用循环写入多个byte)。
(8)每次写入数据都需要等待ACK响应。
(9)写入stop位。