Zaseknutí programu z intteruptu?

Nedaří se vám s projektem a nenašli jste vhodné místo, kde se zeptat? Napište sem.
Pravidla fóra
Tohle subfórum je určeno pro konzultaci ucelených nápadů, popřípadě řešení komplexnějších projektů, které opravdu není možné rozdělit na menší části.
Většinu problémů jde rozdělit na menší a ptát se na ně v konkrétních subfórech.
Odpovědět
QRocky
Příspěvky: 36
Registrován: 28 zář 2017, 16:30
Reputation: 0

Zaseknutí programu z intteruptu?

Příspěvek od QRocky » 08 lis 2022, 08:19

Ahojte, prosím o radu.
Program níže je program z Arduino Mega. Mám tam připojený modul ESP01 (Serial3), SD card shield, malý 0,96" displej a na pin č. 18 anemometr jako interrupt pin. Cílem je, aby Arduino zaznamenávalo rychlost větru, logovalo ho na paměťovou kartu spolu s provozním časem od zapnutí a zároveň to posílalo na Thingspeak a zobrazovalo na displeji.

Funguje to dobře, nicméně program občas spadne v momentě, kdy je interruptů mnoho, program se zasekne ve funkci Windy. Přemýšlel jsem, jestli to není zápisem na Thingspeak, když jej například interrupt vyruší, ale nevím. Zaseknutí se stane vždy, jen pokaždé v jiný moment.

Neuměli byste, prosím, někdo poradit? Mám v kódu ještě nějaké výpočty pro rychlost větru, které jsou hodny vymazání, protože je už nepoužívám. Jinak jsem program napsal tak, aby se každou periodu měnil název textového souboru, aby Arduino například po týdnu provozu nezapisoval do textového souboru o objemu několika MB, proto jsem to udělal tak, ať se zápisy rozdělí do více souborů. Pro test funkce jsem dal samozřejmě zatím periodu kratší...

Děkuji

Kód: Vybrat vše

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <SD.h> //Karta


String myAPIkey = "MOJEAPI";  

unsigned short int Mode = 1; // Aktivovaný mód
int ModePeriod = 3600; // Jak často se bude aktivovat nový mód v S
unsigned int ModePeriodLastTime = 0; // Proměnná pro uložení posledního času přepisu módu
String FileName;
String FileNameOriginal =  "_Wind.txt"; // Název souboru, který se nebude nikdy měnit


Adafruit_SSD1306 display(-1);
File myFile;

String Version = "        WL14";
long int WindPuls = 0; // Při každém pulzu se updatuje

const int PinAnemometer = 18; // Pin anemometru

unsigned int WindyPeriod = 5000; // Perioda v ms, kdy se bude počítat rychlost větru
unsigned long int WindyLastTime = 0; // Poslední čas, kdy byl měřen rychlost větru

int Minutes = 0; // Probíhající minuta
int PreviousMinutes = 0; // Proměnná pro poslední hodnotu
float meterPerSecond = 0; // Rychlost větru

// Proměnné pro ESP
long writingTimer = 17; // Jak často mohu zapisovat na Thingspeak
long startTime = 0; // Váže se k zápisu na Thingspeak
long waitTime = 0; // Váže se k zápisu na Thingspeak

const char* server = "api.thingspeak.com";
int WifiPerioda = 300000; // Jak často v ms se pokusit znovu připojit na WiFi
int WifiLastTime = 0;


unsigned char check_connection=0;
unsigned char times_check=0;
boolean error;

void setup() {
Minutes = (millis()/1000)/60;
pinMode(PinAnemometer, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(PinAnemometer), Windy, FALLING); // Pokud dojde k přerušení, volá se funkce Windy


Serial.begin(9600);
Serial3.begin(115200); // Inicializace ESP
startTime = millis(); 
WiFiInit();

display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

if (!SD.begin(53)) {
Serial.println("initialization failed!");
while (1);
}
Serial.println("initialization done.");

ModeReset ();
}

void loop() 
{

  Minutes = (millis()/1000)/60 - PreviousMinutes;
   // Pokud čas dospěje k časové periodě měření, proběhne výpočet rychlosti větru
  if (millis() - WindyLastTime >= WindyPeriod)
    {
      WindyLastTime = millis();
      WindCount();
      Store();     
    }

  if ((millis()/1000) - ModePeriodLastTime >= ModePeriod)
  {
    ModeReset ();
    ModePeriodLastTime = millis()/1000;
  }

    waitTime = millis()-startTime;   
  if (waitTime > (writingTimer*1000)) 
  {
    writeThingSpeak();
    startTime = millis();   
  }

  if (millis() - WifiLastTime >> WifiPerioda) // Opětovné připojení k WiFi
    {
    WifiLastTime = millis();
    WiFiInit();
    }


}


void Windy()
{
  WindPuls++;
  Serial.print("WindPuls: ");
  Serial.println(WindPuls);
}

// Výpočet měření rychlosti větru
void WindCount()
{
  
  int AnemometerRadius = 70; // Poloměr anemometru v mm
  int Rotation = WindPuls/2; // Počet otáček anemometru - dva pulzy na jednu otáčku
  WindPuls = 0; // Pulzy se začnou počítat znovu
 
  float AnemometerCircle = (2*3.14)*AnemometerRadius; // Obvod anemometru
  float AnemometerCircleMetres = AnemometerCircle/1000; // Obvod anemometru do metrů, protože chci rychlost v m/s
  float AnemometerMetresRotated = AnemometerCircleMetres * Rotation; // Takové vzdálenosti anemometr zřejmě dosáhl
  int WindyPeriodSeconds = WindyPeriod/1000; // WindyPerioda na vteřiny

  float WindSpeed = AnemometerMetresRotated / WindyPeriodSeconds;

  meterPerSecond = Rotation * (0.66 / WindyPeriodSeconds); // Tato hodnota sedí lépe s anemometrem


  Serial.print("meterPerSecond: ");
  Serial.println(meterPerSecond);
  
  Serial.print("WindSpeed: ");
  Serial.println(WindSpeed);
  Serial.println(); // Mezera

  DisplayShow(meterPerSecond);
  
  
}

void DisplayShow(float meterPerSecond)
{

  
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.print("Rychlost m/S: ");
  display.print(meterPerSecond);
  //display.display();
  display.setCursor(0, 10);
  display.print("M: ");
  display.print(Mode);
  display.print(Version);
  display.setCursor(0, 20);
  display.print("Provoz: ");
  display.print(Minutes);

  
  if (SD.begin(53))
  {
  display.setCursor(100, 20);
  display.print("SD-A");
  }
  else
  {
  display.setCursor(100, 20);
  display.print("SD-N");  
  }

  
  display.display();
 

}

void Store()
{
  Serial.print("Minutes: ");
  Serial.println(Minutes);
  myFile = SD.open(FileName, FILE_WRITE);
  Serial.print("Store, FileName: ");
  Serial.println(FileName);
  
  if (myFile) 
  {
  myFile.print(meterPerSecond);
  myFile.print(" ");
  myFile.print(Minutes);
  myFile.println("");
  myFile.close();
  }
}

void ModeReset () // V této funkci se změní mód, aby Arduino nepracovalo s příliš objemným poznámkovým blokem
{
  Mode = Mode + 1;
  FileName = int(Mode)+FileNameOriginal; // S novým módem se založí i nový soubor na kartě, aby nevznikl příliš velký soubor
  Serial.println("Mode Reset");
  PreviousMinutes = (millis()/1000)/60;
}

void writeThingSpeak(void)
{
  startThingSpeakCmd();
  // preparacao da string GET
  String getStr = "GET /update?api_key=";
  getStr += myAPIkey;
  getStr +="&field1=";
  getStr += String(meterPerSecond);
  getStr += "\r\n\r\n";
  Serial.print("getStr: ");
  Serial.println(getStr);
  GetThingspeakcmd(getStr); 
}

void startThingSpeakCmd(void)
{
  Serial3.flush();
  String cmd = "AT+CIPSTART=\"TCP\",\"";
  cmd += "52.22.110.199"; // api.thingspeak.com IP address // Změna oproti návodu
  cmd += "\",80";
  Serial3.println(cmd);
  
  Serial.print("Start Commands: ");
  Serial.println(cmd);

  if(Serial3.find("Error"))
  {
    Serial.println("AT+CIPSTART error");
    return;
  }
}

String GetThingspeakcmd(String getStr)
{
  String cmd = "AT+CIPSEND=";
  cmd += String(getStr.length());
  Serial3.println(cmd);
  Serial.println(cmd);

  if(Serial3.find(">"))
  {
    Serial3.print(getStr);
    Serial.println(getStr);
    delay(500);
    String messageBody = "";

    while (Serial3.available())
    {
      String line = Serial3.readStringUntil('\n');
      if (line.length() == 1) 
      { 
        messageBody = Serial3.readStringUntil('\n');
      }
    }

    Serial.print("MessageBody received: ");
    Serial.println(messageBody);
    return messageBody;
  }
  else
  {
    Serial3.println("AT+CIPCLOSE");     
    Serial.println("AT+CIPCLOSE"); 
  } 
}

void WiFiInit() // Nastavení WiFi
{
  int TimeNow = millis();
  Serial3.println("AT+RST");
  Serial3.println("AT+CWMODE=1");
  delay(2000);
  Serial.println("Connecting to Wifi");
   while(check_connection==0)
  {
  if (millis() - TimeNow >= 15000)
  {
    Serial.println("Opoustim smycku");
    break;
  }
  
  
  Serial.print(".");
  Serial3.print("AT+CWJAP=\"Název Wifi\",\"Heslo na Wifi\"\r\n");
  Serial3.setTimeout(5000);
 if(Serial3.find("WIFI CONNECTED\r\n")==1)
 {
 Serial.println("WIFI CONNECTED");

 break;
 }
 times_check++;
 if(times_check>3) 
 {
  times_check=0;
   Serial.println("Trying to Reconnect..");

  }
  }
}
Kousek z výstupu na Serial - v příloze dva textové soubory s výstupy ze dvou testů
20:54:24.023 -> writeThingSpeak
20:54:24.023 -> startThingSpeakCmd
20:54:24.063 -> Start Commands: AT+CIPSTART="TCP","52.22.110.199",80
20:54:24.143 -> Windy
20:54:24.143 -> WindPuls: 15
20:54:24.422 -> Windy
20:54:24.422 -> WindPuls: 16
20:54:24.663 -> Windy
20:54:24.663 -> WindPuls: 17
20:54:24.903 -> Windy
20:54:24.903 -> WindPuls: 18
20:54:25.183 -> Windy
20:54:25.183 -> WindPuls: 19
20:54:25.423 -> Windy
20:54:25.423 -> WindPuls: 20
20:54:25.662 -> Windy
20:54:25.662 -> WindPuls: 21
20:54:25.943 -> Windy
20:54:25.943 -> WindPuls: 22
20:54:26.183 -> Windy
20:54:26.183 -> WindPuls: 23
20:54:26.463 -> Windy
20:54:26.463 -> WindPuls: 24
20:54:26.703 -> Windy
20:54:26.703 -> WindPuls: 25
20:54:26.983 -> Windy
20:54:26.983 -> WindPuls: 26
20:54:27.223 -> Windy
20:54:27.223 -> WindPuls: 27
20:54:27.503 -> Windy
Přílohy
Analýza_02.txt
(41.83 KiB) Staženo 49 x
Analýza_01.txt
(163.77 KiB) Staženo 46 x

ondraN
Příspěvky: 932
Registrován: 08 srp 2019, 20:01
Reputation: 0

Re: Zaseknutí programu z intteruptu?

Příspěvek od ondraN » 08 lis 2022, 09:53

Mohlo by to způsobovat použití serial.print v obsluze přerušení Windy(). Nestane se to vždy, ale jen pokud je zaplněný tiskový buffer a čeká se na jeho uvolnění po odeslání znaku. To odeslání je ohlášeno přes interrupt, jenže v obsluze inetrruptu je zakázané přerušení. Takže se čeká a čeká donekonečna. Takže, pokud tam chceš použít serial.print, tak musíš zkontrolavat volné místo v bufferu (.available). Pokud se ti do něj potřebný počet znaků nevejde, tak netisknout. Obecně je daleko lepší si v obsluze interruptu jen nastavit nějaký globální flag a na ten pak zareagovat v superloopu a po ukončení printu ho shodit.

QRocky
Příspěvky: 36
Registrován: 28 zář 2017, 16:30
Reputation: 0

Re: Zaseknutí programu z intteruptu?

Příspěvek od QRocky » 08 lis 2022, 12:18

ondraN píše:
08 lis 2022, 09:53
Mohlo by to způsobovat použití serial.print v obsluze přerušení Windy(). Nestane se to vždy, ale jen pokud je zaplněný tiskový buffer a čeká se na jeho uvolnění po odeslání znaku. To odeslání je ohlášeno přes interrupt, jenže v obsluze inetrruptu je zakázané přerušení. Takže se čeká a čeká donekonečna. Takže, pokud tam chceš použít serial.print, tak musíš zkontrolavat volné místo v bufferu (.available). Pokud se ti do něj potřebný počet znaků nevejde, tak netisknout. Obecně je daleko lepší si v obsluze interruptu jen nastavit nějaký globální flag a na ten pak zareagovat v superloopu a po ukončení printu ho shodit.
Ahoj a děkuji za odpověď.
Takže, pokud jsem to správně pochopil, myslíš, že by mohlo pomoci smazání těchto dvou řádků z funkce Windy: Serial.print("WindPuls: ");
Serial.println(WindPuls);? Já je tam obecně nepotřebuji, mám to tam jen abych viděl na výčtu, co program dělá...

Díky moc

ondraN
Příspěvky: 932
Registrován: 08 srp 2019, 20:01
Reputation: 0

Re: Zaseknutí programu z intteruptu?

Příspěvek od ondraN » 08 lis 2022, 14:06

Tak, nevím jestli to určitě pomůže, natolik jsem ten program nezkoumal, ale je to potenciální problém, který to může zaseknout. Vyhoď to a uvidíš. Další problémy se mohou skrývat v různých nechtěných konfliktech mezi knihovnami, ale to se hledá nejhůře, takže nejdřív to jednoduché :mrgreen:

Ještě si uprav definici proměnné

Kód: Vybrat vše

volatile long int WindPuls = 0;
protože je použitá v přerušení a není žádoucí, aby ji kompiler optimalizoval.

Uživatelský avatar
kiRRow
Příspěvky: 1151
Registrován: 07 kvě 2019, 07:03
Reputation: 0
Bydliště: Opava

Re: Zaseknutí programu z intteruptu?

Příspěvek od kiRRow » 08 lis 2022, 15:10

Taky jsem se zarazil stím výpisem v interuptu ... lepší je fakt si do interuptu nastavit
vypisInfo = true;

loop(){
...
if(vypisInfo){
//Serial.....;
vypisInfo = false;
}
...
}

peterple
Příspěvky: 156
Registrován: 22 zář 2021, 20:20
Reputation: 0

Re: Zaseknutí programu z intteruptu?

Příspěvek od peterple » 08 lis 2022, 17:28

No a ešte tam máš problematický kúsok kódu tu. Nevedie to síce k zaseknutiu ale iba k potencionálnemu chybnému výpočtu.

Kód: Vybrat vše

  int Rotation = WindPuls/2; // Počet otáček anemometru - dva pulzy na jednu otáčku
  WindPuls = 0; // Pulzy se začnou počítat znovu
Nemáš vyriešený atomický prístup k premennej WindPuls. Problém je ten že táto premenná sa môže zmeniť asynchrónne s chodom hlavného programu (teda hocikedy). Ak je tam napísané hocikedy tak to znamená naozaj hocikedy. Treba si uvedomiť že MEGA je 8 bitový procesor. A teda môže vykonávať operácie iba s bytom. Premenná je ale long int teda 4 byte. Ak tam ideš iba strčiť nulu, alebo to podeliť dvomi, tak to musíš urobiť celou radou inštrukcií. Ak v tom čase nastane náhodou prerušenie tak nastane to že to rozbije hodnotu uchovávanú v tej premennej.
Ak chceš teda čítať alebo dokonca meniť hodnotu premenej musíš zabezpečiť aby k nej v tom okamihu pristupoval výlučne iba jeden z kódov. Robí sa to zakázaním prerušenia na nevyhnutne krátku dobu kedy sa premenná číta, alebo mení.

Je to v podstate presne rovnaké ako funguje millis. Niekde v prerušení sa posúva počítadlo s počtom milisekund a ty ho potom čítaš pomocou millis. Pozri si teda ako to majú urobené arduinisti v millis a urob to rovnako.

Uživatelský avatar
gilhad
Příspěvky: 778
Registrován: 07 bře 2018, 11:22
Reputation: 0

Re: Zaseknutí programu z intteruptu?

Příspěvek od gilhad » 09 lis 2022, 03:58

Taky bych doporučoval věnovat spoustu pozornosti tomu, co se děje v interruptu (obzvláště když pracuje s více byty informace - je na zvážení, jak to atomicky číst, například zda na čtení zakázat interrupty, nebo to řešit jinými triky).

A pokud zapisuju na kartu, nebo komunikuju s něčím jiným protokolem, který může být časově závislý, tak bych asi právě na dobu té komunikace interrupty zakázal (a hned po komunikaci zase povolil) - a nějak si to zorganizoval tak, aby mi to nezničilo výsledky měření (protože zakázané interrupty = ignoruju pulzy od anemometru) - třeba tak, že bych komunikaci jel v jednom bloku a měření větru v jiném a ty bloky by se nepotkávaly v čase.

Tyhle časové závislosti dokážou být hodně zákeřné a občas i dost obskurním způsobem ... :?

afilip
Příspěvky: 116
Registrován: 26 črc 2017, 16:34
Reputation: 0
Kontaktovat uživatele:

Re: Zaseknutí programu z intteruptu?

Příspěvek od afilip » 11 lis 2022, 10:07

To se divím, že ti to vůbec na TS odesílá. Já jsem to řešil nedávno u rainmetru.
Té rutině odesílání na TS opravdu vadí interupt. Je teda potřeba před odesíláním na TS interupt zakázat, a potom zase povolit.
Zkus toto:

if (waitTime > (writingTimer*1000))
{
detachInterrupt(0);
writeThingSpeak();
attachInterrupt(digitalPinToInterrupt(PinAnemometer), Windy, FALLING);
startTime = millis();
}

Samozřejmě to po dobu odesílání nebude měřit, ale s tím asi nic neuděláš. ;)

ondraN
Příspěvky: 932
Registrován: 08 srp 2019, 20:01
Reputation: 0

Re: Zaseknutí programu z intteruptu?

Příspěvek od ondraN » 11 lis 2022, 13:02

A už jsme zase u toho. Naprosto nechápu, proč si tvůrci těchto knihoven, nedají alepoň chvilku práci s dokumentací. To je bolavá noha a ostuda většiny těchto knihoven. A přitom by stačila jen setina času, kterou na tom stráví :?

Odpovědět

Kdo je online

Uživatelé prohlížející si toto fórum: Žádní registrovaní uživatelé a 14 hostů