4.07. Псевдопараллелизм.md
До сих пор мы сталкивались с одной фундаментальной проблемой - пока микроконтроллер выполняет одну задачу, особенно если в ней есть задержка delay()
, весь остальной мир для него перестаёт существовать. Программа просто «зависает» на строчке delay(1000);
и не может в это время ни считать данные с датчика, ни проверить нажатие кнопки. Это происходит потому, что микроконтроллер на плате Рудирон
, как и большинство его собратьев, имеет одноядерный процессор. Он может выполнять только одну команду в один момент времени.
Но как же тогда старые компьютеры, тоже с одноядерными процессорами, умудрялись одновременно держать открытыми текстовый редактор, браузер и музыкальный плеер? Неужели они тоже были «однозадачными»? Ответ кроется в гениальном трюке под названием псевдопараллелизм или многозадачность с разделением времени.
Псевдопараллелизм
Идея псевдопараллелизма проста - вместо того чтобы выполнять одну задачу от начала и до конца, процессор очень быстро переключается между несколькими задачами. Он уделяет каждой задаче крошечный квант времени (несколько миллисекунд), а затем переходит к следующей. Для человека это переключение настолько быстрое, что создаётся полная иллюзия, будто все программы работают одновременно, или параллельно.
Представьте жонглёра, который подбрасывает в воздух несколько мячей. В любой момент времени его рука касается только одного мяча, но, быстро переключая внимание между ними, он удерживает в воздухе все. Микроконтроллер может стать таким же жонглёром для ваших задач.
Чтобы реализовать этот подход, нам нужно отказаться от delay()
и научиться управлять временем по-другому. Для этого в Рудироне
есть две волшебные функции: millis()
и micros()
.
Функции millis()
и micros()
Функции millis()
и micros()
работают как внутренний секундомер (или, скорее, хронометр) микроконтроллера, который запускается в момент включения платы.
millis()
возвращает количество миллисекунд (тысячных долей секунды), прошедших с момента старта программы.micros()
возвращает количество микросекунд (миллионных долей секунды), прошедших с момента старта.
Эти функции не останавливают программу. Они просто сообщают текущее «время». С их помощью мы можем построить логику, основанную не на ожидании, а на проверке - «Прошло ли уже достаточно времени, чтобы сделать вот это?».
Давайте посмотрим, как это работает на практике.
Реализация многозадачности с помощью millis()
Допустим, нам нужно решить две задачи «одновременно»: 1. Каждые 500 миллисекунд мигать светодиодом. 2. Каждые 2 секунды (2000 мс) отправлять сообщение в последовательный порт.
Если бы мы использовали delay()
, наша программа выглядела бы ужасно и работала бы неправильно, потому что одна задержка блокировала бы другую. С millis()
мы можем сделать это элегантно.
Пример (мигание светодиодом и отправка данных «одновременно»):
const int ledPin = LED_BUILTIN_1; // Используем встроенный светодиод L1
// Переменные для отслеживания времени для каждой задачи
unsigned long previousMillisLED = 0;
unsigned long previousMillisSerial = 0;
// Интервалы для каждой задачи
const long intervalLED = 500; // Интервал для мигания светодиодом (500 мс)
const long intervalSerial = 2000; // Интервал для отправки данных (2000 мс)
// Переменная для хранения состояния светодиода
int ledState = LOW;
void setup() {
Serial.begin(9600);
pinMode(ledPin, OUTPUT);
}
void loop() {
// Получаем текущее время
unsigned long currentMillis = millis();
// --- Задача 1. Мигание светодиодом ---
// Проверяем, прошло ли 500 мс с последнего переключения светодиода
if (currentMillis - previousMillisLED >= intervalLED) {
// Сохраняем время последнего действия
previousMillisLED = currentMillis;
// Инвертируем состояние светодиода
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
digitalWrite(ledPin, ledState);
}
// --- Задача 2. Отправка данных в Serial ---
// Проверяем, прошло ли 2000 мс с последней отправки
if (currentMillis - previousMillisSerial >= intervalSerial) {
// Сохраняем время последнего действия
previousMillisSerial = currentMillis;
Serial.println("Прошло 2 секунды!");
}
// Сюда можно добавить и третью, и четвертую задачу!
}
Как это работает? Внутри loop()
мы постоянно проверяем, не пришло ли время для выполнения одной из наших задач. Сравнивая currentMillis
с previousMillis
(временем последнего выполнения), мы определяем, истёк ли нужный интервал. Если да — выполняем действие и обновляем previousMillis
. Цикл loop()
при этом никогда не останавливается и выполняется тысячи раз в секунду, что позволяет ему очень быстро «переключаться» между проверками.
Зачем это нужно?
Отказ от delay()
в пользу millis()
— это, пожалуй, самый важный шаг на пути от новичка к опытному разработчику. Этот подход, называемый неблокирующим кодом, позволяет создавать сложные и отзывчивые устройства, которые могут:
- Одновременно считывать данные с нескольких датчиков.
- Управлять моторами, не прекращая следить за кнопками.
- Обмениваться данными по сети и в то же время обновлять информацию на дисплее.
- Реагировать на команды пользователя без малейшей задержки.
Теперь вы владеете техникой псевдопараллелизма и можете создавать по-настоящему многозадачные проекты на Рудироне
. Если хотите закрепить эти знания, переходите к лабораторным работам. А если вы готовы начать соединять ваш Рудирон
с другими умными устройствами или компьютером, то в следующем параграфе мы изучим один из самых базовых и полезных интерфейсов связи — UART
.
- Страницы
- 1. Введение
- 1.01. Лабораторная работа №1.1
- 1.02. Лабораторная работа №1.2
- 1.03. Лабораторная работа №1.3
- 1.04. Лабораторная работа №1.4
- 1.05. Лабораторная работа №1.5
- 2. Основы электроники
- 2.01. Электрический ток и параметры цепи
- 2.01.1. Лабораторная работа №2.1.1
- 2.01.2. Лабораторная работа №2.1.2
- 2.02. Сопротивление в цепи
- 2.02.1. Лабораторная работа №2.2.1
- 2.02.2. Лабораторная работа №2.2.2
- 2.03. Полупроводники
- 2.03.1. Лабораторная работа №2.3.1
- 2.03.2. Лабораторная работа №2.3.2
- 3. Основы программирование на C++
- 3.01. Типы данных и переменные
- 3.01.1. Практикум
- 3.02. Операторы в C++
- 3.02.1. Практикум
- 3.03. Структуры ветвления
- 3.03.1. Практикум
- 3.04. Структуры повторения
- 3.04.1. Практикум
- 3.05. Массивы
- 3.05.1. Практикум
- 3.06. Функции
- 3.06.1. Практикум
- 3.07. Решения задач
- 4. Программирование микроконтроллеров
- 4.01. Цифровые сигналы и GPIO
- 4.01.1. Лабораторная работа №4.1.1
- 4.01.2. Лабораторная работа №4.1.2
- 4.01.3. Лабораторная работа №4.1.3
- 4.01.4. Лабораторная работа №4.1.4
- 4.01.5. Лабораторная работа №4.1.5
- 4.01.6. Лабораторная работа №4.1.6
- 4.01.7. Лабораторная работа №4.1.7
- 4.01.8. Лабораторная работа №4.1.8
- 4.02. Аналоговые сигналы и ШИМ
- 4.02.1. Лабораторная работа №4.2.1
- 4.02.2. Лабораторная работа №4.2.2
- 4.02.3. Лабораторная работа №4.2.3
- 4.02.4. Лабораторная работа №4.2.4
- 4.02.5. Лабораторная работа №4.2.5
- 4.03. Аналоговый сигнал и АЦП
- 4.03.1. Лабораторная работа №4.3.1
- 4.03.2. Лабораторная работа №4.3.2
- 4.03.3. Лабораторная работа №4.3.3
- 4.03.4. Лабораторная работа №4.3.4
- 4.03.5. Лабораторная работа №4.3.5
- 4.04. Аналоговый сигнал и ЦАП
- 4.04.1. Лабораторная работа №4.4.1
- 4.04.2. Лабораторная работа №4.4.2
- 4.05. Генерация и измерение импульсов
- 4.05.1. Лабораторная работа №4.5.1
- 4.05.2. Лабораторная работа №4.5.2
- 4.06. Аппаратные прерывания
- 4.06.1. Лабораторная работа №4.6.1
- 4.06.2. Лабораторная работа №4.6.2
- 4.07. Псевдопараллелизм
- 4.07.1. Лабораторная работа №4.7.1
- 4.07.2. Лабораторная работа №4.7.2
- 4.08. Интерфейсы связи - UART
- 4.08.1. Лабораторная работа №4.8.1
- 4.09. Интерфейсы связи - I2C
- 4.09.1. Лабораторная работа №4.9.1
- 4.10. Интерфейсы связи - SPI
- 4.10.1. Лабораторная работа №4.10.1
- home
-
imgs
- 1. Введение
- 1.01. Лабораторная работа №1.1
- 1.02. Лабораторная работа №1.2
- 1.03. Лабораторная работа №1.3
- 1.04. Лабораторная работа №1.4
- 1.05. Лабораторная работа №1.5
- 2. Основы электроники
- 2.01. Электрический ток и параметры цепи
- 2.01.1. Лабораторная работа №2.1.1
- 2.01.2. Лабораторная работа №2.1.2
- 2.02. Сопротивление в цепи
- 2.02.1. Лабораторная работа №2.2.1
- 2.02.2. Лабораторная работа №2.2.2
- 2.03. Полупроводники
- 2.03.1. Лабораторная работа №2.3.1
- 2.03.2. Лабораторная работа №2.3.2
- 3. Основы программирование на C++
- 3.01. Типы данных и переменные
- 3.01.1. Практикум
- 3.02. Операторы в C++
- 3.02.1. Практикум
- 3.03. Структуры ветвления
- 3.03.1. Практикум
- 3.04. Структуры повторения
- 3.04.1. Практикум
- 3.05. Массивы
- 3.05.1. Практикум
- 3.06. Функции
- 3.06.1. Практикум
- 3.07. Решения задач
- 4. Программирование микроконтроллеров
- 4.01. Цифровые сигналы и GPIO
- 4.01.1. Лабораторная работа №4.1.1
- 4.01.2. Лабораторная работа №4.1.2
- 4.01.3. Лабораторная работа №4.1.3
- 4.01.4. Лабораторная работа №4.1.4
- 4.01.5. Лабораторная работа №4.1.5
- 4.01.6. Лабораторная работа №4.1.6
- 4.01.7. Лабораторная работа №4.1.7
- 4.01.8. Лабораторная работа №4.1.8
- 4.02. Аналоговые сигналы и ШИМ
- 4.02.1. Лабораторная работа №4.2.1
- 4.02.2. Лабораторная работа №4.2.2
- 4.02.3. Лабораторная работа №4.2.3
- 4.02.4. Лабораторная работа №4.2.4
- 4.02.5. Лабораторная работа №4.2.5
- 4.03. Аналоговый сигнал и АЦП
- 4.03.1. Лабораторная работа №4.3.1
- 4.03.2. Лабораторная работа №4.3.2
- 4.03.3. Лабораторная работа №4.3.3
- 4.03.4. Лабораторная работа №4.3.4
- 4.03.5. Лабораторная работа №4.3.5
- 4.04. Аналоговый сигнал и ЦАП
- 4.04.1. Лабораторная работа №4.4.1
- 4.04.2. Лабораторная работа №4.4.2
- 4.05. Генерация и измерение импульсов
- 4.05.1. Лабораторная работа №4.5.1
- 4.05.2. Лабораторная работа №4.5.2
- 4.06. Аппаратные прерывания
- 4.06.1. Лабораторная работа №4.6.1
- 4.06.2. Лабораторная работа №4.6.2
- 4.07. Псевдопараллелизм
- 4.07.1. Лабораторная работа №4.7.1
- 4.07.2. Лабораторная работа №4.7.2
- 4.08. Интерфейсы связи - UART
- 4.08.1. Лабораторная работа №4.8.1
- 4.09. Интерфейсы связи - I2C
- 4.09.1. Лабораторная работа №4.9.1
- 4.10. Интерфейсы связи - SPI
- 4.10.1. Лабораторная работа №4.10.1
- home