diff --git a/src/Comms/AuxDevice.cpp b/src/Comms/AuxDevice.cpp index 5b5a67ae3..fdb9bc0a5 100644 --- a/src/Comms/AuxDevice.cpp +++ b/src/Comms/AuxDevice.cpp @@ -316,6 +316,40 @@ GCodeResult AuxDevice::ReadModbusRegisters(uint8_t p_slaveAddress, uint8_t p_fun return GCodeResult::ok; } +GCodeResult AuxDevice::ModbusRawTransaction(uint8_t p_slaveAddress, const uint8_t *_ecv_array rawDataOut, size_t numOut, uint8_t *_ecv_array rawDataIn, size_t numIn) noexcept +{ + if (!mutex.Take(BusAvailableTimeout)) + { + return GCodeResult::error; + } + + uart->ClearTransmitBuffer(); + uart->DisableTransmit(); + crc.Reset(ModbusCrcInit); + bytesTransmitted = 0; + + slaveAddress = p_slaveAddress; + ModbusWriteByte(slaveAddress); + function = ModbusFunction::generic; + while (numOut != 0) + { + ModbusWriteByte(*rawDataOut++); + --numOut; + } + uart->write((uint8_t)crc.Get()); + uart->write((uint8_t)(crc.Get() >> 8)); + + txNotRx.WriteDigital(true); // set port to transmit + delay(CalcTransmissionTime(4)); // Modbus specifies a 3.5 character interval + uart->ClearReceiveBuffer(); + uart->EnableTransmit(); + whenStartedTransmitting = millis(); + + bytesExpected = numIn + 3; + receivedData = rawDataIn; + return GCodeResult::ok; +} + // Check whether the Modbus operation completed. GCodeResult AuxDevice::CheckModbusResult() noexcept { @@ -339,48 +373,59 @@ GCodeResult AuxDevice::CheckModbusResult() noexcept // If we get here then we received sufficient bytes for a valid reply crc.Reset(ModbusCrcInit); - if (ModbusReadByte() == slaveAddress && ModbusReadByte() == (uint8_t)function) + if (ModbusReadByte() == slaveAddress) { - switch(function) + if (function == ModbusFunction::generic) { - case ModbusFunction::writeSingleCoil: - case ModbusFunction::writeSingleRegister: - case ModbusFunction::writeMultipleCoils: - case ModbusFunction::writeMultipleRegisters: - if (ModbusReadWord() == startRegister && ModbusReadWord() == numRegistersOrDataWord) + for (size_t i = 0; i + 3 < bytesExpected; ++i) { - return ReleaseMutexAndCheckCrc(); + *receivedData++ = ModbusReadByte(); } - break; - - case ModbusFunction::readCoils: - case ModbusFunction::readDiscreteInputs: - if (ModbusReadByte() == (numRegistersOrDataWord + 7u)/8u) + return ReleaseMutexAndCheckCrc(); + } + else if (ModbusReadByte() == (uint8_t)function) + { + switch(function) { - for (size_t i = 0; i < (numRegistersOrDataWord + 7u)/8u; ++i) + case ModbusFunction::writeSingleCoil: + case ModbusFunction::writeSingleRegister: + case ModbusFunction::writeMultipleCoils: + case ModbusFunction::writeMultipleRegisters: + if (ModbusReadWord() == startRegister && ModbusReadWord() == numRegistersOrDataWord) { - *receivedData++ = ModbusReadByte(); + return ReleaseMutexAndCheckCrc(); } - return ReleaseMutexAndCheckCrc(); - } - break; + break; - case ModbusFunction::readInputRegisters: - case ModbusFunction::readHoldingRegisters: - if (ModbusReadByte() == 2 * numRegistersOrDataWord) - { - while (numRegistersOrDataWord != 0) + case ModbusFunction::readCoils: + case ModbusFunction::readDiscreteInputs: + if (ModbusReadByte() == (numRegistersOrDataWord + 7u)/8u) { - *(uint16_t*)receivedData = ModbusReadWord(); - receivedData += sizeof(uint16_t); - --numRegistersOrDataWord; + for (size_t i = 0; i < (numRegistersOrDataWord + 7u)/8u; ++i) + { + *receivedData++ = ModbusReadByte(); + } + return ReleaseMutexAndCheckCrc(); } - return ReleaseMutexAndCheckCrc(); - } - break; + break; - default: - break; + case ModbusFunction::readInputRegisters: + case ModbusFunction::readHoldingRegisters: + if (ModbusReadByte() == 2 * numRegistersOrDataWord) + { + while (numRegistersOrDataWord != 0) + { + *(uint16_t*)receivedData = ModbusReadWord(); + receivedData += sizeof(uint16_t); + --numRegistersOrDataWord; + } + return ReleaseMutexAndCheckCrc(); + } + break; + + default: + break; + } } } diff --git a/src/Comms/AuxDevice.h b/src/Comms/AuxDevice.h index 8e872987d..3bd0fdfa5 100644 --- a/src/Comms/AuxDevice.h +++ b/src/Comms/AuxDevice.h @@ -56,6 +56,7 @@ class AuxDevice GCodeResult SendModbusRegisters(uint8_t p_slaveAddress, uint8_t p_function, uint16_t p_startRegister, uint16_t p_numRegisters, const uint8_t *_ecv_array data) noexcept; GCodeResult ReadModbusRegisters(uint8_t p_slaveAddress, uint8_t p_function, uint16_t p_startRegister, uint16_t p_numRegisters, uint8_t *_ecv_array data) noexcept pre(function == 3 || function == 4); + GCodeResult ModbusRawTransaction(uint8_t p_slaveAddress, const uint8_t *_ecv_array rawDataOut, size_t numOut, uint8_t *_ecv_array rawDataIn, size_t numIn) noexcept; GCodeResult CheckModbusResult() noexcept; void TxEndedCallback() noexcept; diff --git a/src/Comms/Modbus.h b/src/Comms/Modbus.h index 9d9908165..aaed0ae1b 100644 --- a/src/Comms/Modbus.h +++ b/src/Comms/Modbus.h @@ -11,7 +11,7 @@ #include // Modbus RTU function codes -enum class ModbusFunction : uint8_t +enum class ModbusFunction : uint16_t { readCoils = 0x01, readDiscreteInputs = 0x02, @@ -22,7 +22,9 @@ enum class ModbusFunction : uint8_t writeMultipleCoils = 0x0F, writeMultipleRegisters = 0x10, readDeviceId1 = 0x0E, - readDeviceId2 = 0x2B + readDeviceId2 = 0x2B, + + generic = 0x100 }; #endif /* SRC_COMMS_MODBUS_H_ */ diff --git a/src/GCodes/GCodeBuffer/GCodeBuffer.cpp b/src/GCodes/GCodeBuffer/GCodeBuffer.cpp index b0ea61786..b2094598c 100644 --- a/src/GCodes/GCodeBuffer/GCodeBuffer.cpp +++ b/src/GCodes/GCodeBuffer/GCodeBuffer.cpp @@ -576,7 +576,7 @@ uint32_t GCodeBuffer::GetUIValue() THROWS(GCodeException) return PARSER_OPERATION(GetUIValue()); } -// Get an unsigned integer value, throw if >= limit +// Get an unsigned integer value, throw if >= limit or the parameter letter is not seen uint32_t GCodeBuffer::GetLimitedUIValue(char c, uint32_t minValue, uint32_t maxValuePlusOne) THROWS(GCodeException) { MustSee(c); @@ -1392,6 +1392,16 @@ void GCodeBuffer::ThrowGCodeException(const char *_ecv_array msg, uint32_t param throw GCodeException(this, column, msg, param); } +[[noreturn]] void GCodeBuffer::ThrowGCodeException(const char *_ecv_array msg, const char *_ecv_array param) const THROWS(GCodeException) +{ + const int column = +#if HAS_SBC_INTERFACE + (isBinaryBuffer) ? -1 : +#endif + stringParser.GetColumn(); + throw GCodeException(this, column, msg, param); +} + #if SUPPORT_COORDINATE_ROTATION bool GCodeBuffer::DoingCoordinateRotation() const noexcept diff --git a/src/GCodes/GCodeBuffer/GCodeBuffer.h b/src/GCodes/GCodeBuffer/GCodeBuffer.h index 5016d45b4..6af238472 100644 --- a/src/GCodes/GCodeBuffer/GCodeBuffer.h +++ b/src/GCodes/GCodeBuffer/GCodeBuffer.h @@ -302,6 +302,7 @@ class GCodeBuffer final INHERIT_OBJECT_MODEL [[noreturn]] void ThrowGCodeException(const char *_ecv_array msg) const THROWS(GCodeException); [[noreturn]] void ThrowGCodeException(const char *_ecv_array msg, uint32_t param) const THROWS(GCodeException); + [[noreturn]] void ThrowGCodeException(const char *_ecv_array msg, const char *_ecv_array param) const THROWS(GCodeException); #if SUPPORT_COORDINATE_ROTATION bool DoingCoordinateRotation() const noexcept; diff --git a/src/Platform/Platform.cpp b/src/Platform/Platform.cpp index ca72843d9..c7391e443 100644 --- a/src/Platform/Platform.cpp +++ b/src/Platform/Platform.cpp @@ -2210,17 +2210,6 @@ bool Platform::IsAuxRaw(size_t auxNumber) const noexcept #endif } -static inline uint32_t GetAddress(GCodeBuffer& gb) -{ - uint32_t address = 0; - if (gb.GetCommandFraction() < 2) - { - gb.MustSee('A'); - address = gb.GetUIValue(); - } - return address; -} - /** * Converts a single byte of hex value to its ASCII hex representation. * @@ -2262,22 +2251,40 @@ static inline void CalculateNordsonUltimusVCheckSum(uint8_t* data, size_t len, u ConvertHexToAsciiHex(sum & 0xFF, checksum); // take last byte of sum and convert to ascii hex } +static Variable *_ecv_null GetResultVariable(GCodeBuffer& gb) THROWS(GCodeException) +{ + String varName; + bool seenV = false; + gb.TryGetQuotedString('V', varName.GetRef(), seenV, false); + Variable *_ecv_null resultVar = nullptr; + if (seenV) + { + if (!Variable::IsValidVariableName(varName.c_str())) + { + gb.ThrowGCodeException("variable '%s' is not a valid name", varName.c_str()); + } + auto vset = WriteLockedPointer(nullptr, &gb.GetVariables()); + Variable *_ecv_null const v = vset->Lookup(varName.c_str(), false); + if (v != nullptr) + { + gb.ThrowGCodeException("variable '%s' already exists", varName.c_str()); + } + resultVar = vset->InsertNew(varName.c_str(), ExpressionValue(), gb.CurrentFileMachineState().GetBlockNesting()); + } + return resultVar; +} + // Handle M260 and M260.1 - send and possibly receive via I2C, or send via Modbus GCodeResult Platform::SendI2cOrModbus(GCodeBuffer& gb, const StringRef &reply) THROWS(GCodeException) { // Get the slave address and bytes or words to send - -# if defined(I2C_IFACE) || SUPPORT_MODBUS_RTU - const uint32_t address = GetAddress(gb); -#endif - - int32_t values[MaxI2cOrModbusValues] = {0}; + int32_t valuesToSend[MaxI2cOrModbusValues] = { 0 }; size_t numToSend = 0; if (gb.Seen('B')) { numToSend = MaxI2cOrModbusValues; - gb.GetIntArray(values, numToSend, false); + gb.GetIntArray(valuesToSend, numToSend, false); } else if (gb.Seen('S')) { @@ -2293,7 +2300,7 @@ GCodeResult Platform::SendI2cOrModbus(GCodeBuffer& gb, const StringRef &reply) T for (size_t i = 0; i < numToSend; i++) { - values[i] = (int32_t)str[i]; + valuesToSend[i] = (int32_t)str[i]; } } else if (gb.GetCommandFraction() > 0) @@ -2302,24 +2309,37 @@ GCodeResult Platform::SendI2cOrModbus(GCodeBuffer& gb, const StringRef &reply) T return GCodeResult::error; } + size_t auxChannel = 0; + if (gb.GetCommandFraction() > 0) + { + auxChannel = gb.GetLimitedUIValue('P', 1, NumSerialChannels) - 1; + if (auxDevices[auxChannel].GetMode() != AuxMode::device) + { + reply.copy("Port has not been set to device mode"); + return GCodeResult::error; + } + } + switch (gb.GetCommandFraction()) { # if defined(I2C_IFACE) case 0: // I2C case -1: { + const uint32_t address = gb.GetLimitedUIValue('A', 1u << 10); uint32_t numToReceive = 0; bool seenR; gb.TryGetUIValue('R', numToReceive, seenR); + Variable *_ecv_null const resultVar = GetResultVariable(gb); if (numToSend + numToReceive > MaxI2cOrModbusValues) { numToReceive = MaxI2cOrModbusValues - numToSend; } - uint8_t bValues[MaxI2cOrModbusValues] = {0}; + uint8_t bValues[MaxI2cOrModbusValues] = { 0 }; for (size_t i = 0; i < numToSend; ++i) { - bValues[i] = (uint8_t)values[i]; + bValues[i] = (uint8_t)valuesToSend[i]; } I2C::Init(); @@ -2332,16 +2352,28 @@ GCodeResult Platform::SendI2cOrModbus(GCodeBuffer& gb, const StringRef &reply) T } else if (numToReceive != 0) { - reply.copy("Received"); - if (bytesTransferred == numToSend) + if (resultVar != nullptr) { - reply.cat(" nothing"); + resultVar->AssignArray(bytesTransferred - numToSend, + [bValues, numToSend](size_t index)->ExpressionValue + { + return ExpressionValue((int32_t)bValues[index + numToSend]); + } + ); } else { - for (size_t i = numToSend; i < bytesTransferred; ++i) + reply.copy("Received"); + if (bytesTransferred == numToSend) { - reply.catf(" %02x", bValues[i]); + reply.cat(" nothing"); + } + else + { + for (size_t i = numToSend; i < bytesTransferred; ++i) + { + reply.catf(" %02x", bValues[i]); + } } } } @@ -2352,13 +2384,7 @@ GCodeResult Platform::SendI2cOrModbus(GCodeBuffer& gb, const StringRef &reply) T # if SUPPORT_MODBUS_RTU case 1: // Modbus { - const size_t auxChannel = gb.GetLimitedUIValue('P', 1, NumSerialChannels) - 1; - if (auxDevices[auxChannel].GetMode() != AuxMode::device) - { - reply.copy("Port has not been set to device mode"); - return GCodeResult::error; - } - + const uint32_t address = gb.GetLimitedUIValue('A', 256); const uint16_t firstRegister = gb.GetLimitedUIValue('R', 1u << 16); const uint8_t function = (gb.Seen('F')) ? gb.GetLimitedUIValue('F', 5, 17) : 16; // default to Modbus function Write Multiple Registers but also allow Write Coils, Write Single Coil uint16_t registersToSend[MaxI2cOrModbusValues]; @@ -2370,7 +2396,7 @@ GCodeResult Platform::SendI2cOrModbus(GCodeBuffer& gb, const StringRef &reply) T reply.copy("Invalid Modbus data"); return GCodeResult::error; } - registersToSend[0] = (values[0] == 0) ? 0 : 0xFF00; + registersToSend[0] = (valuesToSend[0] == 0) ? 0 : 0xFF00; break; case (uint8_t)ModbusFunction::writeSingleRegister: @@ -2379,14 +2405,14 @@ GCodeResult Platform::SendI2cOrModbus(GCodeBuffer& gb, const StringRef &reply) T reply.copy("Invalid Modbus data"); return GCodeResult::error; } - registersToSend[0] = (uint16_t)values[0]; + registersToSend[0] = (uint16_t)valuesToSend[0]; break; case (uint8_t)ModbusFunction::writeMultipleCoils: memset(registersToSend, 0, sizeof(registersToSend)); for (size_t i = 0; i < numToSend; ++i) { - if (values[i] != 0) + if (valuesToSend[i] != 0) { registersToSend[i/16] |= 1u << (i % 16); } @@ -2396,7 +2422,7 @@ GCodeResult Platform::SendI2cOrModbus(GCodeBuffer& gb, const StringRef &reply) T case (uint8_t)ModbusFunction::writeMultipleRegisters: for (size_t i = 0; i < numToSend; ++i) { - registersToSend[i] = (uint16_t)values[i]; + registersToSend[i] = (uint16_t)valuesToSend[i]; } break; @@ -2424,23 +2450,67 @@ GCodeResult Platform::SendI2cOrModbus(GCodeBuffer& gb, const StringRef &reply) T } return rslt; } + + case 4: // generic Modbus send/receive + { + const uint32_t address = gb.GetLimitedUIValue('A', 256); + Variable *_ecv_null const resultVar = GetResultVariable(gb); + uint8_t bValues[MaxI2cOrModbusValues] = { 0 }; + for (size_t i = 0; i < numToSend; ++i) + { + bValues[i] = (uint8_t)valuesToSend[i]; + } + const uint32_t numToReceive = gb.GetLimitedUIValue('R', 1, MaxI2cOrModbusValues + 1); + uint8_t dataIn[MaxI2cOrModbusValues]; + GCodeResult rslt = auxDevices[auxChannel].ModbusRawTransaction(address, bValues, numToSend, dataIn, numToReceive); + if (rslt == GCodeResult::ok) + { + do + { + delay(2); + rslt = auxDevices[auxChannel].CheckModbusResult(); + } while (rslt == GCodeResult::notFinished); + + if (rslt == GCodeResult::ok) + { + if (resultVar != nullptr) + { + resultVar->AssignArray(numToReceive, [dataIn](size_t index)->ExpressionValue + { + return ExpressionValue((int32_t)dataIn[index]); + } + ); + } + else + { + reply.copy("Received"); + for (size_t i = 0; i < numToReceive; ++i) + { + reply.catf(" %02x", dataIn[i]); + } + } + } + else + { + reply.copy("no or bad response from Modbus device"); + } + } + else + { + reply.copy("couldn't initiate Modbus transaction"); + } + return rslt; + } # endif # if HAS_AUX_DEVICES case 2: { - const size_t auxChannel = gb.GetLimitedUIValue('P', 1, NumSerialChannels) - 1; - if (auxDevices[auxChannel].GetMode() != AuxMode::device) - { - reply.copy("Port has not been set to device mode"); - return GCodeResult::error; - } - uint8_t data[MaxI2cOrModbusValues] = {0}; for (size_t i = 0; i < numToSend; i++) { - data[i] = (uint8_t)values[i]; + data[i] = (uint8_t)valuesToSend[i]; } GCodeResult rslt = auxDevices[auxChannel].SendUartData(data, numToSend); @@ -2451,15 +2521,9 @@ GCodeResult Platform::SendI2cOrModbus(GCodeBuffer& gb, const StringRef &reply) T return rslt; } +# if !defined(DUET_NG) // don't support this on Duet 2 because we are running low on flash memory space case 3: // Nordson Ultimus V https://www.manualslib.com/manual/2917329/Nordson-Ultimus-V.html?page=46#manual { - const size_t auxChannel = gb.GetLimitedUIValue('P', 1, NumSerialChannels) - 1; - if (auxDevices[auxChannel].GetMode() != AuxMode::device) - { - reply.copy("Port has not been set to device mode"); - return GCodeResult::error; - } - AuxDevice& dev = auxDevices[auxChannel]; // Send `ENQ` @@ -2495,7 +2559,7 @@ GCodeResult Platform::SendI2cOrModbus(GCodeBuffer& gb, const StringRef &reply) T for (size_t i = 0; i < numToSend; i++) { - data[i + 3] = (uint8_t)values[i]; + data[i + 3] = (uint8_t)valuesToSend[i]; } uint8_t checksum[2] = {0}; @@ -2554,6 +2618,7 @@ GCodeResult Platform::SendI2cOrModbus(GCodeBuffer& gb, const StringRef &reply) T return rslt; } +# endif #endif default: @@ -2564,30 +2629,18 @@ GCodeResult Platform::SendI2cOrModbus(GCodeBuffer& gb, const StringRef &reply) T // Handle M261 and M261.1 GCodeResult Platform::ReceiveI2cOrModbus(GCodeBuffer& gb, const StringRef &reply) THROWS(GCodeException) { -# if defined(I2C_IFACE) || SUPPORT_MODBUS_RTU - const uint32_t address = GetAddress(gb); -#endif - const uint32_t numValues = gb.GetLimitedUIValue('B', 0, MaxI2cOrModbusValues + 1); - String varName; - bool seenV = false; - gb.TryGetQuotedString('V', varName.GetRef(), seenV, false); - Variable *_ecv_null resultVar = nullptr; - if (seenV) + Variable *_ecv_null const resultVar = GetResultVariable(gb); + + size_t auxChannel = 0; + if (gb.GetCommandFraction() > 0) { - if (!Variable::IsValidVariableName(varName.c_str())) - { - reply.printf("variable '%s' is not a valid name", varName.c_str()); - return GCodeResult::error; - } - auto vset = WriteLockedPointer(nullptr, &gb.GetVariables()); - Variable *_ecv_null const v = vset->Lookup(varName.c_str(), false); - if (v != nullptr) + auxChannel = gb.GetLimitedUIValue('P', 1, NumSerialChannels) - 1; + if (auxDevices[auxChannel].GetMode() != AuxMode::device) { - reply.printf("variable '%s' already exists", varName.c_str()); + reply.copy("Port has not been set to device mode"); return GCodeResult::error; } - resultVar = vset->InsertNew(varName.c_str(), ExpressionValue(), gb.CurrentFileMachineState().GetBlockNesting()); } switch (gb.GetCommandFraction()) @@ -2596,6 +2649,7 @@ GCodeResult Platform::ReceiveI2cOrModbus(GCodeBuffer& gb, const StringRef &reply case 0: // I2C case -1: { + const uint32_t address = gb.GetLimitedUIValue('A', 1u << 10); I2C::Init(); uint8_t bValues[MaxI2cOrModbusValues]; const size_t bytesRead = I2C::Transfer(address, bValues, 0, numValues); @@ -2634,13 +2688,7 @@ GCodeResult Platform::ReceiveI2cOrModbus(GCodeBuffer& gb, const StringRef &reply #if SUPPORT_MODBUS_RTU case 1: // Modbus { - const size_t auxChannel = gb.GetLimitedUIValue('P', 1, NumSerialChannels) - 1; - if (auxDevices[auxChannel].GetMode() != AuxMode::device) - { - reply.copy("Port has not been set to device mode"); - return GCodeResult::error; - } - + const uint32_t address = gb.GetLimitedUIValue('A', 256); const uint16_t firstRegister = gb.GetLimitedUIValue('R', 1u << 16); const uint8_t function = (gb.Seen('F')) ? gb.GetLimitedUIValue('F', 1, 5) : 4; // default to Modbus function Read Input Registers but also allow Read Holding Registers, Read Coils, Read Inputs uint16_t registersToReceive[MaxI2cOrModbusValues]; @@ -2715,13 +2763,6 @@ GCodeResult Platform::ReceiveI2cOrModbus(GCodeBuffer& gb, const StringRef &reply #if HAS_AUX_DEVICES case 2: // Uart { - const size_t auxChannel = gb.GetLimitedUIValue('P', 1, NumSerialChannels) - 1; - if (auxDevices[auxChannel].GetMode() != AuxMode::device) - { - reply.copy("Port has not been set to device mode"); - return GCodeResult::error; - } - uint8_t dataReceived[MaxI2cOrModbusValues]; GCodeResult rslt = auxDevices[auxChannel].ReadUartData(dataReceived, numValues); if (rslt == GCodeResult::ok) @@ -2752,6 +2793,8 @@ GCodeResult Platform::ReceiveI2cOrModbus(GCodeBuffer& gb, const StringRef &reply } #endif + case 3: // Nordson Ultimus V, use M260.3 + case 4: // Modbus generic, use M260.4 default: return GCodeResult::errorNotSupported; }