Wir haben nun das Zeitalter der KI erreicht, weshalb hier weniger Lösungen und mehr das Technik Tagebuch Posts kommen.
Lösungen verlinke ich Kategorisch extra auf den weiteren Seiten des Blogs
Manchmal sind Lösungen so einfach, dass man sie in der Komplexität nicht mehr sieht. So hatte ich tatsächlich eine Menge XAML Code schreiben müssen, die sich praktisch immer wiederholten. Die Premisse war, dass eigentlich nur ein Wert anders war im Template. Die beschriebenen Trigger und Style Aufbauten im Template sind in den meisten fällen identisch für ein Steuerelement. Eine andere Lösung wäre den Aufbau per Behind Code zu schreiben, aber das war tatsächlich die aufwendigere Lösung. Aufwendig, weil mehr code entstehen würde als nötigt.
Umgebung
WPF, UWP oder XAML Anwendung
Verschieden und doch gleich
Beide Buttons, sollen die gleichen überschriebenen Animationen erhalten. Der Butten bekommt ein Path Objekt, um die Darstellung für jeden Button zu individualisieren. Dabei soll kein Redundanter Code entstehen.
Los geht’s
Der Ordnung halber, wird der Style in eine eigene Resourcendatei geschrieben. Falls Ihr ein Xaml Path Objekt braucht, gibt es auf dem folgenden Link eine Seite mit vielen verschienden Icons.
Ausgangslage In diesem Beispiel verwende ich das File-Icon und ein Folder-Icon. Im folgenden XAML wird zunächst gezeigt, wie zwei Styles die fast identisch sind und sich nur in dem eingesetzten Path Control unterscheiden. Der XAML Auszug kommt aus 'ButtonWithRepeadTemplate.xaml' (Leider lies sich das nicht vernüfitg hier im Blog abbilden, mit ausnahme eines Bildes). Die orange markierten Zeilen, sind die Stellen die sich unterscheiden und grün sind die Teile, dessen Inhalt gleich sind. (Ellipse habe ich nicht mit einbezogen)
Die Lösung ist nahezu offensichtlich, dass dieser Aufbau selbst als Basis Style festegelgt werden kann. Da es sich um einen Wert handelt, kann über die nicht verwendete DependencyProperty 'Content' später die eigentliche Zieländerungen eingesetzt werden. Das sieht dann im folgendem XAML Code aus.
BLAU: Da kann ein Path Element gesetzt werden mit einer Default Zeichnung.
GRÜN: Der Teil, der später nicht ein zweites mal geschrieben werden muss. Wobei hier angemerkt werden muss, dass auch der Teil in der 'Viewbox' nur in der Basis besteht.
Kommen wir zu den Ziel Button Styles, die als Basis Style (BaseOn) von 'ButtonTemplateBase' verwenden. Durch zuweisung eines Path Element in Content, wird das Aussehen in form eines Icon festgelegt. Also der Teil, der wirklich sich unterscheidet.
Ausprobieren
Mit diesen neunen Button Styles geht das Zuweisen auf der Windows.xaml (oder andere View) weiter, um dort die Resource einzubetten und dessen Styles auf die Ziel Buttons festzulegen.
Fertig Und so sollte das Ergebnis aussen. Beide Buttons sollte die gleichen Trigger Effekte haben.
Zum Schluss
Wie ich bereits am Anfang geschrieben habe, ist die Lösung simpel und sehr überschaubar. Selbst wenn deutlich mehr Wpf Steuerelemente mit dem gleichen Style Grundaufbau kommen, bleibt die Übersicht.
Aber wie sieht es aus, wenn sich zwei Inhalte im Template unterscheiden? Kurz gesagt, das geht. Aber dazu gehe ich in einem späteren Blogpost darauf ein.
Für die Lösung, wie eine Figur auf der Karte Bewegt werden kann, ist davon abhängig, wieviel Leistung mein Ziel System hat. In den meisten Fällen hat man mehr als genügend Leistung, so dass man sich hierbei entscheiden kann. Für relativ schwache System, z.B. wie bei einem Arduino Esplora kann eine Figur auf dem Bildschirm bewegt werden, ohne einem starken flackern. Eine Karte auf dem Bildschirm zu bewegen sieht dagegen wieder schlecht aus, weil der Bildaufbau zu lange dauert. Und das bei einer Auflösung von 128x160 Pixeln.
Nicht die Figur, sondern die Karte bewegt sich
Nun zurück zum Eigentlichen. Die Figur ist immer mittig auf dem Bildschirm und bewegt sich auf einer Karte. Aber Technisch gesehen, wird die Karte bewegt und die Figur hat eine Laufanimation.
Neues MonoGame Projekt
Aktuell wird für dieses Beispiel eine Neue Solution angelegt mit dem Ziel einer UWP Anwendung. Die Zielplattform und Version hat jedoch keinen Effekt für das Beispiel. Der Grund für UWP ist, das die Lauffähigkeit auf PC, Tablet, Raspberry Pi 2&3 und wenn ich mich nicht irre, auch auf der Xbox funktionieren soll. Sollte die Anwendung/Spiel auch auf dem Windows Phone laufen, dann ist eine ältere Minimum Ziel Version von Windows 10 einzustellen.
Ordnung ist das halbe Leben
Als erstes brauchen wir eine Projekt Struktur und legen ein paar Ordner an. Components wird es später drei Komponenten geben, die für die Benutzereingaben, die Karteverarbeitung und das Zeichnen zuständig sind.
Der Umfang für das Realisieren benötigt einige Zeilen Code, so das eine Aufteilung der Inhalte Notwendig sind. Deshalb kann zunächst in der 'Game1.cs' fast alles weg. Nur der Konstruktor bleibt.
using Microsoft.Xna.Framework;
namespace ExampleWalkingOnMap
{
public class Game1 : Game
{
private GraphicsDeviceManager _graphics;
public Game1()
{
this._graphics = new GraphicsDeviceManager(this);
this.Content.RootDirectory = "Content";
this.Window.Title = "Example Walking on map";
this.IsMouseVisible = true;
}
}
}
Eingabegerät
Für das erfassen von Tasteneingaben, wird die Klasse 'ComponentInputs.cs' und für die Steuerinformation 'InputData.cs' angelegt.
Zunächst muss die 'InputData.cs' bearbeitet werden, um die verarbeiteten Steuerinformationen weiter zutragen.
using Microsoft.Xna.Framework;
namespace ExampleWalkingOnMap.Components.Inputs
{
public class InputData
{
public Vector2 Move => new Vector2(this.MoveX, this.MoveY);
public float MoveX { get; set; }
public float MoveY { get; set; }
}
}
Mit der ‚InputData‘ können die Eingaben in ComponentInputs.cs aufgenommen werden. Im Beispiel wird die Tastatur und ein Xbox Controller eingelesen. Damit wäre die Eingabe Komponente fertig.
Die Realisierung der Karte wird in Kacheln unterteilt und ist auch relativ leicht um zusetzten. Jede Kachel hat eine eigene Position und eine Textur, womit schon mal das nächste Objekt definiert ist.
Für die Textur mit verschiedenen Untergründen wird eine Bilddatei zusammen gefasst, die die Unterschiedlichen Bodentexturen beinhaltet. Jedes Objekt kennt dann nur den Ausschnitt aus der Bilddatei. Für das Beispiel verwende ich die Texturen von https://kenney.nl/assets.
Im Projekt wurde bereits mit der Vorlage, der Ordner 'Content' angelegt. Hier wird nun die Textur abgelegt.
Nun muss die Bilddatei noch mit dem Pipline Tool verarbeitet werden, indem ihr doppelkick auf 'Content.mgcb' ausführt. Im Ausschnitt Projekt wird die Bilddatei hinzugefügt.
Wirkt leider etwas doppelt, aber dies ist notwendig, damit die Textur später im Programmcode geladen werden kann.
Anschließend auf den Button 'Build' oder F6 drücken und fertig ist dieser Teil und könnt das Tool wieder schließen.
Nun zurück zum Code für die Kachel Information. Wie bereits erwähnt, kennt die Kachel nur die Position der Textur, die später über die Methode Draw hinein gereicht wird. Mit dem weiteren Parameter 'offset' wird bestimmt, wo im Fenster gerendert wird.
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace ExampleWalkingOnMap.Components.Map
{
public class GroundTile
{
private Rectangle _texturePosition;
public GroundTile(int x, int y, int width, int height)
{
this._texturePosition = new Rectangle(x, y, width, height);
}
public void Draw(Texture2D textureMapTiles, SpriteBatch spriteBatch, Vector2 offset)
{
spriteBatch.Draw(textureMapTiles,
offset,
this._texturePosition,
Color.White,
0f,
new Vector2(0, 0),
new Vector2(1, 1),
SpriteEffects.None,
0f);
}
}
}
Die Karte soll aus 10x10 Kacheln bestehen und wird in einer Hilfsklasse erstellt.
Mit dem zwei Dimensionalen Integer Array, lässt sich visuell schnell eine Karte dieser Größe zusammenstellen, die wiederum noch übersichtlich ist. Zumindest im ersten Abschnitt der Methode. Anschließend wird hier in das Ziel Array gemappt mit den eigentlichen Ziel Objekt.
using System;
namespace ExampleWalkingOnMap.Components.Map
{
internal class MapHelper
{
internal static GroundTile[,] CreateMap()
{
int[][] map = new int[10][];
map[0] = new int[10] { 0, 0, 0, 0, 0, 0, 0, 0, 2, 2 };
map[1] = new int[10] { 0, 0, 0, 0, 0, 0, 0, 0, 2, 2 };
map[2] = new int[10] { 1, 1, 1, 1, 1, 1, 1, 0, 2, 2 };
map[3] = new int[10] { 0, 0, 0, 0, 0, 0, 1, 0, 2, 2 };
map[4] = new int[10] { 0, 0, 0, 0, 0, 0, 1, 0, 2, 2 };
map[5] = new int[10] { 0, 0, 0, 0, 0, 0, 1, 0, 2, 2 };
map[6] = new int[10] { 0, 0, 0, 0, 0, 0, 1, 0, 2, 2 };
map[7] = new int[10] { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 };
map[8] = new int[10] { 0, 0, 0, 0, 0, 0, 1, 0, 3, 3 };
map[9] = new int[10] { 0, 0, 0, 0, 0, 0, 1, 0, 3, 0 };
GroundTile[,] groundTiles = new GroundTile[10, 10];
for (int iY = 0; iY < 10; iY++)
{
for (int iX = 0; iX < 10; iX++)
{
groundTiles[iY, iX] = GetMappingGroundTile(map[iY][iX]);
}
}
return groundTiles;
}
private static GroundTile GetMappingGroundTile(int groundTile)
{
switch (groundTile)
{
case 0:
return new GroundTile(0, 0, 64, 64);
case 1:
return new GroundTile(64, 0, 64, 64);
case 2:
return new GroundTile(0, 64, 64, 64);
case 3:
return new GroundTile(64, 64, 64, 64);
}
throw new ArgumentException("groundTile can be only 0, 1, 2 and 3");
}
}
}
Für die Verwaltung der Karte wird die Klasse 'ComponentMap.cs' erstellt. Dort werden die Karteninhalte gesteuert und gehalten.
Der Umfang der Klasse ist hier größer als bei den anderen, auch wenn dessen Ausführung sehr klein gehalten ist.
using ExampleWalkingOnMap.Components.Inputs;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace ExampleWalkingOnMap.Components.Map
{
public class ComponentMap : GameComponent
{
private readonly ComponentInputs _inputs;
private GroundTile[,] _mapTiles;
private Texture2D _textureMapTiles;
private int _mapTileWidth, _mapTileHeight;
private Vector2 _screenOffset, _movePosition;
public Vector2 Position => new Vector2(this._movePosition.X + this._screenOffset.X,
this._movePosition.Y + this._screenOffset.Y);
private int _mapWidth, _mapHeight;
public ComponentMap(Game game, ComponentInputs inputs) : base(game) => this._inputs = inputs;
internal void SetScreenOffset(float screenWidth, float screenHeight)
{
var centerScreenWidth = screenWidth / 2;
var centerScreenHeight = screenHeight / 2;
this._screenOffset = new Vector2(centerScreenWidth, centerScreenHeight);
this._movePosition = new Vector2(0, 0);
}
public override void Initialize()
{
using (var stream = TitleContainer.OpenStream("Content/MapTiles.png"))
{
this._textureMapTiles = Texture2D.FromStream(this.Game.GraphicsDevice, stream);
}
this._mapTiles = MapHelper.CreateMap();
this._mapTileWidth = this._textureMapTiles.Width / 2;
this._mapTileHeight = this._textureMapTiles.Height / 2;
this._mapWidth = this._mapTileWidth * this._mapTiles.GetLength(0);
this._mapHeight = this._mapTileHeight * this._mapTiles.GetLength(1);
}
public override void Update(GameTime gameTime)
{
this._movePosition += this._inputs.Inputs.Move * 2f;
}
public void DrawMapTiles(SpriteBatch spriteBatch)
{
for (int iY = 0; iY < this._mapTiles.GetLength(0); iY++)
{
for (int iX = 0; iX < this._mapTiles.GetLength(1); iX++)
{
Vector2 textureOffset = new Vector2(iX * this._mapTileWidth, iY * this._mapTileHeight);
Vector2 offset = this.Position + textureOffset;
this._mapTiles[iY, iX].Draw(this._textureMapTiles, spriteBatch, offset);
}
}
}
}
}
In der Update Methode wird die Bewegung mit 2 Multipliziert. Wenn die Bewegung zu langsam erscheint, dann kann hier der Wert erhöht werden. Wie bereits angemerkt, zeigt dieser Teil das Nötigste, um eine Karte anzulegen und für das Rendern vorzubereiten.
Rendern
Am Ende muss die Karte auf dem Bildschirm gerendert werden. Dazu wird noch eine weitere Komponente angelegt mit dem Namen 'ComponentRender.cs'.
Damit die Karte auf die Start Position in die Mitte zentriert wird, wird die Größe des Fensters eingelesen und dessen Werte für den Offset der Karte zugewiesen.
using ExampleWalkingOnMap.Components.Map;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Windows.UI.ViewManagement;
namespace ExampleWalkingOnMap.Components.Render
{
public class ComponentRender : DrawableGameComponent
{
private readonly ComponentMap _componentMap;
private SpriteBatch _spriteBatch;
public ComponentRender(Game game, ComponentMap componentMap) : base(game)
{
this._componentMap = componentMap;
}
public override void Initialize()
{
this._spriteBatch = new SpriteBatch(this.GraphicsDevice);
var screenWidth = (float)ApplicationView.GetForCurrentView().VisibleBounds.Width;
var screenHeight = (float)ApplicationView.GetForCurrentView().VisibleBounds.Height;
this._componentMap.SetScreenOffset(screenWidth, screenHeight);
}
public override void Draw(GameTime gameTime)
{
this.GraphicsDevice.Clear(Color.DarkBlue);
this._spriteBatch.Begin();
this._componentMap.DrawMapTiles(this._spriteBatch);
this._spriteBatch.End();
}
}
}
Zurück zum Start
Am Ende müssen die Komponenten in die GameComponentCollection hinzugefügt werden, damit die Inhalte ausgeführt werden. Dazu geht es wieder in die 'Game1.cs' Code Datei. Hier werden in den Member die Komponenten eingetragen und im Konstruktor wird die 'UpdateOrder' zugewiesen und schließlich in die Collection aufgenommen.
using ExampleWalkingOnMap.Components.Inputs;
using ExampleWalkingOnMap.Components.Map;
using ExampleWalkingOnMap.Components.Render;
using Microsoft.Xna.Framework;
namespace ExampleWalkingOnMap
{
public class Game1 : Game
{
private GraphicsDeviceManager _graphics;
private ComponentInputs _componentInputs;
private ComponentMap _componentMap;
private ComponentRender _componentRender;
public Game1()
{
this._graphics = new GraphicsDeviceManager(this);
this.Content.RootDirectory = "Content";
this.Window.Title = "Example Walking on map";
this.IsMouseVisible = true;
this._componentInputs = new ComponentInputs(this);
this._componentInputs.UpdateOrder = 1;
this.Components.Add(this._componentInputs);
this._componentMap = new ComponentMap(this, this._componentInputs);
this._componentMap.UpdateOrder = 2;
this.Components.Add(this._componentMap);
this._componentRender = new ComponentRender(this, this._componentMap);
this._componentRender.UpdateOrder = 3;
this.Components.Add(this._componentRender);
}
}
}
Wenn ich hier im Blog-Post keinen Code ausgelassen habe, dann sollte sich die Anwendung starten lassen und die Karte mit den Tasten AWSD sich bewegen lassen. Oder auch mit dem Xbox Controller.
Nachwort
Mit ein wenig mehr Code, kann man dann einige Unreinheiten beheben, wie z.B. die Übergänge von Kachel zu Kachel sehen nicht immer sauber aus oder das der Untergrund Einfluss auf die Laufgeschwindigkeit hat. Und Überhaupt fehlt die Laufende Figur in der Mitte.
Die Anwendung läuft soweit auch auf einem Raspberry Pi mit Windows 10 IoT. Jedoch sollte man keine hohen Performance erwarten, da hier keine DirectX Unterstützung vorhanden ist. Im Folgenden Video zeige ich die Anwendung auf einem Bildschirm mit einer Eingestellten Auflösung von 640x350 Pixeln (Nativ sind jedoch 480x320). In HD, also 1920x1080p, sind weniger als fünf Bilder pro Sekunde zu erwarten. Zudem ist der Boot Vorgang relativ lang und bis die MonoGame App gestartet ist. Da vergehen schon einige Minuten.
Der Aufbau und das Prinzip funktioniert auch mit Windows Forms, WPF und XAML. Auch wenn diese UI Technologien nicht dafür gedacht sind, kann man sehen zumindest sehen wie Leistungsfähig die anderen sind.
Die fertige Solution habe ich wie immer auf dem GitHub geladen. Zusätzlich sind die meisten Inhalte kommentiert.
Was mit dem Netduino geht, geht für gewöhnlich auch auf dem Raspberry Pi (wenn es nicht gerade um PWM Ausgänge geht). Grundsätzlich hatte ich das Modul tatsächlich für den Rasperry Pi gekauft, um die Fehlende Ausgabemöglichkeit eines PWM Signal auszugleichen. Zwar kann man einen Pin so programmieren, dass ein PWM Signal erzeugt wird, aber ich fand dieses Lösung zunächst nicht sehr ansprechend.
Benötigt:
Raspberry Pi 2 oder 3
8GB SD Karte mit installierten Windows 10 IoT
Mindestens ein Servo zum Testen
Externe Spannungsquelle mit Maximal 6V
UWP Anwendung erstellen
Nam dem anlegen einer neuen Solution wird für den Zugriff auf die Schnittstelle I²C die Reference "Windows IoT Extensions for the UWP" hinzugefügt. Das Beispiel geht mit allen Version die für Windows 10 IoT und Raspberry Pi.
Fast identisch
Außer der Zugriff auf das Interface für I²C, ist der Aufruf gleich. Das Konfigurieren der Schnittstelle erfolgt z.B. nicht im Konstruktor, sondern in einer eigenen Initialisierungsmethode. Grund hierfür ist das abrufen der Instanz von I2cDevice das über die DeviceInformation abgeholt werden kann.
internal class Pca9685 {
private readonly byte _address = 0x40;
private readonly byte PCA9685_MODE1 = 0x00;
private readonly byte PCA9685_PRESCALE = 0xFE;
private readonly byte LED0_ON_L = 0x06;
private readonly byte LED0_ON_H = 0x07;
private readonly byte LED0_OFF_L = 0x08;
private readonly byte LED0_OFF_H = 0x09;
private I2cDevice _i2cDevice;
public Pca9685(int period) : base()
{
this._period = period * 1000;
}
private async Task Init()
{
var i2cSettting = new I2cConnectionSettings(this._address);
i2cSettting.BusSpeed = I2cBusSpeed.StandardMode;
var deviceSelector = I2cDevice.GetDeviceSelector();
var deviceInfo = await DeviceInformation.FindAllAsync(deviceSelector);
Zudem sind alle Methodenaufrufe Asynchron. Die Lösung ist auch in Synchron möglich, jedoch denke ich, dass die Verwendung von Async, Await und Task noch im Übersichtlichen Rahmen ist.
Allerdings muss man sich bewusst machen, obwohl die Methoden Asynchron ausgeführt werden können, müssen die Aufrufe nacheinander erfolgen. Überschneiden oder gleichzeitig ist Technisch für die Schnittstelle nicht möglich, weil die Bits nacheinander geschrieben werden können.
Zur Vollständigkeit kommen noch die restlichen Methoden. Das Umrechnen (GetPrescale) ist hier direkt kopiert und unterscheidet sich auch nicht mal von der C++ Version die in den Sourcecode von Adafruit fand.
Das Versenden der Bytes braucht nicht mehr code als die Netduino Lösung.
…
private byte GetPrescale(float frequency)
{
frequency *= 0.9f;
// internal clock frequency
float prescaleval = 25000000;
prescaleval /= 4096;
prescaleval /= frequency;
prescaleval -= 1;
return (byte)(prescaleval + 0.5);
}
private bool Write(byte[] buffer)
{
var result = this._i2cDevice.WritePartial(buffer);
if (result.Status != I2cTransferStatus.FullTransfer)
Bereits im Blog Post für das Netduino Beispiel, hatte ich den Eindruck, dass die Ansteuerung verdreht ist. Das Verhalten ist auch hier Identisch.
Verkabeln
Vier Leitungen reichen, um das Modul Anzusteuern. Hier sind keine Besonderheiten. Damit der Angeschlossene Servo zuverlässig reagiert, sollte eine Externe Spannungsquelle angeschlossen werden.
Die Wesentlichen Technischen Infos:
PWM Signal kann in 4096 Stufen gesteuert werden (12Bit)
PWM Signal kann zwischen 24Hz bis maximal 1526Hz eingestellt werden.
Die 16 Anschlüsse für die Stromversorgung der Servos, können maximal betrieben werden in Abhängigkeit der Angeschlossenen Spannungsversorgung.
PWM Output selbst kann maximal bis 25mA verwendet werden für LEDs
Spannungsversorgung für die Steuerung über I²C darf von 2,3V bis 5,5V liegen.
16 PWM Ausgänge
Kann über einen Externen Clock betrieben werden, der maximal bis 50MHz sein darf.
Mit einem Expansion Board für Raspberry Pi können die Shields für Arduino verwendet werden. In diesen Beispiel wird ein Motor Shield verwendet, zu dem ich bereits zwei Blog Einträge geschrieben habe. In dem Beispiel mit dem Netduino ist nur die Programmiersprache gleich, aber das verwendete Framework unterscheidet sich stark von einander. Allein schon das Konfigurieren eines Pins für den Raspberry Pi geht vollständig einen anderen Weg. Offen gestanden bin ich wohl an der Stelle konservativ und würde eher im dot Net Umfeld das .NET Micro Framework bevorzugen.
Benötigte Hardware:
Raspberry Pi 2 oder 3
Waveshare ARPI600 IO Expansion Board
Arduino MotorShield mit L293D
Windows IoT Extensions
Mit dem Anlegen einer neuen Solution als Universal Application oder kurz gesagt UWP, muss zunächst die Reference Windows IoT Extensions hinzugefügt werden. Je nach Ziel Version von Windows IoT kann die aktuelle passende Bibliothek hinzugefügt werden. Mit dieser werden dann Klassen bereit gestellt, mit dem der Zugriff auf die GPIOs möglich ist.
Neue MotorShield Klasse anlegen
Den Aufbau der Klasse ist fast wie aus dem Beispiel mit dem Netduino. Die öffentlichen Methoden sind die gleichen. Nur intern musste das Initialisieren der Hardware Pins anders gelöst werden. (Motor Treiber für Rover (.NETMF))
Für mich verwunderlich war, dass es keine PWM Ansteuerung gab oder ich habe das einfach übersehen. Zumindest nach kurzer Suche kamen keine passenden Ergebnisse, wie man die GPIOs mit einem PWM Signal ansteuert. Daher nur Digitales Gas geben.
Anzusteuernde Pins
Im Netz sind Zahlreiche Bilder, die die Pins beschreiben. Auf Waveshare (https://www.waveshare.com/wiki/ARPI600) sind die Pins ebenfalls beschrieben, allerdings ist das auf der Erweiterung anders gekennzeichnet. Hier war es nicht immer schlüssig, ob der Hardware Pin oder GPIO Pin gemeint ist. Am Ende habe ich festgestellt, dass man die Pin Ansicht von Raspberry Pi benötigt und dann auf dem ARPI600 die Beschriftung ablesen und vergleichen musste, um auf das richtige Mapping zu kommen.
Die folgende Tabelle zeigt, mit welchen Bezeichnungen man zu tun hat und wie diese zu einander passen.
Raspberry Pin
Raspberry GPIO
ARPI600
Arduino
MotorShield
8
UARTTO TX
P_RX
D0
10
UARTTO RX
P_TX
D1
11
17
P0
D2
12
18
P1
D3
M2
13
27
P2
D4
Clock
15
22
P3
D5
M4
16
23
P4
D6
M3
18
24
P5
D7
Enable Shiftregister
22
25
P6
D8
Data
7
4
P7
D9
24
8
CE0
D10
19
10
MOSI
D11
M1
21
9
MISO
D12
Latch
23
11
SCK
D13
Verhältnismäßigkeit
Abgesehen der fehlenden Stufenlosen Regeln der Motoren, ist die Steuerung nicht anders als mit einem Arduino oder Netduino. Um nur den Motor Treiber zu verwenden, ist das verwenden mit einem Raspberry Pi gerade zu übertrieben. Selbst mit dem Raspberry Pi 3 und Windows 10 IoT dauerte der Bootvorgang ca. eine Minute, bis dann endlich der Motor Treiber ausgeführt wird. Das relativiert sich dann jedoch, wenn man weitere Funktionalitäten implementiert.