The Connect button
It functions as a Connect or Disconnect button. After you click this button, the KINGSTAR Subsystem will connect to hardware. If hardware is already connected, click the button to disconnect.
The button's name is btnConnect, which has the signal clicked connected to the slot btnConnectClicked. After we click the button, we use setEnabled to disable it. This way, the button can't be pressed while the connection is being created. Timeout is a variable that determines whether the EtherCAT connection takes too much time to be created.
The following code is in QtGui.cpp
:
void QtGui::btnConnectClicked()
int Timeout = 0;
ui->btnConnect->setEnabled(false);
Before we connect to hardware, we use the getecState function to check the EtherCAT connection state. getecState returns the value of maSts.State, by which we know whether an EtherCAT connection has been established. If it has, it means this button functions as a Disconnect button, otherwise it functions as a Connect.
Either the button functions as Connect or Disconnect, it calls two functions: processEvents and commandConnect. processEvents is a member function of QApplication that keeps the user interface (UI) responsive while the EtherCAT connection is being created or broken. commandConnect is a signal connected to the slot actionConnect. When we emit commandConnect, it immediately triggers actionConnect.
The following code is in QtGui.cpp
:
while (ks->getecState() != ecatOP && Timeout < 2000)
{
QApplication::processEvents();
emit commandConnect();
actionConnect is written in ksworker.cpp. It uses a switch statement to test wkState, which is a variable of the enum workerState, in which there are three states: disconnected, connecting, and connected.
Connect
As a Connect button, the state is disconnected, in which we set wkState to connecting, which runs the connecting block in the switch statement.
Before connection
When we click Connect, the code in the disconnected
block is run. The following functions initialize and create the EtherCAT link, and start the KINGSTAR Subsystem.
- Create: prepares to link a program to the KINGSTAR Subsystem. It allows you to select which KINGSTAR Runtime instance you want to use if you have Multiple Master Package, and configures which core the runs on.
- SetAxisAccessMode: sets the data transfer mode for EtherCAT drives. The mode can be selected from the KsAccessMode type.
- SetCycleTime: sets the EtherCAT cycle time. In this sample,
cycle1000
means 1 millisecond. If you want to type a number, it is SetCycleTime(0.001). We use variables to represent numbers. You can find the following definitions inksapi.h
, which is located atC:\Program Files\IntervalZero\KINGSTAR SDK\4.0\Include
. To use the cycle time less than 1 millisecond, High Speed Timer Package is required. - SetConfiguredAxesCount: configures a number of simulated axes. In this sample, we use one simulated axis.
- ConfigureDc: configures distributed clock (DC) options.
- WaitForCommand: waits for a command to be completed. We give Start 0.2 second to be run. If it is not, the next command will be run.
- Start: starts the KINGSTAR Subsystem.
NOTE: Create is put in the ksworker
constructor, which can be called faster than that is put in actionConnect.
Variables | Number |
---|---|
cycle100 | 0.0001 |
cycle125 | 0.000125 |
cycle250 | 0.000250 |
cycle500 | 0.0005 |
cycle1000 | 0.001 |
The following code is in ksworker.cpp
:
ksWorker::ksWorker(QObject *parent)
: QObject(parent), dataTimer(new QTimer(this)), data({false, 0.0, 0.0}),
maSts({ ecatOffline, ecatOffline, 0, 0, 0, { ecatOffline }, { ecatOffline }, { axisOffline } }),
wkState(disconnected), currentIndex(-1), commandVelocity(360)
{
nRet = Create(0, 0);
if (nRet != errNoError) {
return;
}
......
}
void ksWorker::actionConnect()
{
switch (wkState)
{
case disconnected:
wkState = connecting;
nRet = SetAxisAccessMode(KsAccessMode::accessPos);
nRet = SetCycleTime(cycle1000);
nRet = SetConfiguredAxesCount(1);
nRet = ConfigureDc(TRUE, TRUE, TRUE, 0);
WaitForCommand(0.2, FALSE, Start());
GetStatus(&maSts, nullptr);
break;
case connecting:
GetStatus(&maSts, nullptr);
if (maSts.State == EthercatState::ecatOP)
{
wkState = connected;
SlaveStatus *ss = new SlaveStatus;
McProfileSettings Motion = {
3,
360,
5000,
50000,
50000,
5000000,
0
};
for (int i = 0; i < maSts.AxesCount; i++)
{
int Resolution = 10000;
DWORD InputVariables = 0;
DWORD OutputVariables = 0;
GetAxisByIndex(i, ss, &Resolution, &InputVariables, &OutputVariables);
SetAxisMotionProfile(i, profileUnitPerSecond, Motion);
SetAxisCountsPerUnit(i, Resolution, 360, FALSE);
EnableAxisUnitConversion(i, TRUE);
SetAxisPositionOffset(i, 0, FALSE, McExecutionMode::mcImmediately);
powerStatus.push_back(FALSE);
emit sendName(i, ss->Name);
}
delete ss;
dataTimer->start(100);
}
break;
...........
}
...........
}
During connection
In the connecting block, we use the following functions to initialize the motion settings:
- GetStatus: gets the state of an EtherCAT link.
- McProfileSettings: it is a structure that contains the settings relevant to the motion of an axis, such as acceleration, deceleration, and jerk.
- GetAxisByIndex: gets information from an axis.
- SetAxisMotionProfile: configures the motion settings of an axis. It determines the unit for acceleration, deceleration, and jerk, and applies the motion values we defined in McProfileSettings.
- SetAxisCountsPerUnit: sets the conversion ratio of the user-defined position unit to the count (pulse) unit used by an axis. In this sample,
Numerator
is 10000 andDenominator
is 360, so the ratio is 250:9.Reverse
is FALSE, so the direction of the axis is not reversed. - EnableAxisUnitConversion: enables the axis to use a real-world unit. After you set a conversion ratio using SetAxisCountsPerUnit, you need to use this method to enable the conversion so the ratio takes effect.
- SetAxisPositionOffset: shifts the coordinate system of an axis by manipulating both the setpoint position as well as the actual position of an axis with the same value without causing any movement (recalibration with same following error).
powerstatus
: defined inksworker.h
, it is a variable that represents the power of an axis. When the KINGSTAR Subsystem is connecting to hardware, the power of each axis is set to false (axes are disabled).sendName
: defined inksworker.h
. It is a signal contains the index and name of an axis. InQtGui.cpp
,sendName
is connected to the slot updateDevices, which updates the deivces in the list.
Timer
We use dataTimer as a timer at the end of connecting. start sets the interval of dataTimer to 100 milliseconds. Every 100 milliseconds dataTimer emits the QTimer::timeout signal, which is connected to a lambda function as the third parameter.
When timeout is triggered, the lambda function checks whether the EtherCAT link state is Operational (Op) and currentIndex is greater than zero. If they are, we get the power state of the selected axis and assign it to data.powerStatus. currentIndex is the index of an axis. data is a variable of the axisData structure that is used get the state of an axis. We then use GetAxisPosition and GetAxisVelocity to get the actual position and actual velocity of the axis. Finally, we emit the sendData signal that is connected to the slot updateAxisData, which is run immediately. updateAxisData updates the GUI elements according to their states.
The following code is in ksworker.cpp
:
QObject::connect(dataTimer, &QTimer::timeout, [this]()
{
if (maSts.State == ecatOP && currentIndex >= 0)
{
double pos = 0.0;
double vel = 0.0;
data.powerStatus = powerStatus[currentIndex];
nRet = GetAxisPosition(currentIndex, McSource::mcActualValue, &pos);
if (nRet == KsError::errNoError)
data.actualPos = pos;
else
data.actualPos = (double)nRet;
nRet = GetAxisVelocity(currentIndex, McSource::mcActualValue, &(vel));
if (nRet == KsError::errNoError)
data.actualVel = vel;
else
data.actualVel = (double)nRet;
}
emit sendData(data);
});
The following code is in QtGui.cpp
:
void QtGui::updateAxisData(axisData data)
{
if (!ui->btnEnable->isEnabled())
{
if (ui->btnEnable->text() == "Enable" && data.powerStatus)
{
ui->btnEnable->setText("Disable");
ui->btnEnable->setEnabled(true);
ui->btnJogForward->setEnabled(true);
ui->btnJogBackward->setEnabled(true);
}
else if (ui->btnEnable->text() == "Disable" && !data.powerStatus)
{
ui->btnEnable->setText("Enable");
ui->btnEnable->setEnabled(true);
ui->btnJogForward->setEnabled(false);
ui->btnJogBackward->setEnabled(false);
}
}
else
{
if (data.powerStatus)
{
...........
}
else
{
...........
}
}
ui->leActPosition->setText(QString::number(data.actualPos, 'f', 3));
ui->leActVelocity->setText(QString::number(data.actualVel, 'f', 3));
}
After the connection is established, we change the settings of the following GUI elements.
The following code is in QtGui.cpp
:
ui->listDevices->setCurrentRow(0);
ui->leCommVelocity->setEnabled(true); //Enable the Command Velocity box.
ui->leCommVelocity->setText("360"); //Set the value to 360 in the Command Velocity box.
ui->btnEnable->setEnabled(true); //Enable the Enable button.
ui->btnReset->setEnabled(true); //Enable the Reset button.
ui->btnJogForward->setEnabled(true); //Enable the Jog Forward button.
ui->btnJogBackward->setEnabled(true); //Enable the Jog Backward button.
ui->btnConnect->setText("Disconnect"); //Change the text of the Connect button to "Disconnect."
statusLabel->setText("EC state: OP"); //Change the text of the Status label to "EC state: OP."
statusProgress->setMaximum(100); //Change the maximum step of the progress bar to 100.
statusProgress->setValue(100); //Set the current value of the progress bar to 100.
Disconnect
As a Disconnect button, the state is connected. We stop the timer, use PowerAxis to disable all the axes, and use Stop to stop the EtherCAT network and KINGSTAR Subsystem. WaitForCommand is used to give some time for PowerAxis and Stop to complete their work. We give PowerAxis 5 seconds and Stop 2 seconds. wkState is set to disconnected to set the button as a Connect. currentIndex is set to -1, which means there is no axis. maSts is restored to its default setting.
The following code is in ksworker.cpp
:
case connected:
dataTimer->stop();
for (int i = 0; i < maSts.AxesCount; i++)
{
KsCommandStatus Command = { 0 };
Command = WaitForCommand(5, FALSE, PowerAxis(i, FALSE, FALSE, FALSE));
}
WaitForCommand(2, FALSE, Stop());
wkState = disconnected;
currentIndex = -1;
maSts = { ecatOffline, ecatOffline, 0, 0, 0, { ecatOffline }, { ecatOffline }, { axisOffline } };
break;
default:
break;
}