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

时间:2018-01-19 22:09:07

标签: c++ communication avr i2c

我正在开发一个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(使用{{ 1}})。所以在I2C通信之间存在一些问题。

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

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

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

Wire.read()

我的SLAVE库的一部分(无法发布,对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;
}

1 个答案:

答案 0 :(得分:1)

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

我从之前的项目中了解到,Esp8266在延迟方面非常挑剔,由于某种原因需要太长时间的流程可能会导致崩溃或设备开始出现故障,但实际上,这种超时太小,尤其是当您使用更多时phiperials例如显示器或想要通过BUS发送更多字节。

这是ESP8266 twi库中twi_init函数内的错误。它与I2C总线读取超时有关,不能通过类功能改变(你可以改变总线速度而不是这个),在这个功能中硬编码值。

功能位于 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个字节的数据信息(errorCode(字节),dataType(字节),长度(2个字节))。

玩得开心; - )