Open form

Wielozadaniowość Arduino - część pierwsza

Większe i lepsze projekty

Jeśli opanowałeś już podstawowe migające diody LED, proste czujniki i serwomechanizmy, jesteś gotowy zacząć przygodę z większymi i lepszymi projektami. Zazwyczaj wiąże się to z łączeniem części prostszych szkiców i próbą ich wspólnego działania. Pierwszą odkrytą przez Ciebie rzeczą będzie to, że niektóre z tych szkiców, które osobno działają idealnie, nie zgrywają się z innymi.

 

Arduino to bardzo prosty moduł bez systemu operacyjnego, który może uruchomić tylko jeden program. W przeciwieństwie do Twojego osobistego komputera lub Raspberry Pi, Arduino nie ma możliwości ładowania i uruchamiania wielu programów. 

 

Nie oznacza to, że nie możemy wykonać wielu zadań w Arduino. Musimy po prostu użyć innej metody. Nie mamy systemu operacyjnego do pomocy, więc musimy wziąć sprawę we własne ręce.

 

Wielozadaniowość Arduino

Pozbądź się funkcji delay()

Pierwsze, czego się nauczyłeś podczas zabawy z Arduino, to najprawdopodobniej użycie powyższej funkcji (delay-opóźnienie). Funkcja delay() nie jest skomplikowana, ale sprawia ona problemy gdy dodaje się dodatkowe funkcje. Kłopotem jest to, że owa funkcja, jest "aktywnym czekaniem" (z ang. busy wait), która dominuje procesor. 

 

Podczas wywoływania tej funkcji, nie możesz reagować na wejścia, nie możesz przetwarzać żadnych danych i nie możesz zmienić jakiegokolwiek wyjścia. Blokuje ona cały procesor. Więc, jeśli któraś część Twojego kodu korzysta z tej funkcji, na ten czas, wszystko inne zostaje zatrzymane.

 

Pamiętasz Blink?

 

  1. /*
  2. Blink
  3. Turns on an LED on for one second, then off for one second, repeatedly.
  4. This example code is in the public domain.
  5. */
  6. // Pin 13 has an LED connected on most Arduino boards.
  7. // give it a name:
  8. int led = 13;
  9. // the setup routine runs once when you press reset:

  10. void setup() {

  11. // initialize the digital pin as an output.

  12. pinMode(led, OUTPUT);

  13. }

  14. // the loop routine runs over and over again forever:

  15. void loop() {

  16. digitalWrite(led, HIGH); // turn the LED on (HIGH is the voltage level)

  17. delay(1000); // wait for a second

  18. digitalWrite(led, LOW); // turn the LED off by making the voltage LOW

  19. delay(1000); // wait for a second

  20. }

  21.  

Prosty szkic Blink jest prawie cały czas w funkcji delay(). Więc w tym czasie, procesor nie może robić nic innego.

 

Nie zapomniałeś o sweep?

Sweep używa funkcji delay(), aby kontrolować swoją prędkość. Jeśli spróbujesz połączyć podstawowy szkic blink z przykładem serwomechanizmu sweep, zauważysz, że naprzemiennie miga dioda i porusza się serwo. Nie będzie jednak działo się to jednocześnie.

 

  1. #include
  2. // Pin 13 has an LED connected on most Arduino boards.
  3. // give it a name:
  4. int led = 13;
  5. Servo myservo; // create servo object to control a servo
  6. // twelve servo objects can be created on most boards
  7. int pos = 0; // variable to store the servo position
  8. void setup()
  9. {
  10. // initialize the digital pin as an output.
  11. pinMode(led, OUTPUT);
  12. myservo.attach(9); // attaches the servo on pin 9 to the servo object
  13. }
  14. void loop()
  15. {
  16. digitalWrite(led, HIGH); // turn the LED on (HIGH is the voltage level)
  17. delay(1000); // wait for a second
  18. digitalWrite(led, LOW); // turn the LED off by making the voltage LOW
  19. delay(1000); // wait for a second
  20. for (pos = 0; pos <= 180; pos += 1) // goes from 0 degrees to 180 degrees
  21. { // in steps of 1 degree
  22. myservo.write(pos); // tell servo to go to position in variable 'pos'
  23. delay(15); // waits 15ms for the servo to reach the position
  24. }
  25. for (pos = 180; pos >= 0; pos -= 1) // goes from 180 degrees to 0 degrees
  26. {
  27. myservo.write(pos); // tell servo to go to position in variable 'pos'
  28. delay(15); // waits 15ms for the servo

 

Więc jak skontrolujemy opóźnienie bez użycia funkcji delay?

Użycie funkcji millis() w opóźnieniu

Prostą techniką wdrożenia opóźnienia jest zrobienie planu i obserwowanie zegarka. Zamiast skupiać się na funkcji delay, po prostu systematycznie sprawdzasz czas, aby wiedzieć kiedy działać. W międzyczasie, inne zadania mogą korzystać z procesora. Bardzo prostym tego przykładem jest szkic BlinkyWithoutDelay ze środowiskiem programistycznym. 

 

Schemat podłączenia dla powyższego kodu znajduje się poniżej:

  

Wielozadaniowość Arduino - schemat podłączenia

 

Migotanie bez opóźnień 

Oto przykładowy kod BlinkWithoutDelay:

 

  1. /* Blink without Delay
  2. Turns on and off a light emitting diode(LED) connected to a digital
  3. pin, without using the delay() function. This means that other code
  4. can run at the same time without being interrupted by the LED code.
  5. The circuit:
  6. * LED attached from pin 13 to ground.
  7. * Note: on most Arduinos, there is already an LED on the board
  8. that's attached to pin 13, so no hardware is needed for this example.
  9. created 2005
  10. by David A. Mellis
  11. modified 8 Feb 2010
  12. by Paul Stoffregen
  13. This example code is in the public domain.
  14. http://www.arduino.cc/en/Tutorial/BlinkWithoutDelay

  15. */

  16. // constants won't change. Used here to

  17. // set pin numbers:

  18. const int ledPin = 13; // the number of the LED pin

  19. // Variables will change:

  20. int ledState = LOW; // ledState used to set the LED

  21. long previousMillis = 0; // will store last time LED was updated

  22. // the follow variables is a long because the time, measured in miliseconds,

  23. // will quickly become a bigger number than can be stored in an int.

  24. long interval = 1000; // interval at which to blink (milliseconds)

  25. void setup() {

  26. // set the digital pin as output:

  27. pinMode(ledPin, OUTPUT);

  28. }

  29. void loop()

  30. {

  31. // here is where you'd put code that needs to be running all the time.

  32. // check to see if it's time to blink the LED; that is, if the

  33. // difference between the current time and last time you blinked

  34. // the LED is bigger than the interval at which you want to

  35. // blink the LED.

  36. unsigned long currentMillis = millis();

  37. if(currentMillis - previousMillis > interval) {

  38. // save the last time you blinked the LED

  39. previousMillis = currentMillis;

  40. // if the LED is off turn it on and vice-versa:

  41. if (ledState == LOW)

  42. ledState = HIGH;

  43. else

  44. ledState = LOW;

  45. // set the LED with the ledState of the variable:

  46. digitalWrite(ledPin, ledState);

  47. }

  48. }

  49.  

Czy ma to jakiś sens?

Na pierwszy rzut oka, BlinkWithoutDelay nie wydaje się zbytnio interesujące. Wygląda jak bardziej skomplikowany sposób na włączenie diod LED. Jednakże, BlinkWithoutDelay prezentuje bardzo ważny koncept znany jako Automat skończony (z ang. State Machine).

Nie musisz już polegać na funkcji delay(), żeby stworzyć program migających diod. BlinkWithoutDelay zapamiętuje obecny status diod LED i ostatni czas, w którym się on zmienił. Po każdej pętli, sprawdza on zegar millis(), aby wiedzieć czy powinien ponownie zmienić status diod LED.

 

Witaj w maszynie

Zwróćmy uwagę na trochę bardziej interesujący wariant blink, który posiada różne czasy włączenia i wyłączenia. Nazwaliśmy go "FlashWithoutDelay"

 

  1. // These variables store the flash pattern
  2. // and the current state of the LED
  3. int ledPin = 13; // the number of the LED pin

  4. int ledState = LOW; // ledState used to set the LED

  5. unsigned long previousMillis = 0; // will store last time LED was updated

  6. long OnTime = 250; // milliseconds of on-time

  7. long OffTime = 750; // milliseconds of off-time

  8. void setup()

  9. {

  10. // set the digital pin as output:

  11. pinMode(ledPin, OUTPUT);

  12. }

  13. void loop()

  14. {

  15. // check to see if it's time to change the state of the LED

  16. unsigned long currentMillis = millis();

  17. if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))

  18. {

  19. ledState = LOW; // Turn it off

  20. previousMillis = currentMillis; // Remember the time

  21. digitalWrite(ledPin, ledState); // Update the actual LED

  22. }

  23. else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))

  24. {

  25. ledState = HIGH; // turn it on

  26. previousMillis = currentMillis; // Remember the time

  27. digitalWrite(ledPin, ledState); // Update the actual LED

  28. }

  29. }

  30.  

Status + Mashine = State Machine

Zauważ, że mamy zmienne, które śledzą czy dioda LED jest włączona czy wyłączona. Są też takie, które obserwują ostatnio zaszłą zmianę. Jest to część Automatu zwana Statusem.

 

Posiadamy również kod, który czuwa nad statusem i decyduje kiedy i jak trzeba go zmienić. To jest część zwana Maszyną. Po każdej pętli, "uruchamiamy maszynę", a ona aktualizuje status.

 

Następnie, sprawdzimy jak połączysz automaty jednocześnie je uruchamiając. 

 

Cel - dwa na raz

Czas na wielozadaniowość! Najpierw podłącz kolejną diodę LED, tak jak na schemacie poniżej:

 

Wielozadaniowość Arduino - schemat podłączenia diod LED

 

Następnie stworzymy kolejny automat dla drugiej diody LED, która miga z zupełnie innymi szybkościami. Użycie dwóch oddzielnych automatów sprawi, że te dwie diody będą migać, nie będąc od siebie zależne.

 

  1. // These variables store the flash pattern
  2. // and the current state of the LED
  3. int ledPin1 = 12; // the number of the LED pin

  4. int ledState1 = LOW; // ledState used to set the LED

  5. unsigned long previousMillis1 = 0; // will store last time LED was updated

  6. long OnTime1 = 250; // milliseconds of on-time

  7. long OffTime1 = 750; // milliseconds of off-time

  8. int ledPin2 = 13; // the number of the LED pin

  9. int ledState2 = LOW; // ledState used to set the LED

  10. unsigned long previousMillis2 = 0; // will store last time LED was updated

  11. long OnTime2 = 330; // milliseconds of on-time

  12. long OffTime2 = 400; // milliseconds of off-time

  13. void setup()

  14. {

  15. // set the digital pin as output:

  16. pinMode(ledPin1, OUTPUT);

  17. pinMode(ledPin2, OUTPUT);

  18. }

  19. void loop()

  20. {

  21. // check to see if it's time to change the state of the LED

  22. unsigned long currentMillis = millis();

  23. if((ledState1 == HIGH) && (currentMillis - previousMillis1 >= OnTime1))

  24. {

  25. ledState1 = LOW; // Turn it off

  26. previousMillis1 = currentMillis; // Remember the time

  27. digitalWrite(ledPin1, ledState1); // Update the actual LED

  28. }

  29. else if ((ledState1 == LOW) && (currentMillis - previousMillis1 >= OffTime1))

  30. {

  31. ledState1 = HIGH; // turn it on

  32. previousMillis1 = currentMillis; // Remember the time

  33. digitalWrite(ledPin1, ledState1); // Update the actual LED

  34. }

  35. if((ledState2 == HIGH) && (currentMillis - previousMillis2 >= OnTime2))

  36. {

  37. ledState2 = LOW; // Turn it off

  38. previousMillis2 = currentMillis; // Remember the time

  39. digitalWrite(ledPin2, ledState2); // Update the actual LED

  40. }

  41. else if ((ledState2 == LOW) && (currentMillis - previousMillis2 >= OffTime2))

  42. {

  43. ledState2 = HIGH; // turn it on

  44. previousMillis2 = currentMillis; // Remember the time

  45. digitalWrite(ledPin2, ledState2); // Update the actual LED

  46. }

  47. }

  48.  

Dziękuję serdecznie! Czy mogę dostać następny?

Możesz dodawać więcej automatów, dopóki będziesz mieć wystarczająco pamięci lub pinów GPIO. Każdy automat posiada swoją własną szybkość migotania. Jako zadanie, zedytuj powyższy kod, aby móc dodać trzeci automat. 

 

  • Po pierwsze, skopiuj wszystkie zmienne statusu i kod jednego z automatów.
  • Potem, nazwij ponownie każdą zmienną, aby uniknąć sprzeczności z pierwszą maszyną.

 

Nie jest to trudne do zrobienia. Lecz ciągłe przepisywanie kodu wydaje się raczej czasochłonne. Musi być jakiś inny sposób by to zrobić!

 

Istnieją lepsze metody radzenia sobie z tym. Są to techniki programowania, które są prostsze i bardziej efektywne. 

 

Klasy

Spójrzmy jeszcze raz na nasz ostatni szkic. Jak widzisz, jest on bardzo monotonny. Ten sam kod jest kopiowany niemalże słowo w słowo dla każdej migoczącej diody LED. Jedyną rzeczą, która się zmienia (w niewielkim stopniu) to nazwa zmiennych. 

 

Kod ten jest najlepszym kandydatem do programowania obiektowego (OOP, z ang. Object Oriented Programming)

 

OOP w pętli

Język Arduino jest odmianą języka C++, który obsługuje programowanie obiektowe. Korzystając z języka OOP możemy zebrać wszystkie zmienne statusu i funkcje migającej diody LED w klasie C++.

  

Nie jest to trudne do zrobienia, ponieważ już posiadamy cały napisany kod. Musimy tylko przepakować go jako klasę.

 

Określanie klasy:

Zaczynamy zadeklarowaniem klasy "Flasher":

  

Następnie dodajemy wszystkie zmienne z FlashWithoutDelay. Są one częścią klasy, więc znamy je jako zmienne składowe (member variables).

 

  1. class Flasher
  2. {
  3. // Class Member Variables
  4. // These are initialized at startup
  5. int ledPin; // the number of the LED pin
  6. long OnTime; // milliseconds of on-time
  7. long OffTime; // milliseconds of off-time
  8. // These maintain the current state

  9. int ledState; // ledState used to set the LED

  10. unsigned long previousMillis; // will store last time LED was updated

  11. };

  12.  

Potem dodajemy konstruktor (constructor). Ma on tę samą nazwę co klasa, a jego zadaniem jest inicjować wszystkie zmienne.

 

  1. class Flasher
  2. {
  3. // Class Member Variables
  4. // These are initialized at startup
  5. int ledPin; // the number of the LED pin
  6. long OnTime; // milliseconds of on-time
  7. long OffTime; // milliseconds of off-time
  8. // These maintain the current state

  9. int ledState; // ledState used to set the LED

  10. unsigned long previousMillis; // will store last time LED was updated

  11. // Constructor - creates a Flasher

  12. // and initializes the member variables and state

  13. public:

  14. Flasher(int pin, long on, long off)

  15. {

  16. ledPin = pin;

  17. pinMode(ledPin, OUTPUT);

  18. OnTime = on;

  19. OffTime = off;

  20. ledState = LOW;

  21. previousMillis = 0;

  22. }

  23. };

  24.  
  25.  

Na koniec, zmieniamy pętlę w funkcję składową nazwaną "Update()". 

 

  1. class Flasher
  2. {
  3. // Class Member Variables
  4. // These are initialized at startup
  5. int ledPin; // the number of the LED pin
  6. long OnTime; // milliseconds of on-time
  7. long OffTime; // milliseconds of off-time
  8. // These maintain the current state

  9. int ledState; // ledState used to set the LED

  10. unsigned long previousMillis; // will store last time LED was updated

  11. // Constructor - creates a Flasher

  12. // and initializes the member variables and state

  13. public:

  14. Flasher(int pin, long on, long off)

  15. {

  16. ledPin = pin;

  17. pinMode(ledPin, OUTPUT);

  18. OnTime = on;

  19. OffTime = off;

  20. ledState = LOW;

  21. previousMillis = 0;

  22. }

  23. void Update()

  24. {

  25. // check to see if it's time to change the state of the LED

  26. unsigned long currentMillis = millis();

  27. if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))

  28. {

  29. ledState = LOW; // Turn it off

  30. previousMillis = currentMillis; // Remember the time

  31. digitalWrite(ledPin, ledState); // Update the actual LED

  32. }

  33. else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))

  34. {

  35. ledState = HIGH; // turn it on

  36. previousMillis = currentMillis; // Remember the time

  37. digitalWrite(ledPin, ledState); // Update the actual LED

  38. }

  39. }

  40. };

  41.  

Zmieniając nasz kod w klasę Flasher, zwęziliśmy wszystkie zmienne (status) i funkcje (maszyna) migającej diody LED.

 

A teraz użyjmy tego:

Dla każdej diody LED, którą chcemy zaświecić, tworzymy przykład klasy Flasher przywołując konstruktor. Przy każdym przejściu przez pętlę, musimy przywołać Update() dla każdego przykładu Flashera. 

 

Już nie trzeba powtarzać całego kodu automatu. Pozostaje nam tylko prosić o kolejne przykłady klasy Flasher!

 

  1. class Flasher
  2. {
  3. // Class Member Variables
  4. // These are initialized at startup
  5. int ledPin; // the number of the LED pin
  6. long OnTime; // milliseconds of on-time
  7. long OffTime; // milliseconds of off-time
  8. // These maintain the current state

  9. int ledState; // ledState used to set the LED

  10. unsigned long previousMillis; // will store last time LED was updated

  11. // Constructor - creates a Flasher

  12. // and initializes the member variables and state

  13. public:

  14. Flasher(int pin, long on, long off)

  15. {

  16. ledPin = pin;

  17. pinMode(ledPin, OUTPUT);

  18. OnTime = on;

  19. OffTime = off;

  20. ledState = LOW;

  21. previousMillis = 0;

  22. }

  23. void Update()

  24. {

  25. // check to see if it's time to change the state of the LED

  26. unsigned long currentMillis = millis();

  27. if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))

  28. {

  29. ledState = LOW; // Turn it off

  30. previousMillis = currentMillis; // Remember the time

  31. digitalWrite(ledPin, ledState); // Update the actual LED

  32. }

  33. else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))

  34. {

  35. ledState = HIGH; // turn it on

  36. previousMillis = currentMillis; // Remember the time

  37. digitalWrite(ledPin, ledState); // Update the actual LED

  38. }

  39. }

  40. };

  41. Flasher led1(12, 100, 400);

  42. Flasher led2(13, 350, 350);

  43. void setup()

  44. {

  45. }

  46. void loop()

  47. {

  48. led1.Update();

  49. led2.Update();

  50. }

  51.  

 

Mniej oznacza więcej!

Nareszcie - każda dodatkowa dioda LED wymaga tylko dwóch linijek kodu!

 

Kod ten jest krótszy i łatwiejszy w odczytaniu. A ponieważ nie ma zduplikowanego kodu, jest mniej do skompilowania! To wszystko oszczędza pamięć na inne projekty!

 

Co jeszcze możemy zrobić?

Zastosujmy te same zasady do kodów serwomechanizmów i zacznijmy działać. 

 

Najpierw, połącz ze sobą dwa serwa na płytce stykowej, tak ja na rysunku poniżej. Następnie, podłącz również trzecią diodę LED.

 

Wielozadaniowość Arduino - schemat podłączenia serw i diod LED

  

Oto standardowy kod sweep serwa. Zauważ, że przywołuje on niechcianą przez nas funkcję delay(). Weźmiemy części tego kodu, aby zbudować automat "Sweeper".

 

  1. // Sweep
  2. // by BARRAGAN
  3. // This example code is in the public domain.
  4. #include

  5. Servo myservo; // create servo object to control a servo

  6. // a maximum of eight servo objects can be created

  7. int pos = 0; // variable to store the servo position

  8. void setup()

  9. {

  10. myservo.attach(9); // attaches the servo on pin 9 to the servo object

  11. }

  12. void loop()

  13. {

  14. for(pos = 0; pos < 180; pos += 1) // goes from 0 degrees to 180 degrees

  15. { // in steps of 1 degree

  16. myservo.write(pos); // tell servo to go to position in variable 'pos'

  17. delay(15); // waits 15ms for the servo to reach the position

  18. }

  19. for(pos = 180; pos>=1; pos-=1) // goes from 180 degrees to 0 degrees

  20. {

  21. myservo.write(pos); // tell servo to go to position in variable 'pos'

  22. delay(15); // waits 15ms for the servo to reach the position

  23. }

  24. }

 

Poniższa klasa Sweeper streszcza akcję sweep, ale używa funkcji millis() do ustawienia opóźnienia. 

 

Musimy także dodać funkcje Attach() i Detach(), żeby powiązać serwo z konkretnym pinem:

 

  1. class Sweeper
  2. {
  3. Servo servo; // the servo
  4. int pos; // current servo position
  5. int increment; // increment to move for each interval
  6. int updateInterval; // interval between updates
  7. unsigned long lastUpdate; // last update of position
  8. public:

  9. Sweeper(int interval)

  10. {

  11. updateInterval = interval;

  12. increment = 1;

  13. }

  14. void Attach(int pin)

  15. {

  16. servo.attach(pin);

  17. }

  18. void Detach()

  19. {

  20. servo.detach();

  21. }

  22. void Update()

  23. {

  24. if((millis() - lastUpdate) > updateInterval) // time to update

  25. {

  26. lastUpdate = millis();

  27. pos += increment;

  28. servo.write(pos);

  29. Serial.println(pos);

  30. if ((pos >= 180) || (pos <= 0)) // end of sweep

  31. {

  32. // reverse direction

  33. increment = -increment;

  34. }

  35. }

  36. }

  37. };

 

Jak dużo?

Teraz możemy tworzyć tyle egzemplarzy Flasherów Sweeperów, ile tylko chcemy.

 

Każdy przykład Flashera wymaga 2 linii kodu:

  • jedną, aby zadeklarować przykład
  • drugą, żeby przywołać aktualizację w pętli

 

Każdy przykład Sweepera wymaga tylko 3 linii kodu:

  • jedną, aby zadeklarować przykład
  • drugą, żeby powiązać serwo z pinem
  • i trzecią, aby przywołać aktualizację w pętli

 

  1. #include
  2. class Flasher

  3. {

  4. // Class Member Variables

  5. // These are initialized at startup

  6. int ledPin; // the number of the LED pin

  7. long OnTime; // milliseconds of on-time

  8. long OffTime; // milliseconds of off-time

  9. // These maintain the current state

  10. int ledState; // ledState used to set the LED

  11. unsigned long previousMillis; // will store last time LED was updated

  12. // Constructor - creates a Flasher

  13. // and initializes the member variables and state

  14. public:

  15. Flasher(int pin, long on, long off)

  16. {

  17. ledPin = pin;

  18. pinMode(ledPin, OUTPUT);

  19. OnTime = on;

  20. OffTime = off;

  21. ledState = LOW;

  22. previousMillis = 0;

  23. }

  24. void Update()

  25. {

  26. // check to see if it's time to change the state of the LED

  27. unsigned long currentMillis = millis();

  28. if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))

  29. {

  30. ledState = LOW; // Turn it off

  31. previousMillis = currentMillis; // Remember the time

  32. digitalWrite(ledPin, ledState); // Update the actual LED

  33. }

  34. else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))

  35. {

  36. ledState = HIGH; // turn it on

  37. previousMillis = currentMillis; // Remember the time

  38. digitalWrite(ledPin, ledState); // Update the actual LED

  39. }

  40. }

  41. };

  42. class Sweeper

  43. {

  44. Servo servo; // the servo

  45. int pos; // current servo position

  46. int increment; // increment to move for each interval

  47. int updateInterval; // interval between updates

  48. unsigned long lastUpdate; // last update of position

  49. public:

  50. Sweeper(int interval)

  51. {

  52. updateInterval = interval;

  53. increment = 1;

  54. }

  55. void Attach(int pin)

  56. {

  57. servo.attach(pin);

  58. }

  59. void Detach()

  60. {

  61. servo.detach();

  62. }

  63. void Update()

  64. {

  65. if((millis() - lastUpdate) > updateInterval) // time to update

  66. {

  67. lastUpdate = millis();

  68. pos += increment;

  69. servo.write(pos);

  70. Serial.println(pos);

  71. if ((pos >= 180) || (pos <= 0)) // end of sweep

  72. {

  73. // reverse direction

  74. increment = -increment;

  75. }

  76. }

  77. }

  78. };

  79. Flasher led1(11, 123, 400);

  80. Flasher led2(12, 350, 350);

  81. Flasher led3(13, 200, 222);

  82. Sweeper sweeper1(15);

  83. Sweeper sweeper2(25);

  84. void setup()

  85. {

  86. Serial.begin(9600);

  87. sweeper1.Attach(9);

  88. sweeper2.Attach(10);

  89. }

  90. void loop()

  91. {

  92. sweeper1.Update();

  93. sweeper2.Update();

  94. led1.Update();

  95. led2.Update();

  96. led3.Update();

  97. }

 

W tej chwili mamy 5 niezależnych zadań działających non-stop, bez zakłóceń. A nasza funkcja loop() posiada tylko 5 linijek kodu! 

Wszystko razem!

Chcemy też Twojego wejścia

Kolejnym problemem z taktowaniem bazującym na funkcji delay(), jest to, że wejście użytkownika, takie jak wciśnięcie przycisku, jest często ignorowane. Dzieje się tak dlatego, że procesor nie potrafi sprawdzić stanu przycisku kiedy jest w funkcji delay(). Z opóźnieniem na bazie funkcji millis(), procesor może regularnie sprawdzać stan przycisków i innych wejść. To pozwala nam zbudować złożone programy, które mogą robić wiele rzeczy na raz i nadal reagować.

 

Zademonstrujemy to dodając przycisk do naszego obwodu, tak jak na rysunku:

 

Wielozadaniowość Arduino - schemat podłączenia z przyciskiem

 

Poniższy kod sprawdzi stan przycisku na każdym przejściu pętli. Led1 sweeper2 nie będą aktualizowane podczas wciśnięcia przycisku.

 

  1. #include
  2. class Flasher

  3. {

  4. // Class Member Variables

  5. // These are initialized at startup

  6. int ledPin; // the number of the LED pin

  7. long OnTime; // milliseconds of on-time

  8. long OffTime; // milliseconds of off-time

  9. // These maintain the current state

  10. int ledState; // ledState used to set the LED

  11. unsigned long previousMillis; // will store last time LED was updated

  12. // Constructor - creates a Flasher

  13. // and initializes the member variables and state

  14. public:

  15. Flasher(int pin, long on, long off)

  16. {

  17. ledPin = pin;

  18. pinMode(ledPin, OUTPUT);

  19. OnTime = on;

  20. OffTime = off;

  21. ledState = LOW;

  22. previousMillis = 0;

  23. }

  24. void Update()

  25. {

  26. // check to see if it's time to change the state of the LED

  27. unsigned long currentMillis = millis();

  28. if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))

  29. {

  30. ledState = LOW; // Turn it off

  31. previousMillis = currentMillis; // Remember the time

  32. digitalWrite(ledPin, ledState); // Update the actual LED

  33. }

  34. else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))

  35. {

  36. ledState = HIGH; // turn it on

  37. previousMillis = currentMillis; // Remember the time

  38. digitalWrite(ledPin, ledState); // Update the actual LED

  39. }

  40. }

  41. };

  42. class Sweeper

  43. {

  44. Servo servo; // the servo

  45. int pos; // current servo position

  46. int increment; // increment to move for each interval

  47. int updateInterval; // interval between updates

  48. unsigned long lastUpdate; // last update of position

  49. public:

  50. Sweeper(int interval)

  51. {

  52. updateInterval = interval;

  53. increment = 1;

  54. }

  55. void Attach(int pin)

  56. {

  57. servo.attach(pin);

  58. }

  59. void Detach()

  60. {

  61. servo.detach();

  62. }

  63. void Update()

  64. {

  65. if((millis() - lastUpdate) > updateInterval) // time to update

  66. {

  67. lastUpdate = millis();

  68. pos += increment;

  69. servo.write(pos);

  70. Serial.println(pos);

  71. if ((pos >= 180) || (pos <= 0)) // end of sweep

  72. {

  73. // reverse direction

  74. increment = -increment;

  75. }

  76. }

  77. }

  78. };

  79. Flasher led1(11, 123, 400);

  80. Flasher led2(12, 350, 350);

  81. Flasher led3(13, 200, 222);

  82. Sweeper sweeper1(15);

  83. Sweeper sweeper2(25);

  84. void setup()

  85. {

  86. Serial.begin(9600);

  87. sweeper1.Attach(9);

  88. sweeper2.Attach(10);

  89. }

  90. void loop()

  91. {

  92. sweeper1.Update();

  93. if(digitalRead(2) == HIGH)

  94. {

  95. sweeper2.Update();

  96. led1.Update();

  97. }

  98. led2.Update();

  99. led3.Update();

  100. }

 

3 diody LED będą migać we własnym tempie. 2 sweepery też będą działać z własną szybkością. Ale po wciśnięciu przycisku, sweeper2 led1 zostaną zatrzymane, dopóki nie zwolnimy przycisku. 

 

wielozadaniowość Arduino - działanie

 

Mamy teraz 5 zadań z wejściem użytkownika, działających niezależnie. Nie ma żadnych opóźnień, które zablokują procesor. A nasz wydajny kod programowania obiektowego pozostawia dużo miejsca na rozszerzenia!

 

Wnioski:

W tym przewodniku pokazaliśmy, że Arduino potrafi wykonywać wiele różnych zadań i nadal reagować na zewnętrzne zdarzenia. 

 

  • Nauczyliśmy się jak mierzyć czas za pomocą funkcji millis() zamiast delay(), co pozwoliło procesorowi robić inne rzeczy.
  • Dowiedzieliśmy się jak określać zadania jako automaty, które mogą działać w tym samym czasie co inne automaty, ale niezależnie od nich.
  • Odkryliśmy jak zawrzeć te automaty w klasach C++, aby kod pozostał prosty i zwięzły.

 

Techniki te nie zmienią Twojego Arduino w superkomputer. Jednakże, pomogą Ci wydobyć to co najlepsze z tego małego, ale zadziwiająco potężnego modułu.

W części drugiej, będziemy budować korzystając z tych technik. Odkryjemy też inne sposoby na to, aby Arduino reagowało na zewnętrzne zdarzenia utrzymując przy tym działanie wielu zadań.  

 

część druga ->

 

Źródło: https://learn.adafruit.com/multi-tasking-the-arduino-part-1

zapraszamy do współpracy!