Forum: Mikrocontroller und Digitale Elektronik ESP8266: Arduino IDE


von Tim S. (Firma: Google) (tuxut83)


Lesenswert?

Hallo Gemeinde.


Ich verwende derzeit einen ESP8266 und programmiere diesen unter der 
weit verbreiteten Arduino IDE.

Mal ganz kurz ne simple Frage:
Angenommen ich habe an drei Pins jeweils eine LED angeschlossen und 
möchte diese in unterschiedlichen Intervallen blinken lassen:

z.B.
LED an Pin D1 100ms an/aus
LED an Pin D2 200ms an/aus
LED an Pin D3 300ms an/aus

Diese Zeiten müssen exakt eingehalten werden. (Ich möchte diese am Oszi 
nachmessen können.)

Parallel möchte ich natürlich mit dem ESP8266-Modul via WLAN 
kommunizieren.
Ich sehe da bisher nur blockierende Funktionen für den Empfang von 
Daten.
Also wie um Himmelswillen, sollen da bitteschön noch die Zeiten der LEDs 
eingehalten werden? Einen Task/Thread kann ich scheinbar nicht 
aufziehen...

Ich habe nur meine Setup()- und Loop()-Funktion.

Vielleicht kann mir jemand grundsätzlich erläutern, wie man mit 
"pseudo-parallelen" Aufgaben umgeht?

Im WWW finde ich überall nur sehr sehr einfach gehaltene Beispiele. 
Entweder ein reines Webserver-Beispiel oder das Blinken von LEDs.

Herzlichen Dank.

von Michael U. (amiga)


Lesenswert?

Hallo,

es gibt inzwischen auch fast alles asyncron, AsyncHTTP, AsyncMQTT usw.

Die LEDs kann man aber auch per Timer-Interrupt blinken lassen.
Aber: was ist "exakt"? Der Jitter, der durch die WLAN-Routinen des SDK 
erzeugt wird, ist bemerkbar, Multiplex-Routinen z.B. für 7-Segmet/8 
Stellen können durchaus mal erkennbar "zucken" wenn WLAN ungünstig 
dazwischen gerät.

Gruß aus Berlin
Michael

von Uwe D. (monkye)


Lesenswert?

Wenn Du das per Timer-Interrupt machst, dann sollte das klappen ohne das 
der Webserver dazwischen haut. Ist sicher von der Priorität der 
Interrupts her abbildbar. (genau weiß ich es in dem konkreten Fall 
nicht, aber alle gängigen Architekturen können es mehr oder weniger fein 
granuliert)

von Tim S. (Firma: Google) (tuxut83)


Lesenswert?

Ich würde viel lieber mit "richtigen" Tasks arbeiten...
Gibt es da ne Lösung?

von STM Apprentice (Gast)


Lesenswert?

Stefan S. schrieb:
> Ich würde viel lieber mit "richtigen" Tasks arbeiten...
> Gibt es da ne Lösung?

Ja, das Zauberwort heisst "Betriebssystem".

Aber unter Arduino IDE, neeeee neeeee .....

Uwe D. schrieb:
> Ist sicher von der Priorität der Interrupts her abbildbar.

Aber unter Arduino IDE, neeeee neeeee .....

von Stefan F. (Gast)


Lesenswert?

Du hast das Problem korrekt erkannt. Einige API Aufrufe sind 
blockierend, manche sogar mehrere Sekunden. Selbst bei den nicht 
blockierenden werden Dir Interrupts dazwischen kommen.

> Ich würde viel lieber mit "richtigen" Tasks arbeiten

Das würde dein Problem nicht lösen, denn wenn ein Single-Core Prozessor 
mehr als einen Task ausführt, muss das Betriebsystem zwischen den Tasks 
umschalten und das wird auch nicht so 100% genau sein, wie du das gerne 
hättest. Bestes beispiel ist Windows. Toggel doch mal in Windows einen 
Pin von parallel-Port und staune, wie unregelmäßig das abläuft.

Langer Rede kurzer Sinn: Ich denke es geht nicht. Du brauchst einen 
zweiten Mikrocontroller dessen Firmware Echtzeit-tauglich ist. Am 
einfachsten, indem du Timer und ihre Output-Compare Ausgänge verwendest.

> Aber unter Arduino IDE, neeeee neeeee .....

In diesem Fall ist ausnahmsweise weder die Arduino IDE noch das Arduino 
Framework der Knackpunkt, sondern die Firmware von Espressif.

von Chr. M. (snowfly)


Lesenswert?

Auf dem ESP gibt es den Befehl 'ticker'
https://github.com/esp8266/Arduino/tree/master/libraries/Ticker

Ob dir die Genauigkeit reicht musst du checken.

Tipp: WLAN nicht einschalten oder auf esp32 wechseln.


EDIT: Einfach mal alle Beispiele die mit dem esp Package
kamen durcharbeiten und die Doku auf Github lesen
(ist recht kurz)https://github.com/esp8266/Arduino

: Bearbeitet durch User
von Sheeva P. (sheevaplug)


Lesenswert?

Stefan S. schrieb:
> Ich verwende derzeit einen ESP8266 und programmiere diesen unter der
> weit verbreiteten Arduino IDE.
> [...]
> z.B.
> LED an Pin D1 100ms an/aus
> LED an Pin D2 200ms an/aus
> LED an Pin D3 300ms an/aus
>
> Diese Zeiten müssen exakt eingehalten werden. (Ich möchte diese am Oszi
> nachmessen können.)
> [...]
> Ich würde viel lieber mit "richtigen" Tasks arbeiten...
> Gibt es da ne Lösung?

Leider nicht.

Kurz zusammengefaßt: der ESP hat nur einen Kern und kann damit zu jedem 
gegebenen Zeitpunkt nur genau einen einzigen Prozeß ausführen. Damit ist 
präemptives Multitasking unmöglich. Es geht nur kooperatives 
Multitasking, welches sich aber nicht mit blocking Calls vereinbaren 
läßt, sofern harte Echtzeitvorgaben einzuhalten sind.

Du könntest Deine LEDs aber an einen Arduino und diesen wiederum an den 
ESP hängen. Der ESP würde sich dann um die Kommunikation, und der 
Arduino um die Blinkerei kümmern. Damit hättest Du im Prinzip eine Art 
asymmetrisches Multiprocessing, das (vage) einer big.LITTLE-Architektur 
wie etwa im Exynos 5  Octa 5422 (Odroid-XU4) ähnelt. YMMV.

von L. N. (derneumann)


Lesenswert?

öhm blöde frage: den teil der zeitkritisch ist, mit externen hardware 
timern lösen?
da muss sich doch auch eine schaltung aufbauen lassen, dass die zeiten 
per software einstellbar sind.

könnte einfacher/billiger und vor allem genauer sein als ein zweiter 
mikrocontroller oder ein esp32.

von Chr. M. (snowfly)


Lesenswert?

Sheeva P. schrieb:
> hat nur einen Kern und kann damit zu jedem
> gegebenen Zeitpunkt nur genau einen einzigen Prozeß ausführen. Damit ist
> präemptives Multitasking unmöglich. Es geht nur kooperatives
> Multitasking

Also kein Linux auf z.B. 486er möglich? ;)
Stichwort Interrupt und Scheduler.

Im Falle des ESP hast du natürlich recht
aber das liegt nicht an dem einen Kern.


Auch wenn ich mich wiederhole: 'Ticker' ist der gesuchte "Taskmaker"

von Rolf H. (b0d0)


Lesenswert?

Wie  STM Apprentice (Gast)  schon schrieb, ginge das nur mit einer 
Interrupt-Server-Routine. Ob das auf der Arduino SDK geht, oder 
überhaupt ein unbenutzter Timer-Interrupt (des ESP) mit höherer 
Priorität als die SDK zur Verfügung steht weiß ich aber auch nicht. Wenn 
das aber der Fall ist, müsste nur eine entsprechende ISR geschrieben und 
mit eingebunden werden und beim StartUp entsprechend scharf gemacht 
werden.

Da muss man aber schon recht tief einsteigen.

von Sheeva P. (sheevaplug)


Lesenswert?

Chr. M. schrieb:
> Also kein Linux auf z.B. 486er möglich? ;)

Stimmt, aber konkurrierende harte Echtzeit mit blocking Calls? Klar, 
kann man machen, aber bitte sei so nett und erklär' Du das dem TO. ;-)

von Stefan K. (stefan64)


Lesenswert?

Stefan S. schrieb:
> Diese Zeiten müssen exakt eingehalten werden. (Ich möchte diese am Oszi
> nachmessen können.)

Was bedeutet das genau? Was verstehst Du unter "exakt", 1us oder 1ms 
erlaubte Abweichung?

Wenn es Dir nicht um us-Genauigkeit geht, dann kann das entweder mit 
einem Multitask-System (z.B. freeRTOS) oder über einen Timer-ISR gelöst 
werden.

Den/die Tasks für die Leds priorisierst Du etwas höher als den Task für 
die WLAN-Kommunikation.

Übrigens, der ESP32 hat ein "Remote Controller Peripheral", über das 
kannst Du auf bis zu 8 Kanälen sehr flexible Ausgangspattern erzeugen, 
ganz ohne Eingriff der CPU und mit der Genauigkeit der APB_Clock.

Viele Grüße, Stefan

von Stefan F. (Gast)


Lesenswert?

> da muss sich doch auch eine schaltung aufbauen lassen, dass die zeiten
> per software einstellbar sind.

Wie du selbst erkannt hast, brauchst du dazu programmiere, also digital 
konfigurierbare Timer. Und die bekommt man mit ESP kompatibler 
Schnittstelle am einfachsten und billigsten in Form eines 
Mikrocontrollers. AVR wäre hier zu bevorzugen, weil deine Arduino IDE 
diese unterstützt.

Das muss ja kein großes Teil sein, vermutlich genügt Dir schon ein 
ATtiny85.

von L. N. (derneumann)


Lesenswert?

Stefan U. schrieb:
>> da muss sich doch auch eine schaltung aufbauen lassen, dass die zeiten
>> per software einstellbar sind.
>
> Wie du selbst erkannt hast, brauchst du dazu programmiere, also digital
> konfigurierbare Timer. Und die bekommt man mit ESP kompatibler
> Schnittstelle am einfachsten und billigsten in Form eines
> Mikrocontrollers. AVR wäre hier zu bevorzugen, weil deine Arduino IDE
> diese unterstützt.
>
> Das muss ja kein großes Teil sein, vermutlich genügt Dir schon ein
> ATtiny85.

jo, wenn die zeiten wirklich per software einstellbar sein müssen dann 
mag das so sein. das wusste ich nicht, habe nicht so einen 
bauteilüberblick. wobei so ein attiny85 auch nicht so teuer ist, oder?

wenn fixe zeiten reichen, würde sich ein NE555 anbieten?!

von Pete K. (pete77)


Lesenswert?

Wenn Du auf die Arduino IDE verzichtest und auf den ESP8266 LUA 
(nodemcu) aufspielst, ist das ein Thema von ca. 10 Zeilen code.

https://nodemcu.readthedocs.io/en/master/

Nodemcu bietet mehrere Timer an, in denen man eine LED togglen kann:
https://nodemcu.readthedocs.io/en/master/en/modules/tmr/

Der Timer ist dann ein Einzeiler:

tmr.alarm(0,300,1, function() toggle_led() end)

Und ja, paralleler Webserver geht auch.
Ungenauigkeiten von einigen Mikrosekunden sind natürlich möglich, kann 
Dein Auge aber nicht erfassen. Die Theorie von Echtzeitbetriebssystemen 
wurde ja oben schon behandelt.

: Bearbeitet durch User
von apollo2mond (Gast)


Lesenswert?

ich benutze dafür nicht blockierende makros

#ifndef PERIODIC_H
#define PERIODIC_H


//usage:
//  PERIODIC(100) //{
//    digitalWrite(PIN, HIGH/LOW)
//  }

//create an unique name from given parameter and line number
#define UNIQUE(name) __CONCAT(name,__LINE__)

//creates an unique timer for each instance, ms period in milli-seconds
#define WAITBLOCK(ms)                                     \
  static uint32_t UNIQUE(timer) = millis();               \
  if (millis() - UNIQUE(timer) >= ms) {

//creates an unique timer for each instance, ms period in milli-seconds
#define PERIODIC(ms)                             \
  static uint32_t UNIQUE(timer) = millis();        \
  if (millis() - UNIQUE(timer) >= ms) {                   \
    UNIQUE(timer) = millis();

//XPERIODIC(ms,1) = WAITBLOCK but takes more program code space!
//XPERIODIC(ms,0) = PERIODIC
//creates an unique timer for each instance, x: repeats 0-255, 0=endless
#define XPERIODIC(ms, x)                                  \
  static uint32_t UNIQUE(timer) = millis();               \
  static uint8_t UNIQUE(xRepeats) = x;                    \
  static uint8_t UNIQUE(lock);                            \
  if (millis() - UNIQUE(timer) >= ms) {                   \
    if (!UNIQUE(lock)) {                                  \
      if ((UNIQUE(xRepeats)==0) || (--UNIQUE(xRepeats)))  \
        UNIQUE(timer) = millis();                         \
      else UNIQUE(lock)=1;                                \
    }

#endif //PERIODIC_H

von apollo2mond (Gast)


Lesenswert?

oder ich benutze meine timed action queue ...

#ifndef TIMEDQACTION_H
#define TIMEDQACTION_H

//usage:
//timedQAction led/buzzer(functionOn, functionOff);
//buzzer.addQAction(500, 500, 2 ); //action two times with 500ms on/off
//buzzer.updateQAction(); //check and call action

class timedQAction {
  public:
  //---------------------------------------------------------------------- 
-------------------
    #define QUEUE_SIZE 8

    //TODO: remove to mytypdefs.h
    #define SIZE_T uint8_t
    typedef void (*fptr_t)(void);

    //********************************************************************** 
*****************
    timedQAction(fptr_t callBackOn, fptr_t callBackOff) { //constructor 
to create action object
    //********************************************************************** 
*****************
      callActionOn = callBackOn;
      callActionOff = callBackOff;
    }

    //********************************************************************** 
*****************
    ~timedQAction() {}; //destructor to delete action object, also if 
...out of scope
    //********************************************************************** 
*****************

    //********************************************************************** 
*****************
    inline void updateQAction(void) {
    //********************************************************************** 
*****************
      if (actionInQueue) {
        if (!actionFlag && ((millis()-offTime) >= 
fifo[readIdx][OFFDELAY]))
          actionOn(); //turn on the next action
        if (actionFlag && ((millis()-onTime) >= fifo[readIdx][ONDELAY]))
          actionOff();
      }
    }

    //********************************************************************** 
*****************
    inline void clearQAction(void) {
    //********************************************************************** 
*****************
      actionInQueue = 0;
      readIdx = writeIdx = 0;
    }

    //********************************************************************** 
*****************
    bool addQAction(uint16_t onDelay=200, uint16_t offDelay=200, uint8_t 
numRepeats=1) {
    //********************************************************************** 
*****************
      if (qSize > actionInQueue) {
        //if not full write parameters of action to the queue buffer
        fifo[writeIdx][ONDELAY] = onDelay;
        fifo[writeIdx][OFFDELAY] = offDelay;
        fifo[writeIdx][NUMREPEATS] = numRepeats;

        actionInQueue++;
        writeIdx = (writeIdx + 1) % qSize;
        return true;
      }
      else return false;
    }

  private:
  //---------------------------------------------------------------------- 
-------------------
    enum {
      ONDELAY,      //action time to stay "on"
      OFFDELAY,     //action time to stay "off"
      NUMREPEATS,   //number of on/off-action repeats
    };

    const static size_t qSize   = QUEUE_SIZE; //number of actions that 
can be queued
    uint16_t fifo[qSize][3];                  //buffer to save queued 
action
    unsigned long onTime        = 0;          //time stamp of action 
that was turned "on"
    unsigned long offTime       = 0;          //time stamp of action 
that was turned "off"
    SIZE_T readIdx              = 0;          //read index of active 
timed action
    SIZE_T writeIdx             = 0;          //write index to add next 
timed action entry
    SIZE_T actionInQueue        = 0;          //number of queued actions
    bool actionFlag             = false;      //on/off action state
    fptr_t callActionOn         = NULL;       //action-on call back 
function
    fptr_t callActionOff        = NULL;       //action-off call back 
function

    //********************************************************************** 
*****************
    inline void actionOn() {
    //********************************************************************** 
*****************
      if (callActionOn) callActionOn(); //if not NULL call back 
on-action function
      actionFlag = true;
      onTime = millis();
    }

    //********************************************************************** 
*****************
    inline void actionOff() {
    //********************************************************************** 
*****************
      if (callActionOff) callActionOff(); //if not NULL call back 
off-action function
      actionFlag = false;
      offTime = millis();

      if (!fifo[readIdx][NUMREPEATS]) return; //if null than repeat 
endless
      else if (!--fifo[readIdx][NUMREPEATS]) {
        readIdx = (readIdx+1) % qSize; //increment index or wrap around 
if buf maximum is reached
        actionInQueue--; //action sequence is done, therefore take it 
out from queue
      }
    }
};

#endif //TIMEDQACTION_H

von Stefan F. (Gast)


Lesenswert?

Bleibt also noch zu klären, wie genau die Zeiten eingehalten werden 
müssen.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.