Jeśli masz już pewne doświadczenie w programowaniu mikrokontrolerów, bez wątpienia docenisz, jak przydatna może być wbudowana programowalna jednostka we/wy w celu zmniejszenia obciążenia głównych rdzeni przetwarzających. Najważniejsze pytanie brzmi, czy to urządzenie nie jest nadmiernie skomplikowane w użyciu i czy po prostu jest warte wysiłku poznania go? Z pewnością lepiej zrozumiesz jego możliwości, gdy przeanalizujesz opisane tutaj praktyczne przykłady użycia układów PIO wbudowanych w procesor RP2040.
Jednostka PIO
Elastyczność programowalnej jednostki wejścia/wyjścia pozwala na użycie pinów I/O do implementacji innych typów protokołów komunikacyjnych oprócz tych już istniejących, takich jak SPI czy I²C. Jednostka składa się z dwóch oddzielnych bloków PIO, każdy z czterema procesorami I/O.
W sumie w RP2040 znajdują się dwie jednostki PIO, a każda zawiera cztery maszyny stanu (SM) i jedną wspólną pamięć instrukcji, o rozmiarze 32 słów, do sterowania nimi. Każda SM ma dwa 32-bitowe rejestry X i Y, 32-bitowy wejściowy rejestr przesuwny (ISR) i 32-bitowy wyjściowy rejestr przesuwny (OSR). Każdy z rdzeni PIO jest podłączony do procesora RP2040 za pomocą dwóch kolejek FIFO o szerokości 32 bitów i głębokości czterech słów. Jednostka FIFO TX służy do wysyłania, a jednostka FIFO RX do odbierania danych. Jeśli dane przepływają tylko w jednym kierunku, dwa FIFO można skonfigurować tak, aby działały jako pojedyncze o głębokości ośmiu słów. Każdy procesor PIO ma dzielnik z 16 bitami części całkowitej i z ośmioma bitami ułamkowymi do generowania zegara (patrz poniżej). Daje to elastyczność w ustawianiu częstotliwości taktowania każdego procesora w zakresie od 2 kHz do 125 MHz.
Procesory PIO są w stanie zrozumieć tylko dziewięć instrukcji. Instrukcje te są zakodowane na 16 bitach i są przechowywane w 32×16-bitowej pamięci instrukcji, która steruje czterema modułami SM w każdym bloku PIO. Poszczególne programy PIO są na tyle krótkie, że mieszczą się w tej małej pamięci. Instrukcje są zapisywane tutaj przez procesor RP2040. Te małe programy są pisane przy użyciu specjalnego, ale dość podstawowego asemblera PIO. Wykonanie każdej instrukcji zajmuje jeden cykl zegara.
Pojedynczy procesor PIO może adresować tylko kilka pinów GPIO i używa do tego krótkich adresów. Mapowanie I/O określa, które piny są następnie konkretnie adresowane. Istnieje kilka programowalnych stałych, które określają adres pierwszego pinu dla danego obszaru (dostępnego odpowiednio instrukcjami in, out, side i set). Umożliwia to bardzo elastyczne definiowanie pinów I/O procesora PIO. Wiele procesorów może również uzyskać dostęp do tego samego pinu.
Narzędzia
Jako środowisko programistyczne służy tutaj Visual Studio Code. Instalacja i użycie tego narzędzia są dobrze opisane w [1]. Znajdziesz tam również informacje dotyczące korzystania z generatora projektów. Służy on do tworzenia projektu dla Raspberry Pi ze wszystkimi niezbędnymi plikami (kod źródłowy, pliki konfiguracyjne, Make-Setup itp.).
Musisz także określić, czy funkcja printf() będzie wysyłać dane przez USB czy UART. Tutaj możesz także podać, jakich bibliotek chcesz używać. W tym artykule na przykład używamy narzędzia PIO. Pomoc dotyczącą instalacji VS Code można także znaleźć w [2]. Znajduje się tam również szczegółowy opis, jakie dodatkowe narzędzia (Make, Git, itp.) należy zainstalować.
Wszystkie przykłady oprogramowania można pobrać z [3].
Zacznijmy eksperymenty
Chcemy, aby nasz pierwszy program wysyłał impulsy o długości 1 µs, z czterech pinów wyjściowych, jeden po drugim. Czterokanałowy oscyloskop jest podłączony do czterech pinów wyjściowych, w celu oglądania powstałych sygnałów.
Poniższy listing zawiera pierwszy przykładowy program wykorzystujący PIO. Po trzech liniach dyrektyw asemblera, pierwsza wykonywalna instrukcja znajduje się w linii 4. Ta i kolejne linie generują następujące po sobie cztery impulsy.
Linie 4 i 5 zawierają instrukcje nop, które, jak można podejrzewać, nie robią nic, ale linia 4 jest przedłużona o tekst side 0b01. Oznacza to, że w tej linii „zrobiono coś na boku”. W tym przypadku „boczny” styk 0 jest ustawiony na „1”, a „boczny” styk 1 jest ustawiany na „0”. Każdą instrukcję PIO można w ten sposób rozszerzyć. Dzięki temu możesz łatwo wpływać na stan kilku pinów jednocześnie. Ważne jest, aby określić, które piny są rozstawione „bocznie”, używając poleceń konfiguracyjnych napisanych w C. Definicja została dokonana w C, przy użyciu funkcji sm_config_set_sideset_pins() w linii 5. Oznacza to, że piny zestawu bocznego (side set) zaczynają się od GP2.
Instrukcja nop z linii 4 listingu 1 generuje zbocze narastające poprzez ustawienie 1 na „bocznym” pinie nr 1 (= GP3). Linia 6 zawiera instrukcję set, która ustawia piny nr 0 i 1. Impuls dociera następnie do pinu GP4. Styki „boczne” 0 (GP2) i 1 (GP3) są zerowane instrukcją side. W procedurze konfiguracyjnej na drugim listingu, w linii 3, ustala się, że piny ustawiane instrukcją set zaczynają się od końcówki o numerze GP4. Instrukcja set może także zostać użyta do załadowania rejestrów lub pinów I/O wartościami stałymi.