Acelerar Arduino

Para muchos de nosotros, comenzamos a programar en computadoras de escritorio y servidores, que parecían tener una memoria y un poder de procesamiento infinitos (bueno, dependiendo de cuándo comiences...

Introducción

Para muchos de nosotros, comenzamos a programar en computadoras de escritorio y servidores, que parecían tener memoria y capacidad de procesamiento infinitas (bueno, dependiendo de cuándo comenzaste a programar, supongo). Había pocas razones para optimizar su código ya que no era probable que excediera los límites del sistema de todos modos. Y luego, cuando ingresaste a los sistemas integrados, hubo un duro despertar. Pasar de un sistema tan poderoso a uno mucho más pequeño y menos capaz, como un arduino, fue un poco impactante. De repente, tenías que pensar en ahorrar ciclos de CPU y memoria, lo que no siempre es fácil para los programadores que recién comienzan.

Como verá a lo largo de este artículo, el código rápido no solo es importante para hacer cálculos, sino aún más para las operaciones de E/S. Si incursiona en la robótica (como drones u otros sistemas de control) esto quedará aún más claro ya que gran parte del trabajo realizado por el microcontrolador da como resultado IO. Por lo general, un ciclo de retroalimentación más rápido significaba un mejor rendimiento.

Después de algunas semanas de pelear con un microcontrolador para exprimir cada gramo de potencia de procesamiento posible para un controlador de vuelo de drones, pensé en escribir un artículo para ayudarlo a encontrar formas de mejorar la velocidad y la eficiencia de sus propios proyectos. .

A lo largo de este artículo, me centraré en Arduino Uno, ya que parece ser la placa más común que existe, aunque gran parte de este artículo también debería aplicarse a las otras placas.

Por qué los Arduinos son lentos

Velocidad del reloj

En primer lugar, solo eres tan rápido como tu reloj (sin tener en cuenta los procesadores multinúcleo), que [arduino uno] (https://www.arduino.cc/en/Main/ArduinoBoardUno) utiliza de forma predeterminada con un cristal de 16Mhz. Lo que eso significa es que el microcontrolador ATmega puede ejecutar hasta 16 millones de instrucciones por segundo. Ahora, 16 millones de instrucciones por segundo pueden parecer mucho (y lo es, más o menos), pero cuando consideras todo lo que un Arduino necesita hacer para ejecutar incluso operaciones simples, realmente no es tanto. Para muchos proyectos, los ciclos de reloj se comparten entre cosas como cálculos, comunicación I2C, lectura y escritura en pines y registros, y muchas más operaciones.

Incluso entonces, los comandos aparentemente simples pueden tomar bastantes ciclos de reloj, como configurar un pin digital en alto. Esta es una de las operaciones de E/S más sencillas que puede realizar en un Arduino, pero en realidad lleva mucho tiempo (¡más de 50 ciclos de reloj!) debido a la cantidad de código utilizado en el método digitalWrite(), que yo ll dirección en la siguiente sección. Entonces, un reloj más rápido le permitiría ejecutar las instrucciones a un ritmo más rápido.

Comprobaciones de seguridad y validación

Entonces, fuera de la velocidad del reloj, ¿por qué los Arduinos son lentos? Bueno, principalmente tiene que ver con algunas de las llamadas a métodos y objetos estándar que usamos a lo largo de nuestro código. Estos son solo algunos de los principales culpables:

  • escritura digital()
  • lectura digital()
  • modopin()

Muchos de estos métodos tienen los mismos inconvenientes, así que echemos un vistazo al código de uno de los métodos más utilizados, digitalWrite():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void digitalWrite(uint8_t pin, uint8_t val)
{
    uint8_t timer = digitalPinToTimer(pin);
    uint8_t bit = digitalPinToBitMask(pin);
    uint8_t port = digitalPinToPort(pin);
    volatile uint8_t *out;

    if (port == NOT_A_PIN) return;

    // If the pin that support PWM output, we need to turn it off
    // before doing a digital write.
    if (timer != NOT_ON_TIMER) turnOffPWM(timer);

    out = portOutputRegister(port);

    uint8_t oldSREG = SREG;
    cli();

    if (val == LOW) {
        *out &= ~bit;
    } else {
        *out |= bit;
    }

    SREG = oldSREG;
}

Como puede ver, hay bastante que hacer aquí. ¿Pero no debería ser mucho más simple? Todo lo que realmente necesitamos hacer es configurar el pin alto o bajo en un registro, ¿verdad? Resulta que los creadores de Arduino decidieron que era más importante agregar controles de seguridad y validación al código que hacerlo rápido. Después de todo, esta es una plataforma dirigida más a los principiantes y la educación que a los usuarios avanzados y las aplicaciones que hacen un uso intensivo de la CPU.

Las primeras líneas usan el parámetro pin para encontrar el correspondiente timer, bit y port para el pin dado. El puerto es en realidad solo un registro mapeado en memoria, que controla varios pines. Para activar o desactivar solo el pin que queremos, necesitamos determinar a qué bit del registro corresponde nuestro pin, que es lo que hace la función digitalPinToBitMask().

Una vez que hayamos encontrado el timer, bit y port, verificamos para asegurarnos de que sea un pin válido. Esta línea no es necesaria para que digitalWrite() haga su trabajo, pero actúa como una red de seguridad para los programadores más inexpertos (e incluso para los experimentados). Odiaríamos estar escribiendo en la ubicación de memoria incorrecta y corromper el programa.

La línea if (timer != NOT_ON_TIMER) ... está ahí para asegurarnos de que terminemos cualquier uso anterior de PWM del pin antes de escribir una "constante" alta o baja. Muchos de los pines en Arduinos también se pueden usar para la salida PWM, que requiere un temporizador para operar cronometrando los ciclos de trabajo. Si es necesario, esta línea apagará el PWM. De nuevo, para asegurarnos de que no vemos ningún comportamiento extraño, esta es una verificación de seguridad destinada a ayudar al usuario.

Y finalmente, en las últimas líneas estamos configurando el valor dado para el puerto dado.

Las comprobaciones de seguridad ralentizan un poco la ejecución, pero también facilitan mucho la depuración. De esta manera, cuando algo sale mal, es menos probable que tengas un comportamiento extraño que te deje rascándote la cabeza. No hay nada más frustrante que tener un código aparentemente lógico y no obtener el resultado esperado. Programar microcontroladores es muy diferente a programar aplicaciones de escritorio o de teléfono (aunque también tienen una buena cantidad de dificultades). Dado que está trabajando directamente con el hardware y no tiene un sistema operativo para mantenerse seguro, los problemas pueden ser difíciles de encontrar.

Si la velocidad no es su objetivo, entonces le recomiendo que continúe utilizando estos métodos proporcionados por Arduino. No tiene sentido exponerse a un riesgo innecesario si no le ayuda a alcanzar su objetivo final.

También debe saber que las llamadas a métodos no siempre son lentas debido a la cantidad de código que ejecuta, pero un factor que contribuye podría ser las limitaciones físicas del dispositivo. Por ejemplo, analogRead() tarda unos 100 microsegundos por llamada debido a la resolución que proporciona y al reloj que se le proporciona. Una resolución más baja de ADC disminuiría el tiempo que toma cada llamada. Sin embargo, incluso entonces, si bien el hardware es en última instancia el factor limitante aquí, el código Arduino establece de forma conservadora la frecuencia de muestreo máxima del ADC en solo 9600 Hz (aunque es capaz de alcanzar alrededor de 77 Khz). Entonces, si bien los Arduinos son mucho más lentos de lo que deberían ser, no siempre se debe a las opciones de diseño y las compensaciones. Hay una buena discusión sobre esto aquí, y documentación [aquí](http://www.atmel.com/Images/doc2559. pdf).

Cómo acelerar Arduino

Para ser claros, en realidad no estamos haciendo que Arduino sea más rápido, más bien, estamos haciendo que el código sea más eficiente. Señalo esta distinción porque el uso de estos trucos no nos dará un reloj más rápido (aunque podemos acelerar el reloj, que mencionaré más adelante), simplemente ejecutará menos código. Esta es una distinción importante porque tener un reloj más rápido nos brinda otros beneficios, como tener temporizadores más precisos, una comunicación más rápida, etc.

Además, tenga en cuenta que al usar el código a continuación, está haciendo algunas concesiones. Los programadores que desarrollaron Arduino no eran solo programadores pésimos que no podían escribir código rápido, sino que conscientemente tomaron la decisión de agregar validaciones y controles de seguridad a métodos como digitalWrite(), ya que beneficia a sus clientes objetivo. Solo asegúrese de comprender qué puede (y lo hará) salir mal con este tipo de compensaciones.

De todos modos, en el código.

Escritura digital

Ahora, no voy a mostrarte cómo acelerar cada método, pero muchos de los mismos conceptos de aquí se pueden aplicar a otros métodos como pinMode(). La cantidad mínima de código que necesita para escribir en un pin es:

1
2
#define CLR(x,y) (x&=(~(1<<y)))
#define SET(x,y) (x|=(1<<y))

SIP eso es.

Como puede ver, vamos directo al grano en estas macros. Para usarlos, tendrá que hacer referencia tanto al puerto como a la posición del bit directamente en lugar de usar convenientemente los números de pin. Por ejemplo, estaríamos usando la macro así:

1
SET(PORTB, 0);

Esto terminaría escribiendo un valor ALTO para el pin 8 en su Arduino Uno. Es un poco rudo, pero mucho más rápido. Esto también significa que somos más propensos a hacer algo mal, como hacer referencia a un puerto inexistente, escribir sobre un PWM activo o una serie de otras cosas.

La macro nos da un gran impulso, usando un [estimado](http://www.billporter.info/2010/08/18/ready-set-oscillate-the-fastest-way-to-change-arduino-pins /) 2 ciclos (frecuencia de 8Mhz), mientras que digitalWrite() utiliza la friolera de 56 ciclos (frecuencia de 285Khz).

Serie

Desafortunadamente, para algunas tareas, como si necesita usar la comunicación en serie, no hay mucho que pueda hacer para mejorar la velocidad, pero hay algunas optimizaciones que puede tener en cuenta.

La comunicación en serie se usa comúnmente para enviar información de depuración o estado al IDE de escritorio, lo que significa que probablemente tenga instrucciones Serial.println() en todo el código. Es fácil olvidarse de estas declaraciones después del desarrollo, así que si está buscando un aumento de velocidad y no necesita depurar más, intente eliminar todas las llamadas println() y eliminar Serial de el código por completo. Solo tenerlo inicializado (y ni siquiera usar Serial.println()) significa que está desperdiciando muchos ciclos en las interrupciones TX y RX. En Un caso, se midió que el solo hecho de tener habilitado Serial ralentizaba digitalWrite()s en alrededor de 18 % Son muchos ciclos desperdiciados debido al código inactivo.

Velocidad del reloj

Aunque me he centrado principalmente en las mejoras de software que puedes hacer, no olvides que siempre existe la mejora "simple" de acelerar el reloj. Lo digo de esta manera porque, después de todo, no es realmente una simple mejora de plug-and-play. Para acelerar el reloj en un Arduino, debe insertar un nuevo cristal en la placa, lo que puede o no ser difícil según sus habilidades de soldadura.

Una vez que haya instalado un nuevo oscilador de cristal, deberá actualizar el gestor de arranque para reflejar el cambio; de lo contrario, no podrá recibir el código a través del puerto serie. Y, por último, deberá cambiar el valor F_CPU a la velocidad de reloj adecuada. Si aumentó el reloj a 20Mhz (el reloj más rápido para el que está clasificado el ATmega), por ejemplo, deberá modificar algunos archivos en el IDE de Arduino:

  • En preferencias.txt, cambiar
    • from: build.f_cpu=16000000L
    • to: build.f_cpu=20000000L
  • En el makefile, cambiar
    • from: F_CPU = 16000000
    • to: F_CPU = 20000000

Según esta publicación, el ATmega328 se puede overclockear a 30Mhz, pero no lo recomiendo =)

Conclusión

Espero que haya encontrado algo en esta publicación que pueda aplicar fácilmente a sus proyectos, o al menos espero que lo anime a explorar el código fuente de Arduino para encontrar sus propias optimizaciones. El Arduino es un microcontrolador muy capaz, pero puede ser capaz de mucho más.

  • ¿Tiene alguna optimización propia que le gustaría compartir? ¡Cuéntanos en los comentarios!*

¡No olvides suscribirte a nuestra lista de correo para recibir nuestros mejores artículos en tu bandeja de entrada! e entrada!*