3.1. Mikrokontrolery¶
OpenMV Cam działa na mikrokontrolerze (MCU): pojedynczym układzie scalonym, który łączy w sobie procesor (CPU), pamięć roboczą (RAM), pamięć programu (pamięć flash) oraz zestaw urządzeń peryferyjnych – bloków sprzętowych do interakcji ze światem zewnętrznym.
Najciekawszą częścią są urządzenia peryferyjne. Każde z nich to fragment krzemu dedykowany jednemu zadaniu: ustawianiu pinu w stan wysoki lub niski, pomiarowi napięcia analogowego, taktowaniu bajtów przesyłanych magistralą szeregową. Procesor konfiguruje i odczytuje każde urządzenie peryferyjne poprzez rejestry – stałe adresy pamięci, które sprzęt obserwuje i aktualizuje.
MicroPython opakowuje te rejestry w klasy wewnątrz modułu machine. machine.Pin(...) zwraca obiekt sterujący pinem ogólnego przeznaczenia wejścia/wyjścia (GPIO) – przewodem, który układ może utrzymywać w stanie wysokim (około 3,3 V) lub niskim (około 0 V), albo odczytywać jako jeden z tych dwóch stanów, gdy steruje nim coś zewnętrznego. machine.ADC(...) udostępnia przetwornik analogowo-cyfrowy, który mierzy napięcie na pinie i raportuje je jako liczbę. machine.UART(...) obsługuje uniwersalny asynchroniczny odbiornik/nadajnik (UART) – urządzenie peryferyjne, które wysyła i odbiera bajty bit po bicie przez parę przewodów: TX (nadawanie) i RX (odbiór). Pozostałe klasy obejmują resztę urządzeń peryferyjnych. Skrypt odczytuje i zapisuje obiekty Pythona; MicroPython tłumaczy każdy dostęp na odpowiednie odczyty i zapisy rejestrów, a te przesuwają bity na fizycznych przewodach.
MCU pakuje procesor, pamięć i urządzenia peryferyjne w pojedynczy układ. Każde urządzenie peryferyjne jest udostępniane Pythonowi przez klasę w module machine.¶
3.1.1. Główna pętla¶
Niemal każdy program mikrokontrolera ma taki sam kształt: jednorazowa konfiguracja na początku skryptu (import modułów, konfiguracja pinów, otwarcie magistral), a następnie nieskończona pętla while True: na dole. Wewnątrz pętli program raz po raz odczytuje wejścia, podejmuje decyzje i aktualizuje wyjścia. Pętla jest programem; gdy skrypt się kończy, urządzenie przestaje cokolwiek robić.
# setup, runs once
from machine import Pin
led = Pin("P0", Pin.OUT)
# main loop, runs forever
while True:
led.value(1)
# ... do work ...
led.value(0)
# ... do other work ...
Ten kształt – jednorazowa konfiguracja, a następnie pętla w nieskończoność – to wzorzec głównej pętli. Wszystko, co następuje dalej, dotyczy tego, co umieszcza się w jej wnętrzu.
3.1.2. Sterowanie w czasie rzeczywistym¶
Program desktopowy działa obok wielu innych. System operacyjny planuje jego pracę w jednym lub kilku wątkach – niezależnych strumieniach wykonania, między którymi przełącza się milisekunda po milisekundzie. Gdy jeden wątek czeka na operację wejścia/wyjścia (dysk, sieć, poruszenie myszą przez użytkownika), system operacyjny przekazuje procesor innemu. Program jest w dużej mierze sterowany zdarzeniami: menedżer okien wywołuje Twój kod, gdy nadejdzie sygnał wejściowy, biblioteka HTTP wznawia Twój kod, gdy na gnieździe pojawią się bajty. To coś większego wywołuje Twój kod.
Program mikrokontrolera jest przeciwieństwem tego. Domyślnie nie ma systemu operacyjnego, planisty ani innego wątku. Pokazana przed chwilą główna pętla jest jedyną pętlą. Urządzenia peryferyjne zgłaszają przerwania lub udostępniają flagi stanu; pętla je odpytuje albo obsługuje przerwania bezpośrednio. Jeśli pętla utknie w time.sleep_ms(1000), urządzenie przez tę sekundę nie robi nic; nie ma innego wątku, który wypełniłby tę lukę.
Wynikają z tego dwie konsekwencje, które obowiązują wszędzie:
Czas jest realny. Dwukrotny odczyt pinu w ciasnej pętli zajmuje mikrosekundy; uśpienie na dziesięć milisekund oznacza dziesięć milisekund, w których nic innego się nie dzieje. Odpowiedzią jest wzorzec nieblokującego pomiaru czasu.
Sprzęt jest realny. Ustawienie
machine.Pin.valuena1powoduje pojawienie się około 3,3 V na fizycznym przewodzie; ustawienie na0daje tam około 0 V. Inne części obwodu od razu widzą to napięcie – w tym wszelkie komponenty, które pin może uszkodzić, jeśli zostanie błędnie wysterowany.