工作用到模拟I2C,以下为在网上查到的一些资料,以及实现方式,由于是从不同的文章中引用过来的,没有标明出处,请谅解。
普通模式:100kHz
快速模式:400kHz
快速模式+:1MHz
高速模式:3.4MHz
超高速模式:5MHz
I2C可以支持mul-master系统,允许有多个master并且每个master都可以与所有的slaves通信。
I2C协议两种消息类型:地址帧/数据帧
开始条件:master设备将SCL置为高电平(总线空闲时,SDA和SCL都处于高电平),然后SDA拉低,所有slave设备就会知道传输即将开始。如果两个master设备在同一时刻都希望获得总线的所有权,那么谁先拉低SDA,谁就获得总线控制权。
地址帧:在一次通信的最开始出现,一个7bit的地址从最高位(MSB)开始发送,地址后面会跟1bit操作符,1代表读操作,0代表写操作。8bit发送完后,接收端的设备获得SDA控制权,在第9个时钟脉冲之前回复ACK(将SDA拉低)以表示接收正常。
数据帧:地址帧发送之后,开始传输数据,master继续产生时钟脉冲,数据由master或slave放到SDA上,每个数据帧8bits,数据帧的数量可以是任意的,直到产生停止条件。每一帧数据传输之后,接收方需要回复一个ACK或NACK。
停止条件:当所有数据发送完成时,master将产生一个停止条件,停止条件定义为:在SDA置于低电平时,将SCL拉高并保持高电平,然后将SDA拉高。
重复开始条件:有时master需要再一次通信中进行多次消息交换,并且期间不希望被其他master干扰,这时可以使用“重复开始条件”—在一次通信中,master可以产生多次start condition,最后阐述一个stop condition结束整个通信过程。为了产生一个重复的开始条件,SDA在SCL低电平时拉高,然后SCL拉高。接着master就可以产生一个开始条件继续新的消息传输。
注意:
- 地址的8位传送完毕后,成功配置地址的slave设备必须发送ACK,否则一定时间之后master视为超时,将放弃数据传送,发送stop。
- 当写数据时,master每发送完8个数据位,slave设备如果还有空间接收下一个字节应回复ACK,slave设备如果没有空间接收更多字节应回复NACK,master当接收到NACK或一定时间之后没收到任何数据视为超时,此时master放弃数据传送,发送stop。
当读数据时,slave设备每发送完8个数据位,如果master希望继续读下一个字节,master回复ACK以提示slave准备下一个数据,如果master不希望读取更多字节,master回复NACK以提示slave设备准备接收stop信号。
#define SCL_H LL_GPIO_SetOutputPin(II2C_Port, II2C_SCL_Pin)
#define SCL_L LL_GPIO_ResetOutputPin(II2C_Port, II2C_SCL_Pin)
#define SDA_H LL_GPIO_SetOutputPin(II2C_Port, II2C_SDA_Pin)
#define SDA_L LL_GPIO_ResetOutputPin(II2C_Port, II2C_SDA_Pin)
#define SCL_read (LL_GPIO_ReadInputPort(II2C_Port) & II2C_SCL_Pin)
#define SDA_read (LL_GPIO_ReadInputPort(II2C_Port) & II2C_SDA_Pin)
#define dTime 5
void SI2C_Init(void)
{
LL_GPIO_InitTypeDef GPIO_InitStruct;
LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOA);
GPIO_InitStruct.Pin = II2C_SDA_Pin | II2C_SCL_Pin;
GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
LL_GPIO_Init(II2C_Port, &GPIO_InitStruct);
}
static void SDA_IN(void)
{
LL_GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = II2C_SDA_Pin;
GPIO_InitStruct.Mode = LL_GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;
LL_GPIO_Init(II2C_Port, &GPIO_InitStruct);
}
static void SDA_OUT(void)
{
LL_GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = II2C_SDA_Pin;
GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
LL_GPIO_Init(II2C_Port, &GPIO_InitStruct);
}
static void Delay_Us(uint8_t nus)
{
uint8_t i = 0;
while(nus--)
{
i = 2; //需要根据实际情况设定i值大小,以得到正确的时钟
while(i--);
}
}
static void i2c_start(void) //开始条件
{
SDA_OUT();
SDA_H;
SCL_H;
Delay_Us(dTime);
SDA_L;
Delay_Us(dTime);
SCL_L;
}
static void i2c_stop(void) //停止条件
{
SDA_OUT();
SCL_L;
SDA_L;
Delay_Us(dTime);
SCL_H;
SDA_H;
Delay_Us(dTime);
}
static bool i2c_rd_ack(void) //发送数据后,等待从设备应答ack,超时返回错误
{
uint8_t ucErrTime;
SDA_H;
SDA_IN();
SCL_H;
Delay_Us(dTime);
while(SDA_read)
{
ucErrTime++;
if(ucErrTime > 250)
{
return false;
}
}
SCL_L;
return true;
}
static bool i2c_sb(uint8_t data) //发送一个字节数据
{
uint8_t cnt;
SDA_OUT();
SCL_L;
for(cnt=0; cnt<8; cnt++)
{
if(data&0x80)
{
SDA_H;
}
else
{
SDA_L;
}
Delay_Us(dTime);
SCL_H;
Delay_Us(dTime);
SCL_L;
data<<=1;
}
return i2c_rd_ack();
}
static uint8_t i2c_rb(void) //读取一个字节数据
{
uint8_t cnt;
uint8_t data = 0;
SDA_IN();
for(cnt=0; cnt<8; cnt++)
{
SCL_L;
Delay_Us(dTime);
data <<= 1;
SCL_H;
if(SDA_read)
{
data |= 0x01;
}
Delay_Us(dTime);
}
return data;
}
static void i2c_wr_ack(bool ACK) //向从设备写ack/nack
{
SCL_L;
SDA_OUT();
if(ACK)
{
SDA_L;
}
else
{
SDA_H;
}
Delay_Us(dTime);
SCL_H;
Delay_Us(dTime);
SCL_L;
}
bool si2c_read_addr8_data(uint8_t addr, uint8_t *wdatabuf, uint8_t len)
{
uint8_t saddr = (addr<<1 |0x01);
bool isack = false;
i2c_start();
isack = i2c_sb(saddr);
if(isack)
{
while(len)
{
*wdatabuf = i2c_rb();
if(len == 1)
{
i2c_wr_ack(false);
}
else
{
i2c_wr_ack(true);
}
wdatabuf++;
len--;
}
i2c_stop();
return true;
}
else
{
i2c_stop();
Delay_Us(dTime);
return false;
}
}