COOKIES

This site may be using cookies to melk you with your own data. I, ben0bi, am not the owner of this web service and I also do not maintain their servers. But the EU and the owner of this service think, that the user (me) has the responsibility to inform the consumer (you), that this website uses cookies. Again: I, ben0bi, NEVER use cookies. I am not responsible for the setup of this web service. I just present some information here and do not intend to spy on you for whatever reason ever. But (also again), I do not host this website nor do I maintain any servers related to this website nor do I benefit from using the cookies maintained from this service. I hereby give the responsibility for using cookies on blogspot back to the owners of blogspot.

Freitag, 18. Oktober 2019

SEGA Router

Ich hatte noch die Hülle eines Master-Systems zur Verfügung.


Und eine ausgebaute Karte von einem DLINK GO-RT N300.

Von diesem Modell habe ich 2 Stück, also baue ich die Karte mal in den SEGA rein und mache das Ding in meine Garage.

DLink bietet bei diesem Modell noch eine Gratis DynDns-Adresse an pro Gerät. Ich weiss nicht ob das bei neueren Modellen immer noch so ist. Nur deswegen läuft mein Server seit Jahren von zuhause aus. Ich kann das Ding nur empfehlen: Das einzige Problem bis jetzt ist der Powerknopf, der bei beiden Modellen ein bisschen wackelig ist. (Bei der Karte kommt er zB. nicht mehr aus der Fassung raus, da muss man rausziehen, und bei meinem anderen Modell ist es auch recht fitzelig.) Dafür läuft das Teil aber schon seit c.a. 2012, also...

Falls du das auch machen willst:
Schraube den SEGA auf und werkel alles raus, so dass nur noch die Hülle (oben und unten) sowie der Kartenhalter im Slot auf der Oberseite übrig bleibt. Auch die Metallplatte am Boden muss raus, bis nur noch Plastik übrig ist.

Den Kartenhalter-Slot dran lassen.
Schraube den Router auf und nimm die Karte heraus.

Die Karte passt mit den Anschlüssen und dem Power-Knopf GENAU in den Kartenslot vom SEGA rein.
 


Du musst nur noch das Kabel unten anlöten, damit der Stecker nicht die Slot-Abdeckung blockiert.

Zum Glück sind die Lötstellen ziemlich gross.

Knoten rein und durchs Loch für mehr Stabilität.



Das Kabel vom Dlink-Router passt mit dem Stecker schön durch das Loch hinten auf der Unterseite.
([edit] Den Stecker brauchts ja gar nicht mehr...hihihi)



Nun alles wieder zusammengeschraubt....



Du kannst im Kartenslot die LAN-Kabel einstecken und den Power-Knopf bedienen. Ich finds super.



Im laufenden Betrieb sieht man durch die Löcher von den Gamepads vorne die grünen Lichter leuchten. Das Foto folgt, wenn das Scheissteil richtig läuft, denn zur Zeit bringe ich es noch nicht richtig in mein LAN rein, da hakt was...aber als Haupt-Router ginge es. :)


Mittwoch, 16. Oktober 2019

DIY Emulator Teil 2.1: Der Placeholder-Emulator

DIY Emulator Teil 2.1: Der Placeholder-Emulator

in Unity


(Interlude - Dieser Artikel ist für den weiteren Verlauf der Tutorials nicht relevant.)

Du solltest das Framework des vorigen Artikels nun aufgesetzt haben...

Nachdem wir in den vorigen zwei Artikeln ein kleines 2D-Framework für Emulatoren in Unity3D aufgesetzt haben, soll dieses Framework nun auch einmal etwas anzeigen.

Wie schon beschrieben ist der Placeholder-Emulator kein wirklicher Emulator sondern hat nur die selbe Klasse als Basis. Der Placeholder-Emulator läuft im Hauptmenü, wenn kein anderer Emulator geladen ist. Dies dient der Vereinfachung: Im System ist immer ein Emulator am laufen und somit muss kein Extracode für den Fall geschrieben werden, wenn eben kein Emulator geladen ist. Desweiteren kann man hier verschiedenste Sachen austesten, ohne gross etwas kaputt zu machen.

In diesem Artikel werden wir einen Plasma-Effekt generieren.

Dazu wird eine Farbpalette erstellt.
Dann wird das Display des Emulators mit zufälligen Indexen dieser Palette gefüllt.
Danach wird das Display "refined", also alle Pixel werden vom Wert her an die benachbarten angeglichen. Das gibt schöne "Hügel und Täler".

Dieses Bild aus Paletten-Indexen dient nun als Ausgangsbasis.

In Jedem Frame werden die Paletten-Indexe des Displays als Farbe (Color) aus der Palette geholt und auf die Textur "gemalt". Dabei wird ein "plasmaIndex" dazu gerechnet, welcher das Plasma...plasmieren lässt.

Alles was man pro Frame ändern muss ist der plasmaIndex, und dann natürlich noch das Display in Colors umrechnen.

Wir nehmen den Placeholder-Emulator und erweitern diesen (mehrere Abschnitte):

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Placeholder_Emulator : EmulatorBase
{
    protected int[] m_display; // pos = y * disp_width + x
    protected Color[] m_palette;
    protected int m_palSize = 0xFF; // palsize is one byte. :)

    // this index is added to the actual map palette index to "move" the colors.
    protected float m_plasmaIndex = 0.0f;

    // we need to slow down the things a little.
    protected int colorsPerSecond = 20;

    public Placeholder_Emulator(GameObject g) : base(g)
    {
        Debug.Log("EPlaceholder_Constructor");
        disp_width = 320;
        disp_height = 240;
        m_display = new int[disp_width * disp_height];

        createPalette();
        createPlasmaField();
    }

    public override void Update()
    {
        copyDisplay();

        m_plasmaIndex+=Time.deltaTime*colorsPerSecond;
        if ((int)m_plasmaIndex >= m_palSize)
            m_plasmaIndex = (float)m_palSize-m_plasmaIndex;
    }

Der Placeholder_Emulator ist von der vorher erstellten EmulatorBase abgeleitet.
  • m_display ist das der Textur entsprechende Array mit den Palette-Indexen für das Plasma-Bild.
  • m_palette ist das Array mit den Colors für die Indexe in m_display.
  • m_palSize ist die Grösse der Palette.
  • m_plasmaIndex ist der aktuelle Index, welcher auf jeden Index in m_display aufgerechnet wird.
  • colorsPerSecond zeigt an, wieviele Farbwechsel (Änderung von m_plasmaIndex) es pro Sekunde geben soll.
  • Im Konstruktor wird das GameObject (von EmuCreator) an den Basis-Konstruktor übergeben.
    Die Grösse des Displays wird auf 320x240 (PC-SCREEN 13) eingestellt und m_display wird initialisiert. Dann werden die Palette und das Plasmabild erstellt mit createPalette und createPlasmaField.
  • Schliesslich wird in jedem Update das Display per copyDisplay auf die Textur "gerendert". Dann wird der plasmaIndex um frameTime * colorsPerSecond erhöht.

createPalette()

Erst mal die createPalette-Funktion:

    // create a palette.
    protected void createPalette()
    {
        m_palette = new Color[m_palSize];

        float r = 0.0f;
        float g = 0.0f;
        float b = 0.0f;
        float eight = m_palSize / 8.0f;
        for(int i=0;i < m_palSize;i++)
        {
            // first red to yellow
            if (i<=eight)
            {
                r = 1.0f;
                g = 1.0f / eight * i;
                b = 0.0f;
            }

            // not to white because it's to bright.
            // then yellow to black
            if(i>eight && i<=eight*2)
            {
                r = 1.0f - (1.0f / eight * (i - (eight * 1))); // reverse;
                g = 1.0f - (1.0f / eight * (i - (eight * 1))); // reverse;
                b = 0.0f;
            }

            // then black to turkis
            if (i > eight*2 && i <= eight * 3)
            {
                r = 0.0f;
                g = 1.0f / eight * (i - (eight * 2));
                b = 1.0f/eight*(i-(eight*2));
            }

            // then turkis to green
            if (i > eight * 3 && i <= eight * 4)
            {
                r = 0.0f;
                g = 1.0f;
                b = 1.0f - (1.0f / eight * (i - (eight * 3)));  
            }

            // then green to black
            if (i > eight * 4 && i <= eight * 5)
            {
                r = 0.0f;
                g = 1.0f - (1.0f / eight * (i - (eight * 4))); // reverse
                b = 0.0f;
            }

            // then black to blue
            if (i > eight * 5 && i <= eight * 6)
            {
                r = 0.0f;
                g = 0.0f;
                b = 1.0f / eight * (i - (eight * 5));
            }

            // then blue to magenta
            if (i > eight * 6 && i <= eight * 7)
            {
                r = 1.0f / eight * (i - (eight * 6));
                g = 0.0f;
                b = 1.0f;
            }

            // then magenta to red
            if (i > eight * 7)
            {
                r = 1.0f;
                g = 0.0f;
                b = 1.0f - (1.0f / eight * (i - (eight * 7))); // reverse
            }

            m_palette[i] = new Color(r, g, b);
        }
    }


Diese Palette geht durch alle Farben ausser Weiss (das war zu hell, ich habe es durch Schwarz ersetzt. Rate, wo. ;) ). Darum braucht es acht Abschnitte. Die Variable eight ist ein Achtel der Gesamtpalette. So kann man in jedem Unterabschnitt mit eight und dem Index von 0.0f bis 1.0f oder umgekehrt gehen.

Zunehmende Farbe ist: 1.0f/eight * i
Abnehmende Farbe ist: 1.0f-(1.0f/eight * i)

Dazu muss man von i jeweils noch ein paar Achtel abziehen, damit i immer im Bereich von 0 bis eight ist.

Die Reihenfolge der Farben ist die folgende:  Rot, Gelb, Schwarz, Türkis, Grün, Schwarz, Blau, Magenta, Rot

createPlasmaField()

Nun die createPlasmaField-Funktion:
Erst wird das gesamte Display mit Zufallswerten von 0 bis zur Palettengrösse gefüllt.
Dann wird vier mal durch das Display durchgegangen. Die umliegenden und der aktuelle Pixel werden zusammengezählt und der Durchschnitt des Ergebnisses ausgerechnet. Der Dividor für den Durchschnitt wird jedesmal neu berechnet, da es am Rand weniger Pixel hat.

    protected void createPlasmaField()
    {
         // initialize the display array.
         int m_display = new int[disp_width * disp_height];
         // fill the display with random values.
         for(int i=0;i<m_display.Length;i++)
              m_display[i] = (int)Random.Range(0, m_palSize);

         // go several times through the whole display and
         // smoothen the pixel color(-indexes)
         // 4 steps are appropriate: less do it carvy, more flatten it out to one color.
         int dividor = 0;
 
         int newcol = 0; // color is a palette index, not a color.
         for(int steps=0;steps < 4;steps++)
         {
             // go through x and y instead of mapIndexes because...
             for(int y = 0;y < disp_height; y++)
             {
                for(int x = 0;x < disp_width; x++)
                {
                    dividor = 0;
                    // .. we need to get the right position here.
                    for(int yp= -1;yp<=1;yp++)
                    {
                       for(int xp= -1;xp<=1;xp++)
                       {
                          // get x and y of the pixel to add
                          int newx = x + xp;
                          int newy = y + yp;
                          // check if it is in bounds.
                          if(newx >= 0 && newx < disp_width &&
                             newy >= 0 && newy < disp_height)
                           {
                              dividor++;
                              int idx = newy*disp_height + newx;
                              newcol += m_display[idx];
                           }   
                       }
                    }
                    // divide by dividor.
                    newcol = (int)newcol / dividor;
                    // set new, "smooth" palette index.
                    m_display[y*disp_width +x]=newcol;
                }
             }
         }
    }

In dieser Funktion hat es ziemlich viele for-s, doch das macht nichts, da sie nur einmal aufgerufen wird. Bei Update allerdings musste ich mehrmals "drüber", da schon kleinste Multiplikationen (zB. index aus x,y ausrechnen pro Pixel) einen enormen Einfluss auf den Verarbeitungs-Speed haben (bei einem Array in der Grösse eines Displays). Im EmuGraphicsAdapter hat es dafür eine neue Funktion MarkPixelToChangeByIndex. Eventuell kommt später noch eine "CopyPaletteToTexture(map, palette)" in den EmuGraphicsAdapter, um nur einen Funktions-Call zu machen statt xTausend, doch bis jetzt reicht das.

Was genau gemacht wird, ist oben schon erklärt. Mit den innersten Schleifen werden die Pixel um x und y herum ausgelesen.

copyDisplay():

Schliesslich noch die copyDisplay-Funktion. Da das Display hier gleich gross ist wie die Textur, kann man direkt mit dem Index arbeiten.

    protected void copyDisplay()
    {
       // speed up the things a little with direct indexing.
       for(int i=0; i &lt m_display.Length; i++)
       {
          gfx.MarkPixelToChangeByIndex(i, m_palette[m_display[i]]);
       }
    }
} // end the class here.

Die vielen MarkPixelToChange-Aufrufe pro Frame sind nicht so der Bringer, das wird noch refined.

Desweiteren muss ja eigentlich nicht in jedem Frame das Bild neu gelöscht, aufgebaut und angezeigt werden, sondern nur wenn sich der plasmaIndex ändert (also alle 3 Frames, zur Zeit: 20 Änderungen auf 60 Frames pro Sekunde verteilt). Dazu müsste man switchBuffers aus dem EmuGraphicsAdapter-Update herausnehmen und im Emulator selbst aufrufen.

Ich lerne das Zeug auch alles gleich neu, da wird eventuell noch einiges geändert in älteren Artikeln.
[änderungen folgen] ;)

Jetzt musst du noch.....Play drücken. :)

Das Projekt findest du auch auf github, und zwar hier:
https://github.com/ben0bi/EmulatroniX
im Ordner "JUMPEE".

JUMPEE heisst "JavaScript & Unity Multi Purpose Emulator Environment"
naja, J müsste man rausnehmen, es ist ja jetzt C#. Aber dann tönts nicht mehr so gut....

Den Plasmaeffekt in JavaScript findest du zum angucken hier:
https://ben0bi.github.io/EmulatroniX

Dienstag, 15. Oktober 2019

DIY Emulator Teil 2.1: Plugin-Emulator (UNITY)

DIY Emulator Teil 2.1: Plugin-Emulator-Framework

in Unity

Nachdem wir in diesem Artikel ein grafisches Grundgerüst aufgesetzt haben, wird nun hier das Grundgerüst für die Emulatoren selbst aufgesetzt.

Die Applikation soll mehrere Emulatoren unterstützen können und mit "Plugins" aufgerüstet werden können. Um die Dinge zu vereinfachen, wird "immer" ein Emulator laufen, es gibt keinen spezifischen Hintergrund für die Applikation selbst. Jegliche Grafik bis auf das UI wird durch einen "Emulator" dargestellt. Das heisst, dass, während KEIN Emulator geladen ist, ein "Platzhalter-Emulator" läuft, welcher eine beliebige Grafikausgabe machen kann und auch nicht wirklich ein Emulator ist. Er hat nur die selbe Basisklasse wie ein Emulator.

Dazu gibt es mehrere Scripts:
  • Ein EmuCreator-Script, welches den Emulator auswählt, erstellt und initialisiert. Dieses wird an das GameObject mit dem EmuGraphicsAdapter (das Script aus dem vorigen Artikel) angehängt.
  • Eine Emulator-Basis von welcher alle Emulator-Klassen abgeleitet sind.
  • Eine abgeleitete Emulator-Klasse zu Testzwecken und später als Platzhalter-Emulator.
Erstellen wir also erstmal den EmuCreator und hängen dieses Script an das GameObject an. (In der Szene sollte nur ein GameObject vorhanden sein bis jetzt.)

EmuCreator.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(EmuGraphicsAdapter))]
public class EmuCreator : MonoBehaviour
{
    protected EmuGraphicsAdapter gfx;
    protected EmulatorBase m_emulator=null;

    // Start is called before the first frame update
    void Start()
    {
        Debug.Log("EmuCreator started.");
        gfx = gameObject.GetComponent<EmuGraphicsAdapter>();

        // create an emulator just for testing.
        createEmulator(new Placeholder_Emulator(gameObject));
    }

    // Update is called once per frame
    void Update()
    {
        if (m_emulator!=null)
            m_emulator.Update();
    }

    // create an emulator and initialize it.
    void createEmulator(EmulatorBase emu)
    {
        m_emulator = emu;
        gfx.setEmuScreenSize(emu.getDisplayWidth(), emu.getDisplayHeight());
    }
}

  • gfx ist die Referenz auf die Graphik-Komponente, welche im vorigen Artikel erstellt wurde.
  • m_emulator ist die Instanz des aktuell laufenden Emulators.
Da dieses Script an das GameObjekt angehängt wird, werden Start und Update hier automatisch aufgerufen. Beim Emulator selbst gibt es keine Start-Funktion sondern einen Konstruktor und Update  muss von "hier" aus manuell ausgeführt werden.

Mit createEmulator(emulator) wird m_emulator gesetzt und dann die Bildschirmgrösse auf das Emulatordisplay angepasst.

Mehr braucht es hier gerade nicht. In Start wird der Platzhalter-Emulator gestartet, welcher im übernächsten Script beschrieben wird. Doch dafür brauchen wir erst die EmulatorBase-Klasse, welche als Basis für alle unsere Emulator-Plugins dienen wird.

EmulatorBase.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EmulatorBase
{
    protected GameObject gameObject;
    protected EmuGraphicsAdapter gfx;

    // Change those values and...go. ;)
    protected int disp_width = 20;
    protected int disp_height = 20;

    public int getDisplayWidth() { return disp_width; }
    public int getDisplayHeight() { return disp_height; }

    public EmulatorBase(GameObject g)
    {
        Debug.Log("EBASE Constructor");
        gameObject = g;
        gfx = g.GetComponent();
    }

    // Update is called once per frame
    public virtual void Update() { }
}

  • gfx ist nochmal/wieder die Referenz auf die Graphik-Komponente, welche im vorigen Artikel erstellt wurde.
  • gameObject ist das gameObject welches vom EmuCreator übergeben wird, also das gameObject, in welchem der EmuCreator als Komponente registriert ist.
  • disp_width und disp_height ist die native Grösse des Displays (in Pixeln) des jeweiligen Systems. Stelle diese Werte im Konstruktor ein.
Jede von dieser Basisklasse abgeleitete Klasse kann als Emulator in den EmuCreator "gestöpselt" werden. Update wird "normal" aufgerufen, also in jedem Frame. Update ist virtuell und muss in abgeleiteten Klassen mit override überschrieben werden. Im Konstruktor deines Emulators musst du nur noch disp_width und disp_height richtig einstellen und schon geht alles.

Dieser Platzhalter-"Emulator" setzt einfach einen Pixel. Später folgt noch ein schöner Grafikeffekt. ;)

Placeholder_Emulator.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Placeholder_Emulator : EmulatorBase
{
    public Placeholder_Emulator(GameObject g) : base(g)
    {
        Debug.Log("EPlaceholder_Constructor");
        disp_height = 50;
        disp_width = 50;
    }

    public override void Update()
    {
        gfx.MarkPixelToChange(19, 19, Color.white);
    }
}

Somit können nun verschiedenste Systeme über dieselbe Schnittstelle mit Unity "kommunizieren".

Ich hoffe, das hilft bei deiner Gesundung. :)

Das komplette Projekt befindet sich in diesem Git-Repository, im JUMPEE-Verzeichnis.

Weiter gehts mit ein bisschen Auflockerung: Ein Grafik-Effekt (ohne Shader - wenn mir das jemand in Shadern machen könnte, also das Display, das wär was.)

Freitag, 11. Oktober 2019

DIY Emulator Teil 2: Setup Basisgerüst (UNITY)

DIY: Emulator Teil 2: Setup Basisgerüst

in Unity

Früher hatte ich erwähnt, dass ich den Emulator am liebsten mit Unity3D machen würde, jedoch RTT (Render-To-Texture) damals nicht möglich war mit der frei verfügbaren Edition.

Nun, jetzt gibt es eine Möglichkeit.

Nachdem ich dieses Tutorial gefunden hatte,
habe ich erst mal das neueste Unity installiert: 2018.4.11f
und das Tutorial ausgetestet.

(Hier gehts zum vorigen Artikel: Teil 1: Research (deutsch)
Dabei ging ich noch von Javascript aus, dazu hat es auch ein paar Artikel.
JS war jedoch nur eine Notlösung, siehe oben...)

Nun gut, das läuft..

...machen wir was draus.

Wie schon beschrieben bin ich ein grafischer Mensch also wird erst mal ein Basis-Grafikgerüst aufgesetzt.

Mit dem Setup unten wird am Ende ein Bild auf dem Bildschirm dargestellt, auf welchem der Emulator sein Display zeichnet. Das Bild hat die Pixelgrösse des Emulator-Systems und wird auf Fullscreen hochskaliert. Wenn das keepAspectRatio-Flag gesetzt ist, wird das Bildseitenverhältnis des Original-Displays beibehalten. Dabei gibt es eventuell schwarze Balken an den Seiten oder oben und unten.

Der Emulator kann mit setEmuScreenSize die Displaygrösse anpassen und mit MarkPixelToChange die Farbe eines Pixels ändern.

Das Bild wird gebuffert, das heisst, im Hintergrund wird das Bild gezeichnet während im Vordergrund der "alte" Buffer angezeigt wird.

Man kann mehr als zwei Buffer-Images bestimmen oder auch nur eines: Dann wird direkt auf die sichtbare Textur gezeichnet.

Los gehts...

Lösche alles bis auf die Main-Kamera.

* Erstelle ein leeres GameObjekt auf Position 0,0,0
* Erstelle ein neues Script und hänge es an das GameObjekt an.

Das ist der Code, er wird unten erklärt:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EmuGraphicsAdapter : MonoBehaviour
{
    // each display has the same sprite size.
    protected Vector2 sprite_size;
    protected Vector2 local_sprite_size;

    // The actual emulator display game object which is visible on the screen.
    protected GameObject m_display_image;
    protected SpriteRenderer m_display_renderer;

    // at least 2 sprites for the buffering process.
    public int Buffer_Size = 2;
    protected Sprite[] m_buffer_sprites;
    protected int m_actualBuffer = 0;

    // keep the emulator aspect ratio?
    public bool keepAspectRatio = false;

#if (UNITY_EDITOR)
    private bool m_oldKeepAspectRatio = false;
#endif

    protected Texture2D m_drawable_texture;
    protected Sprite m_drawable_sprite;
    Color32 Reset_Colour = new Color32(0,0,69,0xFF);
    Color32[] m_cur_colors;

    // Start is called before the first frame update
    void Awake()
    {
        if (Buffer_Size < 1)
            Buffer_Size = 1;

        Debug.Log("EmuGraphicsAdapter started.");

        // create the display image game object.
        m_display_image = new GameObject("DISPLAY");
        m_display_image.AddComponent<SpriteRenderer>();
        // get the renderer to not search for it in each frame.
        m_display_renderer = m_display_image.GetComponent<SpriteRenderer>();

        // set some settings on the renderer.
        m_display_renderer.flipY = true;
        m_display_renderer.receiveShadows = false;
        m_display_renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;

        setEmuScreenSize(50, 50);
    }

    // call this function after the screen has resized or the sprite has changed.
    protected void resized()
    {
        // resize.
        var sprr = m_display_image.GetComponent<SpriteRenderer>();

        // The real image size in pixels for later.
        sprite_size = sprr.sprite.rect.size;
        // and in local space units.
        local_sprite_size = sprite_size / sprr.sprite.pixelsPerUnit;

        // get the screen height & width in world space units
        float worldSpriteWidth = sprr.sprite.bounds.size.x;
        float worldSpriteHeight = sprr.sprite.bounds.size.y;

        float worldScreenHeight = Camera.main.orthographicSize * 2.0f;
        float worldScreenWidth = (worldScreenHeight / Screen.height) * Screen.width;

        // initialize new scale to the current scale
        Vector3 newScale = m_display_image.transform.localScale;

        // resize the local scale to the world scale.
        newScale.x = worldScreenWidth / worldSpriteWidth;
        newScale.y = worldScreenHeight / worldSpriteHeight;

        // maybe keep the aspect ratio
        if (keepAspectRatio)
        {
           // get the aspect ratio of the sprite image.
           float aspectRatio = worldSpriteWidth / worldSpriteHeight;
           Debug.Log("Aspect Ratio: " + aspectRatio);
           float newy = newScale.x/aspectRatio;
           float newx = newScale.x;
           float wrldy = newy * local_sprite_size.y;
           // new height is bigger than screen height, switch aspect ratio calculation.
           if (wrldy > worldScreenHeight)
           {
                newy = newScale.y;
                newx = newScale.y * aspectRatio;
           }
           newScale.x = newx;
           newScale.y = newy;
        }
        // set the new local scale on display gameobject.
        m_display_image.transform.localScale = newScale;

        Debug.Log("Emulator display size (Image Pixels): x" + sprite_size.x + " y" + sprite_size.y+" (P->LS) x"+local_sprite_size.x+" y"+local_sprite_size.y);
        Debug.Log("Emulator display size (LS): x" + (newScale.x*local_sprite_size.x)+" y"+(newScale.y*local_sprite_size.y));
        Debug.Log("Real display size (WS): x" + worldScreenWidth + " y" + worldScreenHeight);
    }

    // switch the buffer images.
    protected void switchBuffers()
    {
        // apply all pixel changes to the draw texture.
        ApplyMarkedPixelChanges();

        // set the old stuff to the displayed image.
        m_display_renderer.sprite = m_buffer_sprites[m_actualBuffer];

        // set next buffer image.
        m_actualBuffer++;
        if (m_actualBuffer >= Buffer_Size)
            m_actualBuffer = 0;

        // set the new drawables.
        m_drawable_sprite = m_buffer_sprites[m_actualBuffer];
        m_drawable_texture = m_drawable_sprite.texture;

        m_cur_colors = m_drawable_texture.GetPixels32();

        clearDrawArray();
    }

    // create new textures for a new display size.
    public void setEmuScreenSize(int width, int height)
    {
        m_buffer_sprites = new Sprite[Buffer_Size];

        // create the buffer sprites.
        for (int i = 0; i < Buffer_Size; i++)
        {
            Debug.Log("Creating buffer image #" + i);
            Texture2D tex = new Texture2D(width, height);
            tex.filterMode = FilterMode.Point;
            //tex.Apply(false);
            Sprite spr = Sprite.Create(tex, new Rect(0, 0, width, height), new Vector2(0.5f, 0.5f));
            m_buffer_sprites[i] = spr;
        }

        m_display_renderer.sprite = m_buffer_sprites[0];

        // resize the displays.
        resized();

        // initialize the buffers, set the draw image etc.
        switchBuffers();
    }

    // set or unset the keep aspect ratio flag by code.
    void setKeepAspectRatio(bool setflag)
    {
        keepAspectRatio = setflag;
        resized();
    }

    // Update is called once per frame
    void Update()
    {
#if (UNITY_EDITOR)
        // maybe the keep aspect ratio flag has changed.
        // this will only happen in editor when you click the flag box.
        // else, setKeepAspectRatio(bool setflag) should be used.
        if(keepAspectRatio!=m_oldKeepAspectRatio)
        {
            m_oldKeepAspectRatio = keepAspectRatio;
            resized();
        }
#endif
      // Pixel Test
      //  MarkPixelToChange(10, 10, Color.green);
      //  MarkPixelToChange(11, 11, Color.yellow);
      //  MarkPixelToChange(15, 15, Color.yellow);
 
       switchBuffers();        
    }

    // DRAW FUNCTIONS
    public void MarkPixelToChange(int x, int y, Color color)
    {
        // Need to transform x and y coordinates to flat coordinates of array
        int array_pos = y * (int)m_drawable_sprite.rect.width + x;

        // Check if this is a valid position
        if (array_pos > m_cur_colors.Length || array_pos < 0)
            return;

        m_cur_colors[array_pos] = color;
    }
 
    public void MarkPixelToChangeByIndex(int index, Color color)
    {
        if(index>=0 && index<m_cur_colors.Length)
             m_cur_colors[index]=color; 
    } 
 
    // apply all pixel changes.
    public void ApplyMarkedPixelChanges()
    {
        if (!m_drawable_texture)
            return;

        m_drawable_texture.SetPixels32(m_cur_colors);
        m_drawable_texture.Apply();
    }

    // clear the drawing image.
    public void clearDrawArray()
    {
        for(int i=0;i < m_cur_colors.Length; i++)
        {
            m_cur_colors[i]=Reset_Colour;
        }
    }
}

Die Member-Variablen: 

  • sprite_size : Die Grösse des Emulator-Displays in Pixeln.
  • local_sprite_size: Die Grösse des Emulator-Displays in Units.
  • m_display_image: Das GameObjekt, in welchem der sichtbare Content dargestellt wird.
  • m_display_renderer: Die "SpriteRenderer"-Komponente von m_display_image. Damit muss man nicht immer wieder GetComponent aufrufen.
  • Buffer_Size: Wie viele Bilder werden gebuffert? Doublebuffer = 2, Triplebuffer = 3, etc.
  • m_buffer_sprites: Array mit der Anzahl an Buffer-Sprites welche in Buffer_Size angegeben ist.
  • m_actualBuffer: Der Index des aktuellen Buffer-Sprites in m_buffer_sprites.
  • keepAspectRatio: Das Bild wird auf den gesamten Bildschirm skaliert, wenn dieses Flag auf False ist. Ansonsten wird das Bildverhältnis (x/y) beibehalten und es gibt eventuell schwarze Balken am Rand. Dafür werden die Pixel korrekt dargestellt.
  • m_drawable_texture: Die aktuelle Textur, auf welcher gezeichnet wird.
  • m_drawable_sprite: Das zugehörige Sprite zu m_drawable_texture.
  • Reset_Colour: Die Hintergrundfarbe des Displays.
  • m_cur_colors: Array, auf welchem die Farben geändert werden können. Wird nach jedem Frame neu erstellt für den nächsten Buffer. Dieses Array wird später mit texture.setPixels(...) und texture.Apply() auf die Textur geladen. Wenn man das für jede Pixel-Änderung einzeln machen würde, wäre es wohl sehr langsam.

Die Funktionen:

  • Awake(): Diese Funktion wird VOR Start() aufgerufen und dient der Initialisierung "dieses" Objekts. Hier wird ein neues GameObjekt namens DISPLAY erstellt, welches die sichtbare Grafik darstellt. Dazu wird eine "SpriteRenderer"-Komponente an das "DISPLAY" angehängt und einige Werte eingestellt. Dann wird mit setEmuScreenSize(w, h) das Display initialisiert.
  • resized(): Wenn die Bildschirm- oder Displaygrösse geändert wird, sollte diese Funktion aufgerufen werden. Sie skaliert das Display auf die richtige Grösse.
  • switchBuffers(): Ruft ApplyMarkedPixelChanges() auf, setzt dann den neuen Buffer zum drauf zeichnen und zeigt den vorherigen (fertig gezeichneten) Buffer an.
  • setEmuScreenSize(width, height): Setze die Grösse des Emulator-Bildschirms in Pixeln. Danach wird resized() und switchBuffers() einmal aufgerufen.
  • setKeepAspectRatio(bool): Wenn dieses Flag gesetzt ist, wird das originale Seitenverhältnis beibehalten, ansonsten wird alles (x und y) auf den Bildschirm hochskaliert.
  • Update() : Update wird pro Frame ein mal aufgerufen. Home of switchBuffers() :)
  • MarkPixelToChange(x,y,color): "SetPixel" mit anderem Namen weil ApplyMarkedPixelChanges() aufgerufen werden muss, damit die Änderungen erkannt werden.
  • MarkPixelToChangeByIndex(index, color): Da das Emulator-Display normalerweise gleich gross ist wie das anzuzeigende Bild, kann man hier direkt mit dem Index arbeiten. Das verschnellert den Code ein bisschen.
  • ApplyMarkedPixelChanges(): Überträgt das mit MarkPixelToChange() modifizierte Color-Array auf die aktuelle Buffer-Textur.
  • clearDrawArray(): Setzt alle Pixel des Displays auf die angegebene Hintergrundfarbe (Reset_Colour).
Die Variablen zwischen #if(UNITY_EDITOR) und #endif werden im Spiel nicht gebraucht: Damit wird gecheckt, ob der Kasten im Editor angeklickt wurde.

Als nächstes wird ein Framework für die Emulatoren selbst erstellt:
DIY Emulator Teil 2.1: Plugin-Emulator (UNITY)

Das komplette Projekt befindet sich in diesem Git-Repository, im JUMPEE-Verzeichnis.

Ich hoffe, das gefällt. :) 

Sonntag, 23. Juni 2019

Wir machen Strom: 12v Starterbox

Da mir ja anscheinend trotz allem Enthusiasmus doch keiner wirklich beim Strom machen hilft, mach ich halt mal was ich kann und bau das ganze Zeug modulweise auf.

Modul #001 + 12STB

Modul Nummero Eins ist die 12v-STarterBox 12STB:

Damit kann man den Generator mit einer 12v Batterie starten und dann auf den Generatorstrom umschalten, wenn er läuft.

Hier sind die Werte noch nicht ausgemessen.

Der Variable Widerstand (so heisst das wirklich, nicht "Potentiometer", das ist "eigentlich" was bisschen anderes.) dient dazu, um die richtige Stromstärke an das angehängte Gerät zu schicken. Dies könnte auch an einer eigenen Leitung hängen, später mal vielleicht...  ;)

Intern ist alles ganz einfach mit Kabelklemmen zusammengehängt, so dass man jederzeit das Setup ändern kann:


Und schliesslich die ausgemessenen Werte:


Anschaltern uuuund...
1.5V
3V
6V
9V
12V
Also es braucht einen Variablen Widerstand, ich hab hier 100k, zwei 6-Pin Schalter (je 3 pro Leitung), ein paar Kabel und Kabelklemmen und ne hübsche Box. :)

Hier ist der Leitungsplan:
(Anscheinend hab ich irgendwo GND mit 12v vertauscht, es zeigt ja Minus an...egal, Hauptsache es zeigt überhaupt was an, DU hast ja GAR NIX gemacht. So. *weck weck :)*)

[TODO: leitungsplan]

Mittwoch, 12. Juni 2019

Server mit Backup und Display

Das ist mein Server:



und von Innen:



Online: http://masterbit.net

Vorab: Ich habe weder ein SSL-Zertifikat noch sonstwas und bin auch zu faul und geizig, mir eins zu besorgen. Warum auch, ich habe keine wirklich wichtigen Daten da drauf....darum nur http, ohne s. :)

Er macht jeden Tag ein Backup (remove and copy) und ein Sync (ohne remove, nur neue/geänderte Dateien) auf die Festplatte und zeigt die aktuelle Uhrzeit, Datum, die Startzeit und die Laufzeit des Servers an.  Wie du siehst läuft meiner seit 3 Monaten ohne Unterbruch. :)



Die Zahl in der Mitte der Uhr zeigt an, wieviele Minuten es noch dauert, bis das Display einen kompletten Refresh macht. Ich habe dies auf 10 Minuten eingestellt.

Hardware


Zusammenbau

Der USB-Hub muss auseinander genommen werden. Dann wird ein Kabel an die 5V und GND Leitungen des Hubs gelötet, also bei einem USB-Stecker, an den Lötstellen.
An dieser Leitung wird die Festplatte eingesteckt. Ich weiss nicht, ob der Strom sich an die anderen Leitungen verteilt, darum die Festplatte lieber gleich am richtigen Teil anschliessen.

Das Kabel vom Netzteil wird am Ende, also möglichst nah am KLEINEN Stecker durchgetrennt. ACHTUNG: Noch ein bisschen Kabel am Stecker lassen, dieser wird dann ins Raspi gesteckt.

Wie schon erwähnt,  werden die neuen Kabel am USB-Hub direkt an den Strom vom Adapter angeschlossen. Das ist mit den Kabelklemmen bewerkstelligt: Die Eine ist am kleinen Kabel vom Raspi und geht dann auf die andere, wo noch dazu der Strom vom Hub rein kommt. Diese geht dann an den Stromadapter.

Der Bildschirm wird folgendermassen am Raspi eingesteckt:

Die Kabel haben diese Farben.                          


Das ganze Zeug irgendwie in die Box reinpassen,  Loch für den Bildschirm machen, etc...ich hab das nicht wirklich schön gemacht (hinbekommen) sondern einfach so, dass es geht. Siehe Loch vom Bildschirm... :)

Software

System

Ich benutze Raspian Stretch Lite, also ohne Desktop. Meistens bin ich mit SSH (also remote, von meinem PC aus) drinne, da brauchts keinen Desktop.

Dieses wird mit Balena Etcher auf die SD-Karte geflasht.

Danach die SD-Karte in den Raspi stopfen, LAN-Kabel, Tastatur, Maus, Bildschirm für die Erstinstallation und angeschaltert...

(Noch OHNE USB-Platte zur Sicherheit)

Login: pi / raspberrz (y und z sind erstmal vertauscht auf der deutschen Tastatur)

Erstmal konfigurieren:

sudo raspi-config

sudo heisst superuser do und wird fast immer benötigt, wenn man am System rumbastelt.

Ein Menü öffnet sich. Wir ändern erstmal das Passwort (Change User Password)

Danach stellen wir die Netzwerkoptionen ein: 2->N1 Hostname: Gib einen Namen für deinen Raspi ein.

Die Sprache sollte auch eingestellt werden, damit man nicht immer mühsam z mit y verwechseln muss.
  • 4-> I1 Change Locale -> de.CH.UTF8 -> (nochmal auswählen)
  • 4-> I2 Change Timezone -> Berlin
  • 4-> I3 Change Keyboard Layout -> [ENTER] -> Other -> German (Switzerland) -> Erster Eintrag, default Setting, No Compose Key (immer schön Enter drücken :) )
  • 4-> I4 Change WiFi Country -> Switzerland
Nun muss noch SSH angeschaltert werden und für den Bildschirm auch noch SPI:
  • 5-> P2 SSH -> enable
  • 5-> P4 SPI -> enable
Und schliesslich noch ein bisschen "Schminke" in den "Advanced Options":
  • 7-> A1 Expand Filesystem (beim ersten Start hat der Raspi das eh schon gemacht, dies ist nur nochmal zur Sicherheit.
  • 7-> A3 Memory Split -> zB: 16Mb : Hier wird der Grafikspeicher aufs Minimum beschränkt, da nicht wirklich gebraucht. Ich nehme trotzdem 16MB statt 8, zur Sicherheit.
Nun kann man das Tool beenden und einen Neustart machen (lassen).

Schliesslich wird noch git installiert:
erstmal sudo apt-get update um die Paketliste zu erneuern.
Git: sudo apt-get install git

Die Backup-Platte

Ausschalten, die Festplatte an den USB-Hub und diesen an den Raspi dran und neu starten. Der Hub braucht ab jetzt auch Strom! (Nicht vergessen, die Kabel zu verbinden)

Um eine "normale" NTFS-Platte anzusprechen braucht es eventuell noch ntfs-3g:
sudo apt-get install ntfs-3g

Erstmal erstellen wir einen Pfad, wo die Festplatte herein gemountet wird:
sudo mkdir /media/backupDrive

Mit sudo blkid werden die Labels und Partition-IDs der angeschlossenen Speichermedien angezeigt. Hier die UUID der Festplatte abschreiben.

Diese muss in fstab eingetragen werden:

sudo nano /etc/fstab

Hier eine neue Zeile machen mit:

UUID=DEINEUUID /media/backupDrive ntfs auto,nofail,sync,users,rw

Mit Ctrl-O speichern und mit Ctrl-X beenden. (Ctrl = Strg)

Check: sudo mount -a  oder lsblk

Wenn die Festplatte auch nach einem Neustart hier ist, ist alles richtig.

Der Webserver

Falls noch nicht gemacht:
sudo apt-get update
sudo apt-get upgrade -y


Wir haben ein Script im Hintergrund, welches den Bildschirm steuert. Um es im Hintergrund laufen zu lassen, braucht es screen.

sudo apt-get install screen

Ein Webserver braucht natürlicherweise einen Webserver. Ich nutze Apache, weil der Indianer sich bewährt hat. Nginx kenne ich nicht.

sudo apt-get install apache2

Wenn der Server läuft, ist das Standard-Webverzeichnis in: /var/www/html
Nun wechseln wir ins Homeverzeichnis und installieren meine Scripts:

cd ~
git clone https://github.com/ben0bi/ServerScripts.git

Installation des Backup-Scripts

Das Backup-Script sollte jeden Tag um eine bestimmte Zeit ausgeführt werden.
Dazu können wir cronjobs gebrauchen, welche in der crontab eingefügt werden.

Eventuell muss das BackupScript noch ausführbar gemacht werden:
sudo chmod +x /home/pi/ServerScripts/BackupScript.sh

sudo crontab -e

Dort dann auf eine neue Linie:

30 9 * * * /home/pi/ServerScripts/BackupScript.sh

Mit Ctrl-O speichern und mit Ctrl-X verlassen.

Damit wird jeden Tag um 9.30 morgens ein Backup gemacht. Du kannst natürlich auch eine andere Zeit einstellen.

Installation des Displays

Das Display benötigt python und einige Bibliotheken dafür. Bibliotheken für python können mit pip installiert werden.

sudo apt-get install python python-pip

Dann brauchen wir noch eine SPI-Bibliothek spidev, eine Bildbearbeitungsbibliothek pillow und dazu eine jpeg-Bibliothek libjpeg-dev. Irgendwas braucht noch die python-requests, also wird das auch installiert.

sudo apt-get install spidev libjpeg-dev pillow python-requests

Nun muss das Display nur noch bei jedem Neustart gestartet und beschrieben werden. Da jede Minute ein Bild upgedatet wird, läuft das ganze über screen.

sudo nano /etc/rc.local

Und dort dann auf eine neue Zeile:

/home/pi/SeverScripts/screen.sh

Noch einmal Ctrl-O und Ctrl-X benutzen.

Eventuell muss screen.sh auch ge-chmode-d werden, siehe oben.

Ich hoffe mal es läuft. Bei mir gehts. :)

Das wars, stopf nun dein Webverzeichnis mit Zeug voll, mach den Server erreichbar etc. Da musste selber guggen, ich hab DynDNS vom Router "geschenkt" bekommen. (D-Link rockt :) )

Viel Spass damit. PHP und all den anderen Mist musst du selbst gucken...ich brauch zum Beispiel KEIN SQL mehr...Darum habe ich diesen Krempel auch nicht "explizit" mit-installiert.

Viel Spass beim Ausprobieren.

Donnerstag, 20. Dezember 2018

Mein eigener Gameboy

Also ich halte mich dabei an den Gameboy Zero, ausser folgendem:

+ Ich werde wohl die Hülle selbst drucken/bauen.
+ Der Gameboy soll auch Musik spielen und verschiedene Hacking-Tools haben. :)

Das ist ein "Work-in-Progress" Artikel, also...

ACHTUNG: JEGLICHE Elektronik, welche ich bestellt und in Lego eingebaut habe, ist kaputt gegangen. (Die selbstgebaute Platine nicht.) Was soll mir das sagen? Scheiss auf Scheiss-Plastik, denk ich mal. Ich bau sonst alles in Holz ein, da ist mir noch nie was kaputt gegangen.....Also: Rechne mit heftigen Verlusten, wenn du Lego verwendest. :)

Dies ist die aktuellste Version mit 2" Screen. Hier fehlt nur noch eine Halterung für Raspi und Akku sowie die Kabel zu den Buttons. (Das Screenkabel ist gebrochen, darum gibts dann noch eine ganz neue Version später mit 3.5"-Screen.)
2" Version mit Bauanleitung weiter unten.

Erstmal habe ich mir folgendes bestellt:

Einen Raspi Zero W
Bestell dir am besten gleich das Minimal-Kit, denn mir fehlt nun zum Beispiel der MikroUSB-zu-USB-Adapter. Den HDMI-Adapter hab ich vom vorigen Kit (ohne WLAN).

Hier mit angelöteten Pins, TV-Kabel und SD-Karte.

Einen 2" NTSC/PAL-Screen (den soll man angeblich ganz einfach anhängen können)
(Ich wollte eigentlich einen 2.5"er aber den gabs nicht. 3.5" sind dann schon zu gross für einen originalen Gameboy.) (Neu: Lieber zu gross als zu klein, also bestellt mal schön den 3.5er, der 2er ist nämlich unleserlich klein.)

Etwa so unleserlich ist es auch in echt. :)
Und eine MicroSD Karte brauchts natürlich auch noch. Da mach ich keine Werbung für, weil man nur noch minimum 16GB bekommt. Ich will aber weniger, um dann ein "kleines" Image zu ziehen...egal. :)

Abgewinkelte Pins und Doppelpins (siehe erstes Foto)

Strom hab ich noch zum Testen. Da kommt dann später natürlich ein Akku dran.

Desweiteren noch eine/zwei Platinen für die Buttons und so kleine Pushbuttons zum draufstecken.
Davon mindestens sechs: Dpad = 4 und A+B (noch OHNE Start und Select)

Software, Computer-Hardware

Dann hab ich mir die Recalbox für den Zero gezogen und mit Etcher auf die microSD gebrannt.

(Vorher hab ich raspbian drauf gemacht, aber recalbox geht gleich out-of-the-box)

Das Ganze am (normalen) Bildschirm angesteckt, läuft.
Recalbox installiert sich selbst beim ersten Mal.

Den Bildschirm habe ich mit einem zweiten USB-Netzteil angeschlossen...leuchtet = läuft. :)

Lustigerweise hat die ADAFRUIT-Box, mit welcher der Bildschirm geliefert wurde, genau etwa die Masse, die mein Gameboy haben sollte. Ich werde also diese vorerst mal nehmen, bis ich sie dreiDdrucken kann. (Neu: Lego, siehe unten)


Das Controllerboard vom Bildschirm habe ich mit Klettverschluss(-Klebeband) an die hintere Seite vom Bildschirm angemacht. Es muss sowieso unter dem Screen sein, um Platz für die (Gameboy-)Buttons zu haben. (Nein, die Buttons sind nun seitlich angebracht. Trotzdem ist es so viel weniger fitzelig zum Basteln. :) )

(Tipp: Anstatt dran rumzureissen, um den Klettverschluss zu trennen, kann man vorsichtig mit einem Messer mitten durch "schneiden".)

Den Bildschirm einrichten:

Der Bildschirm braucht seinen eigenen Strom!

Mit dem Kabel auf dem ersten Foto verbinden. Die TV-Pins sind angeschrieben auf dem Zero.

Mit F4 und dann Alt+F2 in den Terminalscreen.
Login: root, recalrootbox
https://github.com/recalbox/recalbox-os/wiki/Connect-your-recalbox-to-a-CRT-with-composite-%28EN%29

mount -o remount,rw /boot

nano /boot/config.txt

hier eintragen:

sdtv_mode = 0 bis 3, siehe Link
hdmi_ignore_hotplug=1

Ctrl-O, Ctrl-X

nano recalbox.conf

Suche mit Ctrl-W: global.videomode

global.videomode=default

Ctrl-O, Ctrl-X

Die Hülle, die Buttons

Erst wollte ich eine "normale" Hülle aus Plastik, Metall oder Karton bauen. Doch dann hab ich mir gedacht, warum nicht aus LEGO?

Also habe ich mir zuerst eine Hülle für den Screen gebastelt. Dieser selbst ist viel zu klein, aber es geht jedenfalls schon mal. Und darunter konnte ich noch eine Aussparung für Akku und weiteres einbauen.

Die Teile stammen hauptsächlich von diesen zwei Lego-Sets:
LEGO 42064: Ocean Explorer
LEGO 42065: RC-Racer

Ich möchte das modular bauen, darum hab ich mich dann an die Buttons gemacht: Man kann einfach ein paar lange Technic-Stäbe aneinanderlegen und dann mit Kreuzstäben die Buttons so anordnen, wie man will. Da mein Screenhüllendesign eher an den Gameboy Advance erinnert, baute ich mir zwei kleinere Pads nach diesem Schema für an die Seite ran.

Verworfene erste Version. Pad 1 links wurde hier (dafür) gelötet...*

Dann habe ich bei einer Platine, welche ich glaubs damals auf Conrad bestellt habe, die Buttons, welche ich glaubs auch damals dort bestellt hatte, angeordnet. Die Platine habe ich halbiert, für jede Seite eine Hälfte.

Nach ein bisschen Gefitzel und Rumgebastel passen nicht nur die Buttons, sondern auch die Platinen "perfekt" in die Hülle und werden von unten angeklemmt. Ein bisschen anpassen und die Buttons funktionieren besser als gedacht.

Nun gucken, wo man auf der Platine am besten die Leitungen hinlegen könnte, um die Buttons dann mit Steckverbindern an den Raspi zu machen....ich benutze hier gekrümmte Stecker.

Der Raspi hat eigene Pull-up/Pull-down Widerstände, die brauchts also nicht.
Hierbei konnte ich drei Buttons seriell in einer Reihe an GND anschliessen und musste nur den letzten noch separat mit GND verbinden. GND bzw. 5v ist der Pin ganz links, die anderen gehen an die GPIO-Eingänge.

Schliesslich hat mir die Grösse von der Bildschirmhülle nicht gefallen, deshalb habe ich das Bildschirm-Modul noch mal neu gebaut. Darum sieht man auf den Fotos auch etwas anderes als in der Bauanleitung.

Vorläufig endgültige Version für 2" Screens.
*..und die Padhülle musste an Pad 1 passen.

(Nur) Das Bildschirmmodul von unten gesehen.

Das neue Modul hält den Bildschirm auch gleich an der Stelle, das ging beim alten noch nicht. Da hat er nur pro forma reingepasst.

Hierbei darauf achten, dass der Bildschirm selbst über den Kreuzstäben, das Controllerboard jedoch unter den Kreuzstäben ist.

ACHTUNG: Der Aufbau geht, aber das Bildschirmkabel ist gebrochen.

FALSCH: Kabel über dem Kreuzstab

Mach ja nicht den Fehler, das Bildschirmkabel über den "einen" Kreuzstab zu legen "zur Stabilisierung". Alles schön lose machen und dann einfach überdecken.


RICHTIG: Bildschirmkabel unter/hinter dem Kreuzstab.
Hier sichtbar die untere Seite, einfach wie oben überdecken.


...darum musst du dir unten selbst einen Batterie- /Raspi-Kasten ausdenken.
Ich bestelle mir gleich einen 3.5" Bildschirm...

Die Pads mussten natürlich auch angepasst werden. Da ich für das eine Pad schon eine Platine gebaut hatte (siehe oben), wollte ich sie von der Form her beibehalten.

Die Anleitung gibts hier:
[TODO: Link zu Anleitung Pads und Centerteil]

Du kannst die Anleitung oben benutzen, wenn du den Adafruit 910 2" Screen bestellst.

Sie ist aber ziemlich verworren. Das hat der LDD (Lego Digital Designer) so gemacht, nicht ich...Darum bei den Pads: Erst die roten an die weissen Teile, dann die grünen und dann die Buttons so anordnen wie du willst. ;)

Die Pads sind jeweils seitenverkehrt genau gleich aufgebaut. Obwohl es unstabil aussieht, sitzen die Dinger "bombenfest".

Ich bin selber überrascht von der kuuuhlen Bauweise. :)

Uuuund das Kabel vom Bildschirm ist gebrochen....DA MUSS MAN EXTREMST AUFPASSEN.

Uund der Raspi ist auch am Arrrrrsch. Hier wird ganz sicher nicht "politisch korrekt" nur "A..." geschrieben, da könnt ihr aber Gift drauf nehmen.

Darum ist dieses Projekt vorerst mal eingestellt, vielleicht lade ich dann noch mal die Bauanleitung hoch. Ich hab ehrlich gesagt grad keine Lust mehr.

Weiteres folgt...