Modified communication protocol for MCU software 2.0
This commit is contained in:
2
Makefile
2
Makefile
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
Reference in New Issue
Block a user