在调通STM32F0硬件I2C之前用的是IO模拟I2C,就顺便理解一下I2C时序!
首先是宏定义,定义了I2C SCL和SDA的引脚,以及SDA输入输出、SCL输入,还有SDA、SCL的拉高拉低,最后就是延时。
#define I2C_SDA_PIN 22 //PB11 data
#define I2C_SCL_PIN 21 //PB10 clock
#define SDA_OUT sys_pin_mode(I2C_SDA_PIN, PIN_MODE_OUTPUT)
#define SDA_IN sys_pin_mode(I2C_SDA_PIN, PIN_MODE_INPUT_PULLUP)
#define SCL_OUT sys_pin_mode(I2C_SCL_PIN, PIN_MODE_OUTPUT)
#define SDA_L sys_pin_write(I2C_SDA_PIN, PIN_LOW)
#define SDA_H sys_pin_write(I2C_SDA_PIN, PIN_HIGH)
#define SCL_L sys_pin_write(I2C_SCL_PIN, PIN_LOW)
#define SCL_H sys_pin_write(I2C_SCL_PIN, PIN_HIGH)
#define sda_read() i2c_sda_read()
#define i2c_delay() i2c_delay_us(25)
使用I2C之前,需要配置I2C引脚的时钟、输出类型等,还需要将SDA、SCL都拉高。
/*
***************************************************************************************************
* Function : i2_init
* Arguments :
* Description: iic初始化
* Returns :
***************************************************************************************************
*/
static int i2c_init(void)
{
SCL_OUT; //时钟线配置为输出
SDA_OUT; //数据线配置为输出
SCL_H; //时钟线拉高
SDA_H; //数据线拉高
return 0;
}
接下来学习I2C的四个重要信号,分别是起始信号、结束信号、应答信号、非应答信号。
1.起始信号:SCL在高电平期间,SDA出现下降沿。
/*
***************************************************************************************************
* Function : i2c_start
* Arguments :
* Description: 时钟线高电平,数据线下降沿 为起始信号
* Returns :
***************************************************************************************************
*/
void i2c_start(void)
{
SCL_H; //时钟线拉高
i2c_delay();
SDA_H; //数据线拉高
i2c_delay();
SDA_L; //数据线拉低(产生下降沿)
i2c_delay();
SCL_L; //时钟线拉低(为传输数据做准备)
i2c_delay();
}
2.结束信号:SCL在高电平期间,SDA出现上升沿。
/*
***************************************************************************************************
* Function : i2c_stop
* Arguments :
* Description: 时钟线高电平,数据线上升沿 为停止信号
* Returns :
***************************************************************************************************
*/
void i2c_stop(void)
{
//uint16_t i;
SDA_L; //数据线拉低
i2c_delay();
SCL_H; //时钟线拉高
i2c_delay();
SDA_H; //数据线拉高(产生上升沿)
//for (i = 0; i < 0x01ff; i++) { //停止后延长一段时间 可去掉
i2c_delay();
//}
}
3.应答信号:SCL在高电平期间,SDA的电平 若为低电平,则为应答信号。
/*
***************************************************************************************************
* Function : i2c_ack
* Arguments :
* Description: 主机向从机发送ACK
* Returns :
***************************************************************************************************
*/
void i2c_ack(void)
{
SDA_L; //数据线拉低
i2c_delay();
SCL_H; //时钟线拉高
i2c_delay();
SCL_L; //时钟线拉低
i2c_delay();
}
/*
***************************************************************************************************
* Function : i2c_get_ack
* Arguments :
* Description: 主机从从机获取ACK
* Returns :
***************************************************************************************************
*/
static int i2c_get_ack(void)
{
int ack;
SDA_IN; //数据线配置为输入模式,为接收ACK做准备
SCL_H; //时钟线拉高
i2c_delay();
if (sda_read()) { //读数据线引脚
ack = 1; //读到高电平
} else {
ack = 0; //读到低电平
}
SCL_L; //时钟线拉低
i2c_delay();
SDA_OUT; //数据配置回输出模式
return ack;
}
4.非应答信号: SCL在高电平期间,SDA的电平 若为高电平,则为非应答信号。
/*
***************************************************************************************************
* Function : i2c_nak
* Arguments :
* Description: 主机向从机发送NACK
* Returns :
***************************************************************************************************
*/
void i2c_nak(void)
{
SDA_H; //数据线拉高
i2c_delay();
SCL_H; //时钟线拉高
i2c_delay();
SCL_L; //时钟线拉低
i2c_delay();
}
接下来介绍I2C读写数据位的有效性规定:
(1) IIC时钟线为高电平期间,数据线必须稳定;
(2) 只有在时钟线为低电平期间,数据线上的高电平或低电平才允许变化。
具体代码如下:
1.主机向从机写一个字节,该字节可以是从机地址、寄存器地址或者寄存器的值。
/*
***************************************************************************************************
* Function : i2c_write_byte
* Arguments :
* Description: IIC写一个字节
* Returns :
***************************************************************************************************
*/
int i2c_write_byte(unsigned char byte)
{
int i;
for (i = 0; i < 8; i++) {
if (byte & 0x80) { //取最高位
SDA_H; //若为1,则数据线拉高
} else {
SDA_L; //若为0,则数据线拉低
}
i2c_delay();
SCL_H; //时钟线拉高,此时需要保证数据稳定
i2c_delay();
SCL_L; //时钟线拉低,为传下一个bit做准备 若为最后一个bit,则为允许从机改变数据线电平(ACK)
i2c_delay();
byte <<= 1;
}
i2c_delay();
return i2c_get_ack(); //等待应答
}
2.主机读取从机发送的数据。
/*
***************************************************************************************************
* Function : i2c_read_byte
* Arguments :
* Description: IIC读取一个字节
* Returns :
***************************************************************************************************
*/
int i2c_read_byte(unsigned char *ch)
{
int i;
unsigned char byte = 0;
SDA_IN; //数据线配置为输入模式,为接收数据做准备
for (i = 0; i < 8; i++) {
SCL_H; //时钟线拉高
i2c_delay();
byte <<= 1; //从最高位接收,所以接收一位左移一位
if (sda_read()) { //读数据线引脚
byte |= 0x01; //若为高电平,置1
} else {
byte &= 0xFE; //若为低电平,置0
}
SCL_L; //时钟线拉低
i2c_delay();
}
*ch = byte;
SDA_OUT; //数据线配置回输出模式
return 0;
}