The best tools to make your project dreams come true

Login or Signup
USD


By Ben Roloff

Celebrating a Chinese New Year

Introduction:

New Years is a huge celebration in China that occurs a little later than the traditional calendar New Year. This is because they follow a lunar calendar. There are couple iconic decorations for Chinese New Year like a dancing lion, firecrackers, and red lanterns. All of these are used to scare Nian (meaning Year) away. We will show you how to easily animate some of these decorations. The video showcasing this project and celebrating the Chinese New Year can be found below...

Bill of Materials:

Project:

The Lanterns

Red Paper Lantern

One of the most iconic decorations is the red paper lantern. We have eight, a lucky number in China. In each lantern is a Neopixel ring with 12 addressable RGBW LEDs on each, totalling 96 individual LEDs. Neopixel rings are convenient because when linked together, they are treated like one string, and allow you to update a few of the LEDs at a time and send out the changes. This makes it easier to control each lantern separately. First, you will need to solder the power and ground of each Neopixel ring in parallel to the next one, and solder the data out to the data in of the next Neopixel. The last Neopixel’s data out will be left open. The first Neopixel’s data in will be connected to pin D5 of the Arduino Uno. We soldered the power cable connections and the pin 5 connection to the Arduino shield. Ensure the Arduino ground, Neopixel ground, and power supply ground are all connected. We used a code for birthday candles that gives a nice orange hue. To make them brighter and appear to breathe as if they are actual candles, we coded the white level of the RGBW pixels to randomize from 200 to 255. One thing to verify is that you have the correct code for the Neopixel library and set it as NEO_GRBW when calling the Adafruit_Neopixel() command. You can see the code we used here:

Copy Code
// NeoPixel Ring simple sketch (c) 2013 Shae Erisson
// released under the GPLv3 license to match the rest of the AdaFruit NeoPixel library
// Modified by: Ben Roloff
// Company: Digi-Key
// Title: Chinese Lanterns 2.0
// Flame Hue code taken from Adafruit Circuit Playground library example Birthday_Candles.ino

#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif

// Neopixel data pin
#define PIN 5

// The total number of LEDs is the NUMPIXELS, the NUMLANTERNS is the number of Neopixel rings, and the NUMLEDs is the number of LEDs on a Neopixel ring
#define NUMPIXELS 96
#define NUMLANTERNS 8
#define NUMLEDS 12

// max brightness level
#define BRIGHTNESS 255

// an orange hue
#define FLAME_HUE 35
float frequencies96 = {0}; // Random frequencies and phases will be generated for each pixel to
float phases96 = {0}; // define an organic but random looking flicker effect.

uint8_t INTENSITY; // I use this variable as the change in the white LED setting

// When we setup the NeoPixel library, we tell it how many pixels, and which pin to use to send signals.
// Note that for older NeoPixel strips you might need to change the third parameter--see the strandtest
// example for more information on possible values.
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRBW + NEO_KHZ800);

const uint8_t PROGMEM gamma8 = {
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, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5,
5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10,
10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16,
17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36,
37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50,
51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68,
69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89,
90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114,
115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142,
144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175,
177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213,
215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 };


float setPixelHSV(int i, float h, float s, float v) {
// Convert HSV to RGB
float r, g, b = 0.0;
HSVtoRGB(&r, &g, &b, h, s, v);
// Lookup gamma correct RGB colors (also convert from 0...1.0 RGB range to 0...255 byte range).
uint8_t r1 = pgm_read_byte(&gamma8int(r*255.0));
uint8_t g1 = pgm_read_byte(&gamma8int(g*255.0));
uint8_t b1 = pgm_read_byte(&gamma8int(b*255.0));
// Set the color of the pixel.
pixels.setPixelColor(i, pixels.Color(r1,g1,b1,INTENSITY));
}

// Flickering candle animation effect.
// Uses a noisey sine wave to modulate the hue of each pixel within a range
// of flame colors. The sine wave has some low and high frequency components
// which give it an organice and seemingly random appearance.
void showLitFlicker(uint32_t current) {
// First determine the low and high bounds of the flicker hues.
// These are +/- 10 degrees of the specified target hue and will
// wrap around to the start/end as appropriate.
float lowHue = fmod(FLAME_HUE - 10.0, 360.0);
float highHue = fmod(FLAME_HUE + 10.0, 360.0);
// Convert time from milliseconds to seconds.
float t = current/1000.0;
// Loop through each pixel and compute its color.
// We first loop through the lanterns
for (int i = 0; i < NUMLANTERNS; i++) {
// We get a random instensity for the the white LED for each lantern
INTENSITY = random(200, 255);
// We then loop through each LED on the each lantern
for (int j = 0; j < NUMLEDS; j++){
// This pixel should be lit, so compute its hue by composing
// a low frequency / slowly changing sine wave with a high
// frequency / fast changing cosine wave. This means the candle will
// pulse and jump around in an organice but random looking way.
// The frequencies and phases of the waves are randomly generated at
// startup in the setup function.
// Low frequency wave is a sine wave with random freuqency between 1 and 4,
// and offset by a random phase to keep pixels from all starting at the same
// color:
float lowFreq = sin(2.0*PI*frequencies(i*NUMLEDS)+j*t + phases(i*NUMLEDS)+j);
// High frequency is a faster changing cosine wave that uses a different
// pixel's random frequency.
float highFreq = cos(3.0*PI*frequencies(((i*NUMLEDS)+j)+5)*t);
// Add the low and high frequency waves together, then interpolate their value
// to a hue that's +/-20% of the configured target hue.
float h = lerp(lowFreq+highFreq, -2.0, 2.0, lowHue, highHue);
setPixelHSV((i*NUMLEDS)+j, h, 1.0, 1.0);
}
}
pixels.show();
}

// HSV to RGB color space conversion function taken directly from:
// https://www.cs.rit.edu/~ncs/color/t_convert.html
void HSVtoRGB( float *r, float *g, float *b, float h, float s, float v )
{
int i;
float f, p, q, t;
if( s == 0 ) {
// achromatic (grey)
*r = *g = *b = v;
return;
}
h /= 60; // sector 0 to 5
i = floor( h );
f = h - i; // factorial part of h
p = v * ( 1 - s );
q = v * ( 1 - s * f );
t = v * ( 1 - s * ( 1 - f ) );
switch( i ) {
case 0:
*r = v;
*g = t;
*b = p;
break;
case 1:
*r = q;
*g = v;
*b = p;
break;
case 2:
*r = p;
*g = v;
*b = t;
break;
case 3:
*r = p;
*g = q;
*b = v;
break;
case 4:
*r = t;
*g = p;
*b = v;
break;
default: // case 5:
*r = v;
*g = p;
*b = q;
break;
}
}

// Linear interpolation of value y within y0...y1 given x and x0...x1.
float lerp(float x, float x0, float x1, float y0, float y1) {
return y0 + (x-x0)*((y1-y0)/(x1-x0));
}

void setup() {
// This is for Trinket 5V 16MHz, you can remove these three lines if you are not using a Trinket

// End of trinket special code
for (int i=0; i<NUMPIXELS; ++i) {
// Generate random floating point frequency values between 1.0 and 4.0.
frequenciesi = random(1000, 4000)/1000.0;
// Generate random floating point phase values between 0 and 2*PI.
phasesi = random(1000)/1000.0 * 2.0 * PI;
}

pixels.setBrightness(BRIGHTNESS); // Set brightness to max
pixels.begin(); // This initializes the NeoPixel library.
}

void loop() {

// For a set of NeoPixels the first NeoPixel is 0, second is 1, all the way up to the count of pixels minus one.

for(int i = 0; i < 1000; i++){
showLitFlicker(i);
}
}

The Dancing Lion

The Dancing Lion

The other popular decoration we animated is the Dancing Lion. These puppets are usually seen dancing around in celebration. To simulate a dancing lion, we used two servo motors to make its head move side to side and mouth open and close. Two panel mount LEDs were fit into the eyes to make them “glow” green. We 3D printed a mount for each servo, a pulley for the mouth control servo, and a cap for the other servo. We have the files for the 3D drawings here that were created using Autodesk Fusion 360:

A piece of wood was placed across the inside of the head to attach the servos. The mouth control servo sat atop that. We attached fishing line from the bottom lip of the mouth through a pair of 3D printed fangs to the pulley on top of the servo. We soldered 3 pin headers (for servo connections) and LED light connections to the Arduino shield. This allows for easy connections and reprogramming of the Arduino. We created a simple animation of the head moving and mouth opening and closing. You can see it in the code below. A function that controls the servos by button was created for testing purposes.

Copy Code
/*
* Author: Ben Roloff
* Company: Digi-Key
* Project: Dancing Lion
* Description:
* Program to add animation to a dancing lion puppet.
* It controls two servos and two LEDs. It moves the head left and right.
* It opens and closes the mouth. The LEDs are used for the eyes.
*/

#include <Servo.h>

Servo Mouth;
Servo Head;
const int MouthOpenBut = 2;
const int MouthCloseBut = 3;
const int HeadLeftBut = 4;
const int HeadRightBut = 5;
const int EyeBut = 7;
const int EyeLED = 6;
int MouthOpenState;
int MouthCloseState;
int HeadLeftState;
int HeadRightState;
int EyeState;
int HeadPos = 90;
int MouthPos = 90;


/*
* Setup
* Set all the buttons to pull up pins.
* Start the LEDs at max output.
*/
void setup() {
Mouth.attach(9);
Head.attach(10);
pinMode(MouthOpenBut, INPUT_PULLUP);
pinMode(MouthCloseBut, INPUT_PULLUP);
pinMode(HeadLeftBut, INPUT_PULLUP);
pinMode(HeadRightBut, INPUT_PULLUP);
pinMode(EyeBut, INPUT_PULLUP);
// uncomment below code if doing the control function
//pinMode(EyeLED, OUTPUT);
analogWrite(EyeLED, 255);
}

/*
Animation Function
The motors and LEDs go through a predetermined animation.
*/
void animation()
{
// i is for changes in the head. j is for the changes in the mouth. k is for changes to the LEDs
int i = 90;
int j = 90;
int k = 255;

// start with moving the head left from center and close the mouth
for(i = 90; i > 45; i--)
{
Mouth.write(j);
delay(20);
Head.write(i);
delay(35);
analogWrite(EyeLED, k);
k -=4;
j -=2;
}
k = 75;
j = 0;

// move the head back to center and and open the mouth completely
for(i = 45; i < 90; i++)
{
Mouth.write(j);
delay(20);
Head.write(i);
delay(35);
analogWrite(EyeLED, k);
k +=4;
j +=4;
}
k = 255;
j = 180;

// move the head to the right and close the mouth
for(i = 90; i < 135; i++)
{
Mouth.write(j);
delay(20);
Head.write(i);
delay(35);
analogWrite(EyeLED, k);
k -=4;
j -=4;
}
k = 75;
j = 0;

// bring everything back to center
for(i = 135; i > 90; i--)
{
Mouth.write(j);
delay(20);
Head.write(i);
delay(35);
analogWrite(EyeLED, k);
k+=4;
j+=2;
}
// wait 2 seconds then repeat
delay(2000);
}

/*
Control Function
The motors and LEDs are controlled by button presses.
*/
void control()
{
// read each button state and delay to combat debouncing
MouthOpenState = digitalRead(MouthOpenBut);
MouthCloseState = digitalRead(MouthCloseBut);
HeadLeftState = digitalRead(HeadLeftBut);
HeadRightState = digitalRead(HeadRightBut);
EyeState = digitalRead(EyeBut);
delay(100);

// Change the mouth state at 3 degrees a press
if (MouthOpenState == LOW)
{
MouthPos += 3;
}
else if (MouthCloseState == LOW)
{
MouthPos -= 3;
}
// change the head state at 3 degrees a press
if (HeadLeftState == LOW)
{
HeadPos += 3;
}
else if (HeadRightState == LOW)
{
HeadPos -= 3;
}
// LEDs toggle
if (EyeState == LOW)
{
digitalWrite(EyeLED, !digitalRead(EyeLED));
}
// make sure to constrain the servo postions
MouthPos = constrain(MouthPos, 10, 170);
HeadPos = constrain(HeadPos, 10, 170);
Mouth.write(MouthPos);
delay(15);
Head.write(HeadPos);
delay(15);
}

/*
* Main Loop
* Either calls the control function or animation function.
*/
void loop() {

//make sure only one is called. the other should be commented out

//control();
animation();

}

Conclusion:

These are some fun, simple projects that can bring your Chinese New Year decorations to the next level. This is a good tutorial on getting started with Neopixel rings and servos for other projects that might come up.

Key Parts and Components

Add all Digi-Key Parts to Cart
  • 1528-1605-ND
  • 1050-1024-ND
  • 102-4196-ND
  • 1528-1076-ND
  • 900-00005-ND
  • 839-1244-ND
  • MG03F-100-ND
  • 1050-1035-ND
  • 732-5316-ND