esp8266 onbegrijpelijk seriele bitbang instabiliteit ?

Pagina: 1
Acties:

Vraag


  • PtrO
  • Registratie: November 2001
  • Laatst online: 26-08 19:23
Heeft iemand hints of tips die kunnen verklaren waarom een C++/Arduino esp8266 programma door het te inhoudelijk wijzigen, met bv totaal ongebruikte code, (te) instabiel wordt om seriële data via bitbang uit te lezen ?

Ik laat mij graag verder hoe dingen daarin beter tot werking te brengen. Mochten er specifieke tldr; vragen zijn over inhoudelijk hoe/waarom/anders ipv van het boek hier beneden door te akkeren, zijn die natuurlijk ook welkom.

Bvd Peter

//--//
Het issue zelf is vrijwel identiek met wat iemand anders ooit (zonder oplossing/antwoord) had besproken op https://github.com/esp8266/Arduino/issues/4193. Een willekeurige sourcecode aanpassing, maakt de programmawerking op andere plaatsen instabiel.
////---////
Het programma zelf is o.a. multitaskend bedoeld als autonome homecontroller, o.a. daarin P1 data data afkomstig van twee meters via software middels 2 GPIO poorten om -en-om seriële bitbangend uit te lezen.
Eenmaal de telegramdata hebbend, wordt dit verder verwerkt en via MQTT naar elders gecommuniceerd.
FYI: De totale hele spaghetti en chili-concarne is te beproeven op GitHub (disfunctionele kritiek op aard of persoon, graag per DM).

Het serieel bitbangen gaat doorgaans goed totdat ik ergens (maakt totaal niet uit waar en/o die actief wordt gebruikt) programmacode verander waarna de ISR (ICACHED) bitbang soms - te vaak - in de datasoep loopt.

NB: in P1 zelf ontbreekt de synchronisatie van bytes en de gebruikte ISR/Gpio routine halverwege een byte op een verkeerd startbit kan triggeren die daarna de rest in de soep draait. Een volledig P1 telegram, wordt permanent elke 10Sec uitgelezen, bevat ca 750bytes die op snelheid van 115200Baud in ongeveer 0.8S dan al bitbangend wordt aangeboden.
Hierbij kan het (soms/frequent) voorkomen dat data vanwege timing e.d. corrupt raakt. Een datastream van bv
code:
1
1-0:31.7.0(002*A)
wordt dan ineens gelezen als runningaway
code:
1
1-0:31.7.0(0aeefegeghdgeh
.

Gelukkig zijn er ook zg "P1 segmentgaps" van ca 0.2-0.3mS, tussen & in de verschillende datadelen, die seriële starttransport weer wat op orde brengt.
Indien ik foute data heb gelezen, probeer ik die automagisch te herstellen door het inhoudelijk al checksummend te vergelijken met een voorheen wel geslaagde actie. Fout gelezen bytes worden dan zo hersteld.

Kern is dat het programma (voor mij) prima werkt wanneer ik het eenmaal heb voorzien van totaal nutteloos "stabiliserende" code toevoegingen. Dit door in de source - maakt niet uit waar/toe - ongebruikte if-then-else, functies oof nop's bij te plaatsen of juist weg te halen. een keer zou direct denken aan corrupt/overlay waar hier geen sprake van lijkt te zijn. een source code vernadering lijkt specifiek betrekking te hebben op het ISR gedrag dat dan ineens te laat start.

Zover ik kan beoordelen lijkt er dus geen sprake van fragmentatie, overlow, stack of heap conflicten. Ik heb de boel inmiddels een keer of 3-4 van de grond af aan opnieuw gebouwd met wanneer het programma wat groter wordt, het daarmee gevoeliger wordt voor sourcecode veranderingen.

Ik ben (ooit) begonnen vanuit de gekende SoftwareSerial (https://github.com/plerup/espsoftwareserial) die ik in allerlei maten/versie heb verbouwd en voorzien heb van aanpassing om seriële poort stuurbaar te krijgen en de "data" leesbaar te krijgen op 115200Baud. De meest stabiele was versie daarin 2.4.1 die daarvoor verregaand is verbouwd zodat het specifiek o.a. werkt op P1-netbeheer telegrammen van ca 600-800bytes. Een datatelegram begint met "/" , eindigt op "!"; gevolgd door een CRC16 checksum. De verwerking van het telegram zelf, mits leesbaar, is verder geen probleem.

NB: het is uitdrukkelijk NIET mijn bedoeling de redenen, mijn (dis)kwaliteiten en werkwijze tot doel van het programma te bespreken, die te bekritiseren of te verklaren met laat staan, dat dit met ander tooling, methoden, hardware of oplossingen kan worden bereikt.
Ik kan ipv de slecht remmende auto te repareren, natuurlijk een nieuw kopen en dan prima een hardware rs232 interface inzetten. Datgene verklaard (mij) nog niet waarom een code aanpassing, tot een andere werking leidt.

Waar ik knettergek van wordt is zodra ik ook maar één byte in de source verander, het programma in meer of minder mate instabiel wordt. Ik heb versies gemaakt die bijna 100% foutloos de seriële lezen en versies dat totaal niet doen.
Ik vervolgens uren tot dagen bezig ben om op willekeurige plaatsen ad-random (dummy) code moet invoegen om het stabiel te krijgen. het meest effectieve is dan invoegen van zg NOP's machinecode instructies die daarmee de objectcode wat doet verschuiven.

Klein voorbeeld waar ik ergens een optelling of aftrekking doe, kan het al gebeuren dat de stabiliteit van de ISR routines daarmee in de soep draait. Dit wordt weer opgelost door ergens dummy code, meest effectief is invoegen van zg één of (veel) N.NOP's (Extensa assembler Instructie die de processor 1 cycle, niets laat doen).
...
Ik begon ooit simpel met de Arduino IDE en stapte later over VSCode/patformIO om het esp8266/ NodeMCU12 programma on the flyby te ontwikkelen.
Daar de boel erg gevoelig bleek op welk platform ik opereerde, heb ik de boel nu vastgezet op: espressif8266@2.5.3 , toolchain-xtensa @ 2.40802.200502 en framework-arduinoespressif8266 @ 3.20701.0 (= --> Github: esp8266 Arduino 2.7.1)
Migreren naar hogere versie van dat , als inmiddels veel vaker gedaan, blijkt effectief zinloos en is vwb het stabiel krijgen vooral tijdrovend bezigheidstherapie.

Wat ik al gevonden of geprobeerd heb : Talloze probeersel, waarom invoegen van dummycode, ongebruikte datavelde, vergroten van databufffers, invoegen van NOP/assembler instructies etc.etc.
Om het programma stabiel te krijgen voeg ik her -en der ongebruikte dummy code toe zodat het van het niet slechtwerkend gaande weg verbeterd tot bijna foutloos. Dit totdat ik het programma weer ander en dat exercitie pad opnieuw kan doen.

Vast dank d:)b hier alles tot hier gelezen te hebben en ik beantwoord natuurlijk graag specifiek vragen mbt tot dit instabiliteitsissue. Ik neem het risico dat niemand zich hierin herkent en dat is het iig opgenomen in de annalen O-) van een verdere toekomst.
...

Go with the flow blocking your way and use AD for achieving results

Beste antwoord (via PtrO op 15-08-2026 01:26)


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 06-09 22:39
Lijkt me dat je het resultaat ziet van het verschil in code optimalisatie door de compiler (let wel, en een gemiddelde code base is het performance verschil huge tussen wel en niet goed kunnen optimaliseren) en andere tijd/scheduling gerelateerde effecten.

Daarnaast vermoed ik dat je code bugs bevat, in de zin dat de compiler zaken wel of niet wegoptimaliseert waarvan jij denkt/vindt dat ze nodig zijn. In (moderne) C en C++ is dat een dingetje. Wat je beschrijft is precies dat.

Beste is om een hardware serial port te gebruiken.

Je code is trouwens niet te volgen (maar dat wist je al) dus daar zouden ook legio logica fouten in kunnen zitten. Serial.print in een ISR lijkt me ook niet handig trouwens, dat zal ook andere ISRs blokkeren. Naja...

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.

Alle reacties


Acties:
  • Beste antwoord

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 06-09 22:39
Lijkt me dat je het resultaat ziet van het verschil in code optimalisatie door de compiler (let wel, en een gemiddelde code base is het performance verschil huge tussen wel en niet goed kunnen optimaliseren) en andere tijd/scheduling gerelateerde effecten.

Daarnaast vermoed ik dat je code bugs bevat, in de zin dat de compiler zaken wel of niet wegoptimaliseert waarvan jij denkt/vindt dat ze nodig zijn. In (moderne) C en C++ is dat een dingetje. Wat je beschrijft is precies dat.

Beste is om een hardware serial port te gebruiken.

Je code is trouwens niet te volgen (maar dat wist je al) dus daar zouden ook legio logica fouten in kunnen zitten. Serial.print in een ISR lijkt me ook niet handig trouwens, dat zal ook andere ISRs blokkeren. Naja...

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • PtrO
  • Registratie: November 2001
  • Laatst online: 26-08 19:23
Helemaal Cool 8-) l !!!!
tldr; ineens viel het muntje ..... de compiler optimaliseert globale variabelen die ooit 'zomaar ' in de BitBang ISR zijn geslopen !!!! Guess the issue what happens elsewhere |:( .
Het probleem is mij nu duidelijk en daarmee min of meer opgelost. Thx _/-\o_

//--//

Ik gebruik op de esp8266 een aantal verschillende ISR's waarvan er twee heel kritisch zijn t.w. seriële 115K2 bitbang van twee verbonden RS232's (P1 van de elektra en een P1 van de verwarming). Daarnaast een ISR om een wobbelende draaiend blinkertje van een watermeter te detecteren.
Ik heb mij t.a.v. RS232 bitbang onvoldoende gerealiseerd om de daarin gebruikte (global) variabelen w.o. met name de databuffers NIET doro de compiler te laten optimaliseren (middels "volatile" declaratie op velden en o.a. pointers). Door ad-random code in te voegen, gaat de compiler anders (of juist niet) optimaliseren. Dit tot een volgende source wijziging en het verhaal zich herhaalt.

FWIW en voor wie dat nog niet (onder)kent: compiler optimalisatie is o.a. dat die aannames doet op de te vertalen code waarbij dingen logisch voorspelbaar zijn zoals 1+1=2, dat dan ook verderop die 2 blijft. Dit niet alleen qua data maar ook (jump/pointer)instructies waarbij bv register-offsets een rol spelen.
Een ISR springt asynchroon weg uit die normale logica en kan dan die compiler - inclusief onveranderlijk aangenomen register/pointer associaties - compleet verprutsen.
Dit laatste is dus precies wat er in mij code gebeurd(e) wat leidt tot een onverklaarbaar gedrag.
Voor een betere uitleg: https://barrgroup.com/embedded-systems/how-to/c-volatile-keyword

Nu dit muntje ruim op de neus is gevallen, is mijn probleem (bijna) als sneeuw voor de zon verdwenen.
Inzake printen in een ISR gaat dat verder prima, zoals je maar niet de ISR van "Hardware Serial" gebruikt. Voor de P1's kon/wilde ik geen hardware serial gebruiken omdat ik die in de toekomst wilde gebruiken om te communiceren met een (highspeed) MBUS systeem.

Thx verder voor de terechte en zacht geuite kritiek. De code is idd erg rommelig en daar wil ik verder niets aan afdoen, anders dat alles een oorzaak tot gevolg is. VSCode helpt mij gelukkig om wat op een spoor te blijven. Nu ik de oorzaak in het snotje heb, wordt het straks puinruimen.
Bedenk hier dat een Arduino/ESP loopmodus moet doen zonder dat die ergens op een WatchDogTimeout instort. Wat daarin mijn uitdaging dan verder compliceert is het gebruik van (wisselende) Wifi en MQTT-monitoring daarin/uit ook wordt afgehandeld. Voorts allerlei andere allerlei I/O (temperatuur & dag/licht) sensoren worden verwerkt en ik de boel ook fault-tolerant zodat mijn verwarming altijd operabel blijft en niet in de winter gata bevriezen.
De ESP (o.a. vv UPS) vormt daarmee min of meer het hart van/naar mijn meterkast.

Alles begint met 10 regels totdat het er 10.000 zijn geworden met weer een idee om op een wankele basis in poging iets te herstellen. In de "code" zit zeker allerlei overbodige troep die er in gekomen om het proberen te begrijpen.

En ja de compiler deed mij hier overduidelijk met optimalisatie ergens de das om.
Daar heel lang al naar zitten kijken zonder te "begrijpen" waar ik faliekant mis ga/ging. Zelf zover dat ik tientallen disassemblies bytewise heb doorgeworsteld en dan idd op hele rare dingen uitkwam de ik totaal niet kon plaatsen.

Go with the flow blocking your way and use AD for achieving results


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 06-09 22:39
Let er wel op dat 'volatile' alleen vaak niet doet wat er nodig is om synchronisatie tussen ISRs/threads veilig te doen, het zorgt er alleen voor dat de variabele niet in bv een register gecached mag worden.
Vaak heb je ook atomic access nodig om zeker te zijn dat de 'andere kant' hetzelfde ziet als de ISR/thread, en specifieke memory ordering om te garanderen dat dingen in de goede volgorde gebeuren.

Ik weet niet wat de status van de 8266 SDK is (tijd om over te stappen naar een ESP32 zou ik zeggen), maar kijk eens naar std::atomic<T> en std::atomic_thread_fence(...) bv, of misschien zijn er intrinsics die je kunt gebruiken.

BTW:
Voor de P1's kon/wilde ik geen hardware serial gebruiken omdat ik die in de toekomst wilde gebruiken om te communiceren met een (highspeed) MBUS systeem.
Normaal gesproken draait MBUS op een lagere snelheid dan de P1 met z'n 115k2. Just saying.

[ Voor 17% gewijzigd door farlane op 16-08-2026 11:09 ]

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • PtrO
  • Registratie: November 2001
  • Laatst online: 26-08 19:23
Overstappen is altijd een optie waarbij de (hobby)uitdaging juist dit te doen met een bestaande esp8266. Ik had ook een Rpi of een STM/ARM neer kunnen zetten of een P1 interface kopen. Dat naast ik niet nog meer kastjes in de meterkast wilde, lang niet zo "leuk" is om mee te prutten. Tekenen vind ik leuker dan een (nieuw) plaatje inkleuren.

Inzake die ISR snap ik wat ik goed wat je zegt waarbij ik de ISR alleen inzet om data, oneway, uit te lezen. De hardware serial, wilde ik die - op de NodeMCU gekoppeld is aan USB - in beginsel vrijlaten. Al was het maar o.a. voor (remote/debug)console access.

Vwb "atomic-ity" - waarbij een interrupt de volgordekijk register/status elders in threads kan vervuilen - ljkt dat issue hier wat mnder aan de orde. De esp8266 zelf maar 1core heeft en doet één enkele thread. Of en in hoeverre de esp8266 daarin zelf verder "atomic-proof" is, moet ik nog 's nazoeken in het Xtensa ISA architecture manual.
In mijn geval maak ik mij geen zorgen, ook omdat ik tijdens bitvangen (van 1 byte) in de ISR, alle andere interrupts sws even ordentelijk afstop en de andere I/O registers van o.a. Wifi & Flash blokkeer. In het begin had ik daarmee nog last met diverse WDT's die ik eerder zijn opgelost.

De code in opzet zelf, is dusdanig dat ik 95% van de via (inverted) RS232 P1 "telegrams" goed (kan) verwerken. Incidentele missers dan verder(op) aanvul of interpoleer.
Met ook allerlei zekerheden (inherent "P1" protocol) dat bv mijn stroomverbruik van tussentijds uitlezingen (elke 10s) niet zal zijn teruggelopen noch substantieel zal oplopen. Ook die (fout) elementen check ik (vooral op de backend). De esp8266, in in code is nu relatief fouttolerant.

Ik ga nog wel even verder kijken naar de diverse compile opties en bekijken of die in welke mate - in mijn geval - zinnig zijn.

NB: Wel leuk :P om zo mij wat te kunnen verdiepen met "intrinsics" die voor jou gesneden koek lijkt.

ThxFtF !!

Go with the flow blocking your way and use AD for achieving results


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 06-09 22:39
Vwb "atomic-ity" - waarbij een interrupt de volgordekijk register/status elders in threads kan vervuilen - ljkt dat issue hier wat mnder aan de orde. De esp8266 zelf maar 1core heeft en doet één enkele thread.
Atomicity heeft te maken met bv dat een read-modify-write ( x++) in een andere thread/isr (het maakt dus niet uit of je verschilende threads gebruikt of een enkele thread en een ISR voor dit probleem), context gezien wordt als een atomaire instructie zodat niet tussendoor een rare andere waarde te zien zou zijn in x.

Een instruction fence/barrier dient ervoor om te zorgen dat alle instructies die ervoor staan zijn uitgevoerd en dat de state van je programma is wat je zou verwachten. In dit voorbeeld bv zou het kunnen zijn dat in de main context 'received' al true is terwijl 'buffer' en/of 'count' nog niet geupdate is (theoretisch):
volatile bool received;
volatile int count;
volatile char buffer[256];

int main(...) {
    while (true) {
        if (received) {
            ...
        }
    }
}

void ISR() {
 buffer[count] = read_usart_data();
 ++count;
 received = true;
}
Het maakt ook niet uit of je maar 1 of meerdere cores hebt, het gedrag is er altijd.

[ Voor 4% gewijzigd door farlane op 17-08-2026 11:47 ]

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • PtrO
  • Registratie: November 2001
  • Laatst online: 26-08 19:23
Helder is "atomic" gedrag ten gevolge van eigen wijze tot programmering met vooral hoe je dan iets (op welk moment) tot onveranderlijke waarheid beschouwd dat asynchroon bezien niet zo hoeft te zijn.
Mooi je heldere uitleg/toelichting daartoe te lezen dat in mijn geval op source-statement niveau n.v.t. was.

//-//
Ik dat probleem niet had/heb omdat ik de boel als enkel thread bezag. Ik mij keurig in de ISR en vv programma met te kruisen variabelen. Ik los(te) dat inter-statement deels eerder op door pas wanneer de ISR aan einde van een datarecord was gekomen, de ISR (als mogelijke andere"thread") dan te deactiveren en pas daarna de daarmee ingenomen data ging verwerken.Ik ken wel op assembler level het PBCAK issue waarbij je bv een load van een register wier initiële referentie, door een ISR, de bedoeling van een volgende (PC) programmainstructie beïnvloedt.

Het probleem dat ik had was uit het blote feit dat betrokken "pointers" werden vernaggeld door compileroptimalisaties die dan "zomaar" tellers veranderde, terwijl ik in mijn code, puur positioneel gezien, daar nog niet aan aan was toegekomen.
Denk aan illustratief aan:
code:
1
2
3
4
5
6
7
8
9
10
int a=0;
a=a+1 ;
...
 // X whatever else dat a niet verandert noch gebruikt.
...
a=a+2 ;
...
 // Y) whatever too else dat a niet verandert noch gebruikt.
...
a=a+3 ;
De compiler is dan zo slim, door in een register ipv a=0, dan direct die a=6 te proppen en zo mijn opdrachten "optimaliseert" door ze als kortere weg op op executie tijdstip dan niet te hoeven uitvoeren.
De ISR die ergens op moment van "X" al doorkreeg dat "a=6" zou zijn terwijl ik op mijn sourcelevel bezien vanuit "Y" gezien nog bezig was/ben met "a=3" te doen om dan bv ISR gerelateerde offset-pointers te (p)resetten.
Ik (opnieuw) simpelweg niet bedacht had dat er ook sprake was van veld referentie optimalisatie, die dan exacte volgordelijkheid van mijn coding in detail beïnvloedde. Vervolgens ik ook hopeloos verdwaalde in een disassembly met idd "logica" te zien die ik er iig niet zelf had neergezet.

Nu met volatile :Y) , is dat "onnavolgbare" gedrag ineens foetsie en verklaard tevens met waarom het vergroten/veranderen van sourcecode, al was het maar één byte, de boel compleet kon doen verschuiven met dat ik bestempelde als instabiel. Leermomentje!!!!

Wel heel leuk O-) om dingen te laten indalen met dat, thx, er voorzieningen zijn in C++ (zoals std::atomic_thread_fence(..) op std::atomic objecten ) die een programmeur kan helpen z'n geheugen/pointers te sync'en. Iets dat ik vroegûh zelf deed door simpelweg ergens bitflips te doen. Stond het bitje op één wist ik dat er elders een andere process/thread daarmee bezig was (geweest).

Go with the flow blocking your way and use AD for achieving results

Pagina: 1