Masz jednordzeniowy, jednowątkowy procesor. Oznacza to, że w danym momencie robi dokładnie jedną rzecz. Jeśli twoja aplikacja wymaga od tego kilku rzeczy, to kod musi być zaprojektowany tak, aby przełączał się między nimi wszystkimi. Może to być tak banalne lub złożone, jak chcesz.
Zwykle procesor kręci się wokół głównej pętli, robiąc wszystko, co tam jest, i to wszystko dobrze i dobrze, dopóki nie nastąpi przerwanie. Sprzęt przerwań w zasadzie wymusza wywołanie funkcji do odpowiedniego ISR, mimo że nie ma dla niego instrukcji wywołania w głównej pętli. Z tej nieprzewidywalności wywodzi się większość reguł pisania ISRów.
Niezależnie od tego, jaki czas spędzasz w ISR, jest to czas, w którym główna pętla zostaje wstrzymana w oczekiwaniu na powrót ISR. Jeśli główna pętla jest jedyną, która resetuje aktywny zegar watchdoga (bardzo dobra praktyka), wówczas watchdog nie zostanie zresetowany w tym czasie. Jeśli watchdog przekroczy limit czasu, spowoduje to twardy reset. Podobnie jak reset zewnętrzny, ale z różnymi flagami, które można sprawdzić podczas uruchamiania. To prawdopodobnie „awaria”, o której słyszałeś.
Bardzo dobrą praktyką jest używanie watchdoga i resetowanie go tylko przy każdym przejściu wokół głównej pętli. Zmusza to do pisania kodu, który pozostaje responsywny. Jeśli musisz na coś poczekać, możesz ustawić wydarzenie (zakończony czas, kolejna otrzymana postać itp.) I przejść dalej. Sprawdzaj okresowo, czy wystąpiło to zdarzenie lub ustaw kolejne przerwanie na jego zakończenie, a następnie wróć do niego. W międzyczasie kontynuuj wszystko, co robiłeś.
Moja główna struktura wygląda zwykle tak:
#include "module1.h"
#include „module2.h”
void main (void)
{
//ogólny
//żeton
//Ustawiać
mod1_init ();
mod2_init ();
// wyczyść flagi przerwań
// włączenie globalnego przerwania
podczas gdy (1)
{
// wyczyść watchdog
mod1_run ();
mod2_run ();
}
}
A moje moduły wyglądają tak:
void modX_init (void)
{
// sprzęt i zmienna init tylko dla tego modułu
// nie używaj przerwań, jeśli odpytywanie jest wystarczająco dobre
}
void modX_run (void)
{
jeśli (POLLED_INTERRUPT_FLAG)
{
POLLED_INTERRUPT_FLAG = 0;
// nieblokujący kod „ISR”
}
}
void ISR modX_ISR (nieważne)
{
// OK, to wymaga * natychmiastowej * odpowiedzi
// Spędź tutaj absolutnie minimalny czas i wyjdź
}
Sygnatury funkcji nie muszą być void
, ale większość z nich tak. Czasami w jednym module mam trochę czasu, który jest również używany przez inny, i przydatne jest użycie wartości zwracanej z jednej modX_run ()
i argumentów innego (lub jakiejś podstawowej logiki) do nawiązać połączenie. Na przykład:
if (DMX_run ()) // zawiera własne taktowanie i zwraca true na początku każdego interwału 30 Hz, w przeciwnym razie false
{
I2C_start (); // Ramki I2C są synchronizowane z DMX
}
I2C_run (); // po uruchomieniu ramka I2C działa swobodnie aż do zakończenia
Jeśli przestudiujesz arkusz danych, może się okazać, że sprzętowe urządzenia peryferyjne można masować, aby robić to, co chcesz, bez jakiejkolwiek interwencji procesora.
Powszechnie jest na przykład generowanie impulsu wyjściowego. Włącz go, ustaw urządzenie peryferyjne, aby je wyłączało jakiś czas później i zapomnij o tym. Zwykle znajduje się w tym samym obszarze co PWM.