Nowe technologie w fizyce biomedycznej/Raspberry Pi

Z Brain-wiki

Raspberry Pi

Raspberry Pi

Raspberry Pi to mała (wymiary: 85.60 mm × 53.98 mm, przy wadze 45 gramów) platforma komputerowa stworzona przez brytyjską organizację charytatywną "Fundację Raspberry Pi" w celach edukacyjnych. Miała za zadanie ułatwić uczniom naukę programowania oraz sterowania zewnętrznymi urządzeniami.

Raspberry Pi zasilany jest napięciem 5V poprzez gniazdo microUSB. Niski pobór mocy jest jedną z przyczyn częstego wykorzystania Raspberry w robotyce. Komputer ten to w głównej mierze układ Broadcom:

  • 512 MB RAMu
  • procesor graficzny
  • procesor taktowany zegarem 700MHz

Raspberry Pi nie posiada wbudowanego dysku twardego - korzysta z karty SD, którą wykorzystuje do załadowania systemu operacyjnego (Raspbian - specjalnie dedykowana wersja Debiana) i przechowywania danych. Do tego komputera można podłączyć urządzenia zewnętrzne poprzez łącza:

  • HDMI - monitor
  • ethernet - internet
  • USB - klawiatura, myszka, itp.

Główną cechą Raspberry Pi jest zestaw połączeń GPIO (General Purpose Input Output), które pozwalają na wysyłanie i odbieranie sygnałów cyfrowych (z i do urządzeń zewnętrznych takich jak motorki, czujniki, diody, przełączniki itd.).

Gertboard

Gertboard jest to moduł rozszerzający możliwości komputera Raspberry Pi. Jego instrukcja obsługi znajduje się pod adresem: [1]. Do obsługi płytki Gertboard będziemy korzystac z modułu RPi napisanego w języku Python [2]. Płytka ta posiada m.in.:

  • sterownik silnika prądu stałego
  • 12 buforowanych portów wyjścia/ wejścia
  • 3 przyciski typu tact-switch
  • 6 wejść typu otwarty kolektor
  • dwukanałowy 10 bitowy przetwornik analogowo-cyfrowy
  • dwukanałowy 8, 10 lub 12 bitowy przetwornik cyfrowo-analogowy

Płytka ta podłączana jest bezpośrednio do pinów GPIO w Raspberry, skąd też pobiera zasilanie.

Płytka Gertboard z zaznaczonymi obszarami funkcyjnymi. Źródło: Gertboard User Maual

Aby zasilić płytkę Gertboard napięciem 3,3 V należy umieścić zworkę w pozycji pokazanej na poniższym rysunku (prawy dolny róg płytki).

Miejsce wpięcia złączki, w celu zasilenia płytki. Źródło: Gertboard User Maual

Połączeń na płytce dokonywać będziemy korzystając ze zworek (małe czarne elementy 'zwierające' piny blisko siebie) oraz złączek (kabelki łączące piny leżące dalej od siebie).

Przewody łączące piny na płytce Gertboard: zworki i złączki. Źródło: Gertboard User Maual

Rząd pinów GPIO (oznaczonych czarną ramką) jest "łącznikiem" płytki Gaertboard z Raspberry Pi. Mamy zatem 17 pinów, które mogą spełniać funkcje wejścia lub wyjścia dla sygnału cyfrowego (w zależności od tego do czego je podłączymy). Przykładem może być podpięcie do nich buforowanych portów wejścia/wyjścia opisanych poniżej.

Buforowane porty I/O

Ramką nr 2 zaznaczono 12 portów wejścia/wyjścia (I/O), których sygnał po przejściu przez układ buforujący (ramki nr 3,4,5) można przekazać do/od Raspberry Pi poprzez połączenie odpowiadających im portów z obszaru ramki nr 1 z portami GPIO. Do każdego z portów I/O przypisana jest dioda (tuż pod portami w ramce nr 2), która wskazuje obecny stan logiczny danego portu.

Zaznaczone obszary buforowanych portów wejścia/wyjścia. Źródło: Gertboard User Maual

Każdy z 12 portów I/O posiada swój obwód buforowy (schemat przedstawiono poniżej), który składa się z buforów przepuszczających prąd tylko w jednym kierunku, oporników, diody LED oraz miejsc łączeniowych. Jeśli połączymy obwód w miejscu 'in', sygnał będzie przekazywany tylko w kierunku od portów I/O do Raspberry Pi. Jeśli umieścimy zworkę w miejscu 'out', sygnał będzie przekazywany tylko w kierunku od Raspberry Pi do portów I/O.

Schemat obwodu buforowego. Źródło: Gertboard User Maual

Na płytce Gertboard porty wejścia/wyjścia o numerach od 1-4 mają swój obwód buforowy w chipie U3 oznaczonym ramką nr 3, porty 5-8 w chipie U4 zaznaczonym ramką nr 4, porty 9-12 w chipie U5 zaznaczonym ramką nr 5. Miejsca połączenia obwodu buforowego 'in' znajdują się w dolnej połowie chipa, natomiast miejsca połączenia obwodu buforowego 'out' w górnej połowie chipa. Poniższy obrazek prezentuje takie ustawienie zworek, dla którego porty 1,2,3 będą ustawione jako 'wyjście', natomiast porty 10,11 jako 'wejście'.

Lokalizacja wpięcia zworek. Źródło: Gertboard User Maual

Po zapoznaniu się z powyższymi oznaczeniami studenci mają za zadanie wykonać połączenia na desce Gertboard tak aby:

  • buforowany port BUF_3 był portem wyjścia, na który sygnał jest przekazywany przez port GP22
  • buforowany port BUF_8 był portem wejścia, który przekazuje sygnał na port GP7
  • buforowany port BUF_10 był portem wyjścia, na który sygnał jest przekazywany przez port GP10
  • buforowany port BUF_11 był portem wejścia, który przekazuje sygnał na port GP25

Przyciski

Deska Gertboard posiada 3 przyciski, które są podłączone do portów B1, B2 oraz B3. Schemat obwodu elektrycznego został przedstawiony poniżej. Jak widać, podczas przyciśnięcia przycisku port wejściowy do Raspberry zostaje połączony poprzez opornik z uziemieniem, co skutkuje odczytaniem logicznej wartości zero. Aby na bieżąco kontrolować zmiany spowodowane przyciskiem, można wpiąć zworkę w pozycji 'out' co sprawi, że stan logiczny przycisku będzie odzwierciedlany przez diodę LED (konkretną dla danego przycisku). Podczas korzystania z przycisku jako wejścia nie wpinamy zworki w miejscu 'in', ponieważ bufor wejściowy będzie zakłócał działanie przycisku.

!Uwaga: przyciski można połączyć z dowolnymi portami GPIO, oprócz GP0 oraz GP1 (porty te mają dodatkowo wbudowane oporniki 1,8 kΩ)

Schemat obwodu z przyciskiem. Źródło: Gertboard User Maual

Test przycisków. Wpinamy zworki oraz złączki w sposób przedstawiony na schemacie poniżej. Następnie uruchamiamy program 'buttons-rg.py'. Program ten będzie wyświetlał w terminalu stan trzech przycisków (gdzie 0 odpowiada wciśniętemu przyciskowi) za każdym razem gdy ulegnie on zmianie (po 19 zmianach stanów przycisków program zakończy działanie).

Schemat połączeń w teście przycisków. Źródło: Gertboard User Maual
# FileName -> buttons-rg.py
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)                                # initialise RPi.GPIO
for i in range(23,26):                                # set up ports 23-25 
    GPIO.setup(i, GPIO.IN, pull_up_down=GPIO.PUD_UP)  # as inputs pull-ups high 
                                                      # (if pin is not connected - input is pulled-up high as 1)      

# Print  Instructions appropriate for the board
print "These are the connections for the Gertboard buttons test:"
print "GP25 in J2 --- B1 in J3"
print "GP24 in J2 --- B2 in J3"
print "GP23 in J2 --- B3 in J3"
print "Optionally, if you want the LEDs to reflect button state do the following:"
print "jumper on U3-out-B1"
print "jumper on U3-out-B2"
print "jumper on U3-out-B3"

raw_input("When ready hit enter.\n")

button_press = 0                                      # set intial values for variables
previous_status = ''

try:
    while button_press < 20:                          # read inputs until 19 changes are made
        status_list = [GPIO.input(25), GPIO.input(24), GPIO.input(23)]
        for i in range(0,3):
            if status_list[i]:
                status_list[i] = "1"
            else:
                status_list[i] = "0"
        # dump current status values in a variable
        current_status = ''.join((status_list[0],status_list[1],status_list[2])) 
        if current_status != previous_status:         # if that variable not same as last time 
            print current_status                      # print the results 
            previous_status = current_status          # update status variable for next comparison
            button_press += 1                         # increment button_press counter

except KeyboardInterrupt:                             # trap a CTRL+C keyboard interrupt
    GPIO.cleanup()                                    # resets all GPIO ports used by this program
GPIO.cleanup()                                        # on exit, reset all GPIO ports

Po zapoznaniu się z implementacją powyższego przykładu studenci mają za zadanie tak zmodyfikować program aby:

  • po naciśnięciu jednocześnie trzech przycisków w terminalu zamiast trzech zer pojawiała się linia wykrzykników
  • po naciśnięciu jednocześnie dwóch przycisków w terminalu zamiast dwóch zer i jedynki pojawiała się linia znaków zapytania

Diody

Aby przetestować działanie diod skorzystamy z programu 'leds_rg.py'. Najpierw łączymy odpowiednie porty tak jak pokazano na poniższym schemacie, następnie uruchamiamy program. W zależności od wybranej procedury sterowania diodami będą się one zapalać/gasić w prawo/w lewo. Studenci mają za zadanie zapoznać się z implementacją.

!Uwaga: porty GP14 i GP15 pozostają bez połączenia, ponieważ są one pierwotnie ustawione przez system operacyjny na mod UART i lepiej ich nie przestawiać na mod OUTPUT

Schemat połączeń w teście diod. Źródło: Gertboard User Maual
# FileName -> leds-rg.py
import RPi.GPIO as GPIO
from time import sleep

if GPIO.RPI_REVISION == 1:                          # check Pi Revision to set port 21/27 correctly
    # define ports list for Revision 1 Pi
    ports = [25, 24, 23, 22, 21, 18, 17, 11, 10, 9, 8, 7]
else:
    # define ports list all others
    ports = [25, 24, 23, 22, 27, 18, 17, 11, 10, 9, 8, 7]   
ports_rev = ports[:]                                # make a copy of ports list
ports_rev.reverse()                                 # and reverse it as we need both

GPIO.setmode(GPIO.BCM)                              # initialise RPi.GPIO

for port_num in ports:
    GPIO.setup(port_num, GPIO.OUT)                  # set up ports for output

def led_drive(reps, multiple, direction):           # define led_function:
    for i in range(reps):                           # (repetitions, single/multiple, direction)
        for port_num in direction:                  
            GPIO.output(port_num, 1)                # switch on an led
            sleep(0.11)                             # wait for ~0.11 seconds
            if not multiple:                        # if we're not leaving it on
                GPIO.output(port_num, 0)            # switch it off again

# Print Wiring Instructions appropriate to the board
print "These are the connections for the Gertboard LEDs test:"                
print "jumpers in every out location (U3-out-B1, U3-out-B2, etc)"
print "GP25 in J2 --- B1 in J3 \nGP24 in J2 --- B2 in J3"
print "GP23 in J2 --- B3 in J3 \nGP22 in J2 --- B4 in J3"
print "GP21 in J2 --- B5 in J3 \nGP18 in J2 --- B6 in J3"
print "GP17 in J2 --- B7 in J3 \nGP11 in J2 --- B8 in J3"
print "GP10 in J2 --- B9 in J3 \nGP9 in J2 --- B10 in J3"
print "GP8 in J2 --- B11 in J3 \nGP7 in J2 --- B12 in J3" 

raw_input("When ready hit enter.\n")

try:                                              
    # one repetition, switching off led before next one comes on, direction: forwards
    led_drive(1, 0, ports)                  
    # one repetition, switching off led before next one comes on, direction: backwards
    led_drive(1, 0, ports_rev)
    # one repetition, leaving each led on, direction: forwards
    led_drive(1, 1, ports)
    # one repetition, leaving each led on, direction: backwards
    led_drive(1, 0, ports)        
except KeyboardInterrupt:                         # trap a CTRL+C keyboard interrupt
    GPIO.cleanup()                                # clean up GPIO ports on CTRL+C
GPIO.cleanup()                                    # clean up GPIO ports on normal exit

Następnie studenci mają za zadanie tak zmodyfikować program aby sterować diodami w następujący sposób:

  • wszystkie diody są zgaszone
  • kolejno w prawą stronę zapalają się parzyste numery diod (i gasną tuż przed zapaleniem następnej)
  • kolejno w lewą stronę zapalają się nieparzyste numery diod (i gasną tuż przed zapaleniem następnej)
  • kolejno w prawą stronę zapalają się parzyste numery diod (i pozostają zapalone)
  • kolejno w lewą stronę zapalają się nieparzyste numery diod (i pozostają zapalone)
  • wszystkie diody są zgaszone

Diody + Przyciski

Aby sprawdzić dotychczasowe zrozumienie materiału, kolejne zadanie polega na połączeniu wiedzy na temat działania przycisków oraz diod. Docelowo chcemy aby naciskanie 3 przycisku powodowało gaszenie 6 diody, puszczanie 3 przycisku powodowało zapalenie 6 diody. Każda zmiana statusu przycisku oraz diody ma być wypisana w terminalu.

Jak pamiętamy z rozdziału o przyciskach, aby sygnał z guzika był wejściem do Raspberry, nie wpinamy zworki w miejscu 'in'. Sygnał z przycisku 3 jest automatycznie przekazywany do portu B3, który z kolei został połączony z portem GP23 w Raspberry za pomocą złączki. Zatem port GP23 dostaje informację o zmianie stanu przycisku.

Następnie chcemy aby ta zmiana stanu wartości logicznej przycisku była widoczna jako zapalanie/gaszenie diody. Najprostszą sytuacją jest wyświetlanie stanu 3 przycisku na 3 diodzie (zgodnie z poprzednim rozdziałem wystarczyłoby ustawić zworkę w miejscu 'out' portu B3). Jednak sprawa się nieco komplikuje, gdy stan przycisku ma być odzwierciedlony na 6 diodzie. Trzeba zatem sygnał wyjściowy ('out' B3) przekierować na port BUF_6. Wtedy dioda 6 będzie pokazywać stan logiczny portu 6.

Jednak to jeszcze nie koniec utrudnień. Dodatkowo chcemy aby stan diody 6 był sczytywany przez Raspberry Pi. Zatem musimy wskazać, że port BUF_6 jest sygnałem wejściowym do Raspberry (zworka 'in' na B6), który zostanie przesłany na port GP22 (złączka między B6 a GP22).

Gdy schemat połączeń jest jasny można go zrealizować, a następnie uruchamić program 'butled_rg.py'.

Schemat połączeń w teście przycisków oraz diod. Źródło: Gertboard User Maual
# FileName -> butled-rg.py
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)                                            # initialise RPi.GPIO
GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_UP)                 # set up port 23 for INPUT pulled-up high
GPIO.setup(22, GPIO.IN)                                           # set up port 22 for normal INPUT no pullup

print "These are the connections you must make on the Gertboard for this test:"
print "GP23 in J2 --- B3 in J3"
print "GP22 in J2 --- B6 in J3"
print "U3-out-B3 pin 1 --- BUF6 in top header"
print "jumper on U4-in-B6"

raw_input("When ready hit enter.\n")

button_press = 0                                                  # set intial values for variables
previous_status = ''

try:
    while button_press < 20:                                      # read inputs constantly until 19 changes are made
        status_list = [GPIO.input(23), GPIO.input(22)]            # put input values in a list variable
        for i in range(0,2):
            if status_list[i]:
                status_list[i] = "1"
            else:
                status_list[i] = "0" 
        current_status = ''.join((status_list[0],status_list[1])) # dump current status values in a variable
        if current_status != previous_status:                     # if that variable not same as last time
            print current_status                                  # print the results 
            previous_status = current_status                      # update status variable for next comparison
            button_press += 1                                     # increment button_press counter

except KeyboardInterrupt:                                         # trap a CTRL+C keyboard interrupt
    GPIO.cleanup()                                                # resets all GPIO ports
GPIO.cleanup()                                                    # on exit, reset  GPIO ports used by program

Płytka uniwersalna

Płytka uniwersalna to narzędzie ułatwiające samodzielne i szybkie tworzenie obwodów elektronicznych. Jej angielska nazwa breadboard wskazuje na historyczne korzenie tego narzędzia. Oryginalnie (lata 20-te XX wieku) były to dosłownie deski do krojenia chleba, do których amatorzy wczesnej elektroniki (np. radia) przymocowywali przewody i inne elementy elektroniczne za pomocą gwoździ. Współcześnie, jednym z rodzajów płytek uniwersalnych jest płytka stykowa, która nie wymaga przylutowywania elementów, a jedynie umieszczenia ich w odpowiednich otworach. Oznaczone linie czerwone i niebieskie, to rzędy otworów połączonych ze sobą. Dodatkowo kolumny otworów wzdłuż krótszego boku płytki również są ze sobą połączone.

Płytka uniwersalna stykowa. Źródło: https://pl.wikipedia.org/wiki/Plik:400_points_breadboard.jpg

Zadanie 1

Korzystając z płytki uniwersalnej należy zbudować obwód przedstawiony na schemacie. Diodę czerwoną wpinamy dłuższą nóżką w stronę +, krótszą w stronę -, natomiast opornik wybieramy o oporności 0,6 kΩ. Docelowo, dioda na płytce uniwersalnej ma być sterowana sygnałem pochodzącym z portu BUF_1 (czyli sygnałem generowanym przez Raspberry Pi podawanym na port GP25):

  • dioda ma zostać zapalona na czas 5 sekund
  • następnie zgaszona na 5 sekund
  • na koniec ma być zapalana i wygaszana co 1 sekundę (10-cio krotnie).
Schemat połączenia do Zadania 1

Zadanie 2

Korzystając z płytki uniwersalnej należy zbudować układ przedstawiony na schemacie. Opornik dobieramy o oporności 1 kΩ. Docelowo chcemy sczytywać (i wyświetlać w terminalu) stan portu BUF_1, który to będzie regulowany poprzez zbudowany przez nas przełącznik.

Schemat połączenia do Zadania 2

Otwarty Kolektor

Do tej pory, za pomocą Raspberry Pi, sterowaliśmy zewnętrznymi "urządzeniami" takimi jak diody. W tym celu wystarczało dostępne w małym komputerze napięcie 3,3 V. Jednak aby sterować zewnętrznymi urządzeniami, które korzystają z wyższego napięcia niż dostępne w Raspberry Pi, niezbędne jest wyjście typu otwarty kolektor (schemat układu, który kryje się pod tym wyjściem został przedstawiony poniżej).

Schemat układu z wyjściem typu otwarty kolektor. Składa się z tranzystorów, oporników i diod. Źródło: Gertboard User Maual

Takich układów otwartego kolektora na desce Gertboard jest 6 (od RLY1 do RLY6), znajdują się one w obszarze oznaczonym żółtą ramką i każdy z nich działa przy napięciach do 50 V i prądach do 500 mA.

Płytka Gertboard z zaznaczonymi obszarami funkcyjnymi. Źródło: Gertboard User Maual

Zasadniczo rola tego obwodu sprowadza się do kontroli połączenia uziemienia zewnętrznego obwodu elektrycznego z uziemieniem deski Gertboard, co pozwala na "zamknięcie" obwodu i popłynięcie prądu. Wyjście może przyjmować dwa stany:

  • wysokiej impedancji (jakby do niczego nie było podłączone)
  • zwarcia z masą (obwód jest "zamknięty" i płynie przez niego prąd)

Zatem aby móc decydować o włączeniu/wyłączeniu zewnętrznego urządzenia należy podłączyć dodatni biegun zasilania zewnętrznego do wejścia 'common' (czyli RPWR na desce Gertboard), ujemny biegun zasilania zewnętrznego do uziemienia deski, oraz koniec obwodu do wyjścia 'out' (np. RLY1). W ten sposób, gdy połączymy również wyjście z Raspberry np. GP4 z portem RLY1 ('Raspi'), będziemy mogli za pomocą sygnału z Raspberry decydować o tym czy przez zewnętrzny układ popłynie prąd.

Aby zapoznać się z działaniem przykładowego wyjścia typu otwarty kolektor, studenci mają za zadanie odtworzyć połączenia przedstawione na poniższym schemacie oraz zbudować zewnętrzny obwód składający się z dwóch diod (czerwonej, zielonej), opornika (o oporze 0,6 kΩ) i źródła zasilania (bateria 9V). Następnie należy zapoznać się z implementacją programu sterującego zasilaniem diod.

Schemat połączenia do testu wyjścia typu otwarty kolektor. Źródło: Gertboard User Maual
# FileName -> ocol-rg.py
import RPi.GPIO as GPIO
from time import sleep

chan_num = 6                                        # available channel number
channel = 0                                         # set channel to 0 initially so 
                                                    # it will ask for user input
def which_channel():
    print "Which driver do you want to test?"
    chan = raw_input("Type a number between 1 and %d\n" % chan_num)      # User inputs channel number
    while not chan.isdigit():                                            # Check valid user input                        
        chan = raw_input("Try again-just numbers 1-%d !\n" % chan_num)   # Make them do it again if wrong   
    return chan

while not 0 < chan < (chan_num + 1):                # ask for a channel number
    chan = int(which_channel())                     # once proper answer given, carry on

print "These are the connections for the open collector test:"
print "GP4 in J2 --- RLY%d in J4" % channel
print "+ of external power source --- RPWR in J6"
print "ground of external power source --- GND (any)"
print "ground side of your circuit --- RLY%d in J%d" % (channel, channel+11)
print "+ of your circuit --- RPWR (any)"
raw_input("When ready hit enter.\n")

GPIO.setmode(GPIO.BCM)                              # initialise RPi.GPIO
GPIO.setup(4, GPIO.OUT)                             # set up port 4 for output

try:
    for i in range(10):                             # do this 10 times
        GPIO.output(4, 1)                           # switch port 4 on
        sleep(0.4)                                  # wait 0.4 seconds
        GPIO.output(4, 0)                           # switch port 4 off
        sleep(0.4)

except KeyboardInterrupt:                           # trap a CTRL+C keyboard interrupt
    GPIO.cleanup()                                  # resets all GPIO ports

GPIO.cleanup()                                      # on finishing,reset all GPIO ports used by this program

Po wykonaniu zadania testowego należy zmodyfikować program sterujący i okablowanie tak, aby diody zapalały się 10-cio krotnie na 2 sekundy z przerwą trwającą 1 sekundę oraz sterowanie odbywało się przez wyjście RLY3.

Sterownik silnika prądu stałego

Sterownik silnika prądu stałego wbudowany w deskę Gertboard może przyjąć maksymalne napięcie 18 V i natężenie 2A. Wymaga on dwóch logicznych sygnałów wejściowych A i B (na desce są one oznaczone MOTOA i MOTOB). Zachowanie silnika w zależności od stanu logicznego sygnałów A i B zostało zobrazowane w tabeli poniżej.

Tabela przedstawiająca zachowanie silnika w zależności od stanu logicznego wejść A i B. Źródło: Gertboard User Maual

O typie ruchu silnika (lub jego braku) decyduje stan logiczny sygnałów. Natomiast gdy chcemy kontrolować jego prędkość musimy kontrolować stosunek pojawiania się 1 i 0 w sygnale. W tym celu stosuje się modulację szerokości impulsów. Przykładowe sygnały logiczne z zastosowaną modulacją szerokości impulsów zostały zaprezentowane na obrazku poniżej.

Przykłady sygnału logicznego na wejściu A. Górny wykres przedstawia sytuację gdy silniczek kręci się dwa razy szybciej niż w sytuacji przedstawionej na dolnym wykresie. Źródło: Gertboard User Maual

Znając podstawy sterowania silniczkiem można przejść do wykonywania połączeń, których schemat został przedstawiony poniżej. Docelowo chcemy aby wysłany przez Raspberry sygnał logiczny z portu GP18 był sygnałem MOTOA, natomiast sygnał z portu GP17 był przekazany dalej jako MOTOB. Po wykonaniu połączeń należy zapoznać się ze sterującym programem komputerowym i uruchomić go.

Schemat połączenia do testu silniczka. Źródło: Gertboard User Maual
# FileName -> motor-rg.py
from __future__ import print_function
import RPi.GPIO as GPIO
from time import sleep

GPIO.setmode(GPIO.BCM)
ports = [18,17]             # define which ports to be pulsed (using a list)
Reps = 400                  # 2000 Hz cycle time, so Reps=400 is 0.2s for each percentage ON
Hertz = 2000                # Cycle time. You can tweak this, Max 3000               
Freq = (1 / float(Hertz)) - 0.0003           # run_motor loop code takes 0.0003s

for port_num in ports:                       # set the ports up for output
    GPIO.setup(port_num, GPIO.OUT)           # set up GPIO output channel
    print ("setting up GPIO port:", port_num)
    GPIO.output(port_num, False)             # set both ports to OFF
    
def run_motor(Reps, pulse_width, port_num, time_period):
    try:                                     # try: except:, traps errors
        for i in range(0, Reps):
            GPIO.output(port_num, True)      # switch port on
            sleep(pulse_width)               # make sure pulse stays on for correct time
            GPIO.output(port_num, False)     # switch port off
            sleep(time_period)               # time_period for port OFF defined in run_loop
    except KeyboardInterrupt:                # reset all ports used by this program if CTRL-C pressed
        GPIO.cleanup()

def run_loop(startloop, endloop, step, port_num, printchar):
    for pulse_width_percent in range(startloop, endloop, step):
        print (printchar, sep='', end='')
        sys.stdout.flush()
        pulse_width = pulse_width_percent / float(100) * Freq           # define exact pulse width
        time_period = Freq - (Freq * pulse_width_percent / float(100))  # sleep period needed to get required Hz
        run_motor(Reps, pulse_width, port_num, time_period)
    print("")                                                           # print line break between runs


print ("\nThese are the connections for the Gertboard motor test:")
print ("GP17 in J2 --- MOTB (just above GP1)")
print ("GP18 in J2 --- MOTA (just above GP4)")
print ("+ of external power source --- MOT+ in J19")
print ("ground of external power source --- GND (any)")
print ("one wire for your motor in MOTA in J19")
print ("the other wire for your motor in MOTB in J19")
command = raw_input("When ready hit enter.\n>")

print (">>>", sep='', end='')
run_loop(5, 95, 1, 18,'+')      # (startloop, endloop, step, port_num, printchar, loopnum)
run_loop(95, 5, -1, 18,'-')     # if you go all the way to 100% it seems out of control at the changeover
sleep(0.2)                      # a slight pause before change direction stops sudden motor jerking
print ("<<<", sep='', end='')
run_loop(5, 95, 1, 17,'+')
run_loop(95, 5, -1, 17,'-')

GPIO.output(port_num, False)            # Finish up: set both ports to off
GPIO.cleanup()                          # reset all ports used by this program

Następnie student ma za zadanie tak zmodyfikować program aby wykonywał:

  • obrót silniczka o około 180 stopni w prawo
  • przerwa trwająca 1 sekundę
  • powtórzenie poprzednich 2 podpunktów 5 razy
  • obrót silniczka o około 180 stopni w lewo
  • przerwa trwająca 1 sekundę
  • powtórzenie poprzednich 2 podpunktów 5 razy

Komunikacja przez SPI

SPI (ang. "Serial Peripheral Interface") to szeregowy interfejs urządzeń peryferyjnych, pozwalający na komunikację głównego mikroprocesora (ang. "Master") z przetwornikiem analogowo-cyfrowym i cyfrowo-analogowym (lub innym urządzeniem peryferyjnym, ang. "Slave"). Składa się on z 3 linii komunikacyjnych działających równocześnie:

  • SCLK (ang. "Serial CLocK") - sygnał zegarowy/taktujący przesyłany od "Master" do "Slave"
  • MOSI (ang. "Master Output Slave Input") - sygnał od "Master" do "Slave"
  • MISO (ang. "Master Input Slave Output") - sygnał od "Slave" do "Master"
Płytka Gertboard z zaznaczonymi obszarami funkcyjnymi. Źródło: Gertboard User Maual

Przetworniki wbudowane w deskę Gertboard znajdują się w obszarze zaznaczonym pomarańczową ramką. Jak widać każdy z przetworników ma dostępne po dwa kanały: analogowo-cyfrowy AD0, AD1 oraz cyfrowo-analogowy DA0, DA1. Wyboru używanego przetwornika dokonuje się poprzez nadanie logicznej wartości 0 na jednym z portów: CSnA w przypadku przetwornika analogowo-cyfrowego, lub CSnB w przypadku przetwornika cyfrowo-analogowego.

Przetwornik cyfrowo-analogowy

Przetwornik cyfrowo-analogowy wbudowany w deskę Gertboard jest 8-bitowy, z maksymalnym napięciem 2,04 V. Napięcie wytworzone na wyjściu (pin DA0 lub DA1) [math]V_{out}[/math] opisane jest wzorem:

[math]V_{out}=\frac{D_{in}}{256} \cdot {2,048 V}[/math]

gdzie [math]D_{in}[/math] to liczba 8-bitowa z zakresu od 0 do 256, którą użytkownik może dowolnie wybrać w zależności od oczekiwanego rezultatu (napięcia na wyjściu). Ta liczba, wraz z innymi niezbędnymi informacjami, musi zostać przekazana do przetwornika poprzez protokół SPI w postaci 2 bajtów (2 liczb 8-bajtowych) czyli 16 bitów:

  • 16-13 bit: różne informacje:
    • 16: numer kanału (0 lub 1)
    • 15: bez znaczenia (0)
    • 14: gain (mnożnik razy dwa - 0 lub mnożnik razy jeden - 1)
    • 13: shutdown mode (off-0, on-1 dla oszczędności energii)
  • 12-5 bit: liczba [math]D_{in}[/math]
  • 4-1 bit: nieznaczące zera

Zatem, chcąc uzyskać na wyjściu przykładowe napięcie [math]V_{out}[/math] 1,5 V najpierw z powyższego wzoru obliczamy liczbę [math]D_{in}[/math], która wyniesie 188. Następnie zawieramy informacje w poszczególnych bitach:

  • 16-13 bit: kanał zerowy-0, bez znaczenia-0, gain-1, shutdown mode-1 ; 0011
  • 12-5 bit: liczba 188 to 10111100
  • 4-1 bit: 0000

których ciąg: 0011101111000000 tworzy dwie liczby 8-bitowe: 00111011 (liczba 59) 11000000 (liczba 192). Te dwie liczby podajemy przy użyciu komunikacji SPI.

Aby zapoznać się z działaniem przetwornika DA studenci mają za zadanie wykonać połączenia przedstawione na poniższym schemacie oraz zapoznać się z przykładowym programem. Do mierzenia wygenerowanego napięcia należy skorzystać z uniwersalnego multimetru.

Schemat połączeń w teście przetwornika DA. Źródło: Gertboard User Maual
# FileName -> dtoa.py
import spidev
import subprocess
from time import sleep

def which_channel():
    channel = raw_input("Which channel do you want to test? Type 0 or 1.\n")  # User inputs channel number
    while not channel.isdigit():                                              # Check valid user input
        channel = raw_input("Try again - just numbers 0 or 1 please!\n")      # Make them do it again if wrong
    return channel

# reload spi drivers to prevent spi failures
unload_spi = subprocess.Popen('sudo rmmod spi_bcm2835', shell=True, stdout=subprocess.PIPE)
start_spi = subprocess.Popen('sudo modprobe spi_bcm2835', shell=True, stdout=subprocess.PIPE)
sleep(3)

spi = spidev.SpiDev()
spi.open(0,1)                                   # The Gertboard DAC is on SPI channel 1 (GPIO7)
                                                # ADC is on SPI channel 0 (GPIO8)

channel = 3                                     # set initial random value to force user selection
voltages = [0.0,0.5,1.02,1.36,2.04]             # voltages for display
common = [0,0,0,160,240]                        # 2nd byte common to both channels
                                                
while not (channel == 1 or channel == 0):       # channel is set by user input
    channel = int(which_channel())              # continue asking until answer 0 or 1 given
if channel == 1:                                
    num_list = [176,180,184,186,191]            # 1st byte list (channel-dependent)
else:
    num_list = [48,52,56,58,63]                 # 1st byte list (channel-dependent)

print "These are the connections for the digital to analogue test:"
print "jumper connecting GP11 to SCLK"
print "jumper connecting GP10 to MOSI"
print "jumper connecting GP9 to MISO"
print "jumper connecting GP7 to CSnB"
print "Multimeter connections (set your meter to read V DC):"
print "  connect black probe to GND"
print "  connect red probe to DA%d on J29" % channel
raw_input("When ready hit enter.\n")

for i in range(5):
    r = spi.xfer2([num_list[i],common[i]])                   #send 1st and 2nd byte through SPI
    print "Your multimeter should read about %.2fV" % voltages[i]   
    raw_input("When ready hit enter.\n")

r = spi.xfer2([16,0])  # clear channel 0 -> set 0 V -> 1st byte 00010000 [16], 2nd byte 00000000 [0]
r = spi.xfer2([144,0]) # clear channel 1 -> set 0 V -> 1st byte 10010000 [144], 2nd byte 00000000 [0]

Następnie studenci mają za zadanie tak zmodyfikować program aby uzyskać na wyjściu napięcia: 0,44 V, 0,88 V, 1,22 V.

Przetwornik analogowo-cyfrowy

Przetwornik analogowo-cyfrowy wbudowany w deskę Gertboard jest 10-bitowy, z częstością próbkowania 72kHz. Przetwornik zwraca przez SPI 3 bajty danych [XXXX XXXX][XXXX DDDD][DDDD DDXX]. Bity od 12 do 3 tworzą 10-cio bitową liczbę z zakresu 0-1023, co odpowiada wartościom napięcia z zakresu 0-3.3 V.

Jednak aby odebrać te dane należy również wysłać do przetwornika informację o tym, z którego kanału sygnał nas interesuje. Do tego celu służy jedna funkcja spi.xfer2, która przesyła informacje do (argument funkcji) i z (wartość zwrócona przez funkcję) przetwornika. Aby odczytać sygnał z kanału 0 należy nadać 3 bajty: [0000 0001] [1000 0000] [0000 0000] (czyli liczby: 1,128,0), natomiast aby odczytać sygnał z kanału 1 należy nadać 3 bajty: [0000 0001] [1100 0000] [0000 0000] (czyli liczby: 1,192,0).

Zadanie testowe będzie wymagało wykorzystania źródła zmiennego sygnału analogowego. W tym celu użyty zostanie potencjometr - regulowany dzielnik napięcia. Napięcie wyjściowe [math]U_{wy}[/math] na potencjometrze opisuje równanie:

[math]U_{wy} = \frac{U_{we}}{R+R_1} \cdot R_1[/math]

gdzie [math]U_{we}[/math] to napięcie wejściowe, R to stały opór potencjometru, natomiast [math]R_{1}[/math] to zmienny opór (regulowany pokrętłem).

W zadaniu testowym podłączamy jedną nóżkę potencjometru do zasilania z Raspbery (3,3 V), drugą do uziemienia, a trzecią jako wejście analogowe do przetwornika analogowo-cyfrowego na kanale AD0. Wykonujemy resztę połączeń niezbędnych do komunikacji przy użyciu SPI. Następnie, należy zapoznać się z programem zamieszczonym poniżej i uruchomić go.

Schemat połączeń w teście przetwornika AD. Źródło: Gertboard User Maual
# FileName -> atod.py
from __future__ import print_function       
from time import sleep
import subprocess
import spidev
import sys
                   
def which_channel():
    channel = raw_input("Which channel do you want to test? Type 0 or 1.\n")  # User inputs channel number
    while not channel.isdigit():                                              # Check valid user input
        channel = raw_input("Try again - just numbers 0 or 1 please!\n")      # Make them do it again if wrong
    return channel

def get_adc(channel):                                   # read SPI data from MCP3002 chip
    if ((channel > 1) or (channel < 0)):                # Only 2 channels 0 and 1 else return -1
        return -1
    r = spi.xfer2([1,(2+channel)<<6,0])                 # these two lines are explained in more detail at the bottom
    ret = ((r[1]&15) << 6) + (r[2] >> 2)
    return ret 

def display(char, reps, adc_value, spaces):             # function handles the display of ##### 
    print ('\r',"{0:04d}".format(adc_value), ' ', char * reps, ' ' * spaces,'\r', sep='', end='') 
    sys.stdout.flush()

# reload spi drivers to prevent spi failures
unload_spi = subprocess.Popen('sudo rmmod spi_bcm2835', shell=True, stdout=subprocess.PIPE)
start_spi = subprocess.Popen('sudo modprobe spi_bcm2835', shell=True, stdout=subprocess.PIPE)
sleep(3)   

channel = 3                # set channel to 3 initially so it will ask for user input (must be 0 or 1)
while not (channel == 1 or channel == 0):       # continue asking until answer 0 or 1 given
    channel = int(which_channel())              # once proper answer given, carry on

print ("These are the connections for the analogue to digital test:")
print ("jumper connecting GP11 to SCLK")
print ("jumper connecting GP10 to MOSI")
print ("jumper connecting GP9 to MISO")
print ("jumper connecting GP8 to CSnA")
print ("Potentiometer connections:")
print ("  (call 1 and 3 the ends of the resistor and 2 the wiper)")
print ("  connect 3 to 3V3")
print ("  connect 2 to AD%d" % channel);
print ("  connect 1 to GND")
raw_input("When ready hit enter.\n")

spi = spidev.SpiDev()
spi.open(0,0)                                   # The Gertboard ADC is on SPI channel 0 (GPIO8)

char = '#'                                      # define the bar chart character
iterations = 0                                  # initial value for iteration counter
while iterations < 600:
    adc_value = (get_adc(channel))
    reps = adc_value / 16
    spaces = 64 - reps
    display(char, reps, adc_value, spaces)
    sleep(0.05)                                 # need a delay so people using ssh don't get slow response
    iterations += 1                             # limits length of program running to 30s [600 * 0.05]

# EXPLANATION of 
# r = spi.xfer2([1,(2+channel)<<6,0])
# x << 6 (it returns x with the bits shifted to the right by 6 places)
# Send 3 bytes: [sgl/diff] [odd/sign] [MSBF]
# sgl/diff = 1; odd/sign = channel; MSBF = 0  
# channel = 0 sends [0000 0001] [1000 0000] [0000 0000]
# channel = 1 sends [0000 0001] [1100 0000] [0000 0000]

# EXPLANATION of 
# ret = ((r[1]&15) << 6) + (r[2] >> 2)
# spi.xfer2 returns three 8-bit bytes. We must then parse out the correct 
# 10-bit byte from the 24 bits returned. The following line discards all bits but
# the 10 data bits from the center of the last 2 bytes: XXXX XXXX - XXXX DDDD - DDDD DDXX

Przetwornik analogowo-cyfrowy + Silnik

Zadanie to łączy w sobie wiedzę na temat przetwornika analogowo-cyfrowego i sterownika silnika prądu stałego. Sygnał analogowy pochodzący z potencjometru ma zostać odczytany przez Raspberry Pi, a następnie zamieniony na sygnał logiczny o odpowiedniej szerokości impulsów będący wejściem do sterownika silnika. W ten sposób, kontrolując wskazania potencjometru (za pomocą śrubokrętu) można jednocześnie sterować prędkością i kierunkiem obrotów silnika. Docelowo chcemy, aby maksymalne napięcie na potencjometrze odpowiadało maksymalnej prędkości w jednym kierunku, zerowe napięcie maksymalnej prędkości w drugim kierunku, natomiast środkowe napięcie braku ruchu silniczka. Poniżej zamieszczono schemat połączeń niezbędnych do tego zadania.

Schemat połączeń w zadaniu z przetwornikiem AD i silnikiem. Źródło: Gertboard User Maual

Tranzystor

Tranzystor to element elektroniczny, który wzmacnia sygnał elektryczny dzięki specyficznej budowie złącz trzech elektrod półprzewodnikowych: emiter, baza, kolektor. Stosunkowo niewielki prąd płynący pomiędzy bazą i emiterem może sterować większym prądem płynącym między kolektorem i emiterem. Na zajęciach korzystać będziemy z tranzystora typu npn.

Tranzystor npn KSP2222a. Źródło: dokumentacja techniczna

Cechą charakterystyczną każdego tranzystora jest współczynnik wzmocnienia prądu (dla naszych tranzystorów β=100), który jednak zależy (dość silnie) od temperatury. Głównym celem tego zadania jest zbadanie charakterystyki tranzystora - zależności prądu na bazie i kolektorze: [math]I_{K} = \beta \cdot I_{B} [/math]. Pozwoli nam ona na wyznaczenie współczynnika wzmocnienia (współczynnik kierunkowy prostej) oraz zaobserwowanie zmian zachodzących wraz z ogrzewaniem tranzystora.

Schemat połączeń do charakterystyki tranzystora

Kolejność procedury jest następująca:

  1. za pomocą przetwornika cyfrowo-analogowego podajemy napięcie [math]U_{B}[/math] na port DA0 (na bazę tranzystora)
  2. znając oporność opornika na bazie [math]R_{B}=22k\Omega[/math], oraz wiedząc, że pomiędzy bazą a emiterem jest spadek napięcia 0.6 V, możemy policzyć prąd na bazie: [math]I_{B} = \frac{U_{B}-0.6 V}{R_{B}} [/math]
  3. za pomocą przetwornika analogowo-cyfrowego odczytujemy napięcie [math]U_{K}[/math] na kolektorze na porcie AD0
  4. znając oporność opornika na kolektorze [math]R_{K}=0.6k\Omega[/math], możemy policzyć prąd na kolektorze: [math]I_{K} = \frac{3.3V -U_{K}}{R_{K}} [/math]
  5. powtarzamy punkty 1-4 dla różnych napięć [math]U_{B}[/math] z zakresu 0-2,04 V
  6. rysujemy zależność [math]I_{K}[/math] od [math]I_{B}[/math] i wyliczamy współczynnik kierunkowy prostej
  7. powtarzamy eksperyment po ogrzaniu tranzystora ciepłem rąk

Warto zauważyć, że zależność prądu na kolektorze od prądu na bazie nie będzie w całym zakresie liniowa. Dla niskich napięć na bazie [math]U_{B}[/math] (poniżej 0.6 V) tranzystor nie przewodzi prądu. Następnie tranzystor pracuje w zakresie liniowym. Natomiast gdy napięcie na bazie (zatem i prąd na bazie) osiągną pewną graniczną wartość, tranzystor będzie nasycony. Oznacza to, że tranzystor może wzmacniać prąd jedynie do pewnej maksymalnej wartości określonej stosunkiem [math]I_{KMAX} = \frac{3.3V}{R_{K}} [/math].

Czujnik temperatury i wilgotności powietrza

Na zajęciach używać będziemy czujnika temperatury i wilgotności powietrza AM2302/DH22. Jest on zasilany napięciem 3,3-6 V. Zakres pomiarowy wynosi 0-100 % RH oraz -40 °C do +80 °C, gdzie RH to wilgotność względna wyrażana w procentach. Jest to stosunek rzeczywistej wilgoci w powietrzu do maksymalnej jej ilości, którą może utrzymać powietrze w danej temperaturze. Sygnał cyfrowy jest 8-bitowy, rozdzielczość pomiaru temperatury wynosi 0,1 °C, natomiast wilgotności powietrza ± 0,1 % RH. Jednak jego szacowana dokładność to ± 0,5 °C oraz ± 2 % RH.

Czujnik temperatury i wilgotności powietrza. Modele DHT11 oraz DHT22. Źródło: https://cdn-learn.adafruit.com/downloads/pdf/dht.pdf

Kolejne nóżki czujnika (od lewej do prawej) to:

  • zasilanie
  • sygnał
  • nic
  • uziemienie

Aby przetestować działanie czujnika, budujemy obwód elektryczny zgodnie ze schematem przedstawionym poniżej (użyty opornik 5kΩ ma spełniać funkcję pull-up).

Schemat połączeń do testowania czujnika

Podczas pracy z czujnikiem korzystać będziemy z biblioteki stworzonej przez Adafruit. Umożliwia ona komunikację z wbudowanym 8-bitowym mikro-kontrolerem, który wykonuje konwersję analogowo-cyfrową i przesyła dane w odpowiednim formacie. Dzięki temu sygnał wyjściowy z czujnika można przesyłać na dowolny pin GPIO. Aby pobrać odpowiednią bibliotekę należy wykonać następujące komendy:

$ sudo apt-get install -y build-essential python-dev git 
$ mkdir -p /home/pi/sources  
$ cd /home/pi/sources  
$ git clone https://github.com/adafruit/Adafruit_Python_DHT.git  
$ cd Adafruit_Python_DHT  
$ sudo python setup.py install

Po ich wykonaniu w lokalizacji '/home/pi/sources/Adafruit_Python_DHT/examples/' znajduje się przykładowy skrypt testowy 'AdafruitDHT.py', który można uruchomić z dwoma argumentami:

  1. typ czujnika: 11 (dla DHT11), 22 (dla DHT22), 2302 (dla naszego AM2302)
  2. numer pinu GPIO, na który nadawany jest sygnał: 4

Hallotron

Hallotron to urządzenie elektroniczne półprzewodnikowe wykorzystywane do pomiaru natężenia pola magnetycznego. Nazwa pochodzi od klasycznego efektu Halla, na którym opiera się jego działanie (wystąpienie różnicy potencjałów w przewodniku znajdującym się w polu magnetycznym). Schemat przedstawiający hallotron i oznaczenie jego "nóżek" znajduje się poniżej.

Halotron A324 oraz oznaczenie jego "nóżek". Źródło: dokumentacja techniczna

Do zasilania hallotronu potrzebne jest napięcie 5V, na wyjściu napięcie waha się w granicach 2.425 - 2.575 V. Jak widać różnice w napięciu wyjściowym (zależnym od natężenia pola magnetycznego) są bardzo małe i aby je zarejestrować potrzebny będzie wzmacniacz różnicowy, który wzmocni jedynie różnicę pomiędzy typowym napięciem hallotronu (2.5 V) a napięciem uzyskanym poprzez zmianę pola magnetycznego (przybliżanie magnesu). Do naszych celów użyjemy wzmacniacza operacyjnego przedstawionego na poniższym rysunku wraz z opisem nóżek wejściowych i wyjściowych.

Wzmacniacz operacyjny LM324N oraz oznaczenie jego "nóżek". Źródło: dokumentacja techniczna

Jako źródło napięcia typowego dla hallotronu użyjemy potencjometru zasilanego napięciem 5 V i ustawionego na 2.5 V. Montując układ przedstawiony na poniższym schemacie, gdzie opór [math]r=1k\Omega[/math] natomiast [math]R=18k\Omega[/math], uzyskamy wzmocnienie napięcia różnicowego [math]\beta=\frac{R}{r}=18[/math]. Dodatkowo, tuż przed przetwornikiem analogowo-cyfrowym, należy jeszcze zastosować wtórnik emiterowy (tranzystor) wraz z opornikiem [math]R_{2}=1k\Omega[/math], który pozwoli na wzmocnienie prądu.

Schemat obwodu i połączeń w teście hallotronu.