Этот материал описывает метод работы с Ардуино при котором создается иллюзия, что плата выполняет одновременно несколько задач.
Используя этот метод, Ардуино сможет одновременно делать ряд процессов:
- переключение светодиода каждые 300 мс (миллисекунды);
- прокрутка строки текста каждые 250 мс;
- изменение настраиваемой гистограммы каждые 100 мс.
Вы сможете использовать данный материал как шаблон для своих собственных проектов.
Комплектующие
Для этого проекта требуется очень немного деталей:
- 1x - микроконтроллер Arduino Uno R3
- 1x - I2C ЖК-дисплей 16x2
- 1x - USB-кабель arduino-to-PC
- 4x - провода для перемычек с разъемами «папа-мама»
Все детали можно заказать в любом интернет-магазине.
Схема подключения
Изображение выше показывает как нам нужно всё соединить.
Код для Ардуино
Код вы найдете ниже. Вы можете скопировать его, либо скачать файл .ino и вставить его содержимое в новый эскиз arduino.
Вам также потребуется установить библиотеку I2C для вашего ЖК-дисплея. Можно посмотреть в наших библиотеках на сайте или использовать библиотеку скачанную с http://arduino-info.wikispaces.com/LCD-Blue-I2C#v1. Если у вас есть другой чип, а не тот, что отмечен на фото выше - вам нужно будет скачать другую библиотеку.
После того как вы найдете подходящую библиотеку, загрузите zip-файл и установите её, используя нашу инструкцию.
// ----- Библиотеки #include <Wire.h> // Требуется для I2C #include <LiquidCrystal_I2C.h> // взято с http://arduino-info.wikispaces.com/LCD-Blue-I2C#v1 #define LED 13 // светодиод платы // ----- определяем выводы ЖК: addr,en,rw,rs,d4,d5,d6,d7,bl,blpol LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // ----- custom bargraph patterns byte column0[8] = { // 1 column B00000, B00000, B00000, B00000, B00000, B00000, B00000, B11111 }; byte column1[8] = { // 2 columns B00000, B00000, B00000, B00000, B00000, B00000, B11111, B11111 }; byte column2[8] = { // 3 columns B00000, B00000, B00000, B00000, B00000, B11111, B11111, B11111 }; byte column3[8] = { // 4 columns B00000, B00000, B00000, B00000, B11111, B11111, B11111, B11111 }; byte column4[8] = { // 4 columns B00000, B00000, B00000, B11111, B11111, B11111, B11111, B11111 }; byte column5[8] = { // 4 columns B00000, B00000, B11111, B11111, B11111, B11111, B11111, B11111 }; byte column6[8] = { // 4 columns B00000, B11111, B11111, B11111, B11111, B11111, B11111, B11111 }; byte column7[8] = { // 4 columns B11111, B11111, B11111, B11111, B11111, B11111, B11111, B11111 }; // ----- message generator char MessageArray[] = " Multi-tasking DEMO: The arduino is controlling the LED, the text, and the custom bar graph. "; int MessageLength = strlen(MessageArray); String Message = MessageArray, Display; // ----- task scheduler (Interrupt Service Routine) int TaskTimer1 = 0, TaskTimer2 = 0, TaskTimer3 = 0; bool TaskFlag1 = false, TaskFlag2 = false, TaskFlag3 = false; /****************************** setup() *******************************/ void setup() { // ----- configure serial port (optional) Serial.begin(9600); //serial port // ----- configure LCD display lcd.begin(16, 2); //16 char x 2 line LCD display // ----- install custom characters lcd.createChar(0, column0); lcd.createChar(1, column1); lcd.createChar(2, column2); lcd.createChar(3, column3); lcd.createChar(4, column4); lcd.createChar(5, column5); lcd.createChar(6, column6); lcd.createChar(7, column7); // ----- configure LED pinMode(LED, OUTPUT); digitalWrite(LED, LOW); // ----- configure Timer 2 to generate a compare-match interrupt every 1mS noInterrupts(); // disable interrupts TCCR2A = 0; // clear control registers TCCR2B = 0; TCCR2B |= (1 << CS22) | // 16MHz/128=8uS (1 << CS20) ; TCNT2 = 0; // clear counter OCR2A = 125 - 1; // 8uS*125=1mS (allow for clock propagation) TIMSK2 |= (1 << OCIE2A); // enable output compare interrupt interrupts(); // enable interrupts } // ------------------------------ // loop() // ------------------------------ void loop() { /* Tasks are only performed if the TaskFlag has been set by the task-scheduler. */ // ----- toggle LED if (TaskFlag1) { TaskFlag1 = false; digitalWrite(LED, !digitalRead(LED)); } // ----- scroll message if (TaskFlag2) { TaskFlag2 = false; scrollMessage(); } // ----- bargraph if (TaskFlag3) { TaskFlag3 = false; bargraph(); } // // ----- title for Instructable // lcd.setCursor(0,0); // lcd.print("How to multitask"); // lcd.setCursor(0,1); // lcd.print(" your ARDUINO "); } // ------------------------------- // task scheduler (1mS interrupt) // ------------------------------- ISR(TIMER2_COMPA_vect) { // ----- timers /* Each timer counts the number of milliseconds since its associated task was last performed. Each additional task requires another timer and flag. */ TaskTimer1++; TaskTimer2++; TaskTimer3++; // ----- task1 if (TaskTimer1 > 300 - 1) { // toggle LED (300mS) TaskTimer1 = 0; TaskFlag1 = true; } // ----- task2 if (TaskTimer2 > 250 - 1) { // scroll message (250mS) TaskTimer2 = 0; TaskFlag2 = true; } // ----- task3 if (TaskTimer3 > 100 - 1) { // bargraph (100mS) TaskTimer3 = 0; TaskFlag3 = true; } } // ---------------------------- // displayMessage() // ---------------------------- void scrollMessage() { // ----- locals /* static variables remember the last value when the subroutine is re-entered. */ static int index = 0; // ----- grab the next 16 characters from the message lcd.setCursor(0, 0); Display = Message.substring(index, index + 16); lcd.print(Display); index++; if (index > MessageLength - 15) index = 0; } // ------------------------------- // bargraph() // ------------------------------- void bargraph() { // ----- locals static int column = 0; // ----- reset display if (column == 0) { lcd.setCursor(0, 1); lcd.print("Volume: "); } // ----- display next symbol lcd.setCursor(8 + column, 1); lcd.write(byte(0 + column)); column++; if (column > 7) column = 0; }
Теория
Пустой цикл loop(){} в коде arduino заставит ардуино «вращаться» (прим.ред. - цикличность) несколько тысяч раз в секунду. Давайте посмотрим, что произойдет, когда мы добавим эту задачу
Одна задача.
Следующий код будет отображать «Hello World!» на вашем последовательном мониторе один раз в секунду:
Serial.println(“Hello World!”); delay(1000);
Каждому циклу loop(){} теперь нужно более 1 секунды для завершения из-за команды задержки delay(1000).
Предполагая, что arduino имеет 16-мегагерцовый кристалл, команда delay(1000) тратит 16 000 000 тактов. Другие задачи возможны, если мы устраним эту задержку.
Многозадачность.
Давайте перепишем вышеприведенный фрагмент кода:
if (Flag1) { Serial.println(“Hello World!”); Flag1=false; }
"Привет мир!" выведется после чего цикл loop(){} возвращается к цикличности с полной скоростью до тех пор, пока флажок 1 = истина.
Поскольку loop(){} нечего делать, а процесс работает на полной скорости, добавим еще одну задачу.
if (Flag2) { digitalWrite(LED, !digitalRead(LED)); Flag2=false; }
Эта часть кода заставляет светодиод «переключаться», после чего loop(){} снова возвращается в процесс, пока флаг Flag2 = true (флажок 2 = ложь).
Не обращая внимания на то, как устанавливаются флажки, мы теперь можем выполнять несколько задач в одно и тоже время.
Длительные задачи требуют специальных методов кода.
Многозадачность, скажем так, "коротких" задач, таких как мигание светодиода, довольно простое дело. Но что делать с многозадачностью при "длинных" задачах, такие как прокрутка текстового сообщения например? Это требуют другого подхода.
Чтобы создать иллюзию многозадачности (см. видео в начале материала), длинные задачи должны быть переписаны как серия небольших задач, таким образом каждый раз через цикл выполняется другой кусок кода.
Это достигается за счет использования «статических» переменных* для отслеживания того, как далеко продвинулась задача. В данном случае это показывают функции «scrollMessage ()» и «bargraph()».
* Когда подпрограмма вызывается, все «переменные» забывают свои предыдущие значения, если они не были объявлены «статическими», и в этом случае предыдущее значение доступно при вызове следующей подпрограммы.
Планировщик заданий
Секрет установки флагов - создать цикл в 1 миллисекунду (мс) с использованием одного из таймеров Arduino Uno R3. Можно использовать Timer/Counter 2 (8 бит), который оставляет Timer/Counter 1 (16-бит) свободным для других задач.
1 мс достигается путем деления частоты 16 000 000 Гц на 128 для получения интервала времени 8 микросекунд. Если теперь считать эти импульсы 8 мкс и генерировать прерывание, когда счетчик достигнет 125, то 125 x 8 или 1 мс истечет**.
** Фактически мы загружаем 125-1 = 124 в «сравнительный регистр соответствия», потому что прерывание не происходит до следующего (125-го) тактового импульса.
Флаги устанавливаются путем помещения следующего кода в ISR (процедуру обслуживания прерываний):
// ----- count the number of milliseconds that have elapsed Counter1++; Counter2++; // ----- print “Hello World!” if (Counter1 > 1000 - 1) //1 second { Counter1 = 0; Flag1 = true; //the loop(){} sees this flag and prints the message } // ----- toggle LED if (Counter2 > 300 - 1) //300 milliseconds { Counter2 = 0; Flag2 = true; //the loop(){} sees this flag and toggles the LED }
Как только счетчик достигнет своего целевого значения, счетчик очищается и устанавливается флаг. Вышеприведенный код занимает очень мало времени для выполнения, так как команд очень мало.
Основной цикл loop(){} видит каждый флаг, который задает планировщик задач и выполняет задачу. Добавление дополнительных задач достаточно просто. Создайте дополнительный счетчик и флаг, а затем имитируйте приведенный выше код. На графике выше показана взаимосвязь между тремя задачами, которые выполняет ардуино в данном примере.
Вы можете заметить, что:
- иногда loop(){} не имеет задач для выполнения;
- иногда loop(){} выполняет только одну или две задачи;
- каждая из задач вызывается разное количество раз;
- гистограмма продвигается каждые 100 мс;
- текст прокручивается каждые 250 мс;
- светодиод переключается каждые 300 мс.
Ключевые моменты
Для успешной многозадачности:
- Избегайте функций delay() или delayMicroseconds(). Вместо этого
- создавайте циклы и
- используйте планировщик задач,
- а длительные задачи следует рассматривать как серию небольших «кусков»
- со «статическими» переменными, отслеживающими прогресс.
На этом пока всё про мультизадачность в Ардуино. Хороших вам проектов.