The Starry Sky Table Lamp is a kinetic light sculpture that bridges digital fabrication with traditional craftsmanship. By integrating 3D printing, laser cutting, and woodworking, the project features a custom-built mechanical assembly powered by a rotary motor and Arduino microcomputer. The design is anchored by a central ring-light and crowned with a high-density panel of 684 LEDs, creating a dynamic, rotating visual experience.
Background
For my final project, I am developing a custom star light projector for my upcoming baby. The goal is to create a device that casts a gentle, soothing starlight pattern onto the nursery ceiling. I plan to use an Arduino microcontroller to program and control a series of LED strips, creating a simple and calming visual effect. This project is a way for me to combine new technical skills with a personal, heartfelt gift for my baby.
Photos of your completed project (video optional):
This Halloween, I built a “Stray Robot” costume as a special tribute to our family. With my husband and I expecting our “second” son at the end of this year, we wanted to make this Halloween all about our “first son,” our orange tabby cat, Apu, putting him in the main spotlight. The costume is inspired by the video game Stray, which was a perfect theme since the main character is an orange tabby cat in a world populated only by robots. Our costume symbolized our deep love for Apu—we would even turn into robots to make him the star. The construction was a learning process; the head was built with an internal support and a waist belt for stability, and I taught myself how to program Arduino for the LED lights. Although it was a success, if I were to make it again, I would definitely design the head to be a bit smaller.
List (with links) of materials and parts used:
BTF-LIGHTING WS2812B RGB 5050SMD Individually Addressable Digital 16×16 256 Pixels 6.3in x 6.3in LED Matrix Flexible FPCB Dream Full Color Works with SP802E Controller Image Video Text Display DC5V
BTF-LIGHTING WS2812B IC RGB 5050SMD Pure Gold Individual Addressable LED Strip 16.4FT 300LED 60Pixel/m Flexible Full Color IP30 DC5V for DIY Chasing Color Project(No Adapter or Controller)
Cardboard
Acrylic board
Spray paints
Glass paints
Hot glue
Circuit diagram
Arduino code
// Simple demonstration on using an input device to trigger changes on your
// NeoPixels. Wire a momentary push button to connect from ground to a
// digital IO pin. When the button is pressed it will change to a new pixel
// animation. Initial state has all pixels off -- press the button once to
// start the first animation. As written, the button does not interrupt an
// animation in-progress, it works only when idle.
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif
// Digital IO pin connected to the button. This will be driven with a
// pull-up resistor so the switch pulls the pin to ground momentarily.
// On a high -> low transition the button press logic will execute.
#define BUTTON_PIN 2
#define PIXEL_PIN 1 // Digital IO pin connected to the NeoPixels.
//#define PIXEL_COUNT 256 // Number of NeoPixels
#define MATRIX_WIDTH 14
#define MATRIX_HEIGHT 16
#define PIXEL_COUNT (MATRIX_WIDTH * MATRIX_HEIGHT)
//#define BRIGHTNESS 10
// Define the brightness values requested
const uint8_t BACKGROUND_BRIGHTNESS = 100; // For the RGB rainbow part
const uint8_t FACE_BRIGHTNESS = 150; // For the RGB components of the white face
// // Get current button state.
// const uint8_t BACKGROUND_BRIGHTNESS = 20; // For the RGB rainbow part
// const uint8_t FACE_BRIGHTNESS = 35; // For the RGB components of the white face
const byte On_HOLDMap[MATRIX_HEIGHT][MATRIX_WIDTH] = {
{0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,1,1,0,0,0,0,0,0,0},
{0,0,0,0,1,1,1,1,0,0,0,0,0,0},
{0,0,0,0,1,1,1,1,0,0,0,0,0,0}, // Eye
{0,0,0,0,1,1,1,1,0,0,1,0,0,0}, // Eye
{0,0,0,0,0,1,1,0,0,0,1,0,0,0}, // Eye
{0,0,0,0,0,0,0,0,0,0,1,0,0,0},
{0,0,0,0,0,0,0,0,0,0,1,0,0,0},
{0,0,0,0,0,0,0,0,0,0,1,0,0,0},
{0,0,0,0,0,0,0,0,0,0,1,0,0,0},
{0,0,0,0,0,1,1,0,0,0,1,0,0,0}, // Mouth
{0,0,0,0,1,1,1,1,0,0,1,0,0,0}, // Mouth
{0,0,0,0,1,1,1,1,0,0,0,0,0,0},
{0,0,0,0,1,1,1,1,0,0,0,0,0,0},
{0,0,0,0,0,1,1,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};
const byte Wink_Map[MATRIX_HEIGHT][MATRIX_WIDTH] = {
{0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,1,1,0,0,0,0,0,0,0},
{0,0,0,0,0,1,1,0,0,0,0,0,0,0},
{0,0,0,0,0,1,1,0,0,0,0,0,0,0}, // Eye
{0,0,0,0,0,1,1,0,0,0,1,0,0,0}, // Eye
{0,0,0,0,0,1,1,0,0,0,1,0,0,0}, // Eye
{0,0,0,0,0,0,0,0,0,0,1,0,0,0},
{0,0,0,0,0,0,0,0,0,0,1,0,0,0},
{0,0,0,0,0,0,0,0,0,0,1,0,0,0},
{0,0,0,0,0,0,0,0,0,0,1,0,0,0},
{0,0,0,0,0,1,1,0,0,0,1,0,0,0}, // Mouth
{0,0,0,0,1,1,1,1,0,0,1,0,0,0}, // Mouth
{0,0,0,0,1,1,1,1,0,0,0,0,0,0},
{0,0,0,0,1,1,1,1,0,0,0,0,0,0},
{0,0,0,0,0,1,1,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};
const byte no_words_Map[MATRIX_HEIGHT][MATRIX_WIDTH] = {
{0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,1,1,0,0,0,0,0,0,0},
{0,0,0,0,0,1,1,0,0,0,0,0,0,0},
{0,0,0,0,0,1,1,0,0,0,0,0,0,0}, // Eye
{0,0,0,0,0,1,1,0,0,0,1,0,0,0}, // Eye
{0,0,0,0,0,1,1,0,0,0,1,0,0,0}, // Eye
{0,0,0,0,0,0,0,0,0,0,1,0,0,0},
{0,0,0,0,0,0,0,0,0,0,1,0,0,0},
{0,0,0,0,0,0,0,0,0,0,1,0,0,0},
{0,0,0,0,0,0,0,0,0,0,1,0,0,0},
{0,0,0,0,0,1,1,0,0,0,1,0,0,0}, // Mouth
{0,0,0,0,0,1,1,0,0,0,1,0,0,0}, // Mouth
{0,0,0,0,0,1,1,0,0,0,0,0,0,0},
{0,0,0,0,0,1,1,0,0,0,0,0,0,0},
{0,0,0,0,0,1,1,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};
const byte heartMap[MATRIX_HEIGHT][MATRIX_WIDTH] = {
{0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,1,1,0,0,0,0,0,0,0,0},
{0,0,0,1,1,1,1,0,0,0,0,0,0,0}, // Bigger Heart Top
{0,0,1,1,1,1,1,1,0,0,0,0,0,0},
{0,0,1,1,1,1,1,1,1,0,0,0,0,0},
{0,0,1,1,1,1,1,1,1,1,0,0,0,0},
{0,0,0,1,1,1,1,1,1,1,1,0,0,0},
{0,0,0,0,1,1,1,1,1,1,1,1,0,0},
{0,0,0,1,1,1,1,1,1,1,1,0,0,0},
{0,0,1,1,1,1,1,1,1,1,0,0,0,0},
{0,0,1,1,1,1,1,1,1,0,0,0,0,0},
{0,0,1,1,1,1,1,1,0,0,0,0,0,0}, // Bigger Heart Tip
{0,0,0,1,1,1,1,0,0,0,0,0,0,0},
{0,0,0,0,1,1,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};
// Declare our NeoPixel strip object:
Adafruit_NeoPixel strip(PIXEL_COUNT, PIXEL_PIN, NEO_GRB + NEO_KHZ800);
// Argument 1 = Number of pixels in NeoPixel strip
// Argument 2 = Arduino pin number (most are valid)
// Argument 3 = Pixel type flags, add together as needed:
// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products)
// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
// NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products)
boolean oldState = HIGH;
int mode = 0; // Currently-active animation mode, 0-9
int brightness = 25;
void setup() {
strip.setBrightness(brightness);
pinMode(BUTTON_PIN, INPUT_PULLUP);
strip.begin(); // Initialize NeoPixel strip object (REQUIRED)
strip.show(); // Initialize all pixels to 'off'
}
void loop() {
boolean newState = digitalRead(BUTTON_PIN);
// Check if state changed from high to low (button press).
if((newState == LOW) && (oldState == HIGH)) {
// Short delay to debounce button.
delay(20);
// Check if button is still low after debounce.
newState = digitalRead(BUTTON_PIN);
if(newState == LOW) { // Yes, still low
if(++mode > 9) mode = 0; // Advance to next mode, wrap around after #8
switch(mode) { // Start the new animation...
case 0:
colorWipe(strip.Color( 0, 0, 0), 50); // Black/off
break;
case 1:
drawPattern_on_hold_face();
break;
case 2:
drawPattern_wink_face();
//
break;
case 3:
//colorWipe(strip.Color( 0, 0, 255), 50); // Blue
drawPattern_on_hold_face();
break;
case 4:
//theaterChase(strip.Color(127, 127, 127), 50); // White
//drawCirclePattern();
drawPattern_rainbow_heart_face();
break;
case 5:
drawPattern_on_hold_face();
//theaterChase(strip.Color(127, 0, 0), 50); // Red
break;
case 6:
drawPattern_no_words_face();
//theaterChase(strip.Color( 0, 0, 127), 50); // Blue
break;
case 7:
//rainbow(10);
//drawCirclePattern();
drawPattern_heart_face();
break;
case 8:
theaterChaseRainbow(50);
break;
}
}
}
// Set the last-read button state to the old state.
oldState = newState;
}
void drawPattern_on_hold_face() {
// Iterate over every pixel in the matrix.
for (int y = 0; y < MATRIX_HEIGHT; y++) {
for (int x = 0; x < MATRIX_WIDTH; x++) {
// Get the 1D index for the pixel at (x,y) coordinates.
int pixelIndex = getPixelIndex(x, y);
// Check the bitmap to see if this pixel is part of the smiley.
if (On_HOLDMap[y][x] == 1) {
// This pixel is part of the face.
uint8_t current_brightness = FACE_BRIGHTNESS;
// Add vertical stripe effect by brightening odd-numbered columns.
if (x % 2 != 0) {
// Add brightness for the stripe, ensuring it doesn't exceed 255.
current_brightness = min(255, current_brightness + 15);
}
// Set an RGB white color by setting R, G, and B to the same value.
strip.setPixelColor(pixelIndex, strip.Color(current_brightness, current_brightness, current_brightness));
} else {
// This pixel is part of the background.
// To reverse the rainbow direction (green->yellow->red...), we start
// at the green hue (21845) and SUBTRACT a value that increases
// across the diagonal. This makes the hue decrease.
uint16_t hue = 21845 - (((uint32_t)((x - y) + (MATRIX_HEIGHT - 1)) * 65535) / (MATRIX_WIDTH + MATRIX_HEIGHT - 2));
uint8_t current_brightness = BACKGROUND_BRIGHTNESS;
// Add vertical stripe effect by brightening odd-numbered columns.
if (x % 2 != 0) {
current_brightness += 8; // Make stripes on background brighter
}
// Get the 32-bit color value from HSV (Hue, Saturation, Value/Brightness).
uint32_t color_background = strip.ColorHSV(hue, 255, current_brightness);
strip.setPixelColor(pixelIndex, color_background);
}
}
}
// After setting all the pixel colors, push the data to the strip.
strip.show();
}
void drawPattern_no_words_face() {
// Iterate over every pixel in the matrix.
for (int y = 0; y < MATRIX_HEIGHT; y++) {
for (int x = 0; x < MATRIX_WIDTH; x++) {
// Get the 1D index for the pixel at (x,y) coordinates.
int pixelIndex = getPixelIndex(x, y);
// Check the bitmap to see if this pixel is part of the smiley.
if (no_words_Map[y][x] == 1) {
// This pixel is part of the face.
uint8_t current_brightness = FACE_BRIGHTNESS;
// Add vertical stripe effect by brightening odd-numbered columns.
if (x % 2 != 0) {
// Add brightness for the stripe, ensuring it doesn't exceed 255.
current_brightness = min(255, current_brightness + 15);
}
// Set an RGB white color by setting R, G, and B to the same value.
strip.setPixelColor(pixelIndex, strip.Color(current_brightness, current_brightness, current_brightness));
} else {
// This pixel is part of the background.
// To reverse the rainbow direction (green->yellow->red...), we start
// at the green hue (21845) and SUBTRACT a value that increases
// across the diagonal. This makes the hue decrease.
uint16_t hue = 21845 - (((uint32_t)((x - y) + (MATRIX_HEIGHT - 1)) * 65535) / (MATRIX_WIDTH + MATRIX_HEIGHT - 2));
uint8_t current_brightness = BACKGROUND_BRIGHTNESS;
// Add vertical stripe effect by brightening odd-numbered columns.
if (x % 2 != 0) {
current_brightness += 8; // Make stripes on background brighter
}
// Get the 32-bit color value from HSV (Hue, Saturation, Value/Brightness).
uint32_t color_background = strip.ColorHSV(hue, 255, current_brightness);
strip.setPixelColor(pixelIndex, color_background);
}
}
}
// After setting all the pixel colors, push the data to the strip.
strip.show();
}
void drawPattern_wink_face() {
// Iterate over every pixel in the matrix.
for (int y = 0; y < MATRIX_HEIGHT; y++) {
for (int x = 0; x < MATRIX_WIDTH; x++) {
// Get the 1D index for the pixel at (x,y) coordinates.
int pixelIndex = getPixelIndex(x, y);
// Check the bitmap to see if this pixel is part of the smiley.
if (Wink_Map[y][x] == 1) {
// This pixel is part of the face.
uint8_t current_brightness = FACE_BRIGHTNESS;
// Add vertical stripe effect by brightening odd-numbered columns.
if (x % 2 != 0) {
// Add brightness for the stripe, ensuring it doesn't exceed 255.
current_brightness = min(255, current_brightness + 15);
}
// Set an RGB white color by setting R, G, and B to the same value.
strip.setPixelColor(pixelIndex, strip.Color(current_brightness, current_brightness, current_brightness));
} else {
// This pixel is part of the background.
// To reverse the rainbow direction (green->yellow->red...), we start
// at the green hue (21845) and SUBTRACT a value that increases
// across the diagonal. This makes the hue decrease.
uint16_t hue = 21845 - (((uint32_t)((x - y) + (MATRIX_HEIGHT - 1)) * 65535) / (MATRIX_WIDTH + MATRIX_HEIGHT - 2));
uint8_t current_brightness = BACKGROUND_BRIGHTNESS;
// Add vertical stripe effect by brightening odd-numbered columns.
if (x % 2 != 0) {
current_brightness += 8; // Make stripes on background brighter
}
// Get the 32-bit color value from HSV (Hue, Saturation, Value/Brightness).
uint32_t color_background = strip.ColorHSV(hue, 255, current_brightness);
strip.setPixelColor(pixelIndex, color_background);
}
}
}
// After setting all the pixel colors, push the data to the strip.
strip.show();
}
void drawPattern_heart_face() {
// Iterate over every pixel in the matrix.
for (int y = 0; y < MATRIX_HEIGHT; y++) {
for (int x = 0; x < MATRIX_WIDTH; x++) {
// Get the 1D index for the pixel at (x,y) coordinates.
int pixelIndex = getPixelIndex(x, y);
// Check the bitmap to see if this pixel is part of the heart.
if (heartMap[y][x] == 1) {
// This pixel is part of the heart.
uint8_t current_brightness = FACE_BRIGHTNESS;
// Add vertical stripe effect by brightening odd-numbered columns.
if (x % 2 != 0) {
// Add brightness for the stripe, ensuring it doesn't exceed 255.
current_brightness = min(255, current_brightness + 15);
}
// Set an RGB red color.
strip.setPixelColor(pixelIndex, strip.Color(current_brightness, 0, 0));
} else {
// This pixel is part of the background.
// Set a static sky blue color.
uint16_t sky_blue_hue = 36000; // A nice cyan/sky blue hue.
uint8_t current_brightness = BACKGROUND_BRIGHTNESS;
// Add vertical stripe effect by brightening odd-numbered columns.
if(x == 0 && y == 0){
Serial.print("before: ");
Serial.println(current_brightness);
}
// if (x % 2 == 0) {
// current_brightness += 8; // Make stripes on background brighter
// }
if(x == 0 && y == 0){
Serial.print("after: ");
Serial.println(current_brightness);
}
// Get the 32-bit color value from HSV (Hue, Saturation, Value/Brightness).
uint32_t color_background = strip.ColorHSV(sky_blue_hue, 255, 255);
strip.setPixelColor(pixelIndex, color_background);
}
}
}
// After setting all the pixel colors, push the data to the strip.
strip.show();
}
void drawPattern_rainbow_heart_face() {
// Iterate over every pixel in the matrix.
for (int y = 0; y < MATRIX_HEIGHT; y++) {
for (int x = 0; x < MATRIX_WIDTH; x++) {
// Get the 1D index for the pixel at (x,y) coordinates.
int pixelIndex = getPixelIndex(x, y);
// Check the bitmap to see if this pixel is part of the smiley.
if (heartMap[y][x] == 1) {
// This pixel is part of the face.
uint8_t current_brightness = FACE_BRIGHTNESS;
// Add vertical stripe effect by brightening odd-numbered columns.
if (x % 2 != 0) {
// Add brightness for the stripe, ensuring it doesn't exceed 255.
current_brightness = min(255, current_brightness + 15);
}
// Set an RGB white color by setting R, G, and B to the same value.
strip.setPixelColor(pixelIndex, strip.Color(current_brightness, current_brightness, current_brightness));
} else {
// This pixel is part of the background.
// To reverse the rainbow direction (green->yellow->red...), we start
// at the green hue (21845) and SUBTRACT a value that increases
// across the diagonal. This makes the hue decrease.
uint16_t hue = 21845 - (((uint32_t)((x - y) + (MATRIX_HEIGHT - 1)) * 65535) / (MATRIX_WIDTH + MATRIX_HEIGHT - 2));
uint8_t current_brightness = BACKGROUND_BRIGHTNESS;
// Add vertical stripe effect by brightening odd-numbered columns.
if (x % 2 != 0) {
current_brightness += 8; // Make stripes on background brighter
}
// Get the 32-bit color value from HSV (Hue, Saturation, Value/Brightness).
uint32_t color_background = strip.ColorHSV(hue, 255, current_brightness);
strip.setPixelColor(pixelIndex, color_background);
}
}
}
// After setting all the pixel colors, push the data to the strip.
strip.show();
}
void drawCirclePattern() {
// Define the colors we will use.
uint32_t color_circle = strip.Color(255, 255, 255); // White
uint32_t color_background = strip.Color(255, 0, 0); // Green
// Define the center of the matrix (using float for accuracy).
float centerX = (MATRIX_WIDTH - 1) / 2.0f;
float centerY = (MATRIX_HEIGHT - 1) / 2.0f;
// Define the radius of the circle.
float radius = 1.0f;
// This value controls the thickness of the circle's line.
float thickness = 1.0f;
// Iterate over every pixel in the matrix.
for (int y = 0; y < MATRIX_HEIGHT; y++) {
for (int x = 0; x < MATRIX_WIDTH; x++) {
// Calculate the distance of the current pixel (x, y) from the center.
float distance = sqrt(pow(y - centerY, 2) + pow(x - centerX, 2));
// Get the 1D index for the pixel at (x,y) coordinates.
int pixelIndex = getPixelIndex(x, y);
// Check if the distance is within the circle's line.
if (fabs(distance - radius) < thickness) {
// This pixel is part of the circle, set it to white.
strip.setPixelColor(pixelIndex, color_circle);
} else {
// This pixel is part of the background, set it to green.
strip.setPixelColor(pixelIndex, color_background);
}
}
}
// After setting all the pixel colors in memory, push the data to the strip.
strip.show();
}
int getPixelIndex(int x, int y) {
if (y % 2 == 0) {
// Even row: 0, 2, 4,...
return y * MATRIX_WIDTH + x;
} else {
// Odd row: 1, 3, 5,...
return y * MATRIX_WIDTH + (MATRIX_WIDTH - 1 - x);
}
}
// Fill strip pixels one after another with a color. Strip is NOT cleared
// first; anything there will be covered pixel by pixel. Pass in color
// (as a single 'packed' 32-bit value, which you can get by calling
// strip.Color(red, green, blue) as shown in the loop() function above),
// and a delay time (in milliseconds) between pixels.
void colorWipe(uint32_t color, int wait) {
for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
strip.setPixelColor(i, color); // Set pixel's color (in RAM)
strip.show(); // Update strip to match
delay(wait); // Pause for a moment
}
}
// Theater-marquee-style chasing lights. Pass in a color (32-bit value,
// a la strip.Color(r,g,b) as mentioned above), and a delay time (in ms)
// between frames.
void theaterChase(uint32_t color, int wait) {
for(int a=0; a<10; a++) { // Repeat 10 times...
for(int b=0; b<3; b++) { // 'b' counts from 0 to 2...
strip.clear(); // Set all pixels in RAM to 0 (off)
// 'c' counts up from 'b' to end of strip in steps of 3...
for(int c=b; c<strip.numPixels(); c += 3) {
strip.setPixelColor(c, color); // Set pixel 'c' to value 'color'
}
strip.show(); // Update strip with new contents
delay(wait); // Pause for a moment
}
}
}
// Rainbow cycle along whole strip. Pass delay time (in ms) between frames.
void rainbow(int wait) {
// Hue of first pixel runs 3 complete loops through the color wheel.
// Color wheel has a range of 65536 but it's OK if we roll over, so
// just count from 0 to 3*65536. Adding 256 to firstPixelHue each time
// means we'll make 3*65536/256 = 768 passes through this outer loop:
for(long firstPixelHue = 0; firstPixelHue < 3*65536; firstPixelHue += 256) {
for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
// Offset pixel hue by an amount to make one full revolution of the
// color wheel (range of 65536) along the length of the strip
// (strip.numPixels() steps):
int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
// strip.ColorHSV() can take 1 or 3 arguments: a hue (0 to 65535) or
// optionally add saturation and value (brightness) (each 0 to 255).
// Here we're using just the single-argument hue variant. The result
// is passed through strip.gamma32() to provide 'truer' colors
// before assigning to each pixel:
strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
}
strip.show(); // Update strip with new contents
delay(wait); // Pause for a moment
}
}
// Rainbow-enhanced theater marquee. Pass delay time (in ms) between frames.
void theaterChaseRainbow(int wait) {
int firstPixelHue = 0; // First pixel starts at red (hue 0)
for(int a=0; a<30; a++) { // Repeat 30 times...
for(int b=0; b<3; b++) { // 'b' counts from 0 to 2...
strip.clear(); // Set all pixels in RAM to 0 (off)
// 'c' counts up from 'b' to end of strip in increments of 3...
for(int c=b; c<strip.numPixels(); c += 3) {
// hue of pixel 'c' is offset by an amount to make one full
// revolution of the color wheel (range 65536) along the length
// of the strip (strip.numPixels() steps):
int hue = firstPixelHue + c * 65536L / strip.numPixels();
uint32_t color = strip.gamma32(strip.ColorHSV(hue)); // hue -> RGB
strip.setPixelColor(c, color); // Set pixel 'c' to value 'color'
}
strip.setBrightness(10);
strip.show(); // Update strip with new contents
delay(wait); // Pause for a moment
firstPixelHue += 65536 / 90; // One cycle of color wheel over 90 frames
}
}
}
Include a description of your costume and its intention:
My love for orange tabby cats drew me to the character Momo from the video game Stray. I believe he embodies the enduring spirit of a cat lover—a spirit that continues to love and be with cats no matter what. Inspired by this, I’m recreating his adorable monitor face for a costume, aiming for a look that is charming, heartwarming, and has a unique tropical feel.
What Arduino technique(s) you are using
My next step is to program Arduino the interaction between buttons and an Adafruit NeoPixel LED.
Include photos of your progress:
Materials and parts you’re using (should be as complete a list as possible, with links to sources)
BTF-LIGHTING WS2812B RGB 5050SMD Individually Addressable Digital 16×16 256 Pixels 6.3in x 6.3in LED Matrix Flexible FPCB Dream Full Color Works with SP802E Controller Image Video Text Display DC5V
BTF-LIGHTING WS2812B IC RGB 5050SMD Pure Gold Individual Addressable LED Strip 16.4FT 300LED 60Pixel/m Flexible Full Color IP30 DC5V for DIY Chasing Color Project(No Adapter or Controller)
Cardboard
Acrylic board
Write a to-do list of the remaining tasks required to finish your costume
Momo is a Companion in The Slums in Stray. As a member of the Outsiders, Momo is a central character in the game.
Shopping List/Supplies – Cardboard Box – White paint – Transparent acrylic – LED Strips – Red Tank Tops – Black Shirt – Green flower Jacket – Arduino
2. BMO from Adventure Time:
BMO (shortened from “Be MOre” phonetically spelled “BeeMO“), also referred to as Moe Mastro “Llabtoof” Giovanni Jr., and called the “King of Ooo” in the future, is one of the main characters of Adventure Time.
Shopping List/Supplies – Cardboard Box – Green paint – Transparent acrylic – LED Strips – Green Shirt – Green pants – Arduino
3. Paragraph description of your project, its story, and the target user. Some ideas to consider: The purpose of this Maneki-neko project is to bring users luck and fortune, symbolized by its bells and glowing coin. The character design was inspired by a powerful, monster-like character from an animation I love called “DAN DA DAN”—the “Turbo Granny” who is a powerful yokai or apparition. I especially like the glowing coin because it is so eye-catching. I hope this final design conveys a feeling of prosperity and good fortune to all who see it.
4. Description of materials and parts used
Sherpa
Wool felt
Flannel
Linen
Zipper
Battery pack from kit provided
3 AAA batteries
3 yellow LEDs
1 white LED
Poly fill
Electrical wires
Heat shrink tubing
3 resistors (Brown Black Red Gold) 1k Ohms
1 resistor (Yellow Violet Brown Gold) 470 Ohms
5. Description of your journey through this project:
Did you try something new during this project? What was the experience like This project provided me with hands-on new experience in several key areas:
Digital knitting
Operating an industrial sewing machine
Stitching specialty fabrics with a standard sewing machine
What surprised or challenged you the most during this project?
The initial phase of the Maneki-neko project flowed seamlessly, moving from sizing and expression design to pattern-making and digital embroidery. The real challenge began during assembly. The chosen fabric was thick and fuzzy, causing the sewing machine to constantly jam, which forced frequent stops for cleaning. This difficulty led to some shape distortion, but through persistence, the piece was successfully stitched together.
If you had more time to work on this project, what would you do to refine it further?
I will resew the Maneki-neko’s body by hand to center it.
6. Circuit diagram
8. In-progress images
Testing the white LED with resistor (Brown Black Red Gold) 1k Ohms
Testing the white LED with resistor (Yellow Violet Brown Gold) 470 Ohms
My character is a Japanese lucky cat from the anime show Dandadan. This cat has a very interesting backstory: a yokai named Turbo Granny is trapped inside of it.
I’ve created three different poses for Turbo Granny Cat, with the LEDs in various locations.
Design 1
Design 2
Design 3
I’ve also run some tests to see how different fabrics affect the lighting.
Cotton
White Cotton
Yellow Cotton
Tissu
Sherpa
Felt
I’ve decided to use Sherpa for Turbo Granny cat’s hair and felt for the decorations, including the neck bell, collar, and gold coin.
Here is the test prototype for the glowing money coin.
Use a pry tool to remove the black plastic cover from the Dualsense controller.
2. Pry both the L1 and R1 shoulder buttons off.
3. Use a Phillips head screwdriver to remove two hidden screws under the shoulder buttons.
4. Remove two screws on the bottom of the left and right handles.
5. Use a prying tool to open the controller via the gap connecting the front and back covers.
6. Remove the back plate to reveal the battery and vibration mechanisms.
7. Using tweezers, remove the battery connection from the motherboard.
8. Remove the battery by lifting it up.
9. Remove the sole screw to remove the battery plate and access the motherboard.
10. Using tweezers, remove the microphone from the motherboard.
11. Remove all ribbon cables connected to the motherboard.
12. Carefully lift the motherboard off the controller without disconnecting the soldered cables.
13. Pull off both thumb stick buttons.
13. Peel off the plastic button cover and remove loose buttons from the D Pad, PS shape buttons, microphone mute button, share button, options button, and PS logo button.
15. Unscrew two screws below the L2 and R2 shoulder buttons.
16. Remove the clear light guide from the controller.
17. Remove four more screws on the side of the shoulder buttons.
18. Remove the front cover from the centerpiece.
19. Remove the side cover from the L2 and R2 shoulder buttons.
20. Pop off both trigger mechanisms without disconnecting from the soldered motherboard.
21. Using tweezers, pull off the rod holding the shoulder buttons to remove L2 and R2.
22. Unscrew the sole screw on the front faceplate to remove the touch pad button.
23. Final disassembled controller.
Identify the materials used for each component
Make a list of the tools and techniques you used to take it apart
Phillips Screwdriver (#00 or #1): Using the wrong type of screwdriver can strip the tiny screws.
Plastic Opening Tools (Pry or pick tools): For prying the plastic shell apart without causing damage.
Tweezers: To safely handle and disconnect the small ribbon cables.
Select two design elements that interest you and describe why you think the designer(s) made it that way
Sony has equipped the PS5 controller with dual rumble motors positioned within the handles. This design allows for the efficient transfer of vibrations to the center of the user’s hands, enhancing the speed and immersion of the sensory feedback.
Trigger mechanisms: The trigger is ergonomically shaped to fit the human hand, allowing for intuitive and easy operation by both left- and right-handed users. This design brings to mind the trigger mechanism of a gun.
Part Numbers on Chips
cxd90064gg – Processes inputs from the buttons, joysticks, and adaptive triggers, and manages the haptic feedback, speakers, and wireless communication with the PS5.
mXT144U – Allows touchpad to operate similar to how a touchscreen would.
Realtek ALC1016 – Handles headset jack support, microphone handling, and audio processing.
Realtek ALC5524 – Handles audio decoding, voice signal processing, and the audio for the 3.5mm headset jack.
Dialog DA9087 – Handles charging, power distribution, and regulation for the controller.
Before coming to PoD: Senior System Administrator at the School of Visual Arts (SVA), BFA Animation department.
Background: Wini holds dual degrees in Computer Science (from Taiwan) and Computer Arts, 3D and VFX (BFA from SVA), giving her a unique foundation in both technology and creative arts.
Interests: Making stop motion & puppets, yoga, and cooking vegetarian food.
Most looking forward to: I am most looking forward to the hands-on opportunity to be creative and bring my ideas to life.
Most apprehensive about: My biggest apprehension is managing my time effectively to keep up with the course demands.