Jak správně používat těkavé proměnné v Arduinu?

Dělal jsem malý projekt s Arduino Uno. Jednalo se o přerušení, protože používám kodéry k měření toho, jak moc se systém diferenciálního kola pohybuje vpřed. Můj robot se pohybuje jen dopředu. Takže používám pouze jeden kanál z každého kodéru. Tady jsou moje dvě rutiny přerušení:

ISR (INT0_vect){ encoderRPos = encoderRPos + 1; } ISR (INT1_vect){ encoderLPos = encoderLPos + 1; } 

Proměnné encoderRPos a encoderLPos jsou typu volatile int. Chápu, že proměnné, které procházejí změnami v jakékoli rutině přerušení, musí být typu těkavé. Tímto upozorňujeme ostatní části kódu, které používají tyto proměnné, že se to může kdykoli změnit.

Ale to, co se stalo v mém kódu, bylo trochu divné a nedokázal jsem to vysvětlit. Takto vypočítám vzdálenost posunutou levým kolečkem:

 #define distancePerCount 0.056196868 float SR = distancePerCount * (encoderRPos - encoderRPosPrev); float SL = distancePerCount * (encoderLPos - encoderLPosPrev); encoderRPosPrev = encoderRPos; encoderLPosPrev = encoderLPos; 

Ale když na svém sériovém monitoru vytisknu následující, všimnu si anomálie:

zde zadejte popis obrázku

Pokud se podíváte na třetí sloupec (SL) je jeho hodnota příliš vysoká jen na nějakou dobu. To narušuje všechny mé výpočty.

Jediné vodítko, které mohu získat, pokud vezmu hodnotu SL, kterou jsem získal ( 3682), což je vždy konstanta, a spočítám zpět (encodeLPos - encoderLPosPrev), dostanu 65519,66, což je blízko k maximální hodnotě unsigned int . Což znamená, že (encoderLPos - encoderLPosPrev) způsobuje přetečení, zatímco obě hodnoty, jejichž rozdíl je vzat, jsou někde jen kolem 5000!

A podařilo se mi to vyřešit. Bylo to tím, štěstí. Takto jsem upravil kód:

 static int encoderRPosPrev = 0; static int encoderLPosPrev = 0; int diffL = (encoderLPos - encoderLPosPrev); int diffR = (encoderRPos - encoderRPosPrev); float SR = distancePerCount * diffR; float SL = distancePerCount * diffL; encoderRPosPrev = encoderRPos; encoderLPosPrev = encoderLPos; 

Nechápu, co se stalo. Je něco o těkavých proměnných, o kterých bych měl vědět?

Aktualizace: Zde je celý kód, pokud si jej budete chtít prohlédnout. A funguje to velmi dobře po změně na to, co bylo navrženo v přijaté odpovědi.

Komentáře

  • Vaše otázka říká, co třetí sloupec výstup je … jaké jsou další sloupce? Upravte prosím otázku a přidejte záhlaví sloupců
  • @ jwpat7 Záměrně jsem je odstranil, protože to čtenáře jen zmást. Majenko však na otázku již dobře odpověděl.
  • Je těžké poskytnout podrobné odpovědi ze svých úryvků. could you explain why it is not happening randomly but at a specific time every time I run the code? Also why does it give the particular value? – to bych pravděpodobně mohl udělat, kdybych viděl celý kód. Mezitím si přečtěte toto: gammon.com.au/interrupts
  • @NickGammon Tady máte: paste.ubuntu.com / 14085127
  • 3683 / .056196868 = 65537 takže to vypadá, že se zvýší ve špatný okamžik, ano? Přistupujete k proměnné, kterou lze v přerušení změnit několikrát v tomto kódu, takže získání místní kopie, když jsou přerušena, by bylo mnohem bezpečnější.

Odpověď

Musíte se dozvědět více o kritických částech .

Co Pravděpodobně se děje to, že proměnné jsou měněny přerušovacími rutinami uprostřed výpočtů. Vaše „oprava“ zkracuje čas strávený výpočtem s těkavými proměnnými, takže je méně pravděpodobné, že dojde ke kolizi.

Co byste měli udělat, je zkopírovat těkavé proměnné do lokálních proměnných s deaktivovanými přerušeními. krátké období.

cli(); int l = encoderLpos; int r = encoderRpos; sei(); 

Protože Arduino je 8bitový procesor, vyžaduje matematické operace na 16bitových hodnotách několik montážních pokynů. Plovoucí desetinná čárka je ještě horší pomocí mnoha mnoha pokynů pro jednoduché přidání. Rozdělení a násobení používají podstatně více. Přerušení má spoustu příležitostí vystřelit během tohoto seznamu pokynů. Provedením takového přiřazení a následným použitím nových lokálních proměnných ve výpočtech jsou pokyny potřebné k řešení těkavých proměnných omezeny na absolutní minimum. Vypnutím přerušení během přiřazení zaručujete, že proměnné nelze nikdy změnit, když je používáte. Tento fragment kódu se nazývá kritická část .

Komentáře

  • Může to tak být. Jen se divíte, mohli byste vysvětlit, proč se to neděje náhodně, ale v určitou dobu pokaždé, když spustím kód? Proč také dává konkrétní hodnota?
  • Zde je skvělý odkaz na cli / sei. nongnu.org/avr-libc/user-manual/… . S paměťovou bariérou není volatilní deklarace ve výše uvedeném kódu opravdu nutná. Zde je zábavné čtení tohoto tématu. jádro .org / doc / Dokumentace / volatile-považováno za škodlivé.txt
  • @MikaelPatel Pěkné, ale pro MCU to není relevantní.V této situaci je potřeba volatilita, aby se zabránilo kompilátoru v optimalizaci případů, kdy si myslí, že ‚ není používán (hodnota se nikdy nemění). CLI / SEI je tam proto, aby se operace Atomic WRT stala jediným dalším vláknem (přerušeními), které se provádí.
  • Zkusili jste zkompilovat kód s volatile a bez? Ale s kritickou částí (cli / sei). Snažím se diskutovat o konceptu paměťové bariéry a o tom, jak poskytuje volatilní přístup (a správné řazení) z kompilátoru s nutností deklarovat proměnné jako volatilní. Většina programátorů se učí, že jakákoli proměnná přístupná v ISR musí být deklarována jako volatilní, ale v tomto příběhu je toho mnohem víc.
  • Nemyslím si ‚ kompilátor má mnoho pojetí toho, co cli () a sei () dělají a jak by to ovlivnilo věci, jako je optimalizace z proměnných, které by neměly být ‚ optimalizovány. Vše, co sei () a cli () dělají, je manipulace s příznakem povoleným globálním přerušením v jeho registru. Nedělají nic pro tok kódu.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *