Saltar al contenido principal
LibreTexts Español

23.1: retraso ()

  • Page ID
    82302
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    o Cómo perder el tiempo

    A veces nuestro código necesita esperar cosas o eventos de tiempo. Por ejemplo, es posible que queramos encender un LED durante unos segundos y luego apagarlo. Hemos visto cómo controlar un LED con DigitalWrite () pero ¿cómo esperamos unos segundos? Un método sencillo es crear un bucle vacío. Este es un bucle que realmente no hace más que perder el tiempo. Por ejemplo, si sabemos que simplemente incrementar, probar y ramificar en un bucle toma un microsegundo, podríamos escribir una función como esta:

    void CheesyDelay( unsigned long msec )
    {
        volatile unsigned long i;
        unsigned long endmsec = msec * 1000;
    
        for( i=0; i<endmsec; i++ );
    }
    

    Tenga en cuenta que especificamos el número de milisegundos que nos gustaría desperdiciar. Dado que cada iteración del bucle toma un microsegundo, multiplicamos por 1000 para lograr milisegundos. El modificador volátil es importante aquí. Esto le dice al compilador que no optimice agresivamente el código para nosotros porque podría cambiarme si el código se ejecuta en otro lugar (por ejemplo, en una interrupción). De lo contrario, el compilador podría darse cuenta de que puede lograr el mismo resultado final ignorando el bucle y haciendo una simple adición. El problema con esta función es que el retardo resultante depende en gran medida del microcontrolador utilizado y su frecuencia de reloj. Si solo necesitas un retraso rápido y sucio esto funcionará bien, pero un retraso mucho más preciso está disponible con la función delay () y su hermano delayMicroseconds (), cuyo material de referencia se repite a continuación.

    Figura\(\PageIndex{1}\): delay docs

    delay () 1

    Descripción

    Pausa el programa por la cantidad de tiempo (en milisegundos) especificada como parámetro. (Hay 1000 milisegundos en un segundo.)

    Sintaxis

    retardo (ms)

    Parámetros

    ms: el número de milisegundos a pausar (sin signo largo)

    Devoluciones

    nada

    Ejemplo

    int ledPin = 13;                       // LED connected to digital pin 13
    
    void setup()
    {
        pinMode(ledPin, OUTPUT);           // sets the digital pin as output
    }
    
    void loop()
    {
        digitalWrite(ledPin, HIGH);        // sets the LED on
        delay(1000);                       // waits for a second
        digitalWrite(ledPin, LOW);         // sets the LED off
        delay(1000);                       // waits for a second
    }
    

    Advertencia

    Si bien es fácil crear un LED parpadeante con la función delay (), y muchos bocetos utilizan retardos cortos para tareas como el desrebote del interruptor, el uso de delay () en un boceto tiene inconvenientes importantes. Ninguna otra lectura de sensores, cálculos matemáticos o manipulación de pines puede continuar durante la función de retardo, por lo que en efecto, pone fin a la mayor parte de la actividad. Para enfoques alternativos para controlar la sincronización, consulte la función millis () y el boceto ubicado a continuación. Los programadores más conocedores suelen evitar el uso de delay () para la sincronización de eventos de más de 10's de milisegundos a menos que el boceto de Arduino sea muy simple.

    Sin embargo, ciertas cosas suceden mientras la función delay () está controlando el chip Atmega, porque la función de retardo no desactiva las interrupciones. Se registra la comunicación serial que aparece en el pin RX, se mantienen los valores PWM (AnalogWrite) y los estados del pin, y las interrupciones funcionarán como deberían.

    Ver también

    RetrasosMicrosegundos ()

    Descripción

    Pausa el programa por la cantidad de tiempo (en microsegundos) especificada como parámetro. Hay mil microsegundos en un milisegundo, y un millón de microsegundos en un segundo.

    Actualmente, el valor más grande que producirá un retraso preciso es 16383. Esto podría cambiar en futuras versiones de Arduino. Para demoras superiores a unos pocos miles de microsegundos, deberías usar delay () en su lugar.

    Sintaxis

    RetrasosMicrosegundos (us)

    Parámetros

    us: el número de microsegundos a pausar (unsigned int)

    Devoluciones

    Ninguno

    Advertencias y problemas conocidos

    Esta función funciona con mucha precisión en el rango de 3 microsegundos y más. No podemos asegurar que DelayMicroseconds funcionará precisamente para tiempos de retraso más pequeños.

    A partir de Arduino 0018, DelayMicroseconds () ya no desactiva las interrupciones.

    Estas funciones también están vinculadas con otras dos funciones, micros () y millis (), que se repiten a continuación:

    Figura\(\PageIndex{2}\): millis and micros docs

    milis ()

    Descripción

    Devuelve el número de milisegundos desde que la placa Arduino comenzó a ejecutar el programa actual. Este número se desbordará (volverá a cero), después de aproximadamente 50 días.

    Parámetros

    Ninguno

    Devoluciones

    Número de milisegundos desde que se inició el programa (largo sin signo)

    Consejo:

    Tenga en cuenta que el parámetro para millis es un largo sin signo, se pueden generar errores si un programador intenta hacer matemáticas con otros tipos de datos como int s

    micros ()

    Descripción

    Devuelve el número de microsegundos desde que la placa Arduino comenzó a ejecutar el programa actual. Este número se desbordará (volverá a cero), después de aproximadamente 70 minutos. En placas Arduino de 16 MHz (por ejemplo, Duemilanove y Nano), esta función tiene una resolución de cuatro microsegundos (es decir, el valor devuelto es siempre un múltiplo de cuatro). En placas Arduino de 8 MHz (por ejemplo, el LilyPad), esta función tiene una resolución de ocho microsegundos.

    Parámetros

    Ninguno

    Devoluciones

    Número de microsegundos desde que se inició el programa (largo sin signo)

    Todas estas funciones dependen del sistema Arduino configurando los temporizadores en el momento en que se restablece la placa. Uno de estos se utilizará para generar una interrupción cuando el contador se desborde. El tiempo de desbordamiento tomará una cantidad predeterminada de tiempo en función de la velocidad del reloj. La interrupción a su vez actualizará tres variables globales que harán un seguimiento de cuánto tiempo lleva funcionando el programa.

    Primero consideremos el código de inicialización junto con algunas definiciones y declaraciones de variables globales. Además de este temporizador, el código de inicio también configura los otros temporizadores para tareas de modulación de ancho de pulso (a través de la función AnalogWrite ()). El código está razonablemente bien comentado y se presenta sin más explicaciones, salvo para un recordatorio de que sbi () es una macro que se reducirá a una sola instrucción en lenguaje ensamblador que establece un bit de registro específico.

    #include "wiring_private.h"
    
    // the prescaler is set so that timer0 ticks every 64 clock cycles, and the
    // the overflow handler is called every 256 ticks.
    #define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))
    
    // the whole number of milliseconds per timer0 overflow
    #define MILLIS_INC (MICROSECONDS_PER_TIMER0_OVERFLOW / 1000)
    
    // the fractional number of milliseconds per timer0 overflow. we shift right
    // by three to fit these numbers into a byte. (for the clock speeds we care
    // about - 8 and 16 MHz - this doesn't lose precision.)
    #define FRACT_INC ((MICROSECONDS_PER_TIMER0_OVERFLOW % 1000) >> 3)
    #define FRACT_MAX (1000 >> 3)
    
    volatile unsigned long timer0_overflow_count = 0;
    volatile unsigned long timer0_millis = 0;
    static unsigned char timer0_fract = 0;
    
    void init()
    {
        // this needs to be called before setup() or some functions won't
        // work there
        sei();
    
        // set timer 0 prescale factor to 64
        sbi(TCCR0B, CS01);
        sbi(TCCR0B, CS00);
    
        // enable timer 0 overflow interrupt
        sbi(TIMSK0, TOIE0);
    
        // timers 1 and 2 are used for phase-correct hardware pwm
        // this is better for motors as it ensures an even waveform
        // note, however, that fast pwm mode can achieve a frequency of up
        // 8 MHz (with a 16 MHz clock) at 50% duty cycle
    
        TCCR1B = 0;
    
        // set timer 1 prescale factor to 64
        sbi(TCCR1B, CS11);
        sbi(TCCR1B, CS10);
        
        // put timer 1 in 8-bit phase correct pwm mode
        sbi(TCCR1A, WGM10);
    
        // set timer 2 prescale factor to 64
        sbi(TCCR2B, CS22);
        
        // configure timer 2 for phase correct pwm (8-bit)
        sbi(TCCR2A, WGM20);
    
        // set a2d prescale factor to 128
        // 16 MHz / 128 = 125 KHz, inside the desired 50-200 KHz range.
        // XXX: this will not work properly for other clock speeds, and
        // this code should use F_CPU to determine the prescale factor.
        sbi(ADCSRA, ADPS2);
        sbi(ADCSRA, ADPS1);
        sbi(ADCSRA, ADPS0);
    
        // enable a2d conversions
        sbi(ADCSRA, ADEN);
    
        // the bootloader connects pins 0 and 1 to the USART; disconnect
        // them here so they can be used as normal digital i/o;
        // they will be reconnected in Serial.begin()
        UCSR0B = 0;
    }
    

    Ahora echemos un vistazo a la rutina del servicio de interrupción. Cada vez que el contador se desborda (es decir, el contador de 8 bits intenta incrementar 255 y se vuelve a ajustar a 0) genera una interrupción que llama a esta función. Básicamente, todo lo que hace es incrementar las variables globales declaradas anteriormente.

    SIGNAL( TIMER0_OVF_vect )
    {
        // copy these to local variables so they can be stored in
        // registers (volatile vars are read from memory on every access)
        unsigned long m = timer0_millis;
        unsigned char f = timer0_fract;
    
        m += MILLIS_INC;
        f += FRACT_INC;
    
        if (f >= FRACT_MAX)
        {
            f -= FRACT_MAX;
            m += 1;
        }
    
        timer0_fract = f;
        timer0_millis = m;
        timer0_overflow_count++;
    }
    

    Como ahora puedes adivinar, todas las funciones millis () y micros () hacen es acceder a estas variables globales y devolver sus valores. Debido a que puede ocurrir una interrupción durante este proceso, se copia el valor del registro de estado (SREG), el bit de habilitación de interrupción global del registro de estado se borra con la llamada cli (), el acceso realizado (más un pequeño cálculo extra para micros ()) y el estado registro devuelto a su estado anterior. El valor recuperado se devuelve entonces a la persona que llama.

    unsigned long millis()
    {
        unsigned long m;
        uint8_t oldSREG = SREG;
    
        // disable interrupts while we read timer0_millis or we might get
        // an inconsistent value (e.g. in the middle of a write to
        // timer0_millis)
    
        cli();
        m = timer0_millis;
        SREG = oldSREG;
    
        return m;
    }
    
    unsigned long micros()
    {
        unsigned long m;
        uint8_t t, oldSREG = SREG;
        
        cli();
        m = timer0_overflow_count;
        t = TCNT0;
        if ((TIFR0 & _BV(TOV0)) && (t < 255)) m++;
        SREG = oldSREG;
        
        return ((m << 8) + t) * (64 / clockCyclesPerMicrosecond());
    }
    

    Entonces la función delay () en sí es bastante sencilla. Simplemente recupera la hora actual desde el reinicio y luego entra en un bucle de “espera ocupada”, verificando constantemente y volviendo a verificar el tiempo hasta que la diferencia entre los dos alcance el valor solicitado.

    void delay(unsigned long ms)
    {
        uint16_t start;
    
        start = (uint16_t)micros();
    
        while (ms > 0)
        {
            if (((uint16_t)micros() - start) >= 1000)
            {
                ms--;
                start += 1000;
            }
        }
    }
    

    En cierto modo, esta es solo una versión un poco más sofisticada de nuestra función inicial de retardo cursi. Es más preciso porque utiliza los contadores internos precisos que operan a partir de una frecuencia de reloj conocida. La versión de microsegundos del retraso es un poco más complicado, especialmente para retardos cortos. Esto también hace una espera ocupada pero lo hace usando el código de ensamblaje en línea. Incluso con esto, los retrasos no son particularmente precisos para periodos de solo unos pocos microsegundos. En los comentarios en línea son instructivos:

    /* Delay in microseconds. Assumes 8 or 16 MHz clock. */
    
    void delayMicroseconds(unsigned int us)
    {
        // for the 16 MHz clock on most Arduino boards
        // for a one-microsecond delay, simply return. the overhead
        // of the function call yields a delay of approximately 1 1/8 us.
        if (--us == 0)
            return;
    
        // the following loop takes a quarter of a microsecond (4 cycles)
        // per iteration, so execute it four times for each microsecond of
        // delay requested.
        us <<= 2;
    
        // account for the time taken in the preceding commands.
        us -= 2;
        
        // busy wait
        __asm__ __volatile__ (
            "1: sbiw %0,1" "\n\t" // 2 cycles
            "brne 1b" : "=w" (us) : "0" (us) // 2 cycles
        );
    }
    

    El principal problema con el uso de delay () se observa en su documentación en línea, es decir, que durante un bucle de espera ocupado no se puede hacer otro trabajo. El controlador está efectivamente “girando sus ruedas”. Una forma más efectiva de retrasar es hacer uso directo de la función millis (). La idea básica es verificar el tiempo usando millis () y luego hacer lo que necesites hacer dentro de un bucle, verificando el tiempo transcurrido en cada iteración. Aquí hay un fragmento de código de ejemplo.

    unsigned long currentMillis, previousMillis, intervalToWait;
    
    // intervalToWait could be a passed variable, global or define
    
    // initialize to current time
    previousMillis = millis();
    currentMillis = millis();
    
    while ( currentMillis - previousMillis < intervalToWait )
    {
        // do whatever you need to do here
    
        currentMillis = millis();
    }
    

    En esencia, has construido tu propio “tipo de” bucle de espera ocupado pero con el código requerido dentro.


    1. http://arduino.cc/en/Reference/Delay

    This page titled 23.1: retraso () is shared under a CC BY-NC-SA 4.0 license and was authored, remixed, and/or curated by James M. Fiore via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.