Krokomer s akcelerometrom MMA8491Q: Rozdiel medzi revíziami
Zo stránky SensorWiki
(26 medziľahlých úprav od rovnakého používateľa nie je zobrazených.) | |||
Riadok 196: | Riadok 196: | ||
Keďže senzor MMA8491Q vysiela dáta o zrýchlení cez zbernicu I2C ako riadiacu jednotku musíme zvoliť mikropočítač, ktorý podporuje takúto komunikáciu. Arduino Uno poskytuje jednoduchý spôsob pripojenia pre komunikáciu cez I2C zbernicu s Xtrinsic senzorovou doskou a taktiež jej poskytuje potrebné napájanie (3.3 V). Napájanie a komunikácia riadiacej jednotky je zabezpečená z PC cez USB rozhranie. | Keďže senzor MMA8491Q vysiela dáta o zrýchlení cez zbernicu I2C ako riadiacu jednotku musíme zvoliť mikropočítač, ktorý podporuje takúto komunikáciu. Arduino Uno poskytuje jednoduchý spôsob pripojenia pre komunikáciu cez I2C zbernicu s Xtrinsic senzorovou doskou a taktiež jej poskytuje potrebné napájanie (3.3 V). Napájanie a komunikácia riadiacej jednotky je zabezpečená z PC cez USB rozhranie. | ||
[[Súbor:Uno.png]] | [[Súbor:Uno.png|center]] | ||
Viac na [https://www.arduino.cc/en/main/arduinoBoardUno Arduino Uno] | Viac na [https://www.arduino.cc/en/main/arduinoBoardUno Arduino Uno] | ||
Riadok 265: | Riadok 265: | ||
Pri návrhu funkcionality krokomeru budeme vychádzať z článku [http://www.analog.com/media/en/technical-documentation/technical-articles/pedometer.pdf Full-Featured Pedometer Design Realized with 3-Axis Digital Accelerometer], ktorý publikoval ''Neil Zhao''. Článok popisuje komplexný krokomer pracujúci v troch osiach, ktorý okrem počtu prejdených krokov poskytuje informácie o vzdialenosti, rýchlosti a spálených kalóriách. Pre naše účely budeme uvažovať len zjednodušený model krokomeru pracujúci v jednej osi, ktorej sa zrýchlenie mení najviac. | Pri návrhu funkcionality krokomeru budeme vychádzať z článku [http://www.analog.com/media/en/technical-documentation/technical-articles/pedometer.pdf Full-Featured Pedometer Design Realized with 3-Axis Digital Accelerometer], ktorý publikoval ''Neil Zhao''. Článok popisuje komplexný krokomer pracujúci v troch osiach, ktorý okrem počtu prejdených krokov poskytuje informácie o vzdialenosti, rýchlosti a spálených kalóriách. Pre naše účely budeme uvažovať len zjednodušený model krokomeru pracujúci v jednej osi, ktorej sa zrýchlenie mení najviac. | ||
Na nasledovnom obrázku je zobrazená závislosť medzi jednotlivými fázami ľudského kroku a zmenami vertikálneho zrýchlenia a zrýchlenia v smere pohybu. | Na nasledovnom obrázku je zobrazená závislosť medzi jednotlivými fázami ľudského kroku a zmenami vertikálneho zrýchlenia a zrýchlenia v smere pohybu. | ||
[[Súbor:Krok.png]] | |||
[[Súbor:Krok.png|center]] | |||
Tieto zmeny v zrýchlení môžeme vyniesť do grafu.Pre bežiacu osobu bude priebeh jednotlivých zložiek zrýchlenia (x, y a z) vyzerať nasledovne | Tieto zmeny v zrýchlení môžeme vyniesť do grafu.Pre bežiacu osobu bude priebeh jednotlivých zložiek zrýchlenia (x, y a z) vyzerať nasledovne | ||
[[Súbor:behAkc.png]] | |||
[[Súbor:behAkc.png|center]] | |||
Vidíme, že aspoň v jednej osi má zrýchlenie relatívne veľké a periodické výchylky. Výchylky však nie sú rovnaké a s časom menia, preto je potrebné dynamicky detegovať hraničné hodnoty (max, min) a upravovať rozhodovaciu úroveň algoritmu. Okrem toho je potrebná filtrácia, pretože pri pohybe človeka (tým pádom aj senzora) budú mať príjmané dáta z akcelerometra vysokú šumovú úroveň. Obrázok nižšie znázorňuje filtrované dáta z najaktívnejšej osi akcelerometra pre kráčajúcu osobu. | Vidíme, že aspoň v jednej osi má zrýchlenie relatívne veľké a periodické výchylky. Výchylky však nie sú rovnaké a s časom menia, preto je potrebné dynamicky detegovať hraničné hodnoty (max, min) a upravovať rozhodovaciu úroveň algoritmu. Okrem toho je potrebná filtrácia, pretože pri pohybe človeka (tým pádom aj senzora) budú mať príjmané dáta z akcelerometra vysokú šumovú úroveň. Obrázok nižšie znázorňuje filtrované dáta z najaktívnejšej osi akcelerometra pre kráčajúcu osobu. | ||
[[Súbor:WalkAkc.png|center]] | |||
Pre zabezpečenie dynamických zmien porovnávacej úrovne musí systém periodicky aktualizovať maximálne a minimálne hodnoty zrýchlenia. Dynamická rozhodovacia úroveň sa potom vypočíta ako (Max + Min)/2. Pre nasledujúcu sadu vzoriek (jednu periódu) bude táto hodnota použitá na určenie či došlo ku kroku. Na vyhodnotenie prítomnosti kroku sa používa lineárny posuvný register s dvomi registrami ''sample_old'' a ''sample_new''. Pri príchode nových dát je hodnota v ''sample_new'' bezpodmienečne posunutá do registra ''sample_old''. Zápis nových dát do registra ''sample_new'' však závisí nastavenej rozlišovacej schopnosti ''precision''. Ak je zmena v zrýchlení (sample_new - nové dáta) väčšia ako ''precision'' potom sú nové dáta zapísané do registra ''sample_new''. V opačnom prípade ostáva register ''sample_new'' bez zmeny. '''Uskutočnenie kroku je potom definované negatívnym sklonom priebehu zrýchlenia (''sample_new'' < ''sample_old'') keď hodnota zrýchlenia klesne pod dynamickú porovnávaciu úroveň.''' | |||
=== Vizualizácia riešenia === | |||
Dalej v tejto časti popíšeme programovacie prostredie C#. Rozhodli sme sa ho použiť pre jeho dostupnosť a jednoduchosť. Pri použití javy sa k COM portom pristupuje zložitejšie celkový návrh vizualizácie je zložitejší, kým v C# sa jedná vo veľkej časti o drag & drop metódu. | Dalej v tejto časti popíšeme programovacie prostredie C#. Rozhodli sme sa ho použiť pre jeho dostupnosť a jednoduchosť. Pri použití javy sa k COM portom pristupuje zložitejšie celkový návrh vizualizácie je zložitejší, kým v C# sa jedná vo veľkej časti o drag & drop metódu. | ||
Riadok 285: | Riadok 287: | ||
Jednoduchý návod na vytváranie Form aplikácí v C# nájdete tu: https://msdn.microsoft.com/en-us/library/360kwx3z(v=vs.90).aspx | Jednoduchý návod na vytváranie Form aplikácí v C# nájdete tu: https://msdn.microsoft.com/en-us/library/360kwx3z(v=vs.90).aspx | ||
[[Súbor:13235776_1214371305242661_1686152704_n.jpg|center]] | |||
== Popis riešenia == | == Popis riešenia == | ||
Prvým krokom pri riešení nášho zadania bolo napísať program, ktorý by bol schopný vyčítať údaje zo spomínaného senzora MMA8491Q, aby sme s nimi mohli ďalej pracovať. Tento krok si vyžadoval preštudovanie dokumentácie senzora kde sme sa zamerali na spôsob pripojenia a komunikácie, štruktúru registrov a reprezentáciu zosnímaných dát. Po naštudovaní potrebných informácii sme pristúpili k pripojeniu senzora k riadiacej jednotke. Použili sme už vyššie spomínaný mikroprocesor Arduino Uno. Kedže komunikácia so senzorom prebieha prostredníctvom I2C zbernice naštudovali sme si spôsob zapojenia senzora k I2C zbernici. Základná schéma zapojenia vyzerá nasledovne | |||
[[Súbor:SchemZap.png|center]] | |||
''' | Aby akcelerometer správne fungoval je potrebné aby bol zapojený týmto spôsobom | ||
* Vnútorný napäťový regulátor si vyžaduje pripojenie kondenzátora na ''Byp'' pin. Odporúča sa 0,1 μF kondenzátor. | |||
* Zariadenie je napájané cez V_DD linku. Čo najbližšie k V_DD pinu musí byť pripojený kondenzátor na odstránenie väzby napájania. | |||
** Ak V_DD a EN piny nie sú spojené je vhodné použiť 1,0 alebo 4,7 μF kondenzátor. | |||
** Naopak ak sú piny V_DD a EN spojené je treba použiť 0,1 μF kondenzátor. Tento kondenzátor spôsobí minimalizáciu spotreby a zachovanie prípustnej úrovne vysokofrekvenčnej filtrácie napájania. | |||
* Obidva zemniace piny musia byť pripojené na zem. | |||
* Ak sa používa I2C komunikácia je potrebné použiť ''pull-up'' odpory na pripojenie SDA a SCL liniek. Ak sa I2C komunikácia nepoužíva piny SDA a SCL by mali byť pripojené na zem. | |||
Po preskúmaní schém zapojenia Xtrinsic senzorovej dosky sme zistili, že všetky vyššie spomínané súčiastky potrebné pre správnu činnosť senzora MMA8491Q sa na doske už nachádzajú. Preto sme pristúpili priamo s pripojeniu senzora s mikropočítačom Arduino pričom je potrebné brať do úvahy nasledovné rozloženie vývodov na Xtrinsic senzorovej doske. | |||
[[Súbor:XtrinDoska.png|center]] | |||
[ | Pri práci s akcelerometrom sme nepotrebovali všetky vývody Xtrinsic dosky. Potrebné vývody sú zvýraznené na obrázku vyššie a jedná sa o piny pre napájanie (3V3 a GND), linky pre I2C komunikáciu (SDA, SCL) a povoľovací pin EN. Porovnaním rozloženia pinov Xtrinsic dosky a mikropočítača Arduino Uno (obrázok vyššie v časti ''Analýza'') vidíme, že umiestnenie potrebných pinov si vzájomne odpovedá a preto môžeme senzorovú dosku Xtrinsic priamo pripojiť v Arduino Uno bez potreby ďalšej kabeláže. Pin na ktorom je pripojený EN vývod Xtrinsic dosky (pin 8) sme použili ako GPIO pin pre ovládanie činnosti akcelerometra. | ||
Po úspešnom pripojení sme v softvérovom prostredí pre Arduino napísali program pre komunikáciu so senzorom, vyčítanie dát a ich konverziu z dvojkového doplnku na hodnotu v "g". Využili sme pritom vzorový kód pre podobný akcelerometer MMA8452Q (kód je dostupný [https://learn.sparkfun.com/tutorials/mma8452q-accelerometer-breakout-hookup-guide/example-code tu]), ktorý sa od nášho akcelerometra v určitých aspektoch líši. V kóde sme zmenili tieto nezrovnalosti (iná adresa, dĺžka dátových registrov, atď.) tak aby kód korešpondoval s naším snímačom. | |||
Následne sme pristúpili k návrhu funkcionality na základe článku popísanom v sekcii ''Analýza - Popis funkcionality krokomeru''. Za relevantnú os pri meraní zrýchlenia sme považovali os x. Podľa postupu sme najskôr pre merané dáta vytvorili posuvný filter dĺžky tri, ktorý pre dáta v osi x dostatočne odstránil šum. Dynamická porovnávacia úroveň (''threshold'') sa aktualizuje každých 10 vzoriek na základe dosiaľ nameraných hodnôt maximálneho a minimálneho zrýchlenia (''threshMax, threshMin''). Pri aktualizácii sa obidve tieto hodnoty nastavia na aktuálnu meranú hodnotu aby sa zabezpečilo čo najrýchlejšie nájdenie nový min. a max. hodnôt v okolí aktuálne meraných vzoriek. Posuvný register je simulovaný dvoma premennými ''sampleNew'' a ''sampleOld'', ktoré prislúchajú jednotlivým registrom. Rozlišovacia schopnosť krokomeru ''precision'' je nastavená na pevnú hodnotu pre nami zvolenú konfiguráciu snímača (orientácia, umiestnenie na tele). Pri zmene konfigurácie je potrebné túto hodnotu na základe meraných dát zmeniť tak aby mal krokomer požadovanú citlivosť pri detekcii uskutočnenia kroku. Detekcia kroku je realizovaná presne podľa úvahy uvedenej v ''Analýza - Popis funkcionality krokomeru'' (časť zvýraznená hrubým písmom). Celý program je detailnejšie popísaný v sekcii nižšie. | |||
Posledným krokom bolo vytvorenie aplikácie na vizualizáciu a demonštráciu realizovaného riešenia. Aplikácia je popísaná nižšie. | |||
=== Algoritmus a program === | === Algoritmus a program === | ||
==== Program pre mikropočítač ==== | |||
Program pre mikropočítač sme vyvíjali v prostredí Arduino. Program zahŕňa komunikáciu so senzorom, načítanie a konverziu dát, úprav dát a ich použitie na realizáciu funkcionality krokomeru. V programe sú použité nasledovné premenné | |||
<source lang="c"> | |||
MMA8452Q accel(0x55); // vytvorí inštanciu triedy akcelerometer s adresou zariadenia 0x55 | |||
// Filter dat | |||
int numReadingsX = 3; // veľkosť filtra (počet hodnôt) | |||
int filterStartX; // indikácia začiatku filtrovania pre rýchleší štart (aby na začiatku neobsahoval iba nuly) | |||
int readIndexX = 0; // index pre iteráciu cez filter | |||
float readingsX[3]; // pole pre hodnoty vo filtri | |||
float totalX = 0; // suma všetkých hodnôt vo filtri | |||
float averageX = 0; // priemer z hodnôt vo filtri | |||
// Krokomer | |||
int krokCount = 0; // celkový počet krokov | |||
int threshUpdateRate = 10; // interval aktualizácie threshold-u | |||
int threshUpdateCount = 9; // počítadlo pre aktualizáciu threshold-u | |||
float threshMax = 0; // horná hranica - max. hodnota zrýchlenia | |||
float threshMin = 0; // dolná hranica - min. hodnota zrýchlenia | |||
float threshold = 0; // rozhodovacia úroveň | |||
float sampleOld = 0; // sample_old register | |||
float sampleNew = 0; // sample_new register | |||
float precision = 0.01; // rozlišovacia úroveň - citlivosť rozoznania kroku | |||
// Tok programu | |||
int unoKrok = 1; // zamedzenie započítaniu jedného kroku viackrát | |||
</source> | |||
Premenná ''accel'' je triedy ''MMA8452Q'', ktorá reprezentuje akcelerometer a obsahuje ďalšie premenné a metódy. Ako sme spomínali vyššie merané dáta sú pred použitím najskôr filtrované posuvným filtrom. Tento filter si môžme predstaviť ako pole konštantnej dĺžky, ktoré je vždy naplnené "n" najaktuálnejšími hodnotami meranej veličiny, kde n je veľkosť poľa. Z hodnôt v poli sa následne počíta ich priemer a ten sa považuje za aktuálnu filtrovanú hodnotu. Pri príchode novej meranej hodnoty sa z poľa odstráni hodnota, ktorá je na jeho konci (bola v poli najdlhšie) a na jej miesto sa zapíše nová hodnota. Pri prvom spustení filtra sa filter celý naplní prvou nameranou hodnotou aby sa predišlo delenie (0....0+newData)/n a filtrovaná hodnota čo najviac približovala meranej hodnote a nie nule. Funkcia takéhoto filtra vyzerá nasledovne | |||
<source lang="c"> | |||
float filterDataX(float newData) // posuvný filter dát v x - osi | |||
{ | |||
if (filterStartX == 1) // pri prvom spustení naplň filter aktuálnou hodnotou aby sa predišlo (0...0+newData)/30 | |||
{ | |||
for (readIndexX = 0; readIndexX < numReadingsX; readIndexX++) | |||
{ | |||
readingsX[readIndexX] = newData; | |||
totalX = totalX + readingsX[readIndexX]; | |||
} | |||
filterStartX = 0; | |||
}else | |||
{ | |||
// odpočítaj hodnotu na konci filtra | |||
totalX = totalX - readingsX[readIndexX]; | |||
// namiesto nej vlož novú hodnotu | |||
readingsX[readIndexX] = newData; | |||
// pričítaj novú hodnotu k celkovému súčtu | |||
totalX = totalX + readingsX[readIndexX]; | |||
} | |||
// posun na ďalšie miesto vo filtri | |||
readIndexX = readIndexX + 1; | |||
if (readIndexX >= numReadingsX) // ak sme prešli cez celý filter vráť sa na začiatok | |||
{ | |||
readIndexX = 0; | |||
} | |||
// vypočítaj priemer | |||
return averageX = totalX / numReadingsX; | |||
} | |||
</source> | |||
V ''setup()'' funkcii programu sa inicializuje sériová linka, nastaví sa mód pinu EN, ktorý používame pre prácu so senzorom, inicializujú sa všetky filtre dát a samotný akcelerometer. Funkcia ''accel.init()'' iba nastaví premennú udávajúcu rozsah akcelerometra a inicializuje ''Wire'' knižnicu príkazom ''Wire.begin()''. | |||
<source lang="c"> | |||
void setup() | |||
{ | |||
Serial.begin(115200); // nastavenie sériovej linky | |||
pinMode(ENpin, OUTPUT); // nastavenie EN pinu senzora (pin 8 na Arduino Uno) | |||
filterStartX = 1; // inicializácia filtrov (indikácia prvého spustenia) | |||
filterStartY = 1; | |||
filterStartZ = 1; | |||
if (accel.init(SCALE_8G) == 1){ // inicializácia akcelrometra | |||
Serial.println("Wire nastart!"); | |||
} | |||
for (int readingIndex = 0; readingIndex < numReadingsX; readingIndex++){ //vynulovanie hodnôt filtrov | |||
readingsX[readingIndex] = 0; | |||
} | |||
for (int readingIndex = 0; readingIndex < numReadingsY; readingIndex++){ | |||
readingsY[readingIndex] = 0; | |||
readingsZ[readingIndex] = 0; | |||
} | |||
} | |||
</source> | |||
V ''loop()'' funkcii sa potom kontinuláne vykonáva meranie nových dát z akcelerometra, a vykonanie funkcie samotného krokomeru. Nastavovanie a nulovanie pinu 8 (EN) slúži na zmenu módu senzora (viď. ''Analýza''). | |||
<source lang="c"> | |||
void loop() | |||
{ | |||
digitalWrite(ENpin, HIGH); // prepnutie sezora do ACTIVE módu | |||
delay(10); // delay potrebný pre ustálenie konverziu porovnanie a zápis hodnôt v senzore (hodnota je prehnane veľká) | |||
if (accel.available()) // ak sú dáta pripravené na čítanie | |||
{ | |||
accel.read(); // načítaj dáta do premenných objektu accel (x, y, z - bez konverzie, cx, cy, cz - v jednotkách "g") | |||
accel.cy = filterDataY(accel.cy); // filtrovanie dát | |||
accel.cz = filterDataZ(accel.cz); | |||
krokomer(accel.cx); // spusti krokomer pre hodnoty z osi x | |||
digitalWrite(ENpin, LOW); // návrat do SHUTDOWN módu | |||
delay(5); // oneskorenie potrebné medzi jednotlivými meraniami | |||
} | |||
} | |||
</source> | |||
Funkcia ''.read()'' objektu ''accel'' zabezpečuje načítanie meraných dát do premenných (''accel.x, accel.y, accel.z'') a konverziu meraných dát na jednotky "g" a ich uloženie do premenných ''acel.cx'', ''accel.cy'' a ''accel.cz''. | |||
<source lang="c"> | |||
void MMA8452Q::read() | |||
{ | |||
byte rawData[6]; | |||
readRegisters(OUT_X_MSB, rawData, 6); // načítaj 6 registrov "raw" dát od adresy OUT_X_MSB | |||
x = (rawData[0]<<8 | rawData[1]) >> 2; // z registrov vyskladaj celé 14 -bit hodnoty | |||
y = (rawData[2]<<8 | rawData[3]) >> 2; | |||
z = (rawData[4]<<8 | rawData[5]) >> 2; | |||
cx = (float) x / (float)(1<<13) * (float)(scale); // konverzia z dvojkového doplnku na jednotky "g" | |||
cy = (float) y / (float)(1<<13) * (float)(scale); | |||
cz = (float) z / (float)(1<<13) * (float)(scale); | |||
} | |||
</source> | |||
Funkcia ''krokomer()'' najskôr vykoná filtráciu nameraných dát pomocou vyššie uvedenej funkcie ''filterDataX()''. Následne skontroluje či je rozdiel v zrýchlení väčší ako rozlišovacia úroveň krokomeru. Rozlišovacia úroveň slúži na odrušenie šumu a malých otrasov, ktoré nepredstavujú uskutočnenie kroku a spôsobili by chybné zaznamenanie. Následne sa vykoná funkcia zabezpečujúca prácu z rozhodovacou úrovňou ''thresholdFire()''. Potom sa uskutoční posunutie hodnôt v LSR (''sample_new'' --> ''sample_old'') a zápis novej hodnoty do registra ''sample_new''. Ďalej sa skontroluje či nedošlo nárastu hodnôt čo slúži na resetovanie premennej ''unoKrok'', ktorá je použitá na zamedzenie viacnásobného započítania toho istého kroku. V poslednej podmienke sa skontroluje či je nová vzorka pod rozhodovacou úrovňou a zároveň má krivka zrýchlenia klesajúci charakter a premenná ''unoKrok'' je nastavená. Všetky tieto podmienky zaručujú, že bol vykonaný krok. Funkcia ''krokomerPrint()'' iba odošle informáciu o uskutočnení kroku na sériovú linku. | |||
<source lang="c"> | |||
{ | |||
merData = filterDataX(merData); // filtrovanie dát | |||
if (abs(merData - sampleNew) >= precision) // ak je rozdiel v zrýchlení väčší ako rozlišovacia úroveň | |||
{ | |||
thresholdFire(merData); // spusti rutinu pre aktualizáciu rozhodovacej úrovne | |||
sampleOld = sampleNew; // posun LSR | |||
sampleNew = merData; // zápis nových dát do registra sample_new | |||
if ((sampleNew > sampleOld)&&(unoKrok == 0)){ // ak dôjde k nárastu hodnôt po detekcii kroku resetuj unoKrok | |||
unoKrok = 1; | |||
} | |||
if ((sampleNew < threshold)&&(sampleNew < sampleOld)&&(unoKrok == 1)) // ak je detekovaný krok | |||
{ | |||
krokCount++; // inkrementuj počet krokov | |||
unoKrok = 0; // nastav unoKrok aby sa zabránilo viacnásobnému započítaniu jedného kroku | |||
krokomerPrint(); // vypíš že nastal krok na sériovú linku | |||
} | |||
}else{ // ak je zmena v zrýchlení < precision | |||
//Serial.println("prilis mala zmena"); | |||
return; | |||
} | |||
} | |||
</source> | |||
Funkcia ''thresholdFire()'' zabezpečuje aktualizáciu hodnôt min. a max. zrýchlenia a testovanie či nie je potrebné aktualizovať rozhodovaciu úroveň. | |||
<source lang="c"> | |||
void thresholdFire(float newData) // funkcia na aktualizáciu min a max. hodnôt rozhodovacej úrovne + test či netreba aktualizovať threshold | |||
{ | |||
if (threshUpdateCount < threshUpdateRate) // ak ešte nie je potrebná aktualizácia rozhodovacej úrovne | |||
{ | |||
if (newData <= threshMin){ // aktualizuj hodnotu min. a max zrýchlenia | |||
threshMin = newData; | |||
}else if(newData >= threshMax){ | |||
threshMax = newData; | |||
} | |||
threshUpdateCount++; // pripočítaj k počítadlu aktualizácie | |||
}else{ | |||
threshUpdate(newData); // ak je potrebná aktualizácia rozhodovacej úrovne - vykonaj aktualizáciu | |||
} | |||
} | |||
</source> | |||
Volanie funkcie 'threshUpdate()'' vykoná samotnú aktualizáciu rozhodovacej úrovne a resetuje hodnoty min. a max. zrýchlenia. | |||
<source lang="c"> | |||
void threshUpdate(float newData) // funkcia na aktualizáciu rozhodovacej úrovne | |||
{ | |||
threshold = (threshMax + threshMin)/2; // výpočet rozhodovacej úrovne | |||
threshUpdateCount = 0; // reset počítadla pre aktualizáciu | |||
threshMin = newData; // nastav min. a max. na aktuálnu hodnotu (aby sa našli nové min a max. v okolí aktuálne meraných dát) | |||
threshMax = newData; | |||
} | |||
</source> | |||
Zdrojové kódy: [[Médiá:MMA8491Q_Main.ino| MMA8491Q_Main]], [[Médiá:SFE_MMA8452Q.h| SFE_MMA8452Q.h ]], [[Médiá:SFE_MMA8452Q.cpp| SFE_MMA8452Q.cpp]] | |||
==== Aplikácia pre vizualizáciu ==== | |||
Vizualizáciu sme sa rozhodli spraviť v prostredí C#. Naše GUI pozostáva z troch častí. Prvou sú nastavenia baudrateu a výber portov. Druhou časťou je indikátor počtu vykonaných krokov a treťou časťou je vizuálne zobrazenie topánky ktorá vykoná pohyb keď program zaregistruje krok. | Vizualizáciu sme sa rozhodli spraviť v prostredí C#. Naše GUI pozostáva z troch častí. Prvou sú nastavenia baudrateu a výber portov. Druhou časťou je indikátor počtu vykonaných krokov a treťou časťou je vizuálne zobrazenie topánky ktorá vykoná pohyb keď program zaregistruje krok. | ||
Baudrate sa vyberá za pomoci comboboxu. Po zakliknutí program porovnáva "tabuľkové" baudratei so zvoleným a keď nájde zhodu, tak ho nastaví na poŽadovanú hodnotu. S COM portmi je to trochu inak. Program jednoducho oskenuje prístupné porty na zariadení a taktiež pomocou komboboxu nás nechá zvoliť aký chceme. | |||
<source lang="c"> | |||
public string PortName //getre, setre baud rateu a porov | |||
{ | |||
get { return _portName; } | |||
set | |||
{ | |||
if (!_portName.Equals(value)) | |||
{ | |||
_portName = value; | |||
SendPropertyChangedEvent("PortName"); | |||
} | |||
} | |||
} | |||
public int BaudRate | |||
{ | |||
get { return _baudRate; } | |||
set | |||
{ | |||
if (_baudRate != value) | |||
{ | |||
_baudRate = value; | |||
SendPropertyChangedEvent("BaudRate"); | |||
} | |||
} | |||
} | |||
</source> | |||
<source lang="c"> | |||
public void UpdateBaudRateCollection(int possibleBaudRates) //vyššie opísaná tabuľka baudrateov a spôsob ich výberu | |||
{ | |||
const int BAUD_075 = 0x00000001; | |||
const int BAUD_110 = 0x00000002; | |||
const int BAUD_150 = 0x00000008; | |||
const int BAUD_300 = 0x00000010; | |||
const int BAUD_600 = 0x00000020; | |||
const int BAUD_1200 = 0x00000040; | |||
const int BAUD_1800 = 0x00000080; | |||
const int BAUD_2400 = 0x00000100; | |||
const int BAUD_4800 = 0x00000200; | |||
const int BAUD_7200 = 0x00000400; | |||
const int BAUD_9600 = 0x00000800; | |||
const int BAUD_14400 = 0x00001000; | |||
const int BAUD_19200 = 0x00002000; | |||
const int BAUD_38400 = 0x00004000; | |||
const int BAUD_56K = 0x00008000; | |||
const int BAUD_57600 = 0x00040000; | |||
const int BAUD_115200 = 0x00020000; | |||
const int BAUD_128K = 0x00010000; | |||
_baudRateCollection.Clear(); | |||
if ((possibleBaudRates & BAUD_115200) > 0) | |||
_baudRateCollection.Add(115200); | |||
if ((possibleBaudRates & BAUD_075) > 0) | |||
_baudRateCollection.Add(75); | |||
if ((possibleBaudRates & BAUD_110) > 0) | |||
_baudRateCollection.Add(110); | |||
if ((possibleBaudRates & BAUD_150) > 0) | |||
_baudRateCollection.Add(150); | |||
if ((possibleBaudRates & BAUD_300) > 0) | |||
_baudRateCollection.Add(300); | |||
if ((possibleBaudRates & BAUD_600) > 0) | |||
_baudRateCollection.Add(600); | |||
if ((possibleBaudRates & BAUD_1200) > 0) | |||
_baudRateCollection.Add(1200); | |||
if ((possibleBaudRates & BAUD_1800) > 0) | |||
_baudRateCollection.Add(1800); | |||
if ((possibleBaudRates & BAUD_2400) > 0) | |||
_baudRateCollection.Add(2400); | |||
if ((possibleBaudRates & BAUD_4800) > 0) | |||
_baudRateCollection.Add(4800); | |||
if ((possibleBaudRates & BAUD_7200) > 0) | |||
_baudRateCollection.Add(7200); | |||
if ((possibleBaudRates & BAUD_9600) > 0) | |||
_baudRateCollection.Add(9600); | |||
if ((possibleBaudRates & BAUD_14400) > 0) | |||
_baudRateCollection.Add(14400); | |||
if ((possibleBaudRates & BAUD_19200) > 0) | |||
_baudRateCollection.Add(19200); | |||
if ((possibleBaudRates & BAUD_38400) > 0) | |||
_baudRateCollection.Add(38400); | |||
if ((possibleBaudRates & BAUD_56K) > 0) | |||
_baudRateCollection.Add(56000); | |||
if ((possibleBaudRates & BAUD_57600) > 0) | |||
_baudRateCollection.Add(57600); | |||
if ((possibleBaudRates & BAUD_128K) > 0) | |||
_baudRateCollection.Add(128000); | |||
SendPropertyChangedEvent("BaudRateCollection"); | |||
} | |||
</source> | |||
<source lang="c"> | <source lang="c"> | ||
/ | public string[] PortNameCollection //zisťovanie portov na zariadení | ||
{ | |||
get { return _portNameCollection; } | |||
set { _portNameCollection = value; } | |||
} | |||
</source> | |||
Indikátor počtu krokov je vytvorený pomerne jednoducho. Vždy keď z COM portu príde 1, indikátor zapíše krok. K tomuto riešeniu sme sa priklonili preto, lebo dovoľuje resetovať počet krokov počas chodu programu. Keby to chceme riešiť prostredníctvom arduina zložitosť algoritmu by sa zvýšila preto, lebo by sme museli už aj dáta posielať a nie iba príjmať. Keď indikátor zaregistruje krok, najprv textbox premaže a potom vypíše žiadanú hodnotu. Mohli sme nechať vypisovať rad čísel ale považovali sme to za zbytočné keď sa jedná o inkrementáciu o jedna. Pri spomenutom premazávaní textboxu bolo použitých viacero príkazov na mazanie/updateovanie/refreshovanie aby sa predišlo zobrazeniu viacerých alebo nesprávnych číselných hodnôt. | |||
<source lang="c"> | |||
kroky++; //toto všetko sa vykoná keď indikátor zistí krok | |||
tbData.ResetText(); //reset textboxu | |||
tbData.Clear(); //vyčistenie potencíalne nechceného textu textboxe | |||
tbData.AppendText(kroky.ToString()); //vypísanie počtu krokov | |||
tbData.ScrollToCaret(); | |||
animate_Topanku(); //spustenie animácie topánky | |||
</source> | </source> | ||
Vizualizácia topánky stúpajúcej zhora nadol je urobená prostredníctvom dvoch cyklov v ktorých sa pomaly mení hodnota x-ovej osy keď program zaregistruje krok. | |||
<source lang="c"> | |||
private void animate_Topanku() | |||
{ | |||
for (int iter = 0; iter < 20; iter++) | |||
{ | |||
topankaObr.Location = new Point(topankaObr.Left, topankaObr.Top - 2); //začína sa odpočítavaním preto, lebo odrátavame vzdialenosť od | |||
Application.DoEvents(); //vrchu, tým obrázok stúpa | |||
System.Threading.Thread.Sleep(5); //malé oneskorenie, aby bola animácia plynulá | |||
} | |||
for (int iter = 0; iter < 20; iter++) | |||
{ | |||
topankaObr.Location = new Point(topankaObr.Left, topankaObr.Top + 2); | |||
System.Threading.Thread.Sleep(5); | |||
} | |||
} | |||
</source> | |||
[[Médiá: | Zdrojový kód: [[Médiá:mainform.cs|MainForm.cs]],[[Médiá:serialsettings.cs|SerialSettings.cs]],[[Médiá:serialportlistener.cs|SerialPortListener.cs]],[[Médiá:program.cs|Program.cs]],[[Médiá:mainform.designer.cs|MainForm.designer.cs]],[[Médiá:SerialPortListener.rar|Projekt]] | ||
=== Overenie === | === Overenie === | ||
Program a zariadenie sa používa jednoducho. Stačí ho nasadiť na miesto ktoré vykonáva najväčší pohyb pri chôdzi a spustiť program. Potom sa nastaví port cez ktorý treba čítať a baudrate; ten sme nastavili predvolene tak, aby bol najoptimálnejší. Potom kliknete na Start Listening. Vtedy zariadenie začína rátať kroky. Ďalej je tam tlačítko Reset a Stop listening. Reset slúži na vynulovanie počítadla a Stop zase na pozastavenie. Je tam jediné pole na zobrazovanie a to zobrazuje počet krokov. Opísané rozhranie môžete vidieť na obrázku v časti Vizualizácia riešenia. | |||
[[Category:MEMS]] [[Category:I2C]] | [[Category:MEMS]] [[Category:I2C]] |
Aktuálna revízia z 07:00, 21. máj 2016
Autori: | Michal Hlavatý, Michal Stromko | |
Študijný odbor: | Aplikovaná mechatronika a elektromobilita | 1. Ing. (2016) |
Zadanie
Pomocou akcelerometra, ktorý sa nachádza na doske Xtrinsic Sensors board vytvorte aplikáciu/program, ktorý bude realizovať funkciu jednoduchého krokomeru.
Xtrinsic Sensors board
Zoznam senzorov:
- MPL3115A2: High-precision Pressure Sensor (20 – 110 kPa)
- MAG3110: Digital 3-axis Magnetometer
- MMA8491Q 3-axis Low-g Accelerometer
Body zadania:
- K použitému senzoru nájdite datasheet a preštudujte jeho štruktúru, spôsob pripojenia, komunikácie, a čítania dát.
- Na základe predošlých vedomostí navrhnite spôsob pripojenia k vhodnej riadiacej jednotke
- Napíšte program v prostredí Arduino zabezpečujúci:
- Komunikáciu so snímačom
- Načítanie a spracovanie potrebných dát
- Realizácia funkcionality krokomeru
- V ľubovoľnom programovacom jazyku/prostredí vytvorte aplikáciu na vizualizáciu a demonštráciu predloženého riešenia
- Dokumentáciu odovzdajte prostredníctvom tejto wiki stránky.
Literatúra:
- Zoznam použitej literatúry, vrátane katalógových údajov (datasheet), internetových odkazov a pod.
- Product page
- Datasheet
- Data Manipulation and the Basic Settings of Xtrinsic MMA8491Q Accelerometer
- Full-Featured Pedometer Design Realized with 3-Axis Digital Accelerometer
Analýza
Použitý senzor
Na realizáciu funkcie krokometra využijeme už vyššie spomínaný senzor Xtrinsic MMA8491Q 3-Axis Multifunction Digital Accelerometer. Je to nízko napäťový, 3-osí "low-g" akcelerometer uložený v 3 mm x 3 mm QFN púzdre. Zariadenie poskytuje dve konfigurácie. Prvou z nich je 45° senzor naklonenia a druhou akcelerometer s digitálnym výstupom cez I2C zbernicu. Pre nás je zaujímavá funkcia akcelerometra kedy senzor poskytuje 14-bit ± 8g dáta, ktoré môžu byť prečítané s 1 mg/LSB rozlíšením.
Vlastnosti
- Extrémne nízka spotreba 400 nA na Hz
- Ultra rýchly čas prenosu, ~700 μs
- Napájací rozsah od 1,95 do 3,6 V
- 3 mm x 3 mm, 0,65 mm stúpanie s vizuálnou inšpekciou spájkovacích spojov
- ± 8g rozsah
- 14-bit digitálny výstup, 1 mg/LSB rozlíšenie
- rýchlosť prenosu výstupných dát (ODR), v závislosti na implementácii od 1 Hz do 800 Hz
- I2C digitálne rozhranie
- výstup v troch osiach a 45° natočenie
Popis vývodov
C | Pin | Funkcia |
1 | Byp | Pripojenie výstupného kondenzátora interného regulátora |
2 | V_DD | Napájacie napätie |
3 | SDA | I2C dátová linka |
4 | EN | Povoľovací pin |
5 | SCL | I2C hodinová linka |
6 | Gnd | Zem |
7 | Gnd | Zem |
8 | Zout | Výstup detekcie naklonenia v osi Z |
9 | Yout | Výstup detekcie naklonenia v osi Y |
10 | Xout | Výstup detekcie naklonenia v osi X |
11 | NC | Nepripojené |
12 | NC | Nepripojené |
Popis registrov
Akcelerometer obsahuje 6 dátových a jeden stavový register. Tieto registre budeme používať na čítanie dát o zrýchlení v jednotlivých osiach.
Mapa registrov
- Obsah registrov je zachovaný ak je EN pin po vzorkovaní nastavený na vysokú úroveň
- Obsah registrov je vynulovaný keď je EN pin nastavený na nízku úroveň.
Stavový register
Register 0x00 obsahuje real-time stavové informácie o vzorkách X, Y a Z dát. Bity (ZYXDR, ZDR, YDR, XDR) sa nastavia keď sú nové nasnímané dáta pripravené na čítanie.
Pole | Popis |
ZYXDR | ZYXDR signalizuje, že sú pripravené nové vzorky vo všetkých kanáloch. ZYXDR je vymazaný keď sú prečítané registre horných bitov vo všetkých kanáloch (OUT_X_MSB, OUT_Y_MSB, OUT_Z_MSB).
0: Nie je pripravený nový set dát 1: Nový set dát je pripravený |
ZDR | ZDR je nastavený kedykoľvek je generovaná nová vzorka pre os Z. ZDR je vymazaný keď je prečítaný register OUT_Z_MSB.
0: Nie sú pripravené nové dáta v osi Z 1: Nové dáta v osi Z sú pripravené |
YDR | YDR je nastavený kedykoľvek je generovaná nová vzorka pre os Y. YDR je vymazaný keď je prečítaný register OUT_Y_MSB.
0: Nie sú pripravené nové dáta v osi Y 1: Nové dáta v osi Y sú pripravené |
XDR | XDR je nastavený kedykoľvek je generovaná nová vzorka pre os X. XDR je vymazaný keď je prečítaný register OUT_X_MSB.
0: Nie sú pripravené nové dáta v osi X 1: Nové dáta v osi X sú pripravené |
Dátové registre
Tieto registre obsahujú 14-bit výstupné dáta akcelerometra pre osi X, Y a Z vyjadrené v dvojkovom doplnku.
- OUT_X_MSB, OUT_X_LSB, OUT_Y_MSB, OUT_Y_LSB, OUT_Z_MSB, a OUT_Z_LSB sú uložené na adresnom rozsahu 0x01 – 0x06 pre automatickú inkrementáciu.
- LSB registre môžu byť prečítané len okamžite po prečítaní z korešpondujúceho MSB registra. "Random access" čítanie z LSB registrov nie je možné.
- Čítanie MSB a LSB registrov v sekvencii zabezpečuje, že obidva bajty patria rovnakej vzorke, dokonca aj vtedy ak medzi čítaním z MSB a LSB prídu nové dáta.
- Dátové registre by mali byť čítané len po tom ako stavový register potvrdil prítomnosť nových dát vo všetkých osiach.
I2C komunikácia
Hodnoty zrýchlenia v jednotlivých osiach sú dostupné cez I2C rozhranie čím sa senzor stáva vhodným pre priame pripojenie k mikropočítaču. Senzor poskytuje signály prerušenia, ktoré indikujú prítomnosť hodnôt natočenia na X, Y a Z osi. Hodnoty zrýchlenia je možné čítať cez I2C v rovnakom čase keď je dostupný prerušovací signál. I2C rozhranie sa zapína nastavením EN pinu na vysokú úroveň. Ak je EN alebo V_DD nastavené na nízkej úrovni potom dáta čítané z akcelerometra sú nesprávne. Odpojenie napájania z V_DD pinu nemá vplyv na I2C zbernicu.
Pri I2C komunikácii sa používajú dva signály "Serial Clock Line (SCL)" a "Serial Data Line (SDA)". SDA je obojsmerná linka používaná na posielanie a príjem dát z/na rozhranie. Pri prenose sa počíta z externými "pull-up" registrami na SDA a SCL linke. Keď je linka voľná obidva signály sú na vysokej úrovni. Slave adresa akcelerometra je 0x55. Viac o I2C prenose je uvedené v dokumentácii.
Postup prenosu jedného bajtu
- Master vyšle štartovaciu podmienku na adresu MMA8491Q s RW bitom nastaveným na nulu pre zápis, a MMA8491Q odošle potvrdenie.
- Potom Master vyšle adresu registra, z ktorého chce čítať a MMA8491Q pošle potvrdenie.
- Master vyšle opakovanú štartovaciu podmienku a potom adresuje MMA8491Q s RW bitom nastaveným na jednotku pre čítanie z predtým vybraného registra.
- MMA8491Q potom potvrdí a pošle dáta z požadovaného registra.
- Master nepotvrdzuje príjem, ale vyšle stop podmienku a ukončí prenos dát.
Módy operácie senzora
Senzor pracuje v štyroch základných módoch.
Aktívny mód
Subsystém akcelerometra je zapnutý nábežnou hranou na EN pine a získa jednu vzorku pre každú z troch osí. EN pin by nemal byť nastavený skôr ako V_DD dosiahne 1,95 V. Vzorky sú získané, konvertované a kompenzované na "zero-g" ofset a chyby zosilnenia a potom porovnané z internou hranicou 0,688g a následne uložené. Prečítaním registra 0x00 v tomto móde zistíme či sú dáta pripravené na čítanie.
Standby mód
Zariadenie prejde automaticky do STANDBY módu po nameraní dát v ACTIVE móde. Výstupný systém poskytuje validné dáta, ktoré môžu byť prečítané cez I2C zbernicu. Tieto hodnoty sú podržané kým sa nezmení mód sezora. Pre zníženie spotreby je dobré vynulovať EN pin okamžite po prečítaní dát. Nové dáta je možné získať až keď privedieme senzor naspäť do aktívneho módu nastavením EN ja log. 1.
Riadiaca jednotka
Keďže senzor MMA8491Q vysiela dáta o zrýchlení cez zbernicu I2C ako riadiacu jednotku musíme zvoliť mikropočítač, ktorý podporuje takúto komunikáciu. Arduino Uno poskytuje jednoduchý spôsob pripojenia pre komunikáciu cez I2C zbernicu s Xtrinsic senzorovou doskou a taktiež jej poskytuje potrebné napájanie (3.3 V). Napájanie a komunikácia riadiacej jednotky je zabezpečená z PC cez USB rozhranie.
Viac na Arduino Uno
Programové prostredie
Program pre činnosť mikropočítača budeme vyvíjať vo voľne dostupnom softvérovom prostredí (IDE) vytvorenom pre tento účel spoločnosťou Arduino. ARDUINO Software 1.6.9
Tento softvér priamo obsahuje knižnicu Wire.h pre komunikáciu cez I2C zbernicu pomocou SDA a SCL vývodov. Na doske pre Arduino Uno sú vývody SDA a SCL umiestnené blízko AREF pinu ako môžeme vidieť na obrázku vyššie. Knižnica používa 7-bit adresy zariadení pričom adresy 0-7 sú rezervované. Knižnica poskytuje nasledovné funkcie
Funkcia | Popis | Parametre | Návratová hodnota |
begin() | Inicializuje Wire knižnicu a pripojí I2C zbernicu ako Master alebo Slave. Táto funkcia by mala byť volaná len raz. | address(optional): 7-bit adresa slave zariadenia. Ak nie je zadaná Arduino sa pripojí na zbernicu ako master. | Žiadne |
requestFrom() | Funkcia používaná master zariadením na vyžiadanie bajtov od slave zariadenia. Bajty môžu byť potom získané pomocou funkcii available() a read(). | address: 7-bit adresa zariadenia, t ktorého požadujeme bajty quantity: počet požadovaných bajtov stop:boolean(Default True): Ak je True master vyšle po žiadosti stop správu čím uvoľní zbernicu. Ak je False spojenie zostane aktívne. | byte: počet bajtov vrátených zo slave zariadenia |
beginTransmission() | Zaháji I2C prenos so slave zariadením s danou adresou. Následne je možné pripraviť dáta na prenos pomocou write() funkcie a odoslať ich volaním endTransmission() funkcie. | address: 7-bit adresa cieľového zariadenia | Žiadne |
endTransmission() | Ukončí prenos so slave zariadením a odošle dáta, ktoré boli pripravené pomocou write() funkcie. | stop:boolean(Default True) Ak je True odošle sa stop správa čím sa po prenose uvoľní zbernica. Ak je False odošle sa restart správa, ktorá udrží spojenie aktívne. | byte: indikácia stavu prenosu 0: úspech. 2: dáta dlhšie ako vysielací buffer. 3: prijatý NACK pri prenose adresy. 4: iná chyba. |
write() | Odosiela dáta zo slave zariadenia ako odpoveď na žiadosť od mater zariadenia, alebo pripraví dáta na prenos z mater zariadenia n slave zariadenie(pred endTransmission() funkciou). | value: dáta, ktoré sa majú odoslať (jeden bajt). string: reťazec dát, ktorý sa má odoslať (alternatíva k value). data: pole dát, ktoré sa má odoslať ako bajty(alternatíva k value/string). length: počet bajtov, ktoré sa majú preniesť. | byte: počet prenesených bajtov |
available() | Vráti počet bajtov pripravených na čítanie pomocou read() funkcie. Funkcia by mala byť volaná na master zariadení po volaní requestFrom() alebo na slave zariadení vnútri onReceive() funkcie. | Žiadne | Počet bajtov pripravených na prečítanie |
read() | Prečíta bajt, ktorý bol poslaný zo slave zariadenia na master zariadenie po volaní requestFrom() funkcie alebo bol prenesený z master zariadenia na slave zariadenie. | Žiadne | Prijatý bajt dát |
onReceive() | Registruje funkciu, ktorá sa má zavolať keď slave zariadenie príjme prenos dát z mater zariadenia. | handler: funkcia, ktorá sa má zavolať keď slave zariadenie príjme dáta. Funkcia by mala byť typu void a ako parameter používať počet prijatých bajtov. | Žiadne |
onRequest() | Registruje funkciu, ktorá sa má zavolať keď master zariadenie žiada dáta z tohto slave zariadenia. | handler: funkcia, ktorá sa má zavolať. Mala by byť typu void a bez parametrov. | Žiadne |
Viac informácii a príkladov s Wire knižnicou na Wire Library
Princíp funkcionality krokomeru
Pri návrhu funkcionality krokomeru budeme vychádzať z článku Full-Featured Pedometer Design Realized with 3-Axis Digital Accelerometer, ktorý publikoval Neil Zhao. Článok popisuje komplexný krokomer pracujúci v troch osiach, ktorý okrem počtu prejdených krokov poskytuje informácie o vzdialenosti, rýchlosti a spálených kalóriách. Pre naše účely budeme uvažovať len zjednodušený model krokomeru pracujúci v jednej osi, ktorej sa zrýchlenie mení najviac. Na nasledovnom obrázku je zobrazená závislosť medzi jednotlivými fázami ľudského kroku a zmenami vertikálneho zrýchlenia a zrýchlenia v smere pohybu.
Tieto zmeny v zrýchlení môžeme vyniesť do grafu.Pre bežiacu osobu bude priebeh jednotlivých zložiek zrýchlenia (x, y a z) vyzerať nasledovne
Vidíme, že aspoň v jednej osi má zrýchlenie relatívne veľké a periodické výchylky. Výchylky však nie sú rovnaké a s časom menia, preto je potrebné dynamicky detegovať hraničné hodnoty (max, min) a upravovať rozhodovaciu úroveň algoritmu. Okrem toho je potrebná filtrácia, pretože pri pohybe človeka (tým pádom aj senzora) budú mať príjmané dáta z akcelerometra vysokú šumovú úroveň. Obrázok nižšie znázorňuje filtrované dáta z najaktívnejšej osi akcelerometra pre kráčajúcu osobu.
Pre zabezpečenie dynamických zmien porovnávacej úrovne musí systém periodicky aktualizovať maximálne a minimálne hodnoty zrýchlenia. Dynamická rozhodovacia úroveň sa potom vypočíta ako (Max + Min)/2. Pre nasledujúcu sadu vzoriek (jednu periódu) bude táto hodnota použitá na určenie či došlo ku kroku. Na vyhodnotenie prítomnosti kroku sa používa lineárny posuvný register s dvomi registrami sample_old a sample_new. Pri príchode nových dát je hodnota v sample_new bezpodmienečne posunutá do registra sample_old. Zápis nových dát do registra sample_new však závisí nastavenej rozlišovacej schopnosti precision. Ak je zmena v zrýchlení (sample_new - nové dáta) väčšia ako precision potom sú nové dáta zapísané do registra sample_new. V opačnom prípade ostáva register sample_new bez zmeny. Uskutočnenie kroku je potom definované negatívnym sklonom priebehu zrýchlenia (sample_new < sample_old) keď hodnota zrýchlenia klesne pod dynamickú porovnávaciu úroveň.
Vizualizácia riešenia
Dalej v tejto časti popíšeme programovacie prostredie C#. Rozhodli sme sa ho použiť pre jeho dostupnosť a jednoduchosť. Pri použití javy sa k COM portom pristupuje zložitejšie celkový návrh vizualizácie je zložitejší, kým v C# sa jedná vo veľkej časti o drag & drop metódu.
C# je orientovaný hlavne na objektovo orientované programovanie. Jeho syntax je veľmi podobná ostatným C jazykom (C,C++), ale aj Jave. Podobnosť je napríklad v tom že príkazy ukončujeme bodkočiarkou, pre porovnanie použijeme dve rovná sa za sebou, kým na priradenie použieme jediné a pod.
Na vytvorenie GUI (graphical user interface) sme vytvorili takzvanú Form aplikáciu. Windows form obsahuje komponenty ako dialógové okná, menu, tlačítka a rôzne iné, ktoré sú potrebné na vytvorenie štandartného užívateľského rozhrania. Po vytvorení takéhoto komponentu sa vytvorí trieda z .NET Framework class library. Nástrojom DESIGNER umožnuje prezerať si navrhnuté rozhranie, prispôsobovať veľkosti a celkový design aplikácie bez nutnosti použitia kódu (v Jave je treba zadávať presné súradnice a veľkosti pomocou zdrojového kódu). Stará sa o to IDE (Integrated development enviroment - integrované vývojové prostredie) ktoré požadovaný zdrojový kód vytvára samé a tak upravuje triedu daného objektu.
Jednoduchý návod na vytváranie Form aplikácí v C# nájdete tu: https://msdn.microsoft.com/en-us/library/360kwx3z(v=vs.90).aspx
Popis riešenia
Prvým krokom pri riešení nášho zadania bolo napísať program, ktorý by bol schopný vyčítať údaje zo spomínaného senzora MMA8491Q, aby sme s nimi mohli ďalej pracovať. Tento krok si vyžadoval preštudovanie dokumentácie senzora kde sme sa zamerali na spôsob pripojenia a komunikácie, štruktúru registrov a reprezentáciu zosnímaných dát. Po naštudovaní potrebných informácii sme pristúpili k pripojeniu senzora k riadiacej jednotke. Použili sme už vyššie spomínaný mikroprocesor Arduino Uno. Kedže komunikácia so senzorom prebieha prostredníctvom I2C zbernice naštudovali sme si spôsob zapojenia senzora k I2C zbernici. Základná schéma zapojenia vyzerá nasledovne
Aby akcelerometer správne fungoval je potrebné aby bol zapojený týmto spôsobom
- Vnútorný napäťový regulátor si vyžaduje pripojenie kondenzátora na Byp pin. Odporúča sa 0,1 μF kondenzátor.
- Zariadenie je napájané cez V_DD linku. Čo najbližšie k V_DD pinu musí byť pripojený kondenzátor na odstránenie väzby napájania.
- Ak V_DD a EN piny nie sú spojené je vhodné použiť 1,0 alebo 4,7 μF kondenzátor.
- Naopak ak sú piny V_DD a EN spojené je treba použiť 0,1 μF kondenzátor. Tento kondenzátor spôsobí minimalizáciu spotreby a zachovanie prípustnej úrovne vysokofrekvenčnej filtrácie napájania.
- Obidva zemniace piny musia byť pripojené na zem.
- Ak sa používa I2C komunikácia je potrebné použiť pull-up odpory na pripojenie SDA a SCL liniek. Ak sa I2C komunikácia nepoužíva piny SDA a SCL by mali byť pripojené na zem.
Po preskúmaní schém zapojenia Xtrinsic senzorovej dosky sme zistili, že všetky vyššie spomínané súčiastky potrebné pre správnu činnosť senzora MMA8491Q sa na doske už nachádzajú. Preto sme pristúpili priamo s pripojeniu senzora s mikropočítačom Arduino pričom je potrebné brať do úvahy nasledovné rozloženie vývodov na Xtrinsic senzorovej doske.
Pri práci s akcelerometrom sme nepotrebovali všetky vývody Xtrinsic dosky. Potrebné vývody sú zvýraznené na obrázku vyššie a jedná sa o piny pre napájanie (3V3 a GND), linky pre I2C komunikáciu (SDA, SCL) a povoľovací pin EN. Porovnaním rozloženia pinov Xtrinsic dosky a mikropočítača Arduino Uno (obrázok vyššie v časti Analýza) vidíme, že umiestnenie potrebných pinov si vzájomne odpovedá a preto môžeme senzorovú dosku Xtrinsic priamo pripojiť v Arduino Uno bez potreby ďalšej kabeláže. Pin na ktorom je pripojený EN vývod Xtrinsic dosky (pin 8) sme použili ako GPIO pin pre ovládanie činnosti akcelerometra.
Po úspešnom pripojení sme v softvérovom prostredí pre Arduino napísali program pre komunikáciu so senzorom, vyčítanie dát a ich konverziu z dvojkového doplnku na hodnotu v "g". Využili sme pritom vzorový kód pre podobný akcelerometer MMA8452Q (kód je dostupný tu), ktorý sa od nášho akcelerometra v určitých aspektoch líši. V kóde sme zmenili tieto nezrovnalosti (iná adresa, dĺžka dátových registrov, atď.) tak aby kód korešpondoval s naším snímačom.
Následne sme pristúpili k návrhu funkcionality na základe článku popísanom v sekcii Analýza - Popis funkcionality krokomeru. Za relevantnú os pri meraní zrýchlenia sme považovali os x. Podľa postupu sme najskôr pre merané dáta vytvorili posuvný filter dĺžky tri, ktorý pre dáta v osi x dostatočne odstránil šum. Dynamická porovnávacia úroveň (threshold) sa aktualizuje každých 10 vzoriek na základe dosiaľ nameraných hodnôt maximálneho a minimálneho zrýchlenia (threshMax, threshMin). Pri aktualizácii sa obidve tieto hodnoty nastavia na aktuálnu meranú hodnotu aby sa zabezpečilo čo najrýchlejšie nájdenie nový min. a max. hodnôt v okolí aktuálne meraných vzoriek. Posuvný register je simulovaný dvoma premennými sampleNew a sampleOld, ktoré prislúchajú jednotlivým registrom. Rozlišovacia schopnosť krokomeru precision je nastavená na pevnú hodnotu pre nami zvolenú konfiguráciu snímača (orientácia, umiestnenie na tele). Pri zmene konfigurácie je potrebné túto hodnotu na základe meraných dát zmeniť tak aby mal krokomer požadovanú citlivosť pri detekcii uskutočnenia kroku. Detekcia kroku je realizovaná presne podľa úvahy uvedenej v Analýza - Popis funkcionality krokomeru (časť zvýraznená hrubým písmom). Celý program je detailnejšie popísaný v sekcii nižšie.
Posledným krokom bolo vytvorenie aplikácie na vizualizáciu a demonštráciu realizovaného riešenia. Aplikácia je popísaná nižšie.
Algoritmus a program
Program pre mikropočítač
Program pre mikropočítač sme vyvíjali v prostredí Arduino. Program zahŕňa komunikáciu so senzorom, načítanie a konverziu dát, úprav dát a ich použitie na realizáciu funkcionality krokomeru. V programe sú použité nasledovné premenné
MMA8452Q accel(0x55); // vytvorí inštanciu triedy akcelerometer s adresou zariadenia 0x55
// Filter dat
int numReadingsX = 3; // veľkosť filtra (počet hodnôt)
int filterStartX; // indikácia začiatku filtrovania pre rýchleší štart (aby na začiatku neobsahoval iba nuly)
int readIndexX = 0; // index pre iteráciu cez filter
float readingsX[3]; // pole pre hodnoty vo filtri
float totalX = 0; // suma všetkých hodnôt vo filtri
float averageX = 0; // priemer z hodnôt vo filtri
// Krokomer
int krokCount = 0; // celkový počet krokov
int threshUpdateRate = 10; // interval aktualizácie threshold-u
int threshUpdateCount = 9; // počítadlo pre aktualizáciu threshold-u
float threshMax = 0; // horná hranica - max. hodnota zrýchlenia
float threshMin = 0; // dolná hranica - min. hodnota zrýchlenia
float threshold = 0; // rozhodovacia úroveň
float sampleOld = 0; // sample_old register
float sampleNew = 0; // sample_new register
float precision = 0.01; // rozlišovacia úroveň - citlivosť rozoznania kroku
// Tok programu
int unoKrok = 1; // zamedzenie započítaniu jedného kroku viackrát
Premenná accel je triedy MMA8452Q, ktorá reprezentuje akcelerometer a obsahuje ďalšie premenné a metódy. Ako sme spomínali vyššie merané dáta sú pred použitím najskôr filtrované posuvným filtrom. Tento filter si môžme predstaviť ako pole konštantnej dĺžky, ktoré je vždy naplnené "n" najaktuálnejšími hodnotami meranej veličiny, kde n je veľkosť poľa. Z hodnôt v poli sa následne počíta ich priemer a ten sa považuje za aktuálnu filtrovanú hodnotu. Pri príchode novej meranej hodnoty sa z poľa odstráni hodnota, ktorá je na jeho konci (bola v poli najdlhšie) a na jej miesto sa zapíše nová hodnota. Pri prvom spustení filtra sa filter celý naplní prvou nameranou hodnotou aby sa predišlo delenie (0....0+newData)/n a filtrovaná hodnota čo najviac približovala meranej hodnote a nie nule. Funkcia takéhoto filtra vyzerá nasledovne
float filterDataX(float newData) // posuvný filter dát v x - osi
{
if (filterStartX == 1) // pri prvom spustení naplň filter aktuálnou hodnotou aby sa predišlo (0...0+newData)/30
{
for (readIndexX = 0; readIndexX < numReadingsX; readIndexX++)
{
readingsX[readIndexX] = newData;
totalX = totalX + readingsX[readIndexX];
}
filterStartX = 0;
}else
{
// odpočítaj hodnotu na konci filtra
totalX = totalX - readingsX[readIndexX];
// namiesto nej vlož novú hodnotu
readingsX[readIndexX] = newData;
// pričítaj novú hodnotu k celkovému súčtu
totalX = totalX + readingsX[readIndexX];
}
// posun na ďalšie miesto vo filtri
readIndexX = readIndexX + 1;
if (readIndexX >= numReadingsX) // ak sme prešli cez celý filter vráť sa na začiatok
{
readIndexX = 0;
}
// vypočítaj priemer
return averageX = totalX / numReadingsX;
}
V setup() funkcii programu sa inicializuje sériová linka, nastaví sa mód pinu EN, ktorý používame pre prácu so senzorom, inicializujú sa všetky filtre dát a samotný akcelerometer. Funkcia accel.init() iba nastaví premennú udávajúcu rozsah akcelerometra a inicializuje Wire knižnicu príkazom Wire.begin().
void setup()
{
Serial.begin(115200); // nastavenie sériovej linky
pinMode(ENpin, OUTPUT); // nastavenie EN pinu senzora (pin 8 na Arduino Uno)
filterStartX = 1; // inicializácia filtrov (indikácia prvého spustenia)
filterStartY = 1;
filterStartZ = 1;
if (accel.init(SCALE_8G) == 1){ // inicializácia akcelrometra
Serial.println("Wire nastart!");
}
for (int readingIndex = 0; readingIndex < numReadingsX; readingIndex++){ //vynulovanie hodnôt filtrov
readingsX[readingIndex] = 0;
}
for (int readingIndex = 0; readingIndex < numReadingsY; readingIndex++){
readingsY[readingIndex] = 0;
readingsZ[readingIndex] = 0;
}
}
V loop() funkcii sa potom kontinuláne vykonáva meranie nových dát z akcelerometra, a vykonanie funkcie samotného krokomeru. Nastavovanie a nulovanie pinu 8 (EN) slúži na zmenu módu senzora (viď. Analýza).
void loop()
{
digitalWrite(ENpin, HIGH); // prepnutie sezora do ACTIVE módu
delay(10); // delay potrebný pre ustálenie konverziu porovnanie a zápis hodnôt v senzore (hodnota je prehnane veľká)
if (accel.available()) // ak sú dáta pripravené na čítanie
{
accel.read(); // načítaj dáta do premenných objektu accel (x, y, z - bez konverzie, cx, cy, cz - v jednotkách "g")
accel.cy = filterDataY(accel.cy); // filtrovanie dát
accel.cz = filterDataZ(accel.cz);
krokomer(accel.cx); // spusti krokomer pre hodnoty z osi x
digitalWrite(ENpin, LOW); // návrat do SHUTDOWN módu
delay(5); // oneskorenie potrebné medzi jednotlivými meraniami
}
}
Funkcia .read() objektu accel zabezpečuje načítanie meraných dát do premenných (accel.x, accel.y, accel.z) a konverziu meraných dát na jednotky "g" a ich uloženie do premenných acel.cx, accel.cy a accel.cz.
void MMA8452Q::read()
{
byte rawData[6];
readRegisters(OUT_X_MSB, rawData, 6); // načítaj 6 registrov "raw" dát od adresy OUT_X_MSB
x = (rawData[0]<<8 | rawData[1]) >> 2; // z registrov vyskladaj celé 14 -bit hodnoty
y = (rawData[2]<<8 | rawData[3]) >> 2;
z = (rawData[4]<<8 | rawData[5]) >> 2;
cx = (float) x / (float)(1<<13) * (float)(scale); // konverzia z dvojkového doplnku na jednotky "g"
cy = (float) y / (float)(1<<13) * (float)(scale);
cz = (float) z / (float)(1<<13) * (float)(scale);
}
Funkcia krokomer() najskôr vykoná filtráciu nameraných dát pomocou vyššie uvedenej funkcie filterDataX(). Následne skontroluje či je rozdiel v zrýchlení väčší ako rozlišovacia úroveň krokomeru. Rozlišovacia úroveň slúži na odrušenie šumu a malých otrasov, ktoré nepredstavujú uskutočnenie kroku a spôsobili by chybné zaznamenanie. Následne sa vykoná funkcia zabezpečujúca prácu z rozhodovacou úrovňou thresholdFire(). Potom sa uskutoční posunutie hodnôt v LSR (sample_new --> sample_old) a zápis novej hodnoty do registra sample_new. Ďalej sa skontroluje či nedošlo nárastu hodnôt čo slúži na resetovanie premennej unoKrok, ktorá je použitá na zamedzenie viacnásobného započítania toho istého kroku. V poslednej podmienke sa skontroluje či je nová vzorka pod rozhodovacou úrovňou a zároveň má krivka zrýchlenia klesajúci charakter a premenná unoKrok je nastavená. Všetky tieto podmienky zaručujú, že bol vykonaný krok. Funkcia krokomerPrint() iba odošle informáciu o uskutočnení kroku na sériovú linku.
{
merData = filterDataX(merData); // filtrovanie dát
if (abs(merData - sampleNew) >= precision) // ak je rozdiel v zrýchlení väčší ako rozlišovacia úroveň
{
thresholdFire(merData); // spusti rutinu pre aktualizáciu rozhodovacej úrovne
sampleOld = sampleNew; // posun LSR
sampleNew = merData; // zápis nových dát do registra sample_new
if ((sampleNew > sampleOld)&&(unoKrok == 0)){ // ak dôjde k nárastu hodnôt po detekcii kroku resetuj unoKrok
unoKrok = 1;
}
if ((sampleNew < threshold)&&(sampleNew < sampleOld)&&(unoKrok == 1)) // ak je detekovaný krok
{
krokCount++; // inkrementuj počet krokov
unoKrok = 0; // nastav unoKrok aby sa zabránilo viacnásobnému započítaniu jedného kroku
krokomerPrint(); // vypíš že nastal krok na sériovú linku
}
}else{ // ak je zmena v zrýchlení < precision
//Serial.println("prilis mala zmena");
return;
}
}
Funkcia thresholdFire() zabezpečuje aktualizáciu hodnôt min. a max. zrýchlenia a testovanie či nie je potrebné aktualizovať rozhodovaciu úroveň.
void thresholdFire(float newData) // funkcia na aktualizáciu min a max. hodnôt rozhodovacej úrovne + test či netreba aktualizovať threshold
{
if (threshUpdateCount < threshUpdateRate) // ak ešte nie je potrebná aktualizácia rozhodovacej úrovne
{
if (newData <= threshMin){ // aktualizuj hodnotu min. a max zrýchlenia
threshMin = newData;
}else if(newData >= threshMax){
threshMax = newData;
}
threshUpdateCount++; // pripočítaj k počítadlu aktualizácie
}else{
threshUpdate(newData); // ak je potrebná aktualizácia rozhodovacej úrovne - vykonaj aktualizáciu
}
}
Volanie funkcie 'threshUpdate() vykoná samotnú aktualizáciu rozhodovacej úrovne a resetuje hodnoty min. a max. zrýchlenia.
void threshUpdate(float newData) // funkcia na aktualizáciu rozhodovacej úrovne
{
threshold = (threshMax + threshMin)/2; // výpočet rozhodovacej úrovne
threshUpdateCount = 0; // reset počítadla pre aktualizáciu
threshMin = newData; // nastav min. a max. na aktuálnu hodnotu (aby sa našli nové min a max. v okolí aktuálne meraných dát)
threshMax = newData;
}
Zdrojové kódy: MMA8491Q_Main, SFE_MMA8452Q.h , SFE_MMA8452Q.cpp
Aplikácia pre vizualizáciu
Vizualizáciu sme sa rozhodli spraviť v prostredí C#. Naše GUI pozostáva z troch častí. Prvou sú nastavenia baudrateu a výber portov. Druhou časťou je indikátor počtu vykonaných krokov a treťou časťou je vizuálne zobrazenie topánky ktorá vykoná pohyb keď program zaregistruje krok.
Baudrate sa vyberá za pomoci comboboxu. Po zakliknutí program porovnáva "tabuľkové" baudratei so zvoleným a keď nájde zhodu, tak ho nastaví na poŽadovanú hodnotu. S COM portmi je to trochu inak. Program jednoducho oskenuje prístupné porty na zariadení a taktiež pomocou komboboxu nás nechá zvoliť aký chceme.
public string PortName //getre, setre baud rateu a porov
{
get { return _portName; }
set
{
if (!_portName.Equals(value))
{
_portName = value;
SendPropertyChangedEvent("PortName");
}
}
}
public int BaudRate
{
get { return _baudRate; }
set
{
if (_baudRate != value)
{
_baudRate = value;
SendPropertyChangedEvent("BaudRate");
}
}
}
public void UpdateBaudRateCollection(int possibleBaudRates) //vyššie opísaná tabuľka baudrateov a spôsob ich výberu
{
const int BAUD_075 = 0x00000001;
const int BAUD_110 = 0x00000002;
const int BAUD_150 = 0x00000008;
const int BAUD_300 = 0x00000010;
const int BAUD_600 = 0x00000020;
const int BAUD_1200 = 0x00000040;
const int BAUD_1800 = 0x00000080;
const int BAUD_2400 = 0x00000100;
const int BAUD_4800 = 0x00000200;
const int BAUD_7200 = 0x00000400;
const int BAUD_9600 = 0x00000800;
const int BAUD_14400 = 0x00001000;
const int BAUD_19200 = 0x00002000;
const int BAUD_38400 = 0x00004000;
const int BAUD_56K = 0x00008000;
const int BAUD_57600 = 0x00040000;
const int BAUD_115200 = 0x00020000;
const int BAUD_128K = 0x00010000;
_baudRateCollection.Clear();
if ((possibleBaudRates & BAUD_115200) > 0)
_baudRateCollection.Add(115200);
if ((possibleBaudRates & BAUD_075) > 0)
_baudRateCollection.Add(75);
if ((possibleBaudRates & BAUD_110) > 0)
_baudRateCollection.Add(110);
if ((possibleBaudRates & BAUD_150) > 0)
_baudRateCollection.Add(150);
if ((possibleBaudRates & BAUD_300) > 0)
_baudRateCollection.Add(300);
if ((possibleBaudRates & BAUD_600) > 0)
_baudRateCollection.Add(600);
if ((possibleBaudRates & BAUD_1200) > 0)
_baudRateCollection.Add(1200);
if ((possibleBaudRates & BAUD_1800) > 0)
_baudRateCollection.Add(1800);
if ((possibleBaudRates & BAUD_2400) > 0)
_baudRateCollection.Add(2400);
if ((possibleBaudRates & BAUD_4800) > 0)
_baudRateCollection.Add(4800);
if ((possibleBaudRates & BAUD_7200) > 0)
_baudRateCollection.Add(7200);
if ((possibleBaudRates & BAUD_9600) > 0)
_baudRateCollection.Add(9600);
if ((possibleBaudRates & BAUD_14400) > 0)
_baudRateCollection.Add(14400);
if ((possibleBaudRates & BAUD_19200) > 0)
_baudRateCollection.Add(19200);
if ((possibleBaudRates & BAUD_38400) > 0)
_baudRateCollection.Add(38400);
if ((possibleBaudRates & BAUD_56K) > 0)
_baudRateCollection.Add(56000);
if ((possibleBaudRates & BAUD_57600) > 0)
_baudRateCollection.Add(57600);
if ((possibleBaudRates & BAUD_128K) > 0)
_baudRateCollection.Add(128000);
SendPropertyChangedEvent("BaudRateCollection");
}
public string[] PortNameCollection //zisťovanie portov na zariadení
{
get { return _portNameCollection; }
set { _portNameCollection = value; }
}
Indikátor počtu krokov je vytvorený pomerne jednoducho. Vždy keď z COM portu príde 1, indikátor zapíše krok. K tomuto riešeniu sme sa priklonili preto, lebo dovoľuje resetovať počet krokov počas chodu programu. Keby to chceme riešiť prostredníctvom arduina zložitosť algoritmu by sa zvýšila preto, lebo by sme museli už aj dáta posielať a nie iba príjmať. Keď indikátor zaregistruje krok, najprv textbox premaže a potom vypíše žiadanú hodnotu. Mohli sme nechať vypisovať rad čísel ale považovali sme to za zbytočné keď sa jedná o inkrementáciu o jedna. Pri spomenutom premazávaní textboxu bolo použitých viacero príkazov na mazanie/updateovanie/refreshovanie aby sa predišlo zobrazeniu viacerých alebo nesprávnych číselných hodnôt.
kroky++; //toto všetko sa vykoná keď indikátor zistí krok
tbData.ResetText(); //reset textboxu
tbData.Clear(); //vyčistenie potencíalne nechceného textu textboxe
tbData.AppendText(kroky.ToString()); //vypísanie počtu krokov
tbData.ScrollToCaret();
animate_Topanku(); //spustenie animácie topánky
Vizualizácia topánky stúpajúcej zhora nadol je urobená prostredníctvom dvoch cyklov v ktorých sa pomaly mení hodnota x-ovej osy keď program zaregistruje krok.
private void animate_Topanku()
{
for (int iter = 0; iter < 20; iter++)
{
topankaObr.Location = new Point(topankaObr.Left, topankaObr.Top - 2); //začína sa odpočítavaním preto, lebo odrátavame vzdialenosť od
Application.DoEvents(); //vrchu, tým obrázok stúpa
System.Threading.Thread.Sleep(5); //malé oneskorenie, aby bola animácia plynulá
}
for (int iter = 0; iter < 20; iter++)
{
topankaObr.Location = new Point(topankaObr.Left, topankaObr.Top + 2);
System.Threading.Thread.Sleep(5);
}
}
Zdrojový kód: MainForm.cs,SerialSettings.cs,SerialPortListener.cs,Program.cs,MainForm.designer.cs,Projekt
Overenie
Program a zariadenie sa používa jednoducho. Stačí ho nasadiť na miesto ktoré vykonáva najväčší pohyb pri chôdzi a spustiť program. Potom sa nastaví port cez ktorý treba čítať a baudrate; ten sme nastavili predvolene tak, aby bol najoptimálnejší. Potom kliknete na Start Listening. Vtedy zariadenie začína rátať kroky. Ďalej je tam tlačítko Reset a Stop listening. Reset slúži na vynulovanie počítadla a Stop zase na pozastavenie. Je tam jediné pole na zobrazovanie a to zobrazuje počet krokov. Opísané rozhranie môžete vidieť na obrázku v časti Vizualizácia riešenia.