Infineon Team
Published © MIT

Star Wars Lightsaber

Young Padawan, in this post you will discover the ancient art of building your own lightsaber—powered by the PSOC™ 6 AI Kit.

IntermediateFull instructions provided10 hours140
Star Wars Lightsaber

Things used in this project

Hardware components

PSOC™ 6 AI Evaluation Kit (CY8CKIT-062S2-AI)
Infineon PSOC™ 6 AI Evaluation Kit (CY8CKIT-062S2-AI)
×1
USB-PD Trigger-Modul 5V
×1
GOOBAY Powerbank
×1
LED-Tube T8
×1
USB-Typ C cable C to C
×1
USB Type C cable A to C
×1
WS2813B LED strip
×3

Software apps and online services

MicroPython
MicroPython

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free
superglue

Story

Read more

Custom parts and enclosures

Inventor file handle lightsaber

Inventor file lightsaber tip

Inventor file triangle parts

triangle part .stp file

light saber tip file .stp

lightsaber file .stp file

Schematics

Schematics

Code

micropython_code_lightsaber.py

MicroPython
Code which has to flashed on the PSOC™ 6 AI Kit
"""
Obi-Wan Lightsaber - MicroPython firmware for the PSOC(TM) 6 AI Kit
-------------------------------------------------------------------
Gesture-controlled lightsaber:
  * Fast flick to the LEFT  -> turn the blade ON (or change color if already on)
  * Fast flick to the RIGHT -> turn the blade OFF

There are 5 selectable blade colors. Every left flick while the blade is
already lit advances to the next color and wraps around.

Hardware:
  * Infineon CY8CKIT-062S2-AI (PSOC(TM) 6 AI Kit) running MicroPython
  * Built-in BMI270 6-axis IMU (I2C on P0_2/P0_3)
  * WS2812-compatible LED strip on P9_0 (126 LEDs)

Required MicroPython packages: bmi270, neopixel
  -> Thonny: Tools > Manage Packages > search & install
"""

from machine import Pin, I2C, WDT
import time
import neopixel
import bmi270

# ---------------------------------------------------------------------------
# Watchdog  initialized after startup so boot-time setup cannot trip it.
# ---------------------------------------------------------------------------
WDT_TIMEOUT_MS = 2000
wdt = None
USE_SENSOR = True
TEST_ALL_LEDS_ON = False
SENSOR_POLL_MS = 50
ANIMATION_STEP_DELAY_S = 0.001

# ---------------------------------------------------------------------------
# LED configuration
# ---------------------------------------------------------------------------
NUM_LEDS = 126              # number of LEDs in the blade
LED_PIN = 'P9_0'            # data line of the WS2812 strip

np = neopixel.NeoPixel(Pin(LED_PIN), NUM_LEDS)

# Five blade colors (R, G, B). Max brightness capped at 40% (~102).
COLORS = [
    (  0,  64, 102),  # 0 - Obi-Wan light blue
    (  0, 102,   0),  # 1 - Qui-Gon / Luke green
    (102,   0,   0),  # 2 - Sith red
    ( 64,   0, 102),  # 3 - Mace Windu purple
    (102, 102, 102),  # 4 - white / Rey
]

# ---------------------------------------------------------------------------
# Gyroscope configuration
# ---------------------------------------------------------------------------
i2c = None
bmi = None
sensor_init_error = None
if USE_SENSOR:
    try:
        i2c = I2C(scl='P0_2', sda='P0_3')
        bmi = bmi270.BMI270(i2c)
    except Exception as e:
        sensor_init_error = e
        bmi = None
last_twist_read_ms = time.ticks_ms()
last_twist_value = 0.0
sensor_fault = False
last_gyro_values = (0.0, 0.0, 0.0)


def feed_watchdog():
    if wdt is not None:
        wdt.feed()

FLICK_THRESHOLD = 200.0
DEBOUNCE_S = 0.45
TWIST_AXIS = 'z'
LEFT_IS_POSITIVE = True


def read_twist():
    global bmi, last_twist_read_ms, last_twist_value, sensor_fault, last_gyro_values
    now = time.ticks_ms()

    if not USE_SENSOR:
        return 0.0

    if time.ticks_diff(now, last_twist_read_ms) < SENSOR_POLL_MS:
        return last_twist_value

    if bmi is None:
        return 0.0

    try:
        gx, gy, gz = bmi.gyro()
        last_gyro_values = (gx, gy, gz)
        last_twist_read_ms = now
        sensor_fault = False
    except Exception as e:
        print("Gyro read error: {}".format(e))
        bmi = None
        sensor_fault = True
        return 0.0
    if TWIST_AXIS == 'x':
        last_twist_value = gx
        return last_twist_value
    if TWIST_AXIS == 'y':
        last_twist_value = gy
        return last_twist_value
    last_twist_value = gz
    return last_twist_value


# ---------------------------------------------------------------------------
# LED helpers
# ---------------------------------------------------------------------------
def show_solid(color):
    for i in range(NUM_LEDS):
        np[i] = color
    np.write()


def blade_off():
    for i in range(NUM_LEDS):
        np[i] = (0, 0, 0)
    np.write()


def ignite(color, step_delay=ANIMATION_STEP_DELAY_S):
    """Animate the blade extending from the hilt to the tip."""
    blade_off()
    for i in range(NUM_LEDS):
        np[i] = color
        np.write()
        feed_watchdog()
        time.sleep(step_delay)


def retract(color, step_delay=ANIMATION_STEP_DELAY_S):
    """Animate the blade retracting from the tip back into the hilt."""
    for i in range(NUM_LEDS - 1, -1, -1):
        np[i] = (0, 0, 0)
        np.write()
        feed_watchdog()
        time.sleep(step_delay)


# ---------------------------------------------------------------------------
# Main loop
# ---------------------------------------------------------------------------
def main():
    global wdt, bmi
    color_index = 0
    is_on = True
    if TEST_ALL_LEDS_ON:
        show_solid(COLORS[color_index])
    else:
        ignite(COLORS[color_index])
    wdt = WDT(timeout=WDT_TIMEOUT_MS)
    feed_watchdog()
    last_action = time.ticks_ms()
    last_status = time.ticks_ms()
    status_interval_ms = 1000

    print("Lightsaber ready. Flick LEFT to ignite / change color, RIGHT to retract.")
    if not USE_SENSOR:
        print("Sensor disabled for diagnostics. Twist input forced to 0.0 dps.")
    elif sensor_init_error is not None:
        print("Sensor init failed: {}".format(sensor_init_error))
    else:
        print("Sensor diagnostics active. Status output will show gyro values.")
    if TEST_ALL_LEDS_ON:
        print("LED test mode active. All LEDs are held on with a fixed color.")

    while True:
        try:
            now = time.ticks_ms()

            if time.ticks_diff(now, last_status) >= status_interval_ms:
                if not USE_SENSOR:
                    sensor_status = "DISABLED"
                elif bmi is None:
                    sensor_status = "FAULT"
                elif sensor_fault:
                    sensor_status = "ERROR"
                else:
                    sensor_status = "OK"

                if is_on:
                    print(
                        "Status | blade=ON | color_index={} | color_rgb={} | sensor={} | gyro=({:.1f}, {:.1f}, {:.1f}) | twist={:.1f} dps".format(
                            color_index,
                            COLORS[color_index],
                            sensor_status,
                            last_gyro_values[0],
                            last_gyro_values[1],
                            last_gyro_values[2],
                            last_twist_value
                        )
                    )
                else:
                    print(
                        "Status | blade=OFF | sensor={} | gyro=({:.1f}, {:.1f}, {:.1f}) | twist={:.1f} dps".format(
                            sensor_status,
                            last_gyro_values[0],
                            last_gyro_values[1],
                            last_gyro_values[2],
                            last_twist_value
                        )
                    )
                last_status = now

            if time.ticks_diff(now, last_action) < int(DEBOUNCE_S * 1000):
                feed_watchdog()
                time.sleep(0.01)
                continue

            twist = read_twist()
            if not LEFT_IS_POSITIVE:
                twist = -twist

            # --- Left flick -------------------------------------------------
            if twist > FLICK_THRESHOLD:
                print("LEFT flick detected | twist={:.1f}".format(twist))
                if not is_on:
                    color_index = 0
                    print("Igniting (color {})".format(color_index))
                    ignite(COLORS[color_index])
                    is_on = True
                else:
                    color_index = (color_index + 1) % len(COLORS)
                    print("Switching to color {} = {}".format(color_index, COLORS[color_index]))
                    show_solid(COLORS[color_index])
                last_action = time.ticks_ms()

            # --- Right flick ------------------------------------------------
            elif twist < -FLICK_THRESHOLD:
                print("RIGHT flick detected | twist={:.1f}".format(twist))
                if is_on:
                    print("Retracting blade")
                    retract(COLORS[color_index])
                    is_on = False
                    last_action = time.ticks_ms()

        except Exception as e:
            print("Loop error: {}".format(e))
            bmi = None

        feed_watchdog()
        time.sleep(0.01)


try:
    main()
except KeyboardInterrupt:
    blade_off()
    print("Lightsaber stopped.")

Credits

Infineon Team
129 projects • 206 followers
Hands-on projects, tutorials, and code for sensors, MCUs, connectivity, security, power, and IoT. Follow for new builds and ideas.

Comments