Montag, 23. Oktober 2017

Du kommst hier nicht vorbei (Arduino Esplora, Part 6)


Sicherlich habt ihr entweder am Programcode oder beim Testen der Spielfunktionen bemerkt, dass die Kollisionsabfrage nur bedingt funktioniert. Sie ist zwar einfach, aber hier fehlt die Einschränkung, dass man sich nur von Block zu Block bewegen kann. Offen gestanden war ich kein Fan davon, das sich die Figur weiter bewegt bis der nächste Feld oder Kachel erreicht wurde.
Zu dem Thema Spieleprogrammierung und Kollisionsabfrage für 2D Spiele, können verschiedene Lösung im Internet gefunden werden. Ein Beispiel wird hier auf spieleprogrammierer.de/wiki beschrieben, wie man mit Geometrischen Objekten die Kollision Abfragen kann.


Die simple Form für die Kollisionserkennung ist das Verwenden von zwei Rechtecken. Im folgenden Code zeigt die Methode die wesentliche Abfrage von überschneidenden Rechtecken.

 // Kachel Position mit zukuenftiger Position der Figur abgeleichen,  
 // durch ansetzten von Rechtecken und ob diese sich ueberschneiden.  
 boolean checkCollide(byte positionX, byte positionY, byte mapOffsetX, byte mapOffsetY) {  
  if(positionX < mapOffsetX + mapTileSize &&  
     positionX + 10 > mapOffsetX &&  
     positionY < mapOffsetY + mapTileSize &&  
     positionY + 16 > mapOffsetY)  
   {  
    // DEBUG: Nur fuer debug und visuelle kontrolle  
    EsploraTFT.drawRect(positionX, positionY, 10, 16, 0xFA8A);  
    return false;  
   }  
  return true;  
 }  

Die Abfrage reicht jedoch nicht aus, um zu verhindern, dass die Figur wieder durch die Wand geht. Oft müssen auch übereinander oder nebeneinander liegende Kacheln zusätzlich geprüft werden. Für einen späteren Blogpost wird die Kachelgröße Reduziert von 16x16 auf 8x8. Spätestens dann wird die jetzige Abfrage erforderlich sein, alle Hindernisse zu erkennen. Das war leider nicht ganz ohne und zugegeben habe ich daran relativ viel Zeit damit verbracht, die Kollisionen durch zu debuggen.

 // Prüfen, ob in diesem Bereich sich bewegt werden kann.
boolean CanEnterArea(int positionX, int positionY) {
  boolean resultColide = true;
  
  // umliegende Kacheln auf hindernis prüfen
  // wenn hoch oder runter
  if(directionX == 0) {
    
    for(uint8_t i = 0; i < 3; i++) {
      
      int tileX = (positionX + (collisionTiles[i] * mapTileSize)) / mapTileSize;
      int tileY = -1;
 
      int tileYTemp = tileY;
      int positionYShift = 0;
 
      while(tileY == tileYTemp && tileY != 0) {
        if(directionY == -1) { 
          tileY = (positionY + directionY + positionYShift) / mapTileSize; 
          }
        else { tileY = (positionY + directionY + 16 + positionYShift) / mapTileSize; }
 
        positionYShift += mapTileSize * directionY;
      }
      
      resultColide = checkCollideNeighbor(positionX, positionY + directionY, tileX, tileY);
 
      if(!resultColide) {
        break;
      }
    }
  }
 
  // wenn links oder rechts
  if(directionY == 0) {
 
    for(uint8_t i = 0; i < 3; i++) {
      int tileX = positionX / mapTileSize;
      int tileY = (positionY + (collisionTiles[i] * mapTileSize)) / mapTileSize;
      
      int tileXTemp = tileX;
      int positionXShift = 0;
 
      while(tileX == tileXTemp) {
        if(directionX == -1) {  tileX = (positionX + directionX + positionXShift) / mapTileSize; }
        else { tileX = (positionX + directionX + 10 + positionXShift) / mapTileSize; }
        
        positionXShift += mapTileSize * directionX;
      }
  
      resultColide = checkCollideNeighbor(positionX + directionX, positionY, tileX, tileY);
  
      if(!resultColide) {
        break;
      }
    }
  }
  
  return resultColide;
}
 
// Laedt aus dem Flashspeicher die Kachelelemente ab und 
// prueft die Kollision mit neben anliegende Kacheln.
// Verhindert speziel den Fehhler zwischen zwei Kacheln, nur eine zu pruefen.
boolean checkCollideNeighbor(int positionX, int positionY, int tileX, int tileY) {
 
  boolean resultColide = true;
 
  int mapOffsetX = tileX * mapTileSize;
  int mapOffsetY = tileY * mapTileSize;
 
  int indexStart = (tileY * mapTileCountX) + tileX;
  // hole die content Nummer ab um die kollisionsart zu bestimmen
  byte bTile = pgm_read_byte_near(mapContent + indexStart);
 
  if(bTile == 1 && resultColide) {
 
    // DEBUG: Nur fuer debug und visuelle kontrolle
    EsploraTFT.drawRect(mapOffsetX, mapOffsetY,  mapTileSize, mapTileSize, 0xFA8A);
    resultColide = checkCollide(positionX, positionY, mapOffsetX, mapOffsetY);
  }
 
  return resultColide;
}
 

Nun eckt die Figur in positiven Sinne überall an und kann sich nicht mehr wie ein Geist durch die Wand bewegen. In einen späteren Post wird die Kollisionsabfrage auch für Türen verwendet, um z.B. einen Kartenwechsel auszulösen.
Für Debug und Demo Zwecke, werden die Rechtecke mit eingezeichnet, die visuell die Kollision abbilden.


Der Clip zeigt die ungenaue Kollisionsabfrage, wie sie zuvor war. Wie bereits beschrieben, war diese simple und schnell umgesetzt.


Mit der implementieren der Abfrage von überschneidenden Rechtecken sieht das Ergebnis besser aus.




Nächster Post: Ich packe in  meinen Rucksack (Arduino Esplora, Part 7)

Zu guter letzt der gesamte Programmcode auf Github

Github - BlogPost_06_BetterCollision

Samstag, 21. Oktober 2017

Voller Arbeitsspeicher (Arduino Esplora, Part 5)


Der Arduino oder auch vielmehr der verwendete Mikrocontroller hat für viele Anwendungen genügend Arbeitsspeicher. Im ersten Teil der Blogpost Reihe verwendete ich einen Arduino Nano, der einen ATmega328 hat und einen Arbeitsspeicher von 2kByte besitzt. Der Arduino Esplora verwendet den ATmega32u4 der wiederum 2,5kByte Arbeitsspeicher aufweist. Trotz des etwas größeren Arbeitsspeichers muss für dieses Projekt dennoch sparsam damit umgegangen werden.

ATmega328P und ATmega32u4

Arbeitsspeicher verbrauch
Ein Sprite Bild besteht selbst aus 160 Bytes. Das klingt jetzt nicht viel, aber verbraucht den Arbeitsspeicher bereits mit über 6%. Würde man die Sprite Animation der Figur nicht mit dem Trick einzelner Bilder spiegeln, dann würden insgesamt 2,92kByte Arbeitsspeicher anfallen. Stattdessen werden momentan 1,12kB verwendet, dass allerdings für das Ziel immer noch zu viel ist. Und dann kommt noch die Karte mit 160 Bytes hinzu, die noch sehr grob ist. Da bleibt am Ende nicht viel übrig. Der jetzige Sketch verwendet ca. 1,575kBytes Arbeitsspeicher.

Vom Flashspeicher
Im Gegensatz zu dem insgesamten Flash Speicher mit 32kB, ist dieser gerade mal mit 12,45kB belegt. Damit liegt nahe, dass Sprites und weitere Daten am besten zur Laufzeit geladen werden. Hier kommt ein Kompromiss zustande über die Lesegeschwindigkeit von Flasch und RAM.
Eine kleine Umstellung und die Byte Array lassen sich aus dem Flashspeicher lesen, wenn diese zur Laufzeigt benötigt werden. Die folgenden Ergebnisse nach dem Kompilieren zeigen den Unterschied zwischen dem Sketch vom letzten Stand mit dem Anlegen der Karte und das gleiche jedoch nach der Umstellung mit PROGMEM.

Ohne PROGMEM

Mit PROGMEM

Weitere Informationen könnt ihr auf der Arduino Seite über PROGMEM erfahren.

Das folgende Code Ausschnitt zeigt die Änderung der Funktionsvariable eines Byte Array ergänzt wird.

// Figur Sprites load from lokal
byte spriteFigureFrontLeft[160] = { …

// Figur Sprites load from flash
const PROGMEM byte spriteFigureFrontLeft[160] = { …

Sobald alle Byte Arrays mit dem Präfix 'const' und 'PROGMEM' erweitert wurden, dürfte der Belegte Speicher deutlich gesunken sein. Ein Byte Array bleibt allerdings immer im Speicher, dass ist der Buffer oder wie im Beispiel 'tempArray' benannt wird aus dem Flash in das Byte Array geladen, das über eine einfache Funktion übertragen wird.

// kopiert den Array Inhalt vom Flashspeicher in den SRAM
void memCopy(byte arrayContent[]) {
  for(byte index = 0; index < 160; index++) {
    tempArray[index] = pgm_read_byte_near(arrayContent + index);
  }
}

Wann ist der Einsatz von PROGMEM Sinnvoll
Alle Funktionsvariablen die nicht zur aktuellen Ausführung verwendet werden, könnten über die Funktion erweitert werden. Also eine Spriteanimation rendert immer nur eines der angelegten Sprites.

Nächster Post: Du kommst hier nicht vorbei (Arduino Esplora, Part 6)

Donnerstag, 19. Oktober 2017

Bibliotheken installieren für den Wemos@Lolin


Kaum angeschaut, habe ich mir den Wemos mit OLED Display beim Chinesischen Händler bestellt. Dann ca. vier Wochen später lag nun das Wemos@Lolin auf meinem Tisch und versuchte gleich ein Beispiel Code darauf zu schreiben. Leider musste ich zunächst feststellen, dass die bereits bei mir installierte Bibliothek nicht dieses Board aufführte. Und damit fing die Abendliche Suche an.

Nach kurzer suche fand ich diesen Link zu espressif. Zugegeben wollte ich nicht noch ein Tool installieren, dass mir im Grunde nur die Dateien in das Ziel Verzeichnis kopiert, also klickte ich auf den Download Button von dieser Seite des Github Accounts. Anschließend kopierte ich die Sourcen in den selbst angelegten Ordner "esp32/esp32" im Unterverzeichnis der Arduino Anwendung "../Arduino/hardware/"


Nach dem Start der Arduino Anwendung konnte unter Werkzeuge => Bord => WEMOS LOLIN32 ausgewählt werden.


Dann noch den Port auswählen und die Verbindung konnte hergestellt werden. Weitere Einstellungen mussten nicht vorgenommen werden.


Zuletzt fehlt noch die Bibliothek für die OLED Display von squix78 Github Account.

Nach dem Zip Download kann der entpackte Inhalt unter "…/Dokumente/Arduino/libraries/.." hinein kopiert werden.


Jetzt kann endlich ein Programmcode auf den Wemos geschrieben werden, dass auch das integrierte OLED ansteuern kann. Zu der OLED Bibliothek liegen bereits ein paar Beispiele.


Damit diese auch funktionieren, muss die Pin Zuweisung geändert werden. Sonst erhält ihr die Meldung "'D3' was not decleared in this scope".


Hierzu ändert die Pin Zuweisung auf 5 und 4. Dann sollte sich er Programmcode kompilieren lassen.


Damit nun der Programmcode auch auf dem Wemos geschrieben werden kann, muss mit dem hochladen die ‚Boot‘ Taste gedrückt werden.


Erst dann lässt sich das Programm erfolgreich hoch laden und euer Ergebnis ansehen.


Hier nochmal die Links zusammengefasst:

Montag, 16. Oktober 2017

Karte anlegen (Arduino Esplora, Part 4)


Eine Figur durch einen leeren Raum zu steuern, ist auf Dauer sehr öde. Man kann nun den Hintergrund zunächst eine Farbe geben, ist aber dennoch sehr eintönig ist. Schauen wir uns andere Spiele an, könnte man meinen, dass alles in der Umgebung in Blöcken unterteilt ist.
Und so wird dies auch in diesem Beispiel umgesetzt. Die Karte wird Blockweise angelegt. Das ermöglicht uns weiterhin nur die Bereiche neu zu rendern, die sich auch geändert haben.

Karten Eigenschaften
Mit der Unterteilung in Blöcken, kann ein Block verschiedene Eigenschaften aufweisen. Hier stellt die '0' die Frei Begehbaren Blocke da, in dem sich die Figur bewegen kann. Der Wert '1' wiederum stellt eine Mauer da, an dem die Figur nicht hindurch gehen kann. Die Fläche eines Blockes ist etwas größer als die der Figur. Daher weist die Kanten länge 16 Pixel mal 16 Pixel auf.


Ordnung ist das halbe Leben
Zunächst muss vorweg etwas Ordnung eingebracht werden. Zwar habe ich bereits mit dem letzten Post die Funktionen zu der Figur in einen eignen Tab/Seite eingesetzt, aber ich bin nicht weiter darauf eingegangen.
Damit nicht zu viel Code auf einer Seite ist, teilen wir die Inhalte in Zugehörigkeiten auf. Somit kommen die Funktionen/Methoden für die Farbe und das Ausfüllen eines Quadrates in einen eignen neuen Tab mit dem Namen 'RenderComponent'. Die Funktionen zur Karte werden unter 'MapComponent' abgelegt und 'FigureComponente' sollte nur noch die Inhalte zur Spielfigur haben.












Das Schreiben von Pixeln
Die Methode mit dem die Farbnummern, die die Farbwerte für die Ausgabe zurückgibt, kommt in den 'RenderComponent'.  Die zuvor verwendete Funktion/Methode drawFigurArray wird zusammengefasst und um weitere Parameter erweitert, die dann drawTile genannt wird. Im späteren Blog Post Teil, wird die Funktion/Methode nicht nur für das Rendern der Figur eingesetzt.

 void drawTile(int relationX, int relationY, byte tileWidth, byte tileHeight, byte tilePic[], boolean mirror) {  
  int index = 0;  
  for(int y = 0; y &lt; tileHeight; y++) {  
   for(int x = 0; x &lt; tileWidth; x++) {  
     int indexTarget = index;  
     if(mirror) {  
      indexTarget = index - x + (tileWidth - x) - 1;  
     }  
     byte colorNumber = tilePic[indexTarget];  
     // Nur Farbe  
     if(colorNumber != 0) {  
      EsploraTFT.drawPixel(relationX+x, relationY+y, mapNumberToColor(colorNumber));  
     }  
     index++;  
   }  
  }  
 }  

 uint16_t mapNumberToColor(byte c) {  
  uint16_t result = ST7735_RED;  
  switch(c) {  
   case(1):{ result = ST7735_BLACK; break; }  
   case(2):{ result = 0xF590; break; } // haut  
   case(3):{ result = 0x81E1; break; } // braun  
   case(4):{ result = 0xC2C2; break; } // hell braun  
   case(5):{ result = 0x8300; break; } // braun gelb  
   case(6):{ result = 0x5406; break; } // gruen  
   case(7):{ result = 0x32A4; break; } // dunkel gruen  
   case(8):{ result = 0xAE91; break; } // hell gruen  
   case(9):{ result = 0x2146; break; } // dunkel grau blau  
   case(10):{ result = 0x31E9; break; } // grau blau  
   case(11):{ result = 0x84B6; break; } // hell blau  
   case(13):{ result = 0xFC08; break; } // orange  
   case(14):{ result = 0xFA8A; break; } // hell rot  
   case(15):{ result = 0xD759; break; } // hell gruen 2  
   default: {  
    result = 0;  
    break;  
   }  
  }  
  return result;  
 }  

Sprite Render Methode ändert sich
Nun sollte auch der Programmcode in 'FigureComponent' angepasst werden. Die Methode 'drawFigure' hatte zum Zeichnen die Methode 'drawFigureArray' und wird nun mit der Methode 'drawTile' aus dem Tab 'RenderComponent' ersetzt. Folgender Code zeigt einen Ausschnitt der Änderung. Wie zu sehen ist, wird nun die Breite und Höhe das Sprite übergeben und der Parameter für 'Clear' entfällt.

…
    if(directionX == 0 && directionY == 1) {
      switch(animStep){
        case(0): { drawTile(relationX, relationY, 10, 16, spriteFigureFrontLeft, false); break; }
        case(1): { drawTile(relationX, relationY, 10, 16, spriteFigureFrontMiddle, false); break; }
        case(2): { drawTile(relationX, relationY, 10, 16, spriteFigureFrontLeft, true); break; }
        default: {  drawTile(relationX, relationY, 10, 16, spriteFigureFrontMiddle, false); break; }
      }
    }
…

Die Karte
Für das Anlegen einer Karte wird ein weiteres Byte Array angelegt. Ein Byte stellt eine Kachel Information da. Wie bereits am Anfang des Posts beschrieben, ist '0' Begehbar und '1' wiederum nicht. Da pro Kachel 16 Pixel mal 16 Pixel groß ist, können in der Breite zehn Kachel Nebeneinander aufgestellt werden. Untereinander werden sechs Kacheln angelegt. Der Restliche Bereich unten bleibt frei für später kommende Spieldaten.

Über die Kacheln und anecken
Die erste Methode 'renderMap' liest das Array ein, dass die Karteninformation auf das Display Zeichnet. Mit 'canEnterArea' wird auf einfache Weise die zu betretende Kachel geprüft, ob diese begehbar ist. Wo die Funktion eingesetzt wird, komme ich in einen späteren Absatz . In einen späteren Absatz gehe beschreibe ich, wo diese Funktion ihren Einsatz findet.
Hier sei Angemerkt, dass die Kollisionsabfrage wirklich sehr simple ist, so dass ein durchlaufen unter Umständen dennoch möglich ist. Eine bessere Lösung zu diesen Thema, gehe ich jedoch erst in einen späteren Post darauf ein.

Zuletzt für das Zeichnen einer Kachel, unternimmt die Methode 'renderMapTile' eigentlich zwei Aufgaben. Sie ruft zu dem Byte eine Farbnummer ab und verwendet diesen Wert, um eine Kachel Ausgefüllt auf dem Display zu zeichnen. Genauere Beschreibungen zu den Funktionen und Variablen findet im Sourcecode.

// Karte
byte mapContent[160] =  { 
  1,1,1,1,1,1,1,1,1,1,
  1,0,1,0,0,0,0,0,0,1,
  1,0,1,0,0,1,0,0,0,1,
  1,0,1,0,0,1,1,1,0,1,
  1,0,0,0,0,0,0,0,0,1,
  1,1,1,1,1,1,1,1,1,1,
};

// Groeße einer Kachel
byte mapTileSize = 16;
// Anzahl Kacheln auf der X Achse
byte mapCountX = 10;
// Anzahl Kacheln auf der Y Achse
byte mapCountY = 6;

void renderMap(int positionX, int positionY, boolean renderAll) {
  byte index = 0;
  for(byte y = 0; y < mapCountY; y++) {
    for(byte x = 0; x < mapCountX; x++) {

      if(((positionX >= (int)(x * mapTileSize) - (int)mapTileSize && positionX <= (int)(x + 1) * (int)mapTileSize && 
          positionY >= (int)(y * mapTileSize) - (int)mapTileSize && positionY <= (int)(y + 1) * (int)mapTileSize)) || 
          renderAll) {
        renderMapTile(x, y, mapContent[index]);
      }
      index++;
    }
  }
}

// Einfache Kollisionsabfrage
boolean canEnterArea(byte positionX, byte positionY) {

  // Kachel Kordinate abrufen
  byte tileX = positionX / 16;
  byte tileY = positionY / 16;

  // Index aus dem Array abfragen
  byte index = (tileY * mapCountX) + tileX;

  // ist das Feld begehbar
  if(mapContent[index] == 0) {
    return true;
  }
  
  return false;
}

// rendert die Kacheln Einfarbig.
void renderMapTile(byte x, byte y, byte mapSegment) {
  
  byte mapTileColorNumber = 0;
  switch(mapSegment) {
    case(1): { mapTileColorNumber = 10; break; }
    default: { mapTileColorNumber = 15; break; }
  }

  EsploraTFT.fillRect(x * mapTileSize, y * mapTileSize, mapTileSize, mapTileSize, mapNumberToColor(mapTileColorNumber));
}

Blockade prüfen
In der 'loop' Funktion wird je nach Ausrichtung die Position um einen hoch oder runter gezählt. Mit der neuen Funktion 'canEnterArea' aus dem Tab 'MapComponent' kann nun verhindert werden, dass die Figur nicht in eine Blockwand laufen kann. Der folgende Code zeigt die Bedingung für Links mit der neuen Funktion und der Parameter Übergabe über die nächste Position.

…
if(buttonLeft && !buttonRight && lastPosX > 0) {
    // nach links und letzte Position Y ist groesser als '0'.
    if(canEnterArea(lastPosX - 1, lastPosY)) {
      lastPosX--;
    }
  }
…

Offenes und ausbessern
Es funktioniert zwar schon, aber so ganz Rund sind die gängigen Funktionen noch nicht. Die kollisionsabfrage funktioniert nicht unter jeder Bedingung und die Figur flimmert. Dennoch sind diese Groben Ausführungen keine Primären Probleme und werden daher später verbessert, sobald diese ein Problem darstellen.
Der Nächste Schritt ist die Arbeitsspeicherauslastung zu verbessern. Denn derzeitig werden noch nicht viele Inhalte angezeigt, aber der Arbeitsspeicher ist mit den jetzigen Inhalten ist fast voll.

Nächster Post: Voller Arbeitsspeicher (Arduino Esplora, Part 5)


Donnerstag, 12. Oktober 2017

.Net Core und die fehlende Exe


Für gewöhnlich erwartet man nach dem Kompilieren in Visual Studio, dass sich im Debug oder Release Ordner eine Datei mit der Endung *.exe befindet. Dass die Dot NET Core Anwendung da anders ist, zeigt sich hier spätestens hier, uns mit einer DLL Datei, die sich so zunächst nicht ausführen lässt.
Damit sich die Anwendung auch ohne Visual Studio Starten lässt, muss die Konsole geöffnet werden, dann in das Verzeichnis wechselt und dann 'dotnet ConsoleHelloDotNetCore.dll' eingegeben werden.


Jedes Mal die Console zu öffnen und bis in das entsprechende Verzeichnis zu Wechsel, kann sich als sehr langwierig und nervig erweisen. Eine einfache Lösung ist, eine Batch Datei (*.bat) zu erstellen, mit der sich dann anschließend wie gewohnt sich das Programm starten lässt.


Doppel Klick auf die Batch und die Anwendung läuft.


Dienstag, 3. Oktober 2017

Arduino Control (Teil 6) - LED über LAN einschalten


Das Internet der Dinge geht die meisten Wege über eine Netzwerk Verbindung. Mit dem passenden Shield für Arduino kann die Netzwerkverbindung hergestellt werden. Mit relative wenig Programm Code kann eine simple Datenübertragen vom PC an den Arduino versendet werden.


Ethernet Shield und LED
Für das Beispiel wird das Ziel sein, die LED auf dem Arduino ein und Auszuschalten. Auf dem PC kommt wiederum eine Consolen Anwendung der die Befehle über die LAN Verbindung versenden kann. Der Arduino benötigt für den Empfang den Ethernet Shield, dass wiederum die selbe Verbindung zum Netzwerk hat wie der PC.


Grüne Low Current LED mit einem 2,2kOhm Widerstand


Consolen Anwendung
Fangen wir zunächst mit der Consolen Anwendung an und legen zunächst eine neue Klasse an mit dem Namen 'NetworkHost.cs'. Für die Verbindung wird die Socket Klasse verwendet und ermöglicht die Kommunikation über das Netzwerk. Initial wird die Klasse im Konstruktor mit den wesentlichen Einstellungen als Server festgelegt. In der Methode 'SendCommand' wird das einzelne Zeichen in das zu Übertragenden UTF8 Format gebracht und als Byte Wert versendet. Die 'Stop' Methode ist zwar für das Beispiel nicht erforderlich, jedoch sollte man auch bei kleinen Dingen aufräumen.


 public class NetworkHost  
 {  
   private Socket _connection;  
   public NetworkHost(int port)  
   {  
     Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  
     socket.Bind(new IPEndPoint(IPAddress.Any, port));  
     socket.Listen(1);  
     this._connection = socket.Accept();  
   }  
   public void SendCommand(char command)  
   {  
     if (!char.Equals(command, ' '))  
     {  
       this._connection.Send(Encoding.UTF8.GetBytes(new char[] { command }));  
     }  
   }  
   public void Stop()  
   {  
     if (this._connection != null)  
     {  
       this._connection.Dispose();  
       this._connection = null;  
     }  
   }  
 }  

In der Klasse 'Program.cs' wird die Klasse instanziiert. Ggf. kann hier auch eine andere Port Nummer verwendet werden. In der Schleife wird die Eingabe eines Zeichen eingelesen und versendet. Allerdings auf 'A' und 'B' beschränkt und mit 'C' lässt sich die Anwendung zusammen mit der Verbindung beenden.

 class Program  
 {  
   static void Main(string[] args)  
   {  
     NetworkHost host = new NetworkHost(1200);  
     bool run = true;  
     while (run)  
     {  
       Console.WriteLine("a = ON");  
       Console.WriteLine("b = OFF");  
       Console.WriteLine("c = Close application");  
       string enter = Console.ReadLine();  
       if(enter.Contains("a") || enter.Contains("b"))  
       {  
         host.SendCommand(enter.ToCharArray()[0]);  
       }  
       else if(enter == "c")  
       {  
         run = false;  
       }  
     }  
     host.Stop();  
   }  
 }  

Arduino Client
Der Programmcode ist kaum länger als das von der Consolen Anwendung. Allerdings habe ich die 'Serial.println' Ausführungen gelassen womit man nicht zwingend eine LED braucht und sich das Ergebnis auch im 'Arduino -> Serielle Monitor' beobachten kann.

Gegebenenfalls anpassen
In der Regel muss die MAC Adresse nicht geändert werden, es sei denn ein anderes Gerät verwendet am Netzwerk genau dieselbe wie im Beispiel.
Für die Funktionsvariable 'ip' wird die IP Adresse für den Arduino mit dem Ethernet Shield festgelegt. Ggf. muss hier die dritte Oktett geändert werden und das vierte, wenn ebenfalls ein System die Adresse verwendet.
Mit der Funktionsvariable 'serverip' wird die Adresse vom PC eingetragen, auf der die Consolen Anwendung ausgeführt wird.
HINWEIS: zu den beiden 'includes' fehlen die spitzen Klammern. Der Code Formatierer kann das nicht erkennen.

#include SPI.h  
#include Ethernet.h 

 byte mac[] = { 0x90, 0xA2, 0xDA, 0x00, 0x91, 0x8C };  
 byte ip[] = {192,168,20,99};  
 byte serverip[] = {192,168,20,69};  
 EthernetClient client;  
 int ledPin = 2;  

 void setup() {  
  pinMode(ledPin, OUTPUT);  
  Ethernet.begin(mac, ip);  
  Serial.begin(115200);  
  tryConnectToServer();  
 }  
 void loop() {  
  if(client.available()) {  
   char c = client.read();  
   if(c == 'a') {  
    Serial.println("ON");  
    digitalWrite(ledPin, true);  
   }  
   else if(c == 'b') {  
    Serial.println("OFF");  
    digitalWrite(ledPin, false);  
   }  
   Serial.println(c);  
  }  
  checkForReconnect();  
 }   
 void checkForReconnect() {  
  if(!client.connected()) {  
   Serial.println("disconnecting");  
   client.stop();  
   delay(1000);  
   tryConnectToServer();  
  }  
 }  
 void tryConnectToServer() {  
  Serial.println("connecting...");  
  bool runTryToConnect = true;  
  while(runTryToConnect) {  
   if(client.connect(serverip, 1200)) {  
    Serial.println("Connected");
    runTryToConnect = false;  
   }  
   else {  
    Serial.println("wait...");  
    delay(1000);  
    Serial.println("try to connect again...");  
   }  
  }  
 }  

Nachdem der Arduino Sketch geschrieben wurde und das Ethernet Shield eine Verbindung zum Netzwerk hat, kann die Consolen Anwendung gestartet werden. Die Verbindung kann ein paar Sekunden andauern, bevor man ein Befehl senden kann. Anschließend kann dann mit 'a' die LED eingeschaltet und mit 'b' wiederum ausgeschaltet werden.


Hmm.. Vielleicht hätte ich doch eine hellere LED verwenden sollen :D

Sonntag, 17. September 2017

Eigene Sprites erstellen (Arduino Esplora, Part 3.1)


Im Internet habe ich nach einer Einfachen Lösung gesucht, wie mein ein Sprite bzw. Bild  auf seine eigenen Anforderungen erstellen kann. Damit ist gemeint, dass eine durchgehende Farbpallette für das Eigene Ziel abbilde und Numerisch bezeichnen kann. Zudem sollte dies in einer Byte Folge ausgegeben werden, so dass ich diese im Programmcode ablegen kann.
Natürlich gibt es so ein Programm nicht. Im Grunde ist die Ausgabe eines Bildes durch ein Skript zu übersetzen relative einfach oder auch mal schnell ein eigenes kleines Programm schreiben. Denn das habe ich zunächst gemacht, um schnell eigene Sprites anzulegen. Also zwei Abende dran ran gesetzt und fertig war der Bildeditor. Die Benutzbarkeit beschränkte sich auf die mehr auf die Verwendung der Funktionen.
Mit der Zeit wurden dann noch ein paar Farben hinzugefügt und neu Sortiert, ansonsten hat sich nichts weiter geändert.


  
Übung ist trainieren
Irgendwann bekam es mich doch. Ich schrieb zu Übungszwecken weiter und räumte einiges an Programmcode auf, fügte ein paar weitere Funktionen hinzu und verpasste noch ein paar Optische Verbesserungen. Da ich nicht immer Zeit hatte, verstrichen Monate und ich stellte mal wieder fest, wenn man etwas ordentlich macht, dann kann dabei schon eine Menge Zeit vergehen. Oberflächlich wird man die Mühe nicht sehen, nur die Dinge die nicht funktionieren. Schließlich sind wir in Deutschland und gemeckert wird immer.

Wie verwende ich das Programm
Man schreibt ein Programm, kopiert sich die fertigen Code aus dem Beispiel, malt ein Bild und klickt auf Exportieren. Na gut, etwas mehr Detaillierter darf die Beschreibung dann doch sein.

Am besten verwendet ihr den folgenden Beispiel Programmcode. Je nach Größe des Bildes, müssen die Breite und Höhe angepasst werden. Das sind die Member Variablen 'pictureWidth' und 'pictureHeight', sowie auch die Array Größe und dessen eingetragenen Byte Werte. Im Beispiel ist ein Bild das 10 Pixel Breit und 16 Pixel hoch ist. Multipliziert man die beiden Werte, dann bekommen wir den Wert 160, dass hier die Byte Array Größe festlegt.

 #include <SPI.h>  
 #include <TFT.h>  
 #include <Esplora.h>  
 // zu renderndes Bild  
 int pictureWidth = 10;  
 int pictureHeight = 16;  
 byte picture[160] = {  
  0,0,1,1,1,1,1,1,0,0,0,1,3,3,3,3,3,3,1,0,1,3,3,3,3,3,3,3,3,1,1,3,3,4,4,2,4,3,3,1,1,3,5,5,2,2,5,5,3,1,1,3,4,1,2,2,1,4,3,1,0,1,2,1,2,2,1,2,1,0,0,0,1,2,2,2,2,1,1,0,0,1,8,8,3,3,8,8,6,1,1,8,8,8,8,8,8,6,2,1,1,2,1,8,8,8,8,1,1,0,0,1,1,10,10,7,7,1,0,0,0,1,9,10,1,7,6,1,0,0,0,0,1,1,1,7,6,1,0,0,0,0,0,0,1,9,11,1,0,0,0,0,0,0,0,1,1,0,0,0  
  };  
 void setup() {  
  // init display  
  EsploraTFT.begin();  
  EsploraTFT.initR(INITR_BLACKTAB);  
  EsploraTFT.setRotation(1);  
  EsploraTFT.background(0, 0, 0);  
 }  
 void loop() {  
   drawPictureArray(40, 40, picture);  
 }  
 uint16_t mapNumberToColor(byte c) {  
  uint16_t result = ST7735_RED;  
  switch(c) {  
   case(1): { result = ST7735_BLACK; break; }  
   case(2): { result = 0xF590; break; } // haut  
   case(3): { result = 0x81E1; break; } // braun  
   case(4): { result = 0xC2C2; break; } // hell braun  
   case(5): { result = 0x8300; break; } // braun gelb  
   case(6): { result = 0x5406; break; } // gruen  
   case(7): { result = 0x32A4; break; } // dunkel gruen  
   case(8): { result = 0xAE91; break; } // hell gruen  
   case(9): { result = 0x2146; break; } // dunkel grau blau  
   case(10):{ result = 0x31E9; break; } // grau blau  
   case(11):{ result = 0x84B6; break; } // hell blau  
   case(12):{ result = 0xFFE0; break; } // gelb  
   case(13):{ result = 0xFC08; break; } // orange  
   case(14):{ result = 0xFA8A; break; } // hell rot  
   case(15):{ result = 0xD759; break; } // hell gruen 2  
   case(16):{ result = 0xF800; break; } // rot  
   case(17):{ result = 0x8208; break; } // dunkel braun  
   case(18):{ result = 0xC618; break; } // grau  
   case(19):{ result = 0xF7BE; break; } // sehr hell grau  
   case(20):{ result = 0xFE97; break; } // hell haut  
   default: {  
    result = 0;  
    break;  
   }  
  }  
  return result;  
 }  
 void drawPictureArray(int relationX, int relationY, byte pictureArray[]) {  
  int index = 0;  
  for(int y = 0; y < pictureHeight; y++) {  
   for(int x = 0; x < pictureWidth; x++) {  
     EsploraTFT.drawPixel(relationX+x, relationY+y, mapNumberToColor(pictureArray[index]));  
     index++;  
    }  
   }  
 }  

Wenn ihr den Bild Editor Startet, dann wird gleich ein neues Bild von 10 mal 16 Pixel angelegt.


Über ‚Edit' kann die Größe des Pixelfeldes geändert werden. ACHTUNG: Nach Umstellung der Größe, gehen bereits eingetragene Pixel verloren.


Ist das Bild fertig, dann kann über File -> Export die Byte Kette selektiert und per STRG+C kopiert werden.


Über STRG+V wird zwischen den geschweifte Klammern der Member Variable 'picture' hinzugefügt. Ggf. Breite, Höhe und byte Array Größe anpassen und schon kann der Programmcode auf den Arduino Esplora geschrieben werden.


Sobald das Programm auf dem Arduino Esplora ausgeführt wird, sollte nun das Bild zu sehen sein, dass als Byte Kette hinein kopiert wurde.


Hinweise:
  • Im Programm sind bereits mehr Farben hinterlegt, da ich zu diesen Zeitpunkt mit dem Eigentlichen Projekt weiter gearbeitet und weitere Farben ergänzt habe.
  • Touch Fähigkeit wird noch bearbeitet.
  • Ein Bild mit Programmcode darf nicht mehr Bytes verbrauchen als der Maximale Arbeitsspeicher des Arduinos.

Der Bildeditor ist an für sich fertig. Fehler sind nicht ausgeschlossen und können über die Kommentar Funktion des Blogs eingetragen werden.

Nächster Post: Karte anlegen (Arduino Esplora, Part 4)

Low Pixel Maker Download
Anmerkung: Ich räume noch das kleine Programm auf. Sobald ich damit fertig bin, lade ich die Solution auf Github hoch.

Ameisen Simulation und andere Dinge

Wer lange sich schon mit C# beschäftig, hat sicherlich schon mal was von AntMe gelesen oder gehört. Diese Idee hatte ich aufgegriffen und mi...