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)