ADIOL100 no-OS driver

See drivers/io-link/adiol100 (doxygen) for the Doxygen documentation.

Supported Devices

Overview

The ADIOL100/ADIOL101 are dual-channel IO-Link Master transceivers. Each channel integrates an L+ sensor supply controller, a C/Q line driver and receiver, and a digital IO-Link framer that handles EstablishCommunication, M-sequence framing, and cyclic process data exchange autonomously.

The device communicates with the host MCU over SPI at up to 26 MHz (CPOL=0, CPHA=0). All registers are 16 bits wide, except the TxRxData FIFO registers. IO-Link message data is exchanged through a per-channel FIFO accessed via SPI burst transfers. The device address is selected by the ADRSEL pin at power-up (4 possible addresses), allowing up to 4 chips on one SPI bus.

The ADIOL101 is the same die in a smaller 40-pin package, without the external high-side FET controller pins.

Key specifications:

  • V24 supply: 9 V to 36 V (typ 24 V)

  • VL logic supply: 1.62 V to 5.5 V

  • CQ current limit: 10 mA to 500 mA (programmable)

  • Operating temperature: -40 C to +125 C

  • Thermal warning at 135 C, thermal shutdown at 150 C

Important note:

In order for IO-Link communication to take place, the master needs to go through several protocol phases:

                         ┌────────────┐
                         │STANDARD I/O│
                         └─────┬──────┘
                               │
                           ┌───▼───┐            ┌──────────────┐
                           │STARTUP├───────────►│ Transceiver  │
                           └───┬───┘            │Configuration │
                               │                │- CQ, LP      │
                               │                │- Protections │
                               │                │- Interrupts  │
                               │                │- ...         │
                               │                └──────┬───────┘
                               │                       │
                               │              ┌────────▼─────────────┐
                               │              │EstablishCommunication│
                               │              │  (probing the slave  │
                               │              │   at every ComRate)  │
                               │              └──────────────────────┘
                               │                       │
┌─────────────────┐      ┌─────▼────┐      ┌───────────▼───────────────┐
│   Master sets   │◄─────┤PREOPERATE│      │          Slave            │
│ MasterCycleTime │      └─────┬────┘      │      Identification       │
└────────┬────────┘            │           │ (reading slave parameters │
         │                     │           │required for communication │
┌────────▼────────┐            │           │   and identification)     │
│   (Optional)    │            │           └────────────┬──────────────┘
│Master reads ISDU│            │                        │
│     pages       │            │             ┌──────────▼───────────┐
└─────────────────┘            │             │       Sending        │
                               │             │ MasterIdent command  │
                               │             └──────────────────────┘
                               │
                               │
                           ┌───▼───┐
                           │OPERATE│
                           └───────┘
                    (live data every cycle)

This driver provides hardware control and configuration up through the EstablishCommunication step. It provides the building blocks for transceiver configuration, FIFO messaging, and cycle timer control. The higher-level IO-Link protocol state machine (STARTUP to PREOPERATE to OPERATE transitions, M-sequence framing, ISDU handling, process data management) must be implemented by the application or by integrating an external IO-Link stack.

Applications

  • IO-Link master ports

  • Factory automation sensor hubs

  • Industrial fieldbus gateways

  • Process instrumentation

ADIOL100 Device Configuration

Driver Initialization

In order to be able to use the device, you will have to provide the support for the communication protocol (SPI), the device address set by the ADRSEL pin, the clock source (crystal, external, or internal), and the clock divider.

The first API to be called is adiol100_init. Make sure that it returns 0, which means that the driver was initialized correctly. The init sequence reads the RevisionID and DeviceID to verify the chip is present, configures the clock source and divider, performs a global reset (disabling interrupts and clearing latched flags), and resets both channels.

Initialization Parameters

The adiol100_init_param struct contains the settings needed for adiol100_init:

Field

Type

Description

spi_ip

pointer

SPI bus configuration (device ID, speed, mode 0, chip select, platform ops). Required.

chip_addr

uint8_t

Device address set by the ADRSEL pin (0-3). On the ADIOL100 EVKIT this corresponds to the J10 jumper setting. The driver rejects values > 3 with -EINVAL.

clock_src

uint8_t

Clock source selection: ADIOL100_CLK_INTERNAL (default on power-up), ADIOL100_CLK_CRYSTAL, or ADIOL100_CLK_EXTERNAL.

clk_div

uint8_t

Clock divider applied to the clock output (ClkDiv[1:0], 0--3).

gpio_irqg

pointer

Optional GPIO init params for the global interrupt pin (IRQG). Set to NULL if not used.

gpio_irqa

pointer

Optional GPIO init params for the channel A interrupt pin (IRQA). Set to NULL if not used.

gpio_irqb

pointer

Optional GPIO init params for the channel B interrupt pin (IRQB). Set to NULL if not used.

Use adiol100_read_revid and adiol100_read_devid to read the chip RevisionID and DeviceID registers. These are called automatically during init but can also be used independently.

Use adiol100_setup_clock to configure the clock source (crystal, external, or internal), divider, and enable clock output. This is also called during init with the values from the init parameters.

Register Access

Use adiol100_read and adiol100_write to access any 16-bit register. Both functions handle the paged register addressing automatically: registers in the 0x1Fxx range are accessed through the ExtraPage gateway register transparently to the caller.

Use adiol100_update for read-modify-write operations on individual bit fields within a register.

CQ Driver Configuration

Use adiol100_config_cq to configure all CQSet register fields: output mode (push-pull or NPN), driver enable, sink current, slew rate, IEC 61131-2 type 2/3 threshold, source/sink select, glitch filter, CQ external FET, logic invert, and transmitter enable input.

Use adiol100_config_cq_protection to configure the CQ current limit, blanking time, TX/RX voltage thresholds, and autoretry behavior. This writes the full CQCurLim register.

L+ Supply Configuration

Use adiol100_config_lp to enable the L+ sensor supply and the reverse polarity protection FET. This modifies only the LPEN and LPFETREVEN fields in the LPCfg register.

Use adiol100_config_lp_protection to configure the L+ FET protection parameters across six registers: nominal current limit (LPCfg), power limit (LPFetPwrCfg), current min/max (LPCurrentCfg), open-load and current-limit timeouts (LPFETProtect), slope control (LPSlope), and autoretry settings (LPRetry).

Framer Configuration

Use adiol100_config_framer to configure the IO-Link framer. It writes both FramCtrl1 (checksum insertion, framer enable, UART parity, safe pulse detect, UART baud rate, UART enable) and FramCtrl2 (CKT error inhibit, message repeat, byte delay, device answer delay, TX size error inhibit).

Cycle Timer

Use adiol100_set_cycle_tmr to write the IO-Link encoded cycle time byte (time base in bits [7:6], multiplier in bits [5:0]).

Use adiol100_get_cycle_tmr to read the current cycle time register value.

Use adiol100_enable_cycle_timer and adiol100_disable_cycle_timer to start and stop autonomous cyclic operation. When enabled, the chip automatically re-transmits the TxFIFO contents at the configured cycle interval. For cyclic process data exchange, call adiol100_load_msg with ADIOL100_KEEP_MSG to retain the master message in the TxFIFO for re-transmission, then use adiol100_send_msg to trigger the first transmission.

Trigger Configuration

Use adiol100_config_trigger to configure the trigger assignment for a channel (TrigAssignA/B): auto-cycler disable, trigger value (0–15), and trigger enable.

Use adiol100_fire_trigger to write a trigger value to the Trigger register, starting transmission on all channels assigned to that trigger.

Use adiol100_led_sync to write the LEDSYNC bit in the Trigger register, synchronizing LED drivers across multiple devices.

DI Configuration

Use adiol100_config_di to configure the DI input for a channel (DICfgA/B): IEC 61131-2 type 2/3 threshold, source/sink select, sink current, glitch filter, and logic invert.

Interrupts

Use adiol100_get_channel_irq and adiol100_get_global_irq to read the interrupt status registers. These functions read the hardware register (read-to-clear) and OR the result into an accumulator in the device struct, so no flags are silently lost between reads. Channel interrupts include EstCom success/fail, RX data ready, L+ errors, CQ driver errors, and more. Global interrupts include thermal warning/shutdown, V24 errors, watchdog, and SPI CRC errors.

Use adiol100_clear_channel_irq and adiol100_clear_global_irq to clear specific flags from the accumulator after handling them. Only clear the flags your code actually acted on, unhandled flags remain accumulated for the next reader.

Use adiol100_enable_channel_irq and adiol100_enable_global_irq to set the interrupt enable masks. Note that interrupt flags latch regardless of the enable mask, the mask only controls whether the IRQ pin is asserted.

IRQ Host Pins

The driver optionally configures the host MCU GPIO pins connected to the ADIOL100 IRQG, IRQA, and IRQB outputs as inputs during adiol100_init. To enable this, pass gpio_irqg, gpio_irqa, and/or gpio_irqb in the init parameters. All GPIO fields default to NULL, in which case no GPIO setup is performed. Each pin is independent, so you can pass only the ones you need.

The driver does not manage the IRQ controller, callback registration, or interrupt enable/disable. These are the responsibility of the application or project code. This separation keeps the driver platform-agnostic: the application sets up the IRQ controller (and any platform-specific NVIC configuration), registers its own ISR using the GPIO descriptors from the driver's device structure (dev->gpio_irqa, etc.), and enables the interrupts.

The device struct includes irq_ch_pending[2] and irq_global_pending volatile bools that the application's ISR can set to signal that an interrupt has fired. The driver does not set or check these flags, they exist purely for the application's use.

The driver's register access functions (adiol100_get_channel_irq, adiol100_enable_channel_irq, etc.) work regardless of how the GPIO interrupt is managed.

/* After adiol100_init() with gpio_irqa set: */
no_os_irq_ctrl_init(&irq_ctrl, &gpio_irq_ip);

struct no_os_callback_desc cb = {
        .callback = my_irq_handler,
        .event = NO_OS_EVT_GPIO,
        .peripheral = NO_OS_GPIO_IRQ,
        .handle = dev->gpio_irqa,
};
no_os_irq_register_callback(irq_ctrl, dev->gpio_irqa->number, &cb);
no_os_irq_trigger_level_set(irq_ctrl, dev->gpio_irqa->number,
                            NO_OS_IRQ_EDGE_FALLING);
no_os_irq_enable(irq_ctrl, dev->gpio_irqa->number);

adiol100_enable_channel_irq(dev, ADIOL100_CH_A, ADIOL100_RXDARDYINT);

ADC Monitoring

The ADIOL100 has a 12-bit ADC that continuously monitors supply voltage, die temperature, and per-channel CQ/LP voltages and currents. Measurements are updated every 400 us. The chip also tracks min/max values since the last statistics reset.

Use adiol100_read_adc_global to read global ADC sources: V24 supply voltage (ADIOL100_ADC_V24_VOLT, _MIN, _MAX) and die temperature (ADIOL100_ADC_TEMP, _MIN, _MAX). Returns a raw 13-bit value in 2's complement.

Use adiol100_read_adc_chan to read per-channel ADC sources: CQ voltage (ADIOL100_ADC_CQ_VOLT), CQ current (ADIOL100_ADC_CQ_CUR), LP voltage (ADIOL100_ADC_LP_VOLT), and LP current (ADIOL100_ADC_LP_CUR), each with _MIN and _MAX variants. The channel parameter selects A or B.

Use adiol100_reset_adc_stats to clear all min/max statistics (V24, temperature, CQ and LP for both channels).

LED Configuration

The ADIOL100 has four bicolor (red/green) LED drivers with a built-in sequencer. Each LED has independent brightness, blink sequence, and operating mode settings.

Use adiol100_set_led_brightness to set the red and green brightness for one LED. Brightness is controlled in 16 steps (see enum adiol100_led_brightness), where each step represents a percentage of the 50% maximum duty cycle.

Use adiol100_set_led_sequence to set the 16-bit blink pattern for each color. Each bit corresponds to a 63 ms time slot; the hardware scans the pattern MSB-first and wraps after 16 bits (~1 s cycle). Common patterns are provided in enum adiol100_led_seq (off, on, slow blink, fast blink, pulse), but any 16-bit value can be used.

Use adiol100_set_led_mode to set the operating mode for one LED (see enum adiol100_led_mode): free-run blink, edge-triggered blink, static on when CQ/DI is high, static on when CQ/DI is low, or fast blink.

Use adiol100_reset_leds to reset all LED registers to their default values (all LEDs off).

CQ External FET Protection

Use adiol100_config_cq_protection_ext to configure the external high-side FET protection parameters across six registers: nominal current limit (CQExtCfg), power limit (CQFETPwrCfg), current min/max (CQCurrentCfg), overload and current-limit timeouts (CQFETProtect), slope control (CQFETSlope), and autoretry settings (CQRetry). This function mirrors adiol100_config_lp_protection in structure. Note that ADIOL101 (40-pin package) does not have the external FET controller pins.

Watchdog

Use adiol100_config_watchdog to configure the watchdog timer: time base (100 us, 500 us, or 2 ms), timeout multiplier (6-bit, 0 to 63), mode (SPI activity or explicit clear), enable, and lock. Once locked, the watchdog configuration cannot be changed until the next power cycle.

Use adiol100_clear_watchdog to clear the watchdog in explicit-clear mode.

Reset

Use adiol100_global_reset to reset all LEDs to off, disable all global interrupts, clear the trigger register, and read-clear all interrupt and status registers for both channels. This does not reset register values to defaults.

Use adiol100_reset_channel to assert CHANRST, which resets all channel-specific registers to their default values.

Use adiol100_reset_tx_fifo and adiol100_reset_rx_fifo to clear the transmit or receive FIFO for a channel without resetting other channel state.

ADIOL100 Driver Initialization Example

#include "no_os_spi.h"
#include "no_os_delay.h"
#include "no_os_print_log.h"
#include "adiol100.h"

struct adiol100_dev *dev;

struct no_os_spi_init_param spi_ip = {
        .device_id = SPI_DEVICE_ID,
        .max_speed_hz = SPI_BAUDRATE,
        .bit_order = NO_OS_SPI_BIT_ORDER_MSB_FIRST,
        .mode = NO_OS_SPI_MODE_0,
        .chip_select = SPI_CS,
        .platform_ops = SPI_OPS,
        .extra = SPI_EXTRA,
};

struct adiol100_init_param ip = {
        .spi_ip = &spi_ip,
        .chip_addr = 0x01,
        .clock_src = ADIOL100_CLK_CRYSTAL,
        .clk_div = 0,
};

int main(void)
{
        int ret;
        uint8_t comrt;
        uint16_t status;

        /* Initialize the ADIOL100 */
        ret = adiol100_init(&dev, &ip);
        if (ret) {
                pr_info("ADIOL100 init failed: %d\n", ret);
                return ret;
        }

        /* Enable L+ sensor supply on channel A */
        ret = adiol100_config_lp(dev, ADIOL100_CH_A,
                                 ADIOL100_LP_EN,
                                 ADIOL100_LP_REV_EN);
        if (ret)
                goto cleanup;

        /* Delay for device wake-up readiness after L+ power-on (spec max 300ms). */
        no_os_mdelay(300);

        /* Configure CQ driver */
        ret = adiol100_config_cq(dev, ADIOL100_CH_A,
                                 ADIOL100_CQ_PUSHPULL,
                                 ADIOL100_CQ_DRV_EN,
                                 ADIOL100_SINKSEL_5MA,
                                 ADIOL100_CQSLEW_250NS,
                                 ADIOL100_IEC3TH_DIS,
                                 ADIOL100_CQ_SINK,
                                 ADIOL100_GLITCH_DIS,
                                 ADIOL100_CQ_PNP,
                                 ADIOL100_CQEXT_DIS,
                                 ADIOL100_INVCQ_DIS,
                                 ADIOL100_TXEN_DIS);
        if (ret)
                goto cleanup;

        /* Configure framer */
        ret = adiol100_config_framer(dev, ADIOL100_CH_A,
                                     ADIOL100_CHKS_EN,
                                     ADIOL100_FRAMER_EN,
                                     ADIOL100_CKTERR_SEND,
                                     ADIOL100_MSGREP_DIS,
                                     ADIOL100_BDELAY_3TBIT,
                                     0x08,
                                     ADIOL100_TSIZEERR_SEND,
                                     ADIOL100_UARTPAR_DIS,
                                     ADIOL100_UART_230400,
                                     ADIOL100_UART_DIS,
                                     ADIOL100_SAFEPULS_DIS);
        if (ret)
                goto cleanup;

        /* Establish communication with the IO-Link device */
        ret = adiol100_estcom(dev, ADIOL100_CH_A);
        if (ret) {
                pr_info("EstCom failed: %d\n", ret);
                goto cleanup;
        }

        /* Read the detected baud rate */
        adiol100_get_comrt(dev, ADIOL100_CH_A, &comrt);
        pr_info("Device responded at COM%d\n", comrt);

        /* Read channel status */
        adiol100_get_status(dev, ADIOL100_CH_A, &status);
        pr_info("Channel A status: 0x%04X\n", status);

        pr_info("ADIOL100 initialized successfully!\n");

        /*
         * Application code goes here:
         * - Read DPP parameters using load_and_send_msg/read_msg
         * - Transition through STARTUP -> PREOPERATE -> OPERATE
         * - Exchange cyclic process data
         * These steps require IO-Link protocol handling above
         * the driver level.
         */

cleanup:
        adiol100_remove(dev);
        return ret;
}