OpenSTLinux dla procesorów z rodziny STM32MP1 (3)

OpenSTLinux dla procesorów z rodziny STM32MP1 (3)

Ostatnim razem dokonaliśmy kilku zmian w konfiguracji jądra w celu dodania sterowników urządzeń peryferyjnych. Tym razem uzupełnimy te zmiany o wpisy w plikach device tree. Dzięki temu system operacyjny Linux, będzie mógł załadować odpowiednie sterowniki i skonfigurować podłączone przez nas urządzenia. Do modyfikacji device tree użyjemy narzędzia STM32CubeMX, które pozwoli nam w łatwy sposób dodać i skonfigurować niezbędne interfejsy.

STM32CubeMX

Narzędzie od STMicroelectronics służące m. in. do generacji plików device tree można pobrać bezpośrednio ze strony producenta: http://bit.ly/39OOOjP. Za jego pomocą można szybko przygotować odpowiadającą nam konfigurację peryferiów procesora, a następnie ręcznie uzupełnić fragmenty odpowiadające na przykład konkretnym sterownikom urządzeń zewnętrznych.

Oprócz zainstalowanego programu STM32CubeMX potrzebujemy również bazowego projektu dla modułu VisionSOM-STM32MP1 z płytką bazową VisionCB-STM32MP1-STD. Projekt ten znajdziemy w repozytorium firmy SoMLabs, przy czym należy zwrócić uwagę na odpowiednią wersje systemu – thud. Źródła możemy pobrać za pomocą polecenia:
git clone https://github.com/SoMLabs/openst-cube-mx.git -b thud

Rysunek 1. Otwarcie nowego projektu w STM32CubeMX

Po uruchomieniu programu STM32CubeMX, w jego głównym oknie klikamy opcje otwarcia nowego projektu (rysunek 1) i wybieramy plik VisionSOM.ioc z katalogu openst-cube-mx/VisionSOM, pobranego z repozytorium (rysunek 2).

Rysunek 2. Wskazanie pliku projektu VisionSOM

Podczas otwierania projektu zostaniemy zapytani, czy kontynuować z aktualną wersją, czy dokonać migracji na najnowszą wersję ekosystemu (rysunek 3). Wybieramy pierwszą z opcji, ponieważ używamy wersji 1.2.0. Na koniec musimy także wyrazić zgodę na pobranie odpowiednich bibliotek, zgodnych z wersją projektu. Możemy teraz przejść do modyfikacji device tree.

Rysunek 3. Okno migracji do nowej wersji ekosystemu

Wyświetlacz LCD RGB

Z uwagi na ograniczoną liczbę pinów musimy wyłączyć obsługę wyświetlacza z interfejsem RGB, aby mieć dostęp do pinów interfejsu SPI na złączu Raspberry Pi płytki VisionCB-STM32MP1-STD. Możemy to zrobić wybierając w programie STM32CubeMX interfejs LTDC w sekcji Multimedia (rysunek 4) i ustawiając wartość pola Display type na Disable. Spowoduje to zwolnienie wszystkich pinów zarezerwowanych wcześniej dla tego interfejsu. Zmiany w plikach device tree zostaną wprowadzone po wygenerowaniu projektu przyciskiem GENERATE CODE w prawym górnym rogu interfejsu.

Rysunek 4. Okno opcji interfejsu LTDC

W wygenerowanych plikach: VisionSOM/CA7/DeviceTree/VisionSOM/kernel/stm32mp157a-visionsom-mx.dts i VisionSOM/CA7/DeviceTree/VisionSOM/u-boot/stm32mp157a-visionsom-mx.dts musimy jeszcze ręcznie usunąć wszystkie sekcje panel { ... }; oraz backlight { ... }; aby uniknąć późniejszych błędów kompilacji.

Konfiguracja interfejsów

Dodanie sterowników urządzeń wymaga skonfigurowania interfejsów, do których będą one podłączone oraz odpowiednich linii GPIO. W tym przypadku musimy włączyć oraz skonfigurować interfejsy SPI3 oraz USART3. Magistrala I2C4, której również będziemy potrzebować jest już skonfigurowana, ponieważ w domyślnym obrazie jest do niej dołączony kontroler panelu dotykowego.

Na początku włączmy interfejs SPI3 wybierając go w programie STM32CubeMX na liście po prawej stronie w kategorii Connectivity. W ustawieniach interfejsu musimy przydzielić go do użycia z kontekstu Cortex-A7 zaznaczając opcję Cortex-A7 non secure (Linux) oraz wybrać tryb full duplex (Mode Full-Duplex Master). Program STM32CubeMX automatycznie przydziela do włączanego interfejsu piny GPIO, które można dowolnie modyfikować. W prawym oknie z widocznymi wyprowadzeniami procesora wyszukujemy pinu PC10, a z listy dostępnych funkcji wybieramy SPI3_SCK. Zmiana będzie widoczna w okienku GPIO Settings. Opisana konfiguracja została pokazana na rysunku 5.

Rysunek 5. Konfiguracja interfejsu SPI3 w programie STM32CubeMX

W oknie konfiguracji GPIO możemy także zmienić ustawienia poszczególnych pinów dodając na przykład rezystory podciągające dla sygnałów MOSI (PA8) i SCK (PC10). Ustawienia te zostały pokazane na rysunku 6.

Rysunek 6. Ustawienia GPIO dla interfejsu SPI

Drugi z interfejsów – USART3, konfigurujemy podobnie do poprzedniego. Ustawiamy asynchroniczny tryb interfejsu (Mode Asynchronous) bez kontroli przepływu danych (Hardware Flow Control Disable) i przypisujemy go do kontekstu Cortex-A7 non secure (Linux). Musimy także upewnić się, że odpowiednie piny zostały przypisane jako TX (PD8) i RX (PD9). Pełna konfiguracja USART3 w programie STM32CubeMX została pokazana na rysunku 7.

Rysunek 7. Konfiguracja portu USART3 w programie STM32CubeMX

Po wprowadzeniu zmian do projektu możemy wygenerować nową wersję plików device tree przyciskiem GENERATE CODE w prawym górnym rogu okna programu STM32CubeMX.

Niestety obecna wersja aplikacji nie pozwala na nadawanie nazw poszczególnym sekcjom device tree, więc skompilowanie tak wygenerowanego kodu zakończyłoby się błędem. Dlatego po wygenerowaniu kodu musimy ręcznie przywrócić ustawioną oryginalnie nazwę interfejsu pwm przez zmianę nazwy sekcji pwm{ ... }; na pwm1: pwm{ ... }; w plikach VisionSOM/CA7/DeviceTree/VisionSOM/kernel/stm32mp157a-visionsom-mx.dts i VisionSOM/CA7/DeviceTree/VisionSOM/u-boot/stm32mp157a-visionsom-mx.dts.

Teraz możemy przejść do definiowania urządzeń podłączonych do skonfigurowanych przez nas przed chwilą interfejsów.

Definiowanie sterowników

Aby system Linux był w stanie załadować i skonfigurować odpowiednie sterowniki, w device tree musi znajdować się dokładna informacja o tym z jakim urządzeniem ma do czynienia i jakie są wartości wymaganych parametrów. Dane te powinniśmy uzupełnić ręcznie w pliku VisionSOM/CA7/DeviceTree/VisionSOM/kernel/stm32mp157a-visionsom-mx.dts wygenerowanym przez STM32CubeMX, w sekcjach oznaczonych jako USER CODE odpowiednich interfejsów. Musimy przy tym zdefiniować dokładny model każdego z podłączanych urządzeń, ponieważ pojedynczy sterownik może obsługiwać całą rodzinę peryferiów. W naszych przykładach będziemy używać następujących urządzeń:

  • LSM6DS3 – akcelerometr z żyroskopem od ST,
  • PCA9533 – sterownik LED od NXP,
  • NEO-6M – odbiornik GPS od u-blox.

Szczegóły dotyczące wpisu w device tree można znaleźć w dokumentacji każdego ze sterowników. Pliki z opisem sterowników znajdują się w źródłach jądra i są to odpowiednio:

  • Documentation/devicetree/bindings/iio/imu/st_lsm6dsx.txt,
  • Documentation/devicetree/bindings/leds/leds-pca9532.txt,
  • Documentation/devicetree/bindings/gnss/gnss.txt.

SPI – LSM6DS3

Pierwsze z urządzeń – LSM6DS3, wymaga wpisu w sekcji interfejsu SPI, pokazanego na listingu 1.

Listing 1. Wpis w sekcji interfejsu SPI wymagany dla pierwszego z urządzeń – LSM6DS3

&spi3{
pinctrl-names = "default", "sleep";
pinctrl-0 = <&spi3_pins_mx>;
pinctrl-1 = <&spi3_sleep_pins_mx>;
status = "okay";
/* USER CODE BEGIN spi3 */
cs-gpios = <&gpiog 9 GPIO_ACTIVE_LOW>;
lsm6ds3@0 {
compatible = "st,lsm6ds3";
reg = <0x0>;
spi-max-frequency = <1000000>;
};
/* USER CODE END spi3 */
};

Pomiędzy znacznikami USER CODE musimy zdefiniować pin pełniący rolę sygnału chip select (cs-gpios), oraz podłączone do interfejsu urządzenie. Zapis @0 w nazwie sekcji oznacza adres – w SPI jest to indeks odpowiadający numerowi urządzenia, aby sterownik mógł wybrać odpowiedni pin chip select. W naszym przypadku mamy tylko jedno urządzenie o adresie 0.

Wewnątrz jego opisu znajdziemy pole compatible, które precyzuje dokładną nazwę. Na jej podstawie będzie wybierany sterownik, który to urządzenie obsłuży. Następnie mamy pole reg, które oznacza adres, podobnie jak w nazwie sekcji. Na koniec musimy jeszcze określić maksymalną prędkość zegara interfejsu SPI w polu spi-max-frequency.

I2C – PCA9533

Sterownik LED PCA9533 musimy podłączyć do magistrali I2C, dlatego informacje o tym urządzeniu dopisujemy do sekcji i2c4 w miejsce sterownika edt-ft5x06 (listing 2).

Listing 2. Informacje o sterowniku LED PCA9533, które musimy dopisać do sekcji i2c4 w miejsce sterownika edt-ft5x06

&i2c4{
pinctrl-names = "default", "sleep";
pinctrl-0 = <&i2c4_pins_mx>;
pinctrl-1 = <&i2c4_sleep_pins_mx>;
status = "okay";
/* USER CODE BEGIN i2c4 */
pca9533@62 {
compatible = "nxp,pca9533";
reg = <0x62>;
green {
label = "pca:green";
type = <PCA9532_TYPE_LED>;
};
red {
label = "pca:red";
type = <PCA9532_TYPE_LED>;
};
blue {
label = "pca:blue";
type = <PCA9532_TYPE_LED>;
};
};
/* USER CODE END i2c4 */
};

Podobnie jak w przypadku interfejsu SPI, także tutaj podajemy adres, jednak tym razem jest on związany bezpośrednio z magistralą I2C. Użyty w przykładzie moduł ma adres 0x62, więc taką samą wartość podajemy w definicji urządzenia. Wewnątrz jego sekcji musimy podać pole compatible o wartości odpowiadającej dokładnemu modelowi urządzenia – pca9533 oraz pole reg zawierające wspomniany już adres. Następnie musimy wymienić wszystkie kanały LED obsługiwane przez sterownik. W naszym module są to odpowiednio kolory: zielony, czerwony i niebieski. Do każdego kanału przypisujemy nazwę, pod którą będzie on widoczny w systemie plików oraz typ. Definicje obsługiwanych typów znajdują się w dodatkowym pliku nagłówkowym, który musimy dopisać na początku modyfikowanego pliku dts:

#include <dt-bindings/leds/leds-pca9532.h>

UART – NEO-6M

Ostatnim z opisywanych przez nas urządzeń jest odbiornik GPS NEO-6M. W przykładzie został podłączony do interfejsu UART, dlatego definiujemy go w sekcji usart3 (listing 3).

Listing 3. Odbiornik GPS NEO-6M podłączony do interfejsu UART wymaga zdefiniowania w sekcji usart3

&usart3{
pinctrl-names = "default", "sleep";
pinctrl-0 = <&usart3_pins_mx>;
pinctrl-1 = <&usart3_sleep_pins_mx>;
status = "okay";
/* USER CODE BEGIN usart3 */
gnss {
compatible = "u-blox,neo-8";
vcc-supply = <&vdd>;
current-speed = <9600>;
};
/* USER CODE END usart3 */
};

Pomiędzy znacznikami USER CODE dodajemy sekcję urządzenia, w której podajemy model (sterownik obsługuje wyłącznie modele neo-8 i neo-m8, jednak są one wstecznie kompatybilne ze starszymi neo-6). Dodatkowo podajemy nazwę regulatora zasilającego urządzenie i prędkość interfejsu. Oprócz definicji samego sterownika, musimy jeszcze dodać alias nowego portu szeregowego (usart3) do sekcji aliases (listing 4).

Listing 4. Alias nowego portu szeregowego (usart3) w sekcji aliases

aliases {
ethernet0 = &ethernet0;
serial0 = &uart4;
serial1 = &uart7;
serial2 = &usart3;
mmc0 = &sdmmc1;
mmc1 = &sdmmc2;
};

Podłączenie i uruchomienie

Każdy z opisanych modułów możemy podłączyć do złącza Raspberry Pi używając sygnałów wyprowadzonych na złącze szpilkowe. Zostały one zaznaczone na rysunku 8, dla każdego z interfejsów.

Rysunek 8. Sposób podłączenia modułów peryferyjnych do złącza Rapsberry Pi płytki VisionCB-STM32MP1-STD

Pierwszy z modułów – LSM6DS3, podłączamy do interfejsu SPI za pomocą pinów 19 (MOSI), 21 (MISO), 23 (SCK), 26 (CS) na złączu Raspberry. Po uruchomieniu systemu zobaczymy nowe urządzenia w katalogu /sys/bus/iio/devices: iio:device0 oraz iio:device1, tak jak zostało to pokazane na rysunku 9.

Rysunek 9. Pliki urządzenia LSM6DS3 reprezentujące akcelerometr

Reprezentują one odpowiednio akcelerometr i żyroskop, o czym możemy się przekonać odczytując plik name w każdym z nich. Wartości mierzone przez te czujniki możemy odczytać z plików:

  • akcelerometr: in_accel_x_raw, in_accel_y_raw, in_accel_z_raw,
  • żyroskop: in_anglvel_x_raw, in_anglvel_y_raw, in_anglvel_z_raw.

Współczynniki skali możemy natomiast odczytać z plików:

  • akcelerometr: in_accel_x_scale, in_accel_y_scale, in_accel_z_scale,
  • żyroskop: in_anglvel_x_scale, in_anglvel_y_scale, in_anglvel_z_scale.

Moduł PCA9533 musimy podłączyć do pinów numer 3 (SDA) i 5 (SCL) złącza Raspberry Pi. W systemie Linux zostanie on znaleziony na magistrali I2C4, o czym możemy przekonać się wywołując polecenie i2cdetect -y 0, które wypisze wszystkie zajęte adresy na danej magistrali. Adres 0x62, to właśnie sterownik LED (rysunek 10).

Rysunek 10. Zarezerwowany adres PCA9533 (0x62) na magistrali /dev/i2c-0

Dostęp do poszczególnych kanałów sterownika mamy za pośrednictwem katalogów /sys/class/leds/pca: blue, /sys/class/leds/pca: green oraz /sys/class/leds/pca: red, zgodnie z nazwami nadanymi poszczególnym kanałom w device tree. W każdym z tych katalogów znajduje się plik brightness, do którego możemy wpisywać wartości od 0 do 255 co odpowiada wypełnieniu sterującego diodami sygnału PWM w zakresie od 0% do 100%:

echo 255 > /sys/class/leds/pca:blue/brightness
echo 0 > /sys/class/leds/pca:blue/brightness

Ostatni z modułów, czyli NEO-6M podłączamy do interfejsu USART3 dostępnego na pinach 8 (TXD) i 10 (RXD) złącza Raspberry Pi, pamiętając o tym, że sygnał TXD modułu VisionSOM powinien być podłączony do sygnału RXD urządzenia NEO-6M, natomiast sygnał RXD modułu VisionSOM do TXD NEO-6M. W działającym systemie znajdziemy nowe urządzenie: /dev/gnss0.

Rysunek 11. Dane odczytywane z urządzenia NEO-6M

Odczytując je poleceniem cat /dev/gnss0 otrzymamy komunikaty wysyłane na port szeregowy przez przez moduł NEO-6M (rysunek 11).

Krzysztof Chojnowski

Artykuł ukazał się w
Elektronika Praktyczna
luty 2021

Elektronika Praktyczna Plus lipiec - grudzień 2012

Elektronika Praktyczna Plus

Monograficzne wydania specjalne

Elektronik kwiecień 2021

Elektronik

Magazyn elektroniki profesjonalnej

Raspberry Pi 2015

Raspberry Pi

Wykorzystaj wszystkie możliwości wyjątkowego minikomputera

Świat Radio kwiecień - maj 2021

Świat Radio

Magazyn krótkofalowców i amatorów CB

Automatyka Podzespoły Aplikacje kwiecień 2021

Automatyka Podzespoły Aplikacje

Technika i rynek systemów automatyki

Elektronika Praktyczna kwiecień 2021

Elektronika Praktyczna

Międzynarodowy magazyn elektroników konstruktorów

Elektronika dla Wszystkich maj 2021

Elektronika dla Wszystkich

Interesująca elektronika dla pasjonatów