Промышленное производство
Промышленный Интернет вещей | Промышленные материалы | Техническое обслуживание и ремонт оборудования | Промышленное программирование |
home  MfgRobots >> Промышленное производство >  >> Manufacturing Technology >> Производственный процесс

Мастер-класс по многопоточности Arduino с протопотоками: пошаговое руководство

Компоненты и материалы

Компонент Количество
Arduino UNO 1
Sunfounder Blue 16x2 LCD-дисплей 1
Платы для прототипирования (breadboard) и провода 1
Поворотный потенциометр (общего назначения, ~1 кОм) 1

О проекте

Это руководство демонстрирует, как заставить однокерновой Arduino выполнять три задачи одновременно:

Что такое протопотоки (protothreading)?

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

«Настоящая» многопоточность vs протопотоки

Для понимания разницы рассмотрим исторический пример: технология Hyperthreading от Intel. Она заставляет одно физическое ядро процессора вести себя как два логических, распределяя такты процессора между потоками. Arduino Uno (ATmega328P) работает на 16 МГц — 16 млн тактов в секунду. Но у нас только одно ядро, поэтому классическая многопоточность недоступна.

Протопотоки работают иначе: вместо разделения тактов процессора мы разбиваем работу на уровне циклов loop(). Основной цикл выполняет самую частую задачу (менеджер), а периодически проверяет, не пора ли выполнить другие задачи. Если задача «блокирующая» (долгая операция без прерываний, например чтение SD-карты), она всё равно заблокирует остальные протопотоки. Но для быстрых операций (изменение переменных, вывод на дисплей) метод работает отлично.

Принцип работы

  1. Определяем задачи и ранжируем их от наименее блокирующих к наиболее блокирующим.
  2. Самая частая и быстрая задача становится основным потоком (менеджером). В примере — пульсация подсветки.
  3. В основном цикле (и во всех вложенных циклах) вызываем .check() для остальных задач.
  4. Задачи срабатывают по заданным интервалам через библиотеку TimedAction.

В коде ниже основной поток — плавное затухание/растворение подсветки (циклы for с analogWrite). Внутри этих циклов вызываются numberThread.check() и textThread.check(), чтобы счетчик и текст обновлялись вовремя.

Код (multithread.ino v1.1)

Использует библиотеку TimedAction.h для трёх независимых таймеров. Важно: в новых версиях Arduino IDE в файле библиотеки TimedAction.h нужно заменить WProgram.h на Arduino.h.

/* Arduino Protothreading Example v1.1 by Drew Alden (@ReanimationXP) 
 * Обновление v1.1 - 18.08.2017: мелкие правки для Arduino 1.6.6+ 
 * (функции объявлены перед использованием, убран foreach). 
 * Примечание: TimedAction устарел. Читайте примечания об ошибках WProgram.h / Arduino.h.
 */

// КОМПОНЕНТЫ
// Код написан для синего LCD из набора Sunfounder Arduino Starter Kit (доступен на Amazon).

// СТОРОННИЕ БИБЛИОТЕКИ (устанавливаются вручную)
// TimedAction — действия по отдельным таймерам
// https://playground.arduino.cc/Code/TimedAction
// https://wiring.uniandes.edu.co/source/trunk/wiring/firmware/libraries/TimedAction
#include <TimedAction.h>
// ПОСЛЕ УСТАНОВКИ БИБЛИОТЕКИ: отредактируйте TimedAction.h,
// замените WProgram.h на Arduino.h

// СТАНДАРТНЫЕ БИБЛИОТЕКИ
#include <LiquidCrystal.h>
/* LiquidCrystal Library - Hello World
 * Пример подключения 16x2 LCD:
 * LCD RS  -> pin 12
 * LCD EN  -> pin 11
 * LCD D4  -> pin 5
 * LCD D5  -> pin 4
 * LCD D6  -> pin 3
 * LCD D7  -> pin 2
 * LCD R/W -> GND
 * LCD VSS -> GND
 * LCD VDD -> 5V
 * 10кОм потенциометр: концы -> 5V и GND, средний -> LCD VO (pin 3)
 * Подсветка: LCD K -> GND, LCD A -> 220Ом резистор -> pin 9 (PWM)
 */

// ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
int backlightPin = 9;        // пин подсветки (PWM)
int timerCounter = 0;        // счетчик (переполнится со временем)
int stringNo = 0;            // индекс текущей строки
// Макс 16 символов
char* stringArray[] = {
  "Check it out... ",
  "I have 3 threads",
  "going at once...",
  "Cool, huh?! :D "
};

// ИНИЦИАЛИЗАЦИЯ
// Лучше делать в setup(), но здесь глобально для простоты
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

// ФУНКЦИИ ЗАДАЧ
// Задача 1: вывод инкрементируемого числа на вторую строку LCD
void incrementNumber() {
  lcd.setCursor(0, 1);           // столбец 0, строка 1 (вторая строка)
  timerCounter = timerCounter + 1;
  lcd.print(timerCounter);
}

// Задача 2: ротация текстовых строк на первой строке LCD
void changeText() {
  lcd.setCursor(0, 0);
  lcd.print(stringArray[stringNo]);
  // Проверка границ массива
  if (stringNo >= sizeof(stringArray) / sizeof(char *)) {
    stringNo = 0;
    changeText(); // рекурсивный вызов для первого элемента
  } else {
    stringNo = stringNo + 1;
  }
}

// ТАЙМЕРЫ (создаются ПОСЛЕ объявления функций!)
TimedAction numberThread = TimedAction(700, incrementNumber);   // каждые 700 мс
TimedAction textThread = TimedAction(3000, changeText);         // каждые 3000 мс

// Третья задача — основной loop() (пульсация подсветки).
// Она выполняется чаще всего, остальные «прерывают» её через .check()

void setup() {
  lcd.begin(16, 2);
  changeText(); // начальную строку сразу
}

void loop() {
  // Проверка фоновых задач
  numberThread.check();
  textThread.check();

  // ЗАДАЧА 3: пульсация подсветки (вход/выход)
  digitalWrite(13, HIGH); // индикатор на плате (опционально)

  // Нарастание яркости 0 → 255 шагом 10
  for (int fadeValue = 0; fadeValue <= 255; fadeValue += 10) {
    numberThread.check(); // критично: проверка внутри цикла!
    textThread.check();
    analogWrite(backlightPin, fadeValue);
    delay(20); // короткая задержка, не блокируем надолго
  }

  // Затухание 255 → 0
  digitalWrite(13, LOW);
  for (int fadeValue = 255; fadeValue >= 0; fadeValue -= 10) {
    numberThread.check();
    textThread.check();
    analogWrite(backlightPin, fadeValue);
    delay(20);
  }

  /* Закомментировано: пример прокрутки текста для будущего
  lcd.setCursor(15,0);
  for (int positionCounter1 = 0; positionCounter1 < 26; positionCounter1++) {
    lcd.scrollDisplayLeft();
    lcd.print(array1[positionCounter1]);
    delay(tim);
  }
  lcd.clear();
  lcd.setCursor(15,1);
  for (int positionCounter = 0; positionCounter < 26; positionCounter++) {
    lcd.scrollDisplayLeft();
    lcd.print(array2[positionCounter]);
    delay(tim);
  }
  lcd.clear();
  */
}

Заключение

Протопотоки — простой и эффективный паттерн для одновременного выполнения нескольких периодических задач на Arduino. Главное правило: в любом длительном цикле вызывайте .check() для фоновых задач, иначе они сработают с задержкой. Для сложных проектов с блокирующими операциями рассмотрите FreeRTOS или другие RTOS.

Производственный процесс

  1. Измерение качества воздуха с OpenSensors: руководство по Arduino и Python
  2. Создание надежного RFID-замка на Arduino — пошаговое руководство
  3. Создание универсального ИК-пульта на Arduino: пошаговое руководство
  4. Цифровой стадиометр на Arduino: отслеживание роста лазерным датчиком VL53L0X
  5. Использование термистора: пошаговое руководство для Arduino
  6. Генерация музыкальных тонов на Arduino: полный спектр 88 клавиш пианино
  7. Подключение Arduino к NMEA-0183: пошаговое руководство
  8. Проект датчика отпечатков пальцев на Arduino Nano – Пошаговое руководство
  9. Мастерство Modbus на Arduino: пошаговое руководство
  10. Урок Arduino для начинающих 01: Начало работы с первым проектом