W pierwszych dwóch częściach poznaliśmy, czym właściwie jest komputerek i uruchomiliśmy go. Teraz, gdy mniej ważne sprawy nie zajmują nam czasu, możemy zająć się programowaniem.
Dowiemy się m.in. jak zmusić Raspberry Pi do rozmowy z innymi układami, wykorzystując do tego różne dostępne interfejsy komunikacyjne.
Pierwszy program, jaki napiszemy to, ku zdziwieniu większości, "Hello World"!
Najpierw sprawdzamy wersję kompilatora gcc:
C
1
gcc-v
Powinna pojawić się informacja podobna do tej poniżej:
C
1
2
...
gcc version4.6.3(Debian4.6.3-14+rpi1)
Kiedy mamy kompilator, to mamy wszystko, co potrzebne. Tworzymy folder HelloWorld, a w nim plik hello.c:
C
1
2
3
mkdir HelloWorld
cd HelloWorld
nano hello.c
A następnie w pliku:
C
1
2
3
4
5
6
#include <stdio.h>
intmain()
{
printf("Hello world!\n");
return0;
}
Kolejnym krokiem jest zbudowanie naszego oszałamiającego programu:
C
1
gcc-chello hello.c
Oraz instrukcja wykonania go:
C
1
./hello
W konsoli powinno pojawić się coś podobnego do:
C
1
Hello world!
Gratulacje! Możemy się już nazywać programistami RPI!
Wybór środowiska programistycznego
W poprzednim punkcie stworzyliśmy pierwszy, zaskakujący program za pomocą zwykłego edytora tekstu oraz bezpośredniego odpalenia kompilatora w terminalu. Efektywne, ale mało wygodne. Dlatego warto zastosować narzędzie do automatycznego budowania programu wraz ze wszystkimi dodatkowymi bibliotekami, kolorowaniem składni itd.
Mamy kilka opcji. Zaczynając od najprostszych edytorów wraz z pisaniem plików makefile, poprzez lekkie środowiska programistyczne takie jak Geany, po duże kombajny wraz z wieloma dodatkowymi bibliotekami jak Eclipse.
Gdy tworzę proste programy, korzystam z Geany, jednak te wykorzystujące bardziej zaawansowane mechanizmy piszę w połączeniu z biblioteką Qt 4.8 i środowiskiem QtCreator. Jest to co prawda koszmarnie wolny program, ale nie chciało mi się bawić w konfigurację Qt pod innym IDE. Instalujemy QtCreatora za pomocą komendy poniżej:
C
1
sudo apt-get install qtcreator
A następnie ustawiamy opcje kompilatora tak jak na screenie poniżej:
Ustawienia Toolchain w QtCreatorze na Raspberry Pi
Jeśli chodzi o Geany to instalacja jest standardowa i nic nie trzeba konfigurować, można od razu kodzić:
C
1
sudo apt-get install geany
Biblioteka wiringPi
Raspbian to prawie zwykły linux. Dlatego wszystkie operacje wykorzystujące niskopoziomowe interfejsy komunikacyjne można wykonać jako odpowiednie działania na odpowiednich plikach. Jednak, jak wszystko co niskopoziomowe, wymaga to od programisty dużego nakładu pracy. Oczywiście osiągnięty w ten sposób efekt jest zazwyczaj lepszy niż napisany za pomocą wyżej poziomowych funkcji – wszystko to kosztem naszego czasu.
Z uwagi na optymalizację czasu spędzonego nad określonym zadaniem, zdecydowałem się na wykorzystanie biblioteki wiringPi (strona domowa projektu). Aby zdobyć źródła biblioteki używamy gita:
C
1
git clonegit://git.drogon.net/wiringPi
Wchodzimy do ściągniętego katalogu i uruchamiamy narzędzie do budowania:
C
1
2
cd wiringpi
./build
Sprawdzamy, czy instalacja na pewno się udała:
C
1
gpio-v
Gotowe! Biblioteki dynamiczne są w katalogu /usr/local/lib, zaś pliki nagłówkowe w katalogu /usr/local/include. Należy również poinformować kompilator, gdzie szukać oraz jakich bibliotek wiringPi używać, co robimy poprzez dodanie tej informacji do linii budowania.
W różnych środowiskach może to wyglądać różnie: w Geany należy wejść w Build->SetBuildCommands, a następnie w okienku Build dopisać -lwiringPi, w Qt należy do pliku *.pro dopisać dwie linijki:
C
1
2
INCLUDEPATH+=-L/usr/local/include
LIBS+=-L/usr/local/lib-lwiringPi
Do przestestowania możemy wykorzystać poniższy kod, który ma za zadanie zainicjować bibliotekę. Warto zaznaczyć, że w każdym programie, wykorzystującym jakąkolwiek funkcję wiringPi, należy na początku użyć funkcji wiringPiSetup().
W pierwszej części artykułu dowiedzieliśmy się, jak połączone są piny wejść/wyjść z układem SoC, co determinowało ich nazewnictwo. Biblioteka wiringPi korzysta jednak z innej nomenklatury, dlatego postanowiłem rozszerzyć wspomniane zestawienie o jeszcze jedną kolumnę.
Zestawienie pinów listwy GPIO wraz z nazwami według wiringPi
General Purpose Input/Output
Mamy szereg możliwości manipulacji modułem GPIO. Aby jak najszybciej dobrać się do pinów malinki wystarczy nam... terminal. Zasada działania jest taka: wpisujemy sprecyzowane komendy do odpowiednich plików, co procesor później interpretuje i ustawia konkretne bity w rejestrach odpowiedzialnych za kontrolę wejść/wyjść.
Przechodząc do konkretów: aby aktywować dostęp do jakiegoś pinu, należy go „wyeksportować”, czyli wpisać jego numer (w nomenklaturze układu SoC, patrz Tab. 2. w pierwszej części artykułu). Dlatego jeżeli chcemy ustawić stan wysoki na GPIO_GEN0, to tak naprawdę będziemy manipulować pinem GPIO17. Najpierw jednak przechodzimy do trybu super user:
C
1
sudo su
Następnie „eksportujemy” pin 17:
C
1
echo17>/sys/class/gpio/export
Teraz w /sys/class/gpio pojawił się katalog gpio17, a w nim kilka nowych plików, umożliwiających różne rodzaje działań: od ustawienia kierunku pinu (wejście/wyjście) po rodzaj zbocza, przy którym ma być zgłaszane przerwanie. My jednak ograniczymy się do zwykłej kontroli binarnej, ustawiając najpierw GPIO17 jako wyjście:
C
1
echo out>/sys/class/gpio/gpio17/direction
Ustawienie stanu wysokiego:
C
1
echo1>/sys/class/gpio/gpio17/value
Teraz miernik w dłoń i podziwiamy naszą władzę nad Raspberry Pi. Aby zakończyć korzystanie z pinu należy go „odeksportować”:
C
1
echo17>/sys/class/gpio/unexport
Taki dostęp może jest prosty i szybki od strony użytkownika, jednak z perspektywy czasu wykonywania instrukcji to programistyczna porażka. Głównie z tego względu, że procesor musi się zajmować czymś, co może zrobić sprzętowo – czytając/pisząc w odpowiednich adresach pamięci układu Broadcom.
Owe adresy można znaleźć we wspomnianej w pierwszej części artykułu nocie katalogowej układu SoC. My jednak wykorzystamy do tego bibliotekę wiringPi. Poniżej przykład wykorzystania wszystkich funkcji dotyczących prostej manipulacji GPIO:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <stdio.h>
#include <wiringPi.h>
intmain(intargc,char**argv)
{
//Zmienna pomocnicza
inti;
//Komunikat poczatkowy
printf("Program testowy WiringPi!\n");
if(wiringPiSetup()==-1)
{
printf("Nie mozna wystartowac wiringPi!\n");
return1;
}
//Ustawiamy jako wyjscie
for(i=0;i>8;i++)pinMode(i,OUTPUT);
//Zapisujemy 1 na wszystkich wyjsciach
digitalWriteByte(255);
//Zapisujemy 0 na wyjsciu 2
digitalWrite(2,0);
//Ustawiamy pin 1 jako wejscie
pinMode(i,INPUT);
//Sciagamy pin 1 do masy
pullUpDnControl(1,PUD_DOWN);
//Wyswietlamy wartosc pin 1
printf("Wartosc pinu 1 : %i\n",digitalRead(1));
return0;
}
Pulse Width Modulation
Zegar PWM
Na początku omawiania tego peryferium należy przyjrzeć się nocie katalogowej układu Broadcom, gdzie można z nutką niepokoju stwierdzić, że na temat sprzętowego PWMu... prawie nic nie ma. Informacje są szczątkowe i niewystarczające (moim zdaniem) do zrozumienia istoty obsługi tego peryferium od strony programistycznej.
Na potrzeby właśnie takich sytuacji wymyślono internet. Okazuje się, że PWM jako jedno z peryferiów jest podpięty do szyny APB procesora, która jest taktowana zegarem o częstotliwości 250 MHz. Zegar PWM otrzymuje się poprzez dzielenie częstotliwości, w wyniku czego peryferium jest napędzane zegarem 19.2MHz.
Dzielnik stanowią dwa 12-bitowe rejestry w pamięci procesora. Pierwszy z nich zawiera informacje o części całkowitej, zaś drugi o jego części ułamkowej. Ze strony biblioteki wiringPi możemy ustawić tylko całkowitą część dzielnika za pomocą funkcji pwmSetClock(int div). Parametr ma wartość maksymalnie 4096 (czyli wypełniony jedynkami, wspomniany wcześniej 12-bitowy rejestr).
Warto również wspomnieć o tym, że omawiane tutaj sprzętowe PWMy w malinie są dwa, jednak jeden z nich jest wykorzystywany do złącza mini jack. Dlatego użytkownik ma dostęp tylko do jednego pinu z modulowaną szerokością impulsu - GPIO18 (według nomenklatury wiringPi - GPIO1).
Tryb Mark:Space
Należy zaznaczyć, że w Raspberry Pi dostępne są dwa tryby działania PWM. Pierwszy z nich to tak zwany Mark:Space, w którym priorytetem jest ustawienie kwantu czasu. Zarówno stan wysoki oraz cały przebieg to odpowiednie wielokrotności tej elementarnej długości. Ustawienia dokonujemy za pomocą funkcji pwmSetClock(int divisor), gdzie parametr divisor to wspomniany całkowity dzielnik częstotliwości zegara PWM (19.2MHz), otrzymywany według wzoru:
Elementy z indeksem q odnoszą się do omawianego kwantu czasu, odpowiednio częstotliwości oraz okresu (czasu trwania). W następnej kolejności należy wywołać dwie funkcje określające sam przebieg:
pwmSetRange(unsigned int range), gdzie parametr range określa okres (T) generowanego przebiegu PWM jako całkowitą wielokrotność kwantu czasu (Tq),
pwmWrite(int pin, int value), gdzie jako wartość pin podajemy liczbę 1 (odnosimy się do GPIO1), natomiast value będzie całkowitą wielokrotnością kwantu czasu (Tq), określającą długość stanu wysokiego (TH).
Rozważmy teraz przykład ustawienia przebiegu PWM o okresie równym 20ms oraz wypełnieniu na poziomie 1.5ms. Pierwszym krokiem jest ustawienie odpowiedniego kwantu czasu. W tym przypadku zdecydowałem się na 100us:
Parametr range funkcji pwmSetRange(unsigned int range) ustawiamy jako:
Parametr value funkcji pwmWrite(int pin, int value) ustawiamy jako:
Poniżej znajduje się listing programu ilustrującego ten przykład oraz screen otrzymanego przebiegu na oscyloskopie:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>
#include <wiringPi.h>
intmain(intargc,char**argv)
{
//Komunikat poczatkowy
printf("Program testowy PWM_MS WiringPi!\n");
if(wiringPiSetup()==-1)
{
printf("Nie mozna wystartowac wiringPi!\n");
return1;
}
//Ustawiamy jako wyjscie PWM
pinMode(1,PWM_OUTPUT);
//Ustawiamy tryb PWM M:S
pwmSetMode(PWM_MODE_MS);
//Ustawiamy dzielnik zegara
pwmSetClock(1920);
//Ustawiamy zakres
pwmSetRange(200);
//Ustawiamy wypenienie
pwmWrite(1,15);
//Koniec programu
return0;
}
Przebieg sygnału PWM 1.5/20 ms wytworzonego przy pomocy metody MS
Tryb balanced
Drugim domyślnym, ale moim zdaniem mniej przydatnym trybem, jest balanced. Tutaj również priorytetem jest ustawienie odpowiedniego kwantu czasu, z tą różnicą, że długość trwania sygnału wysokiego jest równa temu kwantowi, a w locie obliczany jest jedynie okres całego przebiegu.
Konfigurując parametry przebiegu, zaczynamy od kwantu czasu według takiego samego wzoru, jak w poprzednim punkcie. Wiadomo, że stan wysoki będzie trwał dokładnie tyle samo, zaś okres całego przebiegu PWM będzie obliczany według wzoru:
Dlatego od razu widać, że wartości range oraz value same w sobie nie mają znaczenia, istotny jest ich stosunek. Przechodząc do przykładu, w tym trybie nie uzyskamy takiego przebiegu jak w poprzednim punkcie, ponieważ nie da się ustawić kwantu czasu równego 1.5ms. Winą oczywiście obarczamy fakt, że rejestr przechowujący dzielnik częstotliwości zegara PWM ma tylko 12 bitów:
Spróbujmy w takim razie ustawić przebieg o okresie 20us i wypełnieniu 1.5us. Wtedy:
Oraz:
Poniżej znajduje się listing programu ilustrującego ten przykład oraz screen otrzymanego przebiegu na oscyloskopie:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>
#include <wiringPi.h>
intmain(intargc,char**argv)
{
//Komunikat poczatkowy
printf("Program testowy PWM_BAL WiringPi!\n");
if(wiringPiSetup()==-1)
{
printf("Nie mozna wystartowac wiringPi!\n");
return1;
}
//Ustawiamy jako wyjscie PWM
pinMode(1,PWM_OUTPUT);
//Ustawiamy tryb PWM BAL
pwmSetMode(PWM_MODE_BAL);
//Ustawiamy dzielnik zegara
pwmSetClock(29);
//Ustawiamy zakres
pwmSetRange(40);
//Ustawiamy wypenienie
pwmWrite(3);
//Koniec programu
return0;
}
Przebieg sygnału PWM 1.5/20 us wytworzonego przy pomocy metody balanced
Soft PWM
A co jeśli mamy potrzebę stworzenia dwóch przebiegów PWM i nie chcemy kupować dodatkowych układów scalonych? Możemy oczywiście wykorzystać programowy PWM, który można postawić na dowolnym pinie, co ma jednak swoje ograniczenia.
Minimalna jednostkowa szerokość impulsu wynosi 100us, co w połączeniu z domyślnym okresem impulsu równym 100 jednostkowym szerokościom daje sygnał o częstotliwości 100Hz. Oczywiście można zmniejszyć zakres, co spowoduje zwiększenie częstotliwości przebiegu, ale odbije się na rozdzielczości. Podobnie można zwiększyć zakres, co spowoduje zwiększenie rozdzielczości, ale zmniejszenie częstotliwości. Dodatkowo, jak widać na poniższym przykładzie, sygnał jest wyraźnie mniej stabilny od sygnału generowanego przez sprzętowy PWM:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <wiringPi.h>
#include <softPwm.h>
intmain(intargc,char**argv)
{
//Zmienna pomocnicza
chara;
//Komunikat poczatkowy
printf("Program testowy PWM_Soft WiringPi!\n");
if(wiringPiSetup()==-1)
{
printf("Nie mozna wystartowac wiringPi!\n");
return1;
}
//Ustawiamy jako wyjscie
pinMode(1,OUTPUT);
//Tworzymy programowy PWM
softPwmCreate(1,0,200);
//Ustawiamy wypelnienie 1.5ms
softPwmWrite(1,15)
//Czekamy na wcisniecie klawisza
scanf("%c",&a);
//Koniec programu
return0;
}
Przebieg sygnału PWM 1.5/20 ms wytworzonego przez programowy PWM
Kompilując powyższy program, należy pamiętać, by dodać bibliotekę -lpthread. Możemy teraz porównać przebiegi uzyskane przy użyciu sprzętowego i programowego PWMu.
Już na pierwszy rzut oka widać, że okres sygnału wygenerowanego przez sprzętowy PWM wynosi dokładnie 20ms. Analiza programowego PWMu pokazała, że przebieg przez niego wygenerowany ma pewne odstępstwo od żądanej wartości 20ms – sytuacja jest podobna w przypadku ustawianego wypełnienia.
Można również rzucić okiem na odchylenie standardowe sygnału, gdzie w przypadku sprzętowo generowanego przebiegu jest ono prawie dwa razy mniejsze niż w przypadku generacji programowej. Wszystko ma jednak swoje wady i zalety, dlatego warto znać oba sposoby, by móc odpowiednio dopasować go do potrzebnego zastosowania.
Universal Asynchronous Receiver and Transmitter
Czas na próbę rozmowy Raspberry Pi z innymi urządzeniami poprzez najprostszy z interfejsów: UART. To peryferium jest w malince odrobinę niedopieszczone - ma pewne ograniczenia, o których można przeczytać w dokumentacji układu Broadcom (brak bitu parzystości, detekcji błędu ramki i inne).
Niemniej jednak nie najgorzej nadaje się do większości zastosowań – bez problemu można się porozumieć z mikroprocesorem, czy PCtem. Aby jednak zacząć zabawę, należy usunąć ustawienia trybu pinów UART z domyślnego debugowania. Realizujemy to poprzez zmianę zawartości plików konfiguracyjnych. Warto na wszelki wypadek zrobić kopię zapasową plików, które będziemy modyfikować:
C
1
2
sudo cp/boot/cmdline.txt/boot/cmdline.bak
sudo cp/etc/inittab/etc/inittab.bak
Następnie usuwamy zapis console=ttyAMA0, 115200 oraz kgdboc=ttyAMA0,115200 z pliku /boot/cmdline.txt używając ulubionego edytora:
C
1
sudo nano/boot/cmdline.txt
W drugim pliku usuwamy albo komentujemy ostatnią linijkę wstawiając '#' przed T0:23:respawn:/sbwin/getty -L ttyAMA0 115200 vt100, znów używając ulubionego edytora:
C
1
sudo nano/etc/inittab
W ten sposób poprawnie przygotowaliśmy UART do działania. Po zwarciu pinów RXD (GPIO15) oraz TXD (GPIO14) możemy sprawdzić, czy UART działa jak należy, wykorzystując do tego poniższy program:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
#include <wiringPi.h>
#include <wiringSerial.h>
intmain(intargc,char**argv)
{
//Zmienna pomocnicza
chara;
//Zmienna deskryptora UART
intdescriptor;
//Komunikat poczatkowy
printf("Program testowy UART WiringPi!\n");
if(wiringPiSetup()==-1)
{
printf("Nie mozna wystartowac wiringPi!\n");
return1;
}
//Otwieramy port szeregowy
descriptor=serialOpen("/dev/ttyAMA0",9600);
//Petla programu
while(a!='q')
{
//Wysylamy przez UART liczbe 85
serialPutchar(descriptor,(unsignedchar)85);
//Czyscimy bufor
serialFlush(descriptor);
//Pobieramy znak
scanf("%c",&a);
}
//Zamykamy port szeregowy
serialClose(descriptor);
//Koniec programu
return0;
}
Przebieg sygnału na pinie TXD (GPIO14) przy wysyłaniu znaku 85 (binarnie 01010101)
Sprawdźmy, czy wszystko się zgadza. W programie jest ustawiony baud rate jako 9600, czyli okres trwania jednego bitu powinien być równy:
Na załączonym screenie wyraźnie widać, że owe zależności są spełnione. Układ działa poprawnie i jest gotowy do dalszej pracy!
Serial Peripheral Interface
Nadszedł czas na zabawę kolejnym interfejsem komunikacyjnym w Raspberry Pi - SPI. Jest on jednak domyślnie wyłączony, dlatego przed jakimikolwiek czynnościami należy załadować moduł tego urządzenia. Można to zrealizować na różne sposoby, jednak najprostszym jest oferowana przez program gpio funkcja:
C
1
gpio load spi
Aby sprawdzić, czy sterownik SPI został poprawnie zainstalowany w jądrze, wywołujemy:
C
1
lsmod
I szukamy, czy pojawiły się wpisy: spi_bcm2708 oraz spidev. Jeżeli tak, to moduł jest zainstalowany i gotowy do użycia, wystarczy teraz zewrzeć pinyMOSI (GPIO10) oraz MISO (GPIO9), by móc za pomocą poniższego programu przetestować poprawność wysyłania oraz odbierania danych:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <stdio.h>
#include <wiringPi.h>
#include <wiringPiSPI.h>
intmain(intargc,char**argv)
{
//Zmienne pomocnicza
unsignedchara,msg=129;
//Zmienna deskryptora SPI
intdescriptor;
//Komunikat poczatkowy
printf("Program testowy SPI WiringPi!\n");
if(wiringPiSetup()==-1)
{
printf("Nie mozna wystartowac wiringPi!");
return1;
}
//Inicjujemy SPI
descriptor=wiringPiSPISetup(0,500000);
//Sprawdzenie, czy dziala
if(descriptor!=-1)printf("Udalo się zainicjowac SPI!\n");
else
{
printf("Nie udalo się zainicjowac SPI!\n");
return1;
}
//Petla programu
while(a!='q')
{
//Wysylamy/odbieramy bajt danych przez SPI
device=wiringPiSPIDataRW(0,&msg,1);
//Wyswietlamy info
printf("Zwrocone przez devajsa: %c\n",msg);
//Pobieramy znak
scanf("%c",&a);
}
//Koniec programu
return0;
}
Przebieg sygnału na pinie MOSI (GPIO10) przy wysyłaniu znaku 129 (binarnie 10000001)
Warto zatrzymać się jeszcze przy jednej kwestii – częstotliwości taktowania zegara SPI. Autor biblioteki wiringPi podaje, że nie wszystkie częstotliwości są osiągalne przez SPI, jedynie ściśle określone:
0.5 MHz,
1 MHz,
2 MHz,
4 MHz,
8 MHz,
16 MHz,
32 MHz.
Nawet jeśli w funkcji inicjującej SPI będzie podana inna wartość, to zostanie ona zaokrąglona do najbliższej ze wspomnianego szeregu. Sam autor biblioteki nie jest w stanie dokładnie wytłumaczyć zjawiska zaokrąglania. Wiadomym jest, że musi to mieć powiązanie z faktem, iż zegar SPI jest otrzymywany na podstawie odpowiedniego dzielenia częstotliwości zegara szyny APB (który, jak już wcześniej wspomnieliśmy, kręci się z częstotliwością 250MHz). Być może osiągnięcie częstotliwości 10MHz jest niemożliwe przy jakiejkolwiek konfiguracji dzielników częstotliwości zegara szyny APB.
Zakończenie
To już koniec naszej przygody z Raspberry Pi. Udało nam się omówić najważniejsze sprawy potrzebne do budowy własnych systemów (nie tylko) robotycznych, w których Raspberry Pi wcale nie musi grać pierwszych skrzypiec, ale może być na przykład jednym z ogniw łączących najbardziej wysokopoziomowe elementy systemu (interfejs użytkownika) z tymi niskopoziomowymi (czujniki, układy wykonawcze).
Możliwości wykorzystania komputerka w robotyce są naprawdę ogromne. Co prawda do większości zastosowań będzie zapewne potrzebny jeszcze jeden układ oparty o mikrokontroler, który będzie miał więcej portów IO, jednak jako jednostka obliczeniowa malinka sprawdza się świetnie. Mam nadzieję, że niniejszy cykl artykułów przyda się Wam i pozwoli na szybszy start z Raspberry dzięki zebraniu wszystkich najważniejszych informacji w jednym miejscu. Ciekaw jestem, co jesteście w stanie wymyślić z Raspebrry Pi na pokładzie?
Dołącz do 20 tysięcy osób, które otrzymują powiadomienia o nowych artykułach! Zapisz się, a otrzymasz PDF-y ze ściągami (m.in. na temat mocy, tranzystorów, diod i schematów) oraz listę inspirujących DIY na bazie Arduino i Raspberry Pi.
To nie koniec, sprawdź również
Przeczytaj powiązane artykuły oraz aktualnie popularne wpisy lub losuj inny artykuł »
Dołącz do 20 tysięcy osób, które otrzymują powiadomienia o nowych artykułach! Zapisz się, a otrzymasz PDF-y ze ściągami (m.in. na temat mocy, tranzystorów, diod i schematów) oraz listę inspirujących DIY z Arduino i RPi.
Trwa ładowanie komentarzy...