Un poi es una pelota con una cuerda que se hace girar alrededor del cuerpo; es un clásico de los malabaristas y de las presentaciones con fuego. Este proyecto cambia la pelota por un tubo con 36 LED y le agrega un truco de física: si los LED cambian de color lo bastante rápido mientras giran, tu ojo junta todas esas columnas de luz en una sola imagen que parece flotar en el aire. Es lo que se llama persistencia de la visión, o POV por su sigla en inglés.
En esta guía vas a entender cómo funciona un display POV, cómo se arma el poi con una Feather RP2040 con radio RFM69 y una tira DotStar, cómo se preparan las imágenes y cómo agregar el control inalámbrico que manda las mismas imágenes a varios poi a la vez.

Por qué funciona: la física detrás del POV
La retina mantiene cada imagen por una fracción de segundo después de que la luz desaparece. Si un punto luminoso se mueve rápido, no ves un punto que se desplaza sino una línea. Un display POV aprovecha eso: la tira de LED es una sola columna de píxeles, y la imagen se dibuja columna por columna mientras la tira barre el aire.
En el código del poi, cada imagen es un BMP de exactamente 36 píxeles de alto (uno por LED) y tan ancho como quieras. El programa muestra una columna, espera un rato, muestra la siguiente y vuelve al principio cuando se acaba la imagen. Ese rato lo fija la variable speed, que son columnas por segundo:
scanline_period_ns = int(
1_000_000_000
/ speed
)
Con el valor por defecto de 500 columnas por segundo, cada columna dura 2 milisegundos. El rango permitido va de 100 a 1500. Si giras rápido y la imagen sale estirada, sube la velocidad; si sale comprimida, bájala.
Hardware: por qué DotStar y no NeoPixel
La elección de la tira no es un detalle. Las tiras más comunes (WS2812B, conocidas como NeoPixel) usan un solo cable de datos con un tiempo fijo por bit y regulan el brillo con un PWM interno de unos pocos cientos de hercios. En un objeto que gira, ese PWM se nota: los colores tenues salen como una línea punteada en vez de un trazo continuo.
Las DotStar (APA102 o SK9822) tienen dos cables, reloj y datos, y aceptan el reloj tan rápido como el microcontrolador lo pueda mandar. El poi las maneja a 8 MHz y su PWM interno es mucho más rápido, así que las columnas se ven limpias aunque el poi gire a toda velocidad. Así se inicializa la tira en el código:
pixels = adafruit_dotstar.DotStar(
board.SDA,
board.SCL,
NUM_LEDS,
brightness=DEFAULT_BRIGHTNESS / 100.0,
auto_write=False,
pixel_order=adafruit_dotstar.BGR,
baudrate=DOTSTAR_BAUDRATE,
)
pixels.fill(0)
pixels.show()
Fíjate en brightness=DEFAULT_BRIGHTNESS / 100.0: el poi arranca al 25 % del brillo. No es solo para no encandilar. A pleno brillo, 36 LED blancos piden más de dos amperes, mucho más de lo que entrega el elevador de 5 V del proyecto y de lo que aguanta una batería pequeña.
El circuito del poi
El poi lleva muy pocas piezas:
- Feather RP2040 RFM69: el microcontrolador con la radio de 915 MHz integrada.
- Tira DotStar de 36 píxeles: el reloj va al pin SDA (GP2) y los datos al SCL (GP3). Se usan esos pines por su ubicación en la placa, no porque haya I2C de por medio.
- Elevador de 5 V: la batería LiPo da entre 3,2 y 4,2 V y la tira necesita 5 V.
- Interruptor deslizante: corta la alimentación del poi cuando no lo usas.
- Dos resistencias de 100 kΩ: forman un divisor de voltaje entre la batería y el pin A0 para medir la carga.
- Batería LiPo de 3,7 V: va dentro del tubo junto con todo lo demás.

El divisor de voltaje merece una explicación, porque es un truco que sirve en cualquier proyecto a batería. El ADC del RP2040 solo lee hasta 3,3 V y una LiPo cargada llega a 4,2 V. Con dos resistencias iguales en serie, el punto medio queda exactamente en la mitad del voltaje de la batería: 2,1 V con la batería llena, que el ADC lee sin problema. El código promedia 32 lecturas para eliminar el ruido y multiplica por dos:
def read_battery_voltage():
"""Read battery voltage through a 100k / 100k divider."""
total = 0
for _ in range(32):
total += battery_adc.value
time.sleep(
0.002
)
average = (
total / 32
)
adc_voltage = (
average
* 3.3
/ 65535
)
# 100k / 100k divider halves battery voltage.
return (
adc_voltage * 2.0
)
Al encender, el poi usa ese valor para mostrar la carga en los propios LED: verde sobre 3,75 V, amarillo sobre 3,45 V y rojo por debajo. Se usan resistencias de 100 kΩ, y no de 1 kΩ, para que el divisor gaste lo mínimo: a 4,2 V circulan unos 21 microamperes.
Software: de la imagen a la tira
El poi corre CircuitPython. Las imágenes van en la unidad CIRCUITPY dentro de /img/<carpeta>/, como BMP indexados de 36 píxeles de alto. Cada carpeta es un set (por ejemplo, uno para Halloween y otro para un show), y dentro de cada set las imágenes se ordenan alfabéticamente.
El corazón del programa es la función que pinta una columna. Toma los bytes de color de la columna actual, arma cada color como un número de 24 bits y manda los 36 LED de una sola vez con pixels.show():
def draw_scanline(
line_number
):
"""Draw one image column to the 36 DotStars."""
base = (
line_number
* NUM_LEDS
* 3
)
for led in range(
NUM_LEDS
):
offset = (
base
+ led * 3
)
color = (
(
image_data[
offset
]
<< 16
)
|
(
image_data[
offset + 1
]
<< 8
)
|
image_data[
offset + 2
]
)
pixels[
led
] = color
pixels.show()
La variable ROTATE_IMAGE_180 del código gira cada imagen al cargarla, porque en el poi la tira cuelga con el primer LED abajo. Así puedes dibujar los BMP al derecho en cualquier editor y el código se encarga del resto.
Hay un detalle de diseño que vale la pena copiar en tus propios proyectos: si algo retrasa el ciclo principal (leer la radio, por ejemplo), el código no intenta ponerse al día mostrando de golpe las columnas atrasadas, porque eso deformaría la imagen. Salta directamente a la columna que corresponde a ese momento.
El control inalámbrico (segunda etapa)
El poi funciona solo: al encender muestra la batería, escucha la radio por tres segundos y, si no aparece un control, entra en modo autónomo y recorre las imágenes de la carpeta por defecto. Si quieres manejar varios poi a la vez durante un show, el original agrega un control con otra Feather RP2040 RFM69, una pantalla táctil TFT FeatherWing de 3,5 pulgadas y un gamepad I2C pequeño.

Para la antena basta un trozo de cable rígido soldado al pin ANT de la Feather. Para 915 MHz, un cuarto de onda mide unos 7,8 cm (el original usa 3 pulgadas, prácticamente lo mismo).
El control manda un paquete de texto con todo el estado de una vez: S,carpeta,imagen,corriendo,brillo,velocidad,auto,intervalo. Por ejemplo, S,halloween,2,1,25,500,1,8 significa: carpeta halloween, imagen 2, reproduciendo, brillo 25 %, 500 columnas por segundo, cambio automático cada 8 segundos. Como cada paquete trae el estado completo, si un poi pierde un mensaje por estar lejos, se pone al día con el siguiente. Para que esto funcione, el control y los poi deben tener las mismas carpetas con las mismas imágenes y en el mismo orden.
Pruebas antes de girarlo
- Prueba en la mesa: con el poi quieto y la velocidad al mínimo (100 columnas por segundo), mueve el tubo a mano frente a una cámara con exposición larga. Vas a ver la imagen completa en la foto antes de arriesgarte a girarlo.
- Imagen invertida: si sale de cabeza, cambia
ROTATE_IMAGE_180aFalse. - La tira parpadea o muestra colores raros: revisa que reloj y datos no estén cruzados (reloj en SDA, datos en SCL) y que la tierra de la tira esté unida a la de la Feather.
- El poi se reinicia al subir el brillo: el elevador no da abasto. Baja el brillo o usa un elevador de más corriente.
Variantes y mejoras
- Versión con ESP32 y ESP NOW: si prefieres no usar la radio RFM69, un ESP32-C3 Super Mini por poi y otro en el control pueden hablar por ESP NOW, el protocolo directo entre ESP32 que no necesita router. El formato de paquete del original se puede reutilizar tal cual; tendrías que portar el código a MicroPython o Arduino.
- Velocidad automática con un acelerómetro: un MPU6050 o un sensor parecido dentro del tubo puede medir qué tan rápido gira el poi y ajustar
speeden tiempo real, para que la imagen no se estire ni se comprima según cuánto fuerce el brazo quien lo maneja. - Rueda de bicicleta POV: el mismo código, con la tira montada a lo largo de un rayo, dibuja imágenes en la rueda mientras pedaleas. La velocidad de la rueda es mucho más constante que la de un brazo, así que la imagen sale más estable.
Personalización para Chile
En Chile consigues buena parte de la electrónica del poi en MechatronicStore:
- Convertidor Elevador de Voltaje DC DC Boost Step Up 1-5V a 5V 500mA (SKU GI3-6): sube el voltaje de la LiPo a los 5 V de la tira. Entrega menos corriente que el MiniBoost de 1 A del original, así que mantén el brillo bajo (el 25 % por defecto del código).
- Mini Switch Interruptor Proto 3 Pin 7.5x23.5mm (SKU GF1-15): el interruptor deslizante que corta la alimentación del poi.
- Resistencias 1/4W 1 unidad 100kΩ (SKU GK5-14): necesitas dos para el divisor que mide la batería.
- Batería LiPo 852540 3.7V 600mAh (SKU GE1-11): cabe dentro del tubo y alimenta el poi durante una presentación corta con el brillo bajo.
La Feather RP2040 RFM69 y la tira DotStar no están hoy en el catálogo. Ojo con reemplazar la DotStar por una tira WS2812B: funciona, pero por lo explicado arriba la imagen sale punteada al girar y el código tendría que cambiar a la librería de NeoPixel.
Recursos
- Tutorial original (inglés): Supernova POV Pixel Poi V2 with Wireless Controller, de Erin St Blaine, en el que se basa esta guía.
- Código del poi y del control: Supernova_POV_Poi_V2 en Adafruit Learning System Guides, con una biblioteca de imágenes de ejemplo.
- Guía original en PDF: supernova pov pixel poi-v2.pdf
Versión chilena con componentes en stock local en MechatronicStore.








