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:
void main(void)
{
}
Tworzy następujące dane wyjściowe asemblera:
addr opco instruction
---- ---- -----------
0000 EF63 GOTO 0xc6
0002 F000 NOP
0004 0012 RETURN 0
.
. some instructions removed for brevity
.
00C6 EE15 LFSR 0x1, 0x500
00C8 F000 NOP
00CA EE25 LFSR 0x2, 0x500
00CC F000 NOP
.
. some instructions removed for brevity
.
00D6 EC72 CALL 0xe4, 0 // Call the initialisation code
00D8 F000 NOP //
00DA EC71 CALL 0xe2, 0 // Here we call main()
00DC F000 NOP //
00DE D7FB BRA 0xd6 // Jump back to address 00D6
.
. some instructions removed for brevity
.
00E2 0012 RETURN 0 // This is main()
00E4 0012 RETURN 0 // This is the initialisation code
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:
void main(void)
{
while(1)
{
wait_timer();
do_some_task();
}
}
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:
addr opco instruction
---- ---- -----------
00E6 FFFF NOP
00E8 FFFF NOP
00EA FFFF NOP
00EB FFFF NOP
.
. some instructions removed for brevity
.
7EE8 FFFF NOP
7FFA FFFF NOP
7FFC FFFF NOP
7FFE FFFF NOP
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 .