Der Raspberry Pi ist zu vielem in der Lage, so können einfach Rotations- und Beschleunigugnswerte mittels eines Sensors, wie dem MPU-6050, ausgelesen werden. Das Ergebnis sind jedoch einfache Zahlen, worunter man sich im Normalfall nicht all zu viel vorstellen wird. Jedoch ist es sehr einfach diese Zahlen zu visualisieren. Dies geht in modernen Browsern ganz einfach mittels WebGL, womit man 2D und 3D Objekte im Browser rendern kann.
Dazu lassen wir einen Node.JS Webserver auf dem Raspberry Pi laufen, der die Daten sowohl abruft, als auch visualisiert. Der Beispielcode dafür ist am Ende des Tutorials angehängt.
Dieses Tutorial ist aufgrund einer der vielen Tutorial-Wünsche für den Raspberry Pi entstanden.
Zubehör
Neben einem Computer mit Browser, benötigst du folgendes Zubehör, um zu beginnen:
- Raspberry Pi (Modell egal)
- Beschleunigungssensor MPU 6050 (mit Pin Headern)
- Female-Female Jumper Kabel, ebay
Bei diesem Modul sind 2 unterschiedliche Pin Header anbei, wovon einer gebogen ist. Lötet man den gebogenen Pin Header an, spart man ein wenig Platz und die Bewegung später ist intuitiver.
Aufbau und Anschluss des MPU 6050 Sensors am Raspberry Pi
Um den Rotationssensor auslesen zu können, nutzen wir den sog. I2C Bus. Damit lassen sich verschiedene Sensoren mit lediglich einer Datenleitung (SDA) und einer „Clock“ (SCL) auslesen. Dies funktioniert mittels der Hardware Adresse eines Sensors (in unserem Fall 0x68). Der Vorteil von I2C ist, dass man nicht viele GPIO Pins belegen braucht und dennoch viele Daten auslesen kann.
Wir schließen die Jumper Kabel also an den MPU 6050 Sensor und an den Raspberry Pi wie folgt an:
Raspberry Pi | MPU 6050 |
---|---|
Pin 1 (3.3V) | VCC |
Pin 3 (SDA) | SDA |
Pin 5 (SCL) | SCL |
Pin 6 (GND) | GND |
Schematisch sieht das ganze folgendermaßen aus. Wie du siehst, werden die unteren vier Anschlüsse nicht benötigt und können frei bleiben.
Installation der benötigten Raspberry Pi Bibliotheken
Standardmäßig ist I2C am Raspberry Pi nicht aktiviert. Dies ist der erste Schritt, den wir durchführen. Öffne eine SSH Konsole und gib folgendes ein:
sudo raspi-config
Navigiere unter „Advanced Options“ und anschließend unter „Interfacing Options“. Hier findet sich u.a. ein Eintrag zu „I2C“. Diesen wählen wir aus und aktivieren es. Danach kannst du das Tool mit „Finish“ beenden.
Zeit für einen ersten Test! Wir orüfen, ob der Sensor erkannt wurde. Dazu geben wir folgendes ein und erhalten die untere Ausgabe:
sudo i2cdetect -y 1 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- -
Genaueres zur Installation (z.B. falls du Probleme dabei hast) findest du hier.
An Adresse 0x68 (Hexadezimal) befindet sich also ein I2C Gerät – in unserem Fall ist es das MPU 6050 Gyroskop.
Nun können wir auch bereits bestimmte Adressen des Sensors auslesen, wie bspw:
sudo i2cget -y 1 0x68 0x75
Die Registerdaten sind hierbei im Datenblatt definiert. Je nach Position, wirst du hier einen anderen Wert finden. Da wir mit den Hexadezimalen Ergebnissen nicht all zu viel anfangen können, gehen wir vorerst weiter zum nächsten Punkt.
Node.JS und Pakete installieren
Bevor die den Server starten können, müssen wir zunächst Node.JS und den Package Manager npm installieren (wer lieber yarn als Paketmanager nutzen möchte, kann das natürlich auch tun). Davor aktualisieren wir noch die Paketquellen:
sudo apt-get update
curl -sL https://deb.nodesource.com/setup_13.x | bash -
sudo apt-get install git nodejs npm -y
Anschließend klonen wir uns das Projekt aus Github, in dem der gesamte Code enthalten ist, gehen in das Verzeichnis und laden die benötigten Bibliotheken herunter:
git clone https://github.com/tutRPi/Raspberry-Pi-WebGL-Gyroscope cd Raspberry-Pi-WebGL-Gyroscope npm install
Der Vorgang dauert ein paar Minuten, aber sollte sich ohne weitere Probleme abschließen.
Node.JS Server starten und Rotation im Browser testen
Um den Server zu starten, reicht es folgendes Befehl einzugeben:
node index.js
Dadurch wird nun unter http://raspberrypi:3000/ verfügbar (ggf. musst du „raspberrypi“ mit der internen IP Adresse deines Pi’s ersetzen). Im Browser sieht das ganze dann so aus:
Zunächst einmal wird eine HTML Seite ausgegeben, die folgenden Inhalt hat und sich die Rotationsdaten unter http://raspberrypi:3000/api/data holt und diese visualisiert.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
<html> <head> <title>My First Node Project</title> <link rel="stylesheet" type="text/css" href="css/style.css"> <script src="js/jquery.min.js"></script> </head> <body> <canvas id="gl"></canvas> <script> // https://www.tutorialspoint.com/webgl/webgl_cube_rotation.htm /*============= Creating a canvas =================*/ var canvas = document.getElementById('gl'); gl = canvas.getContext('experimental-webgl'); /*============ Defining and storing the geometry =========*/ var vertices = [ -1,-1,-1, 1,-1,-1, 1, 1,-1, -1, 1,-1, -1,-1, 1, 1,-1, 1, 1, 1, 1, -1, 1, 1, -1,-1,-1, -1, 1,-1, -1, 1, 1, -1,-1, 1, 1,-1,-1, 1, 1,-1, 1, 1, 1, 1,-1, 1, -1,-1,-1, -1,-1, 1, 1,-1, 1, 1,-1,-1, -1, 1,-1, -1, 1, 1, 1, 1, 1, 1, 1,-1, ]; // transform cube to cuboid vertices = vertices.map(function(vertex, pos) { var p = (pos+1) % 12; if ( p % 3 == 2) { return vertex * 0.1; } return vertex; }); var colors = [ 5,3,7, 5,3,7, 5,3,7, 5,3,7, 1,1,3, 1,1,3, 1,1,3, 1,1,3, 0,0,1, 0,0,1, 0,0,1, 0,0,1, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0 ]; var indices = [ 0,1,2, 0,2,3, 4,5,6, 4,6,7, 8,9,10, 8,10,11, 12,13,14, 12,14,15, 16,17,18, 16,18,19, 20,21,22, 20,22,23 ]; // Create and store data into vertex buffer var vertex_buffer = gl.createBuffer (); gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); // Create and store data into color buffer var color_buffer = gl.createBuffer (); gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW); // Create and store data into index buffer var index_buffer = gl.createBuffer (); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW); /*=================== Shaders =========================*/ var vertCode = 'attribute vec3 position;'+ 'uniform mat4 Pmatrix;'+ 'uniform mat4 Vmatrix;'+ 'uniform mat4 Mmatrix;'+ 'attribute vec3 color;'+//the color of the point 'varying vec3 vColor;'+ 'void main(void) { '+//pre-built function 'gl_Position = Pmatrix*Vmatrix*Mmatrix*vec4(position, 1.);'+ 'vColor = color;'+ '}'; var fragCode = 'precision mediump float;'+ 'varying vec3 vColor;'+ 'void main(void) {'+ 'gl_FragColor = vec4(vColor, 1.);'+ '}'; var vertShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertShader, vertCode); gl.compileShader(vertShader); var fragShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragShader, fragCode); gl.compileShader(fragShader); var shaderProgram = gl.createProgram(); gl.attachShader(shaderProgram, vertShader); gl.attachShader(shaderProgram, fragShader); gl.linkProgram(shaderProgram); /* ====== Associating attributes to vertex shader =====*/ var Pmatrix = gl.getUniformLocation(shaderProgram, "Pmatrix"); var Vmatrix = gl.getUniformLocation(shaderProgram, "Vmatrix"); var Mmatrix = gl.getUniformLocation(shaderProgram, "Mmatrix"); gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer); var position = gl.getAttribLocation(shaderProgram, "position"); gl.vertexAttribPointer(position, 3, gl.FLOAT, false,0,0) ; // Position gl.enableVertexAttribArray(position); gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer); var color = gl.getAttribLocation(shaderProgram, "color"); gl.vertexAttribPointer(color, 3, gl.FLOAT, false,0,0) ; // Color gl.enableVertexAttribArray(color); gl.useProgram(shaderProgram); /*==================== MATRIX =====================*/ function get_projection(angle, a, zMin, zMax) { var ang = Math.tan((angle*.5)*Math.PI/180);//angle*.5 return [ 0.5/ang, 0 , 0, 0, 0, 0.5*a/ang, 0, 0, 0, 0, -(zMax+zMin)/(zMax-zMin), -1, 0, 0, (-2*zMax*zMin)/(zMax-zMin), 0 ]; } var proj_matrix = get_projection(40, canvas.width/canvas.height, 1, 100); //var mov_matrix = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]; var view_matrix = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]; // translating z view_matrix[14] = view_matrix[14]-6;//zoom /*==================== Rotation ====================*/ function rotateZ(m, angle) { var c = Math.cos(angle); var s = Math.sin(angle); var mv0 = m[0], mv4 = m[4], mv8 = m[8]; m[0] = c*m[0]-s*m[1]; m[4] = c*m[4]-s*m[5]; m[8] = c*m[8]-s*m[9]; m[1]=c*m[1]+s*mv0; m[5]=c*m[5]+s*mv4; m[9]=c*m[9]+s*mv8; } function rotateX(m, angle) { var c = Math.cos(angle); var s = Math.sin(angle); var mv1 = m[1], mv5 = m[5], mv9 = m[9]; m[1] = m[1]*c-m[2]*s; m[5] = m[5]*c-m[6]*s; m[9] = m[9]*c-m[10]*s; m[2] = m[2]*c+mv1*s; m[6] = m[6]*c+mv5*s; m[10] = m[10]*c+mv9*s; } function rotateY(m, angle) { var c = Math.cos(angle); var s = Math.sin(angle); var mv0 = m[0], mv4 = m[4], mv8 = m[8]; m[0] = c*m[0]+s*m[2]; m[4] = c*m[4]+s*m[6]; m[8] = c*m[8]+s*m[10]; m[2] = c*m[2]-s*mv0; m[6] = c*m[6]-s*mv4; m[10] = c*m[10]-s*mv8; } /*================= Drawing ===========================*/ var time_old = 0; var animate = function() { // get data $.ajax({ type: 'GET', contentType: 'application/json', url: '/api/data', success: function(data) { var mov_matrix = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]; var mov_matrix = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]; rotateZ(mov_matrix, ( data.rotation.y / 180) * Math.PI); // rotateY(mov_matrix, ( data.rotation.y / 180) * Math.PI); rotateX(mov_matrix, ( data.rotation.x / 180) * Math.PI); gl.enable(gl.DEPTH_TEST); gl.depthFunc(gl.LEQUAL); gl.clearColor(0.5, 0.5, 0.5, 0.9); gl.clearDepth(1.0); gl.viewport(0.0, 0.0, canvas.width, canvas.height); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl.uniformMatrix4fv(Pmatrix, false, proj_matrix); gl.uniformMatrix4fv(Vmatrix, false, view_matrix); gl.uniformMatrix4fv(Mmatrix, false, mov_matrix); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer); gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0); }, error: function(data) { console.error(data); } }); window.requestAnimationFrame(animate); } canvas.width = window.innerWidth; canvas.height = window.innerHeight; animate(); </script> </body> </html> |
Unter dem Endpunkt /api/data befindet sich ein Controller, der nichts weiter tut, als den Sensor mittels I2C Adresse auszulesen und die Werte im JSON Format auszugeben:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
const i2c = require('i2c-bus'); const MPU6050 = require('i2c-mpu6050'); const MPU_6050_ADDR = 0x68; exports.data = function(req, res) { const i2c1 = i2c.openSync(1); const sensor = new MPU6050(i2c1, MPU_6050_ADDR); const data = sensor.readSync(); i2c1.closeSync(); res.json(data); }; |
Das hier gezeigt Demo Projekt ist natürlich sehr einfach gehalten. Es können dementsprechend noch sehr viel komplexere Projekte und Visualisierungen mit WebGL und dem Raspberry Pi erstellt werden. Falls du ein ähnliches Projekt umgesetzt hast, oder dieses dir als Grundlage dient, kannst du es auf Github gerne forken und/oder hier für andere verlinken.
PS: Solltest auch du einen Tutorial-Wunsch haben, kannst du diesen natürlich gerne mit einer kleinen Beschreibung äußern 🙂
10 Kommentare
Gutes Projekt, echt gut 👍
Danke 🙂
Für ein Projekt in der Uni hab ich etwas ähnliches gemacht. Eine quasi Fernbedienung mit der etwas auf dem Schirm gezeigt bzw. gezeichnet werden kann und Gesten erkannt werden können. Umgesetzt ist das mit ThreeJS. Ist dann jetzt mit Dokumentation auf Github hochgeladen 🙂
https://github.com/FelixSchuermann/imu-remote
Das klingt ziemlich interessant! Hättest du vielleicht Lust daraus ein Gast-Tutorial zu machen? 🙂
Das mach ich gerne! Im moment schreibe ich allerdings meine Masterthesis, daher würde es wohl etwas dauern, bzw. muss ich dann gucken wie ich die Zeit finde. Ich melde mich bei dir, wenn ich etwas habe.
Lieben Gruß
Super, dann wünsche ich dir viel Erfolg dabei!
Viele Grüße
Felix
Hallo Felix
Verwende dein Projekt für mein Wohnmobilprojekt um die Lage des Fahrzeugs darzustellen. Meine Idee wäre ein Fahrzeugimage zu nutzen anstatt den Cube aus deiner Darstellung.
Gibt es für dieses Vorhaben schon was ähnliches ?
Vielen Dank für einen Hinweis.
Gruß Matthias Krefeld
Du müsstest dein Model als Mesh (zusammenhängende Dreiecke, die die Oberfläche repräsentieren) erstellen. Dafür kannst du Tools wie Blender nehmen, oder sie selbst in einem Koordinatensystem erzeugen.
hello sir,
Thanks for you nice tutorial.
I am try to get z axis rotation but I cant find a way.
Plase can you give me some hint about that.
Thank you very much
In Bullseye (with Python 3 preinstalled) when making the final „npm install“ and receiving not supported Python version error try:
sudo apt-get install python2.7
npm install –python=python2.7