From 39e493415886978525851cf32e9c22ebd70db775 Mon Sep 17 00:00:00 2001 From: Adam Corbin Date: Thu, 17 Jan 2019 05:03:38 -0500 Subject: [PATCH] Adding Python library Adding references to python library Adding python library Adding python examples that match arduino's examples --- README.md | 4 + getting_started.md | 2 +- python_examples/1_LED_test/1_led_test.py | 17 ++ .../2_loop_digit_simple.py | 22 ++ .../3_loop_digit_crossfade.py | 28 +++ .../4_multiple_tubes_simple.py | 28 +++ .../5_multiple_tubes_crossfade.py | 33 +++ python_library/README.md | 37 ++++ python_library/exixe.py | 194 ++++++++++++++++++ 9 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 python_examples/1_LED_test/1_led_test.py create mode 100644 python_examples/2_loop_digit_simple/2_loop_digit_simple.py create mode 100644 python_examples/3_loop_digit_crossfade/3_loop_digit_crossfade.py create mode 100644 python_examples/4_multiple_tubes_simple/4_multiple_tubes_simple.py create mode 100644 python_examples/5_multiple_tubes_crossfade/5_multiple_tubes_crossfade.py create mode 100644 python_library/README.md create mode 100644 python_library/exixe.py diff --git a/README.md b/README.md index 7bb70de..fdd725f 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,10 @@ Please see [getting_started.md](/getting_started.md) Please [click here](arduino_library) +## Raspberry Pi/Python Library + +Please [click here](python_library) + ## Pinout, SPI command format and technical details Please see [technical_details.md](/technical_details.md) diff --git a/getting_started.md b/getting_started.md index 5c5fc70..be41a97 100644 --- a/getting_started.md +++ b/getting_started.md @@ -108,7 +108,7 @@ Finally, the [multiple tubes crossfade](/arduino_examples/5_multiple_tubes_cross ## Resources -Consult the [Arduino library docs](arduino_library/README.md) to see how to use library functions. And explore the [sample sketches](/arduino_examples). +Consult the [Arduino library docs](arduino_library/README.md) and [Python library docs](python_library/README.md) to see how to use library functions. And explore the [arduino sample sketches](/arduino_examples) and [python samples](/python_examples). If you want to keep it simple, there are some self-contained [barebone examples](/arduino_examples/barebone) too. diff --git a/python_examples/1_LED_test/1_led_test.py b/python_examples/1_LED_test/1_led_test.py new file mode 100644 index 0000000..d25c756 --- /dev/null +++ b/python_examples/1_LED_test/1_led_test.py @@ -0,0 +1,17 @@ +# exixe modules: https://github.com/dekuNukem/exixe +# python library docs: https://github.com/dekuNukem/exixe/tree/master/python_library +# Demo 1: LED Test +import exixe +import spidev +import time + +spi = spidev.SpiDev() +spi.open(0, 0) +spi.max_speed_hz = 7800000 +cs_pin = 22 + +my_tube = exixe.Exixe(cs_pin, spi) + +while True: + my_tube.set_led(127, 0, 127) # Purple + time.sleep(1) diff --git a/python_examples/2_loop_digit_simple/2_loop_digit_simple.py b/python_examples/2_loop_digit_simple/2_loop_digit_simple.py new file mode 100644 index 0000000..b6068e7 --- /dev/null +++ b/python_examples/2_loop_digit_simple/2_loop_digit_simple.py @@ -0,0 +1,22 @@ +# exixe modules: https://github.com/dekuNukem/exixe +# python library docs: https://github.com/dekuNukem/exixe/tree/master/python_library +# Demo 2: loop digits test +import exixe +import spidev +import time + +spi = spidev.SpiDev() +spi.open(0, 0) +spi.max_speed_hz = 7800000 +cs_pin = 16 + +my_tube = exixe.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) diff --git a/python_examples/3_loop_digit_crossfade/3_loop_digit_crossfade.py b/python_examples/3_loop_digit_crossfade/3_loop_digit_crossfade.py new file mode 100644 index 0000000..8c6ee5c --- /dev/null +++ b/python_examples/3_loop_digit_crossfade/3_loop_digit_crossfade.py @@ -0,0 +1,28 @@ +# 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 +import exixe +import spidev +import time + +spi = spidev.SpiDev() +spi.open(0, 0) +spi.max_speed_hz = 7800000 +cs_pin = 16 + +my_tube = exixe.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 diff --git a/python_examples/4_multiple_tubes_simple/4_multiple_tubes_simple.py b/python_examples/4_multiple_tubes_simple/4_multiple_tubes_simple.py new file mode 100644 index 0000000..39cdcd1 --- /dev/null +++ b/python_examples/4_multiple_tubes_simple/4_multiple_tubes_simple.py @@ -0,0 +1,28 @@ +# 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 +import exixe +import spidev +import time + +spi = spidev.SpiDev() +spi.open(0, 0) +spi.max_speed_hz = 7800000 + +# Update these to the cs pins you're using +cs_pin_1 = 16 +cs_pin_2 = 32 + +my_tube_1 = exixe.Exixe(cs_pin_1, spi) +my_tube_2 = exixe.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) diff --git a/python_examples/5_multiple_tubes_crossfade/5_multiple_tubes_crossfade.py b/python_examples/5_multiple_tubes_crossfade/5_multiple_tubes_crossfade.py new file mode 100644 index 0000000..eafbb5b --- /dev/null +++ b/python_examples/5_multiple_tubes_crossfade/5_multiple_tubes_crossfade.py @@ -0,0 +1,33 @@ +# 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 +import exixe +import spidev +import time + +spi = spidev.SpiDev() +spi.open(0, 0) +spi.max_speed_hz = 7800000 +cs_pin_1 = 16 +cs_pin_2 = 32 + +my_tube_1 = exixe.Exixe(cs_pin_1, spi) +my_tube_2 = exixe.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 diff --git a/python_library/README.md b/python_library/README.md new file mode 100644 index 0000000..67a1bce --- /dev/null +++ b/python_library/README.md @@ -0,0 +1,37 @@ +# exixe Raspberry Pi/Python library + +This is a simple Python library for exixe modules intended to be used on a Raspberry Pi. + +# Requirements +In order to use this library, you must have python installed, SPI interface turned on the Raspberry Pi and spidev python library installed. +## Turning on SPI +Configuration +The SPI peripheral is not turned on by default. To enable it, do the following. +1. Run sudo raspi-config. +2. Use the down arrow to select 5 Interfacing Options +3. Arrow down to P4 SPI. +4. Select yes when it asks you to enable SPI, +5. Also select yes if it asks about automatically loading the kernel module. +6. Use the right arrow to select the button. +7. Select yes when it asks to reboot. +## Installing spidev +From pip: +pip install spidev +From source: +sudo apt-get update sudo apt-get install python-dev +git clone git://github.com/doceme/py-spidev +cd py-spidev +sudo python setup.py install +sudo python3 setup.py install +#Usage +The code has some good documentation that I don’t want to repeat but this will be a general overview on how to use the library. +1. Create SPI initialization +spi = spidev.SpiDev() +spi.open(0,0) +spi.max_speed = 7800000 +2. Identify CS pin to control the individual exixe +3. Create exixe object +exixe_1 = Exixe(cs_pin,spi) +4. Control object +exixe_1.set_digit(5) +exixe_1.set_led(127,0,0) # turn led to red diff --git a/python_library/exixe.py b/python_library/exixe.py new file mode 100644 index 0000000..d2ae487 --- /dev/null +++ b/python_library/exixe.py @@ -0,0 +1,194 @@ +import spidev +import time +import RPi.GPIO as GPIO + +GPIO.setmode(GPIO.BOARD) + +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 + + GPIO.setup(self.cs_pin, GPIO.OUT) + GPIO.output(self.cs_pin, 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 + """ + GPIO.output(self.cs_pin, False) + self.spi.writebytes(self.spi_buf) + GPIO.output(self.cs_pin, True)