DotMatrixPong 1.0
|
Diese Datei enthält das gesamte Programm/Spiel für den Mikrocontroller. Mehr ...
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>
gehe zum Quellcode dieser Datei
Makrodefinitionen | |
#define | F_CPU 1000000 |
Die Geschwindigkeit des Mikrocontroller in Hertz, für _delay_ms von Bedeutung. | |
#define | SCK PB4 |
Dieser Pin wird als Clock-Signal für die seriellen Daten am 74HC595 benutzt. | |
#define | OCK PB3 |
Dieser Pin wird als Clock-Signal zum Schalten der internen Register an die Output-Pins des 74HC595 benutzt. | |
#define | DS PB1 |
Dieser Pin wird zur Übertragung der seriellen Daten zum 74HC595 benutzt. | |
#define | STATIC 0 |
Diese Definition steht als Funktionsparameter für die output_text-Funktion und bestimmt den Text als statisch. | |
#define | SCROLLING 1 |
Diese Definition steht als Funktionsparamater für die output_text-Funktion und lässt den Text scrollen. | |
Funktionen | |
void | clock_serial () |
Diese Funktion kümmert sich um eine HIGH-LOW-Taktflanke bei dem Clockeingang für die seriellen Daten des 74HC595. | |
void | clock_output () |
Diese Funktion kümmert sich um eine HIGH-LOW-Taktflanke bei dem Clockeingang des Output der internen Register des 74HC595. | |
void | shift_high () |
Diese Funktion schiebt einen HIGH-Zustand in den 74HC595. | |
void | shift_low () |
Diese Funktion schiebt einen LOW-Zustand in den 74HC595. | |
void | output (uint8_t *in, uint8_t screen) |
Diese Funktion gibt den übergebenen 5 x 8 Bit großen Speicherbereich aus. | |
void | matrix_output () |
Diese Funktion rechnet den Ausgabe-Puffer in die reelle Ausgabe um. | |
void | matrix_screen_pixel (uint32_t input) |
Diese Funktion taktet vorbereitete Zeilen mit einem gesetzten Pixel an die Shift-Register raus. | |
void | adc_init () |
Diese Funktion initialisiert den ADC (Analog to Digtial Converter) mit Werten. | |
void | get_adcval (uint16_t *val) |
Diese Funktion liest einen ADC-Wert aus und schreibt diesen in den angegeben 16-Bit-Buffer. | |
uint8_t | getkey () |
Diese Funktion wartet auf eine Tasteneingabe. | |
void | timer_init () |
In dieser Funktion wird der Timer initialisiert. | |
void | clear_screen () |
Diese Funktion dient zur Löschung der Punktmatrix. | |
void | start_game () |
Diese Funktion startet das Spiel und setzt alle entsprechenden Umgebungsvariablen. | |
void | game_failed () |
Diese Funktion wird aufgerufen, falls ein Spieler den Ball durchgelassen hat. | |
void | output_game () |
Diese Funktion gibt das Spielfeld auf den beiden Matrizen aus. | |
void | cursor_a_down () |
Diese Funktion setzt den Cursor A um einen Pixel nach unten. | |
void | cursor_a_up () |
Diese Funktion setzt den Cursor A um einen Pixel nach oben. | |
void | cursor_b_down () |
Diese Funktion setzt den Cursor B um einen Pixel nach unten. | |
void | cursor_b_up () |
Diese Funktion setzt den Cursor B um einen Pixel nach oben. | |
void | next_r_ball () |
Diese Funktion berechnet die nächste Position des Spielballs. | |
uint8_t | check_ball_coll () |
Diese Funktion überprüft, ob der Ball einen Cursor berührt. | |
uint32_t | rand () |
Diese Funktion dient als Zufallszahlengenerator. | |
void | output_text (char *text, uint8_t func) |
Diese Funktion gibt einen Text auf den beiden Punktmatrizen aus. | |
int | main () |
Diese Funktion ist der Einsprungspunkt des Programmes und nimmt alle nötigen Initialisierungen vor. | |
void | adc_start () |
Diese Funktion startet den ADC (Analog to Digital Converter) | |
ISR (TIMER0_OVF_vect) | |
Diese Funktion bildet die anzuspringende Interrupt-Funktion bei einem Timer0-Overflow. | |
Variablen | |
uint8_t friendly_sm[5] | PROGMEM = {0x0C,0x12,0x0,0x12,0x0} |
Diese Variable enthält einen freundlichen Smiley zur Ausgabe auf der Punktmatrix. | |
uint8_t | cursor_a [5] = {0x0,0x1,0x1,0x0,0x0} |
Diese Variable enthält den Cursor des Spielers A. Diese Variable wird ebenfalls zur Kollisionsüberprüfung benutzt. | |
uint8_t | cursor_b [5] = {0x0,0x00,0x40,0x40,0x00} |
Diese Variable enthält den Cursor des Spielers B. Diese Variable wird ebenfalls zur Kollisionsüberprüfung benutzt. | |
uint8_t | ball [5] = {0x0,0x0,0x4,0x0,0x0} |
Diese Variable enthält den Spielball. Diese Variable wird ebenfalls zur Kollisionsüberprüfung benutzt. | |
uint8_t | screen_buffer_first [5] |
Diese Variable enthälten den Video-Buffer für die linke Punktmatrix. Dieser Buffer wird auf der Punktmatrix ausgegeben. | |
uint8_t | screen_buffer_second [5] |
Diese Variable enthälten den Video-Buffer für die rechte Punktmatrix. Dieser Buffer wird auf der Punktmatrix ausgegeben. | |
volatile uint8_t | ball_dir = 0 |
Diese Variable gibt an, in welche Richtung der Ball steuert. 0 = links, 1 = rechts. | |
volatile uint16_t | ball_timer = 0 |
Diese Variable enthält eine Zeitverzögerung für die Spielzyklen, damit das Spiel in angenehmer Zeit abläuft. Alle 100 Zählungen, ein Zyklus. | |
uint8_t | rand_timer = 0 |
Diese Variable ist ein Zufallszähler, welcher mit dem ball_timer subtrahiert wird und selber zu regelmäßgen Zeiten inkrementiert und zurückgesetzt wird. | |
uint8_t | game_running = 0 |
Diese Variable gibt an, ob das Spiel derzeit läuft, oder nicht. 0 = läuft nicht, 1 = läuft. | |
volatile uint8_t | game_failed_var = 0x0 |
Diese Variable gibt an, ob der Ball an einem Rand das Spielfeld verlassen hat. 1 = rechter Rand, 2 = linker Rand. | |
uint8_t | points_a = 0 |
Diese Variable enthält den Spielstand für den Spieler A. | |
uint8_t | points_b = 0 |
Diese Variable enthält den Spielstand für den Spieler B. |
Diese Datei enthält das gesamte Programm/Spiel für den Mikrocontroller.
Definiert in Datei pong.c.
void adc_init | ( | ) |
Diese Funktion initialisiert den ADC (Analog to Digtial Converter) mit Werten.
Dabei wird zunächst eine trash-Variable (Müll-Variable, d.h diese Variable wird mit unsinnigen Daten belegt) angelegt, da der ADC am Ende der Initialisierung einen Wert auslesen lassen muss, bevor dieser korrekt funktioniert. Als nächstes wird im Register ADMUX das Bit MUX0 gesetzt, welches bedeutet, das Analog-Daten für die ADC-Konvertierungen aus dem Pin PB2 gelesen werden sollen. Im nächsten Schritt wird im Register DIDR0 das Bit ADC0D gesetzt, womit angewiesen wird, dass aus dem Pin PB2 keine digitalen Daten mehr gelesen werden müssen. Das Register ADCSRA wird mit den Bits ADPS1 und ADPS0 belegt, womit erreicht wird, dass der ADC einen Prescaler von 8 besitzt. Da dieser AVR Atmel Attiny45 mit einem Megahertz betrieben wird, wird mit dem Prescaler erreicht, dass die Frequenzahl im Bereich zwischen 50kHz und 200kHz liegt (Hier: 125kHz). Dies wird benötigt, damit der ADC mit genauer Auflösung arbeiten kann. Das Bit ADEN wird gesetzt, damit der ADC angeschaltet wird. Damit startet jedoch noch keine Konvertierung. Im letzten Schritt wird ein ADC-Wert ausgelesen, danach ist der ADC vollständig einsatzbereit.
void adc_start | ( | ) |
uint8_t check_ball_coll | ( | ) |
Diese Funktion überprüft, ob der Ball einen Cursor berührt.
Dabei werden zunächst beide Cursor in einem gemeinsamen Buffer gespeichert und die Zeile bestimmt, in der sich gerade der Ball befindet. Bei den vielen if-Abfragen finden Überprüfungen statt, ob der Ball sich auf der gleichen Position mit einem Cursor überliegt, welcher temporär einen Pixel in die Richtung des Balles geshiftet wird. Diese Abfrage findet auch für Diagonalen statt.
void clear_screen | ( | ) |
void clock_output | ( | ) |
Diese Funktion kümmert sich um eine HIGH-LOW-Taktflanke bei dem Clockeingang des Output der internen Register des 74HC595.
Dabei wird der dafür benutzte Pin PB3 auf High und danach wieder auf Low gesetzt, indem das entsprechende Bit im PORTB gesetzt und wieder gelöscht wird. Dadurch werden die Zustände der internen Register des 74HC595 auf die Output-Pins gelegt.
void clock_serial | ( | ) |
Diese Funktion kümmert sich um eine HIGH-LOW-Taktflanke bei dem Clockeingang für die seriellen Daten des 74HC595.
Dabei wird der dafür benutzte Pin PB4 auf High und danach wieder auf Low gesetzt, indem das entsprechende Bit im PORTB gesetzt und wieder gelöscht wird. Dadurch wird erreicht, dass alle Zustände der internen Register einen Platz weiterrücken (shift) und im ersten Register der anliegende Zustand am seriellen Dateneingang des 74HC595 übernommen wird.
void cursor_a_down | ( | ) |
Diese Funktion setzt den Cursor A um einen Pixel nach unten.
Dabei wird zunächst überprüft, ob das untere Ende des Cursor A bereits erreicht ist. Falls dies der Fall ist, wird die Funktion vorzeitig verlassen. Falls nicht, wird übernimmt jedes Byte des Cursor das Byte+1, sodass alles nach unten gesetzt wird. Das oberste Byte wird in jedem Falls leer sein und mit Nullen gefüllt.
void cursor_a_up | ( | ) |
Diese Funktion setzt den Cursor A um einen Pixel nach oben.
Dabei wird zunächst überprüft, ob das obere Ende des Cursor A bereits erreicht ist. Falls dies der Fall ist, wird die Funktion vorzeitig verlassen. Falls nicht, wird übernimmt jedes Byte des Cursor das Byte-1, sodass alles nach oben gesetzt wird. Das unterste Byte wird in jedem Falls leer sein und mit Nullen gefüllt.
void cursor_b_down | ( | ) |
Diese Funktion setzt den Cursor B um einen Pixel nach unten.
Dabei wird zunächst überprüft, ob das untere Ende des Cursor B bereits erreicht ist. Falls dies der Fall ist, wird die Funktion vorzeitig verlassen. Falls nicht, wird übernimmt jedes Byte des Cursor das Byte+1, sodass alles nach unten gesetzt wird. Das oberste Byte wird in jedem Falls leer sein und mit Nullen gefüllt.
void cursor_b_up | ( | ) |
Diese Funktion setzt den Cursor B um einen Pixel nach oben.
Dabei wird zunächst überprüft, ob das obere Ende des Cursor B bereits erreicht ist. Falls dies der Fall ist, wird die Funktion vorzeitig verlassen. Falls nicht, wird übernimmt jedes Byte des Cursor das Byte-1, sodass alles nach oben gesetzt wird. Das unterste Byte wird in jedem Falls leer sein und mit Nullen gefüllt.
void game_failed | ( | ) |
Diese Funktion wird aufgerufen, falls ein Spieler den Ball durchgelassen hat.
Dabei wird zunächst ein unfreundlicher Smiley ausgegeben. Dann wird überprüft, welches Spieler einen Punkt bekommt und der Buchstabe des Spielers angezeigt, welcher den Ball durchgelassen hat. Nun wird der Punktestand ausgegeben. Falls ein Spieler neun Punkte erreicht hat, werden die Punkte zurückgesetzt und es wird ein Schriftzug mit einem Win für den Spieler ausgegeben, welcher gewonnen hat. Danach wird das Spiel wieder freigegeben.
void get_adcval | ( | uint16_t * | val | ) |
Diese Funktion liest einen ADC-Wert aus und schreibt diesen in den angegeben 16-Bit-Buffer.
Dabei wird zunächst mit adc_start() eine ADC-Konvertierung gestartet. Danach wird solange eine Schleife durchlaufen, bis das Bit ADSC im Register ADCSRA nicht mehr gesetzt ist. Ist dies der Fall, bedeutet es, dass die Konvertierung abgeschlossen ist. Danach wird der ausgelesene Wert in den angegebenen 16-Bit-Buffer geschrieben.
val | Dieser Parameter enthält die Adresse des 16-Bit-Buffer, in dem der ausgelesene ADC-Wert gespeichert werden soll |
uint8_t getkey | ( | ) |
Diese Funktion wartet auf eine Tasteneingabe.
Dabei wird der an dem Pin 2 angelegten Spannungswert über die get_adcval-Funktion ausgelesen. Handelt es sich um einen in Referenz zu 5V angelegten Wert von 50 oder weniger, ist die Taste 4 gedrückt worden. Zwischen 51 und 280 liegt die Taste 3, die Taste 2 hat einen Wertebereich von 281 bis 540. Die Taste 1 ist gedrückt worden, wenn der Wert 790 bis 1023 beträgt. Alles über diesen Werte ist keine angeschlossene Taste. Wurde nach zehn Auslese- und Verarbeitungsvorgängen keine gültige Taste gedrückt oder wurde eine Taste gedrückt, wird die Schleife beendet und die angegebene Taste zurückgegeben. Bei keiner gültigen Taste wird 0 zurückgegeben.
ISR | ( | TIMER0_OVF_vect | ) |
Diese Funktion bildet die anzuspringende Interrupt-Funktion bei einem Timer0-Overflow.
Während dieser Funktion werden verschiedene Aufgaben erledigt. Zum einem wird die Bildschirmausgabe aktualisiert, solange das Spiel am Laufen ist, zum anderen aktualisiert diese Funktion bei allen hundert Timer0-Overflows die Ballposition.
TIMER0_OVF_vect | Dieser Parameter gibt den Typ des Interrupt an, bei dem diese Funktion angesprungen werden soll |
int main | ( | ) |
Diese Funktion ist der Einsprungspunkt des Programmes und nimmt alle nötigen Initialisierungen vor.
Zunächst werden die Pins PB0 (KontrollLED), PB1 (serielle Daten), PB3 (Datenausgabe-Clock) und PB4 (serielle Daten-Clock) auf Ausgang gesetzt, damit man dessen Zustände setzen kann. Danach wird der ADC initialisiert, welcher zum Einlesen der Tasten benötigt wird. Als nächster wird der Timer initialisert, welcher regelmäßig einen Interrupt auslöst, welcher sich dann um die Ausgabe und die Spielzyklen kümmert. Mit der Funktion start_game() werden da die Spieldaten initialisiert und das Spiel gestartet. In der unendlichen Schleife wird für eine bestimmte kurze Zeitdauer abgefragt, ob eine Taste gedrückt wurde. Danach wird überprüft, ob der Ball das Spielfeld inzwischen verlassen hat, wenn nicht wird überprüft, welche Taste gedrückt wurde. Je nach Taste wird die dazu gehörige Funktion ausgeführt. Danach beginnt die Schleife wieder von vorne.
void matrix_output | ( | ) |
Diese Funktion rechnet den Ausgabe-Puffer in die reelle Ausgabe um.
Dabei wendet diese Funktion die Multiplexing-Methode an, bei der jeweils immer nur ein Pixel auf einmal dargestellt wird, keine Konflikte zwischen Zeilen und Spalten zu verursachen. Dies wird so schnell wiederholt, sodass das Endbild komplett aussieht. Dieser Vorgang wird direkt für beide Matrizen wiederholt. Zunächst wird für jede der fünf Zeilen, eine Zeile in den row-Buffer geladen. Diese Zeile wird nun auf gesetzte Bits untersucht. Wird eines an einer Stelle gefunden, wird der out-Buffer dementsprechend vorbereitet, dass dieses Bit ein darzustellenden Pixel darstellt. Wichtig ist dabei zu beachten, dass dieses gesetzte Bit, also das darzustellende Pixel auf den richtigen Pin der Shift-Register geschaltet werden muss, wodurch die komplizierten Shitfs entstehen, da der out-Buffer direkt an die Shift-Register weitergetaktet wird. Für den zweiten Bildschirm muss das Ganze zudem um 16 Bits/Pins verschoben sein, da diese 16 Pins weiter angeschlossen ist. Die gesamte Zeile wird danach in matrix_screen_pixel rausgetaktet. Danach wird das nächste Pixel überprüft.
void matrix_screen_pixel | ( | uint32_t | input | ) |
Diese Funktion taktet vorbereitete Zeilen mit einem gesetzten Pixel an die Shift-Register raus.
Dabei wird jedes der gestzten Bits von hinten nach vorne überprüft. Da die zu aktivierenden Zeilen Anoden und die zu aktivierenden Spalten Kathoden darstellen, müssen die zu aktivierenden Spalten invertiert behandelt werden, sodass ein gesetztes Bit, also die Aufforderung zur Aktivierung, eine Null taktet und somit einen Masse-Eingang im Shift-Register darstellt und die Spalte freigeschaltet wird. Nachdem am Ende alle Bits entweder logisch Eins oder logisch Null getaktet worden sind, werden die Ausgänge mit clock_output an den Shift-Registern gleichzeitig freigebenen und übernehmen die getakteten Werte. Am Ende werden alle Steuerleitungen wieder auf Null gelegt.
input | Dieser Parameter enthält die zu taktenen Bits |
void next_r_ball | ( | ) |
Diese Funktion berechnet die nächste Position des Spielballs.
Dabei wird zunächst überprüft, ob der Spielball bereits mit einem Cursor kollidiert und dieses Ergebnis gespeichert. Sollte dies der Fall sein und der Rückgabewert ist nicht Null, wird durch Prüfung, ob der Rückgabewert gerade ist (rechte Seite berührt) oder ungerade ist (linke Seite berührt) überprüft, in welcher Richtung der Ball umdrehen muss. Durch die Zufallsfunktion wird entschieden ob, der Ball gerade oder in einer Diagonalen zurückgespielt wird. Das Ergebnis wird in die neue Ballrichtung. Danach wird zur späteren Berechnung überprüft, in welcher Zeile sich der Ball derzeit befindet. Bei dem switch-Block folgt nun die Berechnung für die Position des Balls, welche durch ball_dir bestimmt ist. Das Ball-Array wird dementsprechen in die Richtung geshiftet. Sollte bei einem diagonalen Verlauf der obere oder untere Rand den Ball berühren, wird die nächste Ball-Richtung direkt die entgegengesetzte Diagonale und der Ball der Block vorzeitig verlassen. Nachdem der Ball neu berechnet wurde, wird am Ende überprüft, ob der Ball die Spiel-Aus-Grenzen überschritten hat und ein Spieler einen Punkt gewonnen hat. Sollte dies der Fall sein, wird game_failed_var gesetzt und sobald die Ausführung wieder in der main-Schleife ist das Spiel für verloren erklären.
void output | ( | uint8_t * | in, |
uint8_t | screen | ||
) |
Diese Funktion gibt den übergebenen 5 x 8 Bit großen Speicherbereich aus.
Dabei wird der 5 x 8 Bit großen Speicherbereich Byte für Byte in den screen_buffer_second kopiert. Der Screen-Buffer enthält die Ausgaben auf der Punktmatrix und wird mit jedem Timer-Interrupt ausgegeben.
in | Dieser Parameter ist der Pointer, der auf den 5 x 8 Bit großen Speicherbereich zeigt |
void output_game | ( | ) |
void output_text | ( | char * | text, |
uint8_t | func | ||
) |
Diese Funktion gibt einen Text auf den beiden Punktmatrizen aus.
Dabei wird zunächst in der while-Schleife die Buchstaben des Textes aus der Variable text in einen text_buffer im RAM gepuffert, um unnötige, ständige Zugriffe auf den Flash zu vermeiden. Ebenfalls wird gleichzeitig die Anzahl der Zeichen im Text gezählt. Mit dem Parameter SCROLLING läuft die Funktion weiter wie folgt ab: Da der Text gescrollt werden soll, muss für jeden Scroll-Schritt alles um einen Pixel verschoben werden. Da die beiden Matrizen zusammen 14 Pixel auf der X-Achse besitzen und die Buchstaben am Anfang außerhalb sind, wird um 14+(Breite_eines_Zeichens_in_Pixel*Anzahl_der_Zeichen) inkrementiert (erste for-Schleife). Nun muss jede der fünf Zeilen für den temp_buff_1 und temp_buff_2 berechnet werden (zweite for-Schleife). Da Direkt jedes sichtbare Zeichen in einem Buffer für jede Matrix erscheinen soll, wird die folgende Berechnung für jedes Zeichen durchgeführt (dritte for-Schleife). Hier wird nun erst überprüft, ob das Zeichen bereits minimal auf der Matrix zu sehen sein kann. Falls dies zutrifft, wird die Zeile um 5 Pixel nach rechts (Links-Shift, durch umgekehrte Reihenfolge der Ausgabe) verschoben, da das Zeichen am Rand erscheinen soll und nicht, wie im Zeichensatz angegeben in der Mitte. Danach folgt der Scrolling-Shift nach links (Rechts-Shift), welcher das bereits fortgeschrittene Scrolling-Shifting einfließen lässt und den Buchstaben an der korrekten Position erscheinen lässt. Sollte j bereits die ersten sieben Pixel gescrollt sein, kann minimal nun die zweite Matrix erreicht worden sein, und lässt alle dort ankommenden Pixel genauso verfahren, lediglich sieben Pixel müssen abgezogen werden, da die zweite Matrix eine neue Matrix darstellt, ohne Kenntnis von der ersten, die bereits sieben Pixel gescrollt hat. Sind alle Zeilen berechnet wird dies in den Ausgabe-Puffer der Matrizen geschrieben und danach die lokalen Puffer gelöscht zur nächsten Scrolling-Inkrementierung.
text | Dieser Parameter enthält den Pointer auf den auszugebenen Text. |
func | Dieser Parameter gibt an, ob der Text gescrollt und statisch erscheinen soll. (statisch funktioniert derzeit nicht) |
uint32_t rand | ( | ) |
Diese Funktion dient als Zufallszahlengenerator.
Dabei wird der unter bestimmten Regeln inkrementierende rand_timer vom ständig inkrementierenden ball_timer subtrahiert. Das Ergebnis wird zurückgegeben. Da rand_timer unter bestimmten Regeln und unter Einfluss der Tasten inkrementiert wird, entstehen beinahe zufällige Zahlenwerte.
void shift_high | ( | ) |
void shift_low | ( | ) |
void start_game | ( | ) |
Diese Funktion startet das Spiel und setzt alle entsprechenden Umgebungsvariablen.
Dabei wird zunächst das möglicherweise vorrausgegange game_failed_var wieder zurückgesetzt, da das Spiel wird freigegeben wird. Danach werdem die Cursor wieder in ihre Ursprungsposition zurückgeschrieben und die Ball-Position wieder zurückgesetzt. Zuletzt wird die Ball-Richtung wieder nach rechts-laufend gesetzt und das Spiel mit game_running = 1 wieder freigegeben. Um optische Verzögerungen beim Spielstart zu vermeiden wird das Startfeld direkt ausgegeben.
void timer_init | ( | ) |
In dieser Funktion wird der Timer initialisiert.
Dabei wird zunächst mit dem TOIE0-Bit im TIMSK-Register erreicht, dass bei einem Timer-Overflow Interrupts ausgelöst werden. Mit dem CS01-Bit im TCCR0B-Register wird der Prescaler auf 8 gesetzt, sodass nur bei jedem achten Taktzyklus der Timer inkrementiert. Mit dem Wert 0x0 im TCNT0-Register wird der Startwert des Timer0 gesetzt.