Ik zie nog iets dat duidt op een denkfout. Je zegt dat procedure leg is. Dat is niet zo gek. Ook al zit je in loop, procedure is leeg zodra je een functie aangeroepen hebt die procedure leeg maakt.
Je moet re dus een soort stack van maken. In het begin van functie naam op de stack, aan het einde er af.
Je kunt ook met bitvelden werken gezien het beperkte aantal functies. (Bit 0 in loop [ is altijd] , bit 1 = in functiex, bit 2 is in functie y etc.) Als je dan de hexwaarde van dat byte print, kun je zien waar je zit.
Ook weer volatile en als je zeker wilt zijn semaforen gebruiken als je het bit update. Maar ik denk dat dat wat overdreven is omdat ze toch na elkaar aangeroepen worden.
fcapri
ik hou van werken ..., ik kan er uren naar kijken
vroeger wist ik niet waar die zat, vandaar de code
zit je in loop, dan staat er 'loop' in de string
de if lussen doen niks, je komt op het einde van de loop, en schrijf niks meer in de string. als je die print, zit je niks (zelf geen spatie)
ga ik in de loop in een functie, bv updatetime, dan komt in het begin 'updatetime' in die string, en op het einde wordt die weer leeg gemaakt. immers na die functie, gaat die onmiddelijk wat anders doen.
in principe zou het dus nooit zijn dat er niks in de string zit want die wordt altijd weer ingevuld door iets in een fractie van een paar millisecondes.
echter loop ik dus vast op het einde van een functie waar die dus leeg word geschreven en niet meer herbegint.
ik ga vanavond nog een extra ESP aanzetten, en die op 100ms delay zetten zodat die 10x sneller overal doorheen gaat. ben dan benieuwd of die na 3dagen vastloopt (ergens geheugenlek dan ofzo)
Op maandag 16 juni 2025 10:31:21 schreef rene037:
En in het stukje code staat de declaratie van 'procedure' niet. Maar dan 'ter leering ende vermaeck' hoe het in C werkt:
.
helemaal in het begin staat die, globaal gedefinieerd samen met alle andere velden die doorheen het hele programma worden gebruikt.
String message = "";
String identif = "";
String value = "";
String Bstatus = "OFF";
String Btime = "12:00";
String cTime;
String StartEnd;
String screenStatus;
String IP;
String onoff;
String procedure;
Als je toch aan het opschonen gaat..
timeClient.setTimeOffset(utcOffsetInSeconds); // Die hoeft niet, want zit al in initialisatie toch?
delay(100); // Hoeft ook niet
timeClient.update(); // Hoort in loop. Hoe vaak hij update zet je in de call naar NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 3600, 60000);
Nu wacht je tot je timeClient.update() aanroept, maar in die routine zit die check al.
Nu gebruik ik deze lib niet meer omdat ik hem verdacht vind, had ik geloof ik al gezegd.
Jet hebt ook een array met de engelse dagen. Die zit ook al ergens in een lib. EN ik zou hem als
char *weekdays[] = {"mon......} declareren als het zelf wilt doen.
fcapri
ik hou van werken ..., ik kan er uren naar kijken
ik wil er ook vanaf.
ik heb een ESP draaien op de P1 poort van mijn digitale meter en daar zit veldje 1.0.0 als tijd van het telegram in (datum en uur).
ik haal daar de tijd nu uit om te verwerken.
mijn powermeter en mijn thuis batterij zijn al herschreven om hiermee te werken, de boiler moet ik nog doen (versie 11 nu) maar komt meer bij kijken want zijn webserver (async) is een ander type dat ik nu gebruik (esp8266webserver).
en het duurt even voor ik weer mee ben hoe die async werkte en waar die ntp overal in zit verwerkt.
ook als ik die delay(1000) uit de loop wil halen, moet ik weer wat aanpassen.
ik overweeg sterk om de hele GUI overboord te gooien en dan kan ik ook de simpele webserver draaien en hoef ik maar de code licht aan te passen van de thuis accu (ipv opladen en ontladen, moet ik enkel nog bepalen waneer de boiler warmt, de logica is eenvoudiger)
Wat ik ook steeds vergeet te zeggen, op een ESP32 gebruik je vTaskDelay() ipv delay(). Ook weer geen garantie dat je probleem verdwijnt, maar vTaskDelay geeft de scheduler lucht.
henri62
1-st law of Henri: De wet van behoud van ellende. 2-nd law of Henri: Ellende komt nooit alleen.
Als je een echte NTP implementatie gebruikt moet die blijven lopen, dus inderdaad is een delay() dodelijk voor de scheduler en NTP gaat er van over de rooie.
Zoals rene037 zegt:
De ESP gebruikt waarschijnlijk FreeRTOS en dan kun je beter vTaskDelay() gebruiken en netjes events of semaphores (xSemaphoreTake/xSemaphoreGive of aanverwanten) en hoef je nergens dom te pollen.
Heb je geen O/S dan doe je wat oxurane zegt, zelf vanaf een counter (die in een IRQ updaten op een vaste tick) de juiste timeslices maken en executen.
Op maandag 16 juni 2025 18:07:01 schreef henri62:
Als je een echte NTP implementatie gebruikt moet die blijven lopen, dus inderdaad is een delay() dodelijk voor de scheduler en NTP gaat er van over de rooie.Zoals rene037 zegt:
De ESP gebruikt waarschijnlijk FreeRTOS en dan kun je beter vTaskDelay() gebruiken en netjes events of semaphores (xSemaphoreTake/xSemaphoreGive of aanverwanten) en hoef je nergens dom te pollen.Heb je geen O/S dan doe je wat oxurane zegt, zelf vanaf een counter (die in een IRQ updaten op een vaste tick) de juiste timeslices maken en executen.
Nou, het woord waarschijnlijk heb ik niet gebruik (toch?), want ik weet wel zeker dat de esp freeRTOS gebruikt.
henri62
1-st law of Henri: De wet van behoud van ellende. 2-nd law of Henri: Ellende komt nooit alleen.
Nee hoor "waarschijnlijk" heb je niet gebruikt. Dat heb ik ervan gemaakt.
Maar ik dacht dat je FreeRTOS optioneel kunt "in-compileren" in de espressif toolsuite. En er zijn tig varianten van de ESP dus even checken of er onder water wel FreeRTOS gebruikt is, want dit is de arduino port? (En daar heb ik wel eens rare dingen gezien)
-edit- Ben geen fan van arduino behalve dat je effe snel een proof of concept kunt maken. Idem python.
Espressif gebruikt zelf ook freeRTOS als basis (in idf), omdat daar de ondersteuning voor multiprocessor en multitasking in zit.
Op maandag 16 juni 2025 10:31:21 schreef rene037:
[...]
char *txt = "Nog niets"; // Nu wordt er een pointer naar de string in txt gezet. Die blijft zoals het programma loopt void functie() { txt = "In functie"; // We zitten nu in de functie, de waarde van txt is geldig /* .... code */ txt = ""; // we zetten nu een pointer naar een lege string in txt, maar dat is zinloos, want die lege string bestaat zo niet meer. // Wat wel mag: txt = NULL; // Dat is de null pointer, en dat is een vaste waarde } int main() { printf("Txt = %s\n", txt); // Print "Nog iets" functie(); printf("Txt = %s\n", txt); // Geen idee wat er geprint wordt, txt verwijst naar random stuk geheugen waar 'vroeger' een character string stond }
Op zich klopt het wel dat een locale variabele niet meer bestaat zodra de functie is afgelopen. Dus daar moet je inderdaad voorzichtig mee zijn.
Maar de "In functie" hierboven is een string literal en geen variabele. Dus die blijft normaal gesproken gewoon geldig, ook als je die binnen een functie toekent aan een globale pointer variabele.
De enige uitzondering hierop is volgens mij Atmel AVR. Want die moet een locale variabele aanmaken om de string literal uit flash naar SRAM te copieren. Dus daar moet je PSTR("") gebruiken en dan werkt het weer wel.
En een lege string assignment als in
txt = "";
is bepaald niet zinloos. Die kun je gewoon printen (of andere dingen mee doen) zonder dat je eerst moet testen op NULL. NULL is een pointer naar adres 0x0000 en daar staat vaak een vector tabel met allerlei bytes die je normaal niet wilt printen, en "" is een pointer naar een null character. Als je die print dan krijg je ook echt niks.
Als het goed is, bestaat die string literal alleen in de adresruimte van die functie. Anders zou dat betekenen dat alle constantes altijd in het (ram)geheugen aanwezig moeten zijn. Dat is niet logisch.
En ik snap wel dat x = "" wel werkt, maar het is niet zo netjes. Natuurlijk kun je een lege string ook printen.
Ik zou dan x = "exit func xyz" schrijven, dan weet je welke functie je het laatst verlaten hebt - aangenomen dat de C++-String zo werkt. Maar zoals ik het eerder schreef, ik zou het meer thread safe oplossen.
String literals (en andere literals) staan normaal nooit in het ram-geheugen, maar alleen in het programma geheugen (doorgaans in Flash bij embedded controllers). En daar zijn ze onderdeel van de firmware/applicatie, onafhankelijk van welke procedure op dat moment runt.
Het zou nogal een serieuze impact op je performance hebben als elke string literal telkens weer naar een temp variabele op de stack gecopieerd zou moeten worden.
Alleen Atmel AVR doet moeilijk omdat flash en Sram aparte adres-ruimtes zijn die verschillende instructies vereisen. Daarom zijn er bij Atmel dus speciale flash pointers gedefinieerd ( avr-gcc PSTR("") en Arduino F"" ), juist om die copy-slag te voorkomen.
En ik zou niet weten waarom een lege string "niet netjes" zou zijn. Niks mis mee toch?
benleentje
Golden Member
Op dinsdag 17 juni 2025 14:48:06 schreef deKees:
String literals (en andere literals) staan normaal nooit in het ram-geheugen, maar alleen in het programma geheugen (doorgaans in Flash bij embedded controllers).
Bij Arduino toch standaard niet is mijn ervaring.
Een tijd geleden even snel een voorbeeld die ik op het web vond in een UNO geprogrammeerd en dat lukt niet omdat ik teveel ram had gebruikt. Toen al die strings ingekort want het was toch maar een test en na het inkorten paste het wel.
Het was een LCD test van adafruit dus daar stond wel vrij veel tekst in.
Inderdaad, bij sommige arduinos gaan ze wel naar SRAM. Dat is een eigenschap van de AVR_GCC compiler en alleen van toepassing op atmel avr processors. En dat geldt voor elke programmeer IDE die de AVR-GCC gebruikt. Atmel studio, arduino, etc.
En ook daar is het zo dat een string literal altijd in het SRAM geheugen blijft staan, ook buiten de context van de funkcie waar die wordt gebruikt.
Zie avr-gcc programming manual:
11.20 Why do all my "foo...bar" strings eat up the SRAM?
By default, all strings are handled as all other initialized variables: they occupy RAM
(even though the compiler might warn you when it detects write attempts to these RAM
locations), and occupy the same amount of flash ROM so they can be initialized to the
actual string by startup code. The compiler can optimize multiple identical strings into
a single one, but obviously only for one compilation unit (i. e., a single C source file).
That way, any string literal will be a valid argument to any C function that expects a
const char ∗ argument.
Het is duidelijk processor specifiek, m.a.w. het 'kan' werken. Maar ik blijf bij mijn punt. Je mag daar niet van uitgaan, zeker niet als je portable code wilt hebben. Flash is trager dan RAM en ik kom uit de tijd dat een programma van schijf in RAM geladen werd. Er kan dan gescheiden I- & D-space zijn, maar blijft ram. En alleen het benodigde deel wordt in het geheugen geladen. (Zoals bij Overlays) Nu denk ik dat FreeRTOS op de ESP niet gaat swappen, maar als jij denkt dat je op die manier veilige code schrijft moet je het vooral blijven doen.
En het voorbeeld van de AVR dat het te beïnvloeden is geeft aan dat je als je het kunt voorkomen niet moet doen. Het wordt vaak als schoolvoorbeeld gebruikt van hoe iets gruwelijk mis kan gaan.
Zie https://en.cppreference.com/w/cpp/language/string_literal.html
Evaluating a string literal results in a string literal object with static storage duration.
En
Static storage duration
...
The storage for these entities lasts for the duration of the program.
Dus string literals verdwijnen niet uit het geheugen, maar blijven onveranderd zolang het programma loopt. En dat is niet processor specifiek. Dat staat zo in de officiele compiler specs.
Wat wel processor-specifiek is is waar die dan in het geheugen staat. Bij de meeste controllers is de string alleen in de flash. Maar voor AVR wordt er bij opstarten een static copie gemaakt in Sram. Maar ook daar blijft die copie onveranderd in het geheugen.
Ok, fijn dat het voor deze processor/compilercombnatie geldt. Maar dat het genoemd wordt, geeft al aan dat dat niet 'per definitie' zo is, in ieder geval niet in de C specificatie. (Zoals ik steeds benadruk, voor C++ weet ik niet zeker, daar kan onder de motorkap wel een memcopy zitten) Dus voor portabiliteit zou ik gewoon het risico niet nemen. In het voorbeeld waar we het over hebben is het onschuldig, maar als je op githbub code van een geweldige functie publiceert om iets te doen en dat is universeel bruikbaar, dan weet je het niet. Als dat per ongeluk in een of andere library belandt vind je het nooit meer terug. Mijn eerste reactie was wat opvoedkundig bedoeld, het is bad practice. Ook al werkt het, klinkt als 'ik doe het al 10 jaar zo'.
Maar "En dat is niet processor specifiek. Dat staat zo in de officiele compiler specs" is tegenstrijdig. Een compiler is voor een specifieke processor. Ook al zijn er tegenwoordig compilers die met een runtime vlag de optie hebben een processor te specificeren. (en dan bedoel ik niet een variant, maar een architectuur.) En in 'de officiële compiler specs' - je bedoelt 'de officiële compiler specs van GCC'. Niet van elke compiler, zeker niet als die met overlays overweg kan.
Maar laten we deze discussie stoppen en kijken naar het probleem van het hangende programma.
Er zit code tussen de functieaanroepen. Die ziet er zo niet verdacht uit, maar als je nauwkeuriger wilt weten waar het stopt (EN of het steeds op dezelfde plek is...) zet dan an het einde van de functie niet procedure = "" maar prodedure = "Verlaat klkjlkjkj()" en zet direct na de aanroep in je programma procedure = "Terug van klkjlkjkj()". Vóór je de volgende routine aanroept procedure = "Ik ga blabla() aanroepen" etc.
hardbass
PE2BAS
Bij nader inzien, wellicht niet het beste advies. Zie post Bobosje hieronder.
---
Ik zou het volgende verwachten:
String xxx; → het object en de inhoud staan in RAM
const String yyy; → de string inhoud kan in flash staan (afhankelijk van hoe het is geïnitialiseerd), maar het String-object zelf staat nog steeds in RAM
Dit hangt af van hoe je de String aanmaakt en of de compiler optimalisaties toepast. Fact-check dit voor jou platform.
Hier nog een voorbeeld:
Hier wordt "Test" als const char[] (in flash) meegegeven aan MyPrintFunction. Maar doordat de parameter een String-object is (geen reference), wordt "Test" omgezet naar een tijdelijke String. Deze tijdelijke String wordt in RAM aangemaakt. Dit betekent dat de oorspronkelijke constante string uit flash wordt gekopieerd naar RAM bij het aanroepen van de functie.
void MyPrintFunction(String someString)
{
// ...
}
void main()
{
MyPrintFunction("Test");
}
Je kan dit efficienter maken door de functieparameter als const String& mee te geven.
void MyPrintFunction(const String& someString);
Dit zorgt ervoor dat er geen nieuwe String wordt aangemaakt of gekopieerd. Je werkt direct met het bestaande object, waardoor je RAM spaart en efficiënter code draait.
Of, als je het String object niet nodig hebt kan je het zo doen. Dit is het aller zuinigste kwa ram.
void MyPrintFunction(const char* someString);
De const specifier zegt helemaal niets waar en of het object in het geheugen wordt opgeslagen, de const specifier geeft alleen aan dat het object read-only is.
Objects declared with const-qualified types may be placed in read-only memory by the compiler, and if the address of a const object is never taken in a program, it may not be stored at all.
Any attempt to modify an object whose type is const-qualified results in undefined behavior.
hardbass
PE2BAS
Zoals ik zij, fact check dit voor jou compiler / platform.
Maar wellicht beter om dat niet zo te doen. Als je code kopieert naar en ander project kan het resultaat ineens anders zijn. Ik heb dit aan mijn vorige bericht toegevoegd voor de na lezers.
[Bericht gewijzigd door hardbass op woensdag 18 juni 2025 15:28:19 (15%)]
benleentje
Golden Member
Even een kleine zijspoor. Hoe weet je nu of de software vastloopt of dat het bv een warmte probleem is en de Chip zelf vastloopt of dat de oscillator ermee is gestopt?
De arduino String is een C++ object. Binnen dat object zit een pointer naar een buffer op de heap en nog wat admin. Zowel het string object als de data staan in SRAM.
@hardbass: Verwar een C++ String object niet met een C char * of C char[], want dat zijn heel verschillende dingen met een heel verschillend gedrag!
Overigens nog een tip die ik niet genoemd heb: Als je de waarde van een variabele wel de levensduur van een functie wil laten overleven (Ofwel dat je wilt dat de inhoud van die variabele behouden blijft) declareer hem static. Dus bijv static int x.
dus:
volatile int x; // x globaal gedeclareerd, kan door meerdere processen gewijzigd worden
static int y; // Lokaal in functie gedeclareerd, als je in een aanroep een waarde toekent, staat die waarde er in de volgende aanroep nog in.
roadrunner84
Meep! Meep!
Ik kan niet je hele code gaan doorvorsen, maar is het mogeliujk om een watchdog toe te voegen? Je voorkomt dan niet het vastlopen, maar hebt wel een gecontroleerde manier om vanuit de vastgelopen situatie automatisch te herstellen.
Aangenomen dat hij echt vastgelopen is. Delay reset bijv de wdt, net als veel andere routines die ongemerkt aangeroepen worden. Als je in de setuproutine de restetreason print, weet je wel of de wdt afgegaan is. Maar hier lijkt het erop dat de wdt niet afgaat, want hij hangt, hij reset niet...