Pokazeny String, Preco?

Sekcia určená pre Arduino nadšencov

Moderátor: Moderátori

TomasNM
Zaslúžilý člen
Zaslúžilý člen
Príspevky: 1371
Dátum registrácie: 22 Jan 2012, 19:24
Bydlisko: Nové Mesto nad Váhom
Vek: 43

Pokazeny String, Preco?

Príspevok od používateľa TomasNM » 05 Apr 2022, 14:27

Pani,

snazim sa komunikovat s externym zariadenim cez LAN a http server/client, POST metodou.
Data (url a data) mu poslem cez seriovu linku, tie nasledne ukladam do String premennych RSurl a RSdata.
Stane sa vsak nieco mne nezname a v momente, ked chcem spustit POST prikaz a pouzit tieto dva stringy, RSurl je "pokazeny" a teda cielove zariadenie(server) mi vrati spravu, ze nepozna prikaz.
Mozno sa podobny problem tyka aj stringu RSdata, ale ten je kratky a nateraz som nevypozoroval divne spravanie.

Preto sa chcem spytat, co sa tam moze diat, ze sa tento string pokazi?

Problem je pritomny v Arduino Nano V3 (Atmega 328p), pricom Arduino Mega 2560 s tym nema problem a funguje s tym istym softwarom korektne.

Dakujem za rady.

Cast kodu:

Kód: Vybrať všetko

void serialEvent() {
  String RS="";
  int c=0;
  delayMicroseconds(3000);
  while (Serial.available() > 0) {
    char inChar = (char)Serial.read(); 
    RS += inChar;
    if (inChar == '\n') { RS=RS.substring(0,RS.length()-1);}
    }
  
  if(RS.substring(0,8)=="POSTURL ") {
    RSurl="";
    while(c<RS.length()-8) { RSurl+=RS[8+c];c++;}
    Serial.println("POST URL: "+RSurl);  
  }
  if(RS.substring(0,9)=="POSTDATA ") {
    RSdata="";
    while(c<RS.length()-9) { RSdata+=RS[9+c];c++;}
    Serial.println("POST DATA: "+RSdata);  
  }
  if(RS.substring(0,12)=="POST REQUEST") {
    Serial.println("POST COMMAND SENT"); 
//    RSurl="http://192.168.10.101/Services/GetWeldResult";
//    RSdata="{\"Sid\":12345}";
    Serial.println("Kontrola URL: "+RSurl);  
    Serial.println("Kontrola DATA: "+RSdata);  
    Serial.println("...");  
    POSTrequest(RSurl,RSdata);
  }
  Serial.flush(); 
}
Vypis terminalu (vsimnite si poskodenu url v riadku "Kontrola").
Najprv mu poslem z terminalu POSTURL ---, cim nastavi RSurl string.
Potom poslem POSTDATA ---, nastavi RSdata string,
Nakoniec to odstartujem prikazom POST REQUEST

Kód: Vybrať všetko

Initialize Ethernet with DHCP:
Connecting to 192.168.10.101...
Connected to 192.168.10.101

POST URL: http://192.168.10.101/Services/GetWeldResult
POST DATA: {"Sid":12345}
POST COMMAND SENT
Kontrola URL: http://192.168.10.101/Services/Geö Result
Kontrola DATA: {"Sid":12345}
...

Response:
>>>
HTTP/1.1 404 Not Found
Content-Length: 122
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8

<html><head><title>Firmware Upload</title></head><body><h2>Server Message:</h2>URL Not found. 192.168.10.101</body></html>
<
<<
0

Používateľov profilový obrázok
Andy99
Stály člen
Stály člen
Príspevky: 339
Dátum registrácie: 05 Mar 2008, 00:00
Bydlisko: BA
Vek: 35

Re: Pokazeny String, Preco?

Príspevok od používateľa Andy99 » 05 Apr 2022, 16:18

Toto je plny kod, alebo len nejaka cast? Ocividne len nejaka cast... nakolko nie je vidno inicializaciu RSurl a RSdata. Tipujem, ze niekde prepisujes pamat, nakolko terminal zobrazuje nejake neencodovane znaky. Dovod preco to ide na "vacsom" MCU moze byt, ze kompilator rozlozil premenne v pamati inym sp. a teda sa neprelinaju.
0

Používateľov profilový obrázok
budvar10
Pokročilý člen
Pokročilý člen
Príspevky: 983
Dátum registrácie: 15 Dec 2014, 10:55
Bydlisko: Košice

Re: Pokazeny String, Preco?

Príspevok od používateľa budvar10 » 05 Apr 2022, 16:28

Zbežne som pozrel ale z toho úseku programu nevidno problém v algoritme, aspoň som si ja nič také nevšimol. Mohol by to byť problém s pamäťou.
Ale...
Takéto používanie String na ATmege je "prasárna" (sorry za výraz). Začneš nulovou veľkosťou reťazca a každé pridávanie znaku je spojené s jeho realokáciou, t.j. aj navyše kopec inštrukcií. Atmega nie je PC. Zaťažuješ MCU a fragmentuješ pamäť. Trochu zložitejší program a začne padať. Prečo to robíš v cykle? Veď String sa dá jednoducho priradiť.

Kód: Vybrať všetko

RSurl = RS.substring(8);
String sa dá vytvoriť aj s určitou dĺžkou, napr. ten RS by som tak dal.
Ďalej cykly. Ak už treba cyklus ako tieto (akože nie sú potrebné), prečo tam nedáš for? A c má byť nastavené na počiatočnú hodnotu v ňom.

Ak vynecháš časť s RSdata, celý if, čo vypíše kontrola?
0

Používateľov profilový obrázok
lucky62
Zaslúžilý člen
Zaslúžilý člen
Príspevky: 1151
Dátum registrácie: 14 Feb 2012, 20:16
Bydlisko: Liptovský Mikuláš, SK

Re: Pokazeny String, Preco?

Príspevok od používateľa lucky62 » 05 Apr 2022, 16:41

Je možné, že by to Nano nestíhalo?...
Viem, že frekvenciu má Nano aj Mega rovnakú 16MHz, ale periférie (teda aj UART) sú odlišné...

Osobne by som ošetril komunikáciu pomocou prerušenia, nie v programovej slučke...
0
....môj bazar....

...Nikdy sa nehádaj s blbcom...

Používateľov profilový obrázok
budvar10
Pokročilý člen
Pokročilý člen
Príspevky: 983
Dátum registrácie: 15 Dec 2014, 10:55
Bydlisko: Košice

Re: Pokazeny String, Preco?

Príspevok od používateľa budvar10 » 05 Apr 2022, 18:13

Nano to stíha, pokiaľ ide o tú funkciu. Bude to najskôr tým, čo už písal aj Andy. Koruptnutá pamäť.
Serial (HW) je ošetrený prerušeniami a má vlasné ISR. Funkcia available() je test, či sú prijaté dáta v buffri. Tak je to dobre, ako to je.
0

peterple
Ultimate člen
Ultimate člen
Príspevky: 2328
Dátum registrácie: 25 Jún 2013, 21:06
Bydlisko: Krajné
Vek: 57
Kontaktovať používateľa:

Re: Pokazeny String, Preco?

Príspevok od používateľa peterple » 05 Apr 2022, 19:16

Súhlasím s budvarom. Na niečom čo má 2kiBy SRAM na všetko (globálne premenné, hromada-heap a zásobník- stack) nemôžeš realokovať heap takýmto spôsobom. Vznikne fragmentácia a potom už nie je volná pamäť plus zrazí sa ti zásobník s datami a je zle.
0
Ukáž múdremu chybu a on sa ti poďakuje. Ukáž chybu hlupákovi a on sa urazí.

TomasNM
Zaslúžilý člen
Zaslúžilý člen
Príspevky: 1371
Dátum registrácie: 22 Jan 2012, 19:24
Bydlisko: Nové Mesto nad Váhom
Vek: 43

Re: Pokazeny String, Preco?

Príspevok od používateľa TomasNM » 05 Apr 2022, 20:29

Pani,

dakujem za rady.

Andy99>
Dal som sem iba cast kodu.
V loop() je iba vypis buffera na serial, ak server odpovie, inak iba v pripade neuspesnej komunikacie so serverom zopar hlasok a stop.
Inicializacia vsetkych stringov je na zaciatku, teda platia globalne.

budvar10>
Uz som neskor zistil, ze je tam problem s pamatou.
Mam tam alokovany buffer na odpoved zo servera na 1000Byte. Som to znizil na 600 a uz to vypada funkcne. To, ze zatazujem MCU instrukciami az tolko nevadi, ma na to casu dost. Zatial som sa neskamaratil so zjednodusenim pouzivania stringov, viem ze su narocne.
Nerozumiem. Preco, co robim v cykle? Jediny cyklus je tam while.
Myslis odstranit while a napisat to takto?

Kód: Vybrať všetko

RSurl=RS.subString(8,RS.length()-8);
Ano, dalo by sa aj takto, len mi nedoslo, ze by to mohol byt problem vzhladom na to, ze hned po tomto cykle to kontrolne vypisem a je to OK.
c je nulovane pred vyhodnotenim/vetvenim, na tom nevidim nic zle.

lucky62>
Nebezi to v loope, ale iba v pripade, ze po seriovej linke nieco pride sa tato cast kodu vykona raz.

peterple>
RAM je malo, ale pri kompilacii mi pisalo cca 1200B volnych, preto som ratal, ze este je to OK.
On ten kod samotny neni komplikovany.
Spekuloval som tie stringy alokovat nejakym sposobom na pevnu dlzku, aby sa nefragmentoval, ale som nebol uspesny.

Pre vsetkych>
Mozte prosim navrhnut, ako by ste vy nahradili v tomto pripade tie stringy a ako by ste ich aj vyhodnocovali?
Predpokladam, ze cez char array, ale sa mi to zda narocne, porovnavat pismeno po pismenku.
Zrejme nemam na vec spravny pohlad.

Dakujem.
0

peterple
Ultimate člen
Ultimate člen
Príspevky: 2328
Dátum registrácie: 25 Jún 2013, 21:06
Bydlisko: Krajné
Vek: 57
Kontaktovať používateľa:

Re: Pokazeny String, Preco?

Príspevok od používateľa peterple » 05 Apr 2022, 22:10

Nuž ťažko radiť. Ide to aj cez klasicke C stringy (teda polia znakov char [ ]), porovnávať sa to neporovnáva po písmenku ale sú na to funkcie. Keby si si pozrel ako je urobený objekt String, tak by si zistil že ich nakoniec ten objekt využíva. Ale možeš to robiť aj cez objekt String. Dĺžka stringu sa dá nanútiť funkciou reserve Potom to nebude po každom znaku alokovať nový dlhší buffer a ten starý sa tam najprv musí skopírovať a až potom uvolniť. To znamená že pri novom volaní musíš zase zobrať z volnej pamete tebo ten kus bude treba dlhší a z toho uvolneného sa nedá uspokojiť. To zákonite vedie na fragmentáciu a myslím si že ak by si to nechal bežať nonstop tak to skôr alebo neskôr ľahne aj na tom Arduino MEGA. Nie náhodou MISRA standard takéto praktiky zakazuje. Tak6e si to nastav hneď na maximálnu dĺžku a urob z toho statický objekt a používaj ho stále dokola.

Tiež nevidím žiadny dôvod na ten while. Veď aj tak z neho určite každú chvíľu vyletíš von, nakoľko znaky asi neprichádzajú tak rýchlo ako ich dokáže spracovávať tá slučka. Akú tam máš bitrate na tej sériovej linke?

Ďalšia vec čo ti tam žerie pamäť sú tie textové stringy. Napríklad

Kód: Vybrať všetko

    Serial.println("POST COMMAND SENT"); 
Interne toto vytvorí v SRAM C string a do neho sa z pamäte programu skopíruje "POST COMMAND SENT"
takže ti to zbytočne zožralo 18 bytov. Tieto sa aspoň započítajú do obsadenej pamäte.
Správne by to malo byť pomocou F makra ktoré zariadi že sa reťazec nachádza iba v pameti programu a nekopíruje sa do SRAM

Kód: Vybrať všetko

Serial.println(F("POST COMMAND SENT"));
Problém je že to čo ide na heap a stack sa tam nezapočíta a keď do toho nevidíš myslíš si že pamäte je dosť. Na takéto finesy sa prichádza ťažkým debuggingom krok po kroku.
0
Ukáž múdremu chybu a on sa ti poďakuje. Ukáž chybu hlupákovi a on sa urazí.

Používateľov profilový obrázok
budvar10
Pokročilý člen
Pokročilý člen
Príspevky: 983
Dátum registrácie: 15 Dec 2014, 10:55
Bydlisko: Košice

Re: Pokazeny String, Preco?

Príspevok od používateľa budvar10 » 06 Apr 2022, 07:47

TomasNM napísal:
05 Apr 2022, 20:29
Nerozumiem. Preco, co robim v cykle? Jediny cyklus je tam while.
Myslis odstranit while a napisat to takto?

Kód: Vybrať všetko

RSurl=RS.subString(8,RS.length()-8);
Áno. Funkcia substring ti rovno vráti požadovaný reťazec a operátor = urobí:
1. otestuje danú String premennú, či je dostatočne veľká na vloženie reťazca,
2. ak je to potrebné zväčší premennú na požadovanú veľkosť a prípadne ju realokuje na dostatočne veľké miesto,
3. nakopíruje dáta.
RSurl sa rovno vytvorí na požadovanú veľkosť, netreba RSurl = "", to by bolo kontraproduktívne. Druhý parameter v substring ak vynecháš, pôjde do konca RS.
Ak to robíš vo vlastnom cykle, tak uvedené kroky 1-3 sa vykonajú v každom jednom cykle, pre každý jeden znak.

A ešte jena poznámka. Videli sme len tento fragment kódu, ale ak RSurl je len kvôli výpisu, rovno môžeš dať substring ako parameter do printu. Netreba nič kopírovať. Šetríš operácie a RAM. Samozrejme, ako píše peterple,
znakové konštanty do PROGMEM. PS môžeš definovať PS napr. PS[80], takže rovno bude mať 80 znakov.
To, ze zatazujem MCU instrukciami az tolko nevadi, ma na to casu dost.
Vadí, presne preto vzniklo toto vlákno. MCU nie je PC. Zdroje sú veľmi limitované. Navyše ATmega ťa nijako neupozorní, že je napr. v koncoch s pamäťou, alebo že procesor je preťažený. Nemáš tam OS, ktorý tieto veci riadi.
Horšie je, že tento prístup majú aj profíci, dokonca z renomovaných firiem a úplne najhoršie je, že im za to niekto aj ochotne zaplatí.
Zatial som sa neskamaratil so zjednodusenim pouzivania stringov, viem ze su narocne.
Nie sú náročné. String class vznikol práve kvôli zjednodušeniu zápisu, aby sa dalo narábať so znakovými reťazcami ako s číselnými premennými: c = a + b. Kód je oveľa prehľadnejší.
Ak si dáš námahu a pozrieš všetky funkcie a operátory, ktoré String má, budeš mať ihneď predstavu o možnostiach.
0
Naposledy upravil/-a budvar10 v 06 Apr 2022, 08:12, upravené celkom 1 krát.

Používateľov profilový obrázok
budvar10
Pokročilý člen
Pokročilý člen
Príspevky: 983
Dátum registrácie: 15 Dec 2014, 10:55
Bydlisko: Košice

Re: Pokazeny String, Preco?

Príspevok od používateľa budvar10 » 06 Apr 2022, 08:03

Kód: Vybrať všetko

while (Serial.available() > 0) {
    char inChar = (char)Serial.read(); 
    RS += inChar;
    if (inChar == '\n') { RS=RS.substring(0,RS.length()-1);}
    }
Tento triviálny kúsok. Ak by si najprv testoval inChar, ušetríš. Znak '\n' predsa netreba kopírovať do RS. Nie je potrebný substring lebo RS obsahuje presne to, čo treba.
Už aj keby som potreboval orezať koniec reťazca, tak to nerobím
RS=RS.substring(0,RS.length()-1);
ale napr. takto
RS[length()-1] = '\0';
0

TomasNM
Zaslúžilý člen
Zaslúžilý člen
Príspevky: 1371
Dátum registrácie: 22 Jan 2012, 19:24
Bydlisko: Nové Mesto nad Váhom
Vek: 43

Re: Pokazeny String, Preco?

Príspevok od používateľa TomasNM » 08 Apr 2022, 21:44

Pani,

dakujem za rady.
Mam v tomto velke rezervy, ale na druhu stranu, uz som dost stary na to, studovat moznosti Atmegy.
Skusil som to s Nanom, zistil som, ze je to predsalen slabe na tuto aplikaciu, takze prejdem na Megu 2560.
Budem do toho este aplikovat LCD, nejake menu a nastavenia, potrebujem rozsirit serial Rx buffer (cez softserial) atd.
V buducnosti este urcite dam na vase rady.

Prikladam aj cely kod, ktory som trochu upravil podla rad.

Kód: Vybrať všetko

#include <SPI.h>
#include <Ethernet.h>
#include <ArduinoHttpClient.h>
#include <SoftwareSerial.h>

SoftwareSerial mySerial(8, 9); // RX, TX

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress server(192,168,10,101);  // numeric IP for Welder P/S

IPAddress ip(192, 168, 10, 1);

EthernetClient client;
unsigned long beginMicros, endMicros;
unsigned long byteCount = 0;
bool startup=true;
String RSurl;
String RSdata;
String RS;
  
char * const str[] PROGMEM {
"Initialize Ethernet with DHCP:",
"Connecting to ",
"...",
"Connected to ",
"Connection failed",
"Response:",
">>>",
"<<<",
"disconnecting.",
"Received ",
" bytes in ",
", rate = ",
" kbytes/second",
"POSTURL ",
"URL SET: ",
"POSTDATA ",
"DATA SET: ",
"POST REQUEST",
"POST COMMAND SENT",

};

void setup() {
  Serial.begin(115200);
  mySerial.begin(9600);
  while (!Serial);

  mySerial.println(str[0]); //"Initialize Ethernet with DHCP:"
  Ethernet.begin(mac, ip);
  // give the Ethernet shield a second to initialize:
  delay(1000);
  mySerial.print(str[1]); //"Connecting to "
  mySerial.print(server);
  mySerial.println(str[2]); //"..."

  // if you get a connection, report back via serial:
  if (client.connect(server, 80)) {
    mySerial.print(str[3]); //"Connected to "
    mySerial.println(client.remoteIP());
    mySerial.println();
  } else {
    // if you didn't get a connection to the server:
    mySerial.println(str[4]); //"connection failed"
  }
  beginMicros = micros();

}

void loop() {

  if(startup) {
    // if the server's disconnected, stop the client:
    if (!client.connected()) {
      endMicros = micros();
      mySerial.println();
      mySerial.println(str[8]); //"disconnecting."
      client.stop();
      mySerial.print(str[9]); //"Received "
      mySerial.print(byteCount);
      mySerial.print(str[10]); //" bytes in "
      float seconds = (float)(endMicros - beginMicros) / 1000000.0;
      mySerial.print(seconds, 4);
      float rate = (float)byteCount / seconds / 1000.0;
      mySerial.print(str[11]); //", rate = "
      mySerial.print(rate);
      mySerial.print(str[12]); //" kbytes/second"
      mySerial.println();
    }
  }
 
  if (mySerial.available()>0) { 
    delayMicroseconds(3000);
    RS="";
    while (mySerial.available() > 0) {
      char inChar = (char)mySerial.read(); 
      RS += inChar;
      if (inChar == '\n') { RS=RS.substring(0,RS.length()-1);}
    }

    mySerial.println("."+RS); //Kontrola

    if(RS.substring(0,8)==str[13]) { //"POSTURL "
      RSurl=RS.substring(8);
      mySerial.println(str[14]+RSurl); //"URL SET: "
    }
    if(RS.substring(0,9)==str[15]) { //"POSTDATA "
      RSdata=RS.substring(9);
      mySerial.println(str[16]+RSdata); //"DATA SET: " 
    }
    if(RS.substring(0,12)==str[17]) { //"POST REQUEST"
      HttpClient Hclient = HttpClient(client, server, 80);
      mySerial.println(str[18]); //"POST COMMAND SENT" 
//    RSurl="http://192.168.10.101/Services/GetWeldResult";
//    RSdata="{\"Sid\":12345}";
      mySerial.println("Kontrola URL: "+RSurl);  
      mySerial.println("Kontrola DATA: "+RSdata);  
      mySerial.println("...");  
      Hclient.post(RSurl,"", RSdata); 
    }
  }
    
  // if there are incoming bytes available
  // from the server, read them and print them:
  int len = client.available();
  if (len > 0) {
    byte buffer[600];
    if (len > 600) len = 600;
    client.read(buffer, len);
    mySerial.println();
    mySerial.println(str[5]); //"Response:"
    mySerial.println(str[6]); //">>>"
    mySerial.write(buffer, len); // show in the serial monitor (slows some boards)
    mySerial.println();
    mySerial.println(str[7]); //"<<<"
    byteCount = byteCount + len;
  }
  
  startup=false;
}
0

Používateľov profilový obrázok
budvar10
Pokročilý člen
Pokročilý člen
Príspevky: 983
Dátum registrácie: 15 Dec 2014, 10:55
Bydlisko: Košice

Re: Pokazeny String, Preco?

Príspevok od používateľa budvar10 » 09 Apr 2022, 13:47

Stále tam máš konštantné znakové reťazce v print.

41 starý? :finga:

Aj s Nanom, čo je ATmega328P, sa dá urobiť veľa. Toto čo píšeš, bez problémov. Softverovému serial by som sa vyhol. Nano má ale iba 1 HW sériovú linku, Mega má ale 4, tam naozaj nie je nad čím rozmýšľať.

Buffer pre sériovú linku veľmi zväčšovať nemá zmysel, aspoň nie do extrémov. Čip je dostatočne rýchly aby to obsluhoval, záleží ale na tom, čo vlastne budeš chcieť dosiahnúť.

Najviac SRAM má ATmega1284P, 16kB a 2 HW sériové linky. S obľubou používam hlavne tento čip. Vyrobil som aj vlastné Arduino:
Obrázok
Prílohy
Arduino1284P#01.jpg
0

Atlan
Zaslúžilý člen
Zaslúžilý člen
Príspevky: 1146
Dátum registrácie: 01 Feb 2008, 00:00
Bydlisko: Kosice okolie

Re: Pokazeny String, Preco?

Príspevok od používateľa Atlan » 09 Apr 2022, 13:55

Problem je to ze na to ides blbo. Takym systemom zistis ze ten displej nebudes stihat obsluhovat a budes vecne riesit nejaky soft. Buffer s datami.

Nemozes k tomu pristupovat ako k pc s 1GB ram.
Pozris sa na zx spekttrum 4Mhz uP s 128kB ram a co vsetko dokazal.

Proste spravny pristup, setrenie ram, a efektivne vyuzitie strojneho casu. Sisi isty ze tam potrebujes natrepat kvantum dat naraz? A az potom spracovavat?

Potom bude vhodnejsie sa pozriet po stm alebo inych uP co maju MB ram a taktovane na 128Mhz.

Ale amo vravim, prerusenia, efektivne vyuzivanie implementovanych HW veci ako i2c a uart zbernice dokaze divy pac sa nestras o prijem len o data.
0

Napísať odpoveď