Minimal data logging and sensor setup for Lincoln Utility Board
It is possible to run the Lincoln Utility Board (LUB) with a very small number of components:
- The sockets for the RPi Pico chip.
- The header pins for the SD card cable.
- The I2C connection pins for the PiicoDev sensors and clock. The clock does not have a battery; rather a super capacitor. If an advanced student ever needs milliseconds via interrupt, then a clock with a glued in battery can be provided. (Coin batteries are poisonous if swallowed, so they are not available; they may accessible in rare cases, if glued.)
- The socket for the PiicoDev display.
This is an easy way to get started and the programs work well. The sensors plug in with standard cables and for that part no soldering is required. The four steps above each require some basic soldering skills and this pre-built layout can be provided at an economical price. The LUB has space for a lot more components which allows the scope of the student projects to grow and include control of devices.
A micropython program is provided below. The driver python files used, along with the PiicoDev sensor and clock are available from Core-Electronics AU.
The PiicoDev I2C socket is on the board and the display is PiicoDev for compatibility.
This project is suitable for multiple levels at High School, depending on the approach. Plugging everything in is quite easy, along with running the provided program. The students would then examine the data that they collected.
More advanced students would edit the program and change the layout of the data file. They could even add another sensor, without too much difficulty. Advanced options are shown under Lincoln Railway.
More advanced again would be to graph the pressure, for instance, over a 2 day, then 7 day, run. Success would be confirmed by seeing the graph appear gradually.
At University biology students could use this setup with biological sensors, as the basic data capture method is straight forward.
Computer students at Uni could write interactive programs that use rotary encoder to manipulate the graphical display, of data that has been collected. It is also possible to use the RPi Pico W with WiFi. Ethernet options for a simpler board are also being developed.
- 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.




Basic unit with SD card and holder
This is the basic Micropython unit in the image above, including the SD card and holder extras. You can buy PiicoDev (PD) along with some Adafruit and DFrobot I2C components. Often the same plugs and sockets are used but you need to check on a case by case basis for software compatibility. The safest thing to get started may be to use just PD sensors and devices. (If the non PD demo creates a machine.SoftI2C object and uses that to instantiate the device, this often crashes the PD devices; machine.I2C objects usually work fine.)
You can print the plastic holders yourself if you wish. Print cylindrical housing or print rectangular housing. I use JLCPCB’s service and opt for black nylon, as it has a high melting point. I have not printed these files before, only the previous ones, that have now been edited, where there were no holes to bolt in the reader. I recommend print only one unit to start with, in case there might some printing issues. I know that the unedited versions did print properly before: cylindrical housing and rectangular housing.

Fully populated board
A similar fully populated is shown above. It is possible to simply install the components that will be used. The millisecond timer sub-breakout board is not needed if seconds are sufficiently precise. If millisecond time is needed for students that the coin cell can be glued.
Auxiliary files for python script below
- Download Python Auxiliary files for script (version 20250402)
- Contains GFX.py with some extra functions: ellipse and text
Acknowledgements
- datafile charset extractor font7x12sx
- datafile charset extractor font8x12sx
- datafile charset extractor font10x18sx
- datafile charset extractor font12x16sx
- gfx.py original
IDE (Integrated Development Environment) for Micropython
Thonny is uncomplicated and gives students a good start to using Python scripts. It is easy to upload libraries to the MCU, such as the RPi Pico and run the main program from the host computer. For more advanced students and more fully featured debugging, one can use Visual Studio Code, with the Python Add-on. Visual Studio Community itself, often fails to find libraries that are available and other similar problems occur with it, as well.
- 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.
===================
main.py
===================
from machine import Pin, ADC
import sys
import gfx
import time
import sdcard
import uos
import PiicoDev_Unified
from PiicoDev_Unified import sleep_ms
from PiicoDev_RV3028 import PiicoDev_RV3028
from PiicoDev_BME280 import PiicoDev_BME280
from PiicoDev_SSD1306 import *
import math
adc = machine.ADC(4)
vpin = machine.ADC(29)
# Assign chip select (CS) pin (and start it high)
cs = machine.Pin(17, machine.Pin.OUT)
# Intialize SPI peripheral (start with 1 MHz)
spi = machine.SPI(0,
baudrate=1000000,
polarity=0,
phase=0,
bits=8,
firstbit=machine.SPI.MSB,
sck=machine.Pin(18),
mosi=machine.Pin(19),
miso=machine.Pin(16))
# Initialize SD card
sd = sdcard.SDCard(spi, cs)
# Mount filesystem
vfs = uos.VfsFat(sd)
uos.mount(vfs, "/sd")
rtc = PiicoDev_RV3028(0, None, 20, 21)
sensor = PiicoDev_BME280(0, None, 20, 21)
display = create_PiicoDev_SSD1306(0x3C, 0, None, 20, 21, None)
displaya = create_PiicoDev_SSD1306(0x3D, 0, None, 20, 21, 1)
oledg = gfx.GFX(128, 64, display.pixel)
display.fill(0) # empty the frame buffer
display.pixel(10,10,1)
display.show()
sleep_ms(1000)
display.fill(0) # empty the frame buffer
oledg.circle(40, 40, 10, 1)
display.show()
sleep_ms(1000)
display.fill(0) # empty the frame buffer
for x in range(6):
oledg.ellipse(20+17*x, 15, 15, 7, 1)
oledg.printstring16(13,30,"Microtron", 1)
display.show()
sleep_ms(1000)
rtc.day = 4
rtc.month = 12
rtc.year = 2024
rtc.hour = 12
rtc.minute = 45
rtc.second = 00
rtc.ampm = '24' # 'AM','PM' or '24'. Defaults to 24-hr time
rtc.weekday = 4 # Rolls over at midnight, works independently of the calendar date
rtc.setDateTime() # Sets the time with the above values
# Get the current time
rtc.getDateTime()
print("sys.implementation:{}".format(sys.implementation))
print("sys.version:{}".format(sys.version))
while True:
print(rtc.timestamp())
ADC_voltage = adc.read_u16() * (3.3 / (65536))
temperature_celcius = 27 - (ADC_voltage - 0.706)/0.001721
temp_fahrenheit=32+(1.8*temperature_celcius)
tempC, presPa, humRH = sensor.values() # read all data from the sensor
pres_hPa = presPa / 100 # convert air pressurr Pascals -> hPa (or mbar, if you prefer)
print(str(tempC)+" °C " + str(pres_hPa)+" hPa " + str(humRH)+" %RH")
print("Temperature: {}°C {}°F".format(temperature_celcius,temp_fahrenheit))
adc_reading = vpin.read_u16()
adc_voltage = (adc_reading * 3.3) / 65535
vsys_voltage = adc_voltage * 3
print("""VSYS voltage:{}""".format(vsys_voltage))
displaya.fill(0)
displaya.text(rtc.timestamp(),10,5, 1)
displaya.text("Pi:{0:.1f}C,".format(temperature_celcius) + "V:{0:.1f}".format(vsys_voltage),10,20, 1)
displaya.text("T:{0:.1f}C,".format(tempC) + "P:{0:.1f}mbar".format(pres_hPa),10,35, 1)
displaya.text("Hum: {0:.1f}%RH".format(humRH),10,50, 1)
displaya.show()
with open("/sd/test04.txt",'a') as f:
f.write(rtc.timestamp() + "\n")
f.write("Temperature: {}°C {}°F\n".format(temperature_celcius,temp_fahrenheit))
f.write(str(tempC)+" °C " + str(pres_hPa)+" hPa " + str(humRH)+" %RH\n")
f.write("VSYS voltage:{}\n\n".format(vsys_voltage))
time.sleep_ms(500)
Here is some data collected on the SD card using this setup.
2024-11-22 13:55:02
Temperature: 16.28353°C 61.31035°F
20.12 °C 1010.873 hPa 49.40625 %RH
VSYS voltage:4.885873
2024-11-22 13:55:02
Temperature: 16.28353°C 61.31035°F
20.12 °C 1010.882 hPa 49.41895 %RH
VSYS voltage:4.900375
2024-11-22 13:55:03
Temperature: 16.28353°C 61.31035°F
20.12 °C 1010.875 hPa 49.42969 %RH
VSYS voltage:4.88829
2024-11-22 13:55:03
Temperature: 16.28353°C 61.31035°F
20.12 °C 1010.891 hPa 49.44043 %RH
VSYS voltage:4.890707
2024-11-22 13:55:04
Temperature: 15.81538°C 60.46769°F
20.12 °C 1010.898 hPa 49.45117 %RH
VSYS voltage:4.885873
2024-11-22 13:55:05
Temperature: 15.81538°C 60.46769°F
20.13 °C 1010.891 hPa 49.42969 %RH
VSYS voltage:4.900375
2024-11-22 13:55:05
Temperature: 15.81538°C 60.46769°F
20.13 °C 1010.866 hPa 49.44043 %RH
VSYS voltage:4.88829
2024-11-22 13:55:06
Temperature: 16.28353°C 61.31035°F
20.13 °C 1010.858 hPa 49.45215 %RH
VSYS voltage:4.883456
2024-11-22 13:55:07
Temperature: 16.28353°C 61.31035°F
20.13 °C 1010.885 hPa 49.44141 %RH
VSYS voltage:4.893124
2024-11-22 13:55:07
Temperature: 16.28353°C 61.31035°F
20.13 °C 1010.871 hPa 49.43066 %RH
VSYS voltage:4.881039
2024-11-22 13:55:08
Temperature: 16.28353°C 61.31035°F
20.13 °C 1010.864 hPa 49.41992 %RH
VSYS voltage:4.893124
2024-11-22 13:55:08
Temperature: 15.81538°C 60.46769°F
nan °C nan hPa nan %RH
VSYS voltage:4.893124
Servo Motors

Angular Servo
This type of servo motor can be set positionally. PiicoDev provide an I2C server driver. The code below is the Core-Electronics example code with the I2C and channel settings customised. Video.
# Drive an angular servo with generally safe-to-use default properties
from PiicoDev_Unified import sleep_ms
from PiicoDev_Servo import PiicoDev_Servo, PiicoDev_Servo_Driver
# Initialise the Servo Driver Module
controller = PiicoDev_Servo_Driver(bus=0,sda=20,scl=21)
# Simple setup: Attach a servo to channel 2 of the controller with default properties
servo = PiicoDev_Servo(controller, 2)
# Customised setup - Attach a servo to channel 1 of the controller with the following properties:
# - min_us: the minimum expected pulse length (microsecconds)
# - max_us: the maximum expected pulse length (microsecconds)
# - degrees: the angular range of the servo in degrees
# Uncomment the line below to use customised properties
# servo = PiicoDev_Servo(controller, 1, min_us=600, max_us=2400, degrees=180)
# Step the servo
servo.angle = 0
sleep_ms(1000)
servo.angle = 90
sleep_ms(1000)
servo.angle = 180
sleep_ms(1000)
servo.angle = 0
sleep_ms(2000)
# Sweep the servo slowly 0->180°
for x in range(0,180,5):
servo.angle = x
sleep_ms(40)
Continuous Servo
This motor is set with a direction and speed. Video.
# Drive a Continuous Rotation (a.k.a. 360 degree) servo.
from PiicoDev_Unified import sleep_ms
from PiicoDev_Servo import PiicoDev_Servo, PiicoDev_Servo_Driver
controller = PiicoDev_Servo_Driver(bus=0,sda=20,scl=21)
continuous_servo = PiicoDev_Servo(controller, 1, midpoint_us=1500, range_us=1800) # Connect a 360° servo to channel 1
continuous_servo.speed = 1 # fast
sleep_ms(1000)
continuous_servo.speed = 0.2 # slow
sleep_ms(1000)
continuous_servo.speed = -0.2 # slow reverse
sleep_ms(1000)
continuous_servo.speed = -1 # fast reverse
sleep_ms(1000)
continuous_servo.speed = 0 # stop
Relay Board and LED Array
Here is a video of the relay board working with the LED array and the Lincoln Utility board. If users want an updated version of this board, with greater spacing between the relays and slots in the board, then that can be arranged.


This LincolnRelay Board is available for $10NZD plus postage, as shown directly below. There is an option to provide the board with the SMD transistors and optocoupler chips mounted; please enquire. Relay mounting is also available.

import latch74hc259
import time
alatch0=latch74hc259.SN74HC259(8)
alatch1=latch74hc259.SN74HC259(1)
alatch2=latch74hc259.SN74HC259(26)
alatch1.d0.value=0
alatch1.d1.value=0
alatch1.d2.value=0
alatch1.d3.value=0
alatch1.d4.value=0
alatch1.d5.value=0
alatch1.d6.value=0
alatch1.d7.value=0
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 << 0) > 0
alatch2.d1.value=(1 << count) & (1 << 1) > 0
alatch2.d2.value=(1 << count) & (1 << 2) > 0
alatch2.d3.value=(1 << count) & (1 << 3) > 0
alatch2.d4.value=(1 << count) & (1 << 4) > 0
alatch2.d5.value=(1 << count) & (1 << 5) > 0
alatch2.d6.value=(1 << count) & (1 << 6) > 0
alatch2.d7.value=(1 << count) & (1 << 7) > 0
count += 1
if count==8:
count=0
time.sleep_ms(500)
Stepper Motor. (LincolnStepper)
Here is a video of a stepper motor running. The 5 volt supply can be provided from the Lincoln Utility Board, by setting the jumpers for VBUS or VSYS when using USB supply. Here is a video of a test of the user friendly driver for this single motor scenario.

import latch74hc259
import time
alatch0=latch74hc259.SN74HC259(8)
alatch1=latch74hc259.SN74HC259(1)
alatch2=latch74hc259.SN74HC259(26)
alatch1.d0.value=0
alatch1.d1.value=0
alatch1.d2.value=0
alatch1.d3.value=0
alatch1.d4.value=0
alatch1.d5.value=0
alatch1.d6.value=0
alatch1.d7.value=0
direction=False
step=False
count=0
while 1:
alatch1.d0.value=direction
alatch1.d1.value=True
time.sleep_ms(10)
alatch1.d1.value=False
if count < 8:
alatch2.d0.value=(1 << count) & (1 << 0) > 0
alatch2.d1.value=(1 << count) & (1 << 1) > 0
alatch2.d2.value=(1 << count) & (1 << 2) > 0
alatch2.d3.value=(1 << count) & (1 << 3) > 0
alatch2.d4.value=(1 << count) & (1 << 4) > 0
alatch2.d5.value=(1 << count) & (1 << 5) > 0
alatch2.d6.value=(1 << count) & (1 << 6) > 0
alatch2.d7.value=(1 << count) & (1 << 7) > 0
else:
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
direction= not direction
time.sleep_ms(50)
========================================
lincolnstepper1driver.py
# user friendly driver for single motor scenario
========================================
# Copyright (c) 2024 Stephen Eichler for Microtron Ltd NZ
# 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
# test of the user friendly driver for this single motor scenario
=======================================
import lincolnstepper1driver
import time
import sys
stpdrv0=lincolnstepper1driver.STEPDRV(1)
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()
Stepper motor with 2 relays peripheral board. (LincolnStepper2Relay)
This Lincoln peripheral board has one stepper driver and two relays. Here is the test video.


The LincolnStepRelay is suited to the Lincoln utility board or the Welton/WeltonCpy boards, as a peripheral. It typically runs on 3.3V logic but the A4988 drivers can run on 5V as well. 12V power is needed for the relay coils, however if this is the same voltage as for the stepper motor driver then only the one external supply is needed. The jumpers can be set to implement this option. Right and left jumpers should occupy the same/parallel position.
This I2C LincolnStepRelay Board is available for $10NZD plus postage, as shown directly below. There is an option to provide the board with the SMD transistors and optocoupler chip mounted; please enquire. Relay mounting is also available.

=========================================================
lincolnstepper1driver2relay.py
# Software driver for Lincoln peripheral 1 stepper driver & 2 relays board
=========================================================
# Copyright (c) 2024 Stephen Eichler for Microtron Ltd NZ
# 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
from machine import Pin
class STEPDRV:
def __init__(self, latchid):
if latchid == 1:
self.alatchn=latch74hc259.SN74HC259(1, reset=True)
self.pin8 = Pin(8,Pin.OUT)
self.pin8.value(1)
self.pin26 = Pin(26,Pin.OUT)
self.pin26.value(1)
elif latchid == 2:
self.alatchn=latch74hc259.SN74HC259(26, reset=True)
self.pin8 = Pin(8,Pin.OUT)
self.pin8.value(1)
self.pin1 = Pin(1,Pin.OUT)
self.pin1.value(1)
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 relay2(self):
return self.alatchn.d3.value
@relay2.setter
def relay2(self, value):
self.alatchn.d3.value=value
@property
def relay1(self):
return self.alatchn.d2.value
@relay1.setter
def relay1(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)
============================================
lincstep1relay2more_main.py
============================================
import lincolnstepper1driver2relay
import time
stpdrv0=lincolnstepper1driver2relay.STEPDRV(1)
stpdrv0.DV1.ms1 = 0
stpdrv0.DV1.ms2 = 0
stpdrv0.DV1.ms3 = 0
direction=False
count=0
loops=0
while 1:
stpdrv0.DV1.direction = direction
stpdrv0.DV1.step()
count += 1
if count==16:
count=0
loops += 1
direction= not direction
if loops % 2 == 0:
stpdrv0.relay1 = not stpdrv0.relay1
else:
stpdrv0.relay2 = not stpdrv0.relay2
time.sleep_ms(50)
Stepper Motors: 3 drivers on peripheral board. (Lincoln3Stepper)
Here is a video of a test of the user friendly 3 hardware driver, software driver. Here is a video of the hardware test.

===========================================
lincolnstepper3driver.py
# user friendly 3 hardware driver, software driver
===========================================
# Copyright (c) 2024 Stephen Eichler for Microtron Ltd NZ
# 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
# test of the user friendly 3 hardware driver, software driver
===========================================
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
================================================
lincstep3more_main.py
# Hardware test with the user friendly 3 hardware driver, software driver
================================================
import lincolnstepper3driver
import time
import sys
stpdrv0=lincolnstepper3driver.STEPDRV()
stpdrv0.DV1.ms1 = 0
stpdrv0.DV1.ms2 = 0
stpdrv0.DV1.ms3 = 0
stpdrv0.DV2.ms1 = 0
stpdrv0.DV2.ms2 = 0
stpdrv0.DV2.ms3 = 0
stpdrv0.DV3.ms1 = 0
stpdrv0.DV3.ms2 = 0
stpdrv0.DV3.ms3 = 0
direction=False
count=0
while 1:
stpdrv0.DV1.direction = direction
stpdrv0.DV2.direction = direction
stpdrv0.DV3.direction = direction
stpdrv0.DV1.step()
stpdrv0.DV2.step()
stpdrv0.DV3.step()
count += 1
if count==16:
count=0
direction= not direction
time.sleep_ms(50)