¿Y si la casa repartiera los dulces sola? En Chile la noche de Halloween se resuelve casi siempre igual: un bol en la entrada y alguien parado al lado vigilando que el primer grupo no se lo lleve entero. Este proyecto reemplaza a esa persona por una máquina que dosifica de a poco y, de paso, hace el show.
Es una cabeza estilo Frankenstein impresa en 3D con ojos LED animados que parpadean y miran alrededor, un parlante que suelta una frase distinta cada vez, y un botón gigante de 100 mm con un cerebro traslúcido iluminado adentro. Se presiona el cerebro y caen los dulces por la boca.
Al terminar este tutorial vas a entender tres cosas que sirven mucho más allá de Halloween: cómo un servo de rotación continua moviendo un tornillo de Arquímedes resuelve el problema de dosificar objetos irregulares, cómo se coordinan audio, luces y movimiento desde un solo microcontrolador sin que se atropellen, y qué hay que cambiar si lo armas con una Raspberry Pi Pico y componentes genéricos en vez de las placas Adafruit del diseño original.

Antes de comprar: la placa estrella del diseño original ya no se vende
Conviene decirlo al principio, porque cambia la lista de compras. El diseño original usa el Adafruit LED Glasses Front Panel, una matriz de 116 LEDs RGB con driver I2C integrado, y ese producto aparece hoy como No Longer Stocked en el propio catálogo de Adafruit. No es un problema de importación a Chile: simplemente ya no se fabrica.
Eso no mata el proyecto, pero sí obliga a decidir qué hacen los ojos. Tienes dos caminos honestos:
- Dos matrices 8x8 WS2812 montadas lado a lado detrás del acrílico difusor. Pierdes la forma de anteojos del PCB original, pero ganas resolución para las pupilas y el parpadeo, y son piezas que se consiguen en cualquier parte.
- Dos anillos NeoPixel de 12 o 16 LEDs, uno por ojo. Es la versión más simple: no dibujas una pupila que se mueve, pero sí puedes hacer que el iris cambie de color, pulse y "cierre" apagándose de a sectores.
El resto del proyecto. la mecánica, el audio, el botón, el servo. no depende de esa placa.
El concepto: dosificar es un problema mecánico, no electrónico
La parte que parece trivial es la que en realidad tiene toda la dificultad. Un dispensador de dulces no falla por el código: falla porque los caramelos se atascan.
Si pones una compuerta que se abre y se cierra, el resultado depende del azar: a veces cae uno, a veces caen quince, y tarde o temprano un dulce queda a medio camino y traba la compuerta. Por eso el diseño usa un tornillo de Arquímedes impreso en 3D, movido por un servo de rotación continua.
Un tornillo de Arquímedes empuja el material por desplazamiento: cada vuelta del hélice arrastra un volumen fijo hacia adelante, independiente de cuánto peso haya encima en la tolva. Esa es la propiedad clave. La cantidad dispensada deja de depender de la gravedad y del apilamiento, y pasa a depender solo del tiempo que el servo gira, que es algo que el código controla con precisión. En el código eso es literalmente una variable:
candy_portion— segundos que gira el tornillo. Con 1 segundo caen varios dulces; con 0.6 la porción es chica.servo_throttle— dirección y velocidad. Va en negativo porque el sentido de giro depende de cómo quedó montado el hélice; si tu tornillo empuja hacia adentro en vez de hacia afuera, le cambias el signo y listo. No hay que desarmar nada.
Hay un detalle de impresión que vale más de lo que parece: el tornillo se imprime a 0.12 mm de altura de capa, mucho más fino que el resto de las piezas. La razón es el stepping: las capas gruesas dejan escalones en la superficie helicoidal, y esos escalones son exactamente donde un caramelo se engancha. El resto de las piezas se imprimen a 0.2 mm y la cabeza incluso a 0.24 mm en modo borrador, porque ahí la terminación no cumple ninguna función mecánica.
Por qué se usa un servo de rotación continua (y no un motor paso a paso)
Un servo de rotación continua es, por dentro, un servomotor al que le sacaron el tope mecánico y el lazo de realimentación de posición. Deja de entender "ángulo" y pasa a entender "velocidad y sentido": el mismo pulso PWM de 50 Hz que en un servo normal significa ponte en 90°, acá significa quédate quieto, y desviarse hacia un lado u otro lo hace girar.
Para esta aplicación es la elección correcta por dos razones concretas:
- Trae la reductora incluida. Un tornillo dosificador necesita torque a baja velocidad, no revoluciones. Un motor DC pelado giraría rapidísimo y sin fuerza, y habría que agregarle una caja reductora.
- Se controla con un solo pin y sin driver. Un paso a paso necesita un A4988 o un DRV8825 y cuatro cables; el servo necesita señal, 5 V y tierra. Para un movimiento de "gira un segundo y para" no hay nada que ganar con el control de posición del paso a paso.
Lo que sí hay que saber: un servo continuo no se detiene solo en un punto exacto y tiene una zona muerta alrededor del punto neutro. Si el tuyo sigue girando lento cuando el código pide throttle = 0, no está fallado: hay que calibrar el potenciómetro de neutro que traen casi todos en la carcasa.
El circuito
La placa del diseño original es el Feather RP2040 Prop Maker, que es básicamente un RP2040 con tres cosas ya integradas: un amplificador I2S clase D para el parlante, una salida de servo con conector de 3 pines, y un pin de "external power" que corta la alimentación de todo lo externo por software.

Las conexiones son estas:
| Desde | Hacia | Pines |
|---|---|---|
| Placa | NeoPixel | DATA → pin de datos, GND → GND, 5V → 5V |
| Placa | Servo continuo | Ground → G, Voltage → V+, Signal → Sig |
| Placa | Parlante 8 Ω 2 W | Positivo → +, Negativo →, |
| Placa | Botón arcade | Pin 1 → Btn, Pin 2 → G |
| Placa | Matriz de ojos | STEMMA QT (I2C) |
Todo se alimenta desde una fuente USB de 5 V 1 A, y este es el punto donde más gente se queda pegada. Vale la pena hacer el cálculo antes de armar:
- El servo continuo en régimen consume poco, pero en el arranque y cuando el tornillo encuentra resistencia sube a medio ampere o más por un instante.
- La matriz de ojos con 116 LEDs encendidos a full sería más de 3 A ella sola. Por eso el código la limita con
glasses.global_current = 100, que baja la corriente del driver IS31FL3741 a un tercio de su máximo. - El amplificador I2S en un golpe de audio fuerte también pide su parte.
Los tres picos ocurren al mismo tiempo: presionas el botón y arrancan juntos el sonido, el servo y la animación. Si alimentas desde el puerto USB de un notebook, es muy probable que la placa se reinicie justo en ese momento. El síntoma es inconfundible: funciona perfecto mientras pruebas cada cosa por separado y se resetea apenas juntas todo. La solución no es el código, es un cargador de 5 V con al menos 1 A real, y un cable USB de datos. no uno de solo carga, que es el otro clásico de este proyecto porque impide incluso que aparezca la unidad CIRCUITPY.
El código
El firmware corre en CircuitPython. Antes de copiarlo hay que instalar CircuitPython en la placa: se descarga el .uf2 desde circuitpython.org, se entra al bootloader manteniendo apretado BOOT/BOOTSEL mientras se presiona y suelta reset, y se arrastra el archivo a la unidad RPI-RP2 que aparece. Cuando esa unidad desaparece y en su lugar aparece CIRCUITPY, ya está.
Todo lo que normalmente vas a querer tocar está agrupado arriba del archivo, así que no hay que leer el resto para personalizarlo:
# CONFIGURABLES ------------------------
eye_color = (0, 255, 0) # color of pupils
x_eye_color = (255, 0, 0) # color of X eyes
ring_open_color = (128, 0, 128) # color of LED rings when eyes open
ring_blink_color = (0, 0, 0) # color of LED ring "eyelid" when blinking
radius = 3.4 # Size of pupil (3X because of downsampling later)
candy_portion = 1 # servo run time in seconds (small 0.6)
servo_throttle = -1.0 # direction/speed of the screw while dispensing
volume = 1.0 # playback level for the sound effects, 0.0 to 1.0
idle_pixel_color = (255, 255, 255) # NeoPixel color when sitting idle
dispense_pixel_color = (255, 255, 255) # Color it pulses while dispensing
pulse_rate = 3.0 # NeoPixel pulses per second while dispensing
Dato útil: radius = 3.4 está en un espacio 3× más grande que la matriz real. La pupila se dibuja en alta resolución y después se reduce, y ese sobremuestreo es lo que hace que un círculo de 3 LEDs de diámetro se vea redondo y se mueva suave en vez de saltar de LED en LED. Es la misma idea que el antialiasing de una pantalla.
La inicialización del hardware muestra bien qué hace especial a esta placa:
# enable external power pin
# provides power to the external components
external_power = DigitalInOut(board.EXTERNAL_POWER)
external_power.direction = Direction.OUTPUT
external_power.value = True
# servo control
pwm = pwmio.PWMOut(board.EXTERNAL_SERVO, duty_cycle=2 ** 15, frequency=50)
cont_servo = servo.ContinuousServo(pwm)
cont_servo.throttle = 0
# external neopixels
num_pixels = 1
pixels = neopixel.NeoPixel(board.EXTERNAL_NEOPIXELS, num_pixels)
pixels.brightness = 1
pixels.fill(idle_pixel_color)
# external button
keys = keypad.Keys((board.EXTERNAL_BUTTON,), value_when_pressed=False, pull=True)
Fíjate en frequency=50: son los 50 Hz estándar de servo, o sea un pulso cada 20 ms. Y en keypad.Keys con pull=True: el botón no necesita resistencia externa porque se usa el pull up interno del microcontrolador, y por eso se conecta simplemente entre el pin y tierra.
El audio elige un .wav al azar en cada presión, en vez de repetir siempre el mismo:
# Collect every .wav in /sounds so one can be picked at random per press
wavs = []
for filename in os.listdir("/sounds"):
if filename.lower().endswith(".wav") and not filename.startswith("."):
wavs.append("/sounds/" + filename)
audio = audiobusio.I2SOut(board.I2S_BIT_CLOCK, board.I2S_WORD_SELECT, board.I2S_DATA)
mixer = audiomixer.Mixer(voice_count=1, sample_rate=22050, channel_count=1,
bits_per_sample=16, samples_signed=True,
buffer_size=8192)
audio.play(mixer)
mixer.voice[0].level = 0.0
current_file = None
def play_random_sound():
"""Pick a random wav from /sounds and start it playing."""
global current_file # pylint: disable=global-statement
if not wavs:
return
mixer.voice[0].stop()
if current_file is not None:
current_file.close()
current_file = open(random.choice(wavs), "rb")
mixer.voice[0].level = volume
mixer.voice[0].play(audiocore.WaveFile(current_file), loop=False)
Acá hay dos decisiones de diseño que conviene copiar en cualquier proyecto con audio:
- Se usa un mixer en vez de reproducir el
WaveFiledirecto. El mixer mantiene el bus I2S siempre abierto y corriendo, así que no hay ese chasquido que se escucha cuando el amplificador arranca y se apaga en cada sonido. El volumen se controla bajandolevela 0.0 en silencio, no cortando el audio. - Los
.wavdeben ser 22050 Hz, mono, 16 bits con signo, tal como declara el mixer. Si preparas tus propios sonidos con otra frecuencia o en estéreo, se van a escuchar acelerados, lentos o directamente como ruido. Es el error número uno al personalizar las frases.
Montaje
El armado sigue este orden, y conviene respetarlo porque algunas piezas quedan inaccesibles después:
- La placa se atornilla a la plancha inferior.
- El servo se fija a un soporte que va pegado al alimentador.
- El alimentador se monta a la tapa inferior con tornillos.
- La tapa del alimentador y la tolva calzan a presión sobre la base.
- El parlante entra en su soporte, atornillado al costado del alimentador.
- La matriz de ojos se fija a la cabeza.
- El NeoPixel va dentro de la tapa del cerebro, que se mete adentro del botón arcade.
- Los pernos falsos se pegan a los costados de la pieza del pelo.
- El botón arcade se monta en la pieza del pelo.
- La pieza del pelo se desliza sobre la cabeza.

La ferretería que necesitas es toda métrica estándar: 4 golillas M3, 2 tornillos M2×6, 2 tornillos M2.5×6, 10 tornillos M3×6, 2 tornillos M3×8, 2 tornillos M3×10 y 1 tornillo M3×12. Todos de acero.
Sobre la impresión: el volumen mínimo de impresión es de 170 × 136 × 212 mm, así que la pieza más alta no entra en las impresoras más chicas de escritorio. El perfil usado es PLA a 220 °C de extrusor, 0.2 mm de capa, 8 % de relleno giroide, 200 mm/s, y soportes tipo árbol con ángulo de umbral de 30°.
El difusor de los ojos es una lámina de acrílico negro para LED cortada a láser. En el diseño original se pide con un archivo DXF a un servicio de corte; acá se resuelve igual en cualquier taller de corte láser, que abundan.
Prueba antes de cerrar
Dos pruebas separadas, antes de atornillar la cabeza:
Prueba del tornillo, sin dulces. Presiona el botón con la tolva vacía y mira hacia dónde gira el hélice. Si empuja hacia adentro, cambia el signo de servo_throttle. Si el servo sigue girando lento después de terminar, hay que calibrar su punto neutro.
Prueba de dosificación, con dulces. Llena la tolva y presiona diez veces seguidas contando cuántos caen cada vez. Ajusta candy_portion hasta que la cantidad sea la que quieres. Hazlo con los dulces reales que vas a repartir: un caramelo cilíndrico y un chocolate cuadrado se comportan completamente distinto en el mismo tornillo.
Problemas típicos y cómo depurarlos
Casi todas las fallas de este proyecto son una de estas seis, y ninguna requiere osciloscopio:
| Síntoma | Causa más probable | Qué hacer |
|---|---|---|
No aparece la unidad CIRCUITPY al conectar |
Cable USB de solo carga | Cambia el cable por uno de datos. Es la falla número uno y no da ninguna señal de error |
| La placa se reinicia justo al presionar el botón | Caída de tensión: servo, audio y LEDs arrancan juntos | Aliméntala desde un cargador de 5 V 1 A o más, no desde el USB del notebook |
| El servo gira lento aunque nadie lo pidió | Punto neutro descalibrado | Ajusta el potenciómetro de neutro del servo con el código en throttle = 0 |
| El tornillo empuja los dulces hacia adentro | Sentido de giro invertido | Cambia el signo de servo_throttle |
| El sonido se escucha acelerado, lento o como ruido | El .wav no coincide con el formato del mixer |
Reexporta a 22050 Hz, mono, 16 bits PCM con signo |
| Los ojos no encienden y el resto sí | I2C sin comunicación | Revisa el cable STEMMA QT y que la matriz esté alimentada; el I2C falla silencioso, no avisa |
Un truco de depuración que ahorra tiempo: conéctate a la consola serial de CircuitPython mientras pruebas. Si la placa se está reiniciando por caída de tensión, vas a ver el banner de arranque aparecer de nuevo en el momento exacto de la presión, y eso descarta de una que el problema sea del código.
Preparar tus propios sonidos
Las frases son lo que más personaliza el proyecto, y es donde más gente se frustra sin necesidad. El mixer está declarado a 22050 Hz, mono, 16 bits con signo, y los archivos tienen que coincidir exactamente con eso. Con ffmpeg se convierte cualquier audio en una línea:
Exporta a WAV PCM de 16 bits, un canal, 22050 Hz de frecuencia de muestreo, y deja los archivos en la carpeta /sounds de la unidad CIRCUITPY. El código toma todos los .wav que encuentre ahí y elige uno al azar en cada presión, así que agregar frases nuevas es literalmente copiar archivos. no hay que tocar una sola línea.
Un detalle de capacidad: la memoria flash de la placa es chica y el código y las librerías también viven ahí. A 22050 Hz mono 16 bits, cada segundo de audio ocupa unos 44 kB, o sea que diez frases de tres segundos son más o menos 1,3 MB. Si te quedas sin espacio, acorta las frases antes de bajar la calidad: reducir la frecuencia de muestreo obligaría a cambiar la declaración del mixer y a reexportar todo de nuevo.
Variantes y mejoras
Contador de dulces repartidos. Como cada presión dispensa una porción conocida, basta con llevar la cuenta de las presiones en una variable y guardarla en un archivo de la unidad CIRCUITPY. Al final de la noche sabes exactamente cuántas visitas tuviste, y como el conteo queda en el archivo, sobrevive a que lo desenchufes.
Aviso de tolva vacía. Un sensor infrarrojo TCRT5000 apuntando al fondo de la tolva detecta cuando ya no hay dulces apilados sobre él. Con eso puedes cambiar el color del cerebro a rojo y hacer que el parlante diga otra cosa en vez de mover el tornillo en vacío, que es lo que más desgasta el servo.
Activación por presencia en vez de botón. Un sensor PIR en la base hace que los ojos se despierten y sigan el movimiento cuando alguien se acerca, antes de que toque el botón. El proyecto ya tiene la animación de ojos mirando alrededor; el PIR solo decide cuándo empieza.
Versión Raspberry Pi Pico sin placas Adafruit. El proyecto completo se puede rehacer sobre una Pico W, que es bastante más barata y fácil de conseguir. Los cambios son acotados y todos del mismo tipo: las constantes board.EXTERNAL_SERVO, board.EXTERNAL_NEOPIXELS, board.EXTERNAL_BUTTON y board.EXTERNAL_POWER no existen en la Pico, así que hay que reemplazarlas por pines GPIO concretos. por ejemplo board.GP15 para el servo, board.GP16 para el NeoPixel y board.GP14 para el botón. . Los pines board.I2S_BIT_CLOCK, board.I2S_WORD_SELECT y board.I2S_DATA también pasan a ser tres GPIO consecutivos cualquiera, porque el RP2040 genera I2S por PIO y no tiene un periférico fijo. El resto del código, incluida toda la lógica de ojos y de audio, queda igual.
Los dos agregados de hardware en esa ruta son un amplificador I2S MAX98357A (la Pico no lleva amplificador integrado, y es exactamente el mismo chip que trae el Prop Maker) y un transistor MOSFET o un módulo relé si quieres conservar la función de cortar la alimentación externa que hacía EXTERNAL_POWER. Sin eso, el servo y los LEDs quedan energizados siempre; no rompe nada, solo consume de más.
Personalización para Chile
Este proyecto se puede armar completo con componentes que están en el catálogo de MechatronicStore, salvo la matriz de ojos del diseño original, que como ya se explicó está descontinuada en su fabricante.
Las equivalencias son directas:
- Raspberry Pi Pico / Pico W en lugar del Feather RP2040 Prop Maker. Es el mismo microcontrolador RP2040 y corre el mismo CircuitPython; lo que se pierde son los periféricos integrados, que se reponen con los dos módulos de abajo.
- Amplificador I2S MAX98357A para reemplazar el amplificador integrado del Prop Maker.
- Servo de rotación continua (tipo SG90 de rotación continua o FS90R). Es la pieza donde no conviene ahorrar: el torque es lo que evita atascos.
- Parlante 8 Ω 2 W con cables. La impedancia importa: un parlante de 4 Ω exige el doble de corriente al amplificador.
- Módulo NeoPixel WS2812 individual para el cerebro iluminado.
- Matriz LED WS2812 8x8 (dos unidades) o anillos NeoPixel para los ojos, según el camino que elijas.
- Botón arcade grande con LED, que es lo que aloja el cerebro impreso.
- Fuente o cargador USB de 5 V con 1 A o más, más un cable USB de datos.
- Filamento PLA en los cuatro colores del diseño: verde, negro, rosado y plateado metálico.
Dos ítems no salen de una tienda de electrónica y conviene tenerlos resueltos aparte: la ferretería métrica M2/M2.5/M3 y el corte láser del difusor de acrílico negro, que se encarga a cualquier taller con el archivo DXF.
Como referencia de presupuesto, la parte electrónica completa en la ruta Pico ronda los $55.000 CLP, más el filamento. Los precios y el stock exactos hay que confirmarlos en el catálogo al momento de comprar.
Recursos
- Tutorial original: Franky Candy Dispenser. por Ruiz Brothers y Liz Clark, Adafruit Learning System
- Guía completa en PDF (incluye todas las páginas del original): franky candy dispenser.pdf
- Archivos 3MF y fuentes CAD: disponibles para descarga en la página CAD Files del tutorial original
- CircuitPython para tu placa: circuitpython.org
- Librería Adafruit para Fritzing (para rehacer o modificar el diagrama): Adafruit Fritzing Parts en GitHub
Versión chilena inspirada en el proyecto de Adafruit, re angulada para armarse con Raspberry Pi Pico y componentes genéricos disponibles en stock local.




