Многозадачность в Arduino

В этом уроке

  • Что такое переменные. Типы переменных
  • Как обойтись без delay() и почему это необходимо
  • Параллельное выполнение нескольких задач
  • Системное время и как с ним работать
  • Условный оператор

Видео версия урока

Переменные

В жизни Вы часто сталкивались с переменными, когда работали с какой-либо формулой. Например, в формуле закона Ома I = U/R, U и R — это переменные, вместо которых мы подставляем числа, чтобы найти значение I. Точно таким же образом переменные можно использовать и в программировании.

Вместе с тем, очень часто возникает необходимость менять какие-либо значения. Включение-выключение светодиода — вот к примеру постоянно меняющееся значение. Время, прошедшее с момента начала выполнения программы, — ещё одно значение и т.д. Эти данные удобно записывать в переменные — своеобразные ящики для хранения информации.

Переменные имеют свой тип, который показывает, данные какого размера и с каким знаком  можно в них записать. Например, включенный/выключенный светодиод может иметь только два состояния и никак не больше, поэтому рациональнее будет использовать переменную типа bool, которая может хранить только два значения: 1 или 0 ( true или false). А вот в переменную со значением времени может быть записано очень большое число, и для его записи обычно пользуются типом unsigned long, который вмещает числа от 0 до 4294967295.

Для работы с переменной её необходимо сначала создать:

bool ledState;       //  Создаём переменную с именем ledState типа bool

Значение в переменной можно менять при необходимости, например:

ledState = 1;       //  Записываем в переменную ledState значение 1

И, разумеется, это значение можно считать:

bool ledState = 1;  //  Создаём переменную с именем ledState типа bool и сразу присваиваем ей значение 1
bool x;             //  Создаём переменную с именем x. Так как мы не указали значение, по умолчанию она равна 0
x = ledState;       //  Присваиваем переменной x значение переменной ledState
                    //  Теперь и ledState, и x равны 1

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

Ниже мы привели самые распространённые типы переменных. Они могут записываться с помощью нескольких вариантов, приведенных в первом столбце.

Тип переменнойВозможные значениеНазначение
bool
boolean
0, 1 (или false, true)Целочисленные значения
char
int8_t
-128...127 (или 'a'...'z')Целочисленные значения или символы
byte
uint8_t
0...255Беззнаковые целочисленные значения
int
int16_t
-32768...32767Целочисленные значения
unsigned int
uint16_t
0...65535Беззнаковые целочисленные значения
long
int32_t
-2147483648...2147483647Целочисленные значения
unsigned long
uint32_t
0...4294967295Беззнаковые целочисленные значения
float-2147483648,0...-2147483647,0Числа с плавающей точкой

Формально, можно всем переменным в программе присвоить тип long, но лучше так не делать, т.к. чем большее значение может вместить переменная, тем больше места она займёт в памяти контроллера. И даже если Вы записываете в переменную значение 0 или 1, а тип переменной — long, в памяти контроллера она займет столько же места, как если бы Вы записали в неё число, например, 1546834346. Поэтому лучше использовать типы переменных подходящего размера.

Важность применения принципа многозадачности

До этого момента для установки длительности какого-либо действия (время вращения  мотора или время его паузы, время звучания зуммера и т.д.) мы использовали функцию delay(). Однако, мы сразу сказали, что её крайне нежелательно использовать в настоящих проектах для управления модулями. 

Дело в том, что время, указанное в этой функции, контроллер расходует впустую — он не может выполнять никаких действий, он просто ждёт, пока это время закончится. А ведь он мог бы считывать показания датчиков, регулировать скорость двигателей и т.д. Поэтому сейчас мы рассмотрим принцип, позволяющий не останавливать работу контроллера.

Приведённый ниже скетч позволяет одновременно мигать светодиодом на плате и воспроизводить звук полицейской сирены с помощью зуммера. Зуммер, как и в предыдущих уроках, остаётся быть подключенным к 6 пину.

long timeLed;                       //  Переменная, хранящая время включения светодиода
long zumStart;                      //  Переменная, хранящая время последнего изменения частоты зуммера (для создания эффекта сирены)
bool ledState = 0;                  //  Состояние светодиода: 0 - выключен, 1 - включен
int i = 0;                          //  Начало отсчёта для увеличения частоты зуммера

void setup(){      
  pinMode (6, OUTPUT);              //  6 пин - выход. К нему подключен зуммер
  pinMode (13, OUTPUT);             //  13 пин - выход. К нему подключен светодиод (на плате)
}                                                
void loop(){                                    
  if (timeLed + 150 < millis()){    //  Если с момента последнего изменения состояния прошло более 150 мс
    ledState = !ledState;           //  Инвертируем (меняем на противоположное) состояние светодиода
    timeLed = millis();             //  Обновляем время смены состояния. В следующем цикле отсчёт будет идти уже от него
  }
  digitalWrite (13, ledState);      //  Записываем состояние (зажигаем или гасим светодиод).
  
  if (zumStart + 15 < millis()){    //  Если с момента последнего изменения тона прошло больше 15 миллисекунд
    zumStart = millis();            //  Обновляем время смены тона
    i++;                            //  Увеличиваем значение для пересчёта тона
    if (i >= 255) i = 0;            //  Если произошло переполнение, сбрасываем в 0
    int res = (sin((2*PI*i)/128)+1)*45+160; //  Формула для расчёта тона через синус
    tone(6, res*3);                 //  Генерируем звуковой сигнал на 6 выводе
  }
}   

Как реализуется многозадачность

Функция millis() — функция, возвращающая значение времени в миллисекундах, прошедшее с момента запуска программы. Если задать переменной значение времени, полученное от этой функции, а потом сравнивать его с текущим временем, то можно узнать, сколько времени прошло с момента задания этого времени в переменную. 

Например, инструкция timeLed = millis() (13 строка) записывает в переменную timeLed системное время момента, когда изменилось состояние светодиода (12 строка). Далее, ориентируясь на текущее системное время, мы можем сделать вывод о том, сколько времени прошло (11 строка). Если это время больше заданного значения ( в нашем случае это 150 мс), то состояние светодиода меняется.

Таким образом, можно организовать параллельное выполнение множества процессов. Посмотрите — одновременно со светодиодом мы управляем ещё и зуммером (17-23 строки). При использовании функции delay() сделать это было бы невозможно.

Также, как Вы уже заметили, мы используем условие: if ( УСЛОВИЕ ) { ДЕЙСТВИЕ }.
ДЕЙСТВИЕ выполнится в том случае, если будет истинно УСЛОВИЕ, записанное в скобках.

Ну что же, поздравляем!
Вы освоили основы программирования, выполнили множество упражнений и проектов. Не стесняйтесь возвращаться к предыдущим урокам, если что-нибудь забыли. А мы предлагаем двигаться дальше и переходить к следующей главе.
перейти к выбору главы

Продукт в магазине

«ROBORACE» — Образовательный набор на базе Arduino

В магазин

Обсуждение

Гарантии и возврат. Используя сайт, Вы соглашаетесь с условиями.