Modified communication protocol for MCU software 2.0

This commit is contained in:
2025-03-10 14:29:56 +01:00
parent d975af75a2
commit cf9899062a
4 changed files with 57 additions and 51 deletions

View File

@ -13,7 +13,7 @@ REQUIRED+=sinqMotor
motorBase_VERSION=7.2.2 motorBase_VERSION=7.2.2
# Specify the version of sinqMotor we want to build against # Specify the version of sinqMotor we want to build against
sinqMotor_VERSION=0.8.0 sinqMotor_VERSION=mathis_s
# These headers allow to depend on this library for derived drivers. # These headers allow to depend on this library for derived drivers.
HEADERS += src/masterMacsAxis.h HEADERS += src/masterMacsAxis.h

View File

@ -4,6 +4,8 @@
This is a driver for the masterMacs motion controller with the SINQ communication protocol. It is based on the sinqMotor shared library (https://git.psi.ch/sinq-epics-modules/sinqmotor). The header files contain detailed documentation for all public functions. The headers themselves are exported when building the library to allow other drivers to depend on this one. This is a driver for the masterMacs motion controller with the SINQ communication protocol. It is based on the sinqMotor shared library (https://git.psi.ch/sinq-epics-modules/sinqmotor). The header files contain detailed documentation for all public functions. The headers themselves are exported when building the library to allow other drivers to depend on this one.
Compatible to MasterMACS software 2.0.0.
## User guide ## User guide
This driver is a standard sinqMotor-derived driver and does not need any specific configuration. For the general configuration, please see https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md. This driver is a standard sinqMotor-derived driver and does not need any specific configuration. For the general configuration, please see https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md.

View File

@ -303,6 +303,34 @@ asynStatus masterMacsAxis::doPoll(bool *moving) {
// Are we currently waiting for a handshake? // Are we currently waiting for a handshake?
if (waitForHandshake_) { if (waitForHandshake_) {
// Check if the handshake takes too long and communicate an error in
// this case. A handshake should not take more than 5 seconds.
time_t currentTime = time(NULL);
bool timedOut = (currentTime > timeAtHandshake_ + 5);
if (pC_->msgPrintControl_.shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, timedOut,
pC_->pasynUserSelf)) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAsked for a "
"handshake at %ld s and didn't get a positive reply yet "
"(current time is %ld s).\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
timeAtHandshake_, currentTime);
}
if (timedOut) {
pl_status =
setStringParam(pC_->motorMessageText_,
"Timed out while waiting for a handshake");
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
}
pC_->read(axisNo_, 86, response); pC_->read(axisNo_, 86, response);
if (rw_status != asynSuccess) { if (rw_status != asynSuccess) {
return rw_status; return rw_status;
@ -580,7 +608,7 @@ asynStatus masterMacsAxis::doPoll(bool *moving) {
} }
} }
} else { } else {
pC_->msgPrintControl_.resetCount(keyError); pC_->msgPrintControl_.resetCount(keyError, pC_->pasynUserSelf);
} }
// Read out the limits, if the motor is not moving // Read out the limits, if the motor is not moving

View File

@ -84,10 +84,9 @@ masterMacsController::masterMacsController(const char *portName,
/* /*
Define the end-of-string of a message coming from the device to EPICS. Define the end-of-string of a message coming from the device to EPICS.
It is not necessary to append a terminator to outgoing messages, since It is not necessary to append a terminator to outgoing messages, since
the message length is encoded in the message header in the getSetResponse the message length is encoded in the message header.
method.
*/ */
const char *message_from_device = "\x03"; // Hex-code for ETX const char *message_from_device = "\x0D"; // Hex-code for CR
status = pasynOctetSyncIO->setInputEos( status = pasynOctetSyncIO->setInputEos(
lowLevelPortUser_, message_from_device, strlen(message_from_device)); lowLevelPortUser_, message_from_device, strlen(message_from_device));
if (status != asynSuccess) { if (status != asynSuccess) {
@ -180,52 +179,17 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
return asynError; return asynError;
} }
/* // TODO: CR at the end
PSI SINQ uses a custom protocol which is described in
PSI_TCP_Interface_V1-8.pdf (p. // 4-17).
A special case is the message length, which is specified by two bytes
LSB and MSB: MSB = message length / 256 LSB = message length % 256. For
example, a message length of 47 chars would result in MSB = 0, LSB = 47,
whereas a message length of 356 would result in MSB = 1, LSB = 100.
The full protocol looks as follows:
0x05 -> Start of protocol frame ENQ
[LSB]
[MSB]
0x19 -> Data type PDO1
value [Actual message] It is not necessary to append a terminator, since
this protocol encodes the message length in LSB and MSB.
0x0D -> Carriage return (ASCII alias \r)
0x03 -> End of text ETX
*/
fullCommand[0] = '\x05'; // ENQ
fullCommand[1] = 1; // Placeholder value, can be anything other than 0
fullCommand[2] = 1; // Placeholder value, can be anything other than 0
fullCommand[3] = '\x19'; // PD01
// Create the command and add CR and ETX at the end
if (isRead) { if (isRead) {
snprintf(&fullCommand[4], MAXBUF_ - 4, "%dR%02d\x0D\x03", axisNo, snprintf(fullCommand, MAXBUF_ - 1, "%dR%02d\x0D", axisNo, tcpCmd);
tcpCmd);
} else { } else {
snprintf(&fullCommand[4], MAXBUF_ - 4, "%dS%02d=%s\x0D\x03", axisNo, snprintf(fullCommand, MAXBUF_ - 1, "%dS%02d=%s\x0D", axisNo, tcpCmd,
tcpCmd, payload); payload);
} }
// Calculate the command length // Calculate the command length
const size_t fullCommandLength = strlen(fullCommand); const size_t fullCommandLength = strlen(fullCommand);
// Length of the command without ENQ and ETX
const size_t lenWithMetadata = fullCommandLength - 2;
// Perform both division and modulo operation at once.
div_t lenWithMetadataSep = std::div(lenWithMetadata, 256);
// Now set the actual command length
fullCommand[1] = lenWithMetadataSep.rem; // LSB
fullCommand[2] = lenWithMetadataSep.quot; // MSB
adjustForPrint(printableCommand, fullCommand, MAXBUF_); adjustForPrint(printableCommand, fullCommand, MAXBUF_);
// Send out the command // Send out the command
@ -237,14 +201,30 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__); msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
if (status == asynSuccess) { if (status == asynSuccess) {
msgPrintControl_.resetCount(writeKey); msgPrintControl_.resetCount(writeKey, pasynUserSelf);
// Try to read the answer repeatedly // Try to read the answer repeatedly
int maxTrials = 2; int maxTrials = 2;
for (int i = 0; i < maxTrials; i++) { for (int i = 0; i < maxTrials; i++) {
/*
A typical response of the MasterMacs controller looks like this:
(.. TCP Header ...) 31 20 52 20 31 31 3d 35 31 32 2e 30 30 30 30 06
0d 00 00 00 00 00 00 00 00 00 00 00 00 00
The message terminator is the carriage return (0d), which is
specified in the controller constructor as the end-of-string (EOS)
character. However, we also need to remove the buffer zeros at the
end, because they will otherwise confuse the
pasynOctetSyncIO->read() during the next call. The
pasynOctetSyncIO->flush() method does exactly that: it takes all
bytes it can find in the socket and throws them away. We don't check
the return value of flush(), because it is always asynSuccess (see
https://www.slac.stanford.edu/grp/lcls/controls/global/doc/epics-modules/R3-14-12/asyn/asyn-R4-18-lcls2/asyn/interfaces/asynOctetBase.c)
*/
status = status =
pasynOctetSyncIO->read(lowLevelPortUser_, fullResponse, MAXBUF_, pasynOctetSyncIO->read(lowLevelPortUser_, fullResponse, MAXBUF_,
comTimeout_, &nbytesIn, &eomReason); comTimeout_, &nbytesIn, &eomReason);
pasynOctetSyncIO->flush(lowLevelPortUser_);
if (status == asynSuccess) { if (status == asynSuccess) {
status = parseResponse(fullCommand, fullResponse, status = parseResponse(fullCommand, fullResponse,
@ -283,10 +263,6 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
} }
} }
// MasterMACS needs a bit of time between messages, therefore thr
// program execution is paused after the communication happened.
// usleep(1500);
// Create custom error messages for different failure modes // Create custom error messages for different failure modes
switch (status) { switch (status) {
case asynSuccess: case asynSuccess:
@ -434,7 +410,7 @@ asynStatus masterMacsController::parseResponse(
} }
if (responseValid) { if (responseValid) {
msgPrintControl_.resetCount(parseKey); msgPrintControl_.resetCount(parseKey, pasynUserSelf);
// Check if the response matches the expectations. Each response // Check if the response matches the expectations. Each response
// contains the string "axisNo R tcpCmd" (including the spaces) // contains the string "axisNo R tcpCmd" (including the spaces)
@ -475,7 +451,7 @@ asynStatus masterMacsController::parseResponse(
printableResponse, printableCommand); printableResponse, printableCommand);
return asynError; return asynError;
} else { } else {
msgPrintControl_.resetCount(responseMatchKey); msgPrintControl_.resetCount(responseMatchKey, pasynUserSelf);
} }
} }
return asynSuccess; return asynSuccess;