一、初始化
理论知识链接:
二、代码实现
1、SDA和SCL设置成开漏输出模式
开漏输出的作用:
- 因为IIC总线是一种双向的通信协议,需要使用开漏输出实现共享总线。
- 开漏输出类似于一种线与的方式,即无论总线上哪个设备下拉了,所有设备都能知道。
- IIC总线通常需要使用上拉电阻来保证总线上的高电平。
以GD32为例,使用普通GPIO模拟IIC,初始化如下:
//PA1 -- SDA
//PA2 -- SCL
#define iic_addr 0x30 //根据从机地址进行修改
#define GPIO_PORT_I2C GPIOA //根据引脚进行修改
#define GPIO_RCC_I2C RCU_GPIOA//根据引脚进行修改
#define GPIO_SCL_I2C GPIO_PIN_2//根据引脚进行修改
#define GPIO_SDA_I2C GPIO_PIN_1//根据引脚进行修改
#define delay_time 2 //2us
//将引脚设置成开漏输出
void iic_init()
{
rcu_periph_clock_enable(GPIO_RCC_I2C);//启动时钟
gpio_mode_set(GPIO_PORT_I2C, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_SCL_I2C | GPIO_SDA_I2C);//默认上拉
gpio_output_options_set(GPIOA, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_SCL_I2C | GPIO_SDA_I2C);//设置开漏输出
i2c_stop();//复位一下
}
2、写启动信号
启动信号表现形式为:
- 静默状态:SCL、SDA为高电平。
- 启动信号:SCL为高电平时,SDA由高电平变成低电平
#define I2C_SCL_H gpio_bit_set(GPIO_PORT_I2C,GPIO_SCL_I2C)//拉高
#define I2C_SCL_L gpio_bit_reset(GPIO_PORT_I2C,GPIO_SCL_I2C)//拉低
#define I2C_SDA_H gpio_bit_set(GPIO_PORT_I2C,GPIO_SDA_I2C)//拉高
#define I2C_SDA_L gpio_bit_reset(GPIO_PORT_I2C,GPIO_SDA_I2C)//拉低
//启动信号
/*
SCL ————————————————————\
\___________
SDA ——————————\
\____________________
*/
void i2c_start()
{
I2C_SDA_H;
I2C_SCL_H;
delay_us(delay_time);
I2C_SDA_L;
delay_us(delay_time);
I2C_SCL_L;
delay_us(delay_time);
}
3、写终止信号
终止信号表现形式为:
- 静默状态:SCL高电平、SDA为低电平。
- 终止信号:SCL为高电平时,SDA由低电平变成高电平
/*
SCL ————————————————————————————————————————
SDA /——————————————————
_________________/
*/
void i2c_stop()
{
I2C_SDA_L;
I2C_SCL_H;
delay_us(delay_time);
I2C_SDA_H;
}
4、发送一个字节数据
void i2c_sendbyte(unsigned char data)
{
unsigned char i;
for(i = 0; i < 8; i++)//8bit = 1byte
{
if(data & 0x80)//取出最高位
{
I2C_SDA_H;
}
else
{
I2C_SDA_L;
}
delay_us(delay_time);
I2C_SCL_H;
delay_us(delay_time);
I2C_SCL_L;
if(i == 7)
{
I2C_SDA_H;//释放总线
}
data <<= 1;
delay_us(delay_time);
}
}
5、接收一个字节数据
#define I2C_SDA_READ gpio_input_bit_get(GPIO_PORT_I2C,GPIO_SDA_I2C)
unsigned char i2c_readbyte()
{
unsigned char i;
unsigned char data = 0x00;
for(i = 0; i < 8; i++)
{
data <<= 1;
I2C_SCL_H;
delay_us(delay_time);
if(I2C_SDA_READ)
{
data |= 0x01;//存入数据
}
I2C_SCL_L;
delay_us(delay_time);
}
return data;
}
6、产生一个ACK信号
- SCL为低电平时,SDA写入低电平
- SCL为高电平时,SDA读取低电平
- 读取完成后将SDA拉高
/*
SCL /————————\
_______/ \________________
SDA /————————————————————————————————————
__________________________/
*/
void i2c_ack()
{
I2C_SDA_L;
delay_us(delay_time);
I2C_SCL_H;
delay_us(delay_time);
I2C_SCL_L;
delay_us(delay_time);
I2C_SDA_H;
}
7、产生一个NACK信号
void i2c_nack()
{
I2C_SDA_H;
delay_us(delay_time);
I2C_SCL_H;
delay_us(delay_time);
I2C_SCL_L;
delay_us(delay_time);
}
8、检测是否从机是否返回ACK
//0:表示ACK 1:表示NACK
unsigned char i2c_read_ack()
{
unsigned char re;
I2C_SDA_H;
delay_us(delay_time);
I2C_SCL_H;
delay_us(delay_time);
if(I2C_SDA_READ) re = 1;
else re = 0;
I2C_SCL_L;
delay_us(delay_time);
return re;
}
9、发送地址和读操作,并读回一个字节数据
unsigned char read_data()
{
i2c_start();//启动信号
i2c_sendbyte(iic_addr | 0x01);//发送地址和读位
if(i2c_read_ack()) return 0;//如果没有读到ACK,终止执行。
unsigned char data = i2c_readbyte();//读取数据
i2c_stop();//发送终止信号
return data;//返回接收的数据
}