4.07.1. Лабораторная работа №4.7.1.md


Псевдопараллелизм на практике

Тема работы

В предыдущих работах мы использовали аппаратные прерывания, чтобы мгновенно реагировать на события. Это мощный инструмент, но количество пинов с прерываниями ограничено. Что делать, если нам нужно выполнять несколько задач «одновременно» без использования delay()? Например, плавно мигать одним светодиодом, опрашивать датчик и одновременно ждать нажатия кнопки?

В этой лабораторной работе мы изучим важнейшую технику псевдопараллелизма, используя функцию millis(). Мы напишем программу, в которой светодиод L2 будет мигать с постоянным интервалом, а кнопка B1 будет в любой момент управлять светодиодом L1 — и всё это без единой блокирующей паузы!

Цель

  • Понять, почему функция delay() мешает созданию многозадачных программ.
  • Освоить использование функции millis() для создания неблокирующих задержек.
  • Научиться организовывать код для одновременного выполнения нескольких независимых задач.

Оборудование и материалы

  • Отладочная плата Рудирон.
  • USB-кабель для подключения Рудирона к компьютеру.
  • Компьютер с настроенной средой Arduino IDE.

Ход работы

  1. Проблема delay() - «Спящий» микроконтроллер

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

  2. Функция millis()

    millis() — это встроенный счётчик, который запускается в момент включения Рудирона и непрерывно считает количество прошедших миллисекунд. Он никогда не останавливается и не блокирует программу.

    Представьте, что delay() — это команда «Спать 10 минут», а работа с millis() — это как периодически поглядывать на часы и спрашивать себя: «10 минут уже прошли? Нет? Тогда я пока займусь другими делами».

    Алгоритм работы с millis() всегда одинаков: 1. Запоминаем время последнего события (например, последнего мигания). 2. В цикле loop() постоянно проверяем, сколько времени прошло с того момента. 3. Если прошло достаточно времени (например, 1000 мс), выполняем нужное действие и сбрасываем таймер, то есть снова запоминаем текущее время.

  3. Настройка и подключение

    Для этого эксперимента нам понадобятся только встроенные компоненты: кнопка B1 и светодиоды L1 и L2.

    • Установите перемычку PRG | RUN в положение PRG.
    • Подключите плату к компьютеру и настройте Arduino IDE (плата Rudiron_Buterbrod_R10_20MHz и правильный COM-порт).
  4. Написание и загрузка кода

    Напишем программу, которая будет выполнять две задачи «одновременно»:

    • Задача 1. Мигать светодиодом L2 каждые 500 мс.
    • Задача 2. По отпусканию кнопки B1 переключать состояние светодиода L1.
    • В Arduino IDE введите следующий код:

      // --- Переменные для задачи 1. Мигание L2 ---
      bool led2State = LOW;
      unsigned long previousMillis = 0;   // Время последнего мигания L2
      const long interval = 500;          // Интервал мигания L2 (500 мс)
      
      // --- Переменные для задачи 2. Кнопка B1 и светодиод L1 ---
      bool led1State = LOW;
      bool lastButtonState = LOW; 
      
      void setup() {
        pinMode(LED_BUILTIN_1, OUTPUT);
        pinMode(LED_BUILTIN_2, OUTPUT);
        pinMode(BUTTON_BUILTIN_1, INPUT_PULLDOWN);
      }
      
      void loop() {
        // --- Обработка задачи 1. Мигание L2 без delay() ---
        unsigned long currentMillis = millis();
        if (currentMillis - previousMillis >= interval) {
        // Сохраняем время этого мигания
        previousMillis = currentMillis;
        // Меняем состояние светодиода L2
        led2State = !led2State;
        digitalWrite(LED_BUILTIN_2, led2State);
        }
      
        // --- Обработка задачи 2. Проверка кнопки B1 ---
        bool currentButtonState = digitalRead(BUTTON_BUILTIN_1);
        if (currentButtonState != lastButtonState) {
        // Если состояние изменилось и сейчас кнопка отпущена (LOW),
        // то переключаем светодиод L1
        if (currentButtonState == LOW) {
            led1State = !led1State;
            digitalWrite(LED_BUILTIN_1, led1State);
        }
        }
        lastButtonState = currentButtonState; // Запоминаем состояние кнопки
      }
      
    • Нажмите кнопку «Upload», чтобы загрузить код на плату.
  5. Проверка работы

    • Сразу после загрузки светодиод L2 начнёт равномерно мигать.
    • В любой момент нажмите на кнопку B1. Ничего не произойдёт.
    • Теперь отпустите кнопку. Светодиод L1 должен мгновенно отреагировать и изменить своё состояние. Мигание L2 при этом не прервётся ни на мгновение.

Результаты

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

Анализ результатов

  • Задача мигания. if (currentMillis - previousMillis >= interval) — эта конструкция проверяет, прошло ли 500 мс, не останавливая выполнение loop(). Это позволяет коду для кнопки выполняться тысячи раз в секунду.
  • Задача кнопки. Алгоритм с currentButtonState и lastButtonState работает точно так же, как и в лабораторной работе 4.1.3. Он отлавливает именно момент отпускания.
  • Отзывчивость. Поскольку loop() не блокируется функцией delay(), он готов среагировать на изменение состояния кнопки практически мгновенно.

Выводы

В этой лабораторной работе вы освоили самый важный приём для написания сложных программ для микроконтроллеров — неблокирующую многозадачность с помощью millis(). Этот подход является стандартом в программировании встроенных систем и позволяет создавать сложные, отзывчивые и надёжные устройства.

Вопросы для самопроверки

  1. Почему delay() мешает созданию многозадачных программ?
  2. Что возвращает функция millis()?
  3. Зачем в алгоритме мигания нужна переменная previousMillis?
  4. Что произойдёт, если забыть обновить previousMillis внутри блока if?
  5. Как изменить код, чтобы светодиод L2 мигал в два раза быстрее, а светодиод L1 по-прежнему реагировал на отпускание кнопки?

Поздравляем с освоением псевдопараллелизма! Теперь вы готовы к созданию по-настоящему сложных проектов. В следующем параграфе мы перейдём к изучению интерфейсов связи и научим Рудирон общаться с компьютером и другими устройствами.