Nowe technologie w fizyce biomedycznej: Różnice pomiędzy wersjami

Z Brain-wiki
Linia 1088: Linia 1088:
 
*MISO (ang. "Master Input Slave Output") - sygnał od "Slave" do "Master"
 
*MISO (ang. "Master Input Slave Output") - sygnał od "Slave" do "Master"
  
[[Plik:gertboard2.png|400px|thumb|center|<figure id="fig:wbb"></figure>Płytka Gertboard z zaznaczonymi obszarami funkcyjnymi. Źródło: Gertboard User Maual]]
+
[[Plik:gertboard2.png|600px|thumb|center|<figure id="fig:wbb"></figure>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.  
 
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.  

Wersja z 19:10, 13 mar 2017

Wprowadzenie

Przedmiot "Nowe technologie w fizyce biomedycznej" to 30 godzin zajęć i 3 punkty ECTS. Jego celem jest umożliwienie studentom wykorzystania rewolucyjnych i tanich technologii, nie uwzględnianych dotychczas w programach kształcenia w UW, w działalności naukowej i gospodarczej, oraz rozbudzenie kreatywności. Adresowany jest do studentów studiów II stopnia Fizyki Medycznej i Neuroinformatyki na Wydziale Fizyki. Zajęcia mają formę zajęć warsztatowych/pracowni. Przewiduje się 7 stanowisk dla 14 studentów pracujących w parach. Pojedyncze zajęcia na pracowni trwają 2h. Program podzielony jest na 3 bloki tematyczne:

  • Posturografia - 8h (4 zajęcia)
  • Kamery 3D - 8h (4 zajęcia)
  • Raspberry Pi - 14h (7 zajęć)

Warunkiem uczestnictwa w zajęciach jest uprzednie zaliczenie przedmiotów „Pracownia fizyczna i elektroniczna”, „Analiza sygnałów” oraz podstawowa znajomość języka Python.

Zaliczenie przedmiotu odbywa się na podstawie:

  • obecności (maksymalnie 2 nieusprawiedliwione nieobecności)
  • przedstawienia prezentacji z wynikami na koniec tematu: Posturografia, Kamery 3D
  • projektu zaliczeniowego na koniec tematu: Raspberry Pi

Posturograf

Opis bloku tematycznego: Zajęcia warsztatowe składające się z wprowadzającego w tematykę zajęć wykładu i indywidualnych ćwiczeń wykonywanych przez studentów. Studenci w czasie zajęć przeprowadzają standardowe pomiary posturograficzne, a następnie analizują zebrane dane.

Plan zajęć

Zajęcia 1:

  • Wstęp teoretyczny:
    • Wii Balance Board (budowa, główne biblioteki obsługujące sensor, zastosowania)
    • Projesjonalne systemy do rejestracji siły nacisku
    • Równowaga a stabilność posturalna
    • Podstawowe zadania posturograficzne
    • Opis wybranych wskaźników do zadań posturograficznych
  • Wprowadzenie do pomiarów przeprowadzanych na zajęciach (zapoznanie się z wybranymi scenariuszami oraz modułami do analizy)
  • Zapoznanie się z działaniem Wii Balance Board
  • Przeprowadzenie pomiarów

Media: WiiBoard.pdf Informacje wstępne oraz opis zadań

Zajęcia 2,3,4:

  • Analiza zebranych danych
  • Prezentacja wyników

Pomiary

Pomiary przeprowadzane są w środowisku OpenBCI. Architektura systemu oraz opis wybranych scenariuszy jest dostępny na stronie http://bci.fuw.edu.pl/wiki/Tutorials. Szczegółowe informacje dotyczące konfiguracji OpenBCI na Ubuntu 14.04 LTS można znaleźć pod adresem http://deb.braintech.pl/. Po zainstalowaniu pakietów źródła znajdą sie w katalogu /usr/share/openbci.

Z OpenBCI można również korzystać na systemie Windows -> instrukcja Media: obci_Windows.pdf

Rejestracja danych odbywa się w środowisku OpenBCI. System ten uruchamiamy wykonując w terminalu polecenie:

$ obci_gui --presets new_tech

Podczas zajęć przeprowadzone zostaną następujące pomiary:

  • stanie swobodne z oczami otwartymi/zamkniętymi,
  • wychylenia szybkie i "z przytrzymaniem" bez informacji zwrotnej dla badanego (w przód, w tył, w prawo, w lewo),
  • wychylenia szybkie i "z przytrzymaniem" z informacją zwrotną dla badanego (w przód, w tył, w prawo, w lewo).

W wyniku każdego pomiaru otrzymujemy komplet trzech plików (lokalizacja: Katalog Domowy):

  • Plik z sygnałem (.raw) zapisanym w formacie binarnym. W pliku znajdują się próbki z pięciu kanałów – wartości z czterech czujników WBB oraz momenty w czasie (w sekundach) mierzone względem pojawienia się pierwszej próbki.
  • Plik z metadanymi (.xml), w którym znajdują się szczegółowe informacje dotyczące rejestracji (nazwy kanałów, częstość próbkowania, całkowita liczba próbek itp.).
  • Plik ze znacznikami (.tag), w którym zapisywane są momenty kolejnych zdarzeń (np. początek i koniec wykonywania zadania) zsynchronizowane z sygnałem. Każdy znacznik posiada charakterystyczną nazwę, moment wystąpienia w sygnale, długość oraz ewentualnie opis.

W przypadku zadań z informacją zwrotną dla badanego generowane są dwa pliki ze znacznikami. Interesujące nas informacje znajdują się w pliku z rozszerzeniem .game.tag.

Analiza danych

Przygotowanie danych do analizy

Pierwszym etapem analizy jest wstępne przetworzenie danych. W tym celu należy wyestymować rzeczywistą częstość próbkowania Fs (w idealnym przypadku wynosi ona 65 Hz) oraz przepróbkować sygnał do częstości fs ok. 30 Hz (wychylenia swobodne widoczne są głównie w niższych częstościach).

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from obci.analysis.balance.wii_read_manager import WBBReadManager
from obci.exps.ventures.analysis import analysis_baseline
from obci.analysis.balance.wii_preprocessing import *
from obci.analysis.balance.wii_analysis import *

def read_file(file_path, file_name, tag_format = 'obci'):
    file_name = file_path+file_name
    wbb_mgr = WBBReadManager(file_name+'.obci.xml', file_name+'.obci.raw', file_name + '.' + tag_format + '.tag')
    return wbb_mgr

FILE_PATH = '/home/newtech/'
FILE_NAME = 'wii_baseline_2015-03-04_15-02-01' #przykładowa nazwa pliku

#wczytanie danych
wbr_baseline = read_file(FILE_PATH, FILE_NAME)
#estymacja częstości próbkowania Fs                                  
Fs = analysis_baseline.estimate_fs(wbr_baseline.mgr.get_channel_samples('TSS')) 
#wpisanie częstości Fs do obiektu
wbr_baseline.mgr.set_param('sampling_frequency', Fs)   
#przepróbkowanie z czynnikiem 2                         
wbr_baseline = wii_downsample_signal(wbr_baseline, factor=2, pre_filter=True, use_filtfilt=True) 
#odczytanie nowej częstości próbkowania fs
fs = float(wbr_baseline.mgr.get_param('sampling_frequency'))

Po takich operacjach dane przechowywane w obiekcie 'wbr_baseline' klasy WBBReadManager są gotowe do użycia. Klasa ta posiada metodę 'get_raw_signal', która zwraca 4-kanałową macierz z danymi z 4 czujników (są to kanały odpowiadające kolejno czujnikom: górny lewy-TL, górny prawy-TR, dolny prawy-BR, dolny lewy-BL). Można wyznaczyć wartości wychyleń w kierunkach x i y na podstawie informacji z czterech czujników (rys. 27).:

[math]x=\frac{(TR+BR)-(TL+BL)}{TR+TL+BL+BR}[/math]

[math]y=\frac{(TR+TL)-(BR+BL)}{TR+TL+BL+BR}[/math]

Należy pamiętać, że dane z czujników pochodzą z układu odniesienia deski Wii Board - zatem uzyskane wartości x i y mieszczą się w zakresie od -1 do 1. Aby uzyskać dane w cm trzeba przemnożyć współrzędne x i y przez odpowiednie czynniki:

Wii Balance Board z oznaczonymi płaszczyznami ML i AP. TR, TL, BR, BL ozanczają pozycje czterech czujników.

Jednak w przypadku niektórych zadań należy jeszcze, przed wyznaczaniem x i y, odpowiednio wyciąć interesujące nas dane względem znaczników. Czynność tę wykonuje funkcja 'wii_cut_fragments', która zwraca listę obiektów 'smart_tags'. Liczba tych obiektów odpowiada liczbie zdarzeń z danym znacznikiem (dla stania swobodnego lista będzie miała tylko jeden element, natomiast dla wielokrotnych wychyleń będzie ich kilka). Każdy element na tej liście, posiada metodę 'get_samples' zwracającą dane w macierzy 7 kanałowej. W pierwszych 4 kanałach znajdują się dane z czujników TL,TR,BR,BL.

smart_tags = wii_cut_fragments(wbr_baseline, start_tag_name='ss_start', end_tags_names=['ss_stop'])
TL = smart_tags[0].get_samples()[0,:]
TR = smart_tags[0].get_samples()[1,:]
BR = smart_tags[0].get_samples()[2,:]
BL = smart_tags[0].get_samples()[3,:]

Znaczniki 'start_tag_name' oraz 'end_tags_names' dla poszczególnych pomiarów:

  • stanie swobodne oczy otwarte: 'ss_start', 'ss_stop'
  • stanie swobodne oczy zamknięte: 'ss_oczy_start', 'ss_oczy_stop'
  • wychylenia szybkie bez informacji zwrotnej: 'szybkie_start', 'szybkie_stop'
  • wychylenia "z przytrzymaniem" bez informacji zwrotnej: 'start', 'stop'
  • stanie swobodne jako kalibracja do zadań z informacją zwrotną: 'ss_start', 'ss_stop'
  • wychylenia szybkie z informacją zwrotną: nie ma znaczników - ciągły zapis danych
  • wychylenia "z przytrzymaniem" z informacją zwrotną: 'start_1', 'finish'

Podczas wczytywania danych z informacją zwrotną interesujące nas informacje znajdują się w pliku z rozszerzeniem .game.tag. Zatem należy ustawić parametr 'tag_format' w wywołaniu funkcji read_file na 'game' (zamiast domyślnie ustawionego 'obci'). Znaczniki czasowe gry nie są zsynchronizowane z sygnałem, ponieważ gra uruchamiana jest w osobnym wątku, który nie ma możliwości komunikacji z multiplekserem. Z tego względu każdy znacznik zapisany w tym pliku posiada czas systemowy, który należy wyrównać względem pierwszej próbki w sygnale. Procedura ta została zaimplementowana w poniższej funkcji, którą należy wykonać przed przepróbkowaniem sygnału:

wbr_sway.mgr = analysis_helper.set_first_timestamp(wbr_sway.mgr)

Dalsze kroki przygotowania danych do analizy wykonujemy tak samo. Dla przypadku wychyleń "z przytrzymaniem" obiekty w liście zwróconej przez funkcję 'wii_cut_fragments' odpowiadają kolejnym realizacjom zadania. Mają one jednak nieco inną strukturę. Oprócz metody 'get_samples()', za pomocą której tak jak poprzednio wczytamy dane z 4 czujników, posiadają również metody:

  • 'get_end_tag()' - zwraca strukturę, która w polu ['desc']['type'] przechowuje informację o tym czy zadanie zostało wykonane poprawnie (0-niepoprawnie, 1-poprawnie)
  • 'get_start_tag()' - zwraca strukturę, która w polu ['desc']['type'] przechowuje informację o kierunku wychylenia (pole 'direction', wartości: 'up','down','left','right') oraz o poziomie trudności zadania (pole 'level'). Aby wydobyć informację o kierunku wychylenia (analogicznie dla poziomiu trudności) można skorzystać z funkcji: eval(smart_tags[index].get_start_tag()['desc']['type'])['direction']

Informacje o poziomie trudności, poprawności wykonania zadania oraz kierunku będą potrzebne do podjęcia decyzji, które próby wykonania zadania będą brane do dalszej analizy. Interesują nas jedynie te poprawne o najwyższym poziomie trudności w każdym z kierunków.

Analiza danych: stanie swobodne

W przypadku stania swobodnego z oczami zamkniętymi oraz otwartymi, studenci mają za zadanie wyznaczyć następujące wskaźniki posturograficzne:

  • położenie środka równowagi COP (center of posture)
  • maksymalne przemieszczenie względem położenia środka równowagi (w AP,ML oraz przestrzeni AP/ML),
  • długość drogi względem położenia środka równowagi (w AP,ML oraz przestrzeni AP/ML),
  • średnia prędkość przemieszczenia (w AP,ML oraz przestrzeni AP/ML),
  • wskaźnik Romberga - stosunek różnicy długości drogi przy oczach zamkniętych i otwartych, do sumy długości drogi przy oczach zamkniętych i otwartych

Dokładny opis matematyczny wyżej wymienionych wskaźników znajduje się w pracy (Prieto, 1996).

Należy również przedstawić na wykresie przebieg ruchu COP oddzielnie w płaszczyźnie AP, ML oraz w przestrzeni AP/ML.

Dodatkowo studenci mają za zadanie przeprowadzić analizę rozkładu przestrzennego punktów statokinezjogramu. Statokinezjogramem lub posturogramem nazywamy wędrówkę COP w dwuwymiarowej płaszczyźnie podparcia. Kierunki na tej płaszczyźnie określa się jako AP (y) lub ML (x), przy czym ML oznacza wychylenia w płaszczyźnie czołowej (medio-lateral), a AP w płaszczyźnie strzałkowej (anterio-posterior). W celu przeprowadzenie takiej analizy, cały zakres zostaje podzielony na jednakowe komórki. Następnie obliczony zostaje histogram przestrzenny czasu przebywania w każdej z nich. Taki histogram pozwala ocenić, czy kontrola położenia referencyjnego COG (center of gravity), a tym samym pionowa orientacja ciała, jest prawidłowa. Wyznacznikiem prawidłowej kontroli jest histogram o skupionym rozkładzie i z wyraźnym maksimum. Upośledzenie kontroli objawia się tym, że histogram przestrzenny staje się rozmyty lub wyraźnie niesymetryczny (Błaszczyk, 2004).

Analiza danych: wychylenia dynamiczne

Oprócz wskaźników statycznych stabilności do oceny kontroli posturalnej wykorzystuje się również miary dynamiczne. Z punktu widzenia miar bezpośrednich, istotna jest ocena kontroli środka ciężkości ciała w czasie jego świadomego przemieszczania w wyznaczonym kierunku. W ramach pomiarów, studenci mieli za zadanie wykonać dwa rodzaje wychyleń (szybkie i "z przytrzymaniem") w dwóch warunkach: bez informacji zwrotnej dla badanego oraz z informacją zwrotną dla badanego. Dla każdego z przypadków należy wyznaczyć następujące parametry:

  • położenie środka równowagi (dla warunku bez informacji zwrotnej będzie to COP ze stania swobodnego z oczami otwartymi, natomiast dla warunku z informacją zwrotną będzie to COP z sesji kalibracyjnej)
  • wartość maksymalnego wychylenia (w określonym kierunku) względem położenia równowagi,
  • wykresy składowych wychwiań względem położenia równowagi w płaszczyźnie AP, ML w zależności od czasu oraz wypadkowa trajektoria przemieszczeń COP (w dwuwymiarowej przestrzeni AP, ML).

i zbadać czy informacja zwrotna wpływa na rezultaty badanego.

Literatura:

  • Błaszczyk J., Biomechanika kliniczna, PZWL, 2004
  • Prieto T.E. et al., Measures of postural steadiness: differences between healthy young and elderly adults, IEEE Trans Biomed Eng, 1996, 43(9):956-66

Kamery 3D

Plan zajęć

Zajęcia 1:

  • Wstęp teoretyczny:
    • Budowa i zasada działania sensora Kinect.
    • Opis podstawowych bibliotek wykorzystywanych do komunikacji z urządzeniem.
    • Profesjonalne systemy do rejestracji ruchu
    • Zastosowania w biomechanice oraz rehabilitacji.
  • Zapoznanie się z działaniem sensora Kinect
  • Pomiary:
    • W ramach pomiarów studenci wykonają standardowy test wykorzystywany w medycynie sportowej do oceny prawdopodobieństwa urazów więzadła krzyżowego przedniego (ACL).

Media:3D_Kinect.pdf Informacje wstępne oraz opis zadań

Zajęcia 2,3,4:

  • Analiza zebranych danych
  • Zaprezentowanie uzyskanych wyników

Sensor Kinect

Śledzenie ruchów postaci ludzkiej w ogólności znajduje zastosowanie m.in. w analizie chodu, diagnostyce chorób związanych z układem ruchu człowieka, analizie ruchu sportowców, czy nawet w procesie animacji postaci na potrzeby produkcji filmów i gier. W tym celu wykorzystuje się głównie profesjonalne markerowe systemy śledzenia ruchu (marker-based motion capture systems) określane w skrócie jako systemy mocap. Systemy te wymagają zastosowania specjalistycznego sprzętu, a na ciele śledzonej postaci muszą zostać umieszczone odpowiednie markery, przez co rejestracja musi odbywać się w warunkach laboratoryjnych. Systemy te nie nadają się do śledzenia ruchu w czasie rzeczywistym, a przesłonięcie lub przemieszczenie markerów w trakcie ruchu może być przyczyną błędów. Z tych względów coraz większe zainteresowanie zyskują bezmarkerowe systemy śledzenia ruchu, które wykorzystują zaawansowane algorytmy analizy obrazu.

Sensor Kinect firmy Microsoft do śledzenia ruchu postaci wykorzystuje informacje z kamery głębokości. Znajduje on zastosowanie w różnego rodzaju systemach umożliwiających interakcję człowiek-komputer, ale zaczyna również budzić coraz większe zainteresowanie w dziedzinach związanych z biomechaniką i rehabilitacją. W tym celu konieczne jest jednak określenie dokładności pozycji estymowanych przy pomocy bibliotek obsługujących sensor. Problem ten został poruszony w pracy (Webster, 2014).


Sensor Kinect Xbox 360.


Budowa sensora Kinect Xbox 360 (rys. 2):

  • kamera wizyjna RGB (typu CMOS, o rozdzielczości 640x480) - przesyła serię obrazów z prędkością 30 klatek na sekundę,
  • kamera głębokości (typu CMOS, o rozdzielczości ~300x200) - zwraca informację o głębokości poprzez analizę zniekształconej przez obiekt wiązki promieni podczerwonych,
  • emiter podczerwieni - emituje wiązkę promieni podczerwonych,
  • 4 mikrofony kierunkowe - wykorzystywane przez funkcje rozpoznawania mowy,
  • napęd umożliwiający ruch głowicą z akcelerometrem.

Pomiary

Rejestracja danych odbywa się w środowisku OpenBCI. System ten uruchamiamy wykonując w terminalu polecenie:

$ obci_gui

Obsługa sensora wraz z algorytmami estymacji poszczególnych pozycji anatomicznych śledzonej postaci, możliwa jest dzięki bibliotekom OpenNI i NiTE. Scenariusz do rejestracji danych przy pomocy sensora Kinect pokazany został poniżej (rys. 3). Umożliwia on rejestrację w trybie online, i wówczas zarówno obraz RGB, jak mapa głębokości mogą zostać zapisane w domyślnym formacie .oni. Dodatkowo tworzony jest plik binarny .algs z metadanymi rejestracji, gdzie zapisywane są między innymi współrzędne wyestymowanych pozycji anatomicznych. Tryb offline umożliwa odtwarzanie plików .oni oraz .algs. W przypadku rejestracji w trybie online w scenariuszu zmieniamy następujące parametry:

      capture_raw = 1		        – zapis obrazu do pliku .oni (0 lub 1)
      out_raw_file_path = test.oni	– nazwa pliku .oni
      capture_hands = 1                 – zapis pozycji rąk (0 lub 1)
      capture_skeleton = 1		– zapis pozycji anatomicznych (0 lub 1)
      out_algs_file_path = test.algs	– nazwa pliku .algs z metadanymi rejestracji

Wybierając tryb offline należy podać odpowiednią ścieżkę do plików .oni oraz .algs:

      in_raw_file_path = test.oni
      in_algs_file_path = test.algs

Wszystkie pliki zapisywane są w lokalizacji: /home/.obci/sandbox

Scenariusz w OpenBCI do rejestracji danych przy pomocy Kinecta.

Zeskok z następującym wyskokiem pionowym (drop vertical jump, DVJ) należy do badań przesiewowych, pozwalających oszacować ryzyko występienia urazu (w szczególności więzadła krzyżowego przedniego u kobiet) lub określić efekty rehabilitacji. Przed rozpoczęciem skoku osoba badana stoi na platformie o wysokości ok. 30 cm. Ma ona za zadanie wykonać zeskok z platformy na ziemię, a następnie maksymalny skok pionowy w górę. W dalszej analizie interesujące będą momenty: kontaktu pięt z podłożem (initial contact, IC) zaraz po wykonaniu zeskoku oraz moment maksymalnego zgięcia kolan (peak flexion, PF) zaraz po IC, ale przed oderwaniem pięt od podłoża w celu wykonania skoku pionowego.

Analiza danych

Studenci zapoznają się z modułami (napisanymi w języku programowania Python) do wczytywania oraz wstępnego przetwarzania danych. Następnie samodzielnie wyznaczą następujące wskaźniki biomechaniczne (opisane w pracy (Stone et al., 2013), którą można znaleźć pod adresem: [1]):

  • knee valgus motion (KVM),
  • frontal plane knee angle (FPKA) w dwóch momentach (w trakcie kontaktu pięt z podłożem zaraz po wykonaniu zeskoku oraz w trakcie maksymalnego zgięcia kolan),
  • knee-to-ankle separation ration (KASR) w momencie PF,
  • zmiana w czasie średniej pozycji bioder.

Wyniki opracowane powinny zostać w formie tabeli, gdzie zestawione zostaną wartości KVM, FPKA, KASR otrzymane dla każdej osoby z grupy. Trajektoria średniej pozycji bioder podczas wykonywania zadania powinna zostać przedstawiona na wykresie.

Analiza danych

Przykładowy skrypt do wczytywania wyników algorytmów śledzenia pozycji anatomicznych (wraz z metadanymi rejestracji):

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import print_function

import sys
sys.path.append('/usr/share/openbci/drivers/kinect/')

from KinectUtils import Serialization

class KinectDataReader(object):
    def __init__(self, file_name):
        super(KinectDataReader, self).__init__()
        self.file_name = file_name
        self.in_algs_file = open(self.file_name + '.algs', 'rb')
        self._s = Serialization()

    def readNextFrame(self):
        return self._s.unserialize_frame(self.in_algs_file)

if __name__ == '__main__':
    try:
        file_name = sys.argv[1]
    except Exception:
        file_name = 'test'
    kinect = KinectDataReader(file_name)
    while True:
        frame = kinect.readNextFrame()
        if frame is None:
            print('END OF FILE')
            break

        frame_index = frame[0]
        hands_included = frame[1]
        skeleton_included = frame[2]
        frame_time = frame[3]

        if skeleton_included:
            skel = frame[8]
        else:
            skel = None

        if hands_included:
            hands = frame[9]
        else:
            hands = None
                    
        print('Idx:', frame_index, 'Time:', frame_time)
        if skel is not None:
            print('Skeleton: ', skel.user_x, skel.user_y, skel.user_z)

        if hands is not None:
            print('Hands: ', hands.hands[0].x, hands.hands[0].y, hands.hands[0].z)

Plik z metadanymi rejestracji zawiera: nagłówek, wyniki algorytmów śledzenia 15 pozycji anatomicznych, wyniki algorytmów śledzenia pozycji rąk (oddzielna opcja w scenariuszu). Pozycje anatomiczne sylwetki zapisane są w następującej kolejności:

   joints = [JOINT_HEAD,
             JOINT_NECK,
             JOINT_RIGHT_SHOULDER,
             JOINT_LEFT_SHOULDER,
             JOINT_RIGHT_ELBOW,
             JOINT_LEFT_ELBOW,
             JOINT_RIGHT_HAND,
             JOINT_LEFT_HAND,
             JOINT_TORSO,
             JOINT_RIGHT_HIP,
             JOINT_LEFT_HIP,
             JOINT_RIGHT_KNEE,
             JOINT_LEFT_KNEE,
             JOINT_RIGHT_FOOT,
             JOINT_LEFT_FOOT]

Ich położenie (x,y,z) wyrażone jest w jednostkach [mm].

Układ pozycji anatomicznych.

Literatura

  • Stone E.E., Butler M., McRuer A., Gray A., Marks J., Skubic M., Evaluation of the Microsoft Kinect for Screening ACL Injury, Conference proceedings: ... Annual International Conference of the IEEE Engineering in Medicine and Biology Society, 2013:4152-5, 2013.
  • Webster D., Celik O., Systematic review of Kinect applications in elderly care and stroke rehabilitation, Journal of NeuroEngineering and Rehabilitation, 11:108, 2014.

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: [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 1 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
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 jedynek pojawiała się linia wykrzykników
  • po naciśnięciu jednocześnie dwóch przycisków w terminalu zamiast dwóch jedynek i zera 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
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 zapalanie 6 diody, puszczanie 3 przycisku powodowało gaszenie 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
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
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
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 x 1 - 0 lub mnożnik x 2 - 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ł-1, 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
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_bcm2708', shell=True, stdout=subprocess.PIPE)
start_spi = subprocess.Popen('sudo modprobe spi_bcm2708', 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. Wartość zwracana przez przetwornik mieści się w zakresie 0-1023 co odpowiada wartościom napięcia z zakresu 0-3.3 V.


Schemat połączeń w teście przetwornika AD. Źródło: Gertboard User Maual

Tranzystor

Czujnik temperatury i wilgotności powietrza

Halometr