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.