Руководство по коду для многозадачных и неблокирующих таймеров

Я начинаю делать сложные вещи (ну... для моего начального уровня), и мне нужно управлять дисплеем с 3 светодиодами и 4/5 типами событий и состояний.

Я использую пример BlinkWithoutDelay, дублирующий все переменные для разных статусов/событий, но код действительно беспорядочный, полный загадочных вложенных операторов if.

Как упростить вещи? Я не знаю, что выбрать, потому что ООП может быть выбором, но я думаю, что это повредит малой памяти внутри Arduino Uno/Leonardo. Прерывания могут быть другим вариантом, но я думаю, что они похожи на потоки, и, следуя принципу KISS, я хотел бы держаться подальше от их.

Итак, может ли кто-нибудь посоветовать мне какую-нибудь статью/руководство/код для чтения/что-то еще, чтобы понять, как использовать много светодиодов и сохранять много внутреннего состояния, не ломая себе голову?

ИЗМЕНИТЬ:

Немного сложно воспроизвести простой скелет моего беспорядочного кода, но он выглядит примерно так (возможно, в псевдокоде)

void loop() {
    if(state==0) handleState0();
    if(state==1) handleState1();
    if(state==2) handleState2();
    if(state==3) handleState3();
    readButtons();
    readSensors();
    …
}

// это блок переменных, который я должен скопировать и вставить
// каждый раз, когда я должен мигать светодиодом (надеясь, что есть
// ничего блокирующего, что может все испортить)
unsigned long previousMillis_1 = 0;
const long interval_1 = 1000;
int ledState_1 = LOW;

void handleState1() {
  // Зелёный светодиод мигает, жёлтый горит постоянно
  // это код копирования и вставки, который трудно поддерживать,
  // легко ломается из-за любого блокирующего вызова (как и I2C)
  // и сложно рефакторить или изолировать
  unsigned long currentMillis_1 = millis();
  if(currentMillis_1 - previousMillis_1 >= interval_1) {
    previousMillis_1 = currentMillis_1;   
    if (ledState_1 == LOW) ledState_1 = HIGH;
    else ledState_1 = LOW;
    digitalWrite(pinGreen, ledState_1);
    digitalWrite(pinYellow, LOW);
}

unsigned long previousMillis_2 = 0;
const long interval_2 = 1000;
int ledState_2 = LOW;

void handleState2() {
  // Мигает желтый светодиод, горит зеленый
  // то же, что и раньше
  unsigned long currentMillis_2 = millis();
  if(currentMillis_2 - previousMillis_2 >= interval_2) {
    previousMillis_2 = currentMillis_2;   
    if (ledState_2 == LOW) ledState_2 = HIGH;
    else ledState_2 = LOW;
    digitalWrite(pinYellow, ledState_2);
    digitalWrite(pinGreen, LOW);
}

void handleButtonPressed() {
  // это все испортит и потому блокирует
  // и должен быть вставлен в основной цикл с помощью
  // некоторые глобальные переменные: активные/деактивированные, счетчик,
  // и неблокирующие переменные, такие как предыдущая мельница, интервал и т. д. и т. д.
  Serial.println("\ndisplayAccessDenied");
  for(int i=0; i<3; i++) {
    digitalWrite(pinRed, LOW);
    delay(100);
    digitalWrite(pinRed, HIGH);
    delay(100);
  } digitalWrite(pinRed, LOW);
}

Я чувствую себя немного растерянным, но все примеры Arduino очень просты, и чтобы создать светодиодный интерфейс среднего уровня, мне приходится ломать голову над чем-то, что не работает на 100%, и кроме Arduino я нахожу только супер -жесткие статьи о микропроцессорах с низкоуровневым кодом C. Но вставленный пример не работает на 100% И его очень сложно читать, поддерживать и т. д.

, 👍0

Обсуждение

Я бы посоветовал вам взглянуть на фреймворк Cosa для Arduino (https://github.com/mikaelpatel/Cosa): мощный и довольно простой в использовании, полностью ООП, но при этом имеет небольшой объем памяти и отличную производительность., @jfpoilpret

Не могли бы вы опубликовать свой код? Разные задачи имеют разные (оптимальные) решения. PS. прерывания - это не что иное, как потоки. Возможно, это не лучшее место для новичка, но они могут быть чрезвычайно мощными. Я не думаю, что они против принципа KISS. Вы можете, например, сделать так, чтобы основной цикл проверял все события, а прерывание устанавливало светодиод в соответствии с некоторыми переменными, установленными в цикле., @Gerben

хорошо, я отредактировал. так что .. прерывание - это не дьявол .. какие-нибудь советы по некоторым руководствам / статьям среднего уровня об этом?, @nkint

Можешь попробовать: http://bleaklow.com/2010/07/20/a_very_simple_arduino_task_manager.html Это просто, но мощно., @Damian G.

прерывание не дьявол - см. [Прерывания](http://www.gammon.com.au/interrupts), @Nick Gammon


5 ответов


3

Не совсем понятно, что вы здесь хотите...

Если вы хотите выполнять действия в определенное время, загляните на http://playground.arduino.cc/Code. /Планировщик

Или, если вам нужно нечто большее (несколько задач, вытесняющая многозадачность), https://launchpad.net/arduos (arduOS также может служить для запуска вещей в определенное время, но без необходимости постоянно использовать функцию)

В то время как ООП МОЖЕТ занимать больше памяти программы, объект без виртуальных функций занимает ОЗУ только переменные в нем (если у вас есть байт (1 байт) и целое число (2 байта) в объекте, это займет 3 байта )

На архитектуре AVR (та, которую использует большинство Arduino) программа напрямую выполняется из флэш-памяти, а в ОЗУ находятся только переменные

Обработку if-state, которую вы там выполняете, можно упростить, используя такие массивы, как:

typedef void(*handler)(void)
handler stateHandlers[STATES] = {&handle1, &handle2, &handle3 ...
// ручка? являются функциями без () !
void handleState(int state)
{
    if(state >= STATES)
        return;
    stateHandlers[state]();
}

Очевидно, что вы также можете использовать массивы для хранения состояний светодиодов.

Также обратите внимание, что HIGH равно 1, а LOW равно 0, поэтому технически для хранения состояний достаточно бита.

,

1

Cosa поддерживает большое количество механизмов многозадачности. Последним дополнением является абстрактный класс для заданий (отложенных или периодические функции) и планировщики заданий. Эта абстракция позволяет планировать функции на микро-, милли- и секундном уровне.

Для начала просмотрите скетчи примеров Cosa. Если у вас есть Logic Analyzer (например, Salea Logic), есть несколько примеров скетчей, которые показывают генерацию импульсов.

Есть также привязка планировщика заданий к уровню тика в секундах. Это абстрагируется от часов и будильников.

Для более высокой абстракции многозадачности Cosa также реализует FSM, протопотоки, потоки (многостеки), капсулы UML и многое другое.

,

Поддерживает ли он принудительное вытеснение? Семафоры? Пробежался беглым взглядом, но не нашел их с первого взгляда., @Igor Stoppa

Потоки (см. [Cosa/libraries/Nucleo](https://github.com/mikaelpatel/Cosa/tree/master/libraries/Nucleo)) не являются принудительным вытеснением. Он совместный, т.е. нужно вызывать yield(), delay() и т. д. Есть семафоры и некоторая базовая поддержка передачи сообщений. Job и Job::Scheduler можно использовать в режиме принудительного вытеснения, поскольку отправка выполняется из ISR и может быть изменена на run() в контексте ISR. Простейшей из всех многозадачных абстракций является периодический блок (см. Cosa/Periodic.hh и макрос Periodic(timer, ms))., @Mikael Patel

Хорошо, спасибо за подтверждение. Для моих нужд отсутствие принудительного упреждения уже дисквалифицирует., @Igor Stoppa

Это вопрос о ресурсах. Cosa поддерживает AVR MPU от ATtiny до ATmega. Имея всего 0,5-1 Кбайт SRAM, сложно разрешить стеки потоков и кадры ISR и при этом иметь избыточную память для приложения. Это очень компромисс. Механизм в Cosa можно использовать с принудительным вытеснением, но этого нет в версии с открытым исходным кодом. Ваше здоровье!, @Mikael Patel


1

Вот пример независимого мигания двух светодиодов:

// Какие выводы к какому светодиоду подключены
const byte greenLED = 12;
const byte redLED = 13;

// Периоды мигания в миллисекундах (от 1000 до секунды).
const unsigned long greenLEDinterval = 500;
const unsigned long redLEDinterval = 1000;

// Переменная, содержащая текущее значение таймера. По одному на каждый "Таймер"
unsigned long greenLEDtimer;
unsigned long redLEDtimer;

void setup () 
  {
  pinMode (greenLED, OUTPUT);
  pinMode (redLED, OUTPUT);
  }  // конец настройки

void toggleLED (const byte which, unsigned long & whenToggled)
  {
   if (digitalRead (which) == LOW)
      digitalWrite (which, HIGH);
   else
      digitalWrite (which, LOW);
  // помним, когда мы его переключали
  whenToggled = millis ();  
  }  // конец toggleLED

void loop ()
  {
  // Обработка мигания одного светодиода.
  if ( (millis () - greenLEDtimer) >= greenLEDinterval)
     toggleLED (greenLED, greenLEDtimer);

  // Другой светодиод управляется таким же образом. Повторите для большего количества светодиодов
  if ( (millis () - redLEDtimer) >= redLEDinterval) 
    toggleLED (redLED, redLEDtimer);

/* Other code that needs to execute goes here.
   It will be called many thousand times per second because the above code
   does not wait for the LED blink interval to finish. */

  }  // конец цикла

Обратите внимание, что вместо копирования и вставки мигающего кода я создал универсальную функцию. Таким образом любые ошибки легко устраняются.


Для большего количества светодиодов вы можете использовать массив:

const int NUMBER_OF_LEDS = 5;
// Какие пины подключены к какому светодиоду
const byte LED_PINS [NUMBER_OF_LEDS] = { 8, 9, 10, 11, 12 };
// Периоды мигания в миллисекундах (от 1000 до секунды).
const unsigned long LED_INTERVALS [NUMBER_OF_LEDS] = { 200, 400, 100, 1000, 2000};
// Переменная, содержащая текущее значение таймера. По одному на каждый "Таймер"
unsigned long timeLEDtoggled [NUMBER_OF_LEDS];

void setup () 
  {
  for (int i = 0; i < NUMBER_OF_LEDS; i++)
    pinMode (LED_PINS [i], OUTPUT);
  }  // конец настройки

void toggleLED (const byte which, unsigned long & whenToggled)
  {
   if (digitalRead (which) == LOW)
      digitalWrite (which, HIGH);
   else
      digitalWrite (which, LOW);
  // помним, когда мы его переключали
  whenToggled = millis ();  
  }  // конец toggleLED

void loop ()
  {
  // смотрим, не пора ли переключить светодиоды
  for (int i = 0; i < NUMBER_OF_LEDS; i++)
    if ( (millis () - timeLEDtoggled [i]) >= LED_INTERVALS [i])
       toggleLED (LED_PINS [i], timeLEDtoggled [i]);

  /* Other code that needs to execute goes here. */
  }  // конец цикла

Во всяком случае, приведенный выше код проще, но он мигает 5 светодиодами с разной частотой. Теперь у вас могут быть более сложные тесты, но общая концепция та же.


Ссылки

  • Как делать несколько дел одновременно... например, готовить бекон и яйца

  • Конечные автоматы

,

2

Следуя принципу KISS, я недавно опубликовал Instructable " Простая многозадачность в Arduino на любой плате"

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

Вот пример кода

void loop() {
 callTask_1(); // сделай что-нибудь
 callTask_2(); // сделать что-то еще
 callTask_1(); // снова проверяем первую задачу, так как она должна быть более отзывчивой, чем другие.
 callTask_3(); // сделать что-то еще
}

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

Наконец, инструкции переносят код без изменений на ESP32, чтобы добавить дистанционное управление через Wi-Fi, Bluetooth или BLE к заслонке с регулируемой температурой, управляемой шаговым двигателем.

Если вы непонятно что-либо в инструкции.

,

0

Используйте библиотеку NonBlockingSequence. Там есть четкие примеры применения последовательностей мигания без задержек. (Ссылка на пример)

    #include <NonBlockingSequence.h>


class Blinking_Led{

  private:
  // Объявляем последовательность
  ClassNonBlockingSequence<Blinking_Led> Sequence;

  // светодиодный контакт
  byte _pin; 

  // Определить ступенчатые функции
  bool led_on(){
    digitalWrite(_pin,1);
    return true;
  }
  bool led_off(){
      digitalWrite(_pin,0);
      return true;
  }

  public:
  Blinking_Led(){};  
  void init(byte pin,unsigned long pause){
    
    // определяем вывод светодиода
    _pin=pin;                    
    pinMode(_pin,OUTPUT);    

    // Эта строка объясняет важный факт для последовательности
    Sequence.AttachedObj(this);
    // Последовательность должна использовать шаги, определенные только внутри этого класса

    // Добавляем шаги
    Sequence.AddNewStep(&led_on);
    Sequence.AddDelayInMillis(pause);
    Sequence.AddNewStep(&led_off);
    Sequence.AddDelayInMillis(pause);
    Sequence.Repeat();
    // повторяем последовательность бесконечное количество раз
    
  }
  
  Blink(){ // используем знакомое имя для светодиода
    Sequence.DoSequence();
  }
  
};

// Объявляем новые объекты
Blinking_Led Led1;
Blinking_Led Led2;

void setup() {

  // Led.init(контакт, время мигания)
  Led1.init(13,200);
  Led2.init(12,1500);
 
}

void loop() {
  // поместите сюда свой основной код для многократного запуска:
  Led1.Blink();
  Led2.Blink();
}
,

Хотя эта ссылка может ответить на вопрос, лучше включить сюда основные части ответа и предоставить ссылку для справки. Ответы, содержащие только ссылки, могут стать недействительными, если связанная страница изменится., @sempaiscuba