1.基本理解
iic通信协议:双线制串行通信协议,由时钟线SCL和数据线SDA构成.
通信方式:主从模式,主设备发起通信,从设备响应通信
2.通信的基本步骤
a.主设备发送一个开始信号,表示开始通信,即启动I2C
条件:SCL=1,SDA出现下降沿
IIC启动之后,SCL=1时,SDA的电平不允许有变化
SCL=0时,数据发送方才能在SDA上改变发送电平
b.主设备发送一个从设备的地址和(读写位) 一般地址的长度是7bit,最后一个bit是读写指令,
1是读(主机接收从机数据),0是写(主机发送数据到从机)
C.仲裁机制和应答,从机只有收到自己的地址信息才会被唤醒,这个过程是以一个低电平的SDA脉冲应答,
主机检测这个应答位,0是 1不是
d.数据传输 确认目标后,主设备发送或者接收数据,数据传输在每个时钟周期的上升沿或者下降沿进行
e.停止:SCL = 1,SDA出现上升沿
注意:I2C外部需根据传输速率匹配上拉电阻,速率越高,上拉电阻越小,否则会影响时序;
I2C引脚作为输出时需是开漏输出,作为输入时需是浮空输入,不能匹配内部上拉或下拉电阻;
3.软件模拟
1.结构体定义总线的SDA和SCL两个引脚:用于控制两个引脚的输入输出
typedef struct{uint32_t SDA_RCC_APB2Periph;//SDA脚的时钟GPIO_TypeDef* SDA_Port;//SDA脚Portuint16_t SDA_Pin;//SDA脚Pinuint32_t SCL_RCC_APB2Periph;//SCL脚的时钟GPIO_TypeDef* SCL_Port;//SCL脚Portuint16_t SCL_Pin;//SCL脚Pin}sw_i2c_gpio_t;
2.宏定义
#define I2C_USE_7BIT_ADDR //如果使用的从机地址是7Bit模式,则打开这个宏,否则注释掉这个宏#define I2C_DELAY 50 // I2C每个Bit之间的延时时间,延时越小I2C的速率越高#define SW_I2C_SCL_LOW GPIO_ResetBits(gpio->SCL_Port,gpio->SCL_Pin) // I2C SCL脚输出0 地电平#define SW_I2C_SCL_HIGH GPIO_SetBits(gpio->SCL_Port,gpio->SCL_Pin) // I2C SCL脚输出1 高电平#define SW_I2C_SDA_LOW GPIO_ResetBits(gpio->SDA_Port,gpio->SDA_Pin) // I2C SDA脚输出0 低电平#define SW_I2C_SDA_HIGH GPIO_SetBits(gpio->SDA_Port,gpio->SDA_Pin) // I2C SDA脚输出1 高电平#define SW_I2C_SDA_INPUT sw_i2c_set_sda_input(gpio) // 将SDA脚方向设置为输入 #define SW_I2C_SDA_OUTPUT sw_i2c_set_sda_output(gpio) // 将SDA脚方向设置为输出#define SW_I2C_SDA_STATUS sw_i2c_sda_status(gpio) // 获取SDA脚输入电平状态 #define i2c_delay_us(a) SystemDelayUs(a) // 延时
3,其他准备函数
SDA输入输出及状态/**************************************************************************
*** 读取SDA脚的状态 ***
***************************************************************************/
static uint8_t sw_i2c_sda_status(sw_i2c_gpio_t *gpio)
{uint8_t sda_status;sda_status = GPIO_ReadInputDataBit(gpio->SDA_Port,gpio->SDA_Pin); return sda_status?1:0;
}
/**************************************************************************
*** 设置SDA脚为输入 ***
***************************************************************************/
static void sw_i2c_set_sda_input(sw_i2c_gpio_t *gpio)
{GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Pin = gpio->SDA_Pin; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入模式GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init (gpio->SDA_Port, & GPIO_InitStructure );
}
/**************************************************************************
*** 设置SDA脚为输出 ***
***************************************************************************/
static void sw_i2c_set_sda_output(sw_i2c_gpio_t *gpio)
{GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Pin = gpio->SDA_Pin; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; //开漏输出模式GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init (gpio->SDA_Port, & GPIO_InitStructure );
}I2C启动static void sw_i2c_start(sw_i2c_gpio_t *gpio)
{// I2C 开始时序:SCL=1时,SDA由1变成0.SW_I2C_SDA_HIGH; i2c_delay_us(I2C_DELAY);SW_I2C_SCL_HIGH; i2c_delay_us(I2C_DELAY);SW_I2C_SDA_LOW;i2c_delay_us(I2C_DELAY);SW_I2C_SCL_LOW;i2c_delay_us(I2C_DELAY);
}
I2C停止
static void sw_i2c_stop(sw_i2c_gpio_t *gpio)
{// I2C 开始时序:SCL=1时,SDA由0变成1.SW_I2C_SDA_LOW;i2c_delay_us(I2C_DELAY);SW_I2C_SCL_HIGH;i2c_delay_us(I2C_DELAY);SW_I2C_SDA_HIGH;
}等待数据接收方反馈
static uint8_t sw_i2c_wait_ack(sw_i2c_gpio_t *gpio)
{uint8_t sda_status;uint8_t wait_time=0;uint8_t ack_nack = 1;//先设置SDA脚为输入SW_I2C_SDA_INPUT;//等待SDA脚被从机拉低while(SW_I2C_SDA_STATUS){wait_time++;//如果等待时间过长,则退出等待if (wait_time>=200){ack_nack = 0;break;}}// SCL由0变为1,读入ACK状态// 如果此时SDA=0,则是ACK// 如果此时SDA=1,则是NACKi2c_delay_us(I2C_DELAY);SW_I2C_SCL_HIGH;i2c_delay_us(I2C_DELAY);//再次将SCL=0,并且将SDA脚设置为输出SW_I2C_SCL_LOW;i2c_delay_us(I2C_DELAY);SW_I2C_SDA_OUTPUT;i2c_delay_us(I2C_DELAY);return ack_nack;
}发送ACK给数据发送方 应答信号
static void sw_i2c_send_ack(sw_i2c_gpio_t *gpio)
{// 发送ACK就是在SDA=0时,SCL由0变成1SW_I2C_SDA_LOW;i2c_delay_us(I2C_DELAY);SW_I2C_SCL_HIGH;i2c_delay_us(I2C_DELAY);SW_I2C_SCL_LOW;i2c_delay_us(I2C_DELAY);
}发送NACK给数据发生方 非应答信号,表示接受没有成功
static void sw_i2c_send_nack(sw_i2c_gpio_t *gpio)
{// 发送NACK就是在SDA=1时,SCL由0变成1SW_I2C_SDA_HIGH;i2c_delay_us(I2C_DELAY);SW_I2C_SCL_HIGH;i2c_delay_us(I2C_DELAY);SW_I2C_SCL_LOW;i2c_delay_us(I2C_DELAY);
}
4.主设备接受和发送字节
static void sw_i2c_write_byte(sw_i2c_gpio_t *gpio,uint8_t aByte)
{uint8_t i;for (i=0;i<8;i++){//先将SCL拉低;SW_I2C_SCL_LOW;i2c_delay_us(I2C_DELAY);//然后在SDA输出数据if(aByte&0x80){SW_I2C_SDA_HIGH;}else{SW_I2C_SDA_LOW;}i2c_delay_us(I2C_DELAY);//最后将SCL拉高,在SCL上升沿写入数据SW_I2C_SCL_HIGH;i2c_delay_us(I2C_DELAY);aByte = aByte<<1;//数据位移}//写完一个字节只后要将SCL拉低SW_I2C_SCL_LOW;i2c_delay_us(I2C_DELAY);
}主设备从从设备读取字节
static uint8_t sw_i2c_read_byte(sw_i2c_gpio_t *gpio)
{uint8_t i,aByte;//先将SDA脚设置为输入SW_I2C_SDA_INPUT;for (i=0;i<8;i++){//数据位移aByte = aByte << 1;//延时等待SDA数据稳定i2c_delay_us(I2C_DELAY);//SCL=1,锁定SDA数据SW_I2C_SCL_HIGH;i2c_delay_us(I2C_DELAY);//读取SDA状态if(SW_I2C_SDA_STATUS){aByte |= 0x01;}//SCL=0,解除锁定SW_I2C_SCL_LOW;}//读完一个字节,将SDA重新设置为输出SW_I2C_SDA_OUTPUT;return aByte;
}