SNES Homebrews erstellen + C-Einführung
fonte:https://circuit-board.de/forum/index.php/Thread/15087-SNES-Homebrews-erstellen-C-Einf%C3%BChrung/?postID=372512#post372512
Hallo zukünftige Homebrewentwickler,
Im Sommer habe ich eine Umfrage gestartet, ob Interesse an einer kleinen Einführung
in die Programmierung für Konsolen besteht. Gewonnen hat mit knapper Mehrheit 'C für's SNES'.
Ich kann vorweg nehmen, dass ich kein Programmierer bin und somit ist es nicht verwunderlich, wenn
sich hier der ein oder andere Fehler einschleicht.
Ein Programm auf dem SNES bekomme ich trotzdem programmiert, somit wird schon irgendwas am Ende dabei herumkommen.
Im Internet kann man zur Programmiersprache C, Unmengen an Tutorials, Einführungen und sogar ganze Bücher gratis finden.
Es besteht überhaupt kein Grund, sich selber mit solch einem dicken Schwarten aus der örtlichen Buchhandlung einzudecken.
Es ist auch weitaus einfacher als es sich mancheiner vorstellt. An der Uni lernt man sowas in ein paar Wochen im ersten
Semster. Die meisten Leute haben sich wahrscheinlich schon weitaus schwierigere Konzepte selber angeeignet.
Kurz gesagt, wer es geschafft hat durch die Mittelstufe zu kommen, für den ist C in seinen Grundzügen ein Kinderspiel.
Wer außerhalb dieser Einführung ein paar Randinformationen mehr benötigt, dem will ich zwei Links mit auf den Weg geben.
Eine einfache umfangreiche strukturierte Einführung:
openbook.galileocomputing.de/c_von_a_bis_z/
Eine Schnelleinführung für Hartgesottene:
c.learncodethehardway.org/book/
Ich kann und will hier einfach nicht so viel schreiben, wie in diesen ausführlichen Werken.
Es würde einfach zu viel Zeit kosten und vermutlich den Rahmen sprengen.
Momentan arbeiten wir im euirc/#retrotardation channel gerade etwas am 'snesoip-projekt'
github.com/mupfelofen-de/SNESoIP
Es wäre schön, wenn sich durch diesen Thread jemand angesprochen fühlen würde, dieses Projekt zu unterstützen.
Obwohl das snesoip bzw. dessen Firmware noch nicht fertig ist, gibt es bereits eine virtuelle version mit der Programmierer
Onlinespiele für das SNES testen und entwickeln können.
github.com/mupfelofen-de/SNESoIP/tree/testing/utils/vsnesoip
Hierbei handelt es sich um ein virtuelles Netzwerk-Joypad unter Linux, das man in seinen Emulatoren mappen kann.
-> tldr: bitte hier anfangen zu lesen
Vorraussetzungen:
Ein Computer!
Eigentlich egal ob Desktop/Notebook/Smartphone oder Tablet,
zumindest mit Android sollte es auch funktionieren. (Vorsicht aufwendig!) :>
Ab hier gibt's den Rest kostenlos:
Ein Texteditor:
Ich empfehle mindestens einen mit Syntaxhighlighting, um den Source besser lesen zu können.
Linux/Windows:
geany.org/
Windows:
notepad-plus-plus.org/
Einen Pixeleditor:
mtpaint.sourceforge.net/download.html
Alternativ kann man auch gleich zum integrated development environment (IDE) greifen.
Der Nachteil ist hierbei für Einsteiger, dass es relativ überladen und kompliziert wirkt, solange man noch nicht
mit allen Begrifflichkeiten etwas anfangen kann. Manche behaupten solche Programme seinen nur etwas für Fortgeschrittene und
Anfänger sollten erst später damit anfangen. Ich persönlich halte die Bedenken für überzogen. IDEs bieten viele Features, um sich
das Leben als Programmierer leichter zu machen.
eclipse.org/
Für unsere Zwecke reicht allerdings auch ein Editor vollkommen aus.
Als letztes verwenden wir noch eine sog. Toolchain bzw. Devkit.
code.google.com/p/pvsneslib/
Hierbei handelt es sich um eine Ansammlung von Supportprogrammen wie z.b. Konverter und Librariers, welche
uns einiges an Programmieraufwand abnehmen werden.
Auch die Compiler und Linker sind hier enthalten.
Ein Compiler wandelt den C Sourcecode in Maschienensprache um.
Auch die Programmiersprache Assembler braucht einen Compiler, die effiziente Programmierung rührt daher, dass
die Programmiersprache Assembler dem eigetlichen Maschienencode sehr ähnlich in Struktur und Ablauf ist.
Der Linker, bindet quasie alle Objekte zu einem ausführbaren Programm. (Naja ^^)
Gehen wir nun vor der Installation erst einmal grob darauf ein wie ein Programm auf einem PC eigentlich
funktioniert und danach vergleichen wir das mit dem SNES.
Dadurch soll uns ersichtlich werden, was die SNES Programmierung eigentlich von der "normalenProgrammierung
unter Windows/Linux unterscheidet.
Häufig hört man so plakative Sprüche wie Computer arbeiten intern nur mit Einsen und Nullen. Das ist nicht einmal auf
technischer Ebene richtig und eher etwas für Film und Fernsehen.
In der Realität werden eher potentiale gemessen und eine logische '1' ist evtl. +1v bis +1,5v und eine '0' -1v bis -1,5v.
Auch sind Computer nicht unfehlbar und werden ständig durch äußere Störeinflüsse, sogar bis hin zur kosmischen Strahlung
beeinflusst. Von Hardwaredefekten wie kaputtem Arbeitsspeicher einmal ganz abgesehen.
tomshardware.de/Speicherchips-…l-Patent,news-240660.html
Ein Prozessor beinhaltet ein Rechenwerk ein Steuerwerk und diverse Register über ein Bussystem ist er mit dem
Arbeitsspeicher in dem die Daten und Programme liegen, externem Speicher und dem Ein- und Ausgabeeinheiten verbunden.
Grob gesagt liegt der Machienencode auf der Festplatte/SSD/Diskette/USB-Stick vor und wird vom Betriebsystem, möchte man
ein Programm starten in den Arbeitsspeicher kopiert. Bei Bytecode-Programmen (.net-ILCODE/Java) oder auch Skripten wird
der maschienenlesbare Code durch eine sog. virtual machine oder einem sog. Interpreter dynamisch im Arbeitsspeicher zur
Laufzeit erschaffen.
Eine weitere Abstraktionsstufe bietet das Dateisystem (FAT32/NTFS/exfat/ext4 usw.), somit liegen unsere maschienenlesbaren
Bytes (8 Bit also 8x 1 oder 0) zu allem überfluss auch noch verteilt auf dem Datenträger und müssen vom Betriebsystem erst
wieder zusammengesetzt werden.
Theoretisch könnte man ein Programm, welches keine speziellen Betriebsystemeigenschaften nutzt mit dem entsprechenden
Dateiheader unter Linux oder Windows, ja sogar ganz ohne Betriebsystem auf dem Computer starten.
Ein Beispiel dafür ist der Linux Kernel, welcher auch zu Teilen aus C und Assembler besteht.
Liegt der Code erst einmal im RAM wird der sog. Program Counter PC dem Prozessor übergeben (der Punkt im Speicher, wo er
loslegen soll) und über das Bussystem, wandern die Daten aus dem Arbeitsspeichern in die Prozessorregister.
Der Prozessor verarbeitet den Code, schickt Daten zurück in den RAM usw.
(und eigentlich auch ein bisschen komplizierter und aufwendiger... daran wollen wir uns nun nicht aufhalten)
Das SNES hat kein Dateisystem und keine Festplatte, es besitzt jedoch genauso Arbeitsspeicher (Main RAM 128 kB) sowie
eine Central processing unit (CPU) (Ricoh 5A22, based on a 16-bit 65c816 core) und Bussysteme.
Da es sich nicht um eine x86 basierte CPU handelt brauchen wir auf unserem Computer einen sog. Cross Compiler.
Ein Cross Compiler erzeugt ausführbaren Code für eine andere Plattform als für diejenige auf der man sich gerade
befindet. (btw. es gibt auch auf x86 basierende Konsolen)
Dieser ist in unserer Toolchain/Devkit s.o. enthalten.
Das SNES hält auch kein Betriebsystem vor, somit müssen alle hardwarenahen Funktionen vergleichar mit dem Kernel mit
in userem Programm vorgehalten werden.
Diese Funktionen werden in unserer Library vorgehalten und werden je nach Bedarf zu teilen vom Compiler in unserem
SNES-Programm eingebettet. Etwas ähnliches passiert, wenn man seinen "normalenCompiler von dynamic auf static schaltet,
hierbei werden auch alle nötigen Librarys mit eingebettet, mit Ausnahme einiger Betriebsystemfunktionen natürlich.
Da es kein Dateisystem gibt, müssen unsere Sprites und Sounds auch in das sog. Rom-Image eingebunden werden.
Zugriff darauf, erhalten wird, indem wir mit Speicheroffsets arbeiten. Mehr dazu später, sobald wir die ersten Brocken
C Programmieren können und uns an's SNES wagen.
Auch lässt sich der Cartridge Space direkt Adressieren. :>
Übrigens arbeiten so auch manche Flashcarts. Da sie mit der Cartridge auch am Bussystem angeschlossen sind,
lassen sich bestimmte Adressen dazu nutzen z.B. Rückmeldungen an den FPGA oder CPLD zu geben.
Somit kann man die Geräte über Menüs anweisen Daten von der SD-Karte in den eingen SRAM zu kopieren.
In der Realität ist es auch gar nicht nötig, wie bei den modernen Betriebsystemen jedliche Funktionalitäten als
updatebare Programmbibliotheken vorzuhalten. So mancher Effekt wird direkt per Hardware realisiert.
Spätere Konsolen wie das N64 haben durch ihre gesteigerte Rechenleistung schon weitaus mehr abstrahieren können.
Dort gibt es aufwendige Bibliotheken und C-Compiler von Haus aus.
SNES Spiele wurden tatsächlich wohl mitunter als letzte Konsolengeneration komplett in Assembler programmiert. Das Devkit
welches wir nutzen wollen, ist mehr oder weniger eine nachträgliche Community-Lösung, welche für uns eine große
Erleichterung darstellt aber auch performance Einschränkungen bringt.
demnächst: Part II - Installation des Devkits.
bis dahin könnte ihr euch bereits für ein Betriebsystem und einen Editor oder IDE entscheiden.
Natürlich könnt ihr auch Rückfragen stellen. Ich bin Kritikfähig aber eigentlich interessiert mich das Feedback nicht
sonderlich, wir halten hier ja keine Schulreferate. Das läuft hier eher unter - Take it or leave it.
Scheinbar gibt's auch noch eine besser passende Installationsanleitung hier:
portabledev.com/wiki/doku.php?id=installation_en
hätte ich das einmal vorher gewusst. :>
Part II
Devkit Installation unter Windows.
Extra für euch habe ich einmal eine Installation unter Windows durchgeführt.
Zuerst besuchen wir das pvsneslib Repository.
code.google.com/p/pvsneslib/
unter 'Source' können wir dann die URL für den 'checkout' einsehen
Nun laden wir uns ein Programm für den Download herunter:
der erste Treffer bei google.
das Programm kann einfach ohne Installation gestartet werden
Ich habe einen Ordner auf dem Desktop angelegt names 'snesdevkit'.
Ihr könnte euch natürlich auch eine andere Location suchen, falls es euch dort nicht behagt.
Tragt nun die checkout-url von google code in das Programm ein, sowie euer neues Zielverzeichnis
und klickt auf 'Start'.
Sieht euer Fenster nach einigen Sekunden so aus, hat es funktioniert.
Man könnte jetzt bereits anfangen mittels Eingabeaufforderung oder PowerShell, Programme zu Übersetzen.
Es ist allerdings sinnvoll und einfacher, die vorhanden 'Makefiles' dafür zu benutzen.
in Makefiles werden informationen hinterlegt, welche dem Programm 'make' dazu dienen alle Schritte die zum
erfolgreichen erzeugen des Programm nötig sind durchzuführen.
Die Makefiles, welche pvsneslib beiliegen sind eigentlich für Gnu-Linux gededacht, weshalb wir unser Windows etwas
Ausstatten müssen.
Zuerst installieren wir einmal das Programm 'make' an sich.
Hier klicken wir einfach auf 'Setup', laden das Installationsprogramm herunter und klicken ein paar mal auf weiter.
gnuwin32.sourceforge.net/packages.html#Setup
Als nächstes benötigen wir noch sed und das Paket Sh-Uitls.
sed ist ein Skriptinterpreter und in Sh-Utils (coreutils) befinden sich weitere Befehle welche in den Makefiles
verwendet werden wie mv, rm, basefile usw.
Einige davon (bis auf basefile) kann die Windows PowerShell auch nativ verstehen.
Zu guter letzte, installieren wir noch Python-2.7, ja es gibt ein einziges Python Skript.
python.org/downloads/release/python-278/
Jetzt sind wir bereits mit der Installation bis auf den Texteditor/IDE durch und kommen zur Konfiguration.
Als erstes werden wir die Windows PATH Variable erweitern.
Will man Befehle in der Shell ausführen, sind Programme welche in einem Ordner, welcher in der PATH Environmentvariable
eingetragen wurde, aus jedem Verzeichnis heraus aufrufbar.
Um die PATH env zu erweitern gehen wir zunächst in die Systemsteuerung und starten 'System'.
nun klicken wir auf 'Erweiterte Systemeinstellungen'
und jetzt auf 'Umgebunsvariablen'
die Zeile 'PATH' anklicken und dann auf 'Bearbeiten'
Hier tragen wir zwei Ordner ein, bei mir sind das:
C:\Users\saturnu\Desktop\snesdevkit\devkitsnes\bin
C:\Users\saturnu\Desktop\snesdevkit\devkitsnes\tools
Ihr werdet sicherlich schon entdeckt haben, dass sie durch ein Semikolon getrennt werden müssen.
Jetzt werden zusätzlich noch zwei neue Umgebungsvariablen angelegt.
DEVKITSNES C:\Users\saturnu\Desktop\snesdevkit
DEVKIT65XX C:\Users\saturnu\Desktop\snesdevkit\devkitsnes
Danach können wir die Maske mit OK verlassen.
Aus einem mir nicht einleuchtenden Grund sind leider in allen Makefiles der examples, die env vars
zu DEVKITSNES und DEVKIT65XX hardcoded.
Wollen wir nun ein Beispiel Übersetzen, müssen wir die jeweilige Makefile anpassen.
Hier ein Bsp. zum input example:
C:\Users\saturnu\Desktop\snesdevkit\snes-examples\input
Hier kann man einfach jeweils eine '#' vor die Zeile setzen und dann speichern.
Übersetzt wird nun, indem man den Befehl 'make' in diesem Verzeichnis ausführt.
Dazu kann man eine eine Batch File erstellen, eine Verknüpfung anlegen oder mit der PowerShell bzw. Eingabeaufforderung
hin navigieren.
Habt ihr alles richtig gemacht, solltet ihr mit einer 'input.sfc' belohnt werden
Dies ist nun bereits euer ROM, welches ihr in eurem Emulator oder SNES ausprobieren könnt.
portabledev.com/wiki/doku.php?id=installation_en
hätte ich das einmal vorher gewusst. :>
Part II
Devkit Installation unter Windows.
Extra für euch habe ich einmal eine Installation unter Windows durchgeführt.
Zuerst besuchen wir das pvsneslib Repository.
code.google.com/p/pvsneslib/
unter 'Source' können wir dann die URL für den 'checkout' einsehen
Nun laden wir uns ein Programm für den Download herunter:
der erste Treffer bei google.
das Programm kann einfach ohne Installation gestartet werden
Ich habe einen Ordner auf dem Desktop angelegt names 'snesdevkit'.
Ihr könnte euch natürlich auch eine andere Location suchen, falls es euch dort nicht behagt.
Tragt nun die checkout-url von google code in das Programm ein, sowie euer neues Zielverzeichnis
und klickt auf 'Start'.
Sieht euer Fenster nach einigen Sekunden so aus, hat es funktioniert.
Man könnte jetzt bereits anfangen mittels Eingabeaufforderung oder PowerShell, Programme zu Übersetzen.
Es ist allerdings sinnvoll und einfacher, die vorhanden 'Makefiles' dafür zu benutzen.
in Makefiles werden informationen hinterlegt, welche dem Programm 'make' dazu dienen alle Schritte die zum
erfolgreichen erzeugen des Programm nötig sind durchzuführen.
Die Makefiles, welche pvsneslib beiliegen sind eigentlich für Gnu-Linux gededacht, weshalb wir unser Windows etwas
Ausstatten müssen.
Zuerst installieren wir einmal das Programm 'make' an sich.
Hier klicken wir einfach auf 'Setup', laden das Installationsprogramm herunter und klicken ein paar mal auf weiter.
gnuwin32.sourceforge.net/packages.html#Setup
Als nächstes benötigen wir noch sed und das Paket Sh-Uitls.
sed ist ein Skriptinterpreter und in Sh-Utils (coreutils) befinden sich weitere Befehle welche in den Makefiles
verwendet werden wie mv, rm, basefile usw.
Einige davon (bis auf basefile) kann die Windows PowerShell auch nativ verstehen.
Zu guter letzte, installieren wir noch Python-2.7, ja es gibt ein einziges Python Skript.
python.org/downloads/release/python-278/
Jetzt sind wir bereits mit der Installation bis auf den Texteditor/IDE durch und kommen zur Konfiguration.
Als erstes werden wir die Windows PATH Variable erweitern.
Will man Befehle in der Shell ausführen, sind Programme welche in einem Ordner, welcher in der PATH Environmentvariable
eingetragen wurde, aus jedem Verzeichnis heraus aufrufbar.
Um die PATH env zu erweitern gehen wir zunächst in die Systemsteuerung und starten 'System'.
nun klicken wir auf 'Erweiterte Systemeinstellungen'
und jetzt auf 'Umgebunsvariablen'
die Zeile 'PATH' anklicken und dann auf 'Bearbeiten'
Hier tragen wir zwei Ordner ein, bei mir sind das:
C:\Users\saturnu\Desktop\snesdevkit\devkitsnes\bin
C:\Users\saturnu\Desktop\snesdevkit\devkitsnes\tools
Ihr werdet sicherlich schon entdeckt haben, dass sie durch ein Semikolon getrennt werden müssen.
Jetzt werden zusätzlich noch zwei neue Umgebungsvariablen angelegt.
DEVKITSNES C:\Users\saturnu\Desktop\snesdevkit
DEVKIT65XX C:\Users\saturnu\Desktop\snesdevkit\devkitsnes
Danach können wir die Maske mit OK verlassen.
Aus einem mir nicht einleuchtenden Grund sind leider in allen Makefiles der examples, die env vars
zu DEVKITSNES und DEVKIT65XX hardcoded.
Wollen wir nun ein Beispiel Übersetzen, müssen wir die jeweilige Makefile anpassen.
Hier ein Bsp. zum input example:
C:\Users\saturnu\Desktop\snesdevkit\snes-examples\input
Hier kann man einfach jeweils eine '#' vor die Zeile setzen und dann speichern.
Übersetzt wird nun, indem man den Befehl 'make' in diesem Verzeichnis ausführt.
Dazu kann man eine eine Batch File erstellen, eine Verknüpfung anlegen oder mit der PowerShell bzw. Eingabeaufforderung
hin navigieren.
Habt ihr alles richtig gemacht, solltet ihr mit einer 'input.sfc' belohnt werden
Dies ist nun bereits euer ROM, welches ihr in eurem Emulator oder SNES ausprobieren könnt.
Part III 'C'
Ich hoffe die Installation von pvsneslib hat soweit funktioniert und ihr habt euren Texteditor
griffbereit.
An dieser Stelle könnten wir uns jetzt lang und breit über Vor- und Nachteile,
Compiler, ANSI- bzw. POSIX-C unterhalten, tun wir aber nicht.
Ich finde wir sollten gleich auf dem SNES loslegen anstatt hier lange Trockenübungen auf dem
Computer hinzulegen.
-Infos-
Dateiendungen:
.c - C-Quellcodedateien
.h - Headerdateien
.obj - Objektdateien (Maschinencodedatei)
Andere:
.asm - Assemlber-Quellcodedateien
.bmp - Bitmaps
.pal - Farbpaletten
.pic - Picturedateien
.srm - sram Abbilder
.sym - Symboldateien
.sfc - Super Famicom ROM
.brr - Bit Rate Reduction - Sounddateien
.pnproj - Programmer's Notepad Project File (hat wohl der Entwickler genutzt)
.pnps - Programmer's Notepad State File
Unser erstes HalloWelt Programm:
Zuerst schauen wir uns an wie ein sehr simples 'HalloWelt' Programm auf dem PC aussehen würde, welches den
Text "Hallo Weltauf dem Bildschirm ausgibt.
Datei: hallo.c
Was wir hier sehen ist eine Funktion namens 'main' ohne Rückgabewert (da void), in dessen Funktionsrumpf ( alles was zwischen {..} steht ) eine
weitere Funktion namens 'printf' aufgerufen wird, dessen Übergabeparameter ein String (Character Zeichenkette) "Hallo Weltist.
Damit der Compiler weiß welche Funktion er als erstes aufrufen soll, nennt man immer die Einstiegsfunktion 'main()'.
Funktionsnamen sollten meistens klein geschrieben werden.
Fassen wir zusammen:
Beim Aufrufen von Funktionen wird hinten, anstatt wie bei der Implementation geschweifte Klammern, ein Semikolon angehangen.
C bzw. die meisten Compiler bieten viele Freiheiten. Vollständiger und richtiger würde es so aussehen.
Datei hallo2.c
Die Anweisung '#include' steht vor Headerdateien, in denen die Funktionsnamen passend zu den Programmbibliotheken mit ihren Übergabeparametern und Rückgabewerten
deklariert sind.
Die <>-Klammern geben an, dass sich die Headerdatei im Standart-Includepath befindet, welcher auch mittels Commandozeilenparameter meistens
mit '-I' erweitert werden kann. der Librarypath wird mit '-L' erweitert.
Mittels Anführungszeichen können direkt Pfadangaben zur Headerdatei gemacht werden.
#include "../hallo.h"
würde z.B. dazu führen, dass nach der Datei eine Ebene unter dem 'working directory' gesucht wird.
int main()
Diesmal haben wir einen Rückgabewert 'int' angegeben, hierbei handelt es sich um einen signed integer Wert (Ganzzahl positiv und negativ).
Besitzt die Funktion einen Rückgabewert, muss sich irgendwo in ihrem Funktionsrumpf die Anweisung 'return WERT;' befinden.
printf ("Hallo Welt\n");
Neu ist hier auch das '\n', dies ist eine sog. escape sequence. Der Backslash '\' gibt an, dass das/die nächste(n) Zeichen eine Formatierung einleiten.
n - z.B. springt in die nächste Zeile (new line)
Bevor wir jetzt unser eigenes "Hallo Weltauf dem SNES ausgeben, führen wir noch schnell den Begriff der Variable ein.
Ähnlich wie in der Mathematik, können Variablen unterschiedliche Werte enthalten, auch können sie sich ändern.
Die wichtigsten Variablentypen wären.
int - Integer (Ganzzahl)
short (int)
long
float - Gleitkommazahl
double
long double
char - ein Byte bzw. Character -127 bis +127.
Möchte man die Variable nur im positiven bereich benutzen kann man sie ohne Vorzeichen 'unsinged' verwenden.
unsinged char - 0-255 oder auch hexadecimal 0x00 - 0xFF oder binär 0000 0000 bis 1111 1111 (wichtig beim bitshifting) 2x nibble :>
häufig liest man auch Umbennenungen der klassischen Bezeichnungen.
u8 -> unsinged char - 8bit s.o.
u16 -> unsigend int -> 16bit 0000 0000 0000 0000 -> könnte man also aus 2x char zusammensetzen
u32 long
u64 long long
Genau genommen, ist es Plattformabhängig wie große die Variablen jeweils sind, selbst ein Byte muss nicht immer aus 8 Bit bestehen.
Es besteht meistens aus 8 Bit da historisch damit genau ein Buchstabe dargestellt wurde, deshalb auch die Bezeichnung char (Character) unter C.
Wie verwendet man Variablen.
Deklaration
int autoreifen;
Wertzuweisung
autoreifen = 60;
Kurzschreibweise
int autoreifen = 60;
Um eins erhöhen
autoreifen = autoreifen+1;
Kurzschreibweise
autoreifen++; //nutzt man autoreifen++; als Übergabeparameter wird erst der Wert übergeben und danach hochgezählt.
Erniedrigen
autoreifen--; //Tipp: nutzt man --autoreifen; als Übergabeparameter, wird gleich der niedrigere wert übergeben;
Erhöhen um 4
autoreifen = autoreifen + 4;
Kruzschreibweise
autoreifen+=4;
Negation
int autoreifen=1;
!autoreifen //entspricht dem Wert 0
Kommentare
//einzeiliger Kommentar
/*
Kommentar über
mehrere Zeilen
*/
------
So da wir nun das Wichtigste zu C wissen, sehen wir uns einmal das Hallo Welt - Beispiel von pvsneslib an.
Was kennen wir hier alles?
Alles anzeigen
Na das kommt uns doch alles sehr bekannt vor nicht wahr.
Abgesehen von den speziellen SNES-Funktionen der Library haben wir zwei neuen Sachen beobachtet,
Schleifen und Pointer.
Schleifen wiederholen ihren Funktionsrumpf solange wie ihre Bedingung als Übergabeparameter wahr ist.
Die drei wichtigsten Schleifen wären:
Die 'for' Schleife
hier wird also der Variable 'c' der Wert 0 zugewiesen und je Durchgang der Rumpf ausgeführt und danach 'c' um 1 erhöht.
Die geschieht so lange wie 'c' kleiner als 5 ist.
Die 'while' Schleife
Sie wird sehr gerne für sog. main-loops verwendet und endet häufig erst wenn sie gewaltsam beendet wird.
Stoppen kann man eine Schleife, indem man z.B. den 'continue;' Befehl benutzt oder die Funktion mittels 'return;' abbricht.
Oder man lässt die Bedingung ungültig werden.
Die 'do-while' Schleife.
Hierbei handelt es sich im Gegensatz zu den Kopfgesteuerten Schleifen um eine Fußgesteuerte Schleife.
Eigentlich das selbe wie die 'while-Schleife' nur kann man sie initial ohne eine Bedingung starten lassen.
Pointer!
Aus unerklärlichen Gründen geliebt und gehasst.
Manchmal sogar so sehr, dass man in anderen Programmiersprachen darauf absichtlich verzichtet hat. (z.B. Java)
consoleInitText(0, 0, &snesfont); //hier wird anstelle der Wertes von snesfont, die Speicheradresse von snesfont übergeben.
Wir erinnern uns an 'Part I', dort wurde erwähnt, dass wir auf dem SNES kein Dateisystem besitzen und somit unsere Sprites
direkt mit der Cartridgespace addressrange aufrufen müssen.
Die Schrift, besteht in der Library aus kleinen Sprites und die Funktion 'consoleDrawText'
consoleDrawText(x,y,text);
setzt mittels Schleifen den Text aus kleinen Sprites (oder besser Background Tiles) auf dem Bildschirm zusammen. Möchte man die Schriftart ändern, kann
man z.B. mittels 'mtpaint' die 'pvsneslibfont.bmp' Bilddatei bearbeiten.
Ich gebe zu das Thema Pointer ist etwas noch komplizierter aber wir wollen uns ja hier nicht gleich den Spaß verderben.
Im Beispiel kam uns auch noch ein 'extern' unbekannt vor.
Erstellen wir doch mal eine Eigene Funktion und sprechen dabei über den sog. Scope (Anwendbarkeitsbereich) einer Variable.
Alles anzeigen
Richtig, wir wollen über den Scope von Variablen Sprechen.
Man unterscheidet zwischen globalen und lokalen Variablen.
würde eine Variable am Anfang der Quellcodedatei z.b.
#include <snes.h>
extern char snesfont;
int autoreifen;
ist snesfont eine globale Variable, welche lebt (Speicher belegt) bis das Programm beendet wird.
Zusätzlich kann sie in allen Funktionen angesprochen und verwendet werden.
Ein negativer Aspekt ist auch, dass der name der Variable immer belegt bleibt.
Die Variable autoreifen, bestitzt den file-scope von hello_world.c und kann nur in anderen Quellcodedateien
verwendet werden, wenn sie dort wie bei snesfont mit extern erweitert wird.
Andernfalls, würde es dort eine neue Variable geben mit dessen namen.
Lokale Variablen wie 'line' in 'mehrfach()' belegt nur Speicher und existiert nur innerhalb dieser Funktion,
sowie z.B. dessen Schleifen.
Wird eine Variable innerhalb einer Schleife erzeugt ist ihr Scope, auf dessen Unteraktivitäten und Verschachtelungen
beschränkt.
Kontrollstrukturen:
Die if-Bedingung.
Wir haben schon Bedingungen bei den Schleifen kennen gelernt,
ganz ohne Wiederholungen funktioniert dies bei 'if()'
Gerne wird auch 'else' in der Kombination mit 'if' verwendet oder diverse 'if' ineinander verschachtelt.
Alles anzeigen
Die switch()...
Eigentlich würde ich noch gerne was zu Bitwise Operatoren, Casten und zu arrays und speziell Strings....
Evtl. Rekursionen :>
To be continued.
Den Rest bekommen wir beim Bügeln.
EDIT: 1mio nächtliche Tippfehler behoben
Ich hoffe die Installation von pvsneslib hat soweit funktioniert und ihr habt euren Texteditor
griffbereit.
An dieser Stelle könnten wir uns jetzt lang und breit über Vor- und Nachteile,
Compiler, ANSI- bzw. POSIX-C unterhalten, tun wir aber nicht.
Ich finde wir sollten gleich auf dem SNES loslegen anstatt hier lange Trockenübungen auf dem
Computer hinzulegen.
-Infos-
Dateiendungen:
.c - C-Quellcodedateien
.h - Headerdateien
.obj - Objektdateien (Maschinencodedatei)
Andere:
.asm - Assemlber-Quellcodedateien
.bmp - Bitmaps
.pal - Farbpaletten
.pic - Picturedateien
.srm - sram Abbilder
.sym - Symboldateien
.sfc - Super Famicom ROM
.brr - Bit Rate Reduction - Sounddateien
.pnproj - Programmer's Notepad Project File (hat wohl der Entwickler genutzt)
.pnps - Programmer's Notepad State File
Unser erstes HalloWelt Programm:
Zuerst schauen wir uns an wie ein sehr simples 'HalloWelt' Programm auf dem PC aussehen würde, welches den
Text "Hallo Weltauf dem Bildschirm ausgibt.
Datei: hallo.c
Was wir hier sehen ist eine Funktion namens 'main' ohne Rückgabewert (da void), in dessen Funktionsrumpf ( alles was zwischen {..} steht ) eine
weitere Funktion namens 'printf' aufgerufen wird, dessen Übergabeparameter ein String (Character Zeichenkette) "Hallo Weltist.
Damit der Compiler weiß welche Funktion er als erstes aufrufen soll, nennt man immer die Einstiegsfunktion 'main()'.
Funktionsnamen sollten meistens klein geschrieben werden.
Fassen wir zusammen:
Beim Aufrufen von Funktionen wird hinten, anstatt wie bei der Implementation geschweifte Klammern, ein Semikolon angehangen.
C bzw. die meisten Compiler bieten viele Freiheiten. Vollständiger und richtiger würde es so aussehen.
Datei hallo2.c
Die Anweisung '#include' steht vor Headerdateien, in denen die Funktionsnamen passend zu den Programmbibliotheken mit ihren Übergabeparametern und Rückgabewerten
deklariert sind.
Die <>-Klammern geben an, dass sich die Headerdatei im Standart-Includepath befindet, welcher auch mittels Commandozeilenparameter meistens
mit '-I' erweitert werden kann. der Librarypath wird mit '-L' erweitert.
Mittels Anführungszeichen können direkt Pfadangaben zur Headerdatei gemacht werden.
#include "../hallo.h"
würde z.B. dazu führen, dass nach der Datei eine Ebene unter dem 'working directory' gesucht wird.
int main()
Diesmal haben wir einen Rückgabewert 'int' angegeben, hierbei handelt es sich um einen signed integer Wert (Ganzzahl positiv und negativ).
Besitzt die Funktion einen Rückgabewert, muss sich irgendwo in ihrem Funktionsrumpf die Anweisung 'return WERT;' befinden.
printf ("Hallo Welt\n");
Neu ist hier auch das '\n', dies ist eine sog. escape sequence. Der Backslash '\' gibt an, dass das/die nächste(n) Zeichen eine Formatierung einleiten.
n - z.B. springt in die nächste Zeile (new line)
Bevor wir jetzt unser eigenes "Hallo Weltauf dem SNES ausgeben, führen wir noch schnell den Begriff der Variable ein.
Ähnlich wie in der Mathematik, können Variablen unterschiedliche Werte enthalten, auch können sie sich ändern.
Die wichtigsten Variablentypen wären.
int - Integer (Ganzzahl)
short (int)
long
float - Gleitkommazahl
double
long double
char - ein Byte bzw. Character -127 bis +127.
Möchte man die Variable nur im positiven bereich benutzen kann man sie ohne Vorzeichen 'unsinged' verwenden.
unsinged char - 0-255 oder auch hexadecimal 0x00 - 0xFF oder binär 0000 0000 bis 1111 1111 (wichtig beim bitshifting) 2x nibble :>
häufig liest man auch Umbennenungen der klassischen Bezeichnungen.
u8 -> unsinged char - 8bit s.o.
u16 -> unsigend int -> 16bit 0000 0000 0000 0000 -> könnte man also aus 2x char zusammensetzen
u32 long
u64 long long
Genau genommen, ist es Plattformabhängig wie große die Variablen jeweils sind, selbst ein Byte muss nicht immer aus 8 Bit bestehen.
Es besteht meistens aus 8 Bit da historisch damit genau ein Buchstabe dargestellt wurde, deshalb auch die Bezeichnung char (Character) unter C.
Wie verwendet man Variablen.
Deklaration
int autoreifen;
Wertzuweisung
autoreifen = 60;
Kurzschreibweise
int autoreifen = 60;
Um eins erhöhen
autoreifen = autoreifen+1;
Kurzschreibweise
autoreifen++; //nutzt man autoreifen++; als Übergabeparameter wird erst der Wert übergeben und danach hochgezählt.
Erniedrigen
autoreifen--; //Tipp: nutzt man --autoreifen; als Übergabeparameter, wird gleich der niedrigere wert übergeben;
Erhöhen um 4
autoreifen = autoreifen + 4;
Kruzschreibweise
autoreifen+=4;
Negation
int autoreifen=1;
!autoreifen //entspricht dem Wert 0
Kommentare
//einzeiliger Kommentar
/*
Kommentar über
mehrere Zeilen
*/
------
So da wir nun das Wichtigste zu C wissen, sehen wir uns einmal das Hallo Welt - Beispiel von pvsneslib an.
Was kennen wir hier alles?
C-Quellcode
- /* Ganz klar! hier wird ein Header eingebunden und wir haben somit
- Zugriff auf alle Funktionen, welche in der Datei snes.h definiert sind.
- */
- #include <snes.h>
- /*
- hier wird wohl eine Bytevariable deklariert - 'extern' gibt an, dass die Variable eigentlich in einer anderen
- Datei vorhanden ist, (Spoiler: sie ist in der Datei data.asm)
- */
- extern char snesfont;
- //hier ist unsere 'main' Funktion, also der Einsprungpunkt. mit einem Integer Rückgabewert
- int main(void) {
- // Initialize SNES
- consoleInit(); //eine Funktion welche wohl das SNES in Betriebsbereitschaft für uns bringt.
- // Initialize text console with our font
- consoleInitText(0, 0, &snesfont); //hier sehen wir etwas neues einen sog. Pointer mehr dazu später
- // Now Put in 16 color mode and disable Bgs except current
- setMode(BG_MODE1,0); bgSetDisable(1); bgSetDisable(2); //Backgroundeinstellungen, aha ^^
- // Draw a wonderfull text :P
- /*
- ah, hier ist unser Hallo Welt Text mit zwei weiteren Integerwerten als Koordinatenangaben,
- mehrere Übergabeparameter werden also durch ein Komma getrennt.
- */
- consoleDrawText(10,10,"Hello World !");
- consoleDrawText(7,14,"WELCOME TO LIBSNES");
- consoleDrawText(3,18,"HTTP://WWW.PORTABLEDEV.COM");
- // Wait for nothing :P
- setBrightness(0xF); //wohl Farbeinstellungen
- //dies hier ist eine sog. Schleife - mehr dazu gleich
- while(1) {
- WaitForVBlank();
- }
- //unsere main Funktion hat den Rückgabewert 0
- return 0;
- }
Na das kommt uns doch alles sehr bekannt vor nicht wahr.
Abgesehen von den speziellen SNES-Funktionen der Library haben wir zwei neuen Sachen beobachtet,
Schleifen und Pointer.
Schleifen wiederholen ihren Funktionsrumpf solange wie ihre Bedingung als Übergabeparameter wahr ist.
Die drei wichtigsten Schleifen wären:
Die 'for' Schleife
hier wird also der Variable 'c' der Wert 0 zugewiesen und je Durchgang der Rumpf ausgeführt und danach 'c' um 1 erhöht.
Die geschieht so lange wie 'c' kleiner als 5 ist.
Die 'while' Schleife
Sie wird sehr gerne für sog. main-loops verwendet und endet häufig erst wenn sie gewaltsam beendet wird.
Stoppen kann man eine Schleife, indem man z.B. den 'continue;' Befehl benutzt oder die Funktion mittels 'return;' abbricht.
Oder man lässt die Bedingung ungültig werden.
Die 'do-while' Schleife.
Hierbei handelt es sich im Gegensatz zu den Kopfgesteuerten Schleifen um eine Fußgesteuerte Schleife.
Eigentlich das selbe wie die 'while-Schleife' nur kann man sie initial ohne eine Bedingung starten lassen.
Pointer!
Aus unerklärlichen Gründen geliebt und gehasst.
Manchmal sogar so sehr, dass man in anderen Programmiersprachen darauf absichtlich verzichtet hat. (z.B. Java)
consoleInitText(0, 0, &snesfont); //hier wird anstelle der Wertes von snesfont, die Speicheradresse von snesfont übergeben.
Wir erinnern uns an 'Part I', dort wurde erwähnt, dass wir auf dem SNES kein Dateisystem besitzen und somit unsere Sprites
direkt mit der Cartridgespace addressrange aufrufen müssen.
Die Schrift, besteht in der Library aus kleinen Sprites und die Funktion 'consoleDrawText'
consoleDrawText(x,y,text);
setzt mittels Schleifen den Text aus kleinen Sprites (oder besser Background Tiles) auf dem Bildschirm zusammen. Möchte man die Schriftart ändern, kann
man z.B. mittels 'mtpaint' die 'pvsneslibfont.bmp' Bilddatei bearbeiten.
Ich gebe zu das Thema Pointer ist etwas noch komplizierter aber wir wollen uns ja hier nicht gleich den Spaß verderben.
Im Beispiel kam uns auch noch ein 'extern' unbekannt vor.
Erstellen wir doch mal eine Eigene Funktion und sprechen dabei über den sog. Scope (Anwendbarkeitsbereich) einer Variable.
C-Quellcode
- //hier deklarieren wir die Funktion 'mehrfach'
- int mehrfach(int von, int bis){ //die Variablen 'von' und 'bis' gibt es auch nur während des ausführen der Funktion 'mehrfach()'
- int line;
- //die Variable 'line' lebt nur so lange im Speicher, bis die Funktion 'mehrfach()' wieder beendet ist
- for(line=von; line<bis; line++) //lässt man die {...} weg wird genau die nächste Funktion wiederholt
- consoleDrawText(10, line, "Hello World !");
- return bis-von;
- }
- int main(void) {
- consoleInit();
- consoleInitText(0, 0, &snesfont);
- setMode(BG_MODE1,0); bgSetDisable(1); bgSetDisable(2);
- consoleDrawText(10, 7, "%d mal ausgegeben", mehrfach(3,5));
- /* hier wird ein sog. Formatstring %d (decimal) angewendet um den Rückgabewert von 'mehrfach'
- in den Text einzubauen
- zuerst wird die Funktion 'mehrfach' ausgeführt und sobald der Rückgabewert bekannt ist die Funktion
- 'consoleDrawText()'
- */
- setBrightness(0xF);
- while(1) { WaitForVBlank(); }
- return 0;
- }
Richtig, wir wollen über den Scope von Variablen Sprechen.
Man unterscheidet zwischen globalen und lokalen Variablen.
würde eine Variable am Anfang der Quellcodedatei z.b.
#include <snes.h>
extern char snesfont;
int autoreifen;
ist snesfont eine globale Variable, welche lebt (Speicher belegt) bis das Programm beendet wird.
Zusätzlich kann sie in allen Funktionen angesprochen und verwendet werden.
Ein negativer Aspekt ist auch, dass der name der Variable immer belegt bleibt.
Die Variable autoreifen, bestitzt den file-scope von hello_world.c und kann nur in anderen Quellcodedateien
verwendet werden, wenn sie dort wie bei snesfont mit extern erweitert wird.
Andernfalls, würde es dort eine neue Variable geben mit dessen namen.
Lokale Variablen wie 'line' in 'mehrfach()' belegt nur Speicher und existiert nur innerhalb dieser Funktion,
sowie z.B. dessen Schleifen.
Wird eine Variable innerhalb einer Schleife erzeugt ist ihr Scope, auf dessen Unteraktivitäten und Verschachtelungen
beschränkt.
Kontrollstrukturen:
Die if-Bedingung.
Wir haben schon Bedingungen bei den Schleifen kennen gelernt,
ganz ohne Wiederholungen funktioniert dies bei 'if()'
Gerne wird auch 'else' in der Kombination mit 'if' verwendet oder diverse 'if' ineinander verschachtelt.
C-Quellcode
Die switch()...
Eigentlich würde ich noch gerne was zu Bitwise Operatoren, Casten und zu arrays und speziell Strings....
Evtl. Rekursionen :>
To be continued.
Den Rest bekommen wir beim Bügeln.
EDIT: 1mio nächtliche Tippfehler behoben
sábado, 24 de setembro de 2016
Posted by deltablade