Co dzieje się we wbudowanym procesorze, gdy wykonanie osiąga tę końcową return
instrukcję Czy wszystko po prostu się zawiesza; zużycie energii itp., z jednym długim wiecznym NOP na niebie? lub czy NOP są ciągle wykonywane, czy procesor całkowicie się wyłączy?
Jednym z powodów, dla których pytam, jest to, czy zastanawiam się, czy procesor musi się wyłączyć, zanim skończy wykonywanie, a jeśli tak, to w jaki sposób kiedykolwiek zakończy wykonywanie, jeśli wcześniej się wyłączył.
Odpowiedzi:
To pytanie zawsze zadawał mi mój tata. „ Dlaczego po prostu nie wykonuje wszystkich instrukcji i nie zatrzymuje się na końcu? ”
Rzućmy okiem na patologiczny przykład. Poniższy kod został skompilowany w kompilatorze C18 Microchip dla PIC18:
Tworzy następujące dane wyjściowe asemblera:
Jak widać, wywoływana jest funkcja main (), która na końcu zawiera instrukcję return, chociaż nie umieściliśmy jej tam osobiście. Kiedy main powraca, CPU wykonuje następną instrukcję, która jest po prostu GOTO, aby powrócić do początku kodu. main () jest po prostu wywoływany w kółko.
Powiedziawszy to, ludzie nie robią tego zwykle. Nigdy nie napisałem żadnego wbudowanego kodu, który pozwalałby main () na takie wyjście. Przeważnie mój kod wyglądałby mniej więcej tak:
Więc normalnie nigdy nie pozwoliłbym main () wyjść.
„OK ok” mówisz. Wszystko to jest bardzo interesujące, ponieważ kompilator upewnia się, że nigdy nie ma ostatniego zwrotu. Ale co się stanie, jeśli wymuszimy ten problem? Co jeśli ręcznie zakoduję asembler i nie cofnę skoku na początek?
Oczywiście procesor wykonywałby kolejne instrukcje. Te wyglądałyby mniej więcej tak:
Następny adres pamięci po ostatniej instrukcji w main () jest pusty. W mikrokontrolerze z pamięcią FLASH pusta instrukcja zawiera wartość 0xFFFF. Przynajmniej na PIC ten kod operacji jest interpretowany jako „nop” lub „brak operacji”. Po prostu nic nie robi. Procesor kontynuowałby wykonywanie tych zer do końca pamięci.
Co potem?
W ostatniej instrukcji wskaźnikiem instrukcji procesora jest 0x7FFe. Gdy CPU dodaje 2 do wskaźnika instrukcji, otrzymuje 0x8000, co jest uważane za przepełnienie PIC z jedynie 32k FLASH, więc zawija się z powrotem do 0x0000, a CPU szczęśliwie kontynuuje wykonywanie instrukcji z powrotem na początku kodu , tak jakby został zresetowany.
Pytałeś także o potrzebę wyłączenia zasilania. Zasadniczo możesz robić, co chcesz, i to zależy od twojej aplikacji.
Jeśli miałeś aplikację, która musiała zrobić tylko jedną rzecz po włączeniu zasilania, a następnie nie robić nic innego, możesz po prostu odłożyć trochę czasu (1); na końcu main (), aby procesor przestał robić cokolwiek zauważalnego.
Jeśli aplikacja wymagała wyłączenia procesora, wówczas w zależności od procesora prawdopodobnie będą dostępne różne tryby uśpienia. Procesory mają jednak zwyczaj budzić się ponownie, więc musisz się upewnić, że nie ma limitu czasu na sen i nie jest włączony Timer Watch Dog itp.
Możesz nawet zorganizować jakieś zewnętrzne obwody, które pozwolą procesorowi całkowicie odciąć własną moc po zakończeniu. Zobacz to pytanie: Używanie przycisku chwilowego jako przełącznika dwustabilnego włączania / wyłączania .
źródło
W przypadku skompilowanego kodu zależy to od kompilatora. Kompilator gcc ARM Rowley CrossWorks, którego używam, przeskakuje do kodu w pliku crt0.s, który ma nieskończoną pętlę. Kompilator Microchip C30 dla 16-bitowych urządzeń dsPIC i PIC24 (również opartych na gcc) resetuje procesor.
Oczywiście większość wbudowanych programów nigdy się tak nie kończy i wykonuje kod w sposób ciągły w pętli.
źródło
Należy tu zwrócić uwagę na dwa punkty:
Koncepcja zamknięcia programu zwykle nie istnieje w środowisku osadzonym. Na niskim poziomie procesor wykona instrukcje, dopóki może; nie ma czegoś takiego jak „końcowe oświadczenie zwrotne”. Procesor może przerwać wykonywanie, jeśli napotka nieodwracalny błąd lub zostanie wyraźnie zatrzymany (przełączony w tryb uśpienia, tryb niskiego poboru mocy itp.), Ale należy pamiętać, że nawet tryby uśpienia lub usterki nieodwracalne ogólnie nie gwarantują, że nie będzie już więcej kodu być straconym. Możesz obudzić się z trybów uśpienia (tak są zwykle używane), a nawet zablokowany procesor może nadal wykonywać procedurę obsługi NMI (tak jest w przypadku Cortex-M). Watchdog również będzie nadal działał i możesz nie być w stanie go wyłączyć na niektórych mikrokontrolerach, gdy jest włączony. Szczegóły różnią się znacznie w zależności od architektury.
W przypadku oprogramowania układowego napisanego w języku takim jak C lub C ++, co się stanie, jeśli wyjście main () zostanie określone przez kod startowy. Na przykład tutaj jest odpowiednia część kodu startowego ze Standardowej Biblioteki Peryferyjnej STM32 (w przypadku łańcucha narzędzi GNU komentarze są moje):
Ten kod wejdzie w nieskończoną pętlę, gdy main () powróci, chociaż w sposób nieoczywisty (
bl main
ładuje sięlr
z adresem następnej instrukcji, która faktycznie jest skokiem do siebie). Nie podejmowane są próby zatrzymania procesora lub przejścia w tryb niskiego poboru mocy itp. Jeśli masz uzasadnioną potrzebę zastosowania tego w swojej aplikacji, musisz to zrobić samodzielnie.Należy zauważyć, że jak określono w ARMv7-M ARM A2.3.1, rejestr łącza jest ustawiany na 0xFFFFFFFF po zresetowaniu, a rozgałęzienie pod tym adresem wywoła błąd. Dlatego projektanci Cortex-M postanowili potraktować zwrot z procedury resetowania jako nienormalny i trudno się z nimi kłócić.
Mówiąc o uzasadnionej potrzebie zatrzymania procesora po zakończeniu oprogramowania układowego, trudno sobie wyobrazić takie, które nie byłyby lepiej obsługiwane przez wyłączenie urządzenia. (Jeśli wyłączysz procesor „na dobre”, jedyne, co można zrobić z urządzeniem, to cykl zasilania lub resetowanie sprzętu zewnętrznego.) Możesz usunąć sygnał ENABLE dla konwertera DC / DC lub wyłączyć zasilanie w w inny sposób, tak jak robi to komputer ATX.
źródło
loop: b loop
. Zastanawiam się, czy rzeczywiście zamierzali dokonać zwrotu, ale zapomnieli zapisaćlr
.Pytając o
return
, myślisz o zbyt wysokim poziomie. Kod C jest tłumaczony na kod maszynowy. Jeśli więc pomyślisz o tym, że procesor ślepo wyciąga instrukcje z pamięci i wykonuje je, nie ma pojęcia, który z nich jest „ostateczny”return
. Tak więc procesory nie mają wrodzonego końca, ale programista zajmuje się końcową sprawą. Jak Leon wskazuje w swojej odpowiedzi, kompilatory zaprogramowały zachowanie domyślne, ale często programista może chcieć własnej sekwencji wyłączania (robiłem różne rzeczy, takie jak wejście w tryb niskiego poboru mocy i zatrzymanie lub czekanie na podłączenie kabla USB w, a następnie restart).Wiele mikroprocesorów posiada instrukcje zatrzymania, które zatrzymują procesor bez wpływu na części ciała. Inne procesory mogą polegać na „zatrzymaniu” poprzez zwykłe wielokrotne przeskakiwanie pod ten sam adres. Możliwe są opcje, ale to zależy od programisty, ponieważ procesor po prostu będzie dalej czytał instrukcje z pamięci, nawet jeśli ta pamięć nie była zamierzona jako instrukcje.
źródło
Problem nie jest osadzony (system osadzony może obsługiwać Linuxa, a nawet Windowsa), ale samodzielny lub bez systemu operacyjnego: (skompilowana) aplikacja jest jedyną rzeczą, która działa na komputerze (nie ma znaczenia, czy jest to mikrokontroler lub mikroprocesor).
W przypadku większości języków język nie określa, co się stanie, gdy zakończy się „główny” i nie ma systemu operacyjnego, do którego mógłby wrócić. W przypadku C zależy to od tego, co znajduje się w pliku startowym (często crt0.s). W większości przypadków użytkownik może (a nawet musi) dostarczyć kod startowy, więc ostateczna odpowiedź brzmi: cokolwiek napiszesz, to kod startowy lub co dzieje się w określonym przez Ciebie kodzie startowym.
W praktyce istnieją 3 podejścia:
nie podejmuj żadnych specjalnych środków. co dzieje się, gdy główne zwroty są niezdefiniowane.
przeskocz do 0 lub użyj dowolnego innego sposobu, aby zrestartować aplikację.
wejść w ciasną pętlę (lub wyłączyć przerwania i wykonać polecenie zatrzymania), blokując procesor na zawsze.
To, co jest właściwe, zależy od zastosowania. Fur-elise kartka z życzeniami i system kontroli hamulca (żeby wspomnieć tylko o dwóch systemach wbudowanych) prawdopodobnie powinien się zrestartować. Minusem ponownego uruchomienia jest to, że problem może pozostać niezauważony.
źródło
Kiedyś patrzyłem na jakiś zdemontowany kod ATtiny45 (C ++ skompilowany przez avr-gcc), a na końcu kodu przeskakuje do 0x0000. Zasadniczo robi reset / restart.
Jeśli kompilator / asembler pomija ten ostatni skok do 0x0000, wszystkie bajty w pamięci programu są interpretowane jako „poprawny” kod maszynowy i działa przez cały czas, aż licznik programu przejdzie do 0x0000.
W AVR 00 bajtów (domyślna wartość, gdy komórka jest pusta) to NOP = brak operacji. Działa więc bardzo szybko, nie robiąc nic, tylko trochę czasu.
źródło
Zasadniczo skompilowany
main
kod jest następnie łączony z kodem startowym (może być zintegrowany z zestawem narzędzi, dostarczanym przez dostawcę układu, napisany przez ciebie itp.).Linker umieszcza następnie cały kod aplikacji i uruchamiania w segmentach pamięci, więc odpowiedź na pytania zależy od: 1. kodu od uruchomienia, ponieważ może na przykład:
bl lr
lubb .
), która będzie podobna do „końca programu”, ale przerwania i urządzenia peryferyjne włączone wcześniej będą nadal działać,main
).po prostu zignoruj „co będzie dalej” po wezwaniu do
main
zwrotu.main
zachowania będą zależeć od ciebie linkera (i / lub skryptu linkera używanego podczas łączenia).Jeśli po Twojej
main
nazwie zostanie umieszczona inna funkcja / kod , zostanie wykonana z niepoprawnymi / niezdefiniowanymi wartościami argumentów,Jeśli watchdog jest włączony, ostatecznie zresetuje MCU pomimo wszystkich nieskończonych pętli, w których się znajdujesz (oczywiście jeśli nie zostaną przeładowane).
źródło
Najlepszym sposobem na zatrzymanie wbudowanego urządzenia jest wieczne czekanie z instrukcjami NOP.
Drugim sposobem jest zamknięcie urządzenia za pomocą samego urządzenia. Jeśli możesz sterować przekaźnikiem za pomocą instrukcji, możesz po prostu otworzyć przełącznik, który zasila urządzenie wbudowane i huh urządzenie wbudowane zniknęło bez zużycia energii.
źródło
Zostało to jasno wyjaśnione w instrukcji. Zazwyczaj procesor generuje ogólny wyjątek, ponieważ uzyskuje dostęp do lokalizacji pamięci znajdującej się poza segmentem stosu. [wyjątek ochrony pamięci].
Co miałeś na myśli przez system wbudowany? Mikroprocesor lub mikrokontroler? Tak czy inaczej, jest to zdefiniowane w instrukcji.
W procesorze x86 wyłączamy komputer, wysyłając polecenie do kontrolera ACIP. Wejście w tryb zarządzania systemem. Tak więc kontroler jest układem we / wy i nie trzeba go ręcznie wyłączać.
Przeczytaj specyfikację ACPI, aby uzyskać więcej informacji.
źródło