Skip to content

About

Showing off my project to create an animatronic hairclip for my daughter

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Animatronic Triceratops Hair Clip

A wearable, battery-powered triceratops that clips into hair. Press the red eye and it lights up, opens and closes its jaw, swings its tail, and roars.

Designed, printed, wired, and programmed from scratch - mechanism, electronics, firmware, and enclosure - around one non-negotiable requirement: it has to be comfortable on a nine-year-old's head.

The finished clip: eye lights, jaw and tail move, speaker roars

▶ Watch it with sound - nine seconds, and the roar is the payoff.


The primary constraint

The most important constraint here is that the final product needs to be small and light enough for my daughter to wear comfortably for a day. This informs a set of derived requirements.

"comfortable on a 9-year-old's head"
  │
  ├── small motor ──────┬── low torque ────── gear reduction required
  │                     └── low speed ─────── ...that has to fit inside a shape
  │                                            which is already a dinosaur
  │
  ├── small battery ────┬── limited capacity ── firmware must sleep between uses
  │                     └── limited peak current ── motor can't be driven from
  │                                                 a microcontroller pin
  │
  └── thin, flat body ──┬── components laid out in 2D, avoid stacking to minimize depth
                        └── drove the mechanical design (see: worm gear)

Most of what follows is a consequence of that tree rather than a free choice.


What it does

Trigger Response
Press the red eye button Eye LED pulses - four sine-shaped pulses over ~5 seconds
Motor drives the jaw open/closed and the tail up/down
Roar plays through a small speaker
Animation completes Processor and MP3 module return to sleep

One button, one animation, then back to sleep. The interaction is deliberately trivial; the work is in making that fit into a wearable and survive on a battery.


Mechanism

Mechanism running on an early print

Mechanism running on a prototype print, before finishing. ▶ Watch it move

The worm gear is essential to the design

The body has to be thin. My first instinct was to go looking for a thin, flat motor - and I spent real time searching for one that didn't exist...or at least I couldn't find one at the time.

The breakthrough was realizing I'd framed it wrong. I didn't need a flat motor; I needed the motor's axis to lie in the plane of the body instead of poking out of it. A worm gear on the motor shaft, meshing with a spur gear, turns the rotation 90° and delivers a large reduction in a single stage. The motor lies flat along the body, the gear sits in the plane, and the whole drivetrain collapses into a thin section.

That reframe - rotate the axis rather than shrink the motor - is what made the rest of the project possible.

The kinematic chain

motor ──▶ worm on shaft ──▶ spur gear ──▶ crank post on gear face
                                              │
                                              ▼
                                     connecting bar (reciprocates)
                                              │
                                    ┌─────────┴─────────┐
                                    ▼                   ▼
                              jaw lever            tail lever

A post on the face of the spur gear acts as a crank pin. As the gear rotates, the post sweeps a circle and drives a bar back and forth. That single reciprocating bar pushes and pulls levers on both the jaw and the tail - one motor, one bar, two moving parts, with no clutch, differential, or second actuator.

Sizing it

I wrote a SymPy script encoding the geometric constraints - link lengths, pivot positions, and the required angular travel at the jaw and tail - and solved for the dimensions that would produce the motion I wanted inside the space available. Doing it symbolically beat guessing in CAD and re-printing, given how long the physical iteration loop is.

(Script: ./other/calc_rod.py)

Motor and reduction

  • Motor: 6 mm mini DC gear motor, 20 mm type, MDM609-699-0675, standard shaft
  • First attempt was wrong. My initial motor was geared down far too much - the animation crawled. I replaced it with a less-reduced version. The target was simple and behavioral rather than numeric: the tail should move up and down a few times in about five seconds.
  • I never calculated the overall ratio. I tuned the worm-and-gear pairing empirically until the motion looked right. For a one-off toy with a qualitative target, iterating to the answer was faster than deriving it - it worked out pretty fast without spending the time devolving into a thorough analysis.
  • Pivots: pivots are cylindrical posts off the back plate acting as shafts at the jaw, tail, and linkage joints.
  • Noise: PLA-on-PLA gearing squeaked noticeably. Lubricant helped a lot; it's still audible as the motor is pretty loud by itself.

Fusion 360 assembly, internal packaging visible

Fusion 360 assembly. Almost all of the internal volume is spoken for.


Electronics

Hand-wired perfboard and mechanism installed in the body

Block Part Why
Processor DFRobot Beetle CM-32U4 (ATmega32U4) One of the smallest boards I could find that still had the peripherals I needed - hardware serial, PWM, an interrupt-capable pin, and onboard LiPo charging
Audio DFPlayer Pro Self-contained MP3 playback with onboard storage and amplifier, driven over UART. No SD card to house.
Speaker 6 Ω, 700 mW, top-port, 92 dB (Digi-Key 668-1610-ND) Top-port so it fires out of the body rather than into it
Motor switch IRLZ44N logic-level N-channel MOSFET, low-side, 1 kΩ gate resistor See below - I got the package selection wrong
Battery 3.7 V 400 mAh LiPo Traded capacity against volume and against peak current delivery
Eye LED Illuminated button, 330 Ω series resistor, PWM-driven Brightness is animated, so it needs a PWM-capable pin

The motor switch

The motor is on a low-side N-channel MOSFET switch: motor between the battery rail and the MOSFET drain, source to ground, gate driven from a PWM pin through a 1 kΩ resistor.

The point is that a small DC motor's stall and inrush current is far beyond what an MCU pin can source, and pulling it through the microcontroller's rail would brown out the processor mid-animation. The MOSFET puts the motor's current path directly across the battery and leaves the MCU controlling only a gate. The IRLZ44N is logic-level, so it turns fully on from the Beetle's logic without a gate driver.

Because the gate sits on a PWM pin, motor speed is adjustable in firmware - which is how the animation speed got tuned without touching hardware. It runs at a duty cycle of 150/255, roughly 59%.

On the size of it: the IRLZ44N is a TO-220 - a large package for a project whose whole design driver is thinness. Writing this up, I sanity-checked that choice and found I'd been wrong: I'd only looked at TO-220-style parts and tiny surface-mount ones I assumed I couldn't hand-solder, and missed that something like a SOT-23 sits comfortably in between and could have worked. In practice it cost me little - it fit into one of the legs, otherwise dead space, and set the minimum width there only slightly beyond what other components already demanded. But it was a real gap in what I knew was available.

Two components I deliberately left out

The design as originally sketched included a flyback diode across the motor and a capacitor across the speaker leads. I built it without either, tested it, and it worked fine, so they never went in. I figured that's reasonable for a one-off toy - obviously not right for a professional product - but that wasn't my goal.

How the electronics got designed

I'm a software engineer, not an EE. I have the electronics basics from my physics degree, but that was a long time ago and didn't include circuit design anyhow. So for this project I worked the electronics out in an extended back-and-forth with ChatGPT - I described the constraints and what I wanted, it laid out options and explained the relevant techniques, and we iterated to the design above. It wasn't "give me a circuit." It was closer to working through the problem with a knowledgeable colleague who could sketch alternatives faster than I could look them up.

The end result wasn't a black box to me - I understood why every part was there, which is how I was in a position to decide the flyback diode and the speaker capacitor weren't earning their place. And the alternative was real: teaching myself enough electronics from scratch to derive this would have been more overhead than the whole rest of the project combined, on something whose actual goals were learn some new things, end up with a working object, and have fun. Spending that budget on the mechanism instead was the right call.


Firmware

Arduino toolchain, C++. About ninety lines. Source: ./firmware/triceratops.ino

Pin Function
D2 (SDA, INT1) Eye button, INPUT_PULLUP, falling-edge interrupt - the wake source
D9 Eye LED, PWM
D11 MOSFET gate, PWM at 150/255
Serial1 DFPlayer Pro, hardware UART at 115200

Sleep-first design

The device spends essentially all of its life in SLEEP_MODE_PWR_DOWN - the deepest sleep the ATmega32U4 offers. The button pulls D2 low, the interrupt fires, and the processor wakes, runs the animation, and puts itself and the DFPlayer's amplifier back down.

Without this, the battery drain would have been significantly higher. I wanted it to last a day of typical use which I assumed would be mostly standby, with perhaps tens of presses by her friends.

The main loop reads unusually: it starts by sleeping.

void loop()
{
  sleepBeetle();                      // blocks here until the button interrupt fires

  DF1201S.enableAMP();
  analogWrite(MOTOR_PIN, MOTOR_PWM);
  DF1201S.playFileNum(1);

  auto const steps = 1000;
  for (int i = 0; i < steps; i++) {
    auto t = 4 * i / (steps - 1.0);
    auto brightness = static_cast<int>(255.0 * 0.5 * (1.0 + sin(2*M_PI*t - M_PI/2)));
    analogWrite(BUTTON_LED_PIN, brightness);
    delay(5);
  }

  DF1201S.disableAMP();
  analogWrite(MOTOR_PIN, 0);
}

The sleep itself uses the standard race-free sequence - disable interrupts, arm sleep, re-enable interrupts, sleep - so a button press arriving in the window between arming and sleeping can't be lost:

attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), wakeISR, FALLING);
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
cli();
sleep_enable();
sei();
sleep_cpu();
// --- wakes here ---
sleep_disable();
detachInterrupt(digitalPinToInterrupt(BUTTON_PIN));

The ten-second delay in setup() is not decoration. Power-down sleep takes the USB peripheral with it, so a board that goes straight to sleep on boot can't be reprogrammed - you're reduced to catching the bootloader window with a reset. Ten seconds of doing nothing at startup buys a reliable upload window. After racing to plug/unplug/upload a couple of times, this was a welcome change.

Coordinating three outputs

This design didn't require the complexity of RTOS or state machines. The animation is a blocking sequence, because a single-purpose device with exactly one behavior has nothing else to do while it runs. A millis()-driven state machine would have been more code for no benefit.

The three outputs coordinate by construction rather than by scheduling:

  • Motor - PWM set once at the start, cleared at the end. Runs for the whole animation.
  • Audio - fired once and left to the DFPlayer, which plays asynchronously.
  • LED - the 1000-step loop at 5 ms is both the LED animation and the master clock for the whole sequence. 1000 × 5 ms ≈ 5 seconds, which sets the animation length.

The brightness curve is a sine with t running 0→4, so the eye pulses four times across those five seconds. The −π/2 phase offset starts the sine at its minimum, so the LED ramps up from off rather than snapping on.

The amplifier is explicitly disabled in setup() and again at the end of every animation to save power. The DFPlayer's own status LED is turned off for the same reason.

Power

Battery sizing was worked out against the components' datasheet figures rather than measured. I never put a meter on it - so I'm not going to quote a standby current or a battery life. It comfortably lasts a day of typical usage, which was the actual requirement.


Debugging, and what hardware taught me

The thing that struck me most: the make-test-fix loop in hardware is enormously longer than in software. A print is hours. A part is days. A wrong tolerance means reprinting a component and reassembling around it.

Software has slow loops too - long-running simulations, full rebuilds - and I've spent a lot of my career on the wrong side of those. But there the slow path is usually the exception, and you can often shrink it: a smaller test case, a targeted rebuild, a mock. In hardware there's no smaller test case. The part takes as long as it takes.

What changes is where the thinking happens. When iteration is cheap you can often find the answer by trying things; when it isn't, you have to reason it out first. That's why the linkage got solved in SymPy rather than by printing three versions of it.

Things that actually went wrong:

  • The first motor was far too slow. Over-geared. Caught only after it was installed and running. Fixed by ordering a less-reduced version.
  • The DFPlayer Pro intermittently ignored play commands. This cost the most time by a distance - a lot of forum archaeology before I found a sequence that worked reliably. I no longer remember the specific fix, which is its own lesson about writing things down at the time.
  • Reversed pinouts. Some of the pin diagrams I was working from had the pin order mirrored relative to the physical part. Diagnosing "this is wired exactly as documented and does nothing" takes a while.
  • Tolerances. Repeated fit iteration on printed features, most memorably the cutout for the USB charge port.
  • Squeaky gears. PLA on PLA. Lubricant.

printf debugging over serial was available and did most of the work. No debugger, no logic analyzer.


Design for print and assembly

Back of the finished clip: printed mounting rail and commercial barrette

  • Printer: Bambu Lab X1C, Bambu Studio, PLA, printed throughout with a 0.2 mm nozzle rather than the stock 0.4 mm. The finer nozzle was specifically for the gear teeth and the other small features - half the extrusion width means meaningfully better dimensional accuracy on details that have to mesh. The cost is print time, which on a part this size was worth paying. PLA was adequate for a light-duty wearable - stiff in thin sections and accurate at small feature sizes - but see the material note below.
  • Tapers instead of overhangs. I designed geometry to avoid support material wherever possible, because supports leave marks on visible surfaces. Tapered features print cleanly unsupported and come off the plate finished.
  • Learning the minimum feature size. A significant part of the iteration was calibrating intuition for how small a printed feature can be and still come out both strong and dimensionally accurate. That's knowledge you can only get by printing things and breaking them.
  • Retention. Every component has a printed feature holding it - pockets, straps, and screw bosses. A wearable gets shaken, dropped, and slept on; nothing can be left resting in place.
  • PLA was the wrong call for the fasteners. The design uses self-tapping screws into printed bosses, and PLA is brittle enough that the bosses want to crack rather than deform as the thread cuts. It held, but with less margin than I'd like on a part that gets opened repeatedly during iteration and then handled by a nine-year-old. PETG is tougher and more forgiving in exactly this failure mode, and it's what I'd print the next one in. The tradeoff is that PETG is stringier and slightly less crisp on fine features - which matters for the gear teeth, so it might end up being a two-material build: PETG for the structural shell and screw bosses, PLA for the drivetrain.
  • Serviceability. The body opens with screws rather than glue, so the mechanism stays reachable after assembly.
  • Attachment. A commercial metal barrette is screwed to a printed rail on the back, keeping the clip mechanism a proven part rather than something printed and fragile.
  • Iteration. Many prints. Outline and silhouette, fit, wall thickness, feature size, and repeatedly rearranging component positions to make the packaging close.
  • Finish. The silhouette was modelled from a triceratops reference image. The final surface detail is that same artwork printed on sticker paper and applied to the front - a much better result than anything I could have painted, at a fraction of the effort.

Timeline: a few months of evenings and weekends - shape and size, component selection, layout, mechanism design, electronics design, build, firmware, debugging.


What I'd do differently

  • Custom PCB with surface-mount parts. I've since experimented with PCB design on another project and found it far more accessible than expected. It would cut thickness, weight, and assembly complexity dramatically. Realistically I'd lean on Adafruit's open reference designs - full custom board design is beyond me today, and iterating on custom boards is expensive enough and slow enough that mistakes hurt.
  • Drop the MP3 module. For a single short sound, the DFPlayer Pro is overkill. I later learned I could likely output PCM directly from a Beetle pin into a small amplifier, removing a whole module and its UART protocol. Not yet investigated properly.
  • Put the flyback diode and speaker capacitor back in. Left out because it worked without them. Fine here, wrong for anything with more than one unit. See above.
  • Wire retention. Several of the longer wires simply float in the cavity. They should have printed channels and clips like everything else does.
  • PETG for the structural parts. PLA is brittle around self-tapping screws. See above.
  • Quieter drivetrain. PLA gears plus this motor are still louder than I'd like. Different materials, a different tooth profile, or better damping at the mounts.
  • Measure the power budget rather than reasoning about it from datasheets.

Not a change, but worth noting: the mechanism, electronics, and firmware are entirely shape-agnostic. The same drivetrain and board would drop into any animal with a jaw and a tail - the triceratops is just the shell around it.


Repository contents

firmware/triceratops.ino    Arduino firmware
other/calc_rod.py           SymPy constraint script used to size the linkage
cad/triceratops_4.f3d       Fusion 360 source model
media/                      Photos, GIFs, and video used in this README

A note on the firmware: this is hobby code, written to work rather than to ship. The effort here went into the mechanism, the packaging, and the electronics; the software is about ninety lines and is structured accordingly.

About

Showing off my project to create an animatronic hairclip for my daughter

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages