我一直试图让我的ATTINY85进行bit-bang I2C(读/写)。我有以下配置:
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;
}
如果我删除延迟(),I2C不再工作,我无法从设备读取(设备没有响应)。似乎逻辑,但我想删除延迟()的原因是因为它实际上不是'真正的'延迟,它只是打开和关闭不同引脚(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
}
我怀疑我可能“钉”了一个完美的延迟,这会产生另一个设备所需的适当信号长度,所以我尝试使用for循环和示波器进行相同的延迟:
void delay()
{
for( int i=0; i<20; i++){ }
}
没有运气,I2C读取停止工作..
然后我决定将LED切换到另一个PIN并完全离开PB1以查看它是否与延迟相关或引脚/电路相关:
void delay()
{
LED_ON();
LED_OFF();
}
void LED_ON( void )
{
PORTB |= 0b00001000; // PB3
}
void LED_OFF( void )
{
PORTB &= 0b11110111; // PB3
}
奇怪的是,I2C再次停止工作!它只有在我把PB1设为高/低时才有效。我仍然无法理解我是否碰巧确定了所需的完美延迟,而恰好是转动PB1比转动PB3花费更少的时间,或者它与电路本身有关并且LED做了某种拉动 - 在I2C上启动/下拉功能(原谅我的无知,我是初学者),但PB1根本没有连接到I2C线路。
任何人都可以详细说明为什么只有当我打开/关闭PB1而不是真正的延迟时才能正常工作?谢谢!
完整来源:
#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;
}
答案 0 :(得分:1)
I2c定义了许多信号的最小时序 - 这里重要的是SCL的高电平和低电平时间 - SCL在下一次转换到相反状态之前应该保持稳定的时间。 这些时序典型值约为5μs,具体数据应从数据表中获取。
read
循环结束时的循环需要2到3条指令,具体取决于编译器的作用。 AVR指令,取决于你的时钟速率,大约需要200ns,所以(没有延迟)SCL低约600ns,给予或接受 - 这太短了,至少显然对于你的特定“其他终端设备”。
在被调用函数中插入函数调用和端口访问时,插入了足够的指令以使SCL保持低电平足够长的时间以正常工作。
在你的代码中,HIGH时间并不是问题所在,因为你让AVR在SCL为高电平时执行更多指令 - 显然,有足够的时间让你的SCL保持足够长的时间。
您在延迟功能中切换端口引脚的事实与此无关 - 唯一的相关性是您需要在SCL较低时花一些时间。显然,你目前所做的是浪费一个端口引脚只是花一些时间等待 - 使用delay_us
,试验延迟而不是那个。但请查看“另一端”的数据表,了解所需的确切时间,4-5μs应该没问题。
为什么你的延迟循环不起作用?它最有可能被编译器优化掉,它认识到你没有在那个空循环中做一些相关的事情。
理想情况下,您应该尝试在SCL的HIGH阶段的中间读取SDA - 使用8位的非滚动循环和一些delay_us
传播应该可以正常工作。