Skip to main content
LibreTexts - Ukrayinska

23.1: затримка ()

  • Page ID
    29591
  • \( \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}}\)

    або Як витрачати час

    Іноді нашому коду потрібно чекати речей або подій часу. Наприклад, ми можемо захотіти увімкнути світлодіод на кілька секунд, а потім вимкнути його. Ми бачили, як керувати світлодіодом за допомогою DigitalWrite (), але як ми чекаємо кілька секунд? Одним з простих методів є створення порожнього циклу. Це цикл, який дійсно нічого не робить, але витрачати час. Наприклад, якщо ми знаємо, що просто збільшення, тестування і розгалуження в циклі займає мікросекунди, ми могли б написати таку функцію:

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

    Зауважте, що ми вказуємо кількість мілісекунд, які ми хотіли б витратити. Оскільки кожна ітерація циклу займає одну мікросекунду, множимо на 1000 для досягнення мілісекунд. Тут важливий модифікатор летючих речовин. Це говорить компілятору не агресивно оптимізувати код для нас, оскільки я міг бути змінений кодом, запущеним в іншому місці (наприклад, в перериванні). В іншому випадку компілятор може зрозуміти, що він може досягти того ж кінцевого результату, ігноруючи цикл і зробивши просте додавання. Проблема цієї функції полягає в тому, що отримана затримка сильно залежить від використовуваного мікроконтролера і його тактової частоти. Якщо вам просто потрібна швидка і брудна затримка, це буде працювати нормально, але набагато більш точна затримка доступна з функцією delay () та її братом delayMicroseconds (), довідковий матеріал якого повторюється нижче.

    Малюнок\(\PageIndex{1}\): delay docs

    затримка (1)

    Опис

    Призупиняє програму на кількість часу (у мілісекундах), вказане як параметр. (Є 1000 мілісекунд в секунду.)

    Синтаксис

    затримка (мс)

    Параметри

    ms: кількість мілісекунд для паузи (непідписаний довгий)

    Повертає

    нічого

    Приклад

    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
    }
    

    застереження

    Хоча легко створити миготливий світлодіод з функцією delay (), а багато ескізів використовують короткі затримки для таких завдань, як debouncing switch, використання delay () в ескізі має суттєві недоліки. Ніяке інше зчитування датчиків, математичних розрахунків або маніпуляцій з контактами не може тривати під час функції затримки, тому насправді це призводить до зупинки більшості інших дій. Для альтернативних підходів до контролю часу див. Функцію millis () та ескіз, розміщений нижче. Більш обізнані програмісти зазвичай уникають використання delay () для часу подій довше 10 мілісекунд, якщо ескіз Arduino не дуже простий.

    Деякі речі продовжуються, поки функція delay () керує чіпом Atmega, однак, оскільки функція затримки не відключає переривання. Послідовний зв'язок, який з'являється на контакті RX записується, PWM (AnalogWrite) значення та стан контактів підтримуються, а переривання працюватимуть як слід.

    Див. також

    Мікросекунди затримки ()

    Опис

    Призупиняє програму на кількість часу (у мікросекундах), вказаний як параметр. Є тисяча мікросекунд в мілісекунді, а мільйон мікросекунд за секунду.

    В даний час найбільшим значенням, яке дасть точну затримку, є 16383. Це може змінитися в майбутніх випусках Arduino. Для затримок тривалістю більше декількох тисяч мікросекунд слід використовувати delay () замість цього.

    Синтаксис

    Мікросекунди затримки (нас)

    Параметри

    us: кількість мікросекунд для паузи (unsigned int)

    Повертає

    Жоден

    Застереження та відомі проблеми

    Ця функція працює дуже точно в діапазоні 3 мікросекунд і вище. Ми не можемо запевнити, що DelayMicroseconds буде виконувати саме для менших затримок часу.

    Станом на Arduino 0018, delayMicroseconds () більше не відключає переривання.

    Ці функції також пов'язані з двома іншими функціями, micros () та millis (), які повторюються нижче:

    Малюнок\(\PageIndex{2}\): millis and micros docs

    міліс (1)

    Опис

    Повертає кількість мілісекунд з моменту початку роботи поточної програми на платі Arduino. Це число буде переповнюватися (повернеться до нуля), приблизно через 50 днів.

    Параметри

    Жоден

    Повертає

    Кількість мілісекунд з моменту запуску програми (без підпису довго)

    Порада:

    Зверніть увагу, що параметр для millis є незнаковим довгим, помилки можуть генеруватися, якщо програміст намагається виконати математику з іншими типами даних, такими як int s

    мікрос ()

    Опис

    Повертає кількість мікросекунд з моменту початку роботи поточної програми на платі Arduino. Це число буде переповнюватися (повернеться до нуля), приблизно через 70 хвилин. На платах Arduino 16 МГц (наприклад, Duemilanove і Nano) ця функція має роздільну здатність чотирьох мікросекунд (тобто повернене значення завжди кратне чотирьом). На платах Arduino 8 МГц (наприклад, LilyPad) ця функція має роздільну здатність восьми мікросекунд.

    Параметри

    Жоден

    Повертає

    Кількість мікросекунд з моменту запуску програми (без підпису довго)

    Всі ці функції покладаються на систему Arduino, яка налаштовує таймери в момент скидання плати. Один з них буде використовуватися для генерації переривання при переповненні лічильника. Час на переповнення займе заданий проміжок часу, виходячи з тактової частоти. Переривання, в свою чергу, оновить три глобальні змінні, які будуть відстежувати, як довго програма працює.

    Спочатку розглянемо код ініціалізації разом з деякими визначеннями та оголошеннями глобальних змінних. Крім цього таймера, код ініціалізації також встановлює інші таймери для обов'язків широтно-імпульсної модуляції (через функцію AnalogWrite ()). Код досить добре прокоментований і представлений без подальших пояснень, за винятком нагадування про те, що sbi () - це макрос, який зведе до однієї інструкції мови асемблера, яка встановлює певний біт регістра.

    #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;
    }
    

    Тепер давайте розглянемо процедуру обслуговування переривань. Кожен раз, коли лічильник переповнюється (тобто 8-бітовий лічильник намагається збільшити 255 і обертається назад до 0) він генерує переривання, яке викликає цю функцію. В основному, все, що він робить, це збільшити глобальні змінні, оголошені раніше.

    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++;
    }
    

    Як ви можете здогадатися, всі функції millis () та micros () мають доступ до цих глобальних змінних і повертають їх значення. Оскільки під час цього процесу може статися переривання, значення реєстру стану (SREG) копіюється, біт увімкнення глобального переривання реєстру стану очищається викликом cli (), виконаним доступом (плюс трохи додаткового обчислення для micros ()) та статусом реєстр повернувся до попереднього стану. Потім отримане значення повертається абоненту.

    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());
    }
    

    Таким чином, функція затримки () сама по собі досить проста. Він просто витягує поточний час з моменту скидання, а потім переходить у цикл «зайнятого очікування», постійно перевіряючи та перевіряючи час, поки різниця між ними не досягне запитуваного значення.

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

    У певному сенсі, це лише трохи більш складна версія нашої початкової сирної функції затримки. Точніше, оскільки він використовує точні внутрішні лічильники, які працюють від відомої тактової частоти. Мікросекундна версія затримки трохи складніша, особливо для коротких затримок. Це також робить зайняте очікування, але робить це за допомогою вбудованого коду збірки. Навіть при цьому затримки не особливо точні протягом періодів всього в кілька мікросекунд. У рядкових коментарях повчальні:

    /* 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
        );
    }
    

    Основна проблема з використанням delay () відзначається в його он-лайн документації, а саме те, що під час циклу зайнятого очікування ніякої іншої роботи не може бути виконано. Контролер ефективно «крутить колеса». Більш ефективним способом затримки є безпосереднє використання функції millis (). Основна ідея полягає в тому, щоб перевірити час за допомогою millis (), а потім зробити те, що вам потрібно зробити всередині циклу, перевіряючи минулий час на кожній ітерації. Ось фрагмент прикладу коду.

    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();
    }
    

    По суті, ви побудували свій власний «вид» зайнятий цикл очікування, але з необхідним кодом всередині.


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