Lincoln Railway or Utility 24 Channel MBOB Peripherals
8 relay peripheral board. Eight channel relay board controllable via a 24 Way IDC Line Socket on a ribbon cable. This unit can connect to the 3-Bank-Lincoln-Break-Out-Board. It uses 8 optocouplers to control transistors fed with 12V to drive the relay coils. The source of the 12V supply can be an external power adapter or it can be supplied via the ribbon cable. The sources of the ribbon 12V on the Microtron Break Out Board (MBOB) are external supply or RS232 pin 9 (Comes from computer power supply and should normally be avoided). The jumpers shown are set to supply from a power adapter. The unit can also connect to MBOBs with 8 digital outputs. This requires the use of alternative connections and not the IDC Line Socket at the MBOB end. The needed connections are common/MCU-gnd and the 8 digital signals.
- MCU units with sensors and datalogging.
- Break out boards with user interface. Suitable for datalogging and interactive function including stepper motors.
- Break out board customizations. Sensors, stepper motors, WiFi, SD cards and Fram.
- Design of circuit boards, including design of circuit boards that are a customization of a break out board pilot project.
- WiFi and server collection of data.


Lincoln Railway 24 Channel MBOB. The dual row header pins provide 8 logic connections each and connect to these peripherals. See Lincoln Railway for further information. Pairs of pins are connected. Pin 1 can be board 5V (Vbus or Vsys), 3.3V from Pico or external power ground. The 3 Stepper Board uses 5V. The 8 Relay Board has been updated to NC for pin 1. The A4988 has a maximum voltage of 35V. The DRV8825 also plugs in correctly and can operate at up to 45V. The DRV8825 requires a minimum STEP pulse duration of 1.9µs; the A4988 requires 1µs minimum. The DRV8825 offers 1/32 micro-stepping, whereas the A4988 only goes down to 1/16-step.

Single stepper driver peripheral. This board has been tested and functions correctly with low cost A4988 stepper motor driver modules. (See also DRV8825.) There is a logic pin for each available input to the stepper driver mentioned above, in this peripheral board. If jumpers connect sleep and reset together, isolating them from the logic inputs and enable is also isolated from its logic input, then there are these three logic lines that are available for extra connections. To do this jumper leads would be attached to the three header pins coming from the logic line connections.
- MCU units with sensors and datalogging.
- Break out boards with user interface. Suitable for datalogging and interactive function including stepper motors.
- Break out board customizations. Sensors, stepper motors, WiFi, SD cards and Fram.
- Design of circuit boards, including design of circuit boards that are a customization of a break out board pilot project.
- WiFi and server collection of data.

This one motor A4988 stepper board can also be used with the IoExpander board.
# latch74hc259.py
# MIT License (MIT)
# Copyright (c) 2024 Microtron Ltd NZ - Stephen Eichler
# https://opensource.org/licenses/MIT
# Written 24/12/2024
# import latch74hc259
# alatch0=latch74hc259.SN74HC259(8)
# alatch1=latch74hc259.SN74HC259(1)
# alatch2=latch74hc259.SN74HC259(26)
# alatch0.d0.value=1
from machine import Pin
class SN74HC259:
def __init__(self, enablepin, reverse=False, reset=False, addrb0pin=6, addrb1pin=7, addrb2pin=10, valpin=9):
self.d0 = Channel(self, 0)
self.d1 = Channel(self, 1)
self.d2 = Channel(self, 2)
self.d3 = Channel(self, 3)
self.d4 = Channel(self, 4)
self.d5 = Channel(self, 5)
self.d6 = Channel(self, 6)
self.d7 = Channel(self, 7)
self.epin = Pin(enablepin,Pin.OUT)
self.epin.value(1)
self.pin0 = Pin(addrb0pin,Pin.OUT)
self.pin1 = Pin(addrb1pin,Pin.OUT)
self.pin2 = Pin(addrb2pin,Pin.OUT)
self.pinval = Pin(valpin,Pin.OUT)
self.rev = reverse
if reset:
self.d0.value=0
self.d1.value=0
self.d2.value=0
self.d3.value=0
self.d4.value=0
self.d5.value=0
self.d6.value=0
self.d7.value=0
def _set_lat_addr(self, addr):
if addr < 0 or addr >= 8:
addr=0
if self.rev:
addr=7-addr
self.pin0.value(addr & 1)
self.pin1.value((addr & 2) == 2)
self.pin2.value((addr & 4) == 4)
def _set_value(self, channel, val):
self.epin.value(1)
self._set_lat_addr(channel.channel_index)
self.pinval.value(bool(val))
self.epin.value(0)
self.epin.value(1)
class Channel:
"""An instance of a single channel for a multi-channel address latch.
**All available channels are created automatically and should not be created by the user**"""
def __init__(self, alat_instance, index):
self._alat = alat_instance
self.channel_index = index
self._value=0
@property
def value(self):
"""The current value for the channel."""
return self._value
@value.setter
def value(self, value):
self._value=value
self._alat._set_value(self, value)
----------------------------------------------------------
# lincolnstepper1driver.py
# MIT License (MIT)
# Copyright (c) 2025 Microtron Ltd NZ - Stephen Eichler
# https://opensource.org/licenses/MIT
# Written 13/3/2025
# import lincolnstepper1driver
# stpdrv0=lincolnstepper1driver.STEPDRV(1)
# stpdrv0.DV1.ms1 = 1
# stpdrv0.DV2.dir = 1
# stpdrv0.DV3.step()
import latch74hc259
import time
class STEPDRV:
def __init__(self, latchid):
if latchid == 1:
self.alatchn=latch74hc259.SN74HC259(1, reset=True)
elif latchid == 2:
self.alatchn=latch74hc259.SN74HC259(26, reset=True)
self.DV1 = Driver(self, 1)
@property
def enable(self):
return self.alatchn.d7.value
@enable.setter
def enable(self, value):
self.alatchn.d7.value=value
@property
def reset(self):
return self.alatchn.d3.value
@reset.setter
def reset(self, value):
self.alatchn.d3.value=value
@property
def sleep(self):
return self.alatchn.D2.value
@sleep.setter
def sleep(self, value):
self.alatchn.d2.value=value
def _step(self, driver):
Dbit = self.alatchn.d1
if Dbit != "":
Dbit.value = 0
time.sleep_us(2)
Dbit.value = 1
time.sleep_us(2)
Dbit.value = 0
#def _set_value(self, channel, val):
def _set_ms1(self, driver, val):
self._setalatbit(6, val)
def _set_ms2(self, driver, val):
self._setalatbit(5, val)
def _set_ms3(self, driver, val):
self._setalatbit( 4, val)
def _set_dir(self, driver, val):
self._setalatbit(0, val)
def _setalatbit(self, dbitindex, val):
if dbitindex == 0:
self.alatchn.d0.value = val
elif dbitindex == 1:
self.alatchn.d1.value = val
elif dbitindex == 2:
self.alatchn.d2.value = val
elif dbitindex == 3:
self.alatchn.d3.value = val
elif dbitindex == 4:
self.alatchn.d4.value = val
elif dbitindex == 5:
self.alatchn.d5.value = val
elif dbitindex == 6:
self.alatchn.d6.value = val
elif dbitindex == 7:
self.alatchn.d7.value = val
class Driver:
def __init__(self, stpdv_instance, index):
self._stpdv = stpdv_instance
self.driver_index = index
self._ms1=0
self._ms2=0
self._ms3=0
self._dir=0
@property
def ms1(self):
return self._ms1
@property
def ms2(self):
return self._ms2
@property
def ms3(self):
return self._ms3
@property
def direction(self):
return self._direction
@ms1.setter
def ms1(self, value):
self._ms1=value
self._stpdv._set_ms1(self, value)
@ms2.setter
def ms2(self, value):
self._ms2=value
self._stpdv._set_ms2(self, value)
@ms3.setter
def ms3(self, value):
self._ms3=value
self._stpdv._set_ms3(self, value)
@direction.setter
def direction(self, value):
self._dir=value
self._stpdv._set_dir(self, value)
def step(self):
self._stpdv._step(self)
----------------------------------------------------------
# lincstep1_main.py
# MIT License (MIT)
# Copyright (c) 2025 Microtron Ltd NZ - Stephen Eichler
# https://opensource.org/licenses/MIT
import lincolnstepper1driver
import time
import sys
stpdrv0=lincolnstepper1driver.STEPDRV(1)
# Step through the program and allow measurement of the pin voltages.
print ("Press enter to continue.")
while 1:
stpdrv0.DV1.ms1 = 1
time.sleep_ms(500)
print ("stpdrv0.DV1.ms1")
data = sys.stdin.buffer.read(2)
stpdrv0.DV1.ms1 = 0
stpdrv0.DV1.ms2 = 1
time.sleep_ms(500)
print ("stpdrv0.DV1.ms2")
data = sys.stdin.buffer.read(2)
stpdrv0.DV1.ms2 = 0
stpdrv0.DV1.ms3 = 1
time.sleep_ms(500)
print ("stpdrv0.DV1.ms3")
data = sys.stdin.buffer.read(2)
stpdrv0.DV1.ms3 = 0
stpdrv0.DV1.direction = 1
time.sleep_ms(500)
print ("stpdrv0.DV1.direction")
data = sys.stdin.buffer.read(2)
stpdrv0.DV1.direction = 0
stpdrv0.enable = 1
time.sleep_ms(500)
print ("stpdrv0.enable")
data = sys.stdin.buffer.read(2)
stpdrv0.enable = 0
stpdrv0.reset = 1
time.sleep_ms(500)
print ("stpdrv0.reset")
data = sys.stdin.buffer.read(2)
stpdrv0.reset = 0
stpdrv0.sleep = 1
time.sleep_ms(500)
print ("stpdrv0.sleep")
data = sys.stdin.buffer.read(2)
stpdrv0.sleep = 0
stpdrv0.DV1.step()
Triple stepper driver peripheral. This board can be used both with the Lincoln Utility board and the IoExpander. The code below is for the Lincoln Utility board.

# lincolnstepper1driver.py
# MIT License (MIT)
# Copyright (c) 2025 Microtron Ltd NZ - Stephen Eichler
# https://opensource.org/licenses/MIT
# Written 13/3/2025
# import lincolnstepper3driver
# stpdrv0=lincolnstepper3driver.STEPDRV()
# stpdrv0.DV1.ms1 = 1
# stpdrv0.DV2.dir = 1
# stpdrv0.DV3.step()
import latch74hc259
import time
class STEPDRV:
def __init__(self):
self.alatch1=latch74hc259.SN74HC259(1, reset=True)
self.alatch2=latch74hc259.SN74HC259(26, reset=True)
self.DV1 = Driver(self, 1)
self.DV2 = Driver(self, 2)
self.DV3 = Driver(self, 3)
@property
def spare(self):
return self.alatch2.d0.value
@spare.setter
def spare(self, value):
self.alatch2.d0.value=value
def _step(self, driver):
dbitindex = {3:2,2:7,1:4}[driver.driver_index]
dbyteindex = {3:2,2:2,1:1}[driver.driver_index]
alatch = ""
if dbyteindex == 1:
alatch = self.alatch1
else:
alatch = self.alatch2
Dbit = ""
if dbitindex == 2:
Dbit = alatch.d2
elif dbitindex == 4:
Dbit = alatch.d4
elif dbitindex == 7:
Dbit = alatch.d7
if Dbit != "":
Dbit.value = 0
time.sleep_us(2)
Dbit.value = 1
time.sleep_us(2)
Dbit.value = 0
def _setalatbit(self, dbyteindex, dbitindex, val):
alatch = ""
if dbyteindex == 1:
alatch = self.alatch1
else:
alatch = self.alatch2
if dbitindex == 0:
alatch.d0.value = val
elif dbitindex == 1:
alatch.d1.value = val
elif dbitindex == 2:
alatch.d2.value = val
elif dbitindex == 3:
alatch.d3.value = val
elif dbitindex == 4:
alatch.d4.value = val
elif dbitindex == 5:
alatch.d5.value = val
elif dbitindex == 6:
alatch.d6.value = val
elif dbitindex == 7:
alatch.d7.value = val
def _set_ms1(self, driver, val):
dbitindex = {3:5,2:2,1:7}[driver.driver_index]
dbyteindex = {3:2,2:1,1:1}[driver.driver_index]
self._setalatbit(dbyteindex, dbitindex, val)
def _set_ms2(self, driver, val):
dbitindex = {3:4,2:1,1:6}[driver.driver_index]
dbyteindex = {3:2,2:1,1:1}[driver.driver_index]
self._setalatbit(dbyteindex, dbitindex, val)
def _set_ms3(self, driver, val):
dbitindex = {3:3,2:0,1:5}[driver.driver_index]
dbyteindex = {3:2,2:1,1:1}[driver.driver_index]
self._setalatbit(dbyteindex, dbitindex, val)
def _set_dir(self, driver, val):
dbitindex = {3:1,2:6,1:3}[driver.driver_index]
dbyteindex = {3:2,2:2,1:1}[driver.driver_index]
self._setalatbit(dbyteindex, dbitindex, val)
class Driver:
def __init__(self, stpdv_instance, index):
self._stpdv = stpdv_instance
self.driver_index = index
self._ms1=0
self._ms2=0
self._ms3=0
self._dir=0
@property
def ms1(self):
return self._ms1
@property
def ms2(self):
return self._ms2
@property
def ms3(self):
return self._ms3
@property
def direction(self):
return self._direction
@ms1.setter
def ms1(self, value):
self._ms1=value
self._stpdv._set_ms1(self, value)
@ms2.setter
def ms2(self, value):
self._ms2=value
self._stpdv._set_ms2(self, value)
@ms3.setter
def ms3(self, value):
self._ms3=value
self._stpdv._set_ms3(self, value)
@direction.setter
def direction(self, value):
self._dir=value
self._stpdv._set_dir(self, value)
def step(self):
self._stpdv._step(self)
-----------------------------------------------------------------
# lincstep3_main.py
import lincolnstepper3driver
import time
import sys
stpdrv0=lincolnstepper3driver.STEPDRV()
print ("Press enter to continue.")
while 1:
stpdrv0.DV1.ms1 = 1
time.sleep_ms(500)
print ("stpdrv0.DV1.ms1")
data = sys.stdin.buffer.read(2)
stpdrv0.DV1.ms1 = 0
stpdrv0.DV1.ms2 = 1
time.sleep_ms(500)
print ("stpdrv0.DV1.ms2")
data = sys.stdin.buffer.read(2)
stpdrv0.DV1.ms2 = 0
stpdrv0.DV1.ms3 = 1
time.sleep_ms(500)
print ("stpdrv0.DV1.ms3")
data = sys.stdin.buffer.read(2)
stpdrv0.DV1.ms3 = 0
stpdrv0.DV1.direction = 1
time.sleep_ms(500)
print ("stpdrv0.DV1.direction")
data = sys.stdin.buffer.read(2)
stpdrv0.DV1.direction = 0
stpdrv0.DV2.ms1 = 1
time.sleep_ms(500)
print ("stpdrv0.DV2.ms1")
data = sys.stdin.buffer.read(2)
stpdrv0.DV2.ms1 = 0
stpdrv0.DV2.ms2 = 1
time.sleep_ms(500)
print ("stpdrv0.DV2.ms2")
data = sys.stdin.buffer.read(2)
stpdrv0.DV2.ms2 = 0
stpdrv0.DV2.ms3 = 1
time.sleep_ms(500)
print ("stpdrv0.DV2.ms3")
data = sys.stdin.buffer.read(2)
stpdrv0.DV2.ms3 = 0
stpdrv0.DV2.direction = 1
time.sleep_ms(500)
print ("stpdrv0.DV2.direction")
data = sys.stdin.buffer.read(2)
stpdrv0.DV2.direction = 0
stpdrv0.DV3.ms1 = 1
time.sleep_ms(500)
print ("stpdrv0.DV3.ms1")
data = sys.stdin.buffer.read(2)
stpdrv0.DV3.ms1 = 0
stpdrv0.DV3.ms2 = 1
time.sleep_ms(500)
print ("stpdrv0.DV3.ms2")
data = sys.stdin.buffer.read(2)
stpdrv0.DV3.ms2 = 0
stpdrv0.DV3.ms3 = 1
time.sleep_ms(500)
print ("stpdrv0.DV3.ms3")
data = sys.stdin.buffer.read(2)
stpdrv0.DV3.ms3 = 0
stpdrv0.DV3.direction = 1
time.sleep_ms(500)
print ("stpdrv0.DV3.direction")
data = sys.stdin.buffer.read(2)
stpdrv0.DV3.direction = 0
stpdrv0.DV1.step()
stpdrv0.DV2.step()
stpdrv0.DV3.step()
stpdrv0.spare = 1
time.sleep_ms(500)
print ("stpdrv0.spare")
data = sys.stdin.buffer.read(2)
stpdrv0.spare = 0
Lincoln LED Bank peripheral
See Video The Lincoln LED Bank (LLB) uses the two edge LEDs for 5V and 12V detection. 5V can be sourced from Vsys depending on jumper positions; there is limited current available; jumpers can also be set to provide 3.3V. 12V can come from an external power supply or even an RS232 cable, depending on jumper positions. In the case of 12V, it is often not used as 12V supply to peripherals is often direct to the board. The Lincoln Relay Board peripheral has this flexibility.

import latch74hc259
import time
alatch0=latch74hc259.SN74HC259(8)
alatch1=latch74hc259.SN74HC259(1)
alatch2=latch74hc259.SN74HC259(26)
count=0
while 1:
alatch1.d0.value=(1 << count) & (1 << 0) > 0
alatch1.d1.value=(1 << count) & (1 << 1) > 0
alatch1.d2.value=(1 << count) & (1 << 2) > 0
alatch1.d3.value=(1 << count) & (1 << 3) > 0
alatch1.d4.value=(1 << count) & (1 << 4) > 0
alatch1.d5.value=(1 << count) & (1 << 5) > 0
alatch1.d6.value=(1 << count) & (1 << 6) > 0
alatch1.d7.value=(1 << count) & (1 << 7) > 0
alatch2.d0.value=(1 << count) & (1 << 8) > 0
alatch2.d1.value=(1 << count) & (1 << 9) > 0
alatch2.d2.value=(1 << count) & (1 << 10) > 0
alatch2.d3.value=(1 << count) & (1 << 11) > 0
alatch2.d4.value=(1 << count) & (1 << 12) > 0
alatch2.d5.value=(1 << count) & (1 << 13) > 0
alatch2.d6.value=(1 << count) & (1 << 14) > 0
alatch2.d7.value=(1 << count) & (1 << 15) > 0
count += 1
if count==16:
count=0
time.sleep_ms(500)