nodemcu webserver + led villogás

Sziasztok!

 

Kezdő vagyok a nodemcu világában. arduino ide -t használok a nodemcu v3.0 (8266) programozására. A legutóbb amit megnéztem kódot és kipróbáltam az egy egyszerű webserver implementáció amit ha megnyitok a nodemcu-ról a D0-ra kötött ledet egy a felületen lévő ON/OFF kapcsolóval tudok be- illetve kikapcsolni. Teljesen jól működött, de elgondolkoztam azon, hogy ha nem be- illetve kikapcsolni szeretném, hanem villogtatni, tehát az ON gomb hatására ne bekapcsoljon, hanem villogjon, az OFF-ra pedig kapcsoljon ki, akkor azt hogyan lehet megoldani? A webserver kódja vár a kliens kapcsolatra, ha ez meg van akkor vár a kliens interakciójára. Ha kattint, akkor állítja a statikus státuszt. Hol és hogyan lehet ezt a kódot úgy átírni, hogy ne statikus legyen a led státusza, hanem valamilyen frekvencián villogjon? Hogyan tud a villogtató kód folyamatosan futni akkor is, amikor a void loop éppen arra vár, hogy a weben csatkakozott kliens valamit nyomjon? Megszakítás? időzítő?

Ezt a kódot írtam át 2 ledról 1 ledre és most azt szeretném, hogy ON -> villog a led, OFF -> kikapcsol a led

https://www.electronics-lab.com/project/nodemcu-esp8266-webserver-tutor…

előre is köszi a segítséget!

Hozzászólások

a kódot dobd fel pastebin-re

// Happy debugging, suckers
#define true (rand() > 10)

Szerkesztve: 2022. 12. 17., szo – 19:16

Van a programban az a rész, ami a LED-ek kapcsolásával foglalkozik:

// turns the GPIOs on and off

Ahol bekapcsolod a LED-et, ott a sima bekapcsolás helyett váltsd át a kimenet állapotát és utána várj valamennyit (a villogtatás periódusidejének felét).

Vagyis

digitalWrite(greenled, HIGH);

helyett valami ilyesmit írj:

digitalWrite(greenled, !digitalRead(greenled));
delay(250);

Gondolom a jatek es a tanulas a lenyeg, ha penzert csinalod (es nem te tartod karban) akkor persze jo az a vegtelen ciklus :)

RTOS fut a rajta? Teheted kulon taskba a LED kapcsolgatasat, es a webserver csak egy flaget allitana at.

Esetleg, a webserver le is allithatja a LED taksot, illetve ujraindithatja, ha amugy takarekoskodni kell az energiaval vagy CPU idovel.

nekem is az a bajom, hogy ez a rész a loop-ban

char c = client.read();

blokkolja a futást, hiszen vár arra hogy a weben valamit kapjon. gondoltam egy interrupt jó lehet, de olyan mintha itt csak hw interrupt lenne. csak GPIO-hoz lehet kötni interruptot és ahhoz adni handlert, de nekem valamifajta időzítő kellene, ami bizonyos időnkét a handlerre tenné a vezérlést. viszont nem találok sw interruptot, amit mondjuk időzíteni lehetne.

ráadásul a led villogtatása be-illetve kikapcsolása mellé azt is meg szeretném adni a webes felületen, hogy ha be van kapcsolva, akkor milyen frekvencián villogjon. ez megint csak egy időzítőért kiált és egy interruptért. Ha rányomok, hogy bekapcsol, akkor időzítőhöz rendeli a handlert, a handler meg vizsgálná a státuszt, ha LOW akkor HIGH-re állítja, ha HIGH, akkor LOW-ra így villogna és a beadott frekvencián, hiszen a megadott időben kerülne ide a vezérlés. ha OFF-ra kapcsolom, akkor törli az interruptot, mert akkor minek fusson.

létezik ilyen időzítőhöz köthető interrupt? Vagy ha nem akkor hogy lehet megoldani?

megnéztem de az a kód nem igazán hasonlít arra amit nodemcu esetén szoktam látni, ráadásul ez a sei() funkció sem fordul elő ha rákeresek nodemcu kapcsán egy kódban sem.

közben aztán megtaláltam  szininímáit nodemcu kapcsán:

noInterrupts(); és   interrupts();

de ezek csak annyit tudnak, hogy az első tiltja a második engedélyezi az interruptot. a közöttük lévő kód nem szakítható meg interrupttal. egyiknek sincs paramétere. ez a cli() sei() -nek felel meg ott ahonnan a kódot küldted.

ezek csak annyit tudnak, hogy az első tiltja a második engedélyezi az interruptot. a közöttük lévő kód nem szakítható meg interrupttal

Az interrupt pont arra kell, hogy a futó program bármikor megszakítható legyen. Amikor jön egy megszakításkérés, a CPU végrehajtja még az éppen aktuális utasítást, majd következik az adott megszakításkérést kiszolgáló rutin. Nyilván csak akkor megy a dolog, ha engedélyezve van az adott megszakítás, illetve olyan megszakítás is létezik itt-ott, ami nem tiltható a szokásos módon (pl. NMI).

Na jó, de ha van egy nem atomikus műveleted, s annak változóját használod IT-ből is, akkor kellhet az alapszintre IT tiltás, hogy a művelet az IT handler számára atomikusnak tűnjön. Teszem azt, 32 bites architektúrán volatile int64_t változón végzett összeadás. Elég kellemetlen, ha az alsó 32 bit már módosításra került, s ekkor becsap az IT, miközben a felső 32 bit még a régi.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Alapvetően ilyesmiről van szó, de a visszaállítást a programozónak kell kezdeményeznie egy utasítással (pl. RETI, IRET)

https://jnz.dk/z80/reti.html

https://www.hobbyprojects.com/8051_tutorial/reti_return_from_interrupt…

https://faydoc.tripod.com/cpu/iret.htm

Itt több dolog keveredik, nem ugyanarról beszélünk.

Van a státusz mentés, ez részben hardware-esen, részben software-esen történik, s azokra a regiszterekre és változókra vonatkozik, amelyeket használunk IT-ből, s nem írhatunk felül, hiszen az alapszinten futó program nem számít arra, hogy megszakítás fog érkezni. Tegyük fel, ezt jól kezeljük.

Amiről én beszéltem, az viszont az az eset, amikor egy változót módosítunk alap szintről, majd azt IT-ből felhasználjuk, vagy fordítva, tehát IT-ből módosítjuk, s alap szinten használjuk fel. Itt akkor van baj, ha a művelet nem atomikus, azaz több gépi utasításból áll, amelyek közé becsaphat az IT.

Ha egy 64 bites változót kiolvasunk, de 32 bites az architektúránk, ezért ezt két lépésben tesszük meg, s az alsó 32 bitet kiolvastuk, becsap az IT, megváltoztatja a 64 bites változónkat, visszatérés után kiolvassuk a felső 32 bitet, akkor felemás lesz a 64 bites számunk: [új felső 32 bit] [régi alsó 32 bit]. Tehát ilyen szám sohasem volt, sem az IT előtt, sem azt követően, de nekünk sikerült egy öszvér értéket kreálnunk, ami a legtöbb esetben súlyos programhibát eredményez futásidőben. Mi több, a forráskódban nem fog látszani a hiba, úgy tűnik majd, hogy minden jól van írva. Sőt, ritkán fog előjönni a hiba, mert annak az a feltétele, hogy épp a 64 bites nem atomikus műveletvégzés közben csapjon be az IT.

Az egyik megoldás, hogy atomikussá tesszük a műveletet azzal, hogy a két 32 bit adatmozgatás idejére tiltjuk a globális IT-t. Szofisztikáltab, ha csak azt az IT-t tiltjuk, amelyiknek a handlere szintén használja ezt a változót. Lehet azzal operálni, hogy bevezetünk valamilyen flag-et, s azzal jelezzük az IT rutin számára, hogy épp a 64 bites változót kezeljük alapszintről, hagyja békén azt a változót, persze, ha ez opció, mert másfelől nyilván nem véletlenül nyúlna az IT ahhoz a változóhoz.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Pont az a lényeg, hogy ugyanazon a változón dolgozik az IT rutin, mint az alapszint, tehát nem elszeparált memóriaterületen. Legyen 8 bites CPU, az IT rutin növeljen egy 16 bites változót, alapszintről ki akarjuk írni, hány it csapott be eddig.

A változónk legyen 0x00ff éppen. Kiolvassuk az alsó byte-ot: 0xff. Becsap az IT: 0x0100 lesz a változónk. Visszatérünk, kiolvassuk a felső byte-ot: 0x01. Összerakva, kiírva 0x01ff, azaz kiírjuk, hogy 511 IT csapott be eddig. Holott helyes lett volna az is, ha 255-öt, meg az is, ha 256-ot írunk ki, de nekünk sikerül a hibás 511-et kiírni.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

de vannak olyen folyamatok, amiknek nem jó ha megszakadnak, mert fontosak számodra, hogy a lehető leggyorsabban lefussanak. Abban a kódban is van program rész aminek a futása előtt tiltja az interruptot, utána meg engedélyezi, amit az előbb beküldtem. Minden programozási nyelveben tiltahtók az interruptok, de nem mindegyik természetesen.

úgy tűnik nyomra vezettél, mert találtam működő kódot 8266-ra timer interrupttal, csak itt timer1_attachInterrupt a neve :-)

lefordítottam, teéjesen jól működik, a led villog és interrupt vezérli ezt a villogást. már csak a villogás frekvenciájának a beállítását kéne megérteni :-)

forrás: https://forum.arduino.cc/t/esp8266-interrupters-timer/957874

bool led_state = true; 
unsigned long cycle_time = 1; 
unsigned long Output     = 10; 

void blink_led()
{
  noInterrupts();
  if (led_state) 
  {
    timer1_detachInterrupt();
    timer1_disable(); 
    timer1_attachInterrupt(blink_led);
    timer1_isr_init();
    timer1_enable(TIM_DIV256, TIM_EDGE, TIM_SINGLE);
    T1L = ((F_CPU * cycle_time) & 0x7FFFFF); 
    if ((T1C & (1 << TCIT)) == 0) TEIE |= TEIE1;//edge int enable
    //timer1_write(F_CPU * (cycle_time));
  }
  else 
  {
    timer1_detachInterrupt();
    timer1_disable(); 
    timer1_attachInterrupt(blink_led);
    timer1_isr_init();
    timer1_enable(TIM_DIV256, TIM_EDGE, TIM_SINGLE);
    timer1_write(F_CPU * (Output));
  }

  
  digitalWrite(LED_BUILTIN, led_state);   
  led_state = !led_state; 
  interrupts(); 
}

void setup() {
 pinMode(LED_BUILTIN, OUTPUT);
 blink_led(); 
}

void loop() { ; }

ezeknek a timereknek mi az alap koncepciója? elindul egy számláló és az általam paraméterben megadott érték elérésekor meghívja a handlert? előrefele számol, vagy visszafele és a 0 elérésekor aktiválódik? vagy mikor adja a vezérlést a handlerre?

elég szűkszavúak a doksik egyelőre nem tudtam kihámozni még ezt sem belőle

Szerintem te nem programoztál korábban mikrokontrollert assembly-ben vagy C-ben. ;) Amikor leérsz a hardware közelébe, akkor nem elég sterilen a kódot nézni, mik vannak #define-olva, hány bitesre van deklarálva valami, és így tovább. Meg kell nézni a processzor katalóguslapját. Tovább nyomorítja az ember lelkét, hogy a katalógus sok esetben csak egy összefoglaló vázlat, az adott periféria leírása önálló fejezetben van, amelyet a katalógus meghivatkoz. Sok esetben nem linkkel, csak címmel, szóval keresni kell a neten. Külön öröm, hogy ott viszont úgy írják le az adott periféria működését, hogy az több hasonló processzorban is lehet, és visszahivatkozzák a processzor adatlapját, hogy az apró eltéréseket meg abban találod.

A kérdésedre az a válaszom, hogy nem tudom. Lehet olyan, hogy előre számol, s van egy statikus regiszterbe írt érték, ha azt elérte, akkor a következő órajelre 0 kerül bele, tehát ha n lépéses periodicitást akarsz, akkor ebbe a period regiszterbe n-1-et kell írni. De lehet olyan, ami hátra számol, majd a 0-t követően betölti magába a period regiszter tartalmát. Ezen felül mindenféle egyéb regiszterek bitmezőiben szoktak lenni előosztók, meg utóosztók - prescaler, postscaler -, van, amit egyesével lehet programozni, van, amit kettő egész kitevőjű hatványa szerint.

Szóval nem úszod meg, muszáj megnézni a dokumentációt. Ott szoktak blokksémát is közölni, néha logikai elemekből megrajzolt kapcsolási rajz féleséget is.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Szerkesztve: 2022. 12. 17., szo – 20:17

- töröltem mert rosszul használtam a válasz funkciót -

Ez rossz:

if (currentMillis - previousMillis >= interval) {
    // save the last time you blinked the LED
    previousMillis = currentMillis;

Helyesen:

if ((unsigned long) (currentMillis - previousMillis) >= interval) {
    // save the last time you blinked the LED
    previousMillis += interval;

Ugye az első esetben a futásidőből adódó késések mindig hozzáadódnak a periódusidőhöz, az általam javasolt megoldással a futásidő legfeljebb jittert okoz, de a hiba nem halmozódik.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

ez nem interruptos, ugyan úgy a loop() -ban kell futnia folyamatosan, az eredeti problémám ugye az volt, hogy a webserveren vezérelve ne csak be vagy kikapcsolni lehessen, hanem villogtatni. Közben megoldottam mert tudtatok segíteni azzal, hogy irányba állítottatok.

Ja, még valami. Ez kifogás annyiban, hogy egy műszer programját megírtam nem blokkolósra, ahol kell, ott állapotautomaták vándorolnak végig a megfelelő állapotokon, static változóban tárolva az épp aktuális állapotot, így vissza tud térni, s a következő körös híváskor tudja, hol tartott legutóbb. Persze tudom, hogy a blokkolós kód sokszor könnyebben végiggondolható, várunk egy zárt ciklusban, amíg valami bekövetkezik, miegymás, csak közben a CPU semmi mást nem tud csinálni. Érdemes jól szervezni a kódot, mert onnantól kinyílik a világ, nem szorítod magad sarokba. Ettől persze még belátható idő alatt lefutó ciklusok lehetnek egy-egy ágában a programnak, egy memcpy() függvényt nem kell átírni úgy, hogy egyszerre csak egy byte-ot másoljon, majd amikor legközelebb erre jár a végrehajtásban a CPU, jön a következő byte... :)

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

ez itt interrupt vezérelten működik és frankón villog ha bekapcsolom


#include <ESP8266WiFi.h>

// Add wifi access point credentiaals
const char* ssid     = "ssid";
const char* password = "password";


WiFiServer server(80);// Set port to 80


String header; // This storees the HTTP request



int led = D1;

bool led_state = false;

void blink_led()
{
  noInterrupts();
  //Serial.println("in blink_led interrupt. ");
  digitalWrite(led, led_state);
  led_state = !led_state;
  timer1_write(100000);
  interrupts(); 
}

void setup() {
  Serial.begin(115200);
 // Set the pinmode of the pins to which the LEDs are connected and turn them low to prevent flunctuations
  pinMode(led, OUTPUT);
  digitalWrite(led, LOW);
  //connect to access point
  WiFi.begin(ssid, password);
  Serial.print("Connecting to ");
  Serial.println(ssid);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // Print local IP address and start web server
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());// this will display the Ip address of the Pi which should be entered into your browser
  server.begin();
}

void loop(){
  WiFiClient client = server.available();   // Listen for incoming clients

  if (client) {                             // If a new client connects,
    String currentLine = "";                // make a String to hold incoming data from the client
    while (client.connected()) {            // loop while the client's connected
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        header += c;
        if (c == '\n') {                    // if the byte is a newline character
          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println();
            
            // turns the GPIOs on and off
            if (header.indexOf("GET /led/on") >= 0) {
              Serial.println("led on");
              led_state = true;
              timer1_attachInterrupt(blink_led);
              timer1_isr_init();
              timer1_enable(TIM_DIV256, TIM_EDGE, TIM_SINGLE);
              timer1_write(100000);
              digitalWrite(led, HIGH);
            } else if (header.indexOf("GET /led/off") >= 0) {
              Serial.println("led off");
              led_state = false;
              timer1_detachInterrupt();
              timer1_disable();
              digitalWrite(led, LOW);
            }
       
            // Display the HTML web page
            client.println("<!DOCTYPE html><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");
            // CSS to style the on/off buttons 
            // Feel free to change the background-color and font-size attributes to fit your preferences
            client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");
            client.println(".button { background-color: #195B6A; border: none; color: white; padding: 16px 40px;");
            client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");
            client.println(".button2 {background-color: #77878A;}</style></head>");
            
            // Web Page Heading
            client.println("<body><h1>ESP8266 Web Server</h1>");
            
            // Display current state, and ON/OFF buttons for GPIO 5  
            //client.println("<p>LED - State " + led_state + "</p>");
            // If the green LED is off, it displays the ON button       
            if (!led_state) {
              client.println("<p><a href=\"/led/on\"><button class=\"button\">BLINKING</button></a></p>");
            } else {
              client.println("<p><a href=\"/led/off\"><button class=\"button button2\">OFF</button></a></p>");
            } 
               
                        client.println("</body></html>");
            
            // The HTTP response ends with another blank line
            client.println();
            // Break out of the while loop
            break;
          } else { // if you got a newline, then clear currentLine
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }
      }
    }
    // Clear the header variable
    header = "";
    // Close the connection
    client.stop();
    
  
    Serial.println("Client disconnected.");
    Serial.println("");
  }
}

Ez a timer olyan, hogy mindig bele kell rúgni? Nem tartom valószínűnek, hogy ne lenne olyan üzemmódja, amikor adott időközönként IT-t kér beavatkozás nélkül. Tehát amikor újra tölti magát hardware-esen valami default értékkel, vagy, ha előre számol, akkor egy adott érték elérésénél törlődik.

A blink_led() függvényben fájlalom azt a

timer1_write(100000);

sort. Ugyanaz a bajom vele, mint amire írtam, hogy az a kód bugos. Késni fog a LED, halmozódni fog a hiba. Jó, egy öncélú LED villogtatásnál ez kb. mindegy, de attól még igénytelen.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE