Arduino delay [tutorial]

Оглавление

Arduino millis() Timer 0 code operation

There are two variables used in the correction and a few macro
definitions:

clockCyclesPerMicrosecond gives a
result of 16 for a 16MHz clock.

Two other macros are:

clockCyclesToMicroseconds(a) = (
(a) / clockCyclesPerMicrosecond )

microsecondsToClockCycles(a) = (
(a) * clockCyclesPerMicrosecond )

Within wiring.c the following definitions are made:

MICROSECONDS_PER_TIMER0_OVERFLOW =    clockCyclesToMicroseconds( 64 * 256) = 16384 / 16 = 1024

Since clockCyclesPerMicrosecond is 16

and

Timer 0 is prescaled by 64 and overflows after 256 prescaled clocks so it
triggers an interrupt after 64 * 256 clocks cycles.

Which just shows it is a long winded way of doing a simple calculation that
does have the advantage of being generic so it means for different clocks a
different result will occur. However the code comments state:

// 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.)

So the comments state that the The fractional timings calculation (below)
work for 8 and 8Mhz and 16MHz.

The following calculations are for a 16MHz clock.

MILLIS_INC = (
MICROSECONDS_PER_TIMER0_OVERFLOW / 1000 ) = 1.024

The above when used later inserts 1.024 into the code at the point of use
which is in fact when an unsigned long is incremented so that value would be
incremented by one.

Comments in wiring.c ():

// 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.)

FRACT_INC = (
MICROSECONDS_PER_TIMER0_OVERFLOW % 1000 >> 3 )    = 24 >> 3 = 3 (16MHz)

FRACT_MAX = ( 1000 >>3 )    = 125

The reason stated for doing the above right shifts is so that the numbers
fit into a byte.

Three variables are used in the correction and output of the millis value
(timer0_millis — below).

unsigned long
timer0_overflow_count — only used in microseconds
calculation.

unsigned long timer0_millis — the
value output by millis().

Byte timer0_fract

Every time in the interrupt:

timer0_millis is increased by
MILLIS_INC (or by 1) — this is the millis() output value.

timer0_fract is increased by
FRACT_INC (or by 3).

timer0_overflow_count is increased by one — this is
the unadjusted Timer0 interrupt count.

If necessary a correction is made to timer0_millis
when the accumulated error gets too big.

Ардуино задержка включения / выключения

Для этого занятия нам потребуется:

плата Arduino Uno / Arduino Nano / Arduino Mega.

В этой записи мы рассмотрим только основные характеристики функций задержки, а примеры использования представим в виде небольших скетчей. Для работы вам потребуется только сама плата Ардуино. Начнем обзор с delayMicroseconds Arduino, т.к. данную функцию не часто можно встретить в программах, а также рассмотрим, как заменить задержку delay на millis в программировании Arduino IDE.

Ардуино delayMicroseconds()

Команда delayMicroseconds останавливает выполнение программы на заданное количество микросекунд (в 1 секунде 1 000 000 микросекунд). При необходимости задержки в программе более чем на несколько тысяч микросекунд рекомендуется использовать delay(). Продемонстрируем на простом примере использование функции в скетче для мигания встроенным светодиодом на плате Arduino.

// пример использования delayMicroseconds() для мигания светодиодом
void setup() {
   pinMode(13, OUTPUT);
}
 
void loop() {
   digitalWrite(13, HIGH);      // подаем сигнал HIGH на выход
   delayMicroseconds(100);  // задержка 100 микросекунд
   digitalWrite(13, LOW);       // подаем сигнал LOW на выход
   delayMicroseconds(100);  // задержка 100 микросекунд
}

Ардуино delay()

Команда delay останавливает выполнение программы на заданное количество миллисекунд (в 1 секунде 1 000 миллисекунд). Во время задержки программы с помощью функции delay(), не могут быть считаны подключенные к плате датчики или произведены другие операции, например, запись в еепром Ардуино данных. В качестве альтернативы следует использовать функцию millis(). Смотри пример далее.

// пример использования delay() для мигания светодиодом
void setup() {
   pinMode(13, OUTPUT);
}
 
void loop() {
   digitalWrite(13, HIGH);   // подаем сигнал HIGH на выход
   delay(100);                        // задержка 100 миллисекунд
   digitalWrite(13, LOW);    // подаем сигнал LOW на выход
   delay(100);                        // задержка 100 миллисекунд
}

Ардуино millis()

Команда millis возвращает количество прошедших миллисекунд с момента начала выполнения программы. Счетчик времени сбрасывается на ноль при переполнении значения unsigned long (приблизительно через 50 дней). Функция miilis позволяет сделать многозадачность Ардуино, так как выполнение программы не останавливается и можно выполнять параллельно другие операции в скетче.

// пример использования millis() при мигании светодиодом
unsigned long timer;

void setup() {
   pinMode(13, OUTPUT);
   Serial.begin(9600);         // запускаем монитор порта
}
 
void loop() {
   timer = millis();                 // запускаем отсчет времени

   digitalWrite(13, HIGH);   // подаем сигнал HIGH на выход
   delay(1000);                      // задержка 1 секунда
   digitalWrite(13, LOW);    // подаем сигнал LOW на выход
   delay(1000);                      // задержка 1 секунда

   // выводим количество миллисекунд прошедших с момента начала программы
   Serial.print("Time: ");
   Serial.println(timer);
}

Arduino команды millis, delay, delaymicroseconds

Don’t Get Blocked

Using millis() admittedly takes a little bit of extra work when compared to using delay(). But trust me, your programs will thank you for it, and you can’t do multitasking on the Arduino without it.

If you want to see an example of millis() used in a real-world Arduino project, check out James Bruce’s Arduino Night Light and Sunrise Alarm.

Found any other blocking functions we should be wary of? Let me know in the comments below, and we’ll chat.

NordVPN vs. IPVanish: Which Is the Best VPN For You?

Looking for the right VPN for you? We compare IPVanish and NordVPNs’ server locations, pricing, geo-restrictions, and more.

Read Next

About The Author

Matthew Hughes
(385 Articles Published)

Matthew Hughes is a software developer and writer from Liverpool, England. He is seldom found without a cup of strong black coffee in his hand and absolutely adores his Macbook Pro and his camera.
You can read his blog at http://www.matthewhughes.co.uk and follow him on twitter at @matthewhughes.

More
From Matthew Hughes

Мигаем светодиодом без delay()

или всегда ли официальный примеру учат «хорошему».

Обычно это одна из первых проблем с которой сталкивается навичок в микроконтроллерх. Помигал диодом, запустил стандартный скетч blink(), но как только он него возникает желание что-бы контроллер «занимался чем-то еще» его ждет неприятный сюрприз — тут нет многопоточности. Как только он написали где-то что-то типа delay(1000) — обнаруживается что на это строчке «контроллер остановился» и ничего больше не делает (кнопки не опрашивает, датчики не читает, вторым диодом «помигать» не может).

Новичок лезет с этим вопросом на форум и тут же получает ушат поучений: «отказывайтесь от delay()», учитесь работать с millis() и в прочитайте, в конце концов базовые примеры. В частности Мигаем светодиодом без delay()

Приведу его код:

/* Blink without Delay
 2005
 by David A. Mellis
 modified 8 Feb 2010
 by Paul Stoffregen
 */

const int ledPin =  13;      // номер выхода, подключенного к светодиоду
// Variables will change:
int ledState = LOW;             // этой переменной устанавливаем состояние светодиода 
long previousMillis = 0;        // храним время последнего переключения светодиода

long interval = 1000;           // интервал между включение/выключением светодиода (1 секунда)

void setup() {
  // задаем режим выхода для порта, подключенного к светодиоду
  pinMode(ledPin, OUTPUT);      
}

void loop()
{
  // здесь будет код, который будет работать постоянно
  // и который не должен останавливаться на время между переключениями свето
  unsigned long currentMillis = millis();
 
  //проверяем не прошел ли нужный интервал, если прошел то
  if(currentMillis - previousMillis > interval) {
    // сохраняем время последнего переключения
    previousMillis = currentMillis;  

    // если светодиод не горит, то зажигаем, и наоборот
    if (ledState == LOW)
      ledState = HIGH;
    else
      ledState = LOW;

    // устанавливаем состояния выхода, чтобы включить или выключить светодиод
    digitalWrite(ledPin, ledState);
  }
}
  

В принципе отправка к этому примеру — вполне правильна. В нем действительно виден стандартный паттерн как нужно выполнять какое-то переодическое действие (или отложенное):

1. Сохраняем время в какую-то переменную
2. В loop() все время смотрим на разницу «текущие-время — сохраненное»
3. Когда эта разница превысила какое-то значение (нужный нам «интервал переодичности»)

4. Выполняем какое-то действие (меняем состояние диода, заново запоминаем «стартовое время и т.п.»)
С задачей «объяснить идею» — пример справляется. Но, с моей точки зрения, в нем есть несколько методологических ошибок. Написание скетчек в подобно стиле — рано или поздно приведет к проблемам.
Итак, что же тут не так?

1. Не экономно выбираем тип переменных

Переменная ledPin у нас объявлена как тип int. Зачем?  Разве номер пина может быть очень большим числом? Или может быть отрицательным числом? Зачем под его хранение выделять два байта, когда вполне хватит одного. Тип byte может хранить числа от 0 до 255. Для номера пина — этого вполне хватит.

  const byte ledPIN = 13; //  номер выхода, подключенного к светодиоду

Этого будет вполне достаточно.

2. А зачем нам переменная для малых чисел?

А зачем нам тут вообще переменая? (пусть и объявленная как const). Зачем тратить такты процессора на чтение переменной? И расходовать еще один байт?  Воспользуемся директивой препроцессора #define

#define LED_PIN  13 //  номер выхода, подключенного к светодиоду

Тогда еще на этапе компиляции компилятор просто подставить 13-ть везде где в коде используется LED_PIN и не будет выделять отдельных переменных.

3. Тип int опять выбрали как «первое что в голову пришло»?

И опять спотыкаемся на объявлении следующей же переменной. Почему ledState опять int? Кроме того что снова «два байта там где можно один использовать», так еще и «смысловая нагрузка» теряется. Что у нас хранится в переменной? Состояние светодиода. Включен/выключен. Горит/Не горит. Явно же напрашивается тип boolean. По крайней мере до тех пор, пока светодиод у нас может принимать два состояния.

Функции счёта времени

Данные функции возвращают время, прошедшее с момента запуска микроконтроллера. Таких функций у нас две:

  • millis() – Возвращает количество миллисекунд, прошедших с запуска. Возвращает unsigned int, от 1 до 4 294 967 295 миллисекунд (

50 суток), имеет разрешение 1 миллисекунда, после переполнения сбрасывается в 0. Работает на системном таймере Timer 0micros() – Возвращает количество микросекунд, прошедших с запуска. Возвращает unsigned int, от 4 до 4 294 967 295 микросекунд (

70 минут), имеет разрешение в 4 микросекунды, после переполнения сбрасывается в 0. Работает на системном таймере Timer 0

Вы спросите, а как время со старта МК поможет нам организовать действия по времени? Очень просто, схема вот такая:

  • Выполнили действие
  • Запомнили текущее время со старта МК (в отдельную переменную)
  • Ищем разницу между текущим временем и запомненным
  • Как только разница больше нужного нам времени “Таймера” – выполняем действие и так по кругу

Реализация такого “таймера на millis()” выглядит вот так:

Напомню, что uint32_t это второе название типа данных unsigned long, просто оно короче в записи. Почему переменная должна быть именно такого типа? Потому что функция millis() возвращает именно этот тип данных, т.е. если мы сделаем нашу переменную например типа int, то она переполнится через 32.7 секунды. Но миллис тоже ограничен числом 4 294 967 295, и при переполнении тоже сбросится в 0. Сделает он это через 4 294 967 295 / 1000 / 60 / 60 / 24 = 49.7 суток. Значит ли это, что наш таймер “сломается” через 50 суток? Нет, данная конструкция спокойно переживает переход через 0 и работает дальше, не верьте диванным экспертам, проверьте =)

Вернёмся к вопросу многозадачности: хотим выполнять одно действие два раза в секунду, второе – три, и третье – 10. Нам понадобится 3 переменные таймера и 3 конструкции с условием:

И вот так мы можем например 10 раз в секунду опросить датчик, фильтровать значения, и два раза в секунду выводить показания на дисплей. И три раза в секунду мигать лампочкой. Почему нет?

Конструкция громоздкая, но используется очень часто, у меня в крупном проекте может быть десяток таких таймеров. Поэтому для ускорения написания кода я сделал библиотечку GyverTimer.

Loop Code operation : For Non Blocking delays

The value of oldtime is set to a value of millis() at the start. if the
value of millis(), which is increasing every millisecond, is greater than 500
counts above oldtime then the conditional expression in the if statement
becomes true. This means 500 milliseconds have past since the value of oldtime
was set i.e. a delay of 500ms.

Within the body of the if-statement the LED is toggled from its previous
state. This means the LED is flashing at a rate of 1Hz (500ms on and 500ms off)
or once a second.

Since millis() is only tested by the conditional if statement, you can add
other code within the loop to do other useful work i.e. using Arduino millis()
in this way does not stop the processor.

TIP: it is vital that you set oldtime within the if-statement
to the current millis() value so that the next delay period can be performed
otherwise the if-statement expression would always be true after the first time
the expression became true.

Note: The type uint32_t is the same type as
«unsigned long». uint32_t is used in embedded programming as
it directly specifies the number of bits within the type, whereas «unsigned
long» may have a different number of bits for different compilers.

Arduino millis limit

So how long can you measure (and why would you care?). The function millis() returns a
magic number that appears out of the depths of Arduino code but as an engineer
you need to know what it is and how it is created.  You need to know because
all systems have limits which trip you up and make your system fail.

The maximum time that can be measured depends on the type of variable used
to store the millis() data which is an unsigned long and using this type allows
you to measure just over 49 days. If your project will never be on for longer
than 49 days then you don’t have a problem.

For the Arduino the max value from millis() is :

4,294,967,295 or (0xffffffff)

This is because the Arduino millis data type is :

unsigned long (which can also be written as
uint32_t)…

…in which you can more easily see the number of bits in the type.

Why do you need delays in your Arduino programs?

Well, an Arduino program relies a lot on external inputs/outputs to work.

Taking a simple example: you might want to monitor the state of a push button 100 times per second, or make a LED blink every 0.5 second.

Now, how can you achieve that? An Arduino programs works like this:

  • First, the function is executed once.
  • Then the function is executed over and over again, until you power off your Arduino.

Any program that you write will be executed at full speed. So if you’re making an LED blink in your code, what’s going to happen is that the LED will blink at a very high rate (multiple thousands times per second at least).

You are learning how to use Arduino to build your own projects?

Check out Arduino For Beginners and learn step by step.

Get this course for FREE for 14 days! Just click on the link above.

If you want to control time – that is, make sure the LED blinks only every 0.5 second and not at full speed – you need to add some delay in your code.

Interrupts

So far, we’ve learned about one way to approach timing in our Arduino program which is better than delay(). But there’s another, much better way, but more complex: interrupts. These have the advantage of allowing you to precisely time your Arduino program, and respond quickly to an external input, but in an asynchronous manner.

That means that it runs in conjunction with the main program, constantly waiting for an event to occur, without interrupting the flow of your code. This helps you efficiently respond to events, without impacting the performance of the Arduino processor.

When an interrupt is triggered, it either stops the program, or calls a function, commonly known as an Interrupt Handler or an Interrupt Service Routine. Once this has been concluded, the program then goes back to what it was going.

The AVR chip powering the Arduino only supports hardware interrupts. These occur when an input pin goes from high to low, or when triggered by the Arduino’s built-in timers.

It sounds cryptic. Confusing, even. But it isn’t. To see how they work, and see some examples of them being used in the real world, hit the Arduino documentation.

Подготовка Arduino IDE и прошивка

  1. Загружаем и устанавливаем Arduino IDE.
  2. Распаковываем скачанное в папку с библиотеками Arduino IDE (обычно это C:\Users\<Текущий пользователь>\Documents\Arduino\).
  3. Копируем полученный код в Arduino IDE.
  4. В примере вводим фактическое название нашей WiFi-сети и пароль.

В примере также присутствует функция вида:

BLYNK_WRITE(V1)
{
  int pinValue = param.asInt(); // assigning incoming value from pin V1 to a variable

  // process received value
}

Здесь:

  • BLYNK_WRITE(V1) указывает, что функция выполнится при изменении виртуального пина 1,
  • int pinValue = param.asInt(); объявляет переменную pinValue и загружает в неё текущее состояние виртуального пина (0 — пин выключен, 1 — пин включен).

Всего можно использовать 256 виртуальных пинов (V0-V255) — это огромный запас на все случаи жизни. Например, виртуальный пин 1 принимает значение 1 — подаём питание на физический пин NodeMcu и включаем этим реле, или виртуальный пин 2 принимает значение от 0 до 255 — изменяем ШИМ-сигнал, регулируем яркость (либо цвета RGB) диодной подсветки в 255 градациях яркости.

А теперь заставим нашу функцию включать физический пин D4 NodeMcu (в функции будем использовать событие виртуального пина 0, просто для удобства):

BLYNK_WRITE(V0)
{
  int pinValue = param.asInt();
  digitalWrite('''D4''', pinValue);
}

Чтобы управлять этим выводом, в основной функции void setup() обязательно установим пин D4 как выход: pinMode(D4, OUTPUT);

В итоге получаем такой код для прошивки:

#define BLYNK_PRINT Serial
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>

char auth[] = "Мой токен"; //тут токен из e-mail

char ssid[] = "YourNetworkName"; //Название WiFi-сети
char pass[] = "YourPassword"; //Пароль

BLYNK_WRITE(V0) //функция, отслеживающая изменение виртуального пина 0
{
  int pinValue = param.asInt(); //переменная текущего состояния виртуального пина
  digitalWrite(D4, pinValue); //задаем значение на физическом пине NodeMcu D4 равное значению виртуального пина 0
}

void setup() //основная функция, выполняется один раз при подаче питания на микроконтроллер
{
  Serial.begin(9600); //открываем серийный порт, чтобы видеть как проходит подключение к серверу blynk
  pinMode(D4, OUTPUT); //объявляем D4 "выходным" пином
  Blynk.begin(auth, ssid, pass); //авторизируемся на сервере
}

void loop() //основная функция, которая выполняется постоянно по кругу
{
  Blynk.run(); //запускаем работу blynk. В этом примере - постоянную проверку виртуального пина 0
}

Заливаем прошивку в NodeMcu.

 Так как пример предполагает использование различных плат, то пропустим тонкости настройки Arduino IDE для работы с NodeMcu; к тому же подобную информацию найти нетрудно. 

Наш выключатель готов к работе!

Также данную схему можно использовать и для включения ПК (имитации нажатия кнопки включения). Для этого параллельно пинам кнопки Power (на материнской плате пины POWER SW) нужно подключить линии L и L1 (указанные на схеме !!!НЕ 220В!!!) и немного изменить скрипт, чтобы при изменении виртуального пина реле включалось на короткий промежуток времени, имитируя короткое нажатие.

В скрипте, в блоке:

BLYNK_WRITE(V0) //функция, отслеживающая изменение виртуального пина 0
{
  int pinValue = param.asInt(); //переменная текущего состояния виртуального пина
  if (pinValue = 0){        
     digitalWrite(D4, HIGH); //если работает неверно, то изменить на digitalWrite(D4, LOW); а ниже наоборот
     delay (100); // если задержка мала, можно увеличить
     digitalWrite(D4, LOW); //ДА-ДА, ИМЕННО ТУТ НИЖЕ. Если работает неверно, то изменить на digitalWrite(D4, HIGH);
  }
}

мы добавили задержку delay (100); в 100 мс и выключили после нее физический пин D4 — digitalWrite(D4, LOW);

When to use Arduino millis() vs micros()

First of all, the functionality is the same: both millis() and micros() are keeping the time since the Arduino program started.

If your program requires executing actions with a resolution higher than one millisecond, then use micros(). If not, just use millis().

The drawback you get when using micros() is that the time variable overflows much quicker than the millis() variable (about 71 minutes instead of 49 days). It might be a problem, unless you only use the time functionalities to compare previous and current time, like we did above in this post.

All in all, the 2 things to keep in mind are the time resolution, and the duration before an overflow. Knowing that, you should have no more problem when writing an Arduino program using time functionalities!

FAQ

Всё верно. Разработчики хотят иметь прибыль от своего полезного и качественного продукта.

Но для использования виртуальных пинов через вебхуки элементы управления не требуются, т.е. приложение можно использовать только для получения токена и бесплатно.

Можно только по Wi-Fi?

Нет. Подключение может любым: Wi-Fi, Ethernet, и даже COM-порт через USB компьютера. Всё зависит от целей, задач и вида микроконтроллера.

Почему не использовать готовые решения?

10. Именно такая цифра — делитель стоимости подобных «готовых» устройств (в некоторых случаях меньше или больше, но какой смысл переплачивать, если с равными усилиями получается дешевле и веселее?)

Что еще можно сделать по этой технологии?

А что угодно!

  • Управляемую подсветку (меняющую цвет, яркость, режимы работы);
  • Сбор информации с самых разных датчиков (температура, влажность, давление);
  • Умные выключатели и розетки;
  • ИК-пульт (наподобие Яндекс.Пульта);
  • Управление компьютером через Алису;
  • Голосовое управление детскими игрушками;
  • Сигнализацию с постановкой на охрану голосом (и проверкой статуса).

А считывать состояния Blynk умеет?

Да, ссылка на запрос состояния выглядит так:

https://blynk-cloud.com/<ваш_токен>/get/V0

Так же можно считывать и состояния датчиков, но это тема уже совсем другой статьи 🙂

Насколько это надежно? (Китай долго не проработает…)

Ардуино (в том числе китайские версии) прошли в массах не одну проверку температурой, влажностью и временем. Лично на моем опыте собранные устройства функционируют по сей день в течение 3 лет. Что называется, слепил — и забыл.

Кроме того, модульные сборки имеют преимущество перед «коробочными» в том, что любой модуль, датчик, реле и т.п. можно заменить (при этом недорого!) без вреда для всей схемы. Их намного легче размещать в ограниченном пространстве (в кастомных корпусах, или вообще без корпуса).

А главный плюс — любое устройство в любой момент можно свободно развивать по функционалу в зависимости от приходящих в голову идей — бесценен!

Tight Loops

First let’s discuss the concept of a tight loop. And when we say a tight loop, what does that mean?

Let’s take a look at an Arduino sketch for a demonstration of a tight loop. Starting with the most basic sketch, we’ve only got two functions: void setup, and void loop. And as you may know, void setup only runs once, and then it hands the show over to void loop.

Void loop then goes through every line of code that might be inside the loop (inside these curly brackets).

It executes the first line, then it executes the second, and then the third, and so on and so forth, until it gets to the bottom. And then it goes back to the top.

How fast does it execute the loop? It depends on which Arduino board you’re using, but an Arduino Uno has a clock speed of 16 megahertz. So that means that 16 million instructions are happening every second on the Arduino!

Each line of code isn’t necessarily one instruction. In fact, it’s most likely that it’s multiple instructions. But still, that’s relatively fast (your computer processor is likely running at Gigahertz speeds… that’s billions).

So would you consider that empty sketch a tight loop? Definitely, that’s as fast and as tight as you can make a loop. Since there’s nothing inside of the loop to execute, the time it takes to go thru the sketch is practically zilch. Said another way, the interval from the start of the loop to the finish is short (therefore it is fast, or “tight”).

Let’s add some lines of code. We will start serial communication, and then print something to the serial monitor window.

Is this a tight loop? That is, from the start of the loop to the end of the loop, does that take a lot of time? It takes very little time, so that’s a fast, tight loop.

It’s worth noting, however, that this loop is not as tight as the previous example. In the previous example, we had no code. So it was just racing through the loop. Now that we have a function here, serial print, it will take (a tiny) bit of time to print “Ice Ice Baby” to serial monitor.

But this is still a pretty quick loop. So let’s add a little bit more code. We’ll have the program check to see if a button is pressed, and if it is, we’ll have something new sent to the serial monitor

So we have declared a button and used an if statement to check and see if the button has been pressed. Is the voltage at pin five high? If so, then we print something else to the serial monitor window.

Is this a tight loop? So, from the start of the loop, to the end of the loop, is that pretty quick?

Yes, it’s still quite quick. This is a pretty tight loop. We’ve got four lines of code. We’re printing to the serial monitor, and then we’re doing a quick check to see if a button is being pressed. And if it is, we print something out. Still tight, still fast.

Next let’s add a delay to this program using the Arduino delay() function. You can see below we’ve added a thousand millisecond (1 second) delay to the loop.

Is this still a tight loop? Is the time, from the start of the loop to the end of the loop, a lot of time? No, this is definitely not a tight loop. The code starts fast, we do the serial print, but then we get halted right there at the delay function.

The whole program comes to a standstill while we wait for this delay code to finish.

When the Arduino gets to this line of code, it’s kind of like going to the grocery store. You get into the 12 items or less line, but then the guy in front of you pulls out his checkbook, and starts writing a check. You know you’re gonna be there for a minute. It’s the same deal here.

So this is not a tight loop. The time from the start of the loop to the end of the loop is pretty significant. Especially compared to the last couple programs. The order of magnitude of time is huge.

Now what we’ve tried to demonstrate here is that this whole idea about tight loops is relative. It all depends on your application. If you need to check the status of a sensor every 10 millionth of a second, then a program that has three lines of code may not be tight enough, it just depends.

Another point to make is that not all lines of code take the same amount of time to execute. If you’re calling a function that does a bunch of stuff, like serial print for example, then that one line of code may take a whole lot longer than 10 other lines of code.

So the tightness of a loop is a relative idea.

Резюме

Платформа Arduino предоставляет нам несколько способов выполнения задержки в своем проекте. С помощью delay вы можете быстро поставить на паузу выполнение скетча, но при этом заблокируете работу микроконтроллера. Использование команды millis позволяет обойтись в ардуино без delay, но для этого потребуется чуть больше программировать. Выбирайте лучший способ в зависимости от сложности вашего проекта. Как правило, в простых скетчах и при задержке меньше 10 секунд используют delay. Если логика работы сложнее и требуется большая задержка, то вместо delay лучше использовать millis.

«>

Time resolution

You get a 1 millisecond resolution for the millis() function. Sounds obvious, right?

You are learning how to use Arduino to build your own projects?

Check out Arduino For Beginners and learn step by step.

Get this course for FREE for 14 days! Just click on the link above.

So, for the micros() function, you get a resolution of… one microsecond ? Wrong!

The resolution for micros() is 4 microseconds on all 16MHz Arduino boards: Uno, Mega, Nano, etc. For example, if you read the time with micros() and get 10000, then the next value you get is 10004, and after that 10008, and so on. You won’t be able to go down to multiples of 1, 2 or 3 microseconds.

But that’s not really a problem. 4 microseconds is already a good resolution, especially when considering that you’re using an Arduino, not a microcontroller designed for sending rockets in space.

So, in one second, you’ll get 1,000 values with millis(), and you’ll get 250,000 values with micros(), which corresponds to 1,000,000/4.

Note that the precision of the current time for millis() and micros() is the same. You should always get the same time given by the 2 functions, just with a different resolution.

Example #2: Basic Delay with for() loops

For our 2nd example, we are only going to delay for 1ms, but do so inside of a for() loop.

void setup() {
   pinMode(13, OUTPUT);
}

void loop() {
   digitalWrite(13, HIGH);   // set the LED on
   for (int x=0; x < 1000; x++) {     // Wait for 1 second
      delay(1);
   }
   digitalWrite(13, LOW);   // set the LED on
   for (int x=0; x < 1000; x++) {     // Wait for 1 second
      delay(1);
   }
}

This new sketch will accomplish the same sequence as Example #1. The difference is that the Arduino is only “delayed” for one millisecond at a time. A clever trick would be to call other functions inside of that for() loop. However, your timing will be off because those instructions will add additional delay.

Дополнительные функции ввода/вывода

Функция tone()

Описание

Генерирует на пине сигнал — прямоугольную волну заданной частоты. Можно указать продолжительность, иначе сигнал продолжается до вызова noTone(). К пину может быть подключен пьезо- или другой динамик для воспроизведения различных тонов.

Одновременно может генерироваться только один сингал. Если сигнал уже воспроизводится на одном пине, то вызов функции для другого пина ни к чему не приведет. Повторный вызов же функции для того же пина изменит частоту сигнала на новую.

При использовании функции tone() невозможно генерировать ШИМ-сигнал на 3 и 11 пинах всех плат, кроме Arduino Mega.

Невозможно генерировать сигнал с частотой ниже 31 Гц.

Синтаксис

tone(pin, frequency)

tone(pin, frequency, duration)

Параметры

pin — пин для генерации звука

frequency — частота тона в герцах

duration — длительность тона в миллисекундах (необязательно)

Возвращаемое значение

нет

Пример

Функция noTone()

Описание

Останавливает генерацию сигнала на пине. Если сигнал не генерируется, то никакого эффекта от вызова функции не будет.

Синтаксис

noTone(pin)

Параметры

pin — пин, на котором прекращается генерация сигнала

Возвращаемое значение

нет

Пример

Функция shiftIn()

Описание

Сдвигает байт данных по одному биту за один вызов, начиная с самого старшего (т.е. крайнего левого) или младшего (крайнего правого) бита. Для каждого бита тактовый вывод устанавливается на высокий уровень, следующий бит считывается из линии данных, а затем на тактовом выводе устанавливается низкий уровень.

Синтаксис

byte incoming = shiftIn(dataPin, clockPin, bitOrder)

Параметры

dataPin — пин для вывода данных

clockPin — пин для синхронизации

bitOrder — порядок сдвига: MSBFIRST или LSBFIRST (младший бит первым или старший)

Возвращаемое значение

Считанное значение

Пример

Для понимания работы функции, вместо примера приведу ее реализацию:

Примечания

Перед передачей не забыть сконфигурировать оба пина на тип работы OUTPUT.

Для более удобного и быстрого использования данного протокола, можно воспользоваться библиотекой SPI. Однако она поддерживает не все пины.

Функция shiftOut()

Описание

Побитово выводит байт информации на пин. Вывод может осуществляться как с первого (левого), так и с последнего (правого) бита. Каждый бит выводится по очереди, после чего на синхронизирующий пин подается сигнал, сигнализирующий о возможности считывать бит.

Данный способ передачи информации называется последовательной передачей с синхронизацией.

Синтаксис

shiftOut(dataPin, clockPin, bitOrder, value)

Параметры

dataPin — пин для вывода данных

clockPin — пин для синхронизации

bitOrder — порядок передачи: MSBFIRST или LSBFIRST (младший бит первым или старший)

value — данные для передачи (байт)

Возвращаемое значение

нет

Пример

Отправка последовательно от 0 до 255:

Примечания

Перед передачей не забыть сконфигурировать оба пина на тип работы OUTPUT.

Для более удобного и быстрого использования данного протокола, можно воспользоваться библиотекой SPI.. Однако она поддерживает не все пины.

Функция pulseIn()

Описание

Функция ждет появления импульса (HIGH или LOW) на заданном пине и после его завершения возвращает его длину или 0, если импульса не было на протяжении заданного времени (тайм-аута).

Работает с импульсами от 10 микросекунд до 3 минут.

Синтаксис

pulseIn(pin, value)

pulseIn(pin, value, timeout)

Параметры

pin — номер пина, на которой ожидается импульс

value — тип импульса для чтения: HIGH или LOW

timeout (необязательный параметр) — количество микросекунд ожидания начала импульса, по умолчанию одна секунда

Возвращаемое значение

Длительность импульса (в микросекундах) или 0, если до истечения времени (тайм-аута) не было ни одного импульса

Пример