mirror of
https://github.com/dekuNukem/exixe.git
synced 2025-10-24 11:11:04 -07:00
Adding CircuitPython support
This commit includes modified versions of the provided RPi Python library and examples, tested using the Feather M4 Express
This commit is contained in:
16
circuitpython_examples/1_LED_test/1_led_test.py
Normal file
16
circuitpython_examples/1_LED_test/1_led_test.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# exixe modules: https://github.com/dekuNukem/exixe
|
||||
# python library docs: https://github.com/dekuNukem/exixe/tree/master/python_library
|
||||
# Demo 1: LED Test
|
||||
from exixe import Exixe
|
||||
import busio
|
||||
import board
|
||||
import time
|
||||
|
||||
spi = busio.SPI(board.SCK, board.MOSI)
|
||||
cs_pin = board.D4
|
||||
|
||||
my_tube = Exixe(cs_pin, spi)
|
||||
|
||||
while True:
|
||||
my_tube.set_led(127, 0, 127) # Purple
|
||||
time.sleep(1)
|
||||
@@ -0,0 +1,21 @@
|
||||
# exixe modules: https://github.com/dekuNukem/exixe
|
||||
# python library docs: https://github.com/dekuNukem/exixe/tree/master/python_library
|
||||
# Demo 2: loop digits test
|
||||
from exixe import Exixe
|
||||
import busio
|
||||
import board
|
||||
import time
|
||||
|
||||
spi = busio.SPI(board.SCK, board.MOSI)
|
||||
cs_pin = board.D4
|
||||
|
||||
my_tube = Exixe(cs_pin, spi)
|
||||
|
||||
# Update led to be purple & count from 0 -> 9 and reset back to 0 at 10
|
||||
count = 0
|
||||
while True:
|
||||
my_tube.set_led(127, 0, 127) # Purple
|
||||
my_tube.set_digit(count)
|
||||
|
||||
count = (count + 1) % 10
|
||||
time.sleep(1)
|
||||
@@ -0,0 +1,27 @@
|
||||
# exixe modules: https://github.com/dekuNukem/exixe
|
||||
# python library docs: https://github.com/dekuNukem/exixe/tree/master/python_library
|
||||
# Demo 3: Loop digits with crossfade animation
|
||||
from exixe import Exixe
|
||||
import busio
|
||||
import board
|
||||
import time
|
||||
|
||||
spi = busio.SPI(board.SCK, board.MOSI)
|
||||
cs_pin = board.D4
|
||||
|
||||
my_tube = Exixe(cs_pin, spi)
|
||||
|
||||
count = 0
|
||||
# my_tube.crossfade_run is a non-blocking call which can allow a main loop to handle more tasks
|
||||
# Ideally there will be a 33ms delay but this needs to take in account all other tasks within the loop
|
||||
while True:
|
||||
my_tube.set_led(127, 64, 0) # Orange
|
||||
|
||||
# Initialize the crossfade with next digit and how many frames desired.
|
||||
my_tube.crossfade_init(count, 30)
|
||||
while my_tube.animation_in_progress:
|
||||
my_tube.crossfade_run()
|
||||
# 30 frames at a 33ms delay will ~1 second
|
||||
time.sleep(0.033)
|
||||
|
||||
count = (count + 1) % 10
|
||||
@@ -0,0 +1,27 @@
|
||||
# exixe modules: https://github.com/dekuNukem/exixe
|
||||
# python library docs: https://github.com/dekuNukem/exixe/tree/master/python_library
|
||||
# Demo 4: Loop digits on two tubes
|
||||
from exixe import Exixe
|
||||
import busio
|
||||
import board
|
||||
import time
|
||||
|
||||
spi = busio.SPI(board.SCK, board.MOSI)
|
||||
|
||||
# Update these to the cs pins you're using
|
||||
cs_pin_1 = board.D4
|
||||
cs_pin_2 = board.D6
|
||||
|
||||
my_tube_1 = Exixe(cs_pin_1, spi)
|
||||
my_tube_2 = Exixe(cs_pin_2, spi)
|
||||
|
||||
# Update LED's, have 1 tube count up, the other tube count down
|
||||
count = 0
|
||||
while True:
|
||||
my_tube_1.set_led(127, 0, 127) # Purple
|
||||
my_tube_2.set_led(127, 127, 0) # Yellow
|
||||
my_tube_1.set_digit(count)
|
||||
my_tube_2.set_digit(10 - count)
|
||||
|
||||
count = (count + 1) % 10
|
||||
time.sleep(1)
|
||||
@@ -0,0 +1,34 @@
|
||||
# exixe modules: https://github.com/dekuNukem/exixe
|
||||
# python library docs: https://github.com/dekuNukem/exixe/tree/master/python_library
|
||||
# Demo 5: Loop digits on two tubes with crossfade animation
|
||||
from exixe import Exixe
|
||||
import busio
|
||||
import board
|
||||
import time
|
||||
|
||||
spi = busio.SPI(board.SCK, board.MOSI)
|
||||
|
||||
# Update these to the cs pins you're using
|
||||
cs_pin_1 = board.D4
|
||||
cs_pin_2 = board.D6
|
||||
|
||||
my_tube_1 = Exixe(cs_pin_1, spi)
|
||||
my_tube_2 = Exixe(cs_pin_2, spi)
|
||||
|
||||
count = 0
|
||||
# my_tube.crossfade_run is a non-blocking call which can allow a main loop to handle more tasks
|
||||
# Ideally there will be a 33ms delay but this needs to take in account all other tasks within the loop
|
||||
while True:
|
||||
my_tube_1.set_led(127, 64, 0) # Orange
|
||||
my_tube_2.set_led(127, 0, 127) # Purple
|
||||
|
||||
# Initialize the crossfade with next digit and how many frames desired.
|
||||
my_tube_1.crossfade_init(count, 30)
|
||||
my_tube_2.crossfade_init(10 - count, 30)
|
||||
while my_tube_1.animation_in_progress and my_tube_2.animation_in_progress:
|
||||
my_tube_1.crossfade_run()
|
||||
my_tube_2.crossfade_run()
|
||||
# 30 frames at a 33ms delay will ~1 second
|
||||
time.sleep(0.033)
|
||||
|
||||
count = (count + 1) % 10
|
||||
28
circuitpython_library/README.md
Normal file
28
circuitpython_library/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# exixe Raspberry Pi/Python library
|
||||
|
||||
This is a simple Python library for exixe modules intended to be used on Adafruit CircuitPython devices.
|
||||
|
||||
# Requirements
|
||||
Before you use this library, you should familiarize yourself with CircuitPython by reading through the
|
||||
following guides on the Adafruit Learning System. They will give you a good understanding of how
|
||||
CircuitPython works, and differs from normal Python. They will also help you set up an appropriate
|
||||
Dev environment including the libraries you'll need, and help you make your your CircuitPython
|
||||
compatible board is set up properly
|
||||
https://learn.adafruit.com/welcome-to-circuitpython
|
||||
https://learn.adafruit.com/circuitpython-essentials/circuitpython-essentials
|
||||
https://learn.adafruit.com/circuitpython-basics-i2c-and-spi/spi-devices
|
||||
|
||||
# Usage
|
||||
The code has some good documentation that I dont want to repeat but this will be a general overview on how to use the library.
|
||||
1. Create SPI initialization
|
||||
> spi = busio.SPI(board.SCK, board.MOSI) <br>
|
||||
|
||||
2. Identify CS pin to control the individual exixe
|
||||
> cs_pin = board.D4
|
||||
|
||||
3. Create exixe object
|
||||
> exixe_1 = Exixe(cs_pin,spi)
|
||||
|
||||
4. Control object
|
||||
> exixe_1.set_digit(5) <br>
|
||||
exixe_1.set_led(127,0,0) # turn led to red
|
||||
202
circuitpython_library/exixe.py
Normal file
202
circuitpython_library/exixe.py
Normal file
@@ -0,0 +1,202 @@
|
||||
"""
|
||||
Modified from the RPi Python Library, this version is compatible with Adafruit CircuitPython
|
||||
"""
|
||||
import time
|
||||
import busio # should replace SPIdev
|
||||
import digitalio # Replaces GPIO
|
||||
import board # Adafruit Circuitpython Board library
|
||||
|
||||
EXIXE_SPI_BUF_SIZE = 16
|
||||
EXIXE_SPI_HEADER = 0xAA
|
||||
EXIXE_SPI_HEADER_OD = 0xab
|
||||
EXIXE_ANIMATION_FRAME_DURATION_MS = 33
|
||||
EXIXE_ANIMATION_IN_PROGRESS = 1
|
||||
EXIXE_ANIMATION_FINISHED = 0
|
||||
MAX_BRIGHTNESS = 127
|
||||
|
||||
millis = lambda: int(round(time.time() * 1000))
|
||||
|
||||
|
||||
def cap_digit(digit):
|
||||
"""
|
||||
This function will ensure that entered digit doesnt go above 9 to represent all single digits.
|
||||
Also due to Exixe SPI technical details the 10th slot in the bytes array controls the 0 digit.
|
||||
Here is where we will convert the 0 digit to 10 to represent this correctly.
|
||||
More info here: https://github.com/dekuNukem/exixe/blob/master/technical_details.md
|
||||
"""
|
||||
digit = digit % 10
|
||||
if digit == 0:
|
||||
digit = 10
|
||||
return digit
|
||||
|
||||
|
||||
def cap_brightness(brightness):
|
||||
"""
|
||||
This function will ensure any entered brightness will not go above the MAX_BRIGHTNESS
|
||||
"""
|
||||
if brightness > MAX_BRIGHTNESS:
|
||||
brightness = MAX_BRIGHTNESS
|
||||
if brightness < 0:
|
||||
brightness = 0
|
||||
return brightness
|
||||
|
||||
|
||||
class Exixe:
|
||||
def __init__(self, cs_pin, spi, overdrive=False, initial_val=-1):
|
||||
"""
|
||||
This class is used to control an individual Exixe module's digit, and led along with providing a way to simulate
|
||||
a crossfade animation
|
||||
:param cs_pin: This is the control pin for the specific exixe. 1 per exixe module
|
||||
:param spi: spi object from the spiDev module. Only 1 spi object needs to be created for multiple exixe modules
|
||||
Example below:
|
||||
spi = spidev.SpiDev()
|
||||
spi.open(0, 0)
|
||||
spi.max_speed_hz = 7800000
|
||||
:param overdrive: (Optional) Used to enable current overdrive to make the tube brighter
|
||||
:param initial_val: (Optional) initial digit to set
|
||||
"""
|
||||
self.cs_pin = cs_pin
|
||||
self.spi = spi
|
||||
self.spi_buf = [0] * EXIXE_SPI_BUF_SIZE
|
||||
|
||||
self.animation_in_progress = False
|
||||
# Digits will be stored in values 0-9 and when needed to update SPI buffer use the cap_digit to ensure correct
|
||||
# location/index is used.
|
||||
self.animation_src_digit = 0
|
||||
self.animation_dest_digit = 0
|
||||
self.animation_start_frame = 0
|
||||
self.animation_duration = 0
|
||||
self.animation_brightness = 0
|
||||
self.animation_step = 0
|
||||
|
||||
self.red = 0
|
||||
self.green = 0
|
||||
self.blue = 0
|
||||
|
||||
self.overdrive = overdrive
|
||||
|
||||
self.cs = digitalio.DigitalInOut(self.cs_pin)
|
||||
self.cs.direction = digitalio.Direction.OUTPUT
|
||||
self.cs.value = True
|
||||
if initial_val >= 0:
|
||||
self.set_digit(initial_val)
|
||||
|
||||
def set_digit(self, digit, brightness=MAX_BRIGHTNESS):
|
||||
"""
|
||||
This method will set the single digit specified and will leave the LED unaffected
|
||||
"""
|
||||
brightness = cap_brightness(brightness)
|
||||
self.animation_src_digit = digit
|
||||
self.clear_digit()
|
||||
self.set_en_bit_to_1()
|
||||
# Set first byte of header according to overdrive flag
|
||||
self.spi_buf[0] = EXIXE_SPI_HEADER_OD if self.overdrive else EXIXE_SPI_HEADER
|
||||
self.spi_buf[cap_digit(digit)] = 0x80 | brightness
|
||||
|
||||
self.spi_write()
|
||||
|
||||
def set_en_bit_to_1(self):
|
||||
"""
|
||||
set EN Bit to 1 for all digits. This will ensure that we will control all the digits when sending the SPI signal
|
||||
"""
|
||||
for i in range(0, EXIXE_SPI_BUF_SIZE - 5):
|
||||
self.spi_buf[i] = 0x80
|
||||
|
||||
def set_led(self, red, green, blue):
|
||||
"""
|
||||
This method will update the LED without changing the digits
|
||||
"""
|
||||
red = cap_brightness(red)
|
||||
green = cap_brightness(green)
|
||||
blue = cap_brightness(blue)
|
||||
self.red = red
|
||||
self.green = green
|
||||
self.blue = blue
|
||||
self.spi_buf[13] = 0
|
||||
self.spi_buf[14] = 0
|
||||
self.spi_buf[15] = 0
|
||||
|
||||
self.spi_buf[0] = EXIXE_SPI_HEADER_OD if self.overdrive else EXIXE_SPI_HEADER
|
||||
self.spi_buf[13] = 0x80 | red
|
||||
self.spi_buf[14] = 0x80 | green
|
||||
self.spi_buf[15] = 0x80 | blue
|
||||
|
||||
self.spi_write()
|
||||
|
||||
def set_dots(self, left_brightness, right_brightness):
|
||||
"""
|
||||
This method will update the left and right dots if the Nixie tube supports this.
|
||||
This does not change digits or LED
|
||||
"""
|
||||
left_brightness = cap_brightness(left_brightness)
|
||||
right_brightness = cap_brightness(right_brightness)
|
||||
|
||||
self.clear_digit()
|
||||
self.spi_buf[0] = EXIXE_SPI_HEADER_OD if self.overdrive else EXIXE_SPI_HEADER
|
||||
self.spi_buf[11] = 0x80 | left_brightness
|
||||
self.spi_buf[12] = 0x80 | right_brightness
|
||||
|
||||
self.spi_write()
|
||||
|
||||
def crossfade_init(self, digit, duration_frames, brightness=MAX_BRIGHTNESS):
|
||||
"""
|
||||
This method is used to initialize the crossfade, it will set the destination digit along with the number of
|
||||
frames that is desired for the animation. This should only be called 1 time for set up, where as the
|
||||
crossfade_run method should be called within a loop to do the updates of the animation.
|
||||
"""
|
||||
self.animation_dest_digit = digit
|
||||
self.animation_start_frame = millis() / EXIXE_ANIMATION_FRAME_DURATION_MS
|
||||
self.animation_duration = duration_frames
|
||||
self.animation_brightness = cap_brightness(brightness)
|
||||
self.animation_step = self.animation_brightness / self.animation_duration
|
||||
self.animation_in_progress = True
|
||||
|
||||
def crossfade_run(self):
|
||||
"""
|
||||
This method will update the 2 digits for animation. Where the source digit will go down in brightness and the
|
||||
destination digit will go up in brightness until source is zero and destination is at desired brightness.
|
||||
:return: The status if the crossfade animation has been completed or still running.
|
||||
"""
|
||||
current_frame = (millis() / EXIXE_ANIMATION_FRAME_DURATION_MS) - self.animation_start_frame
|
||||
|
||||
if current_frame > self.animation_duration:
|
||||
self.animation_src_digit = self.animation_dest_digit
|
||||
self.animation_in_progress = False
|
||||
return EXIXE_ANIMATION_FINISHED
|
||||
self.clear_digit()
|
||||
self.set_en_bit_to_1()
|
||||
self.spi_buf[0] = EXIXE_SPI_HEADER_OD if self.overdrive else EXIXE_SPI_HEADER
|
||||
|
||||
temp = int(cap_brightness(self.animation_step * current_frame))
|
||||
self.spi_buf[cap_digit(self.animation_src_digit)] = 0x80 | (self.animation_brightness - temp)
|
||||
self.spi_buf[cap_digit(self.animation_dest_digit)] = 0x80 | temp
|
||||
self.spi_write()
|
||||
self.animation_in_progress = True
|
||||
return EXIXE_ANIMATION_IN_PROGRESS
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
This method will clear all of the SPI buffer with zero's
|
||||
"""
|
||||
self.spi_buf = [0] * EXIXE_SPI_BUF_SIZE
|
||||
|
||||
def clear_digit(self):
|
||||
"""
|
||||
This method will clear only the slots related to the digits
|
||||
"""
|
||||
for i in range(0, EXIXE_SPI_BUF_SIZE - 5):
|
||||
self.spi_buf[i] = 0
|
||||
|
||||
def spi_write(self):
|
||||
"""
|
||||
This method will enable the CS pin and then send the SPI Buffer to the exixe driver. This method is not intended
|
||||
to be called by it self but directly from the set_led, set_digit, and set_dots
|
||||
"""
|
||||
while not self.spi.try_lock():
|
||||
pass
|
||||
try:
|
||||
self.cs.value = False
|
||||
self.spi.write(bytes(self.spi_buf))
|
||||
self.cs.value = True
|
||||
finally:
|
||||
self.spi.unlock()
|
||||
Reference in New Issue
Block a user