Wprowadzenie
Zacznijmy od tego czym jest Yocto Project. Yocto Project to zestaw narzędzi, które pozwalają na tworzenie własnych, dopasowanych systemów Linux dla urządzeń wbudowanych. W przeciwieństwie do gotowych dystrybucji, takich jak Ubuntu czy Debian, Yocto pozwala zbudować system zawierający elementy których potrzebujemy. Dzięki takiemu rozwiązaniu obraz może być mniejszy, bezpieczniejszy i lepiej dostosowany do konkretnej platformy sprzętowej. Yocto umożliwia także pełną kontrolę nad wersjami kernela, bootloadera, bibliotek, sterowników czy aplikacji. Konfiguracja systemu zapisywana jest w plikach tekstowych, co zapewnia powtarzalność procesu budowania i ułatwia jego automatyzację. Własne aplikacje możemy integrować z systemem za pomocą receptur i umieszczać w oddzielnej warstwie projektu. Podejście to pozwala zachować podział między częścią dostarczoną od producenta platformy a naszymi elementami, które będą już charakterystyczne tylko dla konkretnego produktu. Yocto dobrze sprawdza się w projektach komercyjnych, urządzeniach przemysłowych oraz systemach, które mają być rozwijane i utrzymywane przez wiele lat. Jeżeli chodzi o główne wady to należy zazanczyć, że Yocto posiada stosunkowo wysoki próg wejścia, dodatkowo czas pierwszej kompilacji trwa bardzo długo oraz co ważniejsze wymagania sprzętowe komputera używanego do budowania obrazu są duże. Pomimo tych trudności Yocto daje kontrolę, elastyczność i powtarzalność, które są szczególnie ważne podczas tworzenia systemu Linux dla platformy STM32MP1/MP2.
Budowa dystrybucji w oparciu o pliki dostarczone od STMicroelectronics
Zacznijmy od stworzenia folderu na dysku
mkdir folder/gdzie/beda/pliki/do/budowania
cd folder/gdzie/beda/pliki/do/budowania

ponieważ pracuję na prywatnym komputerze to dodam użytkownika do grupy docker żeby nie musieć za każdym razem wpisywać polecenia sudo i hasła przy wywoływaniu polecenia docker.
sudo usermod -aG docker $USER
sudo reboot

Po wykonaniu polecenia należy się wylogować i zalogować ponownie albo uruchomić polecenie:
newgrp docker

Uruchommy teraz tymczasowy, interaktywny kontener Docker z obrazem crops/poky:ubuntu-22.04 i udostępnijmy mu nasz lokalny katalog roboczy.
docker run --rm -it -v /home/dominik/workspace_docker/stm32mp1_yocto:/workdir crops/poky:ubuntu-22.04 --workdir=/workdir

wyjaśnijmy co oznaczają poszczególne elementy tej komendy.
- docker run – tworzy i uruchamia nowy kontener na podstawie wskazanego obrazu.
- –rm – po zakończeniu pracy kontener zostanie automatycznie usunięty. Nie zostanie jednak usunięty obraz ani pliki zapisane w podmontowanym katalogu.
- -it – pozwala na interaktywną pracę w terminalu.
- -v /home/dominik/workspace:/workdir – montuje katalog z komputera gospodarza wewnątrz kontenera jako workdir. Zmiany są dwukierunkowe. Plik utworzony w /workdir przez kontener pojawi się w /home/dominik/workspace na hoście.
- crops/poky:ubuntu-22.04 – jest to obraz Dockera używany do uruchomienia kontenera, gdzie crops/poky to repozytorium obrazu a ubuntu-22.04 to tag
- –workdir=/workdir – ustawia /workdir jako katalog roboczy kontenera oraz przygotowuje użytkownika i odpowiednie uprawnienia do pracy w zamontowanym katalogu.
Jeżeli nie mamy zainstalowanego narzędzia repo wówczas można je zainstalować w następujący sposób
mkdir -p ~/bin
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo
echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
repo --version

w tym miejscu należy jeszcze skonfigurować gita, ponieważ kontener nie pobiera konfiguracji z środowiska poza kontenerem.
git config --global user.email "dominik.bednarski@dmbp.pl"
git config --global user.name "Dominik Bednarski"

Teraz zainicjalizujmy katalog roboczy projektu przy pomocy narzędzia repo.
repo init -u https://github.com/STMicroelectronics/oe-manifest.git -b refs/tags/openstlinux-6.6-yocto-scarthgap-mpu-v26.06.10

wyjaśnijmy co oznaczają poszczególne elementy tej komendy.
- repo init – tworzy w bieżącym katalogu konfigurację narzędzia repo, głównie ukryty katalog .repo.
- -u – wskazuje adres repozytorium zawierającego manifest projektu.
- -b – wybiera konkretną gałąź, tag albo inną referencję Git manifestu.
Samo repo init nie pobiera jeszcze wszystkich źródeł projektu. Po poprawnym wykonaniu należy jeszcze uruchomić polecenie:
repo sync

Polecenie to odczytuje manifest i pobiera wszystkie wymagane repozytoria.
wchodzimy z powrotem do dockera
Teraz przygotujmy środowisko do budowania obrazu OpenSTLinux dla konkretnej płytki. W tym celu należy wykonać następujące polecenie.
DISTRO=openstlinux-weston MACHINE=stm32mp13-disco source layers/meta-st/scripts/envsetup.sh
Poszczególne elementy oznaczają:
- DISTRO=openstlinux-weston – wybiera dystrybucję OpenSTLinux z graficznym środowiskiem opartym na Weston, czyli referencyjnym compositorze Waylanda.
- MACHINE=stm32mp13-disco – wskazuje platformę sprzętową, dla której będzie wykonywana kompilacja — zestaw STM32MP13 Discovery.
- source layers/meta-st/scripts/envsetup.sh – uruchamia skrypt konfiguracyjny w bieżącej powłoce. Ważny szczegół: zmienne DISTRO i MACHINE należy przypisać przed source, bo są przekazywane do skyptu




Co robi skrypt envsetup.sh:
- przygotowuje katalog build,
- ustawia zmienne środowiskowe Yocto/BitBake,
- generuje lub aktualizuje pliki konfiguracji, takie jak conf/local.conf i conf/bblayers.conf,
- dodaje odpowiednie warstwy Yocto,
- ustawia wybraną maszynę i dystrybucję,
- przygotowuje terminal do uruchamiania poleceń bitbake.
W tym momencie jesteśmy gotowi do uruchomienia budowania systemu poleceniem:
bitbake st-image-weston

proces budowania trwa bardzo długo

Teraz uruchommy skrypt create_sdcard_from_flashlayout.sh. W tym celu przejdźmy pod następująca ścieżkę
cd build-openstlinuxweston-stm32mp13-disco/tmp-glibc/deploy/images/stm32mp13-disco/
i uruchommy następnie skrypt
./scripts/create_sdcard_from_flashlayout.sh flashlayout_st-image-weston/optee/FlashLayout_sdcard_stm32mp135f-dk-optee.tsv


Skrypt ten tworzy jeden zbiorczy plik obrazu dysku z rozszerzeniem .raw, który można nagrać bezpośrednio na kartę SD
FlashLayout_sdcard_stm32mp135f-dk-optee.tsv – to plik opisujący układ partycji i obrazów na karcie SD nazwę tego pliku dekodujemy następująco:
- sdcard — docelowym nośnikiem jest karta SD,
- stm32mp135f-dk — konfiguracja dla płytki STM32MP1F-DK,
- optee — konfiguracja korzysta z OP-TEE, czyli zaufanego środowiska wykonawczego,
- .tsv — plik tekstowy, w którym kolumny są oddzielone tabulatorami.
Plik FlashLayout zawiera informacje takie jak:
- pozycja każdej partycji na nośniku,
- identyfikator i typ partycji,
- nazwa pliku binarnego do umieszczenia w partycji,
- obrazy bootloadera, firmware, kernela i systemu plików,
- partycje OP-TEE oraz partycje systemowe.
Co w uproszczeniu robi skrypt:
- odczytuje plik FlashLayout…tsv.
- znajduje wskazane w nim pliki wynikowe kompilacji.
- tworzy pusty obraz karty SD o odpowiednim rozmiarze.
- tworzy w nim wymagany układ partycji.
- wpisuje poszczególne obrazy pod odpowiednimi offsetami.
- generuje gotowy obraz karty SD.
Teraz włóżmy kartę i sprawdźmy pod jaką nazwą się ona znajduje
lsblk

ponieważ ja skorzystałem z wbudowanego slotu na kartę dlatego karta znajduje się pod nazwą mmcblk0.
następnie zapiszmy gotowy obraz systemu bezpośrednio na kartę SD.
sudo dd if=FlashLayout_sdcard_stm32mp135f-dk-optee.raw of=/dev/mmcblk0 bs=8M conv=fdatasync status=progress

Znaczenie parametrów:
- dd — kopiuje dane bajt po bajcie z pliku wejściowego do urządzenia wyjściowego.
- if=…raw — plik wejściowy, czyli obraz karty SD przygotowany dla STM32MP135F-DK z OP-TEE.
- of=/dev/mmcblk0 — urządzenie docelowe. Zapis nastąpi na cały nośnik, nie na pojedynczą partycję.
- bs=8M — kopiuje dane blokami po 8 MB, co zwykle przyspiesza zapis.
- conv=fdatasync — przed zakończeniem wymusza fizyczne zapisanie danych na nośniku.
- status=progress — pokazuje bieżący postęp i liczbę zapisanych bajtów.
Po zapisaniu obrazu na karcie SD umieszczamy ją w slocie, który znajduje się na płytce STM32MP135F. Następnie należy przestawić przełączniki znajdujące się na płytce w następujący sposób. 1 – ON, 2 – OFF, 3 – ON 4 – OFF. Przełączniki odpowiedzialne są za wybór źródła uruchomienia systemu. https://wiki.st.com/stm32mpu/wiki/STM32MP135x-DK_-_hardware_description#Boot_related_switches

Gdy już kartę SD mamy umieszczoną w slocie oraz mamy ustawione poprawnie przełączniki, wówczas możemy podłączyć zasilanie do naszej płytki. Po podłączeniu zasilania na wbudowanym ekranie powinien pojawić się obrazek, jak na poniższym zdjęciu.

Teraz musimy chwilę poczekać, aż system uruchomi się do końca. Może to trwać kilkanaście sekund do nawet pojedynczych minut. Gdy już system się włączy, wówczas możemy włączyć jedną z wbudowanych aplikacji od ST.

Warto w tym miejscu np. podłączyć klawiaturę oraz ewentualnie myszkę następnie proponuję włączyć terminal i wykonać jakieś przykładowe polecenie, jak na poniższym zdjęciu.

Na koniec włączmy jeszcze na przykład film demo który dosatczony zosatał w buildzie do ST.

Budowa obrazu wraz z własną aplikacją
Gdy już znamy procedurę budowania obrazu, możemy przejść do utworzenia naszej własnej aplikacji. W tym celu, w poniższej części artykułu, stworzymy prostą aplikację w Pythonie i umieścimy ją w nowej warstwie, która będzie dodawała naszą aplikację podczas budowania obrazu.
Pierwszym krokiem jaki należy zrobić jest uruchomienie naszego kontenera poprzez następującą komendę.
docker run --rm -it -v /home/dominik/workspace_docker/stm32mp1_yocto:/workdir crops/poky:ubuntu-22.04 --workdir=/workdir

Gdy już jesteśmy w interaktywnej sesji Dockera, możemy teraz wykonać skrypt, który ustawi nasze środowisko.
source layers/meta-st/scripts/envsetup.sh


Teraz możemy wykonać polecenie do stworzenia nowej warstwy oraz jej dodania do obecnych warstw.
bitbake-layers create-layer ../layers/meta-dominik
bitbake-layers add-layer ../layers/meta-dominik

Po wykonaniu powyższych poleceń, sprawdźmy, czy nasza nowo stworzona warstwa na pewno została dodana do listy warstw, które są brane pod uwagę podczas budowania obrazu. W tym celu wykonajmy następujące polecenie.
bitbake-layers show-layers
Nasza warstwa powinna być widoczna jako ostatnia.

Gdy już upewniliśmy się, że nasza warstwa znajduje się na liście warstw, możemy przejść do utworzenia określonej ścieżki z folderów, w których będzie przechowywana nasza aplikacja.
mkdir -p ../layers/meta-dominik/recipes-apps/hello-python/files

Teraz w stworzonej przez nas lokalizacji możemy utworzyć plik Python z rozszerzeniem .py, który będzie zawierał naszą prostą aplikację.
cat > ../layers/meta-dominik/recipes-apps/hello-python/files/hello-python.py <<'EOF'
#!/usr/bin/env python3
print("Hello World from STM32MP135F-DK!")
print("Warm regards from Dominik and Mateusz at DMBP")
EOF

Teraz musimy stworzyć plik z naszą recepturą BitBake. Receptura ta wygląda następująco.
cat > ../layers/meta-dominik/recipes-apps/hello-python/hello-python_1.0.bb <<'EOF'
SUMMARY = "Simple Python Hello World application"
LICENSE = "CLOSED"
SRC_URI = "file://hello-python.py"
S = "${WORKDIR}"
RDEPENDS:${PN} += "python3-core"
do_install() {
install -d ${D}${bindir}
install -m 0755 ${WORKDIR}/hello-python.py \
${D}${bindir}/hello-python
}
EOF

Wyjaśnijmy teraz, co oznaczają poszczególne dane w recepturze
- SUMMARY – jest to krótki opis naszego pakietu.
- LICENSE = „CLOSED” – Informuje Yocto, że kod nie korzysta z publicznej licencji open source. Często używa się tego dla własnych, prywatnych aplikacji.
- SRC_URI – Receptura oczekuje lokalnego pliku: hello-python.py
- S – Ustawia katalog źródeł na katalog roboczy BitBake.
- RDEPENDS:${PN} += „python3-core” – Dodaje zależność uruchomieniową od podstawowego pakietu Pythona 3.
- python3-core – podstawowy interpreter i elementy Pythona 3
- do_install() – Definiuje etap instalowania plików do tymczasowego systemu plików pakietu.
- install -d ${D}${bindir} – Tworzy katalog na programy wykonywalne.
- ${D} – jest tymczasowym katalogiem docelowym używanym podczas budowania.
- install -m 0755 ${WORKDIR}/hello-python.py ${D}${bindir}/hello-python – Kopiuje skrypt hello-python.py do docelowej lokalizacji /usr/bin/hello-python. Zmienia nazwę z hello-python.py na hello-python, Ustawia uprawnienia 0755. Dzięki temu plik może być wykonywany jako program.
Po instalacji pakietu na urządzeniu powinno być możliwe uruchomienie
hello-python
to sprawdzenie wykonamy na samym końcu.
Podsumowując receptura mówi Yocto weź lokalny skrypt Python, dodaj zależność od Python 3 i zainstaluj go jako wykonywalny program /usr/bin/hello-python
aby sprawdzić recepturę należy wykonać polecenia
bitbake-layers show-recipes hello-python
bitbake hello-python



Następnie musimy dodać aplikację do obrazu w tym celu należy do lokalnego configu dodać
cat >> "$BUILDDIR/conf/local.conf" <<'EOF'
IMAGE_INSTALL:append = " hello-python"
EOF

Następnie możemy sprawdzić czy aplikacja zostanie dodana do obrazu poprze wykonanie polecenia
bitbake -e st-image-weston | grep '^IMAGE_INSTALL=' | grep hello-python

teraz ponownie zbudujmy obraz systemu znanym już nam poleceniem
bitbake st-image-weston


teraz przy pomocy następującej komendy ustalmy gdzie znajdje się stworzony obraz i przejdźmy pod tą lokalizację.
DEPLOY_DIR_IMAGE=$(bitbake-getvar -r st-image-weston DEPLOY_DIR_IMAGE \
| sed -n 's/^DEPLOY_DIR_IMAGE="\([^"]*\)"/\1/p')
echo "$DEPLOY_DIR_IMAGE"
cd "$DEPLOY_DIR_IMAGE"

Teraz tak jak poprzednio uruchommy skrypt create_sdcard_from_flashlayout.sh który przygotuje obraz do zapisania na kartę SD.
./scripts/create_sdcard_from_flashlayout.sh flashlayout_st-image-weston/optee/FlashLayout_sdcard_stm32mp135f-dk-optee.tsv


teraz ponownie umieśćmy kartę SD w naszym laptopie i zapiszmy gotowy obraz na kartę SD.
sudo dd if=FlashLayout_sdcard_stm32mp135f-dk-optee.raw of=/dev/mmcblk0 bs=8M conv=fdatasync status=progress

następnie włóżmy kartę ponownie do naszej płytki, poczekajmy aż się uruchomi i włączmy terminal.

teraz w terminalu wpiszmy komendę
hello-python
w terminalu powinien wyprinować się tekst z naszej aplikacji.

W ten oto sposób umieściliśmy nasz własny program w obrazie z systemem operacyjnym.
Podsumowanie
W tym artykule pokazaliśmy, jak krok po kroku przygotować i zbudować własny obraz systemu Linux dla platformy STM32MP1 z wykorzystaniem Yocto Project oraz środowiska OpenSTLinux dostarczanego przez STMicroelectronics. Rozpoczęliśmy od przygotowania środowiska budowania w kontenerze Docker, pobrania źródeł za pomocą narzędzia repo oraz skonfigurowania projektu dla płytki STM32MP135F-DK. Następnie zbudowaliśmy kompletny obraz systemu, przygotowaliśmy obraz karty SD i uruchomiliśmy go na płytce.
W drugiej części artykułu poszliśmy o krok dalej i pokazaliśmy, jak rozszerzyć system o własną aplikację. Utworzyliśmy dedykowaną warstwę Yocto, przygotowaliśmy prosty program w Pythonie oraz odpowiadającą mu recepturę BitBake. Następnie dodaliśmy aplikację do obrazu systemu, ponownie wykonaliśmy kompilację i uruchomiliśmy program bezpośrednio na STM32MP135F-DK.
Przedstawiony przykład pokazuje podstawowy, ale bardzo istotny mechanizm pracy z Yocto – budowanie własnego, powtarzalnego obrazu Linuxa oraz integrowanie z nim aplikacji charakterystycznych dla konkretnego urządzenia. Takie podejście pozwala w przyszłości tworzyć bardziej rozbudowane systemy, dodawać kolejne aplikacje i usługi, modyfikować konfigurację kernela czy bootloadera oraz organizować własne elementy projektu w niezależnych warstwach.
Mam nadzieję, że przedstawiona procedura będzie dobrym punktem wyjścia do dalszej pracy z Yocto Project, OpenSTLinux oraz platformami STM32MP1/MP2.
Autor: Dominik Bednarski dmbp.pl
