C++: Examples#
Complete programs. Build each as described in C++: Building, and stop the PLC first. See Sharing the Hardware.
Further examples are supplied on the device under
~/Desktop/synergy_cpp/examples/, in the Basic, Utility and Watchdog
folders.
The shape of every program#
#include <Synergy.h>
int main() {
while (!SL.init()); // wait for the modules to sign on
while (true) {
// your control loop
}
}
SL is a global instance created by the library. You do not construct it.
init() returns the number of slots that signed on, so while (!SL.init())
blocks until the backplane is ready.
Blink an output#
#include <Synergy.h>
#include <unistd.h>
int main() {
while (!SL.init());
while (true) {
SL.writeDiscrete(1, 1, 2); // slot 1, channel 2, on
usleep(1 * 1000000);
SL.writeDiscrete(0, 1, 2); // off
usleep(1 * 1000000);
}
}
Argument order is (data, slot, channel), with the value first. Transposing
these arguments is a common mistake, and it compiles cleanly because every
parameter is an integer.
List the modules#
#include <Synergy.h>
int main() {
while (!SL.init());
SL.printModules();
return 0;
}
Read discrete inputs#
#include <Synergy.h>
#include <cstdio>
#include <unistd.h>
int main() {
while (!SL.init());
while (true) {
// One channel
uint32_t ch1 = SL.readDiscrete(1, 1);
// Every channel of the slot at once -- channel 0 means "all"
uint32_t all = SL.readDiscrete(1, 0);
printf("ch1=%u all=0x%08X\n", ch1, all);
usleep(500000);
}
}
Passing channel 0 to the discrete calls operates on the whole slot as a
bitmap, in one bus transaction. Use it when you need several channels, as it
is considerably cheaper than one call per channel.
Read an analog input#
#include <Synergy.h>
#include <cstdio>
#include <unistd.h>
int main() {
while (!SL.init());
while (true) {
int counts = SL.readAnalog(1, 1); // slot 1, channel 1
// SLM-AI-8-V: 13-bit native, 0-10 V
float volts = counts / 8191.0f * 10.0f;
printf("%5d counts %6.3f V\n", counts, volts);
usleep(500000);
}
}
Scale against the module’s own resolution
readAnalog() returns counts at the module’s native resolution, which is
0-8191 for a 13-bit input. writeAnalog() expects the same, 0-4095 for a
12-bit output.
Module Catalog lists the resolution of every module.
Write an analog output#
// SLM-AO-8-V: 12-bit native, 0-10 V
float volts = 5.0f;
uint32_t counts = (uint32_t)(volts / 10.0f * 4095.0f);
SL.writeAnalog(counts, 1, 1);
Read a temperature#
float temp = SL.readTemperature(1, 1); // slot 1, channel 1
printf("%.2f degrees\n", temp);
The thermocouple module returns a float in the configured unit directly. No
scaling, no counts.
Verify the rail at startup#
#include <Synergy.h>
#include <cstdio>
const char* expected[] = {
"SLM-DCI-16NP",
"SLM-DCO-15P",
"SLM-AI-8-V",
};
int main() {
while (!SL.init());
uint16_t mismatch = SL.rollCall(expected, 3);
if (mismatch != 0) {
for (int slot = 1; slot <= 3; slot++) {
if (mismatch & (1 << (slot - 1))) {
printf("backplane does not match: slot %d\n", slot);
}
}
return 1;
}
// safe to proceed
}
Verifying the rail at startup turns a misconfigured stack into an explicit failure rather than silently wrong readings.
Named channels#
Rather than tracking slot and channel numbers through the program, name them:
#include <Synergy.h>
#include <unistd.h>
const channelLabel StartButton = {1, 1}; // slot 1, channel 1
const channelLabel MotorRun = {2, 1};
const channelLabel TankLevel = {3, 1};
int main() {
while (!SL.init());
while (true) {
uint32_t start = SL.readDiscrete(StartButton);
int level = SL.readAnalog(TankLevel);
SL.writeDiscrete(start && level < 6000, MotorRun);
usleep(10000);
}
}
Every I/O function has a channelLabel overload. Using them means a change to
the rail requires editing only the labels at the top of the file.
Status checks#
if (SL.check24V(1)) printf("slot 1: field supply missing\n");
if (SL.checkOverRange(3, 1)) printf("slot 3 ch1: over range\n");
if (SL.checkUnderRange(3, 1)) printf("slot 3 ch1: under range\n");
if (SL.checkBurnout(4, 1)) printf("slot 4 ch1: burnout\n");
uint8_t missing = SL.checkConnection();
if (missing) printf("slot %u has dropped off the bus\n", missing);
checkConnection() returns the first missing module, or 0 if all present. Call
it periodically in any program that has to survive a module coming loose.
The argument is a count, not a slot
The signature is checkConnection(uint8_t numberOfModules = 0). Every other
status call on this page takes a slot first, so checkConnection(3) looks like
“check slot 3” and is not — it means “expect three modules”. Call it with no
argument to check everything that signed on.
Watchdog#
#include <Synergy.h>
#include <unistd.h>
int main() {
while (!SL.init());
SL.configWD(1000, 1); // 1000 ms timeout, toggle mode
SL.startWD();
while (true) {
doTheControlWork();
SL.petWD();
usleep(100000);
}
}
Any bus read or write pets the watchdog implicitly. Call petWD() explicitly
around code paths that might not touch the bus for a while.
A complete control loop#
Everything together: verify the rail, run at a fixed period, handle signals, and fail safe.
#include <Synergy.h>
#include <csignal>
#include <cstdio>
#include <ctime>
const channelLabel LevelSensor = {1, 1};
const channelLabel ValveOutput = {2, 1};
const char* expected[] = { "SLM-AI-8-V", "SLM-AO-8-V" };
volatile sig_atomic_t running = 1;
void handleSignal(int) { running = 0; }
int main() {
signal(SIGINT, handleSignal);
signal(SIGTERM, handleSignal);
while (!SL.init());
if (SL.rollCall(expected, 2) != 0) {
fprintf(stderr, "backplane mismatch\n");
return 1;
}
SL.configWD(500, 1);
SL.startWD();
// Absolute-deadline scheduling: the loop period does not drift
// by however long the previous iteration happened to take.
struct timespec next;
clock_gettime(CLOCK_MONOTONIC, &next);
const long periodNs = 10 * 1000 * 1000; // 10 ms
while (running) {
int level = SL.readAnalog(LevelSensor); // 0-8191
uint32_t cmd = (uint32_t)(level / 8191.0f * 4095.0f);
SL.writeAnalog(cmd, ValveOutput);
next.tv_nsec += periodNs;
if (next.tv_nsec >= 1000000000L) {
next.tv_nsec -= 1000000000L;
next.tv_sec++;
}
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, nullptr);
}
SL.writeAnalog(0, ValveOutput); // fail closed
SL.stopWD();
printf("stopped, valve closed\n");
return 0;
}
Two elements of this example apply to any program.
Absolute-deadline timing. usleep(10000) sleeps for 10 ms after the
work, so the real period becomes the execution time plus 10 ms, and it drifts.
Computing the next deadline and sleeping until it keeps the period fixed.
A signal handler and a fail-safe exit. A service manager sends SIGTERM
when stopping a program.
Without a handler the process dies immediately and the valve stays wherever it
was. This is the guarantee you give up by leaving the PLC runtime, and you have
to put it back yourself.
Next#
C++: API Reference, for the complete API.