Robiłem mały projekt z Arduino Uno. Obejmowało przerwania, ponieważ używam enkoderów do pomiaru, jak bardzo układ koła różnicowego porusza się do przodu. Mój robot porusza się tylko do przodu. Więc używam tylko jednego kanału z każdego kodera. Oto moje dwie procedury przerwań:
ISR (INT0_vect){ encoderRPos = encoderRPos + 1; } ISR (INT1_vect){ encoderLPos = encoderLPos + 1; }
Zmienne encoderRPos
i encoderLPos
są typu volatile int
. Rozumiem, że zmienne, które ulegają zmianie w dowolnej procedurze przerwań, muszą być typu ulotnego. Ma to na celu ostrzeżenie innych części kodu, które używają tych zmiennych, że mogą się zmienić w dowolnym momencie.
Ale to, co stało się w moim kodzie, było trochę dziwne i nie potrafiłem tego wyjaśnić. Oto jak obliczam odległość pokonaną przez lewe koło:
#define distancePerCount 0.056196868 float SR = distancePerCount * (encoderRPos - encoderRPosPrev); float SL = distancePerCount * (encoderLPos - encoderLPosPrev); encoderRPosPrev = encoderRPos; encoderLPosPrev = encoderLPos;
Ale kiedy drukuję na moim monitorze szeregowym, zauważam anomalię:
Jeśli spojrzysz na trzecią kolumna, (SL) jej wartość jest zbyt wysoka tylko na jakiś czas. To zaburza wszystkie moje obliczenia.
Jedyna wskazówka, jaką mogę uzyskać, jeśli wezmę wartość SL, którą otrzymałem ( 3682), która jest zawsze stała, i obliczam z powrotem (encodeLPos - encoderLPosPrev)
, otrzymam 65519,66, czyli blisko maksymalnej wartości unsigned int
. Co oznacza, że (encoderLPos - encoderLPosPrev)
powoduje przepełnienie, podczas gdy obie wartości, których różnica jest brana, wynoszą około 5000!
I udało mi się to rozwiązać. szczęście. Tak zmodyfikowałem kod:
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;
Nie mogę pojąć, co się stało. Czy jest coś o zmiennych zmiennych, o których powinienem wiedzieć?
Aktualizacja: Tutaj jest cały kod, jeśli chcesz rzucić okiem. I działa bardzo dobrze po zmianie na to, co zostało zasugerowane w zaakceptowanej odpowiedzi.
Komentarze
Odpowiedź
Musisz się dowiedzieć o krytycznych sekcjach .
Co Prawdopodobnie dzieje się tak, że zmienne są zmieniane przez procedury przerwań w połowie obliczeń. Twoja „poprawka” skraca czas spędzony na wykonywaniu obliczeń ze zmiennymi nietrwałymi, zmniejszając w ten sposób prawdopodobieństwo wystąpienia kolizji.
Co powinieneś zrobić, to skopiować zmienne ulotne do zmiennych lokalnych z wyłączonymi przerwaniami krótki okres.
cli(); int l = encoderLpos; int r = encoderRpos; sei();
Ponieważ Arduino jest 8-bitowym procesorem, wykonanie operacji matematycznych na wartościach 16-bitowych wymaga wielu instrukcji asemblera. Zmienny punkt jest jeszcze gorszy, gdy używa się wielu instrukcji do prostego dodawania. Dzielenie i mnożenie używają znacznie więcej. Podczas tej listy instrukcji przerwanie ma wiele okazji do odpalenia. Wykonując takie przypisanie, a następnie używając nowych zmiennych lokalnych w swoich obliczeniach, instrukcje potrzebne do radzenia sobie ze zmiennymi nietrwałymi są ograniczone do absolutnego minimum. Wyłączając przerwania podczas przypisywania, gwarantujesz, że zmienne nie mogą być nigdy zmieniane podczas ich używania. Ten fragment kodu nazywa się sekcją krytyczną .
Komentarze
- To może być przypadek. Zastanawiam się tylko, czy mógłbyś wyjaśnić, dlaczego nie dzieje się to losowo, ale w określonym czasie za każdym razem, gdy uruchamiam kod? Również dlaczego daje konkretną wartość?
- Oto świetne odniesienie do cli / sei. nongnu.org/avr-libc/user-manual/… . W powyższym kodzie tak naprawdę nie jest potrzebna deklaracja ulotna z barierą pamięci. Oto kilka zabawnych lektur na ten temat. jądro .org / doc / Documentation / volatile-znamion-szkodliwe.txt
- @MikaelPatel Nice, ale nie dotyczy to MCU.Zmienna jest wymagana w tej sytuacji, aby zapobiec optymalizacji wystąpień kompilatora, w których uważa, że ' nie są używane (wartość nigdy się nie zmienia). Cli / sei jest po to, aby uczynić operację atomic WRT jedynym innym wątkiem (przerwaniami), który jest wykonywany.
- Czy próbowałeś skompilować kod zi bez volatile? Ale z sekcją krytyczną (cli / sei). To, co próbuję omówić, to koncepcja bariery pamięci i tego, jak zapewnia ona nietrwały dostęp (i prawidłową kolejność) z kompilatora z koniecznością deklarowania zmiennych jako nietrwałych. Większość programistów uczy się, że każda zmienna dostępna w ISR musi być zadeklarowana jako zmienna, ale jest o wiele więcej w tej historii.
- Nie ' nie sądzę, że kompilator ma wiele koncepcji tego, co robią cli () i sei () i jak to wpłynie na takie rzeczy, jak optymalizacja ze zmiennych, które nie powinny być ' optymalizowane. Wszystkie sei () i cli () do manipulują flagą włączonego globalnego przerwania w swoim rejestrze. Nie robią nic dla przepływu kodu.
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?
– Prawdopodobnie mógłbym to zrobić, gdybym zobaczył cały kod. W międzyczasie przeczytaj to: gammon.com.au/interrupts3683 / .056196868 = 65537
, więc wygląda na to, że zwiększył się w niewłaściwym momencie, tak? Uzyskujesz dostęp do zmiennej, która może zostać zmieniona w przerwaniu wiele razy w tym kodzie, więc uzyskanie lokalnej kopii przy wyłączonych przerwaniach byłoby znacznie bezpieczniejsze.