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 |
|---|---|---|
|
pointer |
SPI bus configuration (device ID, speed, mode 0, chip select, platform ops). Required. |
|
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 |
|
uint8_t |
Clock source selection: |
|
uint8_t |
Clock divider applied to the clock output (ClkDiv[1:0], 0--3). |
|
pointer |
Optional GPIO init params for the global interrupt pin (IRQG). Set to NULL if not used. |
|
pointer |
Optional GPIO init params for the channel A interrupt pin (IRQA). Set to NULL if not used. |
|
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).
IO-Link Communication
Use adiol100_estcom to run EstablishCommunication. The function clears
CQSend, disables the cycle timer, resets both FIFOs, then sets the EstCom
bit. It polls the channel interrupt register at 1 ms intervals for up to
160 ms. Returns 0 on success, -ENODEV if no device responded,
-ETIMEDOUT if the chip did not finish in time.
Use adiol100_wakeup_request to generate a wake-up pulse on C/Q.
Use adiol100_clear_estcom_fails to clear the EstCom fail counter.
Use adiol100_get_comrt to read the detected baud rate after a successful EstCom (1 = COM1, 2 = COM2, 3 = COM3).
Use adiol100_load_and_send_msg to load IO-Link payload bytes into the TxFIFO and trigger transmission in a single SPI burst. The caller provides the IO-Link payload (MC + CKT + OD bytes) and the current FramCtrl1 register value. The driver prepends the FIFO header (MsgID, RxBytes, TxBytes) and asserts CQSend. The caller must set the SPI burst length via adiol100_set_burst_len before calling.
Use adiol100_load_msg to load the TxFIFO without triggering CQSend. This is used when preparing a message for cyclic re-transmission or when the transmission trigger is handled separately. The caller must set the SPI burst length via adiol100_set_burst_len before calling.
Use adiol100_send_msg to assert CQSend and trigger transmission of the message already in the TxFIFO.
Use adiol100_read_msg to read the device response from the RxFIFO. The driver strips the MsgID and RxBytesAct header and returns only the IO-Link response bytes.
Use adiol100_read_channel_burst to burst-read RxFIFOStat, Interrupt (read-to-clear), Status, and RxFIFO in a single SPI transaction using register auto-increment.
Use adiol100_set_burst_len to configure the SPI burst length for FIFO access on a given channel.
Use adiol100_get_fifo_status to read the RxFIFO status register for a channel, which includes the FIFO level and error flags.
Use adiol100_get_status to read the channel status register.
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;
}