Note
Hello, welcome to the SunFounder Raspberry Pi & Arduino & ESP32 Enthusiasts Community on Facebook! Dive deeper into Raspberry Pi, Arduino, and ESP32 with fellow enthusiasts.
Why Join?
Expert Support: Solve post-sale issues and technical challenges with help from our community and team.
Learn & Share: Exchange tips and tutorials to enhance your skills.
Exclusive Previews: Get early access to new product announcements and sneak peeks.
Special Discounts: Enjoy exclusive discounts on our newest products.
Festive Promotions and Giveaways: Take part in giveaways and holiday promotions.
👉 Ready to explore and create with us? Click [here] and join today!
4.10 Hue Knob
Introduction
In this lesson, you will build a Hue Knob — an interactive color controller that uses a rotary encoder to adjust the hue of a circular WS2812 LED module. This WS2812 LED module contains 12 individually addressable WS2812 RGB LEDs, driven through the Fusion HAT+’s SPI-based NeoPixel interface. The external rotary encoder provides real-time user input via standard GPIO pins.
As you rotate the encoder, the 12 LEDs smoothly cycle through the full RGB color spectrum. Pressing the encoder’s built-in button resets the hue back to the starting value.
What You’ll Need
The following components are required for this project:
COMPONENT INTRODUCTION |
PURCHASE LINK |
|---|---|
- |
|
- |
|
Raspberry Pi |
- |
Wiring Diagram
Refer to the wiring diagram for assembling the components:
Setup Steps
Before running the code, you need to install the required library:
This library provides the necessary functions to control NeoPixel LEDs using SPI communication.
sudo pip3 install adafruit-circuitpython-neopixel-spi --break
All example code used in this tutorial is available in the
ai-lab-kitdirectory. Follow these steps to run the example:cd ~/ai-lab-kit/python/ sudo python3 4.10_Hue_Knob.py
When the script runs, the WS2812 module ring responds to the rotary encoder:
LEDs start in red (Hue 0°).
Rotate the encoder to smoothly cycle through the full RGB color wheel. Colors transition continuously (red → yellow → green → blue → purple → red). The terminal prints the current hue and RGB values.*
Press the encoder button to reset the hue back to 0° (red).
Press Ctrl+C to exit. All LEDs turn off before the program closes.
Code
Here is the Python script for the traffic light simulation:
#!/usr/bin/env python3
from fusion_hat.motor import Motor
from fusion_hat.pin import Pin, Mode, Pull
from fusion_hat.adc import ADC
from time import sleep, time
import math
BtnPin = Pin(22, mode=Mode.IN, pull=Pull.DOWN)
motor = Motor("M0")
thermistor = ADC("A3")
level = 0
currentTemp = None
markTemp = None
PRINT_INTERVAL = 1.0
_last_print = 0.0
button_event = False # flag: button was pressed
def temperature(samples=5, delay=0.01):
"""Read thermistor multiple times and return averaged Celsius (float) or None."""
vals = []
for _ in range(samples):
analogVal = thermistor.read()
Vr = 3.3 * float(analogVal) / 4095.0
if (3.3 - Vr) <= 0.1:
return None
Rt = 10000.0 * Vr / (3.3 - Vr)
tempK = 1.0 / (((math.log(Rt / 10000.0)) / 3950.0) + (1.0 / (273.15 + 25.0)))
vals.append(tempK - 273.15)
sleep(delay)
return sum(vals) / len(vals)
def motor_run(lv):
lv = max(0, min(4, lv))
motor.power(0 if lv == 0 else lv * 25)
return lv
def changeLevel():
"""Button press: cycle level 0~4 and set a flag for main loop to print."""
global level, button_event
level = (level + 1) % 5
button_event = True
BtnPin.when_activated = changeLevel
def main():
global level, currentTemp, markTemp, _last_print, button_event
markTemp = temperature()
while True:
currentTemp = temperature()
if currentTemp is None:
print("Sensor read failed. Please check the sensor.")
sleep(0.5)
continue
# Handle button event in main loop (stable timing)
if button_event:
button_event = False
markTemp = currentTemp
print(f"[Button] Level -> {level} | Temp: {currentTemp:.2f} °C | Mark: {markTemp:.2f} °C")
# Periodic temperature print
now = time()
if now - _last_print >= PRINT_INTERVAL:
if markTemp is None:
markTemp = currentTemp
print(f"Temp: {currentTemp:.2f} °C | Mark: {markTemp:.2f} °C | Level: {level}")
_last_print = now
# Auto adjust level based on ±5°C
if markTemp is None:
markTemp = currentTemp
if level != 0:
diff = currentTemp - markTemp
if diff <= -5:
level = max(0, level - 1)
markTemp = currentTemp
print(f"[Auto] Temp down -> Level {level} (Temp: {currentTemp:.2f} °C)")
elif diff >= 5:
level = min(4, level + 1)
markTemp = currentTemp
print(f"[Auto] Temp up -> Level {level} (Temp: {currentTemp:.2f} °C)")
level = motor_run(level)
sleep(0.5)
try:
main()
except KeyboardInterrupt:
print("\nExiting...")
finally:
motor.stop()
sleep(0.1)
Understanding the Code
NeoPixel Initialization
The script uses SPI to control a NeoPixel LED ring/strip (
LED_COUNT = 12).auto_write=Falseis enabled so the LEDs update only whenstrip.show()is called, which prevents flicker and improves performance.The LEDs are cleared at startup using
strip.fill(0)andstrip.show().
Rotary Encoder Hardware Setup
Three GPIO pins are used: -
CLK_PINandDT_PINprovide the quadrature signals for rotation direction and steps. -SW_PINis the encoder button input.All pins use internal pull-ups (
Pull.UP), and the button is active LOW (pressed =0).
Key Parameters (Tuning Behavior)
DETENTS_PER_CYCLEdefines how many physical “clicks” are needed to complete a full hue rotation (0–360°). - A larger value gives finer color control.TRANSITIONS_PER_DETENTconverts raw quadrature transitions into “one detent = one count”. - Many encoders output 2 transitions per detent, but some produce 4. Adjusting this value improves accuracy.
hue_to_rgb()Converts a hue value in the range
0.0 ~ 1.0into an RGB tuple(R, G, B)with values0 ~ 255.This makes it easy to generate smooth color changes using the HSV color model.
apply_color_from_detent()Maps the encoder detent count to a hue index using modulo: -
hue_idx = detent % DETENTS_PER_CYCLEConverts the hue into an RGB color and updates all LEDs with
strip.fill(color)followed bystrip.show().Uses
last_hue_idxto avoid refreshing the LEDs when the calculated hue does not change, which reduces unnecessary updates.Prints the current hue angle, detent value, and RGB color for debugging/feedback.
reset_all()Resets the internal counter state: -
raw(raw transition count) -last_detent(last displayed detent) -last_hue_idx(last displayed hue index)Calls
apply_color_from_detent(0)to immediately restore the LEDs to the starting color (Hue = 0°).
Main Loop (Polling + Direction Detection)
The program polls
CLKcontinuously and checks for changes: - IfCLKchanges, the encoder moved one step in the quadrature sequence.Direction is determined by comparing DT to CLK: -
dt.value() != cmeans one rotation direction (increment) - otherwise decrementThe raw transition count is converted into detents: -
detent = raw // TRANSITIONS_PER_DETENTThe LED color is updated only when the detent value changes.
Button Reset and Debounce
When the button is pressed (
sw.value() == 0), the program callsreset_all().A short debounce delay is applied, and the script waits until the button is released to prevent multiple resets from a single press.
Clean Exit
Pressing
Ctrl + Cexits the program.In the
finallyblock, all LEDs are turned off (strip.fill(0)andstrip.show()) so the hardware is left in a safe state.
Troubleshooting
LEDs do not light up
Check wiring to the WS2812 LED module.
Ensure the Fusion HAT+ SPI NeoPixel interface is enabled.
Make sure you are using a supported WS2812/WS2812B LED module.
Colors change too quickly or too slowly
Adjust
STEPS_PER_CYCLEto refine or increase sensitivity.
Button press does not reset hue
Verify SW is connected to GPIO27.
Ensure the pin is configured with
pull=Pin.PULL_UP.
Script exits immediately
Confirm
pause()is imported fromsignal.Make sure no other process is using SPI.
Try It Yourself
Want to extend this project further? Try these ideas:
Add Brightness Control
Use another variable (e.g., encoder press + rotation) to adjust LED brightness from 0–255.
Add Multiple Display Modes
Press the encoder to cycle through modes:
Solid color (default)
Rainbow animation
Breathing effect
Color wipe
Add a Power Toggle
Long-press the encoder button to turn the LED ring on/off.
Make Hue Change Smoother
Increase
STEPS_PER_CYCLEor add interpolation for buttery-smooth transitions.Show Direction Feedback
Glow one LED green when rotating clockwise, red when counterclockwise.
These small extensions turn the simple “Hue Knob” into a versatile RGB control interface.