Control RC y Arduino - pensactius/Tutoriales GitHub Wiki


Esta página está en construcción, se irán añadiendo nuevos contenidos


En este tutorial veremos como interpretar las señales que llegan de una emisora RC (Radio Control) desde Arduino para mover motores DC o Servo.

Protocolos de comunicación RC

Existen multitud de protocolos de comunicación entre emisor/receptor, es decir, la forma en que la emisora y el receptor hablan entre ellos. Algnos de estos protocolos son universales entre diferentes marcas de Radio Frecuencia, pero otros son exclusivos de la marca. Algunos de estos protocolos se muestran a continuación:

  • PWM (universal)
  • PPM (universal)
  • PCM (universal)
  • SBUS (Futaba, Frsky)
  • IBUS (Flysky)
  • etc.

Algunas emisoras permiten alternar entre un modo universal, como PWM y el modo propio (p.ej. SBUS).

ATENCIÓN

Este tutorial requiere de un combo emisora/receptor de Radio Control que permitan el uso de PWM como medio de comunicación entre ellos.

PWM - Pulse Width Modulation

Este es un método bastante común usado en Radio Control. En los viejos tiempos cuando sólamente había aviones RC fijos, los receptores usaban señales PWM estandard directamente para controlar los servos o los ESC. A día de hoy esta tecnología se sigue usando en muchos modelos.

PWM son las siglas de Pulse Width Modulation. Es una señal en la que la longitud de los pulsos indica la posición del servo. La longitud del pulso normalmente varía entre 1000us (microsegundos) y 2000us.

Los receptores RC que usan protocolo PWM suelen ser bastante baratos y comunes. El problema es la gran cantidad de cables que usan, por eso ahora se suelen usar otros protocolos con menos cables como SBUS o PPM (especialmente para drones).

Control Remoto con Arduino

Una emisora RC típica tiene varias superfícies de control, como ruedas o joysticks y algunas palancas como interruptores adicionales. Cada grado de libertad que proporciona la emisora tiene asociado un canal. Por ejemplo, uno de los joysticks controla dos canales (x, y), mientras un interruptor puede que controle otro. Las emisoras pueden tener desde tres canales hasta ocho o nueve. Lo típico suele ser entre 4 y 6 canales.

Conexión con Arduino

Vamos a realizar una conexión simple, de momento, para ver el tipo de señal que recibimos en crudo desde la emisora. En la figura se muestra un receptor de seis canales, pero sólamente vamos a usar tres por ahora. Esto significa que necesitamos tres pines digitales para leer la entrada de cada canal, así como 5V para alimentar el receptor. El diagrama siguiente muestra la conexión a realizar:

El programa de Arduino leerá directamente la señal que viene del receptor. El lenguaje de la emisora es, en realidad, el típico PWM usado para controlar el movimiento de un servo. Arduino tiene una función para leer este tipo de pulsos y devolver la longitud de estos en milisegundos. Esta función se llama pulseIn(). Veamos un simple ejemplo que muestra la señal recibida por cada canal desde el receptor:

/*
  RC PulseIn Serial Read out
  By: Nick Poole
  SparkFun Electronics
  Date: 5
  License: CC-BY SA 3.0 - Creative commons share-alike 3.0
  use this code however you'd like, just keep this license and
  attribute. Let me know if you make hugely, awesome, great changes.

  Modificado por: Andrés Mata
*/

int ch1; // Here's where we'll keep our channel values
int ch2;
int ch3;

void setup() {

  pinMode(2, INPUT); // Set our input pins as such
  pinMode(3, INPUT);
  pinMode(4, INPUT);

  Serial.begin(9600); // Pour a bowl of Serial

}

void loop() {

  ch1 = pulseIn(2, HIGH, 25000); // Read the pulse width of
  ch2 = pulseIn(3, HIGH, 25000); // each channel
  ch3 = pulseIn(4, HIGH, 25000);

  Serial.print("Channel 1: ");   // Print the value of
  Serial.print(ch1);             // each channel

  Serial.print(" Channel 2: ");
  Serial.print(ch2);

  Serial.print(" Channel 3: ");
  Serial.println(ch3);

  delay(100); // I put this here just to make the terminal
  // window happier
}

Si consultas la documentación oficial de la función pulseIn() verás que hay dos modos de usarla: con dos o tres argumentos:

pulseIn(pin, value)

pulseIn(pin, value, timeout)

  • pin - Pin de la placa desde el cuál leer el pulse.
  • value - Tipo de pulso a leer HIGH o LOW.
  • timeout (opcional) - tiempo de espera en microsegundos para recibir el pulso; por defecto un segundo.

Hemos usado la versión de tres argumentos para reducir el tiempo de espera entre pulsos.

OBERVACIÓN

En mi caso, he tenido que aumentar el tiempo de espera; ya que no recibía bien los valores del tercer canal. He cambiado las siguientes líneas:

  ch1 = pulseIn(5, HIGH, 25000); // Read the pulse width of 
  ch2 = pulseIn(6, HIGH, 25000); // each channel
  ch3 = pulseIn(7, HIGH, 25000);

por

  ch1 = pulseIn(5, HIGH, 50000); // Read the pulse width of 
  ch2 = pulseIn(6, HIGH, 50000); // each channel
  ch3 = pulseIn(7, HIGH, 50000);

La función pulseIn() devuelve la longitud del pulso en microsegundos, y así es cómo leemos las señales PWM recibidas. Si todo va bien, deberías ver los valores recibidos por el monitor serie de Arduino.

Estos números probablemente no tengan mucho significado ahora, pero deberían oscilar entre 1000 y 2000. Lo que importa de verdad es que al mover algunas de las superfícies de control de la emisora el valor asociado a ese canal debería cambiar.

Qué canal está asociado a cada función depende de la configuración de la emisora. Una forma de descubrirlo sería mover las palancas, joysticks e interruptores pero sólo uno cada vez. Anota qué canal cambia de valor en cada momento.

En general, las emisoras permiten seleccionar enre diferentes modos que determinan qué superfícies controlan cada canal. En la siguiente ilustración se muestra la configuración de canales en Modo 2 para una emisora Turnigy TGY 9X:

Resumiendo esta figura indica lo siguiente:

  • Joystick Izquierdo - Canal 4 (X), Canal 3 (Y)
  • Joystick Derecho - Canal 1 (X), Canal 2 (Y)

De modo que si movemos el joystick izquierdo arriba y abajo (Y) veremos cambiar los valores del canal 3.

Control de un Robot con Movimiento Diferencial

Veamos ahora cómo podemos usar la emisora y el receptor RC para controlar un robot que use movimiento diferencial con motores DC.

En el ejemplo que veremos usaremos los siguientes componentes:

  • 2 x motores de DC de 6V
  • Receptor RC con soporte PWM de 6CH (sólo usamos dos canales, así que un receptor con menos canales servirá)
  • Emisora RC de 6CH con soporte PWM (sólo usamos dos canales, así que una emisora con menos canales servirá)
  • Módulo L298N para controlar los dos motores
  • 2 x baterías tipo 18650 (3.7V/cada una), en total 7.4V. Recuerda que el módulo L298N se "come" unos 2V, así que para dar 6V a los motores necesitamos alimentar el módulo con algo más de voltaje.

Vamos a ver cómo controlar un robot típico de dos ruedas (más un tercer apoyo, normalmente una bola loca) que use un movimiento diferencial. En la figura se muestra cómo funciona el movimiento en este tipo de robots (entraído de 42bots.com):

El principio de operación es muy simple:

  • Cuando las dos ruedas giran en la misma dirección y a la misma velocidad el robot se mueve en línea recta
  • Cuando las dos ruedas giran en la misma dirección, pero a diferentes velocidades, el robot gira en el sentido de la rueda que gira más lento. Por ejemplo, si la rueda derecha gira más rápido que la rueda derecha, el robot girará a la izquierda.
  • Si las dos ruedas giran a la misma velocidad, pero sentido contrario, el robot gira sobre sí mismo respecto el centro entre las dos ruedas.

Esto tiene consecuencias a la hora de pensar el código para controlar el robot desde la emisora de Radio Control. Controlaremos el robot usando dos canales:

  • Canal 3: Controla la potencia (Throttle) del robot Si movemos esta palanca hacia arriba o hacia abajo deberán moverse las ruedas con la misma potencia y los motores girarán en un sentido u otro dependiende de si avanzamos (palanca subida) o retrocedemos (palanca bajada).

  • Canal 1: Controla la dirección (izquierda/derecha) Si movemos esta palanca hacia la izquierda querremos que la rueda izquierda vaya perdiendo velocidad. De esta manera, como hemos visto antes, el robot girará hacia la izquierda. Si movemos la palanca a la derecha queremos que sea la rueda derecha la que pierda velocidad.

Teniendo en cuenta este comportamiento la solución que se me ha ocurrido es la siguiente:

  • Al mover la palanca de throttle se calculará la potencia a aplicar a los dos motores por igual
  • Se aplicará a las potencias de cada motor un factor de escala (diferente para cada motor), que indicará de 0..100 el porcentaje de potencia a aplicar.
  • Este porcentaje variará al mover la palanca que controla la dirección (izquierda/derecha). Al mover la palanca a la izquierda el factor del motor izquierdo disminuye, si se mueve a la derecha es el factor del motor derecho el que disminuye.

De esta manera se consigue que los motores giren a la misma velocidad si queremos ir en línea recta o realizan el movimiento diferencial deseado dependiendo de la dirección de giro.

Conexiones

Código Movimiento Diferencial con Emisora RC

El código que se muestra a continuación implementa la solución explicada anteriormente. Puede parecer muy largo pero un 50% son comentarios. Se podría hacer más corto también, pero he preferido la claridad por encima de la eficiencia. A continuación se explicará con más detalle su funcionamiento.

//
// Control de un robot con movimiento diferencial con emisora de Radio Control
//
// Utiliza un módulo L298N para controlar los motores.
//
// M1: Motor Izquierdo
// M2: Motor Derecho
//
// Se usa un combo emisora/receptor que se comuniquen usando PWM. 
// Se usan dos canales:
//
// Canal 1: Izquierda / Derecha
// Canal 2: Adelante / Atras
//
// Creado por: Andrés Mata para PensActius
//

// Pines motor A
const int ENA = 9;
const int IN1 = 13;
const int IN2 = 12;
// Pines motor B
const int ENB = 6;
const int IN3 = 11;
const int IN4 = 10;

// Pines canales receptor RC
const int CH1 = 2;  // Izquierda/Derecha
const int CH3 = 4;  // Adelante/Atrás
#define DIRECTION CH1
#define THROTTLE  CH3

// Rango de valores que determina cada tipo de
// movimiento para cada canal
// Adelante / Atras / Centro
#define MAX_THROTTLE 1900
#define MIN_THROTTLE 1075
#define ZERO_THROTTLE_MIN 1485
#define ZERO_THROTTLE_MAX 1560
// Izquierda / Derecha / Centro
#define MIN_DIRECTION 1061
#define MAX_DIRECTION 1893
#define ZERO_DIRECTION_MIN 1475
#define ZERO_DIRECTION_MAX 1490

// El tipo (uint16_t) garantiza que siempre tendrá 2 bytes y sin signo. 
// Permite representar valores entre 0..65536. Se usa en este caso
// en lugar de (int) porque este último depende de cada placa. En Arduino
// es de 16 bits, mientras que en otras es de 32 bits.
uint16_t powerM1 = 0;       // Valores posibles: 0 .. 255
uint16_t powerM2 = 0;       // Valores posibles: 0 .. 255
uint16_t scaleLeft = 100;   // Valores posibles: 0 .. 100
uint16_t scaleRight = 100;  // Valores posibles: 0 .. 100
bool backwards = false;

//---------------------------------------------------------
// SETUP    SETUP    SETUP    SETUP    SETUP    SETUP
//---------------------------------------------------------
void setup() {
  // Todos los pines del L298N son SALIDAS
  pinMode (ENA, OUTPUT);
  pinMode (IN1, OUTPUT);
  pinMode (IN2, OUTPUT);
  pinMode (ENB, OUTPUT);
  pinMode (IN3, OUTPUT);
  pinMode (IN4, OUTPUT);

  // Los pines por los que recibimos la información de cada canal de
  // la emisora son ENTRADAS
  pinMode (CH1, INPUT);
  pinMode (CH3, INPUT);

  Serial.begin (9600);
}

//---------------------------------------------------------
// LOOP    LOOP    LOOP    LOOP    LOOP    LOOP    LOOP
//---------------------------------------------------------
void loop() {
  int rc_direction = readRC (DIRECTION);
  int rc_throttle = readRC (THROTTLE);

  moveMotors (rc_direction, rc_throttle);
  delay (100);
}

//---------------------------------------------------------
// moveMotors    moveMotors    moveMotors    moveMotors    
//---------------------------------------------------------
void moveMotors (int rc_direction, int rc_throttle) {
  // Si los valores recibidos por los dos canales son 0 muy probablemnte
  // hemos perdido contacto con la emisora. ¡Paramos motores!
  if (!rc_direction && !rc_throttle) {
    powerM1 = 0;
    powerM2 = 0;
  }

  // Si el stick que controla la velocidad está en la zona central
  // paramos motores.
  else if (rc_throttle > ZERO_THROTTLE_MIN && rc_throttle < ZERO_THROTTLE_MAX) {
    powerM1 = 0;
    powerM2 = 0;
  }

  // Si el stick está entre ZERO_THROTTLE_MAX y MAX_THROTTLE significa que hemos
  // subido la palanca.
  // En este caso establecemos la potencia de ENA y ENB entre 80 y 255 y
  // los motores deben girar hacia adelante.
  else if (rc_throttle > ZERO_THROTTLE_MAX) {
    powerM1 = map(rc_throttle,
                  ZERO_THROTTLE_MAX, MAX_THROTTLE,
                  80, 250);
    powerM2 = map(rc_throttle,
                  ZERO_THROTTLE_MAX, MAX_THROTTLE,
                  80, 250);

    backwards = false;
  }

  // Si es stick está entre ZERO_THROTTLE_MIN y MIN_THROTTLE significa que hemos
  // bajado la palanca.
  // En este caso establecemos la potencia de ENA y ENB entre 80 y 255 y
  // los motores deben girar hacia atrás.
  else if (rc_throttle < ZERO_THROTTLE_MIN && rc_throttle > 0) {
    powerM1 = map(rc_throttle,
                  ZERO_THROTTLE_MIN, MIN_THROTTLE,
                  80, 250);
    powerM2 = map(rc_throttle,
                  ZERO_THROTTLE_MIN, MIN_THROTTLE,
                  80, 250);

    backwards = true;
  }

  // Si el stick que controla dirección está en la zona central queremos ir recto.
  if (rc_direction > ZERO_DIRECTION_MIN && rc_direction < ZERO_DIRECTION_MAX) {
    scaleLeft  = 100;
    scaleRight = 100;
  }

  else if (rc_direction < ZERO_DIRECTION_MIN && rc_direction > 0) {
    scaleLeft = map (rc_direction,
                     MIN_DIRECTION, ZERO_DIRECTION_MIN,
                     1, 99);
  }

  else if (rc_direction > ZERO_DIRECTION_MAX) {
    scaleRight = map (rc_direction,
                      ZERO_DIRECTION_MAX, MAX_DIRECTION,
                      99, 1);
  }

  // Re-escalamos powerM1 y powerM2 segun la posicion del stick izquierda/derecha
  // powerM1 es de tipo (uint16_t), al multiplicar por scaleLeft se sale de rango 
  // en cuanto supera 65536, es decir, casi siempre.
  // Por eso hacemos el cálculo intermedio con unsigned long, que permite representar
  // valores de hasta 4294967295.
  unsigned long scaledPowerM1 = powerM1 * scaleLeft;
  unsigned long scaledPowerM2 = powerM2 * scaleRight;  
  
  powerM1 = scaledPowerM1 / 100;
  powerM2 = scaledPowerM2 / 100;

  // Lo siguiente es un pequeño truco para mostrar salida por el monitor Serial
  // con menos líneas. No es importante
  char buff[80];
  sprintf (buff, "thr: %d, %s, scL: %d, scR: %d, ENA: %d, ENB: %d",
           rc_throttle,
           backwards ? "BWD" : "FWD",
           scaleLeft, scaleRight,
           powerM1, powerM2);
  Serial.println (buff);

  // Una vez tenemos los valores definitivos de potencia para M1 y M2 establecemos
  // las potencias de estos mediante analogWrite().
  analogWrite (ENA, powerM1);
  analogWrite (ENB, powerM2);

  // Finalmente mandamos la combinación IN1,IN2,IN3,IN4 para mover los motores en
  // un sentido u otro dependiendo del sentido de giro deseado.
  if (backwards) {
    digitalWrite (IN1, 1);
    digitalWrite (IN2, 0);
    digitalWrite (IN3, 1);
    digitalWrite (IN4, 0);
  }
  else {
    digitalWrite (IN1, 0);
    digitalWrite (IN2, 1);
    digitalWrite (IN3, 0);
    digitalWrite (IN4, 1);
  }
}

//---------------------------------------------------------
// readRC    readRC    readRC    readRC    readRC    
//---------------------------------------------------------
// Utiliza pulseIn() para leer la señal del canal indicado.
unsigned long readRC (int channel) {
  return pulseIn(channel, HIGH, 50000);
}

Explicación del código

En primer lugar configuramos los diferentes pines como Entradas o Salidas. Los pines correspondientes al L298N son todos SALIDAS, mientras que los pines para recibir las señales de la emisora serán ENTRADAS.

const int ENA = 9;
const int IN1 = 13;
const int IN2 = 12;
// Pines motor B
const int ENB = 6;
const int IN3 = 11;
const int IN4 = 10;

// Pines canales receptor RC
const int CH1 = 2;  // Izquierda/Derecha
const int CH3 = 4;  // Adelante/Atrás

Se ha añadido un par de #defines para poder usar nombres más fáciles de recordar en código como DIRECTION en lugar de tener que acordarme si corresonde al canal 1.

#define DIRECTION CH1
#define THROTTLE  CH3

A continuación definimos unas constantes para los rangos de cada palanca. Podríamos poner los números directamente en el código, pero estas constantes tienen dos ventajas. Por un lado hacen el código más legible y, por otro, si queremos ajustar los rangos no tenemos que buscar los valores por el código. Simplemente cambiándolos una vez en esta sección el cambio se propaga en todo el código allá donde se usen.

// Rango de valores que determina cada tipo de
// movimiento para cada canal
// Adelante / Atras / Centro
#define MAX_THROTTLE 1900
#define MIN_THROTTLE 1075
#define ZERO_THROTTLE_MIN 1485
#define ZERO_THROTTLE_MAX 1560
// Izquierda / Derecha / Centro
#define MIN_DIRECTION 1061
#define MAX_DIRECTION 1893
#define ZERO_DIRECTION_MIN 1475
#define ZERO_DIRECTION_MAX 1490

Necesitamos variables para poder calcular las potencias a aplicar a cada motor y el factor de escala. Estas variables las hemos definido con tipo uint16_t. Este tipo de datos es parecido al int que ya conocemos. Recordemos que el tipo de datos int permite representar números de tipo entero (basicamente números sin decimales, positivos y negativos). El inconveniente del tipo int es que el tamaño que ocupa depende del microcontrolador usado. Para placas Arduino UNO y Nano este es de 16 bits. Mientras que para placas como la ESP32 este es de 32 bits.

También definimos una variable de tipo booleana para saber si hemos de realizar un movimiento hacia adelante o hacia atrás.

// El tipo (uint16_t) garantiza que siempre tendrá 2 bytes y sin signo. 
// Permite representar valores entre 0..65535. Se usa en este caso
// en lugar de (int) porque este último depende de cada placa. En Arduino
// es de 16 bits, mientras que en otras es de 32 bits.
uint16_t powerM1 = 0;       // Valores posibles: 0 .. 255
uint16_t powerM2 = 0;       // Valores posibles: 0 .. 255
uint16_t scaleLeft = 100;   // Valores posibles: 0 .. 100
uint16_t scaleRight = 100;  // Valores posibles: 0 .. 100
bool backwards = false;

En la función setup() configuramos los pines correspondientes. Además de habilitar el puerto Serie para poder mostrar el estado de las diferentes variables. Esto será muy útil si el robot no se comporta como debería.

  // Todos los pines del L298N son SALIDAS
  pinMode (ENA, OUTPUT);
  pinMode (IN1, OUTPUT);
  pinMode (IN2, OUTPUT);
  pinMode (ENB, OUTPUT);
  pinMode (IN3, OUTPUT);
  pinMode (IN4, OUTPUT);

  // Los pines por los que recibimos la información de cada canal de
  // la emisora son ENTRADAS
  pinMode (CH1, INPUT);
  pinMode (CH3, INPUT);

  Serial.begin (9600);

La función loop() es muy corta. Usamos un par de funciones que hemos definido más abajo. La primera, readRC() lee la señal de la emisora. La usamos para leer la señal del canal de potencia y del de dirección.

Usamos los valores que llegan de la emisora para llamar a la segunda función propia -moveMotors()-. Esta función realiza los cálculos pertinentes a partir del estado de las palancas, y aplica la potencia necesaria a los motores y giro para realizar el movimiento deseado.

  int rc_direction = readRC (DIRECTION);
  int rc_throttle = readRC (THROTTLE);

  moveMotors (rc_direction, rc_throttle);
  delay (100);

Veamos ahora la función más importante: moveMotors(). En primer lugar comprobamos si hemos perdido la señal con la emisora. De ser así establecemos las potencias de los motores a cero (¡no queremos que el robot salga despedido sin control si apagamos la emisora!)

  // Si los valores recibidos por los dos canales son 0 muy probablemnte
  // hemos perdido contacto con la emisora. ¡Paramos motores!
  if (!rc_direction && !rc_throttle) {
    powerM1 = 0;
    powerM2 = 0;
  }

Si recibimos valores de la emisora empezamos comprobando el estado de la palanca de potencia. Primero comprobamos si está en posición neutra (el centro). Si es así, la potencia de los motores también ha de ser cero en ambos.

  // Si el stick que controla la velocidad está en la zona central
  // paramos motores.
  else if (rc_throttle > ZERO_THROTTLE_MIN && rc_throttle < ZERO_THROTTLE_MAX) {
    powerM1 = 0;
    powerM2 = 0;
  }

Si no está en la zona central, puede que la hayamos movido hacia arriba o hacia abajo. Esto es lo que se comprueba a continuación. Independientemente de si se mueve la palanca hacia arriba o hacia abajo lo que querremos será acelerar progresivamente los dos motores por igual, hasta llegar a la máxima potencia cuando la palanca esté en su extremo superior o inferior.

Para realizar esto usamos la instrucción map() de Arduino para convertir el valor recibido por el canal a un valor adecuado para la instrucción analogWrite().

Ponemos a true la variable backwards si bajamos la palanca, indicando que deberemos establecer el giro de los motores en sentido inverso. En caso contrario, lo establecemos a false.

  // Si el stick está entre ZERO_THROTTLE_MAX y MAX_THROTTLE significa que hemos
  // subido la palanca.
  // En este caso establecemos la potencia de ENA y ENB entre 80 y 255 y
  // los motores deben girar hacia adelante.
  else if (rc_throttle > ZERO_THROTTLE_MAX) {
    powerM1 = map(rc_throttle,
                  ZERO_THROTTLE_MAX, MAX_THROTTLE,
                  80, 250);
    powerM2 = map(rc_throttle,
                  ZERO_THROTTLE_MAX, MAX_THROTTLE,
                  80, 250);

    backwards = false;
  }

  // Si es stick está entre ZERO_THROTTLE_MIN y MIN_THROTTLE significa que hemos
  // bajado la palanca.
  // En este caso establecemos la potencia de ENA y ENB entre 80 y 255 y
  // los motores deben girar hacia atrás.
  else if (rc_throttle < ZERO_THROTTLE_MIN && rc_throttle > 0) {
    powerM1 = map(rc_throttle,
                  ZERO_THROTTLE_MIN, MIN_THROTTLE,
                  80, 250);
    powerM2 = map(rc_throttle,
                  ZERO_THROTTLE_MIN, MIN_THROTTLE,
                  80, 250);

    backwards = true;
  }

Una vez determinado el estado de la palanca de throttle miramos la palanca de dirección para establecer los factores de escala para cada motor. Esta también puede estar en tres zonas:

  • Zona central: Los factores de escala se mantienen al 100%, con lo que ambos motores mantendrán la misma velocidad y el robot avanza en línea recta.
  • Palanca hacia la izquierda: El factor de escala para el motor izquierdo debe disminuir en proporción al movimiento de la palanca. Nuevamente se usa la instrucción map() para convertir el valor recibido por el canal a un valor adecuado entre 1..99. Este valor representa el porcentaje a aplicar a la potencia del motor izquierdo, cuanto más movemos la palanca a la izquierda, menor es el valor, con lo que el giro se cerrará más hacia la izquierda.
  • Palanca hacia la derecha: El factor de escala para el motor derecho debe disminuir aplicando el mismo principio anterior.

  // Si el stick que controla dirección está en la zona central queremos ir recto.
  if (rc_direction > ZERO_DIRECTION_MIN && rc_direction < ZERO_DIRECTION_MAX) {
    scaleLeft  = 100;
    scaleRight = 100;
  }

  else if (rc_direction < ZERO_DIRECTION_MIN && rc_direction > 0) {
    scaleLeft = map (rc_direction,
                     MIN_DIRECTION, ZERO_DIRECTION_MIN,
                     1, 99);
  }

  else if (rc_direction > ZERO_DIRECTION_MAX) {
    scaleRight = map (rc_direction,
                      ZERO_DIRECTION_MAX, MAX_DIRECTION,
                      99, 1);
  }

Usamos los factores de escala como un porcentaje a aplicar a cada motor. Aplicar un porcentaje significa multiplicar por este y dividir entre 100.

Pero hay un pequeño problema si hacemos esta operación directamente sobre las variables powerM1 y powerM2. Estas variables son de 16 bits si programamos en microcontroladores basados en ATMEL 328P -el que lleva Arduino UNO y Arduino Nano-. El máximo valor que se puede representar con un entero de 16 bits sin signo es 65535. Eso significa que si un cálculo produce un resultado mayor, se "le da la vuelta" y no da el resultado correcto.

Para solucionar esto realizamos el cálculo intermedio de multiplicar por 100 usando una variable de tipo unsigned long. Este tipo de variable garantiza que el cálculo producirá el resultado correcto.

  // Re-escalamos powerM1 y powerM2 segun la posicion del stick izquierda/derecha
  // powerM1 es de tipo (uint16_t), al multiplicar por scaleLeft se sale de rango 
  // en cuanto supera 65536, es decir, casi siempre.
  // Por eso hacemos el cálculo intermedio con unsigned long, que permite representar
  // valores de hasta 4294967295.
  unsigned long scaledPowerM1 = powerM1 * scaleLeft;
  unsigned long scaledPowerM2 = powerM2 * scaleRight;  
  
  powerM1 = scaledPowerM1 / 100;
  powerM2 = scaledPowerM2 / 100;

Ahora ya tenemos todas las variables correctamente calculadas para aplicar las potencias a los motores. Usamos analogWrite() con los valores calculados de powerM1 y powerM2 sobre ENAy ENB para establecer la velocidad de los motores.

Finalmente, gracias a la variable backwards sabemos si hemos de girar en un sentido u otro. Dependiendo del sentido realizamos una combinación de IN1,IN2,IN3,IN4 u otra para establecer el giro en sentido deseado.



  // Una vez tenemos los valores definitivos de potencia para M1 y M2 establecemos
  // las potencias de estos mediante analogWrite().
  analogWrite (ENA, powerM1);
  analogWrite (ENB, powerM2);

  // Finalmente mandamos la combinación IN1,IN2,IN3,IN4 para mover los motores en
  // un sentido u otro dependiendo del sentido de giro deseado.
  if (backwards) {
    digitalWrite (IN1, 1);
    digitalWrite (IN2, 0);
    digitalWrite (IN3, 1);
    digitalWrite (IN4, 0);
  }
  else {
    digitalWrite (IN1, 0);
    digitalWrite (IN2, 1);
    digitalWrite (IN3, 0);
    digitalWrite (IN4, 1);
  }

Sólo nos queda analizar la función readRC(). Simplemente usa la función de Arduino pulseIn() para devolver el valor obtenido de la emisora por el canal especificado.

// Utiliza pulseIn() para leer la señal del canal indicado.
unsigned long readRC (int channel) {
  return pulseIn(channel, HIGH, 50000);
}