Individální projekty MPOA

Mikroprocesory s architekturou ARM

Uživatelské nástroje

Nástroje pro tento web


2015:hid-accelerometer

Vizualizace akcelerometru přes USB HID

Zadání

Pomocí vývojové desky FRDM-KL25Z vytvořte USB HID zařízení a jeho prostřednictvím přenášejte do PC údaje z akcelerometru. Tyto údaje na PC vhodně vizualizujte - např. pomocí bar grafů jednotlivých os, průmětu celkového vektoru zrychlení do jednotlivých rovin, zobrazení vektoru zrychlení ve 3D prostoru apod.

Návrh byl proveden pomocí prostředí mbed.org. K načítání okamžitých hodnot z akcelerometru je využita knihovna MMA8451Q, která byla použita v počítačovém cvičení MPOA. Odesílání dat je realizováno pomocí standardní mbed knihovny USBDevice, konkrétně její podkategorie USBHID. Tato knihovna při vytvoření standardního objektu USBHID automaticky zařízení přiřadí jméno „HID DEVICE“, VID 0x1234 a PID 0x0006.

Formátování hodnot pro přenos

Okamžité hodnoty zrychlení jsou načítány do proměnných typu float. Přenos těchto hodnot bude probíhat pomocí 8-bitových neznaménkových integerů v rámci objektu HID_REPORT, je tedy potřeba získané hodnoty upravit. Nulové hodnotě zrychlení odpovídá hodnota 128, ke které je následně přičtena aktuální hodnota z proměnné typu float, vynásobená číslem 50. Dynamika systému je tedy v obou směrech 2,56 G s rozlišením 0,02 G. Aby bylo zajištěno, že nedojde k přetečení prvků pole vlivem příliš vysoké hodnoty zrychlení, jsou proměnné ještě před uložením do reportu omezeny na maximální hodnotu 2,5 G.

        // Get Acc data
        float accx = acc.getAccX();
        float accy = acc.getAccY();
        float accz = acc.getAccZ();
        
        // Trim data
        if (accx > 2.5)  accx = 2.5;
        if (accx < -2.5) accx = -2.5;           
        if (accy > 2.5)  accy = 2.5;
        if (accy < -2.5) accy = -2.5;
        if (accz > 2.5)  accz = 2.5;
        if (accz < -2.5) accz = -2.5;
        
        report.data[0] = 0x01;
        report.data[1] = 128 + 50*accx;
        report.data[2] = 128 + 50*accy;
        report.data[3] = 128 + 50*accz;
        report.data[4] = NULL;
        report.length = 5;
        

Odesílání dat

Odesílání dat je realizováno pomocí funkce „send“, jejímž vstupním argumentem je objekt typu HID_REPORT. Vzhledem k tomu, že funkce vrací hodnotu typu boolean, je tato skutečnost využita k indikaci úspěšného odesílání dat pomocí blikání zelené LED s periodou 1 s. Samotné odesílání dat probíhá každých 100 ms. Při prvním připojení zařízení k PC začne dioda blikat, jakmile dojde k úspěšné instalaci ovladačů.

        // Send report & indicate state
        if(hid.send(&report))
        {
            led++;
            wait_ms(100);
            // Toogle green LED every 500 ms
            if(led == 5)
            {
                gled = gled xor 1;
                led = 0;
            }
        }

Ověření funkčnosti

Ještě než začneme odesílat data z akecelerometru, je vhodné, ověřit správnou funkci desky. K tomu je možné použít modifikovanou verzi klientu Putty, obohacenou o komunikaci s HID zařízením. Je k dispozici ke stažení zde . Klient jednotlivé příchozí byty reportu interpretuje jako znaky ASCII tabulky, je proto vhodné dočasně upravit report:

        USBHID hid(6,6);
        .
        .
        .        
        // For "ahoj " test
        report.data[0] = 0b01100001;
        report.data[1] = 0b01101000;
        report.data[2] = 0b01101111;
        report.data[3] = 0b01101010;
        report.data[4] = 0b00100000;
        report.data[5] = NULL;
        report.length = 6;

V jednotlivých prvcích pole jsou uloženy bitové reprezentace znaků textu „ahoj “. Text musí být zakončen ukončovacím znakem pro správnou interpretaci. Rovněž je potřeba upravit délku reportu v objektu USBHID.

Nastavení Putty:

Správná funkce programu:

Po ověření funkčnosti můžeme do reportu opět vrátit hodnoty z akecelerometru.

Kompletní zdrojový kód pro FRDM-KL25Z

#include "mbed.h"
#include <math.h>
#include "USBHID.h"
#include "MMA8451Q.h"

#define MMA8451_I2C_ADDRESS (0x1d<<1)

USBHID hid(5,5);
HID_REPORT report;
MMA8451Q acc(PTE25, PTE24, MMA8451_I2C_ADDRESS);
DigitalOut gled(LED_GREEN, 1);

int main() {
    
    int led = 0;
    
    while(1) {
            
        // Get Acc data
        float accx = acc.getAccX();
        float accy = acc.getAccY();
        float accz = acc.getAccZ();
        
        // Trim data
        if (accx > 2.5)  accx = 2.5;
        if (accx < -2.5) accx = -2.5;           
        if (accy > 2.5)  accy = 2.5;
        if (accy < -2.5) accy = -2.5;
        if (accz > 2.5)  accz = 2.5;
        if (accz < -2.5) accz = -2.5;
       
        // For "ahoj " test
        //report.data[0] = 0b01100001;
        //report.data[1] = 0b01101000;
        //report.data[2] = 0b01101111;
        //report.data[3] = 0b01101010;
        //report.data[4] = 0b00100000;
        //report.data[5] = NULL;
        //report.length = 6;
        
        // Transform data, create report
        report.data[0] = 0x01;
        report.data[1] = 128 + 50*accx;
        report.data[2] = 128 + 50*accy;
        report.data[3] = 128 + 50*accz;
        report.data[4] = NULL;
        report.length = 5;
        
        // Send report & indicate state
        if(hid.send(&report))
        {
            led++;
            wait_ms(100);
            // Toogle green LED every 500 ms
            if(led == 5)
            {
                gled = gled xor 1;
                led = 0;
            }
        }
       
    }
}

Aplikace pro PC byla vytvořena pomocí vývojového prostřední Microsoft Visual Studio 2015 v jazyce C#. K přijímání dat byla využita knihovna „MightyHID“, která je dostupná ke stažení zde. Knihovna podporuje tzv. „raw“ reporty, tedy typ, který potřebujeme. V archivu je k dispozici i referenční aplikace, funkčnost knihovny pro FRDM-KL25Z lze jednoduše otestovat.

Ověření funkčnosti knihovny

Testovací aplikace detekuje všechna viditelná HID zařízení, načež do konzole vypíše jejich jméno, VID a PID. Standardně se připojí k prvnímu zařízení v seznamu (index 0), což není vyhovující stav, jelikož každý počítač má připojen různý počet těchto zařízení a není vůbec jisté, že se aplikace připojí právě ke FRDM-KL25Z. Problém lze vyřešit zjištěním pořadí našeho zařízení v seznamu a následným zapsáním příslušného indexu do výběru připojovaného zařízení. V pozdější implementaci je výběr řešen na základě rozpoznání VID a PID, aby byla zajištěna detekce FRDM-KL25Z na libovolném PC. Aplikaci jsem dále rozšířil o schopnost zobrazovat data z přijímaného reportu, tedy dat z akecelerometru. Reporty jsou ukládány do pole typu byte, na jehož jednotlivé prvky je pohlíženo jako na 8-bitové neznaménkové integery - shoda s datovým typem přijímaného reportu.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Mighty.HID;

namespace MightyHIDTest
{
    class Program
    {
        static void Main(string[] args)
        {
            /* hello, world! */
            Console.WriteLine("List of USB HID devices:");
            /* browse for hid devices */
            var devs = HIDBrowse.Browse();
            /* display VID and PID for every device found */
            foreach (var dev in devs) {
                Console.WriteLine("VID = " + dev.Vid.ToString("X4") +
                    " PID = " + dev.Pid.ToString("X4") + 
                    " Product: " + dev.Product);
            }
            
            /* try to connect to first device */
            if (devs.Count > 0) {
                /* new device */
                HIDDev dev = new HIDDev(); 
                /* connect */
                dev.Open(devs[2]);
                /* an example of hid report, report id is always located 
                 * at the beginning of every report. Here it was set to 0x01.
                 * adjust this report so it does meet your hid device reports */
                byte[] report = new byte[6]; report[0] = 0x01;
                /* send report */
                //dev.Write(report);
                /* get response */
                dev.Read(report);

                Console.WriteLine("X: " + report[4]);
                Console.WriteLine("Y: " + report[3]);
                Console.WriteLine("Z: " + report[2]);
            }

            /* bye bye! */
            Console.Read();
        }
    }
}

Výstup programu:

Z výstupu programu je vidět, že naše zařízení bylo detekováno jako třetí - nutnost v kódu změnit index 0 na index 2. Po provedení příslušné úpravy program zobrazí jeden přijatý report, odpovídající formátovaným datům z akcelerometru.

Byla navržena formulářová aplikace, která nabízí několik možností zobrazení dat - formou bar grafů, průmětů v jednotlivých osách, formou zobrazení celkové akcelerace v 3D prostoru. Nad rámec zadání bylo zpracováno zobrazení dat formou zachycení časového průběhu zrychlení v jednotlivých osách a textové zobrazení základních statistických dat. Mezi jednotlivými zobrazeními je možno přepínat pomocí záložek. Aplikace je navržena tak, aby byla spustitelná na každém PC (automaticky detekuje FRDM-KL25Z pomocí VID a PID) a byla ovladatelná bez hlubší znalosti problematiky.

Aplikace dále umožňuje zastavit a obnovit zobrazování dat, resetovat statistiky, informuje o úspěšném připojení k zařízení. V případě neúspěšné detekce zařízení o této skutečnosti rovněž informuje, upozorňuje na možné příčiny problému a umožňuje další pokusy o připojení. Také ošetřuje událost, kdy je FRDM-KL25Z nečekaně odpojeno od PC.

Start aplikace:

Zobrazované informace:

Zobrazení časového průběhu zrychlení v jednotlivých osách

Zobrazení je řešeno pomocí objektu „chart“, typ „line“, přičemž je pro každou osu vytvořena řada o 200 bodech. Při každé aktualizaci hodnot jsou všechny hodnoty posunuty o jednu pozici doleva a do uvolněného místa jsou načteny hodnoty z aktuálně přijatého reportu. Následně je graf překreslen.

            // Initialize
            for (int i = 0; i <= 200; i++)
            {
                TimeChart.Series["X"].Points.AddXY(0, 0);
                TimeChart.Series["Y"].Points.AddXY(0, 0);
                TimeChart.Series["Z"].Points.AddXY(0, 0);
             }
             
            // Update
            for (int i = 0; i < 200; i++)
            {
                TimeChart.Series["X"].Points[i].SetValueY(TimeChart.Series["X"].Points[i + 1].YValues[0]);
                TimeChart.Series["Y"].Points[i].SetValueY(TimeChart.Series["Y"].Points[i + 1].YValues[0]);
                TimeChart.Series["Z"].Points[i].SetValueY(TimeChart.Series["Z"].Points[i + 1].YValues[0]);
             }
             TimeChart.Series["X"].Points[200].SetValueY(x);
             TimeChart.Series["Y"].Points[200].SetValueY(y);
             TimeChart.Series["Z"].Points[200].SetValueY(z);
             
             // Refresh
             TimeChart.Refresh();

Zobrazení pomoci bar grafů

Zobrazení je řešeno pomocí objektu „chart“, typ „range column“, přičemž všechny osy sdílí jednu řadu a každé je přiřazen jeden bod. Hodnota Y je vždy 0, aby sloupec začínal v počátku, hodnota X se mění v závislosti na aktuálně přijatých datech.

             // Initialize
            BarChart.Series["Series1"].Points.AddXY("X axis", 0, 0);
            BarChart.Series["Series1"].Points.AddXY("Y axis", 0, 0);
            BarChart.Series["Series1"].Points.AddXY("Z axis", 0, 0);
             
            // Update Bar graph
            BarChart.Series["Series1"].Points[0].SetValueY(x, 0);
            BarChart.Series["Series1"].Points[1].SetValueY(y, 0);
            BarChart.Series["Series1"].Points[2].SetValueY(z, 0);
            
            // Refresh
            BarChart.Refresh();

Zobrazení průmětů vektoru zrychlení do jednotlivých rovin

Zobrazení je řešeno pomocí objektu „chart“, typ „polar“. Tento graf chápe osu X jako úhel a osu Y jako absolutní hodnotu bodu - tyto parametry je tedy nutno z dat vypočítat pomocí známých vztahů. Úhel je počítán pro každý kvadrant zvlášť. Zobrazení zrychlení je řešeno pomocí bodu, po vzoru palubních systémů moderních automobilů, zobrazujících údaje z jízdy.

            // Initialize
            XYChart.Series["Series1"].Points.AddXY(0, 0);
            XZChart.Series["Series1"].Points.AddXY(0, 0);
            YZChart.Series["Series1"].Points.AddXY(0, 0);
            
            // Update
            if (x == 0) x = 0.001;  // Prevent division by zero
            if (y == 0) y = 0.001;
            if (z == 0) z = 0.001;

            if (x >= 0 && y >= 0) XYChart.Series["Series1"].Points[0].SetValueXY(Math.Atan(y / x) * 180 / Math.PI, xy_abs);
            if (x < 0 && y >= 0) XYChart.Series["Series1"].Points[0].SetValueXY(90 - Math.Atan(x / y) * 180 / Math.PI, xy_abs);
            if (x < 0 && y < 0) XYChart.Series["Series1"].Points[0].SetValueXY(180 + Math.Atan(y / x) * 180 / Math.PI, xy_abs);
            if (x >= 0 && y < 0) XYChart.Series["Series1"].Points[0].SetValueXY(270 - Math.Atan(x / y) * 180 / Math.PI, xy_abs);

            if (x >= 0 && z >= 0) XZChart.Series["Series1"].Points[0].SetValueXY(Math.Atan(z / x) * 180 / Math.PI, xz_abs);
            if (x < 0 && z >= 0) XZChart.Series["Series1"].Points[0].SetValueXY(90 - Math.Atan(x / z) * 180 / Math.PI, xz_abs);
            if (x < 0 && z < 0) XZChart.Series["Series1"].Points[0].SetValueXY(180 + Math.Atan(z / x) * 180 / Math.PI, xz_abs);
            if (x >= 0 && z < 0) XZChart.Series["Series1"].Points[0].SetValueXY(270 - Math.Atan(x / z) * 180 / Math.PI, xz_abs);

            if (y >= 0 && z >= 0) YZChart.Series["Series1"].Points[0].SetValueXY(Math.Atan(z / y) * 180 / Math.PI, yz_abs);
            if (y < 0 && z >= 0) YZChart.Series["Series1"].Points[0].SetValueXY(90 - Math.Atan(y / z) * 180 / Math.PI, yz_abs);
            if (y < 0 && z < 0) YZChart.Series["Series1"].Points[0].SetValueXY(180 + Math.Atan(z / y) * 180 / Math.PI, yz_abs);
            if (y >= 0 && z < 0) YZChart.Series["Series1"].Points[0].SetValueXY(270 - Math.Atan(y / z) * 180 / Math.PI, yz_abs);
            
            // Refresh
            XYChart.Refresh();
            XZChart.Refresh();
            YZChart.Refresh();

Zobrazení akcelerace v 3D prostoru

Žádný objekt integrovaný v prostředí Microsoft Visual Studio 2015 neumožňuje práci s daty ve třech osách. Bylo tedy nutné tento problém obejít. 3D zobrazení využívá 2D datové řady objektu chart, typ „Point“, přičemž je aktivována volba 3D zobrazení řad. Je generováno 256 řad po jednom bodu s vhodně zvolenou hloubkou bodu, aby bylo dosaženo zobrazení ve tvaru krychle. V základní konfiguraci je bod nastaven jako neviditelný, jako viditelný je nastaven pouze bod, nesoucí aktuální data. Souřadnice v osách X a Y jsou nastaveny běžným způsobem, pozice v ose Z je nastavena výběrem příslušné řady dle neupraveného vstupu z reportu pro osu Z.

Řešení je výkonově velmi náročné, tudíž vykreslení neprobíhá v reálném čase, ale se značným zpožděním. Pro lepší přehled jsou hodnoty zrychlení zobrazeny číselně vedle grafu. Bylo by vhodnější problém řešit jiným způsobem, například importem knihovny, která umí pracovat s daty v trojrozměrném prostoru.

            // Initialize
            for (int i = 0; i <= 255; i++)
            {
                ThreeDChart.Series.Add(Convert.ToString(i));
                ThreeDChart.Series[Convert.ToString(i)].ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Point;
                ThreeDChart.Series[Convert.ToString(i)].Points.Add();
                ThreeDChart.Series[Convert.ToString(i)].Points[0].Color = Color.Transparent;
                ThreeDChart.Series[Convert.ToString(i)].Points[0].MarkerStyle = System.Windows.Forms.DataVisualization.Charting.MarkerStyle.Circle;
                ThreeDChart.Series[Convert.ToString(i)].Points[0].MarkerSize = 15;
            }
            
            // Update
            int value = report[2];
            ThreeDChart.Series[Convert.ToString(prev_z)].Points[0].SetValueXY(0, 0);
            ThreeDChart.Series[Convert.ToString(prev_z)].Points[0].Color = Color.Transparent;
            ThreeDChart.Series[Convert.ToString(value)].Points[0].SetValueXY(x, y);
            ThreeDChart.Series[Convert.ToString(value)].Points[0].Color = Color.Blue;
            prev_z = value;
            ThreeDXBox.Text = Convert.ToString(Math.Round(x,2)) + " G";
            ThreeDYBox.Text = Convert.ToString(Math.Round(y,2)) + " G";
            ThreeDZBox.Text = Convert.ToString(Math.Round(z,2)) + " G";
            
            // Refresh
            ThreeDChart.Refresh();

Zobrazení základních statistických dat

Zobrazení je řešeno formou textu v boxech. Jsou k dispozici aktuální data, maximální, minimální a průměrné hodnoty zrychlení. Dále je zobrazen počet přijatých vzorků a celkový čas běhu. K zaznamenání hodnot slouží pole typu double. Maximální doba zaznamenávání je omezena na 6000 vzorků, čímž je navíc zamezeno přetečení čítačů (dobu by však šlo značně prodloužit).

        static double[] stats = new Double[19];
        /* 0 - samples, 1 - elapsed time, 2 - max X, 3 - max Y, 4 - max Z,
           5 - min X, 6 - min Y, 7 -  min Z, 8 - mean X, 9 - mean Y, 10 - mean Z
           11 - max XY, 12 - max XZ, 13 - max YZ, 14 - mean XY, 15 - mean XZ
           16  - mean YZ, 17 - max abs, 18 - mean abs*/

Kompletní zdrojový kód PC aplikace

Aby byla aplikace funkční, je nutné nejdříve navrhnout příslušný formulář a vložit do něj požadované objekty.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using Mighty.HID;

namespace WindowsFormsApplication2
{
    public partial class Form1 : Form
    {
        // Initialize global variables 
        static bool found = false;
        static bool retry = true;
        static bool play = true;
        static int prev_z = 0;
        static bool reset = false;
        static double[] stats = new Double[19];
        /* 0 - samples, 1 - elapsed time, 2 - max X, 3 - max Y, 4 - max Z,
           5 - min X, 6 - min Y, 7 -  min Z, 8 - mean X, 9 - mean Y, 10 - mean Z
           11 - max XY, 12 - max XZ, 13 - max YZ, 14 - mean XY, 15 - mean XZ
           16  - mean YZ, 17 - max abs, 18 - mean abs*/
        

        public Form1()
        {
            InitializeComponent();
        }

        private async void button1_Click(object sender, EventArgs e)        // "async" added to enable task delay
        {
            // Browse all connected HID devices and find supported HID device
            int select = 0;
            var devs = new System.Collections.Generic.List<Mighty.HID.HIDInfo>();
            while (!found)
            {
                await Task.Delay(10);       // Prevent UI freeze 
                if (retry)
                {
                    devs = HIDBrowse.Browse();
                    foreach (var device in devs)
                    {
                        if (device.Vid == 0x1234 && device.Pid == 0x0006) { select = devs.IndexOf(device); found = true; }
                    }
                    if (!found)
                    {
                        InstructionLabel.Text = "No supported HID device detected. Make sure your device is connected and has VID 0x1234 and PID 0x0006 and press Retry.";
                        retry = false;
                        RetryBtn.Show();
                    }
                }
            }

            // Connect
            HIDDev dev = new HIDDev();
            dev.Open(devs[select]);
            byte[] report = new byte[6]; report[0] = 0x01;
            InstructionLabel.Text = "Connected";

            // Initialize UI
            StartBtn.Hide();            
            PauseResumeBtn.Show();      
            TimeAccLabel.Show();
            TimeTimeLabel.Show();
            BarAccLabel.Show();
            ThreeDChart.Show();
            ProjectionsXYLabel.Show();
            ProjectionsYZLabel.Show();
            ProjectionsXZLabel.Show();
            ThreeDXlabel.Show();
            ThreeDYlabel.Show();
            ThreeDZlabel.Show();
            ThreeDYAxlabel.Show();
            ThreeDXAxlabel.Show();
            ThreeDZAxlabel.Show();
            StatsXMaxBox.Text = "0 G";
            StatsYMaxBox.Text = "0 G";
            StatsZMaxBox.Text = "0 G";
            StatsXMinBox.Text = "0 G";
            StatsYMinBox.Text = "0 G";
            StatsZMinBox.Text = "0 G";

            // Initialize graphs
            // Bar graph
            BarChart.Series["Series1"].Points.AddXY("X axis", 0, 0);
            BarChart.Series["Series1"].Points.AddXY("Y axis", 0, 0);
            BarChart.Series["Series1"].Points.AddXY("Z axis", 0, 0);

            // Projections
            XYChart.Series["Series1"].Points.AddXY(0, 0);
            XZChart.Series["Series1"].Points.AddXY(0, 0);
            YZChart.Series["Series1"].Points.AddXY(0, 0);

            // Time graph
            for (int i = 0; i <= 200; i++)
            {
                TimeChart.Series["X"].Points.AddXY(0, 0);
                TimeChart.Series["Y"].Points.AddXY(0, 0);
                TimeChart.Series["Z"].Points.AddXY(0, 0);
             }

            // 3D graph
            for (int i = 0; i <= 255; i++)
            {
                ThreeDChart.Series.Add(Convert.ToString(i));
                ThreeDChart.Series[Convert.ToString(i)].ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Point;
                ThreeDChart.Series[Convert.ToString(i)].Points.Add();
                ThreeDChart.Series[Convert.ToString(i)].Points[0].Color = Color.Transparent;
                ThreeDChart.Series[Convert.ToString(i)].Points[0].MarkerStyle = System.Windows.Forms.DataVisualization.Charting.MarkerStyle.Circle;
                ThreeDChart.Series[Convert.ToString(i)].Points[0].MarkerSize = 15;
            }
                
            while (true)
            {
                await Task.Delay(10);           // Prevent UI freeze  

                if (stats[0] == 6000)            // Maximum capture samples reached
                {
                    play = false;
                    stats[0] = 0;
                    InstructionLabel.Text = "Maximum stats capture time reached! Reset after Resume.";
                    PauseResumeBtn.Text = "Resume";
                    reset = true;
                }

                try
                {
                    dev.Read(report);           // Try to read report
                }
                catch
                {
                    InstructionLabel.Text = "Connection Lost! Please reconnect your HID device and restart the app.";
                    play = false;
                    PauseResumeBtn.Hide();
                }

                // Capture
                if (play)
                {
                    if (reset)
                    {
                        for (int i = 0; i <= 18; i++)
                        {
                            stats[i] = 0;
                            reset = false;
                            InstructionLabel.Text = "Connected";
                        }
                    }

                    stats[0]++;
                    stats[1] = stats[0] / 10;         // Calculate capture time
                      
                    // Update Acc values
                    double x = (report[4] - 128) * 0.02;
                    double y = (report[3] - 128) * 0.02;
                    double z = (report[2] - 128) * 0.02;

                    // Update accumulated values, needed for mean axis values calculation
                    stats[8] = stats[8] + x;
                    stats[9] = stats[9] + y;
                    stats[10] = stats[10] + z;

                    // Update Bar graph
                    BarChart.Series["Series1"].Points[0].SetValueY(x, 0);
                    BarChart.Series["Series1"].Points[1].SetValueY(y, 0);
                    BarChart.Series["Series1"].Points[2].SetValueY(z, 0);

                    // Update Time graph
                    for (int i = 0; i < 200; i++)
                    {
                        TimeChart.Series["X"].Points[i].SetValueY(TimeChart.Series["X"].Points[i + 1].YValues[0]);
                        TimeChart.Series["Y"].Points[i].SetValueY(TimeChart.Series["Y"].Points[i + 1].YValues[0]);
                        TimeChart.Series["Z"].Points[i].SetValueY(TimeChart.Series["Z"].Points[i + 1].YValues[0]);
                    }
                    TimeChart.Series["X"].Points[200].SetValueY(x);
                    TimeChart.Series["Y"].Points[200].SetValueY(y);
                    TimeChart.Series["Z"].Points[200].SetValueY(z);

                    // Update 3D graph
                    int value = report[2];
                    ThreeDChart.Series[Convert.ToString(prev_z)].Points[0].SetValueXY(0, 0);
                    ThreeDChart.Series[Convert.ToString(prev_z)].Points[0].Color = Color.Transparent;
                    ThreeDChart.Series[Convert.ToString(value)].Points[0].SetValueXY(x, y);
                    ThreeDChart.Series[Convert.ToString(value)].Points[0].Color = Color.Blue;
                    prev_z = value;
                    ThreeDXBox.Text = Convert.ToString(Math.Round(x,2)) + " G";
                    ThreeDYBox.Text = Convert.ToString(Math.Round(y,2)) + " G";
                    ThreeDZBox.Text = Convert.ToString(Math.Round(z,2)) + " G";

                    // Calculate Abs Acc values for Projections
                    double xy_abs = Math.Round(Math.Sqrt(Math.Pow(x, 2) + Math.Pow(y, 2)),2);
                    double xz_abs = Math.Round(Math.Sqrt(Math.Pow(x, 2) + Math.Pow(z, 2)),2);
                    double yz_abs = Math.Round(Math.Sqrt(Math.Pow(z, 2) + Math.Pow(y, 2)),2);

                    // Update accumulated values, needed for mean projections values calculation
                    stats[14] = stats[14] + xy_abs;
                    stats[15] = stats[15] + xz_abs;
                    stats[16] = stats[16] + yz_abs;

                    // Update Projections graphs
                    if (x == 0) x = 0.001;  // Prevent division by zero
                    if (y == 0) y = 0.001;
                    if (z == 0) z = 0.001;

                    if (x >= 0 && y >= 0) XYChart.Series["Series1"].Points[0].SetValueXY(Math.Atan(y / x) * 180 / Math.PI, xy_abs);
                    if (x < 0 && y >= 0) XYChart.Series["Series1"].Points[0].SetValueXY(90 - Math.Atan(x / y) * 180 / Math.PI, xy_abs);
                    if (x < 0 && y < 0) XYChart.Series["Series1"].Points[0].SetValueXY(180 + Math.Atan(y / x) * 180 / Math.PI, xy_abs);
                    if (x >= 0 && y < 0) XYChart.Series["Series1"].Points[0].SetValueXY(270 - Math.Atan(x / y) * 180 / Math.PI, xy_abs);

                    if (x >= 0 && z >= 0) XZChart.Series["Series1"].Points[0].SetValueXY(Math.Atan(z / x) * 180 / Math.PI, xz_abs);
                    if (x < 0 && z >= 0) XZChart.Series["Series1"].Points[0].SetValueXY(90 - Math.Atan(x / z) * 180 / Math.PI, xz_abs);
                    if (x < 0 && z < 0) XZChart.Series["Series1"].Points[0].SetValueXY(180 + Math.Atan(z / x) * 180 / Math.PI, xz_abs);
                    if (x >= 0 && z < 0) XZChart.Series["Series1"].Points[0].SetValueXY(270 - Math.Atan(x / z) * 180 / Math.PI, xz_abs);

                    if (y >= 0 && z >= 0) YZChart.Series["Series1"].Points[0].SetValueXY(Math.Atan(z / y) * 180 / Math.PI, yz_abs);
                    if (y < 0 && z >= 0) YZChart.Series["Series1"].Points[0].SetValueXY(90 - Math.Atan(y / z) * 180 / Math.PI, yz_abs);
                    if (y < 0 && z < 0) YZChart.Series["Series1"].Points[0].SetValueXY(180 + Math.Atan(z / y) * 180 / Math.PI, yz_abs);
                    if (y >= 0 && z < 0) YZChart.Series["Series1"].Points[0].SetValueXY(270 - Math.Atan(y / z) * 180 / Math.PI, yz_abs);

                    // Refresh graphs
                    BarChart.Refresh();
                    XYChart.Refresh();
                    XZChart.Refresh();
                    YZChart.Refresh();
                    TimeChart.Refresh();
                    ThreeDChart.Refresh();

                    // Update stats
                    // Curr axis
                    StatsXCurrBox.Text = Convert.ToString(x) + " G";
                    StatsYCurrBox.Text = Convert.ToString(y) + " G";
                    StatsZCurrBox.Text = Convert.ToString(z) + " G";

                    // Max axis
                    if (x > stats[2]) { StatsXMaxBox.Text = Convert.ToString(x) + " G"; stats[2] = x; }
                    if (y > stats[3]) { StatsYMaxBox.Text = Convert.ToString(y) + " G"; stats[3] = y; }
                    if (z > stats[4]) { StatsZMaxBox.Text = Convert.ToString(z) + " G"; stats[4] = z; }

                    // Min axis
                    if (x < stats[5]) { StatsXMinBox.Text = Convert.ToString(x) + " G"; stats[5] = x; }
                    if (y < stats[6]) { StatsYMinBox.Text = Convert.ToString(y) + " G"; stats[6] = y; }
                    if (z < stats[7]) { StatsZMinBox.Text = Convert.ToString(z) + " G"; stats[7] = z; }

                    // Mean axis
                    StatsXMeanBox.Text = Convert.ToString(Math.Round(stats[8] / stats[0],2) + " G");
                    StatsYMeanBox.Text = Convert.ToString(Math.Round(stats[9] / stats[0],2) + " G");
                    StatsZMeanBox.Text = Convert.ToString(Math.Round(stats[10] / stats[0],2) + " G");

                    // Curr projections
                    StatsCurrXYBox.Text = Convert.ToString(xy_abs) + " G";
                    StatsCurrXZBox.Text = Convert.ToString(xz_abs) + " G";
                    StatsCurrYZBox.Text = Convert.ToString(yz_abs) + " G";

                    // Max projections
                    if (xy_abs > stats[11]) { StatsXYMaxBox.Text = Convert.ToString(xy_abs) + " G"; stats[11] = xy_abs; }
                    if (xz_abs > stats[12]) { StatsXZMaxBox.Text = Convert.ToString(xz_abs) + " G"; stats[12] = xz_abs; }
                    if (yz_abs > stats[13]) { StatsYZMaxBox.Text = Convert.ToString(yz_abs) + " G"; stats[13] = yz_abs; }

                    // Mean projections
                    StatsXYMeanBox.Text = Convert.ToString(Math.Round(stats[14] / stats[0],2) + " G");
                    StatsXZMeanBox.Text = Convert.ToString(Math.Round(stats[15] / stats[0],2) + " G");
                    StatsYZMeanBox.Text = Convert.ToString(Math.Round(stats[16] / stats[0],2) + " G");

                    // Curr abs
                    double abs = Math.Round(Math.Sqrt(Math.Pow(x, 2) + Math.Pow(y, 2) + Math.Pow(z, 2)),2);
                    stats[18] = stats[18] + abs;                              // Update accumulated value, needed for mean Abs calculation
                    StatsAbsCurrBox.Text = Convert.ToString(abs) + " G";

                    // Max abs
                    if (abs > stats[17]) { StatsAbsMaxBox.Text = Convert.ToString(abs) + " G"; stats[17] = abs; }

                    // Mean abs
                    StatsAbsMeanBox.Text = Convert.ToString(Math.Round(stats[18] / stats[0],2) + " G");

                    // Samples, elapsed time
                    StatsSamplesBox.Text = Convert.ToString(stats[0]);
                    StatsTimeBox.Text = Convert.ToString(stats[1]) + " s";

                }

            }
            
        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }
 
        private void button2_Click(object sender, EventArgs e)
        {
            // Pause/Resume capture
            play = !play;
            if (!play) PauseResumeBtn.Text = "Resume";
            if (play) PauseResumeBtn.Text = "Pause";
        }

        private void button3_Click(object sender, EventArgs e)
        {
            // Exit app
            this.Close();
        }

        private void ResetBtn_Click(object sender, EventArgs e)
        {
            // Force stats to reset
            reset = true;
        }

        private void button1_Click_1(object sender, EventArgs e)
        {
            // Retry Connection to supported HID device
            retry = true;
            RetryBtn.Hide();
        }
    }


}

Demonstrační video

Dostupnost

Program, včetně firmwaru pro FRDM-KL25Z je dostupný ke stažení zde , alternativně zde . K používání není potřeba žádný dodatečný hardware.

Závěr

Byl vytvořen firmware pro FRDM-KL25Z, který přes standard HID komunikuje s počítačem a odesílá aktuální data z akcelerometru. Následně byla navržena PC aplikace, která tyto data vhodně vizualizuje. Projekt se podařilo zpracovat ve všech bodech zadání. Zobrazení 3D grafu by si zasloužilo vylepšení, současná metoda implementace se jeví jako méně vhodná, vzhledem k pomalým reakcím.

Poznámky

Windows 10 si příliš nerozumí s FRDM-KL25Z v bootloader módu - zařízení se neustále připojuje a odpojuje. Firmware pro mbed musel být na zařízení nahrán přes Windows 7. Následně již práce s vývojovou deskou probíhala korektně.

2015/hid-accelerometer.txt · Poslední úprava: 2016/01/17 18:23 autor: Jan Špůrek