El PWM (modulación por ancho de pulso) es una técnica que regula la potencia media de una señal digital variando cuánto tiempo permanece activa.
Queremos poner un LED al 50% de brillo, o mover un brazo robótico a 45 grados exactos. El problema es que los microcontroladores son digitales; solo entienden de 0V y 3.3V.
Para solucionar esto utilizamos el PWM (Pulse Width Modulation), una técnica que consiste en encender y apagar una señal digital tan rápido que parece analógica.
En Arduino, esto implicaba configurar analogWrite(), preocuparse por qué pines tienen “la olita” (~) y, en el caso de los servos, importar librerías externas. En DeviceScript, fiel a su filosofía, todo esto se abstrae en Servicios.
Regular un LED
En realidad, ya hemos usado PWM. En el artículo anterior escribimos:
await led.intensity.write(0.5)Por debajo, el driver LightBulb configuró automáticamente el hardware de PWM del ESP32 (el periférico LEDC) o de la Pico W.
Esa es la belleza de DeviceScript: No necesitas saber a qué frecuencia oscila el LED. Tú pides el 50% de luz, y el servicio se encarga de generar la señal eléctrica adecuada.
No conectes motores, relés ni otras cargas de potencia directamente a un GPIO. Necesitan una etapa de potencia apropiada, protección frente a cargas inductivas y, según el caso, un servicio o driver específico.
Controlar un servomotor
Aquí es donde la cosa se pone interesante. Un servomotor es un motor con un cerebro y una caja reductora que puede ubicarse en un ángulo específico (normalmente entre -90º y 90º, o 0º y 180º).
Los servos se controlan con una señal PWM muy específica (pulsos de 1ms a 2ms cada 20ms). Generar esto “a mano” es un dolor. DeviceScript nos ofrece el servicio Servo.
Conectar el servo
Un servo tiene 3 cables:
- Marrón/Negro: GND (Tierra).
- Rojo: VCC (5V normalmente).
- Naranja/Amarillo: Señal (Al pin del microcontrolador).
Advertencia de Energía: Los servos consumen mucha corriente al arrancar. Si conectas un servo pequeño (tipo SG90) al pin de 5V de tu placa puede funcionar, pero si usas uno grande o varios a la vez, necesitas una fuente de alimentación externa. Si no, tu microcontrolador se reiniciará constantemente (Brownout).
Si empleas una fuente externa, une su GND con el GND del microcontrolador para que ambos compartan la referencia de la señal.
El servicio Servo
Vamos a mover un servo conectado al pin GPIO 10.
import { delay } from "@devicescript/core"
import { startServo } from "@devicescript/servers"
import { pins } from "@dsboard/esp32_c3_devkit_m"
// Iniciamos el servicio Servo
const miServo = startServo({
pin: pins.GPIO10,
// Opcional: Ajustes de pulso si tu servo es especial
// minAngle: -90, maxAngle: 90
})
// Hacemos un barrido (Sweep)
async function barrido() {
while (true) {
// Mover a -90 grados (mínimo)
console.log("Moviendo a -90º")
await miServo.angle.write(-90)
await delay(1000)
// Mover a 0 grados (centro)
console.log("Moviendo a 0º")
await miServo.angle.write(0)
await delay(1000)
// Mover a 90 grados (máximo)
console.log("Moviendo a 90º")
await miServo.angle.write(90)
await delay(1000)
}
}
barrido()Al abrir el simulador aparecerá el control de un servo. El brazo virtual girará a las posiciones indicadas por el programa.
Habilitar y deshabilitar
Los servos hacen fuerza para mantener la posición. Esto consume energía y puede calentar el motor.
El servicio Servo tiene una propiedad enabled.
enabled = true: El servo hace fuerza para mantener el ángulo.enabled = false: El servo se “relaja” y puedes moverlo con la mano.
// Apagar el servo para ahorrar energía
await miServo.enabled.write(false)Controlar el servo con un potenciómetro
Vamos a unir todo lo aprendido. Vamos a controlar la posición del servo con un potenciómetro. Esto es la base de cualquier brazo robótico teleoperado.
Tenemos un problema matemático:
- El potenciómetro (
Potentiometer) nos da valores de 0 a 1. - El servo (
Servo) espera ángulos de -90 a 90.
Necesitamos una función de mapeo (la regla de tres de toda la vida).
import { startServo, startPotentiometer } from "@devicescript/servers"
import { pins } from "@dsboard/esp32_c3_devkit_m"
const pot = startPotentiometer({ pin: pins.GPIO4 })
const servo = startServo({ pin: pins.GPIO10 })
// Función auxiliar de mapeo lineal
function map(x: number, in_min: number, in_max: number, out_min: number, out_max: number) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
// Nos suscribimos a los cambios del potenciómetro
pot.reading.subscribe(async (valorPot) => {
// valorPot viene de 0 a 1
// Lo transformamos a -90 a 90
const angulo = map(valorPot, 0, 1, -90, 90)
// Movemos el servo
await servo.angle.write(angulo)
})Probarlo en el simulador
Este es uno de los ejemplos más divertidos de probar en el Dashboard:
Arrancad el simulador.
Localiza el deslizador del potenciómetro y el brazo del servo.
Arrastrad el Slider con el ratón.
Comprueba cómo el servo sigue el valor del potenciómetro.
Sin cables, sin riesgos de quemar nada y con una respuesta inmediata.
Otros usos del PWM
DeviceScript aplica la misma lógica para todo:
- Zumbadores (Buzzers): Usan PWM para generar tonos. Tienen su propio servicio
Buzzerdonde mandasplayTone(frecuencia, duracion). - Motores DC: Usan el servicio
Motor. Tú indicas la velocidad (speed) de -1 a 1, y el driver gestiona el PWM y la dirección (si usas un driver de puente H compatible).