Welcome to Hackster!
Hackster is a community dedicated to learning hardware, from beginner to pro. Join us, it's free!
Peter Tizora
Published

ENGI 301 Mechanical Split Flap Display

Split Flap display that shows a timer

IntermediateFull instructions providedOver 3 days130
ENGI 301 Mechanical Split Flap Display

Things used in this project

Hardware components

SPI LCD Screen
×1
Stepper Motor
Digilent Stepper Motor
×1
M4 Nuts
×11
M4 Bolts
×11
Stepper motor driver board A4988
SparkFun Stepper motor driver board A4988
×2
BeagleBone Black
BeagleBoard.org BeagleBone Black
×1
Jumper wires (generic)
Jumper wires (generic)
×4
USB-A to Micro-USB Cable
USB-A to Micro-USB Cable
×1
Acrylic
×1

Hand tools and fabrication machines

Laser cutter (generic)
Laser cutter (generic)

Story

Read more

Schematics

Block Diagram

Code

Main Script

Python
"""
    --------------------------------------------------------------------------
    Main
    --------------------------------------------------------------------------
    License:   
    Copyright 2021 Peter Tizora
    
    Redistribution and use in source and binary forms, with or without 
    modification, are permitted provided that the following conditions are met:
    
    1. Redistributions of source code must retain the above copyright notice, this 
    list of conditions and the following disclaimer.
    
    2. Redistributions in binary form must reproduce the above copyright notice, 
    this list of conditions and the following disclaimer in the documentation 
    and/or other materials provided with the distribution.
    
    3. Neither the name of the copyright holder nor the names of its contributors 
    may be used to endorse or promote products derived from this software without 
    specific prior written permission.
    
    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
    DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 
    FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
    OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    --------------------------------------------------------------------------
"""

import time

import Adafruit_BBIO.GPIO as GPIO

# to be replaced by the SPI library
import button as BUTTON
import motor as MOTOR
import spi_screen as SPI_SCREEN


class Mech_split_flap_disp():
    
    reset_time = None
    button     = None  # for timer
    button2    = None  # for stopwatch
    display    = None
    motor1     = None
    motor2     = None

    def __init__(self, reset_time=1.5, 
                       button="P2_18",button2="P2_20", 
                       i2c_bus=1, i2c_address=0x70,
                       step_pin1="P2_6", dir_pin1="P2_4",
                       step_pin2="P2_10", dir_pin2="P2_8"):
                           
        """ Initialize variables and set up SPI display """

        self.reset_time = reset_time
        self.button     = BUTTON.Button(button)
        self.button2    = BUTTON.Button(button2)
        self.display    = SPI_SCREEN.SPI_Display()
        self.motor1     = MOTOR.StepperMotor(step_pin1, dir_pin1)
        self.motor2     = MOTOR.StepperMotor(step_pin2, dir_pin2)
        
        self._setup()
    
    # End def
    

# End class

    def _setup(self):
        """Setup the hardware components."""
        # Initialize Display

        print("Project setup()")
        self.display.blank()

    # End def
    
    def timer(self):
        # run the timer while the green button has not been pressed for 2 seconds
        print("timer starts")
        
        time.sleep(0.5)
        _time = 0
      
        self.display.text(["                   TIMER",
                           " ",
                           "Press:", 
                                 "   Green Button to set time",
                                 "   Black Button to start timer",
                                 "Long Press Black to exit"])
                                 
        loop = True
        button2_press_time = self.button2.wait_for_press()[0]
        
        if (button2_press_time > self.reset_time):
                loop = False
                
        while(loop):
        
            if (self.button.is_pressed()):
                _time = _time + 10
                self.display.text(["time set", str(_time)])
            elif (self.button2.is_pressed()):
                loop = False
            else:
                time.sleep(0.1)
              
            count = _time    
        while(_time):
            _time -= 1
            _realtime = count - _time
            mins, secs = divmod(_realtime, 60)
            timer = '{:02d}:{:02d}'.format(mins, secs)
            # time.sleep(1)
            print(timer, end="\r")
            self.motor1.set_direction(1)  # Set direction to clockwise
            self.motor1.rotate_motor(1) # Rotate the first motor every second
            # if secs == 0:  # Rotate the second motor once every minute
            #     motor2.set_direction(1)  # Set direction to clockwise
            #     self.motor2.rotate_motor(36,1)  
            self.display.text(str(timer))
            if(self.button2.is_pressed()):
                break
            time.sleep(0.01)
            
        time.sleep(1)
        
    # End Def
   
    
    def stopwatch(self):
        print("stopwatch starts")
        time.sleep(0.5)
        _time2 = 0
        
        loop2 = True
        
        
        self.display.text(["            STOPWATCH",
                           " ",
                           "Press:", 
                                 " Green Button to start/stop",
                                 " Black Button to reset",
                                 " Long Press Black to exit"])
                                 
        button2_press_time = self.button2.wait_for_press()[0]
        if (button2_press_time > self.reset_time):
            loop2 = False  
        while(loop2):                       
            
            if (self.button.is_pressed()):
                loop3_1 = True
                while(loop3_1):
                    _time2 = _time2 + 1
                    mins2, secs2 = divmod(_time2, 60)
                    timer2 = '{:02d}:{:02d}'.format(mins2, secs2)
                    print(timer2, end="\r")
                    # if secs2 == 0:  # Rotate the second motor once every minute
                    #     motor2.set_direction(1)  # Set direction to clockwise
                    #     self.motor2.rotate_motor(36,1)  
                    self.display.text(str(timer2))
                    self.motor1.set_direction(1)  # Set direction to clockwise
                    self.motor1.rotate_motor(1) # Rotate the first motor every second
                    var = 1
                    #time.sleep(0.5)
                    while(var):
                        var = var - 1
                        if (self.button.is_pressed()):
                            loop3_1 = False
                    #time.sleep(0.5)
                    
                    # if (self.button.is_pressed()):
                    #     #time.sleep(1)
                    #     self.display.text(str(timer2))
                    #     loop4 = True
                    #     while(loop4):
                    #         if (self.button.is_pressed()):
                    #             loop4 = False
            if (self.button2.is_pressed()):
                 
                _time2 = 0
                mins2, secs2 = divmod(_time2, 60)
                timer2 = '{:02d}:{:02d}'.format(mins2, secs2)
                print(timer2, end="\r")
                self.display.text(str(timer2))
                time.sleep(0.1)
                loop2 = False 
                time.sleep(1)
                    
       
                
        
    # End Def
    
    def run(self):
        """Execute the main program."""
        button_press_time = 0.0  # Time button was pressed (in seconds)
        button2_press_time = 0.0
        
        print("Run Class")
        print("Welcome Message")
        self.display.text(["        Welcome to my", "  Timer/Stopwatch Program!"])
        time.sleep(2)
        
        while(1):
            print("In Run loop")
            # Wait for button press
            # button_press_time = self.button.wait_for_press()[0]
            # button2_press_time = self.button2.wait_for_press()[0]
            # Record time
    
            # My own code starts here
            
    
            print("SPI displays ")
            self.display.text(["                  HOME"
                                "  ",
                                "Press:",
                                "  Green Button for Timer",
                                "  Black Button for Stopwatch"])
          
            wait_forpress = True
            
            while(wait_forpress):
                
                if (self.button.is_pressed()):
                    # Run timer if green button is pressed
                    
                    wait_forpress = False
                    self.timer()
                    time.sleep(1.5)
           
                if (self.button2.is_pressed()):
                    # Run stopwatch if black button is pressed
                    wait_forpress = False
                    self.stopwatch()
    
                time.sleep(0.1)
           
        
            time.sleep(1)
    
            # Wait for button release
    
            # # Compare time to increment or reset people_count
            # if (button_press_time < self.reset_time):
            #     if people_count < HT16K33.HT16K33_MAX_VALUE:
            #         people_count = people_count + 1
            #     else:
            #         people_count = 0
            # else:
            #     people_count = 0
            # # Update the display
            # self.display.update(people_count)


    # End def


    def cleanup(self):
        """Cleanup the hardware components."""
        
        # Set Display to something unique to show program is complete
        self.display.text("        IDLE", fontsize=40)
        
        # Button does not need any cleanup code
        
    #End def

# End class


if __name__ == '__main__':

    print("Program Start")

    # Create instantiation of the mech_split_flap_disp
    mech_split_flap_disp = Mech_split_flap_disp()

    try:
       
        mech_split_flap_disp.run()

    except KeyboardInterrupt:
        # Clean up hardware when exiting
        mech_split_flap_disp.cleanup()

    print("Program Complete")

Motor

Python
"""
    --------------------------------------------------------------------------
    Main
    --------------------------------------------------------------------------
    License:   
    Copyright 2021 Peter Tizora
    
    Redistribution and use in source and binary forms, with or without 
    modification, are permitted provided that the following conditions are met:
    
    1. Redistributions of source code must retain the above copyright notice, this 
    list of conditions and the following disclaimer.
    
    2. Redistributions in binary form must reproduce the above copyright notice, 
    this list of conditions and the following disclaimer in the documentation 
    and/or other materials provided with the distribution.
    
    3. Neither the name of the copyright holder nor the names of its contributors 
    may be used to endorse or promote products derived from this software without 
    specific prior written permission.
    
    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
    DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 
    FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
    OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    --------------------------------------------------------------------------
"""

import Adafruit_BBIO.GPIO as GPIO
import time

class StepperMotor():
    def __init__(self, step_pin, dir_pin, full_step=True):
        self.STEP_PIN = step_pin
        self.DIR_PIN = dir_pin
        self.FULL_STEP = full_step
        self.STEPS_PER_REVOLUTION = 2048 if self.FULL_STEP else 4096
        self._time2 = 0

        # Set up the GPIO pins
        GPIO.setup(self.STEP_PIN, GPIO.OUT)
        GPIO.setup(self.DIR_PIN, GPIO.OUT)

    def set_direction(self, direction):
        # Set the direction (1 for clockwise, 0 for counterclockwise)
        GPIO.output(self.DIR_PIN, direction)

    def rotate_motor(self, time_in):
        # Calculate the number of degrees to rotate per second
        degrees_per_second = 36
        degrees = degrees_per_second * time_in
        
        # Calculate the number of steps to rotate the specified number of degrees
        steps = int(degrees / 360 * self.STEPS_PER_REVOLUTION)
        
        # Calculate the delay for a 36-degree rotation per second
        delay = 1 / (degrees_per_second / 360 * self.STEPS_PER_REVOLUTION)
        
        # Perform the steps
        for i in range(steps):
            # Pulse the STEP pin to move the motor
            GPIO.output(self.STEP_PIN, 1)
            time.sleep(delay/2)  # Adjusted delay
            GPIO.output(self.STEP_PIN, 0)
            time.sleep(delay/2)  # Adjusted delay

if __name__ == '__main__':
    motor1 = StepperMotor("P2_6", "P2_4")
    #motor2 = StepperMotor("P2_10", "P2_8")
    motor1.set_direction(1)  # Set direction to clockwise
    #motor2.set_direction(1)
    motor1.rotate_motor(10)  # Rotate the motor time_in seconds clockwise
   # motor2.rotate_motor(36,10)

Configure Pins

Python
#!/bin/bash
# --------------------------------------------------------------------------
# People Counter - Configure Pins
# --------------------------------------------------------------------------
# License:   
# Copyright 2023 Peter
# 
# Redistribution and use in source and binary forms, with or without 
# modification, are permitted provided that the following conditions are met:
# 
# 1. Redistributions of source code must retain the above copyright notice, this 
# list of conditions and the following disclaimer.
# 
# 2. Redistributions in binary form must reproduce the above copyright notice, 
# this list of conditions and the following disclaimer in the documentation 
# and/or other materials provided with the distribution.
# 
# 3. Neither the name of the copyright holder nor the names of its contributors 
# may be used to endorse or promote products derived from this software without 
# specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# --------------------------------------------------------------------------
# 
# Configure pins for digital people counter:
#   - I2C1
#   - Button
# 
# --------------------------------------------------------------------------

# I2C1
config-pin P2_09 i2c
config-pin P2_11 i2c

# Motors
config-pin P2_04 gpio
config-pin P2_06 gpio
config-pin P2_08 gpio
config-pin P2_10 gpio

# Buttons
config-pin P2_18 gpio  # Green button
config-pin P2_20 gpio  # Black button

SPI Screen

Python
# -*- coding: utf-8 -*-
"""
--------------------------------------------------------------------------
SPI Display Library
--------------------------------------------------------------------------
License:   
Copyright 2021 Erik Welsh

Redistribution and use in source and binary forms, with or without 
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this 
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, 
this list of conditions and the following disclaimer in the documentation 
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors 
may be used to endorse or promote products derived from this software without 
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------
Software API:

  SPI_DISPLAY()
    - Provide spi bus that dispaly is on
    - Provide spi address for the display
    
    blank()
      - Fills the display with black (i.e. color (0,0,0))
    
    fill(color)
      - Fills the display with the given (R, G, B) color tuple
    
    image(filename, rotation=90)
      - Erases display and shows image from filename
    
    text(value, fontsize=24, fontcolor=(255,255,255), backgroundcolor=(0,0,0), 
                justify=LEFT, align=TOP, rotation=90):
      - Erases display and shows text value on display
      - Value can either be a string or list of strings for multiple lines of text

--------------------------------------------------------------------------
Background Information: 

Links:
  - https://learn.adafruit.com/adafruit-2-8-and-3-2-color-tft-touchscreen-breakout-v2/overview
  - https://learn.adafruit.com/adafruit-2-8-and-3-2-color-tft-touchscreen-breakout-v2/spi-wiring-and-test
  - https://learn.adafruit.com/adafruit-2-8-and-3-2-color-tft-touchscreen-breakout-v2/python-wiring-and-setup
  - https://learn.adafruit.com/adafruit-2-8-and-3-2-color-tft-touchscreen-breakout-v2/python-usage

  - https://circuitpython.readthedocs.io/projects/rgb_display/en/latest/api.html#module-adafruit_rgb_display.rgb
  - https://circuitpython.readthedocs.io/projects/rgb_display/en/latest/_modules/adafruit_rgb_display/rgb.html
  
Software Setup:
  - sudo apt-get update
  - sudo pip3 install --upgrade Pillow
  - sudo pip3 install adafruit-circuitpython-busdevice
  - sudo pip3 install adafruit-circuitpython-rgb-display
  - sudo apt-get install ttf-dejavu -y

"""
import time
import busio
import board
import digitalio

from PIL import Image, ImageDraw, ImageFont

from   adafruit_rgb_display import color565
import adafruit_rgb_display.ili9341 as ili9341

# ------------------------------------------------------------------------
# Constants
# ------------------------------------------------------------------------
LEFT               = 0
RIGHT              = 1
TOP                = 2
BOTTOM             = 3
CENTER             = 4

PADDING            = -5                # May need to adjust based on font

# ------------------------------------------------------------------------
# Functions / Classes
# ------------------------------------------------------------------------
class SPI_Display():
    """ Class to manage an SPI display """
    reset_pin = None
    dc_pin    = None
    cs_pin    = None
    spi_bus   = None
    display   = None
    
    def __init__(self, clk_pin=board.SCLK, miso_pin=board.MISO, mosi_pin=board.MOSI,
                       cs_pin=board.P1_6, dc_pin=board.P1_4, reset_pin=board.P1_2,
                       baudrate=24000000, rotation=90):
        """ SPI Display Constructor
        
        :param clk_pin   : Value must be a pin from adafruit board library
        :param miso_pin  : Value must be a pin from adafruit board library
        :param mosi_pin  : Value must be a pin from adafruit board library
        :param cs_pin    : Value must be a pin from adafruit board library
        :param dc_pin    : Value must be a pin from adafruit board library
        :param reset_pin : Value must be a pin from adafruit board library
        :param baudrate  : SPI communication rate; default 24MHz
        :param rotation  : Rotation of display; default 90 degrees (landscape)
        
        """
        # Configuration for CS and DC pins:
        self.reset_pin = digitalio.DigitalInOut(reset_pin)
        self.dc_pin    = digitalio.DigitalInOut(dc_pin)
        self.cs_pin    = digitalio.DigitalInOut(cs_pin)

        # Setup SPI bus using hardware SPI
        self.spi_bus   = busio.SPI(clock=clk_pin, MISO=miso_pin, MOSI=mosi_pin)

        # Create the ILI9341 display:
        self.display   = ili9341.ILI9341(self.spi_bus, cs=self.cs_pin, dc=self.dc_pin,
                                         baudrate=baudrate, rotation=rotation)
        
        # Initialize Hardware
        self._setup()
    
    # End def
    
    
    def _setup(self):
        """Initialize the display itself"""
        # Clear the display
        self.blank()

    # End def    


    def blank(self):
        """Clear the display a black screen"""
        self.fill((0,0,0))

    # End def


    def fill(self, color):
        """Fill the display with the given color"""
        if ((color[0] < 0) or (color[0] > 255) or 
            (color[1] < 0) or (color[1] > 255) or
            (color[2] < 0) or (color[2] > 255)):
            raise ValueError("(R,G,B) must be between 0 and 255: ({0}, {1}, {2})".format(color[0], color[1], color[2]))

        self.display.fill(color565(color[0], color[1], color[2]))

    # End def


    def _get_dimensions(self, rotation):
        """Get display dimensions"""
        # Check image rotation
        if rotation % 180 == 90:
            height = self.display.width  # Swap height/width to rotate it to landscape!
            width  = self.display.height
        else:
            width  = self.display.width
            height = self.display.height
        
        return (width, height)

    # End def


    def image(self, filename, rotation=90):
        """Display the image on the screen"""
        # Fill display with black pixels to clear the image
        self.blank()

        # Create image with file name
        image = Image.open(filename)

        # Get screen dimensions
        width, height = self._get_dimensions(rotation)

        # Scale the image to the smaller screen dimension
        image_ratio  = image.width / image.height
        screen_ratio = width / height
        if screen_ratio < image_ratio:
            scaled_width  = image.width * height // image.height
            scaled_height = height
        else:
            scaled_width  = width
            scaled_height = image.height * width // image.width
        
        image = image.resize((scaled_width, scaled_height), Image.BICUBIC)

        # Crop and center the image
        x = scaled_width  // 2 - width  // 2
        y = scaled_height // 2 - height // 2
        image = image.crop((x, y, x + width, y + height))

        # Display image
        self.display.image(image)
        
    # End def
    

    def text(self, value, fontsize=22, fontcolor=(255,255,255), 
                   backgroundcolor=(0,0,0), justify=LEFT, align=TOP, 
                   rotation=90):
        """ Update the display with text
        
        :param value           : Value can be a string or list of string
        :param fontsize        : Size of font
        :param fontcolor       : (R, G, B) tuple for the color of the text
        :param backgroundcolor : (R, G, B) tuple for the color of the background
        :param justify         : Value in [LEFT, CENTER, RIGHT]
        :param align           : Value in [TOP, CENTER, BOTTOM]
        :param rotation        : Orientation of the display
        
        Will throw a ValueError 
        """
        # Debug variable
        debug = False
        
        # Check inputs:
        if justify not in [LEFT, CENTER, RIGHT]:
            raise ValueError("Input justify must be in [LEFT, CENTER, RIGHT]")
        if align not in [TOP, CENTER, BOTTOM]:
            raise ValueError("Input align must be in [TOP, CENTER, BOTTOM]")

        # Determine if text value is string or list
        #   - Rest of function assumes value is a list of strings
        if (type(value) is not list):
            value = [value]

        # Clear screen
        self.fill(backgroundcolor)
        
        # Get display dimensions
        width, height = self._get_dimensions(rotation)

        # Create a canvas for drawing
        canvas = Image.new("RGB", (width, height))

        # Get drawing object to draw on canvas
        draw   = ImageDraw.Draw(canvas)

        # Load a TTF Font
        font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", fontsize)

        # Get height of a character
        font_height = font.getsize(" ")[1]

        if (debug):
            print("Canvas h = {0}".format(height))
            print("Font   h = {0}".format(font_height))

        # Calculate number of lines supported on screen w/ font choice        
        num_line = height // font_height
        
        if (debug):
            print("Num Lines      = {0}".format(num_line))

        # Issue warning if too many lines        
        if (len(value) > num_line):
            print("WARNING:  Too many lines for font size.  Truncating.")
            print("    Required lines : {0}".format(len(value)))
            print("    Available lines: {0}".format(num_line))
            # Truncate list
            del value[num_line:]
            
        # Create list of positions for each line
        text_height = len(value) * font_height   # Number of lines * font height

        # Get initial y position
        if align == TOP:
            y = 0
        if align == BOTTOM:
            y = height - text_height
        if align == CENTER:
            y = (height // 2) - (text_height // 2) 
        
        # Adjust y position by padding
        y = y + PADDING
        
        # Only print lines there is space for
        for i, line in enumerate(value):
            # Get width of line
            line_width = font.getsize(line)[0]
            
            # Issue warning if too many characters
            if (line_width > width):
                print("WARNING:  Too many characters for the line.  Truncating.")
                print("    Required width : {0}".format(line_width))
                print("    Available width: {0}".format(width))
                # Truncate line
                for i in range(len(line)):
                    line_width = font.getsize(line[:-(i+1)])[0]
                    if (line_width <= width):
                        line = line[:-(i+1)]
                        break

            # Get x position
            if justify == LEFT:
                x = 0
            if justify == RIGHT:
                x = width - line_width
            if align == CENTER:
                x = (width // 2) - (line_width // 2) 

            # Draw the text
            draw.text((x, y), line, font=font, fill=fontcolor)
            y += font_height
        
        # Display image
        self.display.image(canvas)

    # End def
    
# End class


# ------------------------------------------------------------------------
# Main script
# ------------------------------------------------------------------------

if __name__ == '__main__':
    import time

    delay = 2
    
    print("Test SPI Display:")
    
    print("Create Display")
    display = SPI_Display()
    time.sleep(delay)

    # Test Functions
    print("Fill Red")
    display.fill((255, 0, 0))   
    time.sleep(delay)

    print("Fill Green")
    display.fill((0, 255, 0))   
    time.sleep(delay)
    
    print("Fill Blue")
    display.fill((0, 0, 255))   
    time.sleep(delay)
    
    print("Display blinka.jpg")
    display.image("blinka.jpg")
    time.sleep(delay)

    print("Display Text")
    display.text("This is some text!!")
    time.sleep(delay)
    
    print("Display Multi-line Text")
    display.text(["This is some text", "on multiple lines!!"])
    time.sleep(delay)
    
    print("Display Multi-line Text, centered")
    display.text(["This is some text", "on multiple lines!!"], justify=CENTER, align=CENTER)
    time.sleep(delay)
    
    print("Display Multi-line Text, right justify, align bottom, fontsize 30")
    display.text(["This is some text", "on multiple lines!!", "asdf", "asdf", "asdf", "abcdefghijklmnopqrstuvwxyz"], 
                 fontsize=30, justify=RIGHT, align=BOTTOM)
    time.sleep(delay)
    
    print("Test Finished.")

Run

Python
#!/bin/bash
# --------------------------------------------------------------------------
# People Counter - Run Script
# --------------------------------------------------------------------------
# License:   
# Copyright 2020 <Name>
# 
# Redistribution and use in source and binary forms, with or without 
# modification, are permitted provided that the following conditions are met:
# 
# 1. Redistributions of source code must retain the above copyright notice, this 
# list of conditions and the following disclaimer.
# 
# 2. Redistributions in binary form must reproduce the above copyright notice, 
# this list of conditions and the following disclaimer in the documentation 
# and/or other materials provided with the distribution.
# 
# 3. Neither the name of the copyright holder nor the names of its contributors 
# may be used to endorse or promote products derived from this software without 
# specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# --------------------------------------------------------------------------
# 
# Run People Counter
# 
# --------------------------------------------------------------------------
cd /var/lib/cloud9/ENGI301/python/project1
./configure_pins.sh
PYTHONPATH=/var/lib/cloud9/ENGI301/python/project1:/var/lib/cloud9/ENGI301/python/project1 python3 project01_Peter.py

Button

Python
"""
--------------------------------------------------------------------------
Button Driver
--------------------------------------------------------------------------
License:   
Copyright 2021-2023 - Peter Tizora

Redistribution and use in source and binary forms, with or without 
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this 
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, 
this list of conditions and the following disclaimer in the documentation 
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors 
may be used to endorse or promote products derived from this software without 
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------

Button Driver

  This driver is built for buttons that have a pull up resistor between the
button and the processor pin (i.e. the input is "High"/"1" when the button is
not pressed) and will be connected to ground when the button is pressed (i.e. 
the input is "Low" / "0" when the button is pressed)

Software API:

  Button(pin)
    - Provide pin that the button monitors
    
    is_pressed()
      - Return a boolean value (i.e. True/False) on if button is pressed
      - Function consumes no time
    
    wait_for_press(function=None)
      - Wait for the button to be pressed 
      - Optionally takes in an argument "function" which is the function 
        to be executed when waiting for the button to be pressed
      - Function consumes time
      - Returns a tuple:  
        (<time button was pressed>, <data returned by the "function" argument>)

"""
import time

import Adafruit_BBIO.GPIO as GPIO

# ------------------------------------------------------------------------
# Constants
# ------------------------------------------------------------------------

# None

# ------------------------------------------------------------------------
# Global variables
# ------------------------------------------------------------------------

# None

# ------------------------------------------------------------------------
# Functions / Classes
# ------------------------------------------------------------------------

class Button():
    """ Button Class """
    pin             = None
    unpressed_value = None
    pressed_value   = None
    sleep_time      = None
    
    def __init__(self, pin=None):
        """ Initialize variables and set up the button """
        if (pin == None):
            raise ValueError("Pin not provided for Button()")
        else:
            self.pin = pin
        
        # By default the unpressed_value is "1" and the pressed
        # value is "0".  This is done to make it easier to change
        # in the future
        self.unpressed_value = 1
        self.pressed_value   = 0
        
        # By default sleep time is "0.1" seconds
        self.sleep_time      = 0.1

        # Initialize the hardware components        
        self._setup()
    
    # End def
    
    
    def _setup(self):
        """ Setup the hardware components. """
        GPIO.setup(self.pin, GPIO.IN)

    # End def


    def is_pressed(self):
        """ Is the Button pressed?
        
           Returns:  True  - Button is pressed
                     False - Button is not pressed
        """
        return GPIO.input(self.pin) == self.pressed_value
    # End def


    def wait_for_press(self, function=None):
        """ Wait for the button to be pressed.  This function will 
           wait for the button to be pressed and released so there
           are no race conditions.
        
           Arguments:
               function - Optional argument that is the functon to 
                          executed while waiting for the button to 
                          be pressed
        
           Returns:
               tuple - [0] Time button was pressed
                     - [1] Data returned by the "function" argument
        """
        function_return_value = None
        button_press_time     = None
        
        # Execute function if it is not None
        #   - This covers the case that the button is pressed prior 
        #     to entering this function
        if function is not None:
            function_return_value = function()
        
        # Wait for button press
        #   If the function is not None, execute the function
        #   Sleep for a short amount of time to reduce the CPU load
        #
        # HW#4 TODO: (one line of code)
        #   Update while loop condition to compare the input value of the  
        #   GPIO pin of the buton (i.e. self.pin) to the "unpressed value" 
        #   of the class (i.e. we are executing the while loop while the 
        #   button is not being pressed)
        # while(not self.is_pressed()):
        
        #     if function is not None:
        #         function_return_value = function()
                
        #     time.sleep(self.sleep_time)
        
        # Record time
        button_press_time = time.time()
        
        # Wait for button release
        #   Sleep for a short amount of time to reduce the CPU load
        #
        # HW#4 TODO: (one line of code)
        #   Update while loop condition to compare the input value of the  
        #   GPIO pin of the buton (i.e. self.pin) to the "pressed value" 
        #   of the class (i.e. we are executing the while loop while the 
        #   button is being pressed)
        while(self.is_pressed()):
            time.sleep(self.sleep_time)
        
        # Compute the button_press_time
        button_press_time = time.time() - button_press_time

        # Return a tuple:  (button press time, function return value)        
        return (button_press_time, function_return_value)
        
    # End def

# End class



# ------------------------------------------------------------------------
# Main script
# ------------------------------------------------------------------------

if __name__ == '__main__':

    print("Button Test")

    # Create instantiation of the button
    button = Button("P2_20")
    
    # Create an function to test the wait_for_press function
    def print_time():
        ret_val = time.time()
        print("    Print Time = {0}".format(ret_val))
        return ret_val
    # End def

    # Use a Keyboard Interrupt (i.e. "Ctrl-C") to exit the test
    try:
        # Check if the button is pressed
        print("Is the button pressed?")
        print("    {0}".format(button.is_pressed()))

        print("Press and hold the button.")
        time.sleep(4)
        
        # Check if the button is pressed
        print("Is the button pressed?")
        print("    {0}".format(button.is_pressed()))
        
        print("Release the button.")
        time.sleep(4)
        
        print("Waiting for button press ...")
        value = button.wait_for_press()
        print("    Button pressed for {0} seconds. ".format(value[0]))
        print("    Function return value = {0}".format(value[1]))
        
        print("Waiting for button press with optional argument ...")
        value = button.wait_for_press(print_time)
        print("    Button pressed for {0} seconds. ".format(value[0]))
        print("    Function return value = {0}".format(value[1]))
        
    except KeyboardInterrupt:
        pass

    print("Test Complete")

Credits

Peter Tizora
1 project • 0 followers
Contact

Comments

Please log in or sign up to comment.