====== 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 firmwaru pro FRDM-KL25Z ===== 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í [[https://bitbucket.org/erikfriesen/putty-hid|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: {{ 2015:hid-accelerometer:puttyHID.png }} Správná funkce programu: {{ 2015:hid-accelerometer:puttyHID2.png }} 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 #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; } } } } ===== Návrh aplikace pro PC ===== 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í [[https://github.com/MightyDevices/MightyHID|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: {{ 2015:hid-accelerometer:ConsoleHID.png }} 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. ==== Návrh vlastní aplikace pro zobrazení dat 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: {{ 2015:hid-accelerometer:start.png }} Zobrazované informace: {{ 2015:hid-accelerometer:nenalezeno.png }} {{ 2015:hid-accelerometer:lost.png }} === 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. {{ 2015:hid-accelerometer:time.png }} // 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. {{ 2015:hid-accelerometer:bar.png }} // 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. {{ 2015:hid-accelerometer:planes.png }} // 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. {{ 2015:hid-accelerometer:3D.png }} // 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). {{ 2015:hid-accelerometer:stats.png }} 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(); 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 ===== {{ youtube>P9hmCMS0joE?large }} ===== Dostupnost ===== Program, včetně firmwaru pro FRDM-KL25Z je dostupný ke stažení [[https://onedrive.live.com/redir?resid=BC609650F97EDA47!76397&authkey=!AI7sYmEFVWWvGx4&ithint=file%2czip|zde]] , alternativně [[http://leteckaposta.cz/937977646|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ě.