WS281X (Ring Led) - Documentation

Authors

  • Billy Nguyen
  • Michael Hoang

Introduction

The Adafruit_NeoPixel library is a feature rich and well implement interface created and maintained by Adafruit Industries. The rest of the documentation for this project will be based on this library.

This documentation will be structured around a project involving a DHT11 temperature sensor, however the information given can be applied to other inputs.

This example project will take a temperature reading from the sensor and a target temperature from the user. The output to the ring leds will then inform whether the actual temperature was greater or less than the target.

Circuit

The wiring for this circuit is straight forward and simple to follow. It should be noted that while certain components can be powered with 3.3V, the circuit should not be powered with the Raspberry Pi's 3.3V output. More information about the sensors used can be found on their datasheets.

Components used:

  • Raspberry Pi
  • DHT11
  • Ring Led (WS281X)

alt text

Packages

The following requirements will be used for this project:

Installation

We'll begin by installing the dependencies needed:

$ sudo apt-get update
$ sudo apt-get install build-essential python-dev git scons swig

The rpi-ws281x package will be installed and compiled next:

$ git clone https://github.com/jgarff/rpi_ws281x.git
$ cd rpi_ws281x
$ scons

Once the library is compiled, we can execute by:

$ cd python
$ sudo python setup.py install

The library should now be installed and ready for use.

Using the library

The first step to using the library is to import it into your project:

In [ ]:
from neopixel import *

Setting up the led with constants is a good way of keeping track of important inputs:

In [ ]:
LED_CHANNEL = 0 
LED_PIN = 18                # GPIO.BCM
LED_COUNT = 16              # Number of active leds. 
LED_FREQ_HZ = 800000        # Frequency of the LED signal. Should be 800khz or 400khz.
LED_DMA = 10                # DMA channel to use, can be 0-14.
LED_BRIGHTNESS = 100        # Set to 0 for darkest and 255 for brightest.
LED_INVERT = False          # Set to True to invert the LED signal.

LED_CENTER = LED_COUNT / 2  # Center led on the ring 

Setting up colours for future use is also a trivial matter:

In [ ]:
COLOR_OFF = Color(0, 0, 0)
COLOR_RED = Color(255, 0, 0)
COLOR_BLUE = Color(30, 144, 255)
COLOR_GREEN = Color(50, 205, 50)

Initialize the led by doing the following:

In [ ]:
r_led = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS, LED_CHANNEL)

Functions

The following functions were created for this project:

An important thing to note -- the begin() function must be called before any library functions can be used.

In [ ]:
def r_led_setup(r_led):
    r_led.begin()
    r_led.show()

The ring leds work by "painting" each individual light. To turn on certain leds, a loop must be used. The same idea follows for turning them off (ie. loop through all leds and "paint" each led off).

An important function to note from the libray is setPixelColor(n, red, green, blue) where n specifies the led on the ring.

In [ ]:
def r_led_on(r_led, wait_ms=20, iterations=1):
    for i in range(r_led.numPixels()):
        r_led.setPixelColor(2, 255, 255, 255)
        r_led.show()
        time.sleep(wait_ms/1000.0)
In [ ]:
def r_led_off(r_led, color, wait_ms=50):
    for i in range(r_led.numPixels()):
        r_led.setPixelColor(i, color)
        r_led.show()
        time.sleep(wait_ms/1000.0)

The color generator is used to create random colours that can be used in other functions.

In [ ]:
def color_generator(pos):
    if pos < 85:
        return Color(pos * 3, 255 - pos * 3, 0)
    if pos < 170:
        pos -= 85
        return Color(255 - pos * 3, 0, pos * 3)
    else:
        pos -= 170
        return Color(0, pos * 3, 0, pos * 3)
In [ ]:
def r_led_rainbow(r_led, wait_ms=20, iterations=1):
    for j in range(256*iterations):
        for i in range(r_led.numPixels()):
            r_led.setPixelColor(i, color_generator((i+j) & 255))
        r_led.show()
        time.sleep(wait_ms/1000.0)

The following are aux functions for temp_to_led().

In [ ]:
def r_led_right(diff, r_led, color, led_count):
    if led_count == LED_COUNT:
         return led_count
    for i in range(led_count):
        r_led.setPixelColor(i + led_count, color)
    for i in range(led_count - diff):
        r_led.setPixelColor(i, COLOR_OFF)
    r_led.show()
    return led_count + 1
In [ ]:
def r_led_left(diff, r_led, color, led_count):
    if led_count == 1:
        return led_count
    for i in range(led_count):
        r_led.setPixelColor(led_count - i, color)
    r_led.show()
    return led_count - 1

The following function was used to check whether the temperature was greater or less than the target.

In [ ]:
def temp_to_led(temp, input_temp, r_led, led_count):
    diff_temp = int(input_temp - temp)
    if diff_temp > 0:
        color = COLOR_GREEN
        led_count = r_led_right(diff_temp, r_led, color, led_count)
    elif diff_temp < 0:
        color = COLOR_RED
        led_count = r_led_left(diff_temp, r_led, color, led_count)
    else:
        pass
    return led_count

Additional Functions

The NeoPixel library comes with a set of built-in functions that are very useful. The following ones are listed due to its utility:

setBrightness(brightness) # Min: 0 - Max: 255
getBrightness()
getPixelColor(n)          # n is the specified led

Main Program

The main program of this project can be seen below:

In [ ]:
# Main Program
print("Starting ring_led.py...")
print()

# Starting Ring Led
print("Booting ring led...")
r_led_setup(r_led)
r_led_on(r_led)
r_led_rainbow(r_led)
r_led_off(r_led, COLOR_OFF)
print("...ring led active.")
print()

led_count = LED_CENTER

try:
    while True:

        # Reset LED
        print("Reseting led...")
        r_led_off(r_led, COLOR_OFF, 1)
        led_count = LED_CENTER
        print("...led reset.")
        print()

        # User Input
        input_temp = int(input("Enter temperature (C): "))

        # Starting DHT11
        humidity, temperature = DHT.read_retry(DHT11_SENSOR, DHT11_PIN)
        print("Temp: {0:0.1f}C Humidity: {1:0.1f}%".format(temperature, humidity))
        print()

        # DHT11 -> Ring Led
        led_count = temp_to_led(temperature, input_temp, r_led, led_count)
        time.sleep(2)

except KeyboardInterrupt:
    print()
    print("...stopping ring_led.py")
    r_led_off(r_led, COLOR_OFF)
    pass

GPIO.cleanup()

Execution

Running the program follows the standard CLI Python invocation:

$ sudo python3 ring_led.py

Output

An example of the output is as follows:

Starting ring_led.py...

Booting ring led...
...ring led active.

Reseting led...
...led reset.

Enter temperature (C): 30
Temp: 22.0C Humidity: 10.0%

Reseting led...
...led reset.

Enter temperature (C): 10
Temp: 22.0C Humidity: 10.0%

Reseting led...
...led reset.

Enter temperature (C): 22
Temp: 22.0C Humidity: 10.0%

Reseting led...
...led reset.

^CInterrupted
...stopping ring_led.py

Challenges & Next Steps

The following implementation will turn on all the leds starting from center to the left-most or right-most led. This can be enhanced by implementing a range so that each led represents a certain amount that the actual temperature varies vs the target. This was attempted in the temp_to_led function as well as the corresponding auxiliary functions but was not finished.

The ring led has the potential to relay a lot of information due to the shape, and RGB capabilities. A good idea would be to have the center led stay on all the time and blink when the target temperature matches the measured. Certain colours can also be used to convey information about the humidity since the DHT11 also reads humidity.

As a final note, many of the above functions can be refactored into a much cleaner design. However, most of the time spent for this project was understanding the NeoPixel library.

Additional Resourses