🔍
v1.3.8

Outputs & PWM

Outputs are how your recipe controls the physical world. Each OUTPUT declared in VAR_OUTPUT maps to a PWM channel on the connected PowerUnit. Through PWM (Pulse Width Modulation), you control LED brightness, relay switching, and analog signal levels. Outputs also serve as the data link between the recipe and the XBB mobile app - the app displays each output’s current value, name, and status.

OUTPUT - PWM Output

The OUTPUT function block generates a PWM signal. It is also aliased as PWM100, PWM1000, INFO_ONOFF, INFO_VALUE, and INFO_GRAPH - the aliases control how the output is displayed in the app but the underlying behavior is identical.

Parameters

Parameter Type Direction Description
VALUE INT Input PWM on-time in microseconds (0 to PERIOD)
PERIOD INT Input PWM period in microseconds
USED BYTE Output TRUE if this output is connected to a physical PowerUnit channel
ID BYTE Output Auto-assigned sequential ID (used internally)

Basic Example

VAR_OUTPUT
    HELLJUS ["Helljus"] : OUTPUT;
    HALVLJUS ["Halvljus"] : OUTPUT;
END_VAR;

// Full brightness (100% duty cycle at 1kHz)
HELLJUS(VALUE := 1000, PERIOD := 1000);

// Half brightness (50% duty cycle at 1kHz)
HALVLJUS(VALUE := 500, PERIOD := 1000);

Calling OUTPUT Without PERIOD

If you call OUTPUT without specifying PERIOD, the firmware automatically converts any non-zero VALUE to 100% on. Specifically, the device normalizes the value to 1000 and sets PERIOD to 1000 internally.

VAR_OUTPUT
    EXTRALJUS ["Extraljus"] : OUTPUT;
END_VAR;
// These are all equivalent - 100% on:
EXTRALJUS(VALUE := 1);                        // No PERIOD: firmware converts to full on
EXTRALJUS(VALUE := 2);                        // No PERIOD: any non-zero = full on
EXTRALJUS(VALUE := TRUE);                     // No PERIOD: TRUE (=1) = full on
EXTRALJUS(VALUE := 1000, PERIOD := 1000);     // Explicit: 100% duty cycle

// This turns off the output:
EXTRALJUS(VALUE := 0);                        // Zero = off (with or without PERIOD)
Important

Without PERIOD, you lose all PWM control. The output is either fully on (any VALUE > 0) or off (VALUE = 0). If you need dimming, variable brightness, or specific PWM frequencies, you must specify PERIOD.

PWM Timing Explained

The PERIOD parameter sets the PWM period in microseconds. The VALUE parameter sets the on-time within each period. The PWM frequency and duty cycle are calculated as:

Frequency (Hz)  = 1,000,000 / PERIOD
Duty cycle (%)  = VALUE / PERIOD * 100

Period and Frequency Examples

PERIOD (us) Frequency Notes
1000 1 kHz Standard for LED dimming - no visible flicker
5000 200 Hz Lower frequency, suitable for relays and motors
10000 100 Hz Minimum for flicker-free LED control
20000 50 Hz Servo control (standard RC servo signal)

Common PWM Settings

Use Case PERIOD VALUE Duty Cycle Frequency
Full on 1000 1000 100% 1 kHz
Half brightness 1000 500 50% 1 kHz
Position light (5%) 1000 50 5% 1 kHz
Dim indicator (1%) 1000 10 1% 1 kHz
Off 1000 0 0% 1 kHz
Note: When VALUE equals PERIOD, the output is continuously on (100% duty cycle). When VALUE is 0, the output is continuously off. Values between 0 and PERIOD produce a proportional PWM signal.

Boolean Signals to OUTPUT

Boolean signals (BOOL) have the value 1 (TRUE) or 0 (FALSE). To drive an output fully on or fully off from a boolean, multiply by the period:

VAR
    SIGNAL_TANDNING : BOOL;
END_VAR;

VAR_OUTPUT
    IGNITION ["Tandning"] : OUTPUT;
END_VAR;

// Boolean * PERIOD: TRUE = full on (1000), FALSE = off (0)
IGNITION(VALUE := SIGNAL_TANDNING * 1000, PERIOD := 1000);

This technique works because TRUE * 1000 = 1000 (100% duty) and FALSE * 1000 = 0 (0% duty). It is more compact than an IF/ELSE block:

VAR
    SIGNAL_TANDNING : BOOL;
END_VAR;

VAR_OUTPUT
    IGNITION ["Tandning"] : OUTPUT;
END_VAR;
// Equivalent but more verbose - avoid this
IF SIGNAL_TANDNING THEN
    IGNITION(VALUE := 1000, PERIOD := 1000);
ELSE
    IGNITION(VALUE := 0, PERIOD := 1000);
END_IF;
Tip: The boolean multiplication pattern is idiomatic TSharkRex. Use it whenever you have an on/off signal driving an output. It saves flash space and keeps code readable.

Common Recipe Outputs

Most recipes include a set of standard system outputs that provide status information to the app and to other function blocks. Here is the typical set:

VAR_OUTPUT
    // System status outputs
    SYSTEM_ACTIVE ["System Active"] : OUTPUT;
    CAN_CONTROLLER ["CAN Controller"] : OUTPUT;
    VOLTAGE ["Voltage"] : INFO_VALUE;
    LOW_BATTERY ["Low Battery"] : INFO_ONOFF;
    ANALOG_IN0 ["Analog Input"] : INFO_VALUE;

    // Signal outputs (vehicle-specific)
    TANDNING ["Tandning"] : OUTPUT;
    HELLJUS ["Helljus"] : OUTPUT;
    HALVLJUS ["Halvljus"] : OUTPUT;
    BLINKERS_V ["Blinkers Vanster"] : OUTPUT;
    BLINKERS_H ["Blinkers Hoger"] : OUTPUT;
    BAKLJUS ["Bakljus"] : OUTPUT;
END_VAR;

VAR_SIGNAL
    HW : HARDWARE;
END_VAR;

VAR
    SIGNAL_TANDNING : BOOL;
    SIGNAL_HELLJUS : BOOL;
    SIGNAL_HALVLJUS : BOOL;
    SIGNAL_BLINKERS_V : BOOL;
    SIGNAL_BLINKERS_H : BOOL;
    SIGNAL_BAKLJUS : BOOL;
    canReady : BOOL;
END_VAR;

HW();

// System outputs
SYSTEM_ACTIVE(VALUE := 1000, PERIOD := 1000);                    // Always on
CAN_CONTROLLER(VALUE := canReady * 1000, PERIOD := 1000);        // CAN status
VOLTAGE(VALUE := TRUNC(HW.SUPPLY_VOLTAGE, INT), PERIOD := 1000); // Battery voltage
LOW_BATTERY(VALUE := (HW.SUPPLY_VOLTAGE < 11000) * 1000, PERIOD := 1000); // Warning < 11.0V
ANALOG_IN0(VALUE := TRUNC(HW.ANALOG_IN0, INT), PERIOD := 1000);  // Analog input

// Vehicle signal outputs
TANDNING(VALUE := SIGNAL_TANDNING * 1000, PERIOD := 1000);
HELLJUS(VALUE := SIGNAL_HELLJUS * 1000, PERIOD := 1000);
HALVLJUS(VALUE := SIGNAL_HALVLJUS * 1000, PERIOD := 1000);
BLINKERS_V(VALUE := SIGNAL_BLINKERS_V * 1000, PERIOD := 1000);
BLINKERS_H(VALUE := SIGNAL_BLINKERS_H * 1000, PERIOD := 1000);
BAKLJUS(VALUE := SIGNAL_BAKLJUS * 1000, PERIOD := 1000);

App Display Limitations

App Display Range

App version 1.6.13+: OUTPUT and INFO_VALUE display values as INT (0–65535), so larger values are shown correctly.

Older app versions (< 1.6.13): values were displayed as BYTE (0–255). If your users may have older app versions, keep display values in the 0–255 range or use SETTING_VALUE for guaranteed full-range display.

VAR
    currentBrightness : INT;
END_VAR;
VAR_OUTPUT
    // This will show correctly (0-255 range)
    BRIGHTNESS ["Ljusstyrka"] : INFO_VALUE;

    // This drives a physical output (PWM) - app shows 0-255
    LED_OUT ["LED"] : OUTPUT;
END_VAR;

// INFO_VALUE for display: shows the actual number
BRIGHTNESS(VALUE := currentBrightness, PERIOD := 255);

// OUTPUT for PWM: controls hardware, app shows 0-255
LED_OUT(VALUE := currentBrightness * 4, PERIOD := 1000);

Checking if an Output is Connected

The USED output tells you whether this output channel is physically connected to a PowerUnit. This is useful for conditional logic - you can skip expensive CAN requests for signals that have no physical output connected:

VAR_SIGNAL
    HW : HARDWARE;
END_VAR;

VAR_OUTPUT
    HELLJUS ["Helljus"] : OUTPUT;
END_VAR;

VAR
    ENABLE_HELLJUS : BOOL := FALSE;
END_VAR;

HW();

IF HELLJUS.USED OR HW.HOST_CONNECTED THEN
    // Only read the high beam DID if:
    // - The output is physically connected to a PowerUnit channel, OR
    // - The app is connected (user wants to see the value)
    ENABLE_HELLJUS := TRUE;
END_IF;

HELLJUS(VALUE := ENABLE_HELLJUS * 1000, PERIOD := 1000);
Tip: Checking USED is an optimization. On simple recipes with few outputs, you can skip this check and always read all signals. But on complex recipes with many DID requests, gating reads behind USED reduces CAN bus load and improves performance.

Output Types Summary

All output types use the same underlying OUTPUT function block. The type name controls how the output appears in the XBB mobile app:

Type App Display Typical Use
OUTPUT Standard PWM indicator with on/off and percentage Physical outputs (LEDs, relays, motors)
PWM100 PWM at 100 Hz Lower-frequency PWM applications
PWM1000 PWM at 1000 Hz Standard LED dimming frequency
INFO_ONOFF On/Off toggle indicator Status flags (ignition, door, etc.)
INFO_VALUE Numeric value display Sensor readings (voltage, temperature, speed)
INFO_GRAPH Live graph/chart Trending data (RPM over time, voltage curve)

Type Selection Examples

VAR_SIGNAL
    HW : HARDWARE;
END_VAR;

VAR
    brightness : INT;
    SIGNAL_TANDNING : BOOL;
    rpmValue : INT;
END_VAR;
VAR_OUTPUT
    // Physical PWM output for LED strip
    LED_STRIP ["LED Strip"] : OUTPUT;

    // Show ignition status as on/off toggle
    IGN_STATUS ["Tandning"] : INFO_ONOFF;

    // Show battery voltage as a number
    BATT_VOLTAGE ["Batteri (V)"] : INFO_VALUE;

    // Show RPM as a live graph
    ENGINE_RPM ["Varvtal"] : INFO_GRAPH;
END_VAR;

HW();

// Physical output: PWM dimming
LED_STRIP(VALUE := brightness, PERIOD := 1000);

// Info on/off: boolean * period
IGN_STATUS(VALUE := SIGNAL_TANDNING * 1000, PERIOD := 1000);

// Info value: direct value
BATT_VOLTAGE(VALUE := TRUNC(HW.SUPPLY_VOLTAGE, INT), PERIOD := 1000);

// Info graph: direct value (app draws the graph over time)
ENGINE_RPM(VALUE := rpmValue, PERIOD := 10000);

Dimming and Fade Patterns

Variable Brightness from CAN Signal

Map a CAN byte value (0–255) to PWM duty cycle:

VAR
    dimLevel : BYTE;   // From CAN data (0-255)
END_VAR;

VAR_OUTPUT
    INTERIOR_LIGHT ["Innerbelysning"] : OUTPUT;
END_VAR;

// Scale 0-255 to 0-1000 for PWM
INTERIOR_LIGHT(VALUE := dimLevel * 4, PERIOD := 1000);

Two-Level Output (Position + Full)

A common pattern for vehicle lights: dim for position/parking, full for active:

VAR
    SIGNAL_BAKLJUS : BOOL;       // Tail light signal
    SIGNAL_BROMS : BOOL;         // Brake signal
    outputValue : INT;
END_VAR;

VAR_OUTPUT
    TAIL_LIGHT ["Bakljus"] : OUTPUT;
END_VAR;

// Brake = 100%, tail light = 20%, off = 0%
IF SIGNAL_BROMS THEN
    outputValue := 1000;         // Full brightness
ELSIF SIGNAL_BAKLJUS THEN
    outputValue := 200;          // 20% for position light
ELSE
    outputValue := 0;
END_IF;

TAIL_LIGHT(VALUE := outputValue, PERIOD := 1000);
Note: For more advanced dimming patterns like smooth fading and animation, you can increment/decrement the VALUE by a small step each scan cycle. Combined with the scan time (typically 1–10ms), this produces smooth transitions visible to the eye.