C++: API Reference#
Reference for the Synergy C++ library. See C++: Examples for a tutorial
introduction.
#include <Synergy.h>
The library creates a global instance named SL. You do not construct one.
Initialisation#
uint8_t init();
Sign on the modules. Returns the number of slots that responded. The idiom is
while (!SL.init());, which blocks until the backplane is ready.
void enableBaseController(bool state);
Enable or disable the base controller. init() calls this for you.
bool isBaseActive();
true if the base controller is configured and responding. If it returns
false, call init() again.
uint16_t rollCall(const char* moduleNames[], uint8_t numberOfModules);
Compare an array of expected module names against what signed on. Returns 0 when
every slot matches. Otherwise returns a bitmap of mismatched slots: bit 0 is
slot 1, so mismatches in slots 1 and 3 return 0x05. Each mismatched slot is
disabled and no longer exchanges data.
uint8_t checkConnection(uint8_t numberOfModules = 0);
Check whether any module has dropped off the bus. Returns the first missing module, or 0 if all present.
uint8_t printModules();
Print every signed-on module.
uint32_t getFwVersion();
Base controller firmware version.
Discrete I/O#
uint32_t readDiscrete(uint8_t slot, uint8_t channel = 0);
void writeDiscrete(uint32_t data, uint8_t slot, uint8_t channel = 0);
Channel 0, the default, operates on every channel of the slot at once as
a bitmap, in a single bus transaction. Pass a channel number to address one
channel.
uint32_t all = SL.readDiscrete(1); // whole slot as a bitmap
uint32_t ch3 = SL.readDiscrete(1, 3); // channel 3 only
SL.writeDiscrete(0xFF, 2); // first eight outputs on
SL.writeDiscrete(1, 2, 3); // channel 3 on
Argument order
writeDiscrete and writeAnalog take the data first, then slot, then
channel. readDiscrete and readAnalog take slot then channel.
Transposing these is the most common mistake with this API, and it compiles cleanly because everything is an integer.
Analog I/O#
int readAnalog(uint8_t slot, uint8_t channel);
void writeAnalog(uint32_t data, uint8_t slot, uint8_t channel);
float readTemperature(uint8_t slot, uint8_t channel);
readAnalog returns counts at the module’s native resolution, 0-8191 for a
13-bit input. writeAnalog expects the same, 0-4095 for a 12-bit output.
Values are masked on the base controller.
readTemperature returns a float in the module’s configured unit. No scaling
needed.
Scale against the module’s own resolution
Counts are reported at the resolution of the module, not at a fixed width. Module Catalog lists the resolution of every module.
Status#
uint8_t checkUnderRange(uint8_t slot, uint8_t channel = 0);
uint8_t checkOverRange(uint8_t slot, uint8_t channel = 0);
uint8_t checkBurnout(uint8_t slot, uint8_t channel = 0);
uint8_t check24V(uint8_t slot);
Each returns 1 if the condition is present and the module supports reporting it.
Channel 0 tests whether any channel on the module reports it.
char readStatus(int byteNum, int slot);
void readStatus(char buf[], uint8_t slot);
Raw status bytes: one byte, or all of them into a caller-supplied buffer.
Configuration#
bool configureModule(char cfgData[], uint8_t slot);
bool configureModule(const char cfgData[], uint8_t slot);
void readModuleConfig(char cfgData[], uint8_t slot);
Send a configuration block to a module, or read back what is stored.
readModuleConfig reports the stored configuration, which is not a guarantee
that it has been applied.
Block layouts and field values: Module Configuration Blocks.
Watchdog#
void configWD(uint16_t milliseconds, uint8_t toggle);
void startWD();
void stopWD();
void petWD();
toggle selects the reset behaviour: reset and recover, or hold in reset until
24 V is cycled.
Any bus read or write pets the watchdog implicitly. Call petWD() explicitly
around code paths that might not touch the bus.
Block transfers#
void readBlockData(char* buf, uint16_t len, uint16_t offset, uint8_t type);
void writeBlockData(char* buf, uint16_t len, uint16_t offset, uint8_t type);
Read or write the raw I/O image directly, bypassing per-channel access. This is the fastest way to move a large number of points in one transaction, and also the least forgiving, since you are responsible for the offsets and the byte layout.
Use it where a large rail must be serviced within a tight cycle. See the
examples/Utility/BlockDataTransfer example on the device.
Slot properties#
moduleProps readSlotProps(uint8_t slot);
Returns the properties of the module in the slot, including byte counts, data sizes and capabilities. Useful for code that adapts to the rail rather than assuming a fixed arrangement.
Named channels#
struct channelLabel {
uint8_t slot;
uint8_t channel;
};
Every I/O function has an overload taking a channelLabel in place of separate
slot and channel arguments:
uint32_t readDiscrete(channelLabel label);
void writeDiscrete(uint32_t data, channelLabel label);
int readAnalog(channelLabel label);
void writeAnalog(uint32_t data, channelLabel label);
float readTemperature(channelLabel label);
uint8_t checkUnderRange(channelLabel label);
uint8_t checkOverRange(channelLabel label);
uint8_t checkBurnout(channelLabel label);
Declare labels once at the top of the file:
const channelLabel StartButton = {1, 1};
const channelLabel MotorRun = {2, 1};
SL.writeDiscrete(SL.readDiscrete(StartButton), MotorRun);
This is the recommended style for anything beyond a brief test. It is self-documenting, and a change to the rail requires a single edit.
Hardware constants#
Defined in RPi.h:
#define SPI_DEVPATH "/dev/spidev6.0"
#define GPIO_DEVPATH "/dev/gpiochip0"
The GPIO assignments are chip select 27, acknowledge 25 and base enable 17. The OpenPLC Runtime drives the same pins, which is why only one of the two may run at a time. See Sharing the Hardware.