Función de retardo en la programación ARM

Acabo de empezar a programar en ARM, tenía algo de experiencia en AVR pero no tanta. La pregunta es probablemente demasiado trivial, pero el material sobre ARM es muy poco en la red... lo siento de todos modos.

Sé que para implementar retrasos en ARM podemos usar temporizadores o bucles ocupados. Probé esos métodos. Pero uno de mis amigos sugiere que hay un archivo de cabecera "delay.h" para arm que contiene todas las funciones de retardo necesarias como avr, delay_ms y ... .

En primer lugar, por estúpida que parezca esta pregunta, ¿realmente existe una función de retardo incorporada en la programación ARM? ¿Está en retraso.h?

Si es así, ¿por qué es tan popular el uso de temporizadores para implementar retrasos? ¿Por qué no usan simplemente la función incorporada?

Estoy estudiando la serie LPC17xx.

Piense en una CPU ARM como una CPU completa que podría usar para un sistema operativo como Linux. Ahora imagine lo que sucedería si bloqueara la ejecución en todo el chip cada vez que quisiera la funcionalidad de retardo de tiempo. Todo se bloquearía durante el retraso. Es por eso que se prefieren los temporizadores.
@Polynomial por favor convierta a respuesta. +1
@ChrisK Lo hará :)
¿En qué plataforma estás trabajando? ¿"Folleto NXP" o algo así? Algunas de las familias de la junta tendrán foros y una comunidad para ayudar a las personas que tengan preguntas. En el código chipKIT, he visto un for()recuento de bucles hasta 100 y el comentario decía que era un retraso de 10 usec.
Parece que está confundiendo las características del procesador con las características de una cadena de herramientas o biblioteca determinada, que es donde puede encontrar un archivo de encabezado. En términos del propio procesador, la mayoría de las variantes ARM actuales tienen una hora del sistema que se puede habilitar y usar para ejecutar temporizadores de eventos de software, pero su uso es opcional.

Respuestas (4)

Muchos compiladores y bibliotecas admiten la familia ARM. No perdería el tiempo buscando una solución única definida en un solo nombre de archivo. Ciertamente, esa no es "la" solución para "ARM".

Al igual que con AVR y la mayoría de los otros procesadores, sin duda los microcontroladores, tiene la opción de bloquear en un bucle de primer plano, ya sea utilizando un bucle calibrado o sondeando un temporizador. También puede usar una interrupción basada en temporizador si es necesario. Depende de para qué necesita realizar un retraso y qué más podría estar sucediendo durante ese retraso. Dependiendo del chip específico y la cantidad de tiempo que desee retrasar, es posible que pueda poner el chip en reposo durante ese período de tiempo.

Si está utilizando una biblioteca para otras cosas como SPI, I2C, etc. para el procesador de destino y la cadena de herramientas, es probable que también tenga rutinas de temporizador y pueda usarlas. Si está lanzando el suyo propio (el más portátil pero también el que más trabajo), mire los temporizadores, a la larga será más fácil mantener el código que se basa en un temporizador en lugar de en un bucle calibrado (contar hasta N) .

Hay una serie de cadenas de herramientas y bibliotecas adaptadas a la familia NXP LPC en particular si ese es el camino que desea tomar.

De ninguna manera, forma o forma. ¿Hay una solución única a la que debería limitarse? Ni para ARM ni para AVR, ni para ninguno de ellos.

A veces, el código se ejecutará en un contexto en el que no sucede nada más. Si el procesador establece un bit de E/S, luego ejecuta un montón de instrucciones que tardan un total de 1 ms en ejecutarse y luego borra el bit de E/S, entonces el cable controlado por ese bit pulsará alto durante 1 ms. Los métodos de retardo simples pueden ser útiles en tales contextos. Sin embargo, son mucho menos útiles en contextos en los que pueden estar sucediendo otras cosas. Si mientras se ejecutaba el retraso antes mencionado, ocurría una interrupción que requería 100 us para el servicio, el pin se pulsaría alto durante 1,1 ms en lugar de 1 ms. Peor aún, si se intenta utilizar dicho retraso dentro de una rutina de servicio de interrupción, el código de la línea principal y todas las interrupciones de menor prioridad se suspenderán hasta que se complete dicho retraso.

En general, el enfoque de implementación antes mencionado es apropiado cuando (1) uno está escribiendo algo como un cargador de arranque que debería poder operar sin interrupciones, y no sucederá nada más mientras se está ejecutando; (2) uno necesita un retraso lo suficientemente corto, no tiene mucho sentido tratar de encontrar otra cosa que hacer. Por ejemplo, si uno está usando un procesador de 32 MHz para comunicarse con un dispositivo que requiere 1,6 us de tiempo de inactividad entre bytes de datos, insertar un código que gaste 52 ciclos sin hacer nada significaría que el ARM podría estar bien posicionado para enviar el siguiente byte alrededor de 1,6 nosotros más tarde. Por el contrario, si el ARM intenta encontrar otra cosa que hacer, probablemente estará ocupado haciéndolo al final del 1.6us y no enviará el siguiente byte hasta algún tiempo después.

Piense en una CPU ARM como una CPU completa que podría usar para un sistema operativo como Linux. Ahora imagine lo que sucedería si bloqueara la ejecución en todo el chip cada vez que quisiera la funcionalidad de retardo de tiempo. Todo se bloquearía durante el retraso, lo que daría lugar a una experiencia completamente inutilizable para cualquier tipo de sistema interactivo con el usuario. Es por eso que se prefieren los temporizadores.

Los temporizadores son útiles porque evitan la situación de bloqueo. La CPU continúa ejecutando el código y vuelve a una rutina de controlador una vez que transcurre el tiempo. Esto ofrece una forma de operación asíncrona que permite un código mucho más flexible.

En arquitecturas que no admiten directamente temporizadores internos, pero admiten interrupciones externas, se puede utilizar un dispositivo de temporización externo. El temporizador se programa con una compensación de tiempo (generalmente un escalar y un multiplicador) y un número de interrupción. Cuando el temporizador externo activa la interrupción, la CPU lee una tabla de interrupciones para encontrar el vector de interrupción , que es una dirección lineal a la que se transfiere la ejecución para manejar el evento del temporizador.

Muchas gracias por la linda explicación. Aún así, ¿podemos usar funciones como delay_ms en ARM también? Tan ineficientes como son, ¿existen en la sintaxis oficial?
Depende de lo que entiendas por "sintaxis oficial". ARM es una CPU con un conjunto de instrucciones. No sé qué bibliotecas está ejecutando en él, y la única experiencia que tengo con ARM es con el ensamblaje sin formato. La forma en que lo haría es a través del coprocesador de control del sistema (CP15), a través del registro de conteo de ciclos . Esencialmente, usted configura el Registro de control del monitor de rendimiento (PMCR) para hacer que el CCR marque una escala de 64 ciclos, luego haga un ciclo hasta que pase una cierta cantidad de garrapatas, según la frecuencia de reloj del procesador.
(continuación del último comentario) Entonces, si la frecuencia de su reloj es de 40 MHz y el CCR marca cada 64 ciclos, son 625,000 marcas por segundo. Eso le da una resolución de 1,6 microsegundos y un retraso máximo de 3.435,97 segundos (dado que CCR es un registro firmado de 32 bits).
Las CPU ARM cubren una amplia gama de aplicaciones, desde dispositivos diminutos que compiten directamente con micros de 8 bits hasta dispositivos de gama alta apropiados para computadoras portátiles livianas y clústeres de servidores de bajo consumo. Y además de eso, tanto la espera ocupada como los retrasos basados ​​en el temporizador tienen sus roles, incluso en sistemas grandes; por ejemplo, cuando el núcleo de un sistema operativo necesita esperar una pequeña cantidad de tiempo para el hardware, puede esperar ocupado en lugar de usar un temporizador que no tiene una granularidad lo suficientemente fina.
Lo que dijo Chris: "ARM" es un concepto demasiado amplio como para darnos una pista útil de lo que estás haciendo. ¿Está utilizando un Cortex-M0 que funciona a 24 MHz? ¿O un A15 con cuatro núcleos a 1,8 GHz cada uno? ¿Qué más está pasando en este sistema? ¿Está utilizando un sistema operativo o simplemente golpeando el metal? La respuesta a esas preguntas cambiará en gran medida la respuesta recomendada a su pregunta.
-1 porque, como dijeron Chris y Jon, muchas cosas son "CPU en toda regla". Podría argumentar que incluso un pequeño PIC de 6 pines con ~ 200 bytes de memoria de programa es eso (defina "CPU" en este contexto). La espera ocupada tiene su lugar en todas las arquitecturas de CPU, aunque casi exclusivamente en la programación completa. Sin embargo, la gran mayoría de los dispositivos ARM en el mercado ejecutan código completo, por lo que si se debe hacer alguna suposición, es que el OP pregunta al respecto .

Por supuesto, es posible usar un procesador ARM para hacer un retraso simple. Ver mi código a continuación. Sin embargo, dependiendo de qué más necesite que haga su procesador, puede que no sea la mejor solución. Este código se ejecuta en un procesador de la serie LPC2000 de NXP.

/**********************************************************************/
/* Timer.c                                                            */
/* Implements a delay :                                               */
/*   TMR0 is a mcrosecond timer capable of timing events of upto      */
/*   4294.967295 seconds (1 hour 11 min 36.967295 sec)                */
/*   includes both delay and stop watch functions                     */
/*       void Delay(unsigned int delay); delay in microseconds        */
/*   Does not use interupts.  Note timer may rollover during delay    */
/*   so code checks for this                                          */
/*   Note: delay is minimum delay as interupts can slow it down       */
/**********************************************************************/
/*
 * Note: Assumes TMR0 is already setup with 1 microsecond tick
 */

#include <LPC2103.h>

#include "Timer.h"

void Delay(unsigned int delay){
    unsigned int start;
    unsigned int stop;
    unsigned int now;

    start = T0TC;
    stop = start + delay + 1;           /* +1 because call could arrive just
                                       before TOTC changes */
    if (stop > start){                /* usually is */
        do{
            now = T0TC;
        }while (now < stop && now >= start);  /* Catch timer roll-over as done */
    }else{                                    /* Need timer to roll over */
        do{
            now = T0TC;
        }while (now < stop || now >= start);  
    }
}

Incluso si está feliz de sentarse en un bucle esperando un retraso, un temporizador puede ser mejor, ya que es más difícil estimar los tiempos de retraso con un bucle simple como

for(i=1000; i >0; i--){
    ;
}

Porque un buen compilador de optimización puede simplemente optimizar esto.

Incluso si no se optimiza, es difícil saber exactamente cuánto tiempo tomará este retraso porque los procesadores de la serie NXP LPC tienen aceleradores de memoria y el procesador tiene canalización.

Solo para que conste: para que el bucle funcione con la optimización activada, debe estarfor(volatile int i=1000; i >0; i--) ;