读取 I2C 传感器值(bit-banging)
Reading I2C sensor value (bit-banging)
我有一个非常简单的用于 ATTINY85 的 I2C bit-banging 库。
#define PORT_SDA PB0
#define PORT_SCL PB2
#define SIGNAL_HIGH(PORT) PORTB |= ( 1 << PORT )
#define SIGNAL_LOW(PORT) PORTB &= ~( 1 << PORT )
void LED_ON(void);
void LED_OFF(void);
void i2c_init(void);
void i2c_start(void);
void i2c_read(void);
void i2c_stop(void);
void i2c_write(uint8_t byte);
void i2c_init()
{
DDRB |= ( 1 << PORT_SDA );
DDRB |= ( 1 << PORT_SCL );
}
void LED_ON( void )
{
PORTB |= 0b00000010;
}
void LED_OFF( void )
{
PORTB &= 0b111111101;
}
void i2c_start( void )
{
SIGNAL_HIGH( PORT_SCL );
SIGNAL_HIGH( PORT_SDA );
SIGNAL_LOW( PORT_SDA );
SIGNAL_LOW( PORT_SCL );
}
void i2c_stop( void )
{
SIGNAL_LOW( PORT_SCL );
SIGNAL_LOW( PORT_SDA );
SIGNAL_HIGH( PORT_SCL );
SIGNAL_HIGH( PORT_SDA );
}
void i2c_write(uint8_t byte)
{
uint8_t bit;
for ( bit = 0; bit < 0x08; bit++ )
{
if( ( byte << bit ) & 0x80 )
SIGNAL_HIGH( PORT_SDA );
else
SIGNAL_LOW( PORT_SDA );
SIGNAL_HIGH( PORT_SCL );
SIGNAL_LOW( PORT_SCL );
}
SIGNAL_HIGH( PORT_SDA );
SIGNAL_HIGH( PORT_SCL );
SIGNAL_LOW( PORT_SCL );
}
我能够毫无问题地成功写入 I2C。我已经用 SSD1306 和 LC2404B 测试了这段代码,如果 VCC 设置为 4.2V,一切都很好,即使是时序。
i2c_init();
i2c_start();
i2c_write( 0xA0 );
i2c_write( 0x01 );
i2c_write( 0x13 );
i2c_stop();
虽然写作工作完美,但我似乎无法使用 ATTINY85 触发我的任何 I2C 模块,以 return 我可以稍后阅读的值。
我连接了树莓派和 GY-521 传感器(因为它 returns 值,即使没有设置内部地址)。我可以通过以下方式检测传感器并使用覆盆子从中读取值:
i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
i2cget -y 1 0x68
0x02
这是示波器上的输出:
我可以看到传感器数据在变化。问题是我似乎无法将相同的请求从 ATTINY85 复制到传感器。传感器根本不响应该值。
我似乎无法理解第一个字节中的最后一位是 ACK 还是 'READ' 指示位,所以我尝试了不同的地址。
i2c_start();
i2c_write( 0b11010001 ); // address: 0xD1
i2c_start();
i2c_write( 0b11010000 ); // address: 0xD0
i2c_start();
i2c_write( 0b10100001 ); // address: 0xA1
i2c_start();
i2c_write( 0b10100000 ); // address: 0xA0
但无论我使用什么地址,传感器根本不响应(在示波器中)并且在我发送地址字节后 SDA 线保持高电平。我还尝试在发送地址后附加另一个 start() 条件,但仍然没有成功。关于我哪里出错的任何提示?我只是想让传感器响应 ATTINY85 读取请求,以便稍后读取值。谢谢!
我建议阅读一些有关 I2C 的教程,以大致了解该协议。例如,参见 https://learn.sparkfun.com/tutorials/i2c。
简而言之,I2C就是一条时钟线和一条数据线的两线多主总线。在任何通信中,无论是读还是写,主机都提供时钟信号。您的 i2c_write() 通过 SCL 转换实现了这一点。
为了读回一个值,您还需要为从机用于输出数据提供时钟。没有时钟,就没有数据。因此,您需要实现一个类似于 i2c 写入的 i2c_read(),它会生成时钟转换,并一次移动一位。
你需要让SDA线作为输入,这样你就可以检测slave是否在发送ACK。您没有任何代码将 SDA 线设置为输入,因此从属设备无法将任何数据发送回给您;您在 SDA 线上设置的值可能会压倒从站试图做的任何事情。要读回数据,您需要创建一个 i2c_read
函数,在 SDA 为输入时摆动 SCL 线。因此,您的 I2C 实现还远未完成。您可以仔细阅读 I2C spec from NXP 或寻找更完整的 bit-banging I2C 实现作为参考。
我有一个非常简单的用于 ATTINY85 的 I2C bit-banging 库。
#define PORT_SDA PB0
#define PORT_SCL PB2
#define SIGNAL_HIGH(PORT) PORTB |= ( 1 << PORT )
#define SIGNAL_LOW(PORT) PORTB &= ~( 1 << PORT )
void LED_ON(void);
void LED_OFF(void);
void i2c_init(void);
void i2c_start(void);
void i2c_read(void);
void i2c_stop(void);
void i2c_write(uint8_t byte);
void i2c_init()
{
DDRB |= ( 1 << PORT_SDA );
DDRB |= ( 1 << PORT_SCL );
}
void LED_ON( void )
{
PORTB |= 0b00000010;
}
void LED_OFF( void )
{
PORTB &= 0b111111101;
}
void i2c_start( void )
{
SIGNAL_HIGH( PORT_SCL );
SIGNAL_HIGH( PORT_SDA );
SIGNAL_LOW( PORT_SDA );
SIGNAL_LOW( PORT_SCL );
}
void i2c_stop( void )
{
SIGNAL_LOW( PORT_SCL );
SIGNAL_LOW( PORT_SDA );
SIGNAL_HIGH( PORT_SCL );
SIGNAL_HIGH( PORT_SDA );
}
void i2c_write(uint8_t byte)
{
uint8_t bit;
for ( bit = 0; bit < 0x08; bit++ )
{
if( ( byte << bit ) & 0x80 )
SIGNAL_HIGH( PORT_SDA );
else
SIGNAL_LOW( PORT_SDA );
SIGNAL_HIGH( PORT_SCL );
SIGNAL_LOW( PORT_SCL );
}
SIGNAL_HIGH( PORT_SDA );
SIGNAL_HIGH( PORT_SCL );
SIGNAL_LOW( PORT_SCL );
}
我能够毫无问题地成功写入 I2C。我已经用 SSD1306 和 LC2404B 测试了这段代码,如果 VCC 设置为 4.2V,一切都很好,即使是时序。
i2c_init();
i2c_start();
i2c_write( 0xA0 );
i2c_write( 0x01 );
i2c_write( 0x13 );
i2c_stop();
虽然写作工作完美,但我似乎无法使用 ATTINY85 触发我的任何 I2C 模块,以 return 我可以稍后阅读的值。
我连接了树莓派和 GY-521 传感器(因为它 returns 值,即使没有设置内部地址)。我可以通过以下方式检测传感器并使用覆盆子从中读取值:
i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
i2cget -y 1 0x68
0x02
这是示波器上的输出:
我可以看到传感器数据在变化。问题是我似乎无法将相同的请求从 ATTINY85 复制到传感器。传感器根本不响应该值。 我似乎无法理解第一个字节中的最后一位是 ACK 还是 'READ' 指示位,所以我尝试了不同的地址。
i2c_start();
i2c_write( 0b11010001 ); // address: 0xD1
i2c_start();
i2c_write( 0b11010000 ); // address: 0xD0
i2c_start();
i2c_write( 0b10100001 ); // address: 0xA1
i2c_start();
i2c_write( 0b10100000 ); // address: 0xA0
但无论我使用什么地址,传感器根本不响应(在示波器中)并且在我发送地址字节后 SDA 线保持高电平。我还尝试在发送地址后附加另一个 start() 条件,但仍然没有成功。关于我哪里出错的任何提示?我只是想让传感器响应 ATTINY85 读取请求,以便稍后读取值。谢谢!
我建议阅读一些有关 I2C 的教程,以大致了解该协议。例如,参见 https://learn.sparkfun.com/tutorials/i2c。
简而言之,I2C就是一条时钟线和一条数据线的两线多主总线。在任何通信中,无论是读还是写,主机都提供时钟信号。您的 i2c_write() 通过 SCL 转换实现了这一点。
为了读回一个值,您还需要为从机用于输出数据提供时钟。没有时钟,就没有数据。因此,您需要实现一个类似于 i2c 写入的 i2c_read(),它会生成时钟转换,并一次移动一位。
你需要让SDA线作为输入,这样你就可以检测slave是否在发送ACK。您没有任何代码将 SDA 线设置为输入,因此从属设备无法将任何数据发送回给您;您在 SDA 线上设置的值可能会压倒从站试图做的任何事情。要读回数据,您需要创建一个 i2c_read
函数,在 SDA 为输入时摆动 SCL 线。因此,您的 I2C 实现还远未完成。您可以仔细阅读 I2C spec from NXP 或寻找更完整的 bit-banging I2C 实现作为参考。