读取时 I2C 奇怪的延迟问题
I2C bizarre delay issue when reading
我一直在努力让我的 ATTINY85 成为 bit-bang I2C (read/write)。我有以下配置:
PB0 = SDA
PB1 = LED
PB2 = SCL
我可以毫无问题地写入,但只有当我在读取循环中有我的 'delay()' 函数时,读取才有效,到目前为止一切顺利:
char i2c_read(void)
{
uint8_t B = 0;
DDRB &= 0b11111110; // switch PB0 to input
for ( int bit = 0; bit < 0x08; bit++ )
{
delay(); // <--!!!!!!!!! the root of all evil
SIGNAL_HIGH( PORT_SCL );
B <<= 1;
if( PINB & (1 << PB0 ) )
{
B |= 1;
}
else
{
B |= 0;
}
SIGNAL_LOW( PORT_SCL );
}
DDRB |= 0b00000001; // switch PB0 as output
i2c_nack();
return B;
}
如果我删除 delay(),I2C 将不再工作并且我无法从设备读取(设备没有响应)。似乎合乎逻辑,但我想删除 delay() 的原因是因为它实际上不是 'true' 延迟,它只是打开和关闭位于不同引脚 (PB1) 上的 LED,I2C 线位于 PB0 上和 PB2。
_delay_ms 太慢了,所以我只是打开和关闭 PB1 引脚以产生微小的延迟,这是它工作的唯一方法。这是我的延迟函数的内容,如果我这样保留它,一切都会很好:
void delay()
{
LED_ON();
LED_OFF();
}
void LED_ON( void )
{
PORTB |= 0b00000010; // PB1
}
void LED_OFF( void )
{
PORTB &= 0b11111101; // PB1
}
我怀疑我可能 'nailed' 一个完美的延迟,它产生了其他设备期望的适当信号长度,所以我尝试使用 for 循环和示波器进行相同的延迟:
void delay()
{
for( int i=0; i<20; i++){ }
}
运气不好,I2C 读取停止工作..
然后我决定将 LED 切换到另一个 PIN,让 PB1 完全独立,看看它是否与延迟相关或 pin/circuit 相关:
void delay()
{
LED_ON();
LED_OFF();
}
void LED_ON( void )
{
PORTB |= 0b00001000; // PB3
}
void LED_OFF( void )
{
PORTB &= 0b11110111; // PB3
}
奇怪的是,I2C 又停止工作了!它只有在我输入 PB1 high/low 时才有效。我仍然不明白我是否只是碰巧确定了所需的完美延迟,而恰好是打开 PB1 比打开 PB3 花费的时间更少,或者它与电路本身和 LED 做某种 pull-up/pull-down I2C 上的功能(原谅我的无知,我是初学者),但是 PB1 又一次根本没有连接到 I2C 线路。
谁能解释一下为什么它只在我打开 PB1 on/off 时起作用,而不是真正的延迟?谢谢!
完整来源:
#define PORT_SDA PB0
#define PORT_SCL PB2
#define SIGNAL_HIGH(PORT) PORTB |= ( 1 << PORT )
#define SIGNAL_LOW(PORT) PORTB &= ~( 1 << PORT )
void delay();
void LED_ON(void);
void LED_OFF(void);
void i2c_init(void);
void i2c_start(void);
char i2c_read(void);
void i2c_stop(void);
void i2c_nack(void);
void i2c_ack(void);
void i2c_ack_slave(void);
void i2c_write(uint8_t byte);
void i2c_init()
{
DDRB = 0b00000010; // TODO: should be removed once the weird delay issue is solved
DDRB |= ( 1 << PORT_SDA );
DDRB |= ( 1 << PORT_SCL );
}
void i2c_start( void )
{
SIGNAL_LOW( PORT_SCL );
SIGNAL_HIGH( PORT_SDA );
SIGNAL_HIGH( PORT_SCL );
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_ack(void)
{
SIGNAL_LOW( PORT_SDA );
SIGNAL_HIGH( PORT_SCL );
SIGNAL_LOW( PORT_SCL );
SIGNAL_HIGH( PORT_SDA );
}
void i2c_nack(void)
{
SIGNAL_HIGH( PORT_SDA );
SIGNAL_HIGH( PORT_SCL );
SIGNAL_LOW( PORT_SCL );
}
void i2c_ack_slave(void)
{
SIGNAL_HIGH( PORT_SCL );
SIGNAL_LOW( PORT_SCL );
}
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 );
}
// Clear both lines (needed?)
SIGNAL_LOW( PORT_SCL );
SIGNAL_LOW( PORT_SDA );
i2c_ack();
}
char i2c_read(void)
{
uint8_t B = 0;
DDRB &= 0b11111110; // switch PB0 to input
for ( int bit = 0; bit < 0x08; bit++ )
{
delay(); // <-- the root of all evil
SIGNAL_HIGH( PORT_SCL );
B <<= 1;
if( PINB & (1 << PB0 ) )
{
B |= 1;
}
else
{
B |= 0;
}
SIGNAL_LOW( PORT_SCL );
}
DDRB |= 0b00000001; // switch PB0 as output
i2c_nack();
return B;
}
void delay()
{
LED_ON();
LED_OFF();
}
void LED_ON( void )
{
PORTB |= 0b00000010;
}
void LED_OFF( void )
{
PORTB &= 0b11111101;
}
I2c 定义了一些信号的最小时序 - 这里重要的是 SCL 的高电平和低电平时间 - 在允许下一次转换到相反状态之前 SCL 应该稳定的时间量。
这些时序通常为 ~5µs,具体数字应取自数据sheet。
read
循环末尾的循环大约需要 2 到 3 条指令,具体取决于编译器的作用。 AVR 指令,根据您的时钟速率,大约需要 200ns,因此(没有延迟)SCL 低大约 600ns,给予或接受 - 这太短了,至少对于您的特定 "other-end-device" 显然是这样。
当您在被调用函数中插入一个函数调用和一个端口访问时,您插入了足够多的指令以保持 SCL 低电平足够长的时间以正常工作。
在您的代码中,高电平时间并不是什么大问题,因为您让 AVR 在 SCL 高电平时执行更多指令 - 显然,有足够的时间让您的 SCL 保持高电平足够长的时间。
您在延迟功能中切换端口引脚的事实与此处无关 - 唯一相关的是您需要在 SCL 较低时花费一些时间。显然,您目前所做的是浪费端口引脚,只是花一些时间等待 - 使用 delay_us
,尝试延迟而不是延迟。但是检查 "the other end's" 数据 sheet 以获得所需的确切时间,4-5µs 应该没问题。
为什么你的延迟循环不起作用?它很可能被编译器优化掉了,编译器认识到你没有在那个空循环中做一些相关的事情。
理想情况下,您应该尝试在 SCL 的高电平阶段的中间读取 SDA - 一个 8 位的展开循环和一些 delay_us
散布在其中应该可以完美地工作。
我一直在努力让我的 ATTINY85 成为 bit-bang I2C (read/write)。我有以下配置:
PB0 = SDA
PB1 = LED
PB2 = SCL
我可以毫无问题地写入,但只有当我在读取循环中有我的 'delay()' 函数时,读取才有效,到目前为止一切顺利:
char i2c_read(void)
{
uint8_t B = 0;
DDRB &= 0b11111110; // switch PB0 to input
for ( int bit = 0; bit < 0x08; bit++ )
{
delay(); // <--!!!!!!!!! the root of all evil
SIGNAL_HIGH( PORT_SCL );
B <<= 1;
if( PINB & (1 << PB0 ) )
{
B |= 1;
}
else
{
B |= 0;
}
SIGNAL_LOW( PORT_SCL );
}
DDRB |= 0b00000001; // switch PB0 as output
i2c_nack();
return B;
}
如果我删除 delay(),I2C 将不再工作并且我无法从设备读取(设备没有响应)。似乎合乎逻辑,但我想删除 delay() 的原因是因为它实际上不是 'true' 延迟,它只是打开和关闭位于不同引脚 (PB1) 上的 LED,I2C 线位于 PB0 上和 PB2。
_delay_ms 太慢了,所以我只是打开和关闭 PB1 引脚以产生微小的延迟,这是它工作的唯一方法。这是我的延迟函数的内容,如果我这样保留它,一切都会很好:
void delay()
{
LED_ON();
LED_OFF();
}
void LED_ON( void )
{
PORTB |= 0b00000010; // PB1
}
void LED_OFF( void )
{
PORTB &= 0b11111101; // PB1
}
我怀疑我可能 'nailed' 一个完美的延迟,它产生了其他设备期望的适当信号长度,所以我尝试使用 for 循环和示波器进行相同的延迟:
void delay()
{
for( int i=0; i<20; i++){ }
}
运气不好,I2C 读取停止工作..
然后我决定将 LED 切换到另一个 PIN,让 PB1 完全独立,看看它是否与延迟相关或 pin/circuit 相关:
void delay()
{
LED_ON();
LED_OFF();
}
void LED_ON( void )
{
PORTB |= 0b00001000; // PB3
}
void LED_OFF( void )
{
PORTB &= 0b11110111; // PB3
}
奇怪的是,I2C 又停止工作了!它只有在我输入 PB1 high/low 时才有效。我仍然不明白我是否只是碰巧确定了所需的完美延迟,而恰好是打开 PB1 比打开 PB3 花费的时间更少,或者它与电路本身和 LED 做某种 pull-up/pull-down I2C 上的功能(原谅我的无知,我是初学者),但是 PB1 又一次根本没有连接到 I2C 线路。
谁能解释一下为什么它只在我打开 PB1 on/off 时起作用,而不是真正的延迟?谢谢!
完整来源:
#define PORT_SDA PB0
#define PORT_SCL PB2
#define SIGNAL_HIGH(PORT) PORTB |= ( 1 << PORT )
#define SIGNAL_LOW(PORT) PORTB &= ~( 1 << PORT )
void delay();
void LED_ON(void);
void LED_OFF(void);
void i2c_init(void);
void i2c_start(void);
char i2c_read(void);
void i2c_stop(void);
void i2c_nack(void);
void i2c_ack(void);
void i2c_ack_slave(void);
void i2c_write(uint8_t byte);
void i2c_init()
{
DDRB = 0b00000010; // TODO: should be removed once the weird delay issue is solved
DDRB |= ( 1 << PORT_SDA );
DDRB |= ( 1 << PORT_SCL );
}
void i2c_start( void )
{
SIGNAL_LOW( PORT_SCL );
SIGNAL_HIGH( PORT_SDA );
SIGNAL_HIGH( PORT_SCL );
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_ack(void)
{
SIGNAL_LOW( PORT_SDA );
SIGNAL_HIGH( PORT_SCL );
SIGNAL_LOW( PORT_SCL );
SIGNAL_HIGH( PORT_SDA );
}
void i2c_nack(void)
{
SIGNAL_HIGH( PORT_SDA );
SIGNAL_HIGH( PORT_SCL );
SIGNAL_LOW( PORT_SCL );
}
void i2c_ack_slave(void)
{
SIGNAL_HIGH( PORT_SCL );
SIGNAL_LOW( PORT_SCL );
}
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 );
}
// Clear both lines (needed?)
SIGNAL_LOW( PORT_SCL );
SIGNAL_LOW( PORT_SDA );
i2c_ack();
}
char i2c_read(void)
{
uint8_t B = 0;
DDRB &= 0b11111110; // switch PB0 to input
for ( int bit = 0; bit < 0x08; bit++ )
{
delay(); // <-- the root of all evil
SIGNAL_HIGH( PORT_SCL );
B <<= 1;
if( PINB & (1 << PB0 ) )
{
B |= 1;
}
else
{
B |= 0;
}
SIGNAL_LOW( PORT_SCL );
}
DDRB |= 0b00000001; // switch PB0 as output
i2c_nack();
return B;
}
void delay()
{
LED_ON();
LED_OFF();
}
void LED_ON( void )
{
PORTB |= 0b00000010;
}
void LED_OFF( void )
{
PORTB &= 0b11111101;
}
I2c 定义了一些信号的最小时序 - 这里重要的是 SCL 的高电平和低电平时间 - 在允许下一次转换到相反状态之前 SCL 应该稳定的时间量。 这些时序通常为 ~5µs,具体数字应取自数据sheet。
read
循环末尾的循环大约需要 2 到 3 条指令,具体取决于编译器的作用。 AVR 指令,根据您的时钟速率,大约需要 200ns,因此(没有延迟)SCL 低大约 600ns,给予或接受 - 这太短了,至少对于您的特定 "other-end-device" 显然是这样。
当您在被调用函数中插入一个函数调用和一个端口访问时,您插入了足够多的指令以保持 SCL 低电平足够长的时间以正常工作。
在您的代码中,高电平时间并不是什么大问题,因为您让 AVR 在 SCL 高电平时执行更多指令 - 显然,有足够的时间让您的 SCL 保持高电平足够长的时间。
您在延迟功能中切换端口引脚的事实与此处无关 - 唯一相关的是您需要在 SCL 较低时花费一些时间。显然,您目前所做的是浪费端口引脚,只是花一些时间等待 - 使用 delay_us
,尝试延迟而不是延迟。但是检查 "the other end's" 数据 sheet 以获得所需的确切时间,4-5µs 应该没问题。
为什么你的延迟循环不起作用?它很可能被编译器优化掉了,编译器认识到你没有在那个空循环中做一些相关的事情。
理想情况下,您应该尝试在 SCL 的高电平阶段的中间读取 SDA - 一个 8 位的展开循环和一些 delay_us
散布在其中应该可以完美地工作。