ATMega328P 和 ESP8266ex 之间的 I2c 通信只能发送 8 个字节,错误?

I2c communication between ATMega328P and ESP8266ex can only send 8 bytes, bug?

本文关键字:字节 错误 之间 ESP8266ex I2c 通信 ATMega328P      更新时间:2023-10-16

我正在开发一个 I2C 桥接器,它使我能够以"智能"的方式虚拟扩展 ESP8266ex 上的可用引脚。ESP8266ex 不错,但可用引脚较少。

看起来扩展引脚已集成到 ESP8266ex 本身中。实际上,这并不是因为网桥在后台通过i2c进行通信以访问ATMega328P上的引脚,但是我可以使用标准方法和功能,例如pinModedigitalRead/writeanalogRead/Write。正因为如此,ESP8266ex 可以完全控制 ATMega328P 上的引脚。


I2C MASTER/SLAVE CONFIG            GND
o 
------------------------            |                       ------------------ 
| MASTER         GND o |------------|---------------------- | o GND    SLAVE |
|                   D2 |           <- i2c bus ->            | A4             |
| ESP8266(ex)    SDA o |--------------------|-------------- | o SDA      A E |
|                SCL o |------------|-----/ | /------------ | o SCL      T G |
|                   D1 |            |       |               | A5         M A |
|                +5V o |-|         [ ]     [ ]          --- | o +5V          |
------------------------ |    4.7K [ ]     [ ] 4.7K     |   -----------------
|         [ ]     [ ]          |
|          |       |           |
|----------|-------|-----------|  
D2 = GPIO 4 (SDA)                   |
D1 = GPIO 5 (SCL)                   o +5V

例如,要修改 ATMega328P 上的引脚,我可以执行以下操作(当然会重新映射引脚):


pinMode( D22, OUTPUT );     // Sets pin D13 = ONBOARD_LED on ATMega328P 
digitalWrite( D22, HIGH );  // Turns the onboard LED ON on ATMega328P
delay(2000);                // Wait two seconds
digitalWrite( D22, LOW );   // Turns the onboard LED OFF on ATMega328P

这是非常有效的,非常直接的功能和直接的结果,但是,我也将内部EEPROM与外部EEPROM扩展/链接,使尺寸"加倍"。第一个 1K 属于 ESP8266ex,接下来的 1K 属于 ATMega328P。


我为此开发了一个函数链,并产生了一堆易于使用的函数:

bool setStorage( uint16_t iAddress, uint8_t* buffer, <parameters> );
bool setStorage( uint16_t iAddress, char* buffer, <parameters> );
bool getStorage( uint16_t iAddress, char* buffer, <parameters> );
.....
char* getStorage( uint16_t iAddress, char* psReturnDefault ); // Returns pointer buffered char array
....

例如,我可以做:

setStorage( 2000UL, "Hello world" ); // Set 11 chars in EEPROM on ATMega328P on pos 977
delay(1000);
// Read it back
Serial.println( "String is: " );
Serial.println( (char*)getStorage( 2000UL, "" ) );

问题

我验证了正确写入的数据并正确读取,但是当从站(ATMega328P)发送超过8个字节(Wire.write())时,主站(ESP8266ex)仅读取一堆0xFF(使用Wire.read())。因此,I2C通信之间存在问题。

检查/验证了所有内容,buffersize(32 字节,对于此示例来说就足够了),检查缓冲区的内容,一切都很好。 试图立即发送缓冲,似乎没有任何帮助。

这是 Wire 库中的错误吗?是否有可用的解决方法?



我的MASTER库的一部分(无法全部发布,对于StackOverflow来说太大了),让你知道我在做什么:

......
#define IPMB_HDR_DATASIZE         0x03
#define IPMB_MAX_DATACOUNT        (BUFFER_LENGTH-IPMB_HDR_DATASIZE)
......
typedef struct rIpmbRequestDataStruc
{
uint8_t cmd;                          // Request command, take a look at IPMB_CMD_* above       
uint8_t version;                      // Software version of request, must match
uint8_t dataType;
uint8_t data[ IPMB_MAX_DATACOUNT ];   // Data/parameters to be send
}; 

.........

bool i2cBridgeRequest( uint8_t iCmd,             // Request command
uint8_t*  puResult,       // Pointer to result var
uint16_t  iParam1,        // First parameter
uint16_t  iParam2 = 0,    // Second parameter, data or length  
uint8_t*  pParam3 = 0     // Byte data when stream or string
)
{
bool     bSuccess   = i2cBridgeAvailable();
uint8_t  iErrorCode = 0;
uint8_t  iDataType  = 0;
uint16_t iBytes     = 0;

if( bSuccess )
{

rIpmbRequestDataStruc dataStruc;
memset( (uint8_t*)&dataStruc, 0, sizeof( dataStruc ));
dataStruc.cmd      = iCmd;
dataStruc.version  = IPMB_DSI_VERSION;
dataStruc.dataType = IPMB_DAT_TYPE_UINT16;
uint16_t  i         = 0;
uint16_t  iMax      = IPMB_MAX_DATACOUNT+IPMB_HDR_DATASIZE;
uint8_t*  pParam    = 0;
if( iCmd == IPMB_CMD_EEPROMREAD || iCmd == IPMB_CMD_DIGITALWRITE
|| iCmd == IPMB_CMD_ANALOGWRITE || iCmd == IPMB_CMD_EEPROMWRITE )
{
// First parameter must be 16 bits
pParam = (uint8_t*)&iParam1;
dataStruc.data[i++] = *pParam++;
dataStruc.data[i++] = *pParam;
}
else { 
dataStruc.dataType  = IPMB_DAT_TYPE_UINT8;
dataStruc.data[i++] = iParam1; 
} 
if( iCmd == IPMB_CMD_DIGITALWRITE || iCmd == IPMB_CMD_ANALOGWRITE 
|| (iCmd == IPMB_CMD_CONFIG && iParam1 == IPMB_CFG_PWMCLOCK ) 
|| iCmd == IPMB_CMD_EEPROMREAD || pParam3 )
{
// Second parameter must be 16 bits
pParam = (uint8_t*)&iParam2;
dataStruc.data[i++] = *pParam++;
dataStruc.data[i++] = *pParam;
}
else { dataStruc.data[i++] = iParam2; }
// When pParam3 is specified, we expect iParam2 is the length
if( pParam3 )
{
if( iParam2 > 1 ) 
{ dataStruc.dataType = IPMB_DAT_TYPE_STREAM; }
iParam2+=IPMB_HDR_DATASIZE+1;
while( i < iParam2 && i < iMax )
{ dataStruc.data[i++]=*pParam3++; }
}
else if( iCmd == IPMB_CMD_EEPROMREAD && iParam2 >= 1 )
{ dataStruc.dataType = IPMB_DAT_TYPE_STREAM; 
Serial.println( "Data length = " );
Serial.println( iParam2 );
}      
// Start transmission and send command and data
Wire.beginTransmission( IPMB_I2C_ADDRESS );
Wire.write( (uint8_t*)&dataStruc, IPMB_HDR_DATASIZE + i );
bSuccess = ( Wire.endTransmission() == 0 );
//Serial.println( bSuccess );
// When data successfully send, perform command and data and ask result by request 
if( bSuccess )
{ 
//Wire.requestFrom( IPMB_I2C_ADDRESS, 3 + ( iCmd == IPMB_CMD_ANALOGREAD) ); 
Wire.requestFrom( IPMB_I2C_ADDRESS, IPMB_HDR_DATASIZE+IPMB_MAX_DATACOUNT ); 
//Serial.println( Wire.available() );
if( Wire.available() > 2 )
{ 
iErrorCode = Wire.read(); 
if( !(iErrorCode >= IPMB_ECMD_MIN && iErrorCode <= IPMB_ECMD_MAX ))
{ iErrorCode = IPMB_ECMD_INVALID_RESPONSE; 
// Debug read, reads only 0xFF's when received more than 8 bytes
while( Wire.available() )
{ Serial.println( Wire.read(), HEX ); }
}
}
else { iErrorCode = IPMB_ECMD_INVALID_RESPONSE; } 
bSuccess = ( iErrorCode == IPMB_ECMD_OK );
} 
Serial.println( "ErrorCode:" );
Serial.println( iErrorCode, HEX );
if( bSuccess )
{ 
iDataType = Wire.read();
Serial.println( iDataType, HEX );
if( iDataType != IPMB_DAT_TYPE_NONE )
{
uint8_t*  pFuncResult = puResult?puResult:(uint8_t*)&dataStruc.data[0];
uint16_t  iMaxBytes   = i2cBridgeGetDataSize( iDataType );
Serial.println( "Result is: " );
Serial.println( (char*)pFuncResult );
if( puResult )
{ memset( &pFuncResult[0], 0, sizeof( dataStruc.data )); }
while( Wire.available() && iBytes < iMaxBytes )
{ pFuncResult[iBytes++] = Wire.read(); }
if( iMaxBytes <= 4 )
{ bSuccess = ( iBytes == iMaxBytes ); }
else { bSuccess = ( iBytes > 0 ); }  
}
}
else {
if( puResult ) 
{ *puResult = iErrorCode; } 
}
// Eat all left bytes if any
while( Wire.available() )
{ Wire.read(); }

}
return bSuccess;
}

我的SLAVE库的一部分(无法全部发布,对于StackOverflow来说太大了),让你知道我在做什么:

.........
typedef struct rIpmbResultDataStruc
{
uint8_t errorCode;
uint8_t dataType;
uint8_t data[ IPMB_MAX_DATACOUNT ];
}; 
........
void eventHandleRequestReplyHandler()          // #2 Finish request, 
implement received data
{
/*
Serial.print( "Bytes: " );
Serial.println( __iIpmbDataByteCount );
Serial.print( "Command: " );
Serial.println( __rIpmbDataStruc.cmd );
Serial.print( "Version: " );
Serial.println( __rIpmbDataStruc.version, HEX );
Serial.print( "DataType: " );
Serial.println( __rIpmbDataStruc.dataType, HEX );
*/
resetSendBuffer();
uint16_t i         = 0;
uint16_t iLength   = 0;
uint8_t  iValue    = 0;
uint16_t iAddress  = 0;
// When reboot and sleep mode is previously requested, 
// don't allow other commands 
if( __bIpmbDoDeviceReset || __bIpmbDoDeviceSleep || isRebootSleepModeRequested() )
{
Wire.write( IPMB_ECMD_BUSY );
Wire.write( IPMB_DAT_TYPE_NONE );
Wire.write(0);
return;
}
if( isValidCommand( __rIpmbDataStruc.cmd ) // Valid command received?
&& isValidVersion( __rIpmbDataStruc.version ) // Version the same?
&& isValidDataType( __rIpmbDataStruc.dataType ) ) // Valid dataType specified?
{
if( __rIpmbDataStruc.cmd == IPMB_CMD_DIGITALWRITE )
{
digitalWrite( getBuffDataUint16(0), getBuffDataUint16(1) );
Wire.write( IPMB_ECMD_OK );
Wire.write( IPMB_DAT_TYPE_NONE );
Wire.write(0);
return;
}
if( __rIpmbDataStruc.cmd == IPMB_CMD_ANALOGWRITE )
{
analogWrite( getBuffDataUint16(0), getBuffDataUint16(1) );
Wire.write( IPMB_ECMD_OK );
Wire.write( IPMB_DAT_TYPE_NONE );
Wire.write(0);
return;
}
if( __rIpmbDataStruc.cmd == IPMB_CMD_DIGITALREAD )
{
Wire.write( IPMB_ECMD_OK );
Wire.write( IPMB_DAT_TYPE_UINT8 );
Wire.write( digitalRead( getBuffDataUint8(0) ));
return;
}
if( __rIpmbDataStruc.cmd == IPMB_CMD_ANALOGREAD )
{
Wire.write( IPMB_ECMD_OK );
Wire.write( IPMB_DAT_TYPE_UINT16 );
uint16_t iResult = analogRead( getBuffDataUint8(0) );
uint8_t* pResult = (uint8_t*)&iResult; 
Wire.write( *pResult++ );
Wire.write( *pResult );
return;
}
if( __rIpmbDataStruc.cmd == IPMB_CMD_PINMODE )
{
pinMode( getBuffDataUint8(0), getBuffDataUint8(1) );
Wire.write( IPMB_ECMD_OK );
Wire.write( IPMB_DAT_TYPE_NONE );
Wire.write(0);
return;
}

if( __rIpmbDataStruc.cmd == IPMB_CMD_EEPROMREAD )
{
Serial.println( "EEPROM READ");
//Serial.println( getBuffDataUint16(0) );
iAddress = IPMB_ADR_CUSTOM_DATA + getBuffDataUint16(0);
iLength  = getBuffDataUint16(1);
if( iLength > IPMB_MAX_DATACOUNT ) 
{ iLength = IPMB_MAX_DATACOUNT; }

if( __rIpmbDataStruc.dataType == IPMB_DAT_TYPE_STREAM && iLength > 0 )
{
//Wire.write( IPMB_DAT_TYPE_STREAM );
__rIpmbResultStruc.errorCode = IPMB_ECMD_OK; 
__rIpmbResultStruc.dataType  = IPMB_DAT_TYPE_STREAM;
while( i < iLength )
{
__rIpmbResultStruc.data[i++] = readStorage( iAddress++ );
}
//Serial.println( (char*)&__pIpmbResultByteBuff[0] );
Wire.write( (uint8_t*)&__pIpmbResultByteBuff[0], 2+i ); 
}
else {
Wire.write( IPMB_DAT_TYPE_UINT8 );
Wire.write( readStorage( iAddress,
getBuffDataUint8(1)
)
);
}            
return;
}

if( __rIpmbDataStruc.cmd == IPMB_CMD_EEPROMWRITE )
{
Serial.println( "EEPROM WRITE");
Serial.println( getBuffDataUint16(0) );
Wire.write( IPMB_ECMD_OK );
Wire.write( IPMB_DAT_TYPE_UINT8 );
iAddress = IPMB_ADR_CUSTOM_DATA + getBuffDataUint16(0);
if( __rIpmbDataStruc.dataType == IPMB_DAT_TYPE_STREAM )
{
iLength = getBuffDataUint16(1);
Serial.println( iLength ); delay(100);
while( i < iLength )
{
iValue = getBuffDataUint8(4+i);
Serial.println( (char)iValue ); delay(100);
if( writeStorage( iAddress++, iValue ) != iValue )
{
Wire.write(0);
return;
}
++i;
}
Wire.write( IPMB_ECMD_OK );
Serial.println( "Done" ); delay(100);
}
else { 
Wire.write( writeStorage( iAddress,
getBuffDataUint8(2),
getBuffDataUint8(3),
getBuffDataUint8(4)
)
);
}              
return;
}
if( __rIpmbDataStruc.cmd == IPMB_CMD_RESET )
{
//Serial.println( "SoftReset!" );
Wire.write( IPMB_ECMD_OK );
Wire.write( IPMB_DAT_TYPE_NONE );
Wire.write(0);
__bIpmbDoDeviceReset = true;
return;
}
if( __rIpmbDataStruc.cmd == IPMB_CMD_CONFIG )
{
Wire.write( IPMB_ECMD_OK );
Wire.write( IPMB_DAT_TYPE_UINT8 );
uint8_t iCfg = getBuffDataUint8(0);
if( iCfg == IPMB_CFG_WIPE || iCfg == IPMB_CFG_WIPE_EEPROM )
{ 
wipeStorage( iCfg == IPMB_CFG_WIPE_EEPROM );
Wire.write( IPMB_ECMD_OK );
// Always reset
__bIpmbDoDeviceReset = true; 
}
else
if( iCfg == IPMB_CFG_MCUCLOCK )
{ 
Wire.write( 
setMcuClock( getBuffDataUint8(1), 
__iIpmbConfigAutoSave
) 
); 
}
else
if( iCfg == IPMB_CFG_PWMCLOCK ) 
{ 
Wire.write( 
setPwmClock( getBuffDataUint8(1),
getBuffDataUint8(2),
__iIpmbConfigAutoSave
)
); 
}
else { Wire.write(0); }
// Set reboot flag if required
if( __iIpmbRebootAtConfig && __iIpmbConfigAutoSave )
{ __bIpmbDoDeviceReset = true; }
return;
}
if( __rIpmbDataStruc.cmd == IPMB_CMD_SLEEP )
{
//Serial.println( "Sleep" );
Wire.write( IPMB_ECMD_OK );
Wire.write( IPMB_DAT_TYPE_UINT8 );
if( getBuffDataUint8(0) )
{ __bIpmbDoDeviceSleep = true; }
Wire.write( (uint8_t)__bIpmbDoDeviceSleep );
return;
}
}
if( isValidCommand( __rIpmbDataStruc.cmd ) && !isValidVersion( __rIpmbDataStruc.version ))
{ Wire.write( IPMB_ECMD_INVALID_VERSION ); }
else { Wire.write( IPMB_ECMD_INVALID_REQUEST ); }
Wire.write( IPMB_DAT_TYPE_NONE );
Wire.write(0);
resetDataBuffer();
}

终于找到了bug和解决方案,用ESP的库代码玩了几个小时后,是ESP twi库的问题。我发布这个作为答案,也许它可以帮助其他人。原因:超时范围太小,因此,提前调用超时,依赖此的函数将失败。这就是读取0xFF而不是实际接收数据(它实际上在那里)的原因。

我从以前的项目中知道,Esp8266 对延迟非常挑剔,由于某种原因需要很长时间的过程可能会导致崩溃或设备开始出现故障,但是,实际上,此超时太小,尤其是当您使用更多字符(例如显示器)或想要通过总线发送更多字节时。

这是ESP8266 twi 库中的一个错误,位于twi_init函数内。它与 I2C BUS 读取超时有关,该超时无法通过类函数更改(您可以更改 BUS 速度,但不能更改此),值在此函数中硬编码。

函数在软件包/ESP8266/2.4.0/核心/ESP8266/core_esp8266_si2c.c中:

void twi_init(unsigned char sda, unsigned char scl){
twi_sda = sda;
twi_scl = scl;
pinMode(twi_sda, INPUT_PULLUP);
pinMode(twi_scl, INPUT_PULLUP);
twi_setClock(100000);
twi_setClockStretchLimit(230); // default value is 230 uS
}  

twi_setClockStretchLimit()指令中,"ClockStretch"(无论这意味着什么)设置为 230 uS,这是一种太低或太窄的方式。

要修复它,您需要将此值增加到 600 或更多,并且必须在启动 Wire 库后执行此操作,例如:

Wire.begin();
// Give it some time
delay( 500 );
// default value is set to 230 uS, we change it here
twi_setClockStretchLimit(600); 
.....
.....

现在我可以接收完整的 32 字节(默认缓冲区限制)。因此,当我向 ATMega 从站询问 EEPROM 内部的字符串(流)时,我将收到 28 字节的数据和 4 字节的数据信息(错误代码(字节)、数据类型(字节)、长度(2 字节))。

玩得开心;-)