Operácie

Krokomer s akcelerometrom MMA8491Q

Zo stránky SensorWiki

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:

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.z.

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;
}

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

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. Na obrázku môžete vidieť opísané rozhranie:

Nezabudnite napísať čosi ako užívateľský návod. Z neho by malo byť jasné čo program robí, ako sa prejavuje a aké má užívateľské rozhranie (čo treba stlačiť, čo sa kde zobrazuje). Ak ste namerali nejaké signály, sem s nimi. Ak je výsledkom nejaký údaj na displeji, odfotografujte ho.

Kľúčové slová 'Category', ktoré sú na konci stránky nemeňte.