I am trying to design and implement a system on my 3D printer that would make the process of changing the nozzle and subsequent printer setup as easy as possible. As a result, I came to the design of a swinging double extruder with easily replaceable thermal blocks (assembly of a heater and a heat sink). For quick setup after the change, I planned to store parameters in i2c eeprom inside replaceable block. When it came to the control code, I was very disappointed. It turned out that M260 command can return data only in text form in reply. I tried to google the forum and didn't find a solution. Asked a question in the meta commands section and got no response. It remained only to delve into the firmware itself. It seemed to me that the normal option would be to return data through a local variable defined in the calling code. The name of this variable could be reported to the M260 command through an additional string parameter. For example:
var i2cReadBuffer = vector(10, 0)
M260 A80 B0 R10 S"i2cReadBuffer"
echo "buffer:", var.i2cReadBuffer
I changed the procedure of M260 gcode so that if S parameter is missing, the behavior remains unchanged. If S parameter is set and there are no errors, the received data is stored in a buffer variable. If an error is found, the result of the gcode is changed to ERROR and the reason is reported in the reply. Possible reasons:
- total transfer length exceeds MaxI2cBytes
- variable not found
- does not match the type of the variable (an array of integers)
- insufficient array length.
It works at first glance. I've done a few checks with BMP280 and eeprom 24c04:
I do not understand all the details of the functioning of the firmware. I looked at how similar tasks are solved in other places in the firmware code and tried to do the same. I ask the RepRap firmware developers to look at my code and tell me if I missed something important. (The text of the code has been updated and does not match the previous test output)
// Handle M260 - send and possibly receive via I2C
GCodeResult GCodes::SendI2c(GCodeBuffer& gb, const StringRef &reply)
{
#if defined(I2C_IFACE)
if (gb.Seen('A'))
{
const uint32_t address = gb.GetUIValue();
uint32_t numToReceive = 0;
GCodeResult Result = GCodeResult::ok;
bool seenR;
gb.TryGetUIValue('R', numToReceive, seenR);
int32_t values[MaxI2cBytes];
size_t numToSend;
if (gb.Seen('B'))
{
numToSend = MaxI2cBytes;
gb.GetIntArray(values, numToSend, false); //TODO allow hex values
}
else
{
numToSend = 0;
}
if (numToSend + numToReceive != 0)
{
if (numToSend + numToReceive > MaxI2cBytes)
{
numToReceive = MaxI2cBytes - numToSend;
Result = GCodeResult::error;
}
uint8_t bValues[MaxI2cBytes];
for (size_t i = 0; i < numToSend; ++i)
{
bValues[i] = (uint8_t)values[i];
}
I2C::Init();
const size_t bytesTransferred = I2C::Transfer(address, bValues, numToSend, numToReceive);
if (bytesTransferred < numToSend)
{
reply.copy("I2C transmission error");
return GCodeResult::error;
}
else if (numToReceive != 0)
{
// At this moment transfer completed successfully, may be truncated due to insufficient i2c buffer.
// We check if S parameter exists. If not, the received bytes are printed in hex format to replay.
// If yes, then we try to store received bytes in specified variable
// checking possible errors:
// - total transfer length exceeds MaxI2cBytes
// - unknown read buffer variable
// - wrong type of read buffer (requires an array of integers)
// - insufficient read buffer length
// In case of error, the result of gcode is set to ERROR, the reason is reported in reply.
if (!gb.Seen('S'))
{
reply.copy("Received");
if (bytesTransferred == numToSend)
{
reply.cat(" nothing");
}
else
{
for (size_t i = numToSend; i < bytesTransferred; ++i)
{
reply.catf(" %02x", bValues[i]);
}
}
}
else
{
if (Result != GCodeResult::ok)
{
reply.cat(" Transmission length exceeds MaxI2cBytes");
}
else
{
String<MaxVariableNameLength> varName;
gb.GetQuotedString(varName.GetRef(), true);
WriteLockedPointer<VariableSet> vset = WriteLockedPointer<VariableSet>(nullptr, &gb.GetVariables());
Variable *const var = vset->Lookup(varName.c_str());
if (var == nullptr)
{
Result = GCodeResult::error;
reply.catf(" Unknown read buffer variable '%s'.", varName.c_str());
}
else if ((*var).GetValue().GetType() != TypeCode::HeapArray)
{
Result = GCodeResult::error;
reply.catf(" Incorrect read buffer type %i (requires an array of integers).",
(int) (*var).GetValue().GetType());
}
if (Result == GCodeResult::ok)
{
ReadLocker lock(Heap::heapLock);
if ((*var).GetValue().ahVal.GetNumElements() < numToReceive)
{
Result = GCodeResult::error;
reply.cat(" Insufficient read buffer array length.");
}
else
for (size_t i = 0; i < numToReceive; ++i)
{
if ((*var).GetValue().ahVal.GetElementType(i) != TypeCode::Int32)
{
Result = GCodeResult::error;
reply.catf(" Incorrect read buffer array element type %i (requires int).",
(int) (*var).GetValue().ahVal.GetElementType(i));
break;
}
}
lock.Release();
if (Result == GCodeResult::ok)
{
ExpressionValue ev;
for (size_t i = 0; i < numToReceive; ++i)
{
ev.SetInt(bValues[numToSend + i]);
(*var).GetValue().ahVal.AssignElement(i, ev);
}
}
}
}
}
}
return Result;
}
}
return GCodeResult::badOrMissingParameter;
#else
reply.copy("I2C not available");
return GCodeResult::error;
#endif
}