Guide to using the OnLogic ADP120/ADP102 Isolated DIO module, covering features, wiring, application interface, and sample Python code
''' Example usage of the ADP120 DIO expansion card '''
import sys
from time import sleep
import functools
from serial import Serial # python -m pip install pyserial
# Detecting serial port
import serial.tools.list_ports as system_ports
def get_device_port() -> str:
''' Scans system to detect device CDC ACM port '''
all_ports = system_ports.comports()
for port, _, hwid in sorted(all_ports):
if "1FC9:0094" in hwid:
# Replace with "15A2:0300" if using ADP102
# Fix for windows COM ports above 10
if 'win' in sys.platform:
return "\\\\.\\" + port
else:
return port
return None
class PinConfig:
''' Pin configuration; used to set or report a pin config
PARAMETERS:
state_change: Report to host when pin state has changed
counter_overflow: Report to host when pin counter has overflown
counter_polarity:
latch_polarity:
starting_state: Initial pin state
enable: Enable/disable pin
'''
def __init__(self, state_change=False, counter_overflow=False, counter_polarity=0, latch_polarity=0,
starting_state=0, enabled=True):
self.state_change = state_change
self.counter_overflow = counter_overflow
self.counter_polarity = counter_polarity
self.latch_polarity = latch_polarity
self.starting_state = starting_state
self.enabled = enabled
def bytes(self):
nbytes = [0x00, 0x00, 0x00, 0x00]
if self.state_change:
nbytes[2] |= 0x02
if self.counter_overflow:
nbytes[2] |= 0x01
if self.enabled:
nbytes[3] |= 0x01
if self.starting_state:
nbytes[3] |= 0x04
if self.latch_polarity:
nbytes[3] |= 0x40
if self.counter_polarity:
nbytes[3] |= 0x80
return bytes(nbytes)
@staticmethod
def from_bytes(nbytes):
nbytes = list(nbytes)
config = {
'state_change': True if nbytes[2] & 0x02 else False,
'counter_overflow': True if nbytes[2] & 0x01 else False,
'enabled': True if nbytes[3] & 0x01 else False,
'starting_state': True if nbytes[3] & 0x04 else False,
'latch_polarity': True if nbytes[3] & 0x40 else False,
'counter_polarity': True if nbytes[3] & 0x80 else False,
}
return PinConfig(**config)
class ADP120(Serial):
''' Subclass serial with ADP120 specific commands '''
START = b'\x24'
END = b'\x00\x80\x01'
COMMANDS = {
'model': b'\x00\x00\x01',
'serial': b'\x00\x00\x03',
'read_state': b'\x01\x01',
'read_latch': b'\x01\x02',
'read_count': b'\x01\x03',
'clear_latch': b'\x01\x04',
'clear_count': b'\x01\x05',
'toggle_output': b'\x01\x09',
'save_config': b'\x00\x06',
}
def __init__(self, *args, **kwargs):
''' Initialize serial device; set port timeout if missing '''
if 'timeout' not in kwargs:
kwargs['timeout'] = 5
if 'write_timeout' not in kwargs:
kwargs['write_timeout'] = 0
super(ADP120, self).__init__(*args, **kwargs)
def command(self, cmd: bytes, adr=None) -> bytes:
''' Send and ADP120.COMMAND to the hardware device '''
if adr is None:
return self.write_command_raw(cmd)
else:
return self.write_command_raw(bytes([adr]) + cmd)
def read_response(self) -> bytes:
''' Read the response to a command '''
r = self.read(1)
if r == self.START:
rlen = self.read(1)
return self.read(ord(rlen) - 1)[3:]
else:
sleep(0.01)
return self.read(self.in_waiting)
def write_command_raw(self, cmd: bytes) -> bytes:
''' Write the raw bytes to the serial device '''
# Raw command
raw = self.START + bytes([len(cmd) + 1]) + cmd
# Write the command
count = self.write(raw)
# Check whole command was written
if count != len(raw):
return None
# Get the response
reply = self.read_response()
return reply
def write_config(self, address, config):
''' Configure a pin '''
return self.write_command_raw(bytes([address]) + b'\x00\x05' + config.bytes())
def read_config(self, address):
config = self.write_command_raw(bytes([address]) + b'\x00\x04')
return PinConfig.from_bytes(config)
def write_output(self, address, state):
return self.write_command_raw(bytes([address]) + b'\x01\x08' + bytes([state]))
# Support ADP120.model() syntax
def __getattr__(self, name):
cmd = self.COMMANDS.get(name)
if cmd is None:
raise AttributeError(name)
else:
result = functools.partial(self.command, cmd)
return result
if __name__ == "__main__":
port_name = get_device_port()
# Detect the ADP120 module
if port_name is None:
print("Failed to detect device!")
sys.exit(-1)
adp = ADP120(port_name)
# Report model and firmware version
print(f"Model: {adp.model()}")
# Read input and output states
for i in range(0, 8):
print(f"{'Input' if i < 4 else 'Output'} {i if i < 4 else i - 4} State: {adp.read_state(i)}")
# Read the config of output 0
cfg = adp.read_config(4)
print(f"Output 0:\n Starting state: {cfg.starting_state}\n Enabled: {cfg.enabled}")
# Toggle the starting state, and enable the port
cfg.starting_state = False if cfg.starting_state else True
cfg.enabled = True
adp.write_config(4, cfg)
cfg = adp.read_config(4)
print(f"Output 0:\n Starting state: {cfg.starting_state}\n Enabled: {cfg.enabled}")
# Write an output and confirm it worked
adp.write_output(4, 1)
print(f"Current State: {adp.read_state(4)}")








Guide to Digital I/O (DIO) basics and a tutorial for setting up and testing DIO on Karbon K300 / K700 series systems using Python.
stty -F /dev/serial/by-id/<device> -echo before




import pykarbon.terminal as pkt
def callback_fn(arg):
if arg[3] == '0': #check if 3rd item from popdata() is 0
print("DI 3 --> LOW ", arg)
return True #return True to dev.set_do. sets output high
else: #if 3rd item from popdat() is anything other than 0
print("DI 3 --> HIGH", arg)
return False #return Flase to set_do. sets output low
i = 0
with pkt.Session() as dev:
dev.update_info(print_info=True) # Update and print configuration info
dev.set_do(0, False) # Set digital output zero low
while True: #create loop that runs forever
line = dev.popdata() #popdata will print out data in the queue
if line:
dev.set_do(0, callback_fn(line)) #returns data from queue as argument for use by callback_fn




# Set digital output 0
$ hwc.exe dio set digital-output 0
# Clear digital output 0
$ hwc.exe dio clear digital-output 0# Read the state of a digital input
$ hwc.exe dio read digital-input 0# Clear the toggle-count of a digital input
$ hwc.exe dio clear-count digital-input 0$ hwc.exe dio --help
Read and write digital IO states
Set outputs, read both inputs and outputs.
USAGE:
hwc.exe dio <action> <kind> <pin>
FLAGS:
-h, --help
Prints help information
-V, --version
Prints version information
ARGS:
<action>
Kind of action to take on the IO pin [possible values: ...]
<kind>
The type of IO device to target [possible values: ...]
<pin>
Hardware pin number used by this operation# Set the device baudrate to 500000
$ hwc.exe can -d 1 set-baudrate 500
# Enable the can device
$ hwc.exe can -d 1 enable
# Send a message
$ hwc.exe can -d 1 write 1FF 3 -- 11 22 33
# Receive a message
$ hwc.exe can -d 1 read$ hwc.exe can --help
Control system CAN devices
Send and receive messages, set the system baudrate, and report status
USAGE:
hwc.exe can [OPTIONS] <action> [msg-id] [length] [-- <data>...]
FLAGS:
-h, --help
Prints help information
-V, --version
Prints version information
OPTIONS:
-b, --baudrate <baudrate>
Set the CAN baudrate from 100 - 1000 kbaud [default: 500]
-d, --device <device>
The CAN device to target, if the interface has more than one [default: 0]
-f, --frame-type <frame-type>
Select if this frame is Standard or Remote [default: standard] [possible values: standard, remote]
-i, --id-type <id-type>
CAN ID format specifier. IDs greater that 0x7FF should be sent as extended, or they will be truncated
[default: standard] [possible values: standard, extended]
ARGS:
<action>
CAN action to perform [possible values: read, write, enable, disable, set-baudrate, status-report, status-
clear]
<msg-id>
The ID of a CAN message, must be <0x7FF for standard frames and <0x1FFFFFFF for extended frames [default: 0]
<length>
The length of this CAN message, 0 - 8 If length is greater than the number of provided bytes, they will be
filled with 00 [default: 8]
<data>...
Can data vector, up to eight bytes in length [default: 00]# Set the pwm signal behavior
$ hwc.exe set-cycles --period 1000000 --pulse 500000
# Start the pwm output
$ hwc.exe pwm start
# Stop the pwm output
$ hwc.exe pwm stop$ hwc.exe pwm --help
Control system PWM devices
USAGE:
hwc.exe pwm [OPTIONS] <action> <device>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-t, --period <period> Period, in microseconds [default: 0]
-p, --pulse <pulse> Pulse, in microseconds [default: 0]
ARGS:
<action> Kind of action to take on the pwm device [possible values: start, stop, set-cycles]
<device> The PWM device to target on this controller$ hwc.exe qep --help
Control system QEP (Encoder) peripherals
USAGE:
hwc.exe qep [FLAGS] [OPTIONS] <action>
FLAGS:
-h, --help
Prints help information
--swap-inputs
Swap the Phase A and Phase B inputs
-V, --version
Prints version information
OPTIONS:
-c, --counter-reset <counter-reset>
Select which event will cause a reset of the encoder position counter [default: max-count] [possible
values: max-count, index-event]
-a, --data <data>
Data returned from/ sent to a QEP command. Zero if the command does not return a data value
GetDirection: The direction based on the last change event
0: Clockwise, 1: Counter-Clockwise, 2: Unknown
GetPositionCount: The current position count
StartCapture: The number of edges to capture
GetPhaseError: Whether or not a phase error has been detected [default: 0]
-d, --device <device>
The QEP device to interface with, if the controller has more than one [default: 0]
-g, --edge-type <edge-type>
Edge to trigger capture events on in edge capture mode [default: rising] [possible values: rising, falling,
both]
-e, --event <event>
QEP Event to enable or disable Only used when calling 'enable event' or 'disable event' [default: unknown]
[possible values: watchdog-timeout, counter-reset-up, counter-reset-down, direction-change, phase-error,
edge-capture-done, edge-capture-cancelled, unknown]
-f, --filter-width <filter-width>
The noise filter width in nanoseconds. Set to 0 to disable noise filtering [default: 0]
-i, --index-gating <index-gating>
Select the system index gating, which effects the counter reset in index-event mode [default: a-low-b-low]
[possible values: a-low-b-low, a-low-b-high, a-high-b-low, a-high-b-high]
-m, --mode <mode>
Select encoder or edge-capture QEP operation [default: decoder] [possible values: decoder, edge-capture]
-p, --pulses-per-rev <pulses-per-rev>
The number of pulses per revolution in quadrature decoder mode [default: 0]
-w, --watchdog-timeout <watchdog-timeout>
The watchdog timeout in microseconds. The QEP watchdog will trigger an event on stalls when operating in
decoder mode
Set as zero to disable the watchdog [default: 0]
ARGS:
<action>
Kind of action to take on the qep device [possible values: configure, start-decode, stop-decode, get-
direction, get-position-count, start-capture, stop-capture, enable-event, disable-event, get-phase-
error]