ATMega328P 和 ESP8266ex 之间的 I2c 通信只能发送 8 个字节,错误?
I2c communication between ATMega328P and ESP8266ex can only send 8 bytes, bug?
我正在开发一个 I2C 桥接器,它使我能够以"智能"的方式虚拟扩展 ESP8266ex 上的可用引脚。ESP8266ex 不错,但可用引脚较少。
看起来扩展引脚已集成到 ESP8266ex 本身中。实际上,这并不是因为网桥在后台通过i2c进行通信以访问ATMega328P上的引脚,但是我可以使用标准方法和功能,例如pinMode
,digitalRead/write
和analogRead/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 字节))。
玩得开心;-)
- 运行时错误:引用绑定到类型"int"的未对齐地址0xbebebebebebebec6,这需要 4 个字节对齐 (stl_vector.h)
- 程序错误地读取了二进制文件的一个字节
- 蓝牙LE:设置字节数组的特征会发送错误的值
- OPENCV错误:不良的参数(字节必须为16、32或64)在简介Descriptorextractorimpl中
- UWP和SerialSample字节编码错误
- ATMega328P 和 ESP8266ex 之间的 I2c 通信只能发送 8 个字节,错误?
- 错误:数组的总大小不得超过0x7fffffff字节
- C++结构到字节*引发错误
- Valgrind 错误:系统调用参数 epoll_pwait(sigmask) 指向不可寻址的字节
- 函数返回的 gsl::span 具有错误的字节
- 使用 libsodium 为 iOS 构建 zeromq,随机字节错误
- strlen() 给出了数组中空字节的错误大小原因
- 比较两个文件的第二个字节时出现分段错误
- 为什么我在使用libusb与PIC 18F2550通信时读取或写入超过3个字节时会出现错误
- C++字符串到字节错误
- setw() 在包含 UTF-8 多字节字符/码位的字符串上输入错误的输出
- 通过取消引用指向字节数组的指针来分配值是错误的吗?
- 字节金币每次都回答错误
- GCC 错误,同时对常量无符号长字节使用代码块
- 使用 AES 解密时错误的 16 个字节