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

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

我正在开发一个 I2C 桥,使我能够以 'intelligent' 的方式虚拟扩展 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

这工作非常好,非常直接的功能和直接的结果,但是,我也 extend/chained 内部 EEPROM 和外部 EEPROM 到 'double' 大小。前 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通信之间有问题。

Checked/verified 一切,缓冲区大小(32 字节,对于这个例子来说足够了),检查缓冲区的内容,一切都很好。试图立即发送缓冲,似乎没有任何帮助。

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



我的 MASTER 库的一部分(不能 post 全部,对于 Whosebug 来说太大了),让您了解我在做什么:

......

#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 库的一部分(不能 post 全部,对于 Whosebug 来说太大了),让您了解我在做什么:

.........

    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库的问题。我 post 这作为答案,也许它可以帮助别人。原因:超时范围太小,因此,提前调用超时,依赖它的函数将失败。这就是读取 0xFF 而不是实际接收到的数据(它实际上在那里)的原因。

我从以前的项目中了解到,Esp8266 对延迟非常挑剔,由于某种原因处理时间过长,可能导致崩溃或设备开始出现故障,但是,实际上,这个超时时间太短了,尤其是当你使用更多的 phiperials 例如显示器或想通过总线发送更多的字节。

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

函数在packages/esp8266/2.4.0/cores/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 字节))。

玩得开心 ;-)