Lector PWM Eficiente para Arduino y Decodificador Receptor RC con Fail-Safe Integrado
Componentes y suministros
| Componente | Cantidad |
|---|---|
| Arduino UNO | 1 |
| Micro-servo SG90 | 2 |
Sobre el proyecto
Este proyecto contiene código genérico y eficiente para leer un receptor RC (o cualquier señal PWM) en cualquier pin de entrada Arduino, y aplicar fail-safe ante pérdida de señal del transmisor.
El video muestra un Arduino Uno actuando como mezclador de servos usando el código PWMread_RCfailsafe.ino (disponible al final).
Las funciones en PWMread_RCfailsafe.ino gestionan los registros de interrupción automáticamente y se mueven fácilmente entre proyectos con distintos pines.
En el ejemplo del video:
- Fail-safe activa al perder señal: canal elevador a tope arriba, alerón a neutro.
- Dirección del servo azul según posición del acelerador.
- Rango de movimiento (tasas) del servo azul según slider del transmisor.
- Mezcla activada/desactivada con interruptor de tren de aterrizaje (gear switch).
Contenido
- Cómo se controlan servos por PWM
- Ejemplos de uso de Arduino en modelos RC/robots
- Resumen del código: Decodificar PWM de receptor RC con fail-safe
- Cómo usar
PWMread_RCfailsafe.ino - Mostrar frecuencia y tasa de frames del receptor
- Ejemplo de mezcla de servos
- Razonamiento del enfoque
- Limitaciones
¿Cómo se controlan servos y variadores por PWM?
Se asume conocimiento de señales PWM para servos/variadores. Buen video explicativo.
Conocimientos previos necesarios:
- Arduino IDE
- Variables float, boolean, int
- Bucles if, for
- Arrays
- Librería Servo
Ejemplos de uso de Arduino en modelos RC/robots
Solo tu imaginación te limita:
- Mezcla de servos, encender/apagar luces, controlar bombas/válvulas, secuencias personalizadas…
- Crear controlador (estabilización de vuelo/autopiloto, mantenimiento de rumbo, altitud/profundidad, auto-nivelado, sense-and-avoid, retorno a casa…)
- Responder a pérdida de señal o baja tensión de batería…
- Usar mismo transmisor para múltiples modelos/proyectos sin cambiar ajustes ni usar memoria de modelo.
Resumen del código: Decodificar PWM de receptor RC con fail-safe
El código mide señales PWM usando interrupciones por cambio de pin (PCI). Las funciones automatizan la configuración de interrupciones y extracción de datos de cualquier pin digital/analógico (excepto A6, A7) en Arduino Uno, Nano o Pro Mini. Fácil para principiantes.
Objetivo: crear un "módulo" genérico de receptor RC con fail-safe portable entre proyectos. El código de ejemplo en "cómo usar" es un medio para ese fin.
Nota: No funciona con SoftwareSerial u otras librerías que usen PCI.
Funcionamiento interno (para interesados):
- Pines de entrada en array (cualquier longitud).
- Función setup habilita PCI configurando registros apropiados para cada pin del array.
- Cambio de voltaje en cualquier pin seleccionado dispara una de 3 ISR según registro de puerto:
ISR(PCINT0_vect)→ Puerto B,ISR(PCINT1_vect)→ Puerto C,ISR(PCINT2_vect)→ Puerto D. - Dentro de cada ISR, bucle FOR e IF determinan qué pin cambió y a qué canal RC pertenece. Se anota el tiempo con
micros()antes de volver alloop()principal. - Intervalos entre cambios de pin calculan ancho de pulso y período de repetición.
- Flags en cada ISR indican cuándo se reciben nuevos pulsos.
- Flags usadas por las funciones restantes para extraer y procesar datos de las ISR.
Video de Joop Brokking explica este método para conectar receptor RC a Arduino (primeros 8 min).
Todo este detalle lo gestiona PWMread_RCfailsafe.ino (descarga al final).
Info útil sobre manipulación de puertos: tronixstuff.com
Además del manejo de PCI, función dedicada RC_decode() convierte ancho de pulso (1000-2000µs) en señal de control ±100%. Fail-safe verifica señal válida del transmisor (tolerancias 10-330Hz, 500-2500µs). Si se pierde señal, RC_decode() devuelve valor fail-safe predefinido.
Valores de calibración por transmisor y posiciones fail-safe por canal se configuran en PWMread_RCfailsafe.ino.
Cómo usar PWMread_RCfailsafe.ino
Paso 1: Hardware ejemplo con Arduino Uno
Si sigues el ejemplo con Arduino Uno, conecta tu receptor así (si usas tu propio proyecto, ve al Paso 2):
- Alimenta receptor con 5V y GND.
- Conecta pines de señal del receptor a pines 2-7 del Arduino (jumper hembra-macho). (Si receptor 2 canales, solo pines 2 y 3).
- Para ejemplo mezclador servos: servo 1 pin 9, servo 2 pin 10.

Paso 2: Copiar PWMread_RCfailsafe.ino a carpeta del sketch
Sketch ejemplo RC_Read_Example incluido para descargar. Úsalo como sketch principal.
Copia y pega el archivo PWMread_RCfailsafe.ino en la carpeta de tu sketch principal. Al abrir en IDE, aparecerá segunda pestaña con el código.

Paso 3: Especificar pines de entrada
Abre/reaabre sketch principal en IDE. Pestaña PWMread_RCfailsafe, ve a "USER DEFINED VARIABLES" e introduce pines en array pwmPIN[].
Nota: Cualquier número de pines, cualquier orden. Más entradas = más tiempo en rutina de interrupción. Pines A6 y A7 son solo analógicos, no válidos. Arduino MEGA no soportado actualmente (fácil de añadir si hay demanda).
El primer elemento de pwmPIN[] es canal 1, segundo canal 2… Si usas todos los canales del receptor, asegura que canal 1 del receptor corresponda a canal 1 en pwmPIN[].

Paso 4: Revisar funciones disponibles

Paso 5: Imprimir datos ancho de pulso a serial
Sube RC_Read_Example, enciende transmisor e imprime datos PWM en bruto a serial.
Usa RC_avail() para comprobar nuevos datos en todos los canales, luego print_RCpwm() para enviar anchos de pulso a serial.

Paso 6: Calibrar transmisor
Usa datos PWM en bruto de print_RCpwm() para modificar manualmente arrays RC_min[], RC_mid[], RC_max[] y calibrar cada canal a rango ±100%.

Paso 7: Imprimir canales calibrados a serial
Comenta print_RCpwm(). Usa RC_decode(canal) para calibrar cada canal a rango ±1. Imprime cada canal calibrado con decimal2percentage() seguido de Serial.println("").

Paso 8: Configurar fail-safe
Ajusta posiciones fail-safe en array RC_failsafe[] por canal (rango ±1). Enciende/apaga transmisor para verificar fail-safe.
La entrada RC ya puede usarse en tu sketch.
Nota: Puede que necesites desactivar fail-safe en el receptor, sino Arduino no detectará pérdida de señal.

Mostrar tasa de frames y frecuencia del receptor
Período y frecuencia de repetición del receptor se pueden imprimir a serial. Comprueba nuevos datos en canal elegido con PWM_read(número_canal), luego usa PWM_period() y PWM_freq() para extraer datos. Código ejemplo en RC_FrameRate.ino.
Mejor usar canal 1 (primer pulso de cada frame). PWM_read() usa mismos flags que RC_decode(CH); llama a PWM_read() primero.

El período del receptor indica cuánto tiempo tiene el código antes del siguiente dato. Si RC_avail() no detecta nuevos datos RC tras tiempo predeterminado (ej. 21ms), ejecuta RC_decode() para activar fail-safe y/o continuar programa (ej. controlador PID) a frecuencia estable.
Logrado en RC_Read_Example.ino con:
now = millis();
if(RC_avail() || now - rc_update > 21) {
rc_update = now;
// actualizar datos RC con RC_decode()
// ejecutar controlador PID
// aplicar mezcla servos
// posicionar servos
}
Ejemplo mezcla de servos
Incluido RC_ServoMixer_Example.ino mezclando 2 canales receptores (canales 2 y 3: elevador y alerón). Muestra método para dirección, tasa y sub-trim de servos. Librería Servo controla servos por pines 9 y 10.

Mezcla: sumar/restar canales y limitar salida a -1..+1.
mix1 = canal2 - canal3 (elev - ail)
mix2 = canal2 + canal3 (elev + ail)
Antes de posicionar servos, convierte señal ±100% (±1) a ancho de pulso equivalente en µs. En ejemplo uso función calc_uS() al final del sketch.

Dirección, tasa y sub-trim por servo calculan ancho de pulso apropiado.
Pulso neutro estándar: 1500µs, rango normal ±500µs → mínimo 1000µs (-100%), máximo 2000µs (+100%).
pulso, µs = 1500 + (pos_servo_% * tasas * dirección + sub_trim) * 500
Dirección, tasa y sub-trim pueden ser estáticos o modificados dinámicamente por el sketch según otra entrada del receptor u otro medio.
Razonamiento del enfoque
Se puede leer receptor RC con pulseIn(PIN, HIGH), pero pulseIn() bloquea loop() esperando inicio y fin de pulso, desperdiciando tiempo de procesamiento. Con más de una entrada, también se pierden datos.
Para velocidad, mejor usar interrupción por cambio de pin (PCI) del Arduino con manipulación directa de puertos, permitiendo que loop() corra con mínima demora. Pero es más complejo y lento que simplemente llamar pulseIn().
Por eso quise lo mejor de ambos mundos: código genérico portable entre proyectos. Solo copiar/pegar archivo .ino (funciones y rutinas ISR) a carpeta sketch principal, especificar pines de entrada, y usar funciones en el sketch.
Limitaciones
Función micros()
Temporización en microsegundos usa micros(), que cuenta en pasos de 4µs. Precisión de 4µs al medir pulsos 1000-2000µs; más que adecuado en práctica.
Si se desea, se puede mejorar a 0.5µs usando interrupciones de timer: instructables.com
Eficiencia de PWMread_RCfailsafe.ino
Leyendo receptor 6-9 canales: 1.4-2.0% tiempo procesador en rutinas PCI, aceptable.
Tiempos ISR según canales de entrada:
| Canales | Tiempo ISR |
|---|---|
| 1 | < 8µs |
| 2 | < 12µs |
| 3 | < 16µs |
| 4 | < 20µs |
| 5 | < 20µs |
| 6 | < 24µs |
Más canales = ISR más larga (bucle FOR recorre cada canal cada llamada ISR). Esta ineficiencia extra es insignificante midiendo señales RC baja frecuencia (50Hz).
Además, ~4µs para entrar/salir ISR. Por pulso, ISR corre 2 veces (flanco subida y bajada).
Tiempo medir 1 pulso con 6 entradas RC: 2 × (4µs entrar + 24µs ejecutar) = 56µs (ancho mínimo medible).
Leer 6 canales: 336µs (6 × 56µs).
Período repetición receptor 20ms → interrupción activa 1.68% (0.000336/0.02). Muy superior a pulseIn() que bloquearía hasta 20ms por pin.
FYI: con 2 entradas RC, ISR activo solo 0.16% tiempo.
Frecuencia práctica máxima (Hz)
Para otros usos, frecuencia práctica máx ~2.5kHz (100 pasos resolución micros() ±0.025kHz).
Con 1 pin a esa frecuencia, 3% tiempo en interrupción → duty mínimo medible 0.03 = pulso mínimo 12µs.
Para frecuencias mayores, reescribe ISR para tu aplicación.
Código
PWMread_RCfailsafeRC_Read_ExampleRC_FrameRateRC_ServoMixer_Example
PWMread_RCfailsafe Arduino
Archivo .ino con funciones y rutinas ISR para decodificar receptor RC y aplicar fail-safe si se pierde señal transmisor. Copia en misma carpeta que sketch principal (aparecerá como segunda pestaña en IDE). Sigue instrucciones en el archivo.
/* Kelvin Nelson 24/07/2019
* Decodificación PWM de Receptor RC con fail-safe
* Funciones fáciles para medir señales cuadradas en pines Arduino Uno/Pro Mini/Nano (excluyendo A6,A7).
* Pensado para receptores RC, pero usable en otras apps medición PWM como reemplazo directo de pulseIn(PIN, HIGH).
* (No testado >1kHz ni en Arduino Mega)
* Convierte pulso RC (1000-2000µs) en salida -+100% (-+1.0) para uso en sketch.
* Calibración conversión + fail-safe por canal (tolerancias fail-safe 10-330Hz y 500-2500µs).
* Extrae datos brutos por pin: tiempo pulso, ancho, longitud frame, duty, frecuencia.
* Setup rápido. Organización:
* - Resumen código
* - Lista funciones
* - Cómo usar, sketches ejemplo
* - Variables usuario → pines entrada, calibración transmisor, fail-safe
* - Variables globales y funciones
*/
// LISTA FUNCIONES:
// void setup_pwmRead() // inicializa medición PWM con PCI
// boolean RC_avail() // HIGH cuando nuevos datos RC disponibles
// float RC_decode(canal) // decodifica canal RC a ±100% con fail-safe
// void print_RCpwm() // imprime datos brutos RC a serial (calibración)
// boolean PWM_read(canal) // HIGH cuando nuevo pulso detectado en canal
// unsigned long PWM_time() // tiempo inicio pulso
// float PWM() // ancho pulso
// float PWM_period() // período entre pulsos
// float PWM_freq() // frecuencia
// float PWM_duty() // duty cycle
// NOTA: PWM_read(CH) y RC_decode(CH) usan mismos flags → datos perdidos si ambos usados mismo canal simultáneamente.
// SUGERENCIA: si usas PWM_read(CH) para frame rate de canal RC, llámalo antes que RC_decode(CH).
// CÓMO USAR:
// En "USER DEFINED VARIABLES":
// Paso 1: pines entrada en array pwmPIN[] = {}.
// - Cualquier número pines en pwmPIN[] (disponibles 0-13, A0-A5)
// - Orden no importa, ej: pwmPIN[] = {A0,5,6,10,8} (5 canales) o {A0,5} (2 canales)
// - Primer elemento = pin "canal 1", segundo = "canal 2"...
// - Pines conectados a receptor RC al inicio del array. Ej: primeros 2 canales RC, 3º pin sensor ultrasónico.
// Paso 2: si receptor RC conectado a todas entradas, RC_inputs = 0; si no, nº canales conectados ej: RC_inputs = 2.
// Paso 3: calibra transmisor subiendo sketch simple con este .ino en carpeta, imprime PWM bruto a serial (o copia funciones necesarias al sketch).
// Modifica manualmente RC_min[], RC_mid[], RC_max[] según tu transmisor (usa tasas máximas para mejor resolución).
// Sketch ejemplo imprimir PWM RC a serial:
/* void setup() { setup_pwmRead(); Serial.begin(9600); }
void loop() { if(RC_avail()) print_RCpwm(); } */
// Paso 4: elige posición fail-safe por canal (-1.0 a +1.0) en array RC_failsafe[] = {}
// Nota: para que Arduino responda a pérdida señal transmisor, puede que necesites desactivar fail-safe en receptor.
// Sketch ejemplo comprobar fail-safe e imprimir canales calibrados:
/* unsigned long now, rc_update;
const int channels = 6;
float RC_in[channels];
void setup() { setup_pwmRead(); Serial.begin(9600); }
void loop() {
now = millis();
if(RC_avail() || now - rc_update > 25){
rc_update = now;
//print_RCpwm();
for(int i=0;i=0 && pwmPIN[i]<=7) pwmPIN_port[i]=2; // PCINT2_vect (PORTD)
else if(pwmPIN[i]>=8 && pwmPIN[i]<=13) pwmPIN_port[i]=0; // PCINT0_vect (PORTB)
// convierte nº pin (ej. pin 11 o A0) a posición en registro puerto
if(pwmPIN[i]==0 || pwmPIN[i]==A0 || pwmPIN[i]==8) pwmPIN_reg[i]=0b00000001;
else if(pwmPIN[i]==1 || pwmPIN[i]==A1 || pwmPIN[i]==9) pwmPIN_reg[i]=0b00000010;
else if(pwmPIN[i]==2 || pwmPIN[i]==A2 || pwmPIN[i]==10) pwmPIN_reg[i]=0b00000100;
else if(pwmPIN[i]==3 || pwmPIN[i]==A3 || pwmPIN[i]==11) pwmPIN_reg[i]=0b00001000;
else if(pwmPIN[i]==4 || pwmPIN[i]==A4 || pwmPIN[i]==12) pwmPIN_reg[i]=0b00010000;
else if(pwmPIN[i]==5 || pwmPIN[i]==A5 || pwmPIN[i]==13) pwmPIN_reg[i]=0b00100000;
else if(pwmPIN[i]==6) pwmPIN_reg[i]=0b01000000;
else if(pwmPIN[i]==7) pwmPIN_reg[i]=0b10000000;
}
}
// SETUP INTERRUPCIONES CAMBIO PIN
void setup_pwmRead(){
for(int i=0;inum_ch) RC_inputs=num_ch;
}
// ISR PINES D8-D13 (PORTB)
ISR(PCINT0_vect){
pciTime=micros();
for(int i=0;iRC_inputs) return 0;
int i=CH-1;
int Min=(CH<=size_RC_min)?RC_min[CH-1]:1000;
int Mid=(CH<=size_RC_mid)?RC_mid[CH-1]:1500;
int Max=(CH<=size_RC_max)?RC_max[CH-1]:2000;
float CH_output;
if(FAILSAFE(CH)==HIGH){
CH_output=(CH>size_RC_failsafe)?0:RC_failsafe[i];
} else {
CH_output=calibrate(PW[i],Min,Mid,Max);
}
return CH_output; // rango -1..+1 (0=neutro, 1=desplazamiento máx una dirección, -1=otra)
}
float calibrate(float Rx,int Min,int Mid,int Max){
float calibrated;
if(Rx>=Mid) calibrated=map(Rx,Mid,Max,0,1000);
else if(Rx==0) calibrated=0;
else calibrated=map(Rx,Min,Mid,-1000,0);
return calibrated*0.001;
}
boolean FAILSAFE(int CH){
int i=CH-1;
boolean failsafe_flag=LOW;
if(pwmFlag[i]==1){
pwmFlag[i]=0;
if(pwmPeriod[i]>100000) failsafe_flag=HIGH; // <10Hz
else if(pwmPeriod[i]<3000) failsafe_flag=HIGH; // >330Hz
if(PW[i]<500 || PW[i]>2500) failsafe_flag=HIGH;
} else if(micros()-pwmTimer[i]>100000) failsafe_flag=HIGH; // sin pulso >100ms (10Hz)
return failsafe_flag;
}
void print_RCpwm(){
for(int i=0;i=0) Serial.print(" ");
if(abs(pc)<100) Serial.print(" ");
if(abs(pc)<10) Serial.print(" ");
Serial.print(" ");Serial.print(pc);Serial.print("% ");
}
/* FUNCIONES PWM GENÉRICAS */
unsigned long pin_time;
float pin_pwm;
float pin_period;
boolean PWM_read(int CH){
if(CH<1 && CH>num_ch) return false;
int i=CH-1;
boolean avail=pwmFlag[i];
if(avail==HIGH){
pwmFlag[i]=LOW;
noInterrupts();
pin_time=pwmTimer[i];
pin_pwm=PW[i];
pin_period=pwmPeriod[i];
interrupts();
}
return avail;
}
unsigned long PWM_time(){return pin_time;}
float PWM_period(){return pin_period;}
float PWM(){return pin_pwm;}
float PWM_freq(){return 1000000/pin_period;}
float PWM_duty(){return pin_pwm/pin_period;}
RC_Read_Example Arduino
Sketch ejemplo para mostrar datos brutos, calibrar receptor RC y configurar fail-safe. Requiere PWMread_RCfailsafe.ino en misma carpeta.
unsigned long now, rc_update;
const int channels = 6;
float RC_in[channels];
void setup() { setup_pwmRead(); Serial.begin(9600); }
void loop() {
now = millis();
if(RC_avail() || now - rc_update > 25){
rc_update = now;
print_RCpwm();
for(int i=0;i
RC_FrameRate Arduino
Sketch que imprime tasa de frames y frecuencia receptor RC. Requiere PWMread_RCfailsafe.ino.
void setup() { setup_pwmRead(); Serial.begin(9600); }
void loop() {
if(PWM_read(1)){
Serial.print(PWM_period(),0);Serial.print("uS ");
Serial.print(PWM_freq());Serial.println("Hz");
}
}
RC_ServoMixer_Example Arduino
Ejemplo mezcla servos. 2 canales de receptor 6 canales mezclados y enviados a 2 servos con librería Servo. Requiere PWMread_RCfailsafe.ino.
#include
Servo servo1; Servo servo2;
const int servo1_pin=9; const int servo2_pin=10;
boolean servo_dir[]={0,1};
float servo_rates[]={1,0.5};
float servo_subtrim[]={0.0,0.0};
boolean servo_mix_on=true;
unsigned long now, rc_update;
const int channels=6;
float RC_in[channels];
void setup(){
servo1.attach(servo1_pin,500,2500);
servo2.attach(servo2_pin,500,2500);
setup_pwmRead(); Serial.begin(9600);
}
void loop(){
now=millis();
if(RC_avail() || now-rc_update>25){
rc_update=now;
print_RCpwm();
for(int i=0;i
Esquemáticos
Receptor RC alimentado por 5V/GND pines ICSP, 6 salidas señal a pines 2-7.
Micro servo 1: 5V/GND, señal pin 9.
Micro servo 2: 3.3V/GND, señal pin 10.
Proceso de manufactura
- Crea Animaciones y Juegos en LCD 16×2 con Arduino UNO
- Construye un Voltímetro Inteligente con Arduino y Smartphone – Proyecto DIY Fácil
- Registrador de Temperatura y Humedad con Arduino, DHT11 y Ethernet Shield
- Leer Temperatura y Humedad con DHT11 en Blynk – Tutorial Paso a Paso con Arduino
- Control Arduino UNO desde Python 3 – Envío de Comandos y Demo LED
- Autómatas Celulares en Tiempo Real con Arduino y Pantalla OLED 128x64
- Construye una Radio FM Compacta con Arduino Nano y RDA5807M
- Construye un Receptor FM Potente con Arduino y Módulo TEA5767
- Sistema de control de asistencia RFID en tiempo real con Arduino y Google Sheets
- Circuito de entrada analógica aislada para Arduino
- Dominando Entrada y Salida en programación C