V tomto príklade sa pokúsime signálmi z akcelerometrov, ktoré bude posielať Arduino s príslušným senzorom, alebo mobilný telefón s príslušným softvérom na prenos cez sériovú linku. Budeme predpokladať, že data chodia dostatočne rýchlo v CSV (Comma Separated Values) formáte, kde každý riadok predstavuje jedno kompletné meranie a okrem samotných zrýchlení môže obsahovať aj iné merané veličiny, prípadne aj časové značky.
Príklad: accX, accY, accZ, time, dalsieVeliciny...
Na konci tohto príkladu je uvedené aj ako ho treba modifikovať v prípade, že nepoužívame real-time data, ale len data uložené v súbore offline. Preto je dôležité si uvedomiť, že nemôžete zobrať celý skript ako je, ale treba si ho naozaj poskladať na mieru pre svoje vlastné zariadenie.
Skript postupne poskladáme z nasledovných blokov. Najprv inicializujeme dôležité premenné. Napríklad pole Accelerometer
je dôležité inicializovať aj preto, aby sme nemali kratšie pole v prípade, že nejaký riadok neprijmeme kompletný.
clear all;
simulation_duration = 6; % kolko sekund ma program bezat
i = 0;
oldRoll = 0;
oldPitch = 0;
oldYaw = 0;
Accelerometer = [0,0,0,0];
Začneme tým, že si pripravíme kresliacu plochu - graf
clf
ax = axes('XLim',[-1.5 1.5],'YLim',[-1.5 1.5],'ZLim',[-1.5 1.5]);
view(3);
grid on;
axis equal; axis off;
Aby sme mohli prijímať live data, musíme si pripraviť a otvoriť príslušný sériový port. Na začiatku si pre istotu príkazom serialportlist("available")
vypíšeme zoznam dostupných portov, v nich nájdeme ten správny.
Pozn.: v tomto skripte už používame nový interface serialport
namiesto stareho serial
, podrobnosti a rozdiely sú opísané na stránke https://uk.mathworks.com/help/matlab/import_export/transition-your-code-to-serialport-interface.html
serialportlist("available")' % zoznam dostupnych portov
myDevice = serialport('COM24',9600); % cislo portu a prenosova rychlost (musi byt rovnaka ako na vysielaci)
configureTerminator(myDevice,"CR/LF"); % kazdy riadok je ukonceny tymito znakmi
flush(myDevice); % zahod nejake data ktore medzitym mohli prist
Teraz si vytvoríme nejaký 3D objekt, ktorým budeme pohybovať. Základné tvary, z ktorých ho môžeme vyskladať sú guľa, valec (z ktorého vieme spraviť aj kužeľ) a kocka (hranol). V tomto príklade vytvoríme z valca a kužeľa jednoduchú šípku, ostatné objekty sú zrejmé zo zakomentovanej časti kódu. Všetky objekty dohromady budú tvoriť objekt h
, ktorým budeme potom ďalej manipulovať.
Ak chcete zobraziť komplikovanejší objekt, pozrite sa na tento príklad s lietadlom Lockheed C-130: https://uk.mathworks.com/matlabcentral/fileexchange/47967-draw-a-3d-airplane
Na jeho natáčanie však treba použiť priamo argumenty funkcie c130()
podľa uvedeného návodu. Asi by mala existovať aj možnosť načítať a zobraziť .obj
súbory, ale nepodarilo sa nám ju správne implementovať.
% objekt h(1) je valec s priemerom 0,4
[cylx,cyly,cylz]=cylinder(.4);
h(1)=surface(cylx,cyly,cylz+1);
% objekt h(2) je tie6 valec, ktorý má pri základni priemer 0,5 a pri vrchole 0
[conx,cony,conz]=cylinder([.5,0]);
h(2)=surface(conx,cony,conz+2);
% Objekty h(1) a h(2) spolu tvoria sipku
%% h(3) je gula s priemerom 25
% [sphx,sphy,sphz]=sphere(25);
% h(3)=surface(sphx,sphy,sphz);
%% h(4) je kocka, postup na kreslenie kocky aj s vysvetlivkami
%% pozri https://uk.mathworks.com/help/matlab/visualize/multifaceted-patches.html
% vert = [0 0 0;1 0 0;1 1 0;0 1 0;0 0 1;1 0 1;1 1 1;0 1 1];
% fac = [1 2 6 5;2 3 7 6;3 4 8 7;4 1 5 8;1 2 3 4;5 6 7 8];
% h(4) = patch('Vertices',vert,'Faces',fac,'FaceVertexCData',hsv(6),'FaceColor','flat');
V nasledovnom cykle, ktorý bude bežať tak dlho ako nastavíme do premennej simulation_duration
prebehne všetko potrebné:
readline()
strsplit(data,',')
str2double()
NaN - Not a Number
, pokračujeme výpočtom natočení roll
, pitch
a yaw
(to je úloha študentov, z kódu ju vynechávame).h
podľa vypočítaných parametrovtic; % zmeriame, aký je teraz čas
while( toc < simulation_duration) % zastavíme po "simulation duration"
dataReceived = readline(myDevice); % prečítame jeden riadok dát
dataSplitted = strsplit(dataReceived,','); % rozparsujeme ich (comma separated values)
Accelerometer = str2double(dataSplitted); % a prevedieme na reálne čísla (double)
i = i+1; % pomocne pocitadlo cyklov
Accelerometer(4) = i; % sluzi zaroven ako ochrana ak nejake cislo vypadne,
% aby vzdy existovali aj Accelerometer[1,2,3]
if not(any(isnan(Accelerometer))) % kontrola, ci su vsetko riadne cisla
accX = Accelerometer(1);
accY = Accelerometer(2);
accZ = Accelerometer(3);
roll = accX; % tento vypocet treba nahradit poriadnym a prepocitat na stupne (deg)
pitch = accY;
yaw = accZ;
rotate(h,[0 1 0],roll);
rotate(h,[1 0 0],pitch);
rotate(h,[0 0 1],yaw);
end
end
Po skončení treba ešte korektne zatvoriť port. V prípade, že program niekde havaruje, je tiež vhodné túto premennú zrušiť.
clear myDevice;
No a to je všetko.
Ak nemáme možnosť pracovať s datami v reálnom čase, môžeme si ich nahrať na mobile do .csv
súboru a pracovať s ním ďalej. Potrebné sú nasledovné zmeny:
T = readtable('myData.csv');
pričom nám vznikne objekt typy tabuľka, napriklad tie moje data vyzerajú takto:
1×4 table
Accelerometer_0 Accelerometer_1 Accelerometer_2 Accelerometer_timeStamp
_______________ _______________ ______________ _________________
0.081405 4.6856 8.6194 4.5337e+09
... a dalsich 6000 riadkov
Po načítaní dát spravíme podobný cyklus, ale namiesto sledovania času cez tic
a toc
spravíme cyklus cez všetky data zo súboru
for i = 1: size(T,1) % toto rob az do konca suboru
Data už nemusíme parsovať a konvertovať, to sa stalo automaticky po načítaní tabuľky. Takže si len vložíme hodnoty z aktuálneho riadku tabuľky do príslušných premenných a beh programu nachvíľu pozastavíme, aby sa to nevykreslilo príliš rýchlo. Konkrétny čas vo vašom prípade záleží na perióde vzorkovania pôvodného zdroja signálu, teda ako často ste data do súboru ukladali. Všimnite si tiež, že data z tabuľky T
vyberáme podľa názvov, ktoré sú v hlavičke.
accX = T.Accelerometer_0(i); % i je index z for cyklu
accY = T.Accelerometer_1(i);
accZ = T.Accelerometer_2(i);
pause(0.01); % moje data boli vzorkovane po 10 ms (=0,01 s)
Všetko ostatné v kóde ostáva nezmenené, samozrejme však treba vyhodiť príkazy pre sériový port, ktorý nevyužívame.
8.4.2022