Michael Tang
Published © GPL3+

Programmable Music Box

Music boxes are a lot more fun when you can choose the song yourself!

BeginnerFull instructions provided5 hours1,282
Programmable Music Box

Things used in this project

Hardware components

PocketBeagle
BeagleBoard.org PocketBeagle
×1
Adafruit Breadboard-Friendly PCB Mount Mini Speaker - 8 Ohm 0.2W
×3
SparkFun Servo - Generic Continuous Rotation (Micro Size)
×1
Pushbutton switch 12mm
SparkFun Pushbutton switch 12mm
×1
SparkFun 7-Segment Serial Display - Red
SparkFun 7-Segment Serial Display - Red
×1
Female Header 8 Position 1 Row (0.1")
Female Header 8 Position 1 Row (0.1")
×2
Jumper wires (generic)
Jumper wires (generic)
×20
Resistor 1k ohm
Resistor 1k ohm
×3
Copper Board
×1

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Schematics

Music Box Fritzing Schematic

Fritzing schematic for wiring up the music box (copper plate not depicted)

Code

music_box.py

Python
Python program that contains actual functionality of music box.
"""
--------------------------------------------------------------------------
Music Box
--------------------------------------------------------------------------
License:   
Copyright 2020 Michael Tang
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 for running a programmable music box.
Main Components used:
  - HT16K33 Display
  - 1 push button
  - 3 speakers
  - 1 Micro-size continuous rotation servo
  - Copper plate
Uses:
  - HT16K33 display library developed by Erik Welsh
  - motor.py (in folder) for running thread for continuous rotation servo
  - speaker.py (in folder) for running thread for speaker
"""
from speaker import *
from motor import *
import Adafruit_BBIO.GPIO as GPIO
import Adafruit_BBIO.ADC as ADC
import threading

import ht16k33 as HT16K33

# ------------------------------------------------------------------------
# Constants
# ------------------------------------------------------------------------
# threshold sets what voltages our pins need to hit for a sound to be played
THRESHOLD = 500

# ------------------------------------------------------------------------
# Global variables
# ------------------------------------------------------------------------
# determines whether our music box is on or off; is controlled by the Button thread, and affects the MusicBox class
music_box_on = False

class MusicBox():
    # pins corresponding to each speaker, and a list for storing each speaker's threads
    speaker_0 = None
    speaker_1 = None
    speaker_2 = None
    speaker_threads = []
    
    # dict to store pins corresponding to each note
    note_pins = {}
    
    # object representing 7-segment display
    display = None
    
    # object representing motor
    motor = None
    motor_thread = None
    
    def __init__(self, speaker_0="P1_36", speaker_1="P2_3", speaker_2="P2_1", C="P1_27", D="P1_19", E="P1_21", F="P1_23", G="P1_25", A="P2_35", B="P2_36", i2c_bus=1, i2c_address=0x70, motor="P1_33"):
        """
        Intializes variables to appropriate pins
        """
        # initialize speaker pins
        self.speaker_0 = speaker_0
        self.speaker_1 = speaker_1
        self.speaker_2 = speaker_2

        # initialize note pins
        #self.note_pins['A'] = A
        self.note_pins['B'] = B
        self.note_pins['C'] = C
        self.note_pins['D'] = D
        self.note_pins['E'] = E
        self.note_pins['F'] = F
        self.note_pins['G'] = G
        
        # initialize 7-segment display
        self.display = HT16K33.HT16K33(i2c_bus, i2c_address)
        
        # Initialize motor pin
        self.motor = motor
        
        # setup all of the pins
        self._setup()
    
    def _setup(self):
        """
        Sets up device for initial use
        """
        # Initialize Display
        self.set_display_off()
        
        # Initialize Analog Inputs
        ADC.setup()
        
        # Initialize Motor
        self.motor_thread = RunMotor(self.motor)       
        self.motor_thread.start()
        
        # Setup speaker threads into a dictionary
        self.speaker_threads = [PlayNote(self.speaker_0), PlayNote(self.speaker_1), PlayNote(self.speaker_2)]
        for thread in self.speaker_threads:
            thread.start()
            
    
    def run(self):
        """
        Runs music box
        """
        while(True):
            # only start running music box if button has been pressed to on
            if music_box_on:
                self.turn_on()
                
                # check each input pin to see if certain notes are being played (need 4 consecutive values to be high)
                on = []
                for note in self.note_pins.keys():
                    if self.check_threshold(self.note_pins[note]):
                        on.append(note)
                        
                # only take first 3 notes detected (as we have 3 speakers)
                for i, note in enumerate(on[:3]):
                    self.speaker_threads[i].add_note(note)
            
            # if button is pressed to off, then turn off the music box
            else:
                self.turn_off()
    
    def check_threshold(self, pin):
        """
        Checks if an input has hit under threshold voltage (ie. it is toggled on when
        voltage is low enough
        
        Input: analog input pin to check
        Output: true if input has hit threshold voltage
        """
        measured = []
        # read 3 times to average out noise in pin reading
        for i in range(3):
            measured.append(ADC.read_raw(pin))
        return max(measured) <= THRESHOLD
        
    def set_display_on(self):
        """
        Set display to "on"
        """
        self.display.set_digit_raw(0, 0x6F)        # "G"
        self.display.set_digit_raw(1, 0x3F)        # "O"
        self.display.set_digit_raw(2, 0x00)        # " "
        self.display.set_digit_raw(3, 0x00)        # " "
        
    def set_display_off(self):
        """
        Set display to "off"
        """
        self.display.set_digit_raw(0, 0x3F)        # "O"
        self.display.set_digit_raw(1, 0x71)        # "F"
        self.display.set_digit_raw(2, 0x71)        # "F"
        self.display.set_digit_raw(3, 0x00)        # " "
        
    def turn_off(self):
        """
        Sets music box to off mode
        """
        # set display to off
        self.set_display_off()
        
        # stop motor
        self.motor_thread.pause()
        
    def turn_on(self):
        """
        Sets music box to on mode
        """
        # set display to on
        self.set_display_on()
        
        # start motor
        self.motor_thread.unpause()
        
    def cleanup(self):
        """
        Clean up threads
        """
        # Stop speaker threads
        for speaker_thread in self.speaker_threads:
            speaker_thread.end()
        
        # Stop motor thread
        self.motor_thread.end()
        
        # Clean up threads
        main_thread = threading.currentThread()
        for thread in threading.enumerate():
            if thread is not main_thread:
                thread.join()
        
        # Set Display to show program is complete
        self.display.set_digit(0, 13)        # "D"
        self.display.set_digit(1, 14)        # "E"
        self.display.set_digit(2, 10)        # "A"
        self.display.set_digit(3, 13)        # "D"
        
class BoxButton(threading.Thread):
    button = None
    # False corresponds to off, True corresponds to on
    prev_state = False
    stop = False
    
    def __init__(self, button="P2_2"):
        """
        Initialize thread as well as class fields to appropriate pins
        """
        threading.Thread.__init__(self)
        self.button = button
        
        # Initialize button
        GPIO.setup(self.button, GPIO.IN)
        
    def run(self):
        """
        Runs button by having it continuous checking for it to turn on/off
        """
        # make sure we can set global variable
        global music_box_on
        
        while not self.stop:
            # check if button is pressed
            if GPIO.input(self.button) == 0:
                # if prev_state was false, then it was just turned on
                if self.prev_state == False:
                    # previous state now becomes on
                    self.prev_state = True
                    music_box_on = True
                    
                # if prev_state was true, then it was fjust turned off
                else:
                    self.prev_state = False
                    music_box_on = False
                    
                # pause for a bit so it doesn't read a single click as multiple
                time.sleep(0.2)
    
    def end(self):
        """
        Stops thread
        """
        self.stop = True

if __name__ == '__main__':
    # Initialize all objects necessary to run music box
    box = MusicBox()
    button = BoxButton()
    button.start()
    
    try:
        box.run()
    # program terminates upon KeyBoard Interrupt
    except KeyboardInterrupt:
        # Initiate cleanup 
        box.cleanup()
        button.end()
        
        # Stop threads
        main_thread = threading.currentThread()
        for thread in threading.enumerate():
            if thread is not main_thread:
                thread.join()

motor.py

Python
Python file that contains class that allows continuous rotation servo to be run as a Python thread
"""
--------------------------------------------------------------------------
Continuous Servo Motor Functionality
--------------------------------------------------------------------------
License:   
Copyright 2020 Michael Tang
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.
--------------------------------------------------------------------------
Python file that contains class to operate motor servo. Intended to work using
any continuous rotation servo.
"""

import Adafruit_BBIO.PWM as PWM
import threading

class RunMotor(threading.Thread):
    motor = None
    stop = False
    halt = True
    
    def __init__(self, motor):
        """
        Initializes thread and pin connected to continuous servo motor
        """
        threading.Thread.__init__(self)
        self.motor = motor
    
    def run(self):
        """
        Servo runs by continuuously waiting for a stop signal
        """
        # wait for stop to be signalled
        while True:
            # if halt command is given, pause the motor's operation
            if self.halt:
                PWM.stop(self.motor)
            # if stop command is given, terminate the motor's operation
            if self.stop:
                PWM.stop(self.motor)
                break
                
    def pause(self):
        """
        Pause the motor
        """
        self.halt = True
        
    def unpause(self):
        """
        Unpause the motor
        """
        self.halt = False
        # start motor
        PWM.start(self.motor, 25, 400)
                
    def end(self):
        """
        Stop the thread
        """
        self.stop = True

configure_pins.sh

SH
Shell script to configure pins needed for music box to function properly
#!/bin/bash
# --------------------------------------------------------------------------
# Msuic Box - Configure Pins
# --------------------------------------------------------------------------
# License:   
# Copyright 2020 Michael Tang
# 
# 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 Music Box:
#   - PWM0 A / P1_36 (doesn't work for some reason)
#   - PWM0 B / P1_33 (for motor servo)
#   - PWM1 A / P2_1 (for speaker)
#   - PWM1 B / Pw_3 (for speaker)
#   - I2C1
# 
# --------------------------------------------------------------------------

# PWMs
config-pin P1_36 pwm    # for speaker
config-pin P1_33 pwm    # for motor
config-pin P2_01 pwm    # for speaker
config-pin P2_03 pwm    # for speaker


# I2C1 (for 7-seg display)
config-pin P2_09 i2c
config-pin P2_11 i2c

# Button
config-pin P2_02 gpio

run

SH
Shell script to run project; can be used in conjunction with Chron to allow program to run on PocketBeagle startup
#!/bin/bash
# --------------------------------------------------------------------------
# Music Box - Run Script
# --------------------------------------------------------------------------
# License:   
# Copyright 2020 Michael Tang
# 
# 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 Music Box in /var/lib/cloud9/ENGI301/project_01
# 
# --------------------------------------------------------------------------
cd /var/lib/cloud9/ENGI301/project_01
./configure_pins.sh
python3 music_box.py

speaker.py

Python
Contains class that can be used to play notes on a speaker
"""
--------------------------------------------------------------------------
Speaker Sound Functionality
--------------------------------------------------------------------------
License:   
Copyright 2020 Michael Tang
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.
--------------------------------------------------------------------------
Python threading class designed to provide speaker functionality. Designed to work
with any speaker that operates using PWM.
"""
import Adafruit_BBIO.ADC as ADC
import Adafruit_BBIO.PWM as PWM
import time
import threading
        
class PlayNote(threading.Thread):
    speaker = None
    freq_to_note = {'C' : 261, 'D' : 294, 'E' : 330, 'F' : 249, 'G' : 392, 'A' : 440, 'B': 494}
    stop = False
    notes = []
    
    def __init__(self, speaker):
        """
        Initialize thread and speaker pin
        """
        threading.Thread.__init__(self)
        self.speaker = speaker

    def run(self):
        """
        Keep speaker on continuous loop, waiting for a new note to play
        """
        while not self.stop:
            if self.notes:
                note = self.notes.pop(0)
                PWM.start(self.speaker, 5, self.freq_to_note[note])
                time.sleep(0.5)
            else:
                PWM.stop(self.speaker)
    
    def add_note(self, note):
        """
        Adds a note to speaker's note buffer, which will be played as soon as possible
        """
        self.notes.append(note)
    
    def end(self):
        """
        Stop the thread
        """
        self.stop = True

ht16k33.py

Python
Class to interface with HT16K33 4-digit 7-segment display. Created by Erik Welsh.
# -*- coding: utf-8 -*-
"""
--------------------------------------------------------------------------
HT16K33 I2C Library
--------------------------------------------------------------------------
License:   
Copyright 2018 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:
  HT16K33(bus, address=0x70)
    - Provide i2c bus that dispaly is on
    - Provide i2c address for the display
    
    clear()
      - Sets value of display to "0000"
    
    update(value)
      - Update the value on the display.  Value must be between 0 and 9999. 
      
  
--------------------------------------------------------------------------
Background Information: 
 
  * Using seven-segment digit LED display for Adafruit's HT16K33 I2C backpack:
    * http://adafruit.com/products/878
    * https://learn.adafruit.com/assets/36420
    * https://cdn-shop.adafruit.com/datasheets/ht16K33v110.pdf
    
    * Base code (adapted below):
        * https://github.com/emcconville/HT16K33/blob/master/FourDigit.py
        * https://github.com/emcconville/HT16K33/blob/master/_HT16K33.py
        * https://github.com/adafruit/Adafruit_Python_LED_Backpack/blob/master/Adafruit_LED_Backpack/HT16K33.py
        * https://github.com/adafruit/Adafruit_Python_LED_Backpack/blob/master/Adafruit_LED_Backpack/SevenSegment.py
        * https://github.com/adafruit/Adafruit_Python_LED_Backpack/blob/master/examples/sevensegment_test.py
"""
import os


# ------------------------------------------------------------------------
# Constants
# ------------------------------------------------------------------------
HEX_DIGITS                  = [0x3f, 0x06, 0x5b, 0x4f,    # 0, 1, 2, 3
                               0x66, 0x6d, 0x7d, 0x07,    # 4, 5, 6, 7
                               0x7f, 0x6f, 0x77, 0x7c,    # 8, 9, A, b
                               0x39, 0x5e, 0x79, 0x71]    # C, d, E, F

CLEAR_DIGIT                 = 0x7F
POINT_VALUE                 = 0x80

DIGIT_ADDR                  = [0x00, 0x02, 0x06, 0x08]
COLON_ADDR                  = 0x04
                      
HT16K33_BLINK_CMD           = 0x80
HT16K33_BLINK_DISPLAYON     = 0x01
HT16K33_BLINK_OFF           = 0x00
HT16K33_BLINK_2HZ           = 0x02
HT16K33_BLINK_1HZ           = 0x04
HT16K33_BLINK_HALFHZ        = 0x06

HT16K33_SYSTEM_SETUP        = 0x20
HT16K33_OSCILLATOR          = 0x01

HT16K33_BRIGHTNESS_CMD      = 0xE0
HT16K33_BRIGHTNESS_HIGHEST  = 0x0F
HT16K33_BRIGHTNESS_DARKEST  = 0x00


# ------------------------------------------------------------------------
# Functions / Classes
# ------------------------------------------------------------------------
class HT16K33():
    """ Class to manage a HT16K33 I2C display """
    bus     = None
    address = None
    command = None
    
    def __init__(self, bus, address=0x70):
        """ Initialize variables and set up display """
        self.bus     = bus
        self.address = address
        self.command = "/usr/sbin/i2cset -y {0} {1}".format(bus, address)
        
        self.setup(blink=HT16K33_BLINK_OFF, brightness=HT16K33_BRIGHTNESS_HIGHEST)
    
    # End def
    
    def setup(self, blink, brightness):
        """Initialize the display itself"""
        # i2cset -y 1 0x70 0x21
        os.system("{0} {1}".format(self.command, (HT16K33_SYSTEM_SETUP | HT16K33_OSCILLATOR)))
        # i2cset -y 1 0x70 0x81
        os.system("{0} {1}".format(self.command, (HT16K33_BLINK_CMD | blink | HT16K33_BLINK_DISPLAYON)))
        # i2cset -y 1 0x70 0xEF
        os.system("{0} {1}".format(self.command, (HT16K33_BRIGHTNESS_CMD | brightness)))

    # End def    


    def encode(self, data, double_point=False):
        """Encode data to TM1637 format.
        
        This function will convert the data from decimal to the TM1637 data fromt
        
        :param value: Value must be between 0 and 15
        
        Will throw a ValueError if number is not between 0 and 15.
        """
        ret_val = 0
        
        try:
            if (data != CLEAR_DIGIT):
                if double_point:
                    ret_val = HEX_DIGITS[data] + POINT_VALUE
                else:
                    ret_val = HEX_DIGITS[data]
        except:
            raise ValueError("Digit value must be between 0 and 15.")
    
        return ret_val

    # End def


    def set_digit(self, digit_number, data, double_point=False):
        """Update the given digit of the display."""
        os.system("{0} {1} {2}".format(self.command, DIGIT_ADDR[digit_number], self.encode(data, double_point)))    

    # End def


    def set_digit_raw(self, digit_number, data, double_point=False):
        """Update the given digit of the display using raw data value"""
        os.system("{0} {1} {2}".format(self.command, DIGIT_ADDR[digit_number], data))    

    # End def


    def set_colon(self, enable):
        """Set the colon on the display."""
        if enable:
            os.system("{0} {1} {2}".format(self.command, COLON_ADDR, 0x02))
        else:
            os.system("{0} {1} {2}".format(self.command, COLON_ADDR, 0x00))

    # End def        


    def clear(self):
        """Clear the display to read '0000'"""
        self.set_colon(False)

        self.set_digit(3, 0)
        self.set_digit(2, 0)
        self.set_digit(1, 0)
        self.set_digit(0, 0)

    # End def


    def update(self, value):
        """Update the value on the display.  
        
        This function will clear the display and then set the appropriate digits
        
        :param value: Value must be between 0 and 9999.
        
        Will throw a ValueError if number is not between 0 and 9999.
        """
        if ((value < 0) or (value > 9999)):
            raise ValueError("Value is not between 0 and 9999")
        
        self.set_digit(3, (value % 10))
        self.set_digit(2, (value // 10) % 10)
        self.set_digit(1, (value // 100) % 10)
        self.set_digit(0, (value // 1000) % 10)

    # End def

# End class


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

if __name__ == '__main__':
    import time

    delay = 0.1
    
    print("Test HT16K33 Display:")
    
    display = HT16K33(1, 0x70)

    for i in range(0, 10):
        display.update(i)
        time.sleep(delay)

    for i in range(0, 100, 10):
        display.update(i)
        time.sleep(delay)

    for i in range(0, 1000, 100):
        display.update(i)
        time.sleep(delay)
        
    for i in range(0, 10000, 1000):
        display.update(i)
        time.sleep(delay)

    for value in [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00]:
        display.set_digit_raw(0, value)
        time.sleep(delay)

    display.set_colon(True)
    time.sleep(1)

    display.clear()    
    print("Test Finished.")

Music Box Project Repo

Repo that contains all code displayed above.

Credits

Michael Tang

Michael Tang

1 project • 0 followers

Comments