Chibi-Totoro Lamp

Chibi-Totoro Lamp

Spraying water to turn on the lamp

Inspiration

All bearing the weight of a multitude of technical projects and engineering finals, two of my classmates and I opted to make an art sculpture/decorative piece for our microprocessor project. Being big fans of Studio Ghibli’s animations, we decided to make a lamp that would be themed after the famous rain scene from the movie My Neighbor Totoro.

Rain scene from the movie My Neighbor Totoro

The basic idea was that the lamp would light up and a leaf would begin to sway back and forth when water was sprayed on the leaf. The forest spirit Chibi-Totoro was chosen as the lamp’s figure instead of Totoro because the lamp was going to be tabletop-sized, so we thought the smaller character would be more fitting.


Components

Here’s a list of our project’s components:

  • Capacitive rain sensor

  • 16 LED Neopixel ring

  • Servo motor

  • 3D printed encasing, leaf, and Chibi-Totoro

The encasing was printed out of Polylactic acid (PLA) on an FDM printer because it was cheap and quick. The leaf and Chibi-Totoro, being more intricate shapes, were printed out of resin on an SLA printer because a higher resolution could be achieved.

CAD and Assembly of Internal Components

Internal Components


Device Drivers

Custom drivers for the capacitive rain sensor, Neopixel ring, and servo motor were all written in C using only our microprocessor’s bare-bones functionality. To control the servo motor, we simply sent it a PWM signal with a 0.5 ms duty cycle to set the motor to 0 degrees and a PWM signal with a 2.5 ms duty cycle to set the motor to 180 degrees. We defined a linear function that interpolates the angles between 0 and 180 to their corresponding duty cycle.

static void rotate_motor(float angle) {
  nrfx_pwm_stop(&PWM_INST, true);
  float duty = angle/1800+0.025;
  sequence_data[0] = ((uint16_t) (duty*10000));

  nrfx_pwm_simple_playback(&PWM_INST, &pwm_sequence, 1, NRFX_PWM_FLAG_LOOP);
}

To gather input from the capacitive rain sensor, we connected the output to one of the microprocessor’s ADC pins and converted the digital count into voltage.

static float adc_sample_blocking(uint8_t channel)
{
  int16_t adc_counts = 0;
  ret_code_t error_code = nrfx_saadc_sample_convert(channel, &adc_counts);
  APP_ERROR_CHECK(error_code);

  return 12.0 / (1 << 12) * adc_counts;
}

Understanding the communication protocol for the Neopixel ring was the most complicated step in our project. The Neopixel ring has 16 LEDs, each with RGB channels. Each color channel receives an integer 0 to 255, 0 being off and 255 being the brightest setting. To set the colors on the Neopixel ring, a binary sequence must be translated in the following order: the value of LED 1’s green, LED 1’s red, LED 1’s blue, LED 2’s green, LED 2’s red, LED 2’s blue, and so on.

Neopixel Communication Protocol

0s and 1s are encoded using a 30% and a 70% duty cycle, respectively, of a PWM signal.

For our lamp, we only wanted one, solid color to be displayed when the lamp was turned on. Hence, the same binary sequence is sent for each of the 16 LEDs. The code below shows how the array of 384 values (16 LEDs x 3 colors x 8 bits to store an integer between 0 and 255) representing the sequence of duty cycles is constructed.

nrf_pwm_values_common_t np_sequence_data[384];
static uint16_t GRB[24];

// Sequence structure for configuring DMA
static nrf_pwm_sequence_t np_pwm_sequence = {
  .values.p_common = np_sequence_data,
  .length = 384,
};

static void transmit_sequence(void) {
  nrfx_pwm_stop(&PWM_INST, true);
  nrfx_pwm_simple_playback(&PWM_INST, &np_pwm_sequence, 1, NRFX_PWM_FLAG_STOP); 
  nrf_gpio_pin_clear(PWM_OUTPUT_PIN);
}

void setRGB(uint8_t R, uint8_t G, uint8_t B) {
    for (int i = 0; i < 8; i++) {
        if (extractNthBit(G, i)) {
            GRB[i] = COUNTERTOP*HIGH_DUTY;
        }
        else {
            GRB[i] = COUNTERTOP*LOW_DUTY;
        }
    }
    for (int i = 0; i < 8; i++) {
        if (extractNthBit(R, i)) {
            GRB[i+8] = COUNTERTOP*HIGH_DUTY;
        }
        else {
            GRB[i+8] = COUNTERTOP*LOW_DUTY;
        }
    }
    for (int i = 0; i < 8; i++) {
        if (extractNthBit(B, i)) {
            GRB[i+16] = COUNTERTOP*HIGH_DUTY;
        }
        else {
            GRB[i+16] = COUNTERTOP*LOW_DUTY;
        }
    }
    for (int i = 0; i < 16; i++) { // 16 LEDs
        for(int j = 0; j < 24; j++) {
            np_sequence_data[i*24+j] = GRB[j];
        }
    }
    transmit_sequence();
}
Previous
Previous

Modular Robotics Connection Scheme

Next
Next

Habitable Exoplanets