Bumbum
18.12.2013, 19:05
Hallo,
ich habe mir mal wieder ein neues Projekt einfallen lassen, bzw. über Google gefunden und wollte es nachbauen. Da ich nur das Prinzip übernehmen wollte und alles andere selbst entwickeln möchte halte ich den Link zu meiner Inspiration vorerst geheim. Mein Aufbau sieht folgendermassen aus:
26963 26964
Das Prinzip ist folgendes: 8 Spiegel rotieren auf einem Lüfter. Jeder Spiegel hat einen leicht anderen Kippwinkel. Auf die Spiegel lasse ich einen Laser strahlen. Durch die 8 verschiedenen Winkel habe ich somit 8 verschiedene Abstrahlwinkel in der Höhe. Durch die Rotation erhalte ich damit 8 horizontale, parallele Linien an der Wand. (Das klappt auch einwandfrei)
Durch "pulsen" des Lasers möchte ich dann "Pixel" auf diesen Linien erstellen. Geplant ist eine Auflöung von 128 Pixel in der X-Achse und 8 Pixel in der Y-Achse (durch die Anzahl der Spiegel vorgegeben)
Die Lüfterdrehzahl ohne "Last" (Gewicht der Spiegel) ist laut Datenblatt 2200 Umdrehungen pro Minute bzw. ca. 37 in der Sekunde. Das bedeutet ca. 27 Millisekunden pro Umdrehung. Geteilt durch 8 Zeilen und 128 Pixel ergibt dass einen Pixel-Clock von ca. 38kHz wenn ich mich nicht verrechnet habe. Das sollte mit einem Atmel machbar sein. Entschieden habe ich mich für einen ATmega8 mit einem 16MHz Quarz. Das heißt pro Pixel habe ich ca. 420 Clocks. Das sollte genügen oder?
Da ich nur einen Lüfter ohne Tacho-Signal da hatte habe ich noch eine Lichtschranke für die Synchronisierung angebracht. Weiterhin ist noch eine Status-LED und ein Taster auf der Platine.
Hier ist der Quellcode bis jetzt:
#define AVRGCC
#define LCD
#include <avr/io.h>
#include <compiler.h>
#include <util/delay.h>
#ifdef LCD
#include <ISP_LCD.h>
#endif
#include <font.h>
/**
LaserText:
8 rotierende Spiegel, die einen Laserstrahl auf 8 verschiedene Winkel in der Y-Achse ablenken.
X-Achse wird über ein/ausschalt-Timing des Lasers gelöst.
Buchstabe: 5x8 Pixel
Abstand: 1 Pixel
Buchstaben: 16
-->Breite: (5 + 1) * 16 = 96 Pixel
--> Fläche: 128x8 Pixel
ATmega8 @16 MHz
IOs:
PB0 OUT Motor ein (kann auch PWM-Signal sein)
PB1 OUT Laser ein
PB2 frei
PB3 MOSI
PB4 MISO
PB5 SCK
PC0 frei
PC1 frei
PC2 frei
PC3 frei
PC4 frei
PC5 frei
PD0 RXD
PD1 TXD
PD2 INT0 Tacho
PD3 IN Taste
PD4 frei
PD5 frei
PD6 OUT LED
PD7 OUT Lichtschranke ein
Timer 0: (zur Ermittlung der Rotiergeschwindigkeit)
FCPU = 16000000
Prescaler = 64 (8x wie andere Timer, damit das Teilen durch 8 Zeilen gleich entfällt)
Timer-Frequenz = 250 KHz
normaler Modus; im Overflow-Interrupt wird ein 8 Bit-Counter gezählt um die Reichweite auf 16 Bit zu erhöhen
Timer 1: (zum timen der Zeilen)
Prescaler = 8
Timer-Frequenz = 2 MHz
CTC-Modus um mit OCR1A im Interrupt das den Zeilen-Start zu generieren
Timer 2: (zum timen der Pixel)
Prescaler = 8
Timer-Frequenz = 2 MHz
CTC-Modus um mit OCR2 im Intterrupt den Pixel-Clock zu generieren
erwartete Werte:
Lüfterdrehzahl (ca.): 2200 U/min
36,6 U/sek
Umdrehungsdauer: 27,3 ms
54545 Ticks (Prescaler 8; Timer 1 und 2)
Segmentdauer: 6818 Ticks (Timer 0)
Pixeldauer: 53 Ticks (Prescaler 8; Timer 1 und 2)
tatsächliche Werte:
Segmentdauer (ca.): 9500 Ticks (Timer 0)
Pixeldauer: 74 Ticks (Prescaler 8; Timer 1 und 2)
Umdrehungsdauer: 76000 Ticks (Prescaler 8; Timer 1 und 2)
38 ms
Lüfterdrehzahl: 26,3 U/sek
1579 U/min (kommt vermutlich durch das Gewicht der Spiegel und deren Halterung)
**/
#define BAUD 9600l // Baud-Rate für USART
#define UBRRValue (F_CPU / (BAUD * 16) - 1)
#define USART_BufferSize 32 // Größe des USART-Input-Buffers
#define lines 8 // 8 Spiegel für Y-Auflösung
#define cols 96 // 96 nutzbare Pixel in X-Achse (bei 6 Pixel pro Buchstabe 16 Buchstaben)
#define colsBorder 32 // nicht nutzbarer Rand um Versatz zu korrigieren und auf 2^7 Gesamtpixel zu kommen
#define colStart 16 // Erster Pixel mit Nutz-Daten
#define colDivider 7 // Teiler für den Pixel-Clock (2^7 = 128 = 96 + 32)
#define colBytes 16 // Benötigte Bytes pro Zeile (128 Bit : 8 Bit = 16 Byte)
#define Motor_aus PORTB = ~(~PORTB | (1<<PB0)) // Motor-Kontrolle
#define Motor_ein PORTB = (PORTB | (1<<PB0))
#define Motor_Status ((PORTB & (1<<PB0)) != 0) // Motor-Status
#define Laser_aus PORTB = ~(~PORTB | (1<<PB1)) // Laser-Kontrolle
#define Laser_ein PORTB = (PORTB | (1<<PB1))
#define LED_aus PORTD = ~(~PORTD | (1<<PD6)) // Status-Led-Kontrolle
#define LED_ein PORTD = (PORTD | (1<<PD6))
#define LS_aus PORTD = ~(~PORTD | (1<<PD7)) // Lichtschranken-Kontrolle
#define LS_ein PORTD = (PORTD | (1<<PD7))
#define Tacho ((PIND & (1<<PD2)) == 0) // Lichtschranken-Eingang-Status
#define Taste ((PIND & (1<<PD3)) == 0) // Tasten-Status
#define USART_ready ((UCSRA & (1<<UDRE)) != 0) // alle Zeichen im USART versendet?
#define PixelIRQ_aus TIMSK = (1<<TOIE0) | (1<<OCIE1A) // Timer-Interrupts konfigurieren
#define PixelIRQ_ein TIMSK = (1<<TOIE0) | (1<<OCIE1A) | (1<<OCIE2)
volatile U8 TachoCounter = 0; // Overflow-Variable für Timer 0 um auf 16 Bit Datengröße zu kommen
volatile U16 lineTime = 0; // berechnete Ticks in Timer 1 pro Zeile --> Wert für OCR1A
volatile U8 pixelTime = 0; // berechnete Ticks in Timer 2 pro Pixel --> Wert für OCR2
volatile U8 USART_Pointer = 0; // Pointer im USART-Buffer
volatile char USART_data[USART_BufferSize]; // USART-Buffer
volatile U8 Pixel[lines][colBytes]; // Pixel-Daten
volatile U8 CursorY; // Zeilen-Cursor
volatile U8 CursorXoffset; // Daten-Byte-Zähler für Pixel
volatile U8 CursorXbit; // Bit-Zähler im Daten-Byte für Pixel
volatile U8 CursorXdataTmp; // aktuelles Daten-Byte für Pixel
volatile bool lineFinished = FALSE; // Flag, ob alle Pixel der Zeile angezeigt wurden
volatile const U16 lineOffset[lines] = {0, 0, 0, 0, 0, 0, 0, 0}; // zum kalibrieren des Versatzes
void sendMsg (char *Msg, bool addCR) // sendet eine Nachricht über den USART
{
while (*Msg != 0)
{
while (!USART_ready);
UDR = *Msg++;
}
if (addCR)
sendMsg ("\r\n", FALSE);
}
void Pixel_clearScreen (void) // löscht alle Pixel in den Pixel-Daten
{
U8 i1;
U8 i2;
for (i1 = 0; i1 < lines; i1++)
for (i2 = 0; i2 < colBytes; i2++)
Pixel[i1][i2] = 0;
}
void Pixel_set (U8 x, U8 y, bool value) // setzt oder löscht einen Pixel (je nach value)
{
x = (cols + colsBorder) - (colStart + x); // Pixel-Offset addieren und von rechts nach links umrechnen
U8 offset = x>>3; // Offset-Byte in Pixel-Daten berechnen (:8)
x = x - (offset<<3); // Start-Wert des Offset-Byte abziehen
if (value)
Pixel[y][offset] |= (1<<x);
else
Pixel[y][offset] = ~(~Pixel[y][offset] | (1<<x));
}
void Pixel_lineX (U8 x, U8 y, U8 length, bool value) // zeichnet oder löscht eine Linie in X-Richtung (je nach value)
{
U8 i1;
for (i1 = 0; i1 < length; i1++)
Pixel_set (x + i1, y, value);
}
void Pixel_lineY (U8 x, U8 y, U8 length, bool value) // zeichent oder löscht eine Linie in Y-Richtung (je nach value)
{
U8 i1;
for (i1 = 0; i1 < length; i1++)
Pixel_set (x, y + i1, value);
}
void Char (U8 x, U8 y, U8 ASCII) // zeichnet einen Buchstaben
{
ASCII = ASCII - 16; // Font ASCII-Offset abziehen
U8 i1;
for (i1 = 0; i1 < 5; i1++) // Buchstabe wird Spaltenweise gezeichnet
{
U8 tmp = Font[ASCII][i1]; // Spalten-Daten aus Font holen
U8 i2;
for (i2 = 0; i2 < 8; i2++)
{
Pixel_set (x + i1, y + i2, ((tmp & 1) != 0)); // Pixel setzen oder löschen, wenn entsprechendes Bit gesetzt oder gelöscht
tmp = tmp>>1; // Spalten-Daten weiter schieben
}
}
}
void Text (U8 x, U8 y, char *textdata) // zeichnet einen Text
{
while (*textdata != 0)
{
Char (x, y, *textdata++);
x = x + 6;
}
}
SIGNAL (INT0_vect) // 1 Umdrehung startet (und letzte Umdrehung abgeschlossen):
{
U16 tmp = TachoCounter; // Gesamt-Wert für letzte Umdrehung in tmp berechnen
tmp = (tmp<<8) + TCNT0;
TCNT0 = 0; // Timer für Umdrehungs-Dauer reset
TachoCounter = 0;
TCNT1 = 0; // Timer für Zeilen Reset
OCR1A = 0x8000;
PixelIRQ_aus; // Falls noch alte Umdrehung läuft --> Deaktivieren
Laser_aus;
if (tmp < 0xFF00) // Umdrehung schnell genug?
{
lineTime = tmp; // Zeilen-Zeit speichern
pixelTime = tmp>>colDivider; // Pixel-Zeit berechnen und speichern
lineFinished = TRUE; // Flag beim Start initialisieren
CursorY = 0; // Zeilen-Cursor Reset
TCNT1 = 0; // Zeilen-Timer Reset
OCR1A = (lineTime>>1) + lineOffset[0]; // und Start-Offset des ersten Segment auf halbe Zeilenzeit + Offset
OCR2 = pixelTime; // Pixel-Clock initialisieren
}
else
CursorY = 0xFF; // ungültige Zeile, damit keine gestartet wird
}
SIGNAL (TIMER0_OVF_vect) // Timer für ermittlung der Umdrehungs-Dauer (Overflow)
{
if (TachoCounter < 0xFF) // wenn noch nicht auf Maximum, dann Overflow-Wert erhöhen
TachoCounter++;
else // ansonsten ist die Drehzahl zu langsam --> Laser ausschalten
{
CursorY = 0xFF;
OCR1A = 0x8000;
PixelIRQ_aus;
Laser_aus;
}
}
SIGNAL (TIMER1_COMPA_vect) // Zeilen-Start:
{
if (lineFinished & (CursorY < lines)) // Zeilen-Cursor gültig?
{
OCR1A = lineTime + lineOffset[CursorY]; // OCR1A setzen
// Zeilen-Start:
CursorXoffset = 0; // Offset-Byte auf 0
CursorXbit = 0; // Bit auf 0
CursorXdataTmp = Pixel[CursorY][0]; // Erstes Datenbyte in Temp laden
TCNT2 = 0; // Pixel-Timer Reset,
PixelIRQ_ein; // und starten
lineFinished = FALSE; // Flag löschen
}
else
{
CursorY = 0xFF;
OCR1A = 0x8000;
PixelIRQ_aus;
Laser_aus;
}
}
SIGNAL (TIMER2_COMP_vect) // Timer-Clock:
{
// Test-Code:
if (CursorXbit == 0) // Erster Pixel?
{
Laser_ein; // dann Laser ein
CursorXbit++;
}
else
{ // zweiter Pixel:
PixelIRQ_aus;
Laser_aus;
lineFinished = TRUE;
CursorY++;
}
/** normaler Code:
if ((CursorXdataTmp & 1) == 0) // Bit prüfen, Laser ein oder aus?
Laser_aus;
else
Laser_ein;
if (++CursorXbit == 8) // letzte Bit in Temp-Byte?
{
if (++CursorXoffset == colBytes) // und letztes Temp-Byte?
{
PixelIRQ_aus; // dann Pixel-Timer aus
lineFinished = TRUE; // Flag setzen
CursorY++; // und nächste Zeile...
}
else
{
CursorXdataTmp = Pixel[CursorY][CursorXoffset]; // ansonsten nächstes Temp-Byte laden
CursorXbit = 0; // und Bit wieder auf 0
}
}
else
CursorXdataTmp = CursorXdataTmp>>1; // ansonsten Bits im Temp-Register weiter schieben...
**/
}
SIGNAL (USART_RXC_vect) // USART-Datenempfang:
{
char value = UDR; // USART-Datenregister auf jeden Fall einlesen
if (USART_Pointer < USART_BufferSize); // wenn Buffer noch nicht voll, dann Zeichen anhängen
USART_data[USART_Pointer++] = value;
}
int main(void)
{
DDRB = 0b00000011; // Ports initialiseren:
PORTB = 0b00000000;
DDRC = 0b00000000;
PORTC = 0b01000000;
DDRD = 0b11000000;
PORTD = 0b00001100;
LED_ein; // Einschalt-Reset LED anzeige:
_delay_ms (1000);
#ifdef LCD
LCD_Init (&PORTB, &DDRB, PB3, PB4, PB5); // wenn LCD, dann initialisieren und Begrüßung anzeigen:
LCD_Clear ();
LCD_Text ("LaserText");
#endif
MCUCR = (1<<ISC01); // INT0 konfigurieren: fallende Flanke
GICR = (1<<INT0); // INT0 aktivieren
TCCR0 = (1<<CS01) | (1<<CS00); // Timer 0 konfigurieren: Prescaler 64
TCCR1A = 0; // Timer 1 konfigurieren: normal port operation und CTC-Mode
TCCR1B = (1<<WGM12) |(1<<CS11); // CTC-Mode und Prescaler 8
OCR1A = 0x8000; // OCR1A auf Mittelwert
TCCR2 = (1<<WGM21) | (1<<CS21); // Timer 2 konfigurieren: normal port operation und CTC-Mode und Prescaler 8
OCR2 = 0x80; // OCR2 auf Mittelwert
PixelIRQ_aus; // Timer 0 Overflow und Timer 1 OCIE1 Interrupt aktivieren
UCSRB = (1<<RXCIE) | (1<<TXEN) | (1<<RXEN); // USART konfigurieren: Sender und Empfänger ein und Receive-Interrupt ein
UCSRC = (1<<UCSZ1) | (1<<UCSZ0); // 8 Bit, 1 Stop-Bit, kein Parity
UBRRH = (U8)(UBRRValue>>8); // BAUD-Rate setzen
UBRRL = (U8)UBRRValue;
Pixel_clearScreen (); // alle Pixel löschen
// Test-Ausgaben:
//Pixel_lineX (0, 0, 96, TRUE);
//Pixel_lineX (0, 1, 96, TRUE);
//Pixel_set (0, 0, TRUE);
//Text (0, 0, "Hallo");
sei (); // los gehts, Interrupts freigeben
LED_aus; // Einschalt-Reset-LED aus
while (1)
{
#ifdef LCD // wenn LCD, dann Zeitwerte anzeigen:
LCD_Cursor (1, 2);
LCD_Text ("L ");
LCD_Zahl (lineTime, 5, FALSE);
LCD_Text ("/ P ");
LCD_Zahl (pixelTime, 3, FALSE);
LCD_Text ("/ Y ");
if (CursorY <lines)
LCD_Zahl (CursorY, 1, FALSE);
else
LCD_Zeichen ('-');
#endif
/**
#ifdef LCD // wenn LCD, dann USART anzeigen:
LCD_Cursor (1, 3);
LCD_Zahl (USART_Pointer, 2, FALSE);
if (USART_Pointer > 0)
{
LCD_Cursor (1, 4);
U8 i1;
for (i1 = 0; i1 < USART_Pointer; i1++)
LCD_Zeichen (USART_data[i1]);
}
#endif
**/
if (Taste) // Taste gedrückt?
{
if (Motor_Status)
{ // wenn Motor eingeschaltet, dann abschalten:
PixelIRQ_aus;
Motor_aus;
LS_aus;
Laser_aus;
OCR1A = 0x8000;
OCR2 = 0x80;
lineTime = 0;
pixelTime = 0;
CursorY = 0xFF;
}
else
{ // ansonsten einschalten:
Motor_ein;
LS_ein;
}
while (Taste) // warten, bis Taste losgelassen wurde:
_delay_ms (10);
}
}
return(0);
}
Und hier das Ergebnis als Video: http://www.car-mp3.de/RNetz/Lasertext.avi
Wie im Video zu sehen ist klappt das gar nicht. Die Berechnungen der Zeiten im LCD passen. Nur irgendwo scheine ich einen Fehler bei der Konfiguration der Timer zu haben. Aber ich bin schon zwei Tage am Suchen und finde ihn nicht.
Ich weiß, dass dieses Projekt sehr umfangreich ist. Aber vielleicht hat ja jemand Lust bekommen und denkt sich durch und findet meinen Fehler.
Viele Grüße
Andreas
- - - Aktualisiert - - -
Hier noch der Schaltplan zur Vollständigkeit. Ist nichts besonderes. Manche Bauteile sind etwas überdimensioniert. Das liegt daran, das ich sie rumliegen hatte.
- die Schaltung wird über ein 12V Netzteil versorgt.
- davon wird über einen IRF5305 der Lüfter geschaltet (125mA).
- ebenfalls an 12V hängt die Lichtschranke über einen BC557 geschaltet (10mA). Danach noch ein Pegel-Wandler für den Atmel-Eingang.
- der Standard 7805 für die 5V für den Atmel
- eine USART-Schnittstelle und eine ISP-Schnittstelle, über die auch das LCD angeschlossen werden kann
- dann ein LM317 für die Erzeugung der Spannung des Lasers (4V). Der Laser wird dann über einen BD240 getaktet. (später 300mA, derzeit 30mA). Den IRF5305 konnte ich dafür nicht nehmen, da die Spannung zu niedrig war um das Gate durchzusteuern.
- Quarz, Taster und LED
- was noch fehlt ist der Pegelwandler für den USART um die Schaltung später mit Daten zu füttern. Was das wird habe ich noch nicht entschieden. Vermutlich ein USB-RS232-Wandler. (oder Luxus: ein Bluetooth- oder WLAN-Modul)
26967
Insgesamt musste ich für das Projekt nur die Spiegel für ca. 5€ kaufen. Alles andere war in der Bastelkiste. Die Halterung für die Spiegel hat mir ein Kumpel ausgelasert und gekanntet. Ist praktisch, wenn man auf sowas Zugriff hat. ;-)
Wenn alles läuft möchte ich die Spiegelhalterung noch 3D-Drucken lassen um Gewicht zu sparen. (höhere Refresh-Rate) Obwohl die Linien zur Zeit auch flakerfrei dargestellt werden. Das Flackern im Video kommt von der Kamera und vom falschen Timing.
Außerdem soll der Laser durch einen stärkeren ersetzt werden, damit man auch am Tag gut was erkennen kann.
Viele Grüße
Andreas
ich habe mir mal wieder ein neues Projekt einfallen lassen, bzw. über Google gefunden und wollte es nachbauen. Da ich nur das Prinzip übernehmen wollte und alles andere selbst entwickeln möchte halte ich den Link zu meiner Inspiration vorerst geheim. Mein Aufbau sieht folgendermassen aus:
26963 26964
Das Prinzip ist folgendes: 8 Spiegel rotieren auf einem Lüfter. Jeder Spiegel hat einen leicht anderen Kippwinkel. Auf die Spiegel lasse ich einen Laser strahlen. Durch die 8 verschiedenen Winkel habe ich somit 8 verschiedene Abstrahlwinkel in der Höhe. Durch die Rotation erhalte ich damit 8 horizontale, parallele Linien an der Wand. (Das klappt auch einwandfrei)
Durch "pulsen" des Lasers möchte ich dann "Pixel" auf diesen Linien erstellen. Geplant ist eine Auflöung von 128 Pixel in der X-Achse und 8 Pixel in der Y-Achse (durch die Anzahl der Spiegel vorgegeben)
Die Lüfterdrehzahl ohne "Last" (Gewicht der Spiegel) ist laut Datenblatt 2200 Umdrehungen pro Minute bzw. ca. 37 in der Sekunde. Das bedeutet ca. 27 Millisekunden pro Umdrehung. Geteilt durch 8 Zeilen und 128 Pixel ergibt dass einen Pixel-Clock von ca. 38kHz wenn ich mich nicht verrechnet habe. Das sollte mit einem Atmel machbar sein. Entschieden habe ich mich für einen ATmega8 mit einem 16MHz Quarz. Das heißt pro Pixel habe ich ca. 420 Clocks. Das sollte genügen oder?
Da ich nur einen Lüfter ohne Tacho-Signal da hatte habe ich noch eine Lichtschranke für die Synchronisierung angebracht. Weiterhin ist noch eine Status-LED und ein Taster auf der Platine.
Hier ist der Quellcode bis jetzt:
#define AVRGCC
#define LCD
#include <avr/io.h>
#include <compiler.h>
#include <util/delay.h>
#ifdef LCD
#include <ISP_LCD.h>
#endif
#include <font.h>
/**
LaserText:
8 rotierende Spiegel, die einen Laserstrahl auf 8 verschiedene Winkel in der Y-Achse ablenken.
X-Achse wird über ein/ausschalt-Timing des Lasers gelöst.
Buchstabe: 5x8 Pixel
Abstand: 1 Pixel
Buchstaben: 16
-->Breite: (5 + 1) * 16 = 96 Pixel
--> Fläche: 128x8 Pixel
ATmega8 @16 MHz
IOs:
PB0 OUT Motor ein (kann auch PWM-Signal sein)
PB1 OUT Laser ein
PB2 frei
PB3 MOSI
PB4 MISO
PB5 SCK
PC0 frei
PC1 frei
PC2 frei
PC3 frei
PC4 frei
PC5 frei
PD0 RXD
PD1 TXD
PD2 INT0 Tacho
PD3 IN Taste
PD4 frei
PD5 frei
PD6 OUT LED
PD7 OUT Lichtschranke ein
Timer 0: (zur Ermittlung der Rotiergeschwindigkeit)
FCPU = 16000000
Prescaler = 64 (8x wie andere Timer, damit das Teilen durch 8 Zeilen gleich entfällt)
Timer-Frequenz = 250 KHz
normaler Modus; im Overflow-Interrupt wird ein 8 Bit-Counter gezählt um die Reichweite auf 16 Bit zu erhöhen
Timer 1: (zum timen der Zeilen)
Prescaler = 8
Timer-Frequenz = 2 MHz
CTC-Modus um mit OCR1A im Interrupt das den Zeilen-Start zu generieren
Timer 2: (zum timen der Pixel)
Prescaler = 8
Timer-Frequenz = 2 MHz
CTC-Modus um mit OCR2 im Intterrupt den Pixel-Clock zu generieren
erwartete Werte:
Lüfterdrehzahl (ca.): 2200 U/min
36,6 U/sek
Umdrehungsdauer: 27,3 ms
54545 Ticks (Prescaler 8; Timer 1 und 2)
Segmentdauer: 6818 Ticks (Timer 0)
Pixeldauer: 53 Ticks (Prescaler 8; Timer 1 und 2)
tatsächliche Werte:
Segmentdauer (ca.): 9500 Ticks (Timer 0)
Pixeldauer: 74 Ticks (Prescaler 8; Timer 1 und 2)
Umdrehungsdauer: 76000 Ticks (Prescaler 8; Timer 1 und 2)
38 ms
Lüfterdrehzahl: 26,3 U/sek
1579 U/min (kommt vermutlich durch das Gewicht der Spiegel und deren Halterung)
**/
#define BAUD 9600l // Baud-Rate für USART
#define UBRRValue (F_CPU / (BAUD * 16) - 1)
#define USART_BufferSize 32 // Größe des USART-Input-Buffers
#define lines 8 // 8 Spiegel für Y-Auflösung
#define cols 96 // 96 nutzbare Pixel in X-Achse (bei 6 Pixel pro Buchstabe 16 Buchstaben)
#define colsBorder 32 // nicht nutzbarer Rand um Versatz zu korrigieren und auf 2^7 Gesamtpixel zu kommen
#define colStart 16 // Erster Pixel mit Nutz-Daten
#define colDivider 7 // Teiler für den Pixel-Clock (2^7 = 128 = 96 + 32)
#define colBytes 16 // Benötigte Bytes pro Zeile (128 Bit : 8 Bit = 16 Byte)
#define Motor_aus PORTB = ~(~PORTB | (1<<PB0)) // Motor-Kontrolle
#define Motor_ein PORTB = (PORTB | (1<<PB0))
#define Motor_Status ((PORTB & (1<<PB0)) != 0) // Motor-Status
#define Laser_aus PORTB = ~(~PORTB | (1<<PB1)) // Laser-Kontrolle
#define Laser_ein PORTB = (PORTB | (1<<PB1))
#define LED_aus PORTD = ~(~PORTD | (1<<PD6)) // Status-Led-Kontrolle
#define LED_ein PORTD = (PORTD | (1<<PD6))
#define LS_aus PORTD = ~(~PORTD | (1<<PD7)) // Lichtschranken-Kontrolle
#define LS_ein PORTD = (PORTD | (1<<PD7))
#define Tacho ((PIND & (1<<PD2)) == 0) // Lichtschranken-Eingang-Status
#define Taste ((PIND & (1<<PD3)) == 0) // Tasten-Status
#define USART_ready ((UCSRA & (1<<UDRE)) != 0) // alle Zeichen im USART versendet?
#define PixelIRQ_aus TIMSK = (1<<TOIE0) | (1<<OCIE1A) // Timer-Interrupts konfigurieren
#define PixelIRQ_ein TIMSK = (1<<TOIE0) | (1<<OCIE1A) | (1<<OCIE2)
volatile U8 TachoCounter = 0; // Overflow-Variable für Timer 0 um auf 16 Bit Datengröße zu kommen
volatile U16 lineTime = 0; // berechnete Ticks in Timer 1 pro Zeile --> Wert für OCR1A
volatile U8 pixelTime = 0; // berechnete Ticks in Timer 2 pro Pixel --> Wert für OCR2
volatile U8 USART_Pointer = 0; // Pointer im USART-Buffer
volatile char USART_data[USART_BufferSize]; // USART-Buffer
volatile U8 Pixel[lines][colBytes]; // Pixel-Daten
volatile U8 CursorY; // Zeilen-Cursor
volatile U8 CursorXoffset; // Daten-Byte-Zähler für Pixel
volatile U8 CursorXbit; // Bit-Zähler im Daten-Byte für Pixel
volatile U8 CursorXdataTmp; // aktuelles Daten-Byte für Pixel
volatile bool lineFinished = FALSE; // Flag, ob alle Pixel der Zeile angezeigt wurden
volatile const U16 lineOffset[lines] = {0, 0, 0, 0, 0, 0, 0, 0}; // zum kalibrieren des Versatzes
void sendMsg (char *Msg, bool addCR) // sendet eine Nachricht über den USART
{
while (*Msg != 0)
{
while (!USART_ready);
UDR = *Msg++;
}
if (addCR)
sendMsg ("\r\n", FALSE);
}
void Pixel_clearScreen (void) // löscht alle Pixel in den Pixel-Daten
{
U8 i1;
U8 i2;
for (i1 = 0; i1 < lines; i1++)
for (i2 = 0; i2 < colBytes; i2++)
Pixel[i1][i2] = 0;
}
void Pixel_set (U8 x, U8 y, bool value) // setzt oder löscht einen Pixel (je nach value)
{
x = (cols + colsBorder) - (colStart + x); // Pixel-Offset addieren und von rechts nach links umrechnen
U8 offset = x>>3; // Offset-Byte in Pixel-Daten berechnen (:8)
x = x - (offset<<3); // Start-Wert des Offset-Byte abziehen
if (value)
Pixel[y][offset] |= (1<<x);
else
Pixel[y][offset] = ~(~Pixel[y][offset] | (1<<x));
}
void Pixel_lineX (U8 x, U8 y, U8 length, bool value) // zeichnet oder löscht eine Linie in X-Richtung (je nach value)
{
U8 i1;
for (i1 = 0; i1 < length; i1++)
Pixel_set (x + i1, y, value);
}
void Pixel_lineY (U8 x, U8 y, U8 length, bool value) // zeichent oder löscht eine Linie in Y-Richtung (je nach value)
{
U8 i1;
for (i1 = 0; i1 < length; i1++)
Pixel_set (x, y + i1, value);
}
void Char (U8 x, U8 y, U8 ASCII) // zeichnet einen Buchstaben
{
ASCII = ASCII - 16; // Font ASCII-Offset abziehen
U8 i1;
for (i1 = 0; i1 < 5; i1++) // Buchstabe wird Spaltenweise gezeichnet
{
U8 tmp = Font[ASCII][i1]; // Spalten-Daten aus Font holen
U8 i2;
for (i2 = 0; i2 < 8; i2++)
{
Pixel_set (x + i1, y + i2, ((tmp & 1) != 0)); // Pixel setzen oder löschen, wenn entsprechendes Bit gesetzt oder gelöscht
tmp = tmp>>1; // Spalten-Daten weiter schieben
}
}
}
void Text (U8 x, U8 y, char *textdata) // zeichnet einen Text
{
while (*textdata != 0)
{
Char (x, y, *textdata++);
x = x + 6;
}
}
SIGNAL (INT0_vect) // 1 Umdrehung startet (und letzte Umdrehung abgeschlossen):
{
U16 tmp = TachoCounter; // Gesamt-Wert für letzte Umdrehung in tmp berechnen
tmp = (tmp<<8) + TCNT0;
TCNT0 = 0; // Timer für Umdrehungs-Dauer reset
TachoCounter = 0;
TCNT1 = 0; // Timer für Zeilen Reset
OCR1A = 0x8000;
PixelIRQ_aus; // Falls noch alte Umdrehung läuft --> Deaktivieren
Laser_aus;
if (tmp < 0xFF00) // Umdrehung schnell genug?
{
lineTime = tmp; // Zeilen-Zeit speichern
pixelTime = tmp>>colDivider; // Pixel-Zeit berechnen und speichern
lineFinished = TRUE; // Flag beim Start initialisieren
CursorY = 0; // Zeilen-Cursor Reset
TCNT1 = 0; // Zeilen-Timer Reset
OCR1A = (lineTime>>1) + lineOffset[0]; // und Start-Offset des ersten Segment auf halbe Zeilenzeit + Offset
OCR2 = pixelTime; // Pixel-Clock initialisieren
}
else
CursorY = 0xFF; // ungültige Zeile, damit keine gestartet wird
}
SIGNAL (TIMER0_OVF_vect) // Timer für ermittlung der Umdrehungs-Dauer (Overflow)
{
if (TachoCounter < 0xFF) // wenn noch nicht auf Maximum, dann Overflow-Wert erhöhen
TachoCounter++;
else // ansonsten ist die Drehzahl zu langsam --> Laser ausschalten
{
CursorY = 0xFF;
OCR1A = 0x8000;
PixelIRQ_aus;
Laser_aus;
}
}
SIGNAL (TIMER1_COMPA_vect) // Zeilen-Start:
{
if (lineFinished & (CursorY < lines)) // Zeilen-Cursor gültig?
{
OCR1A = lineTime + lineOffset[CursorY]; // OCR1A setzen
// Zeilen-Start:
CursorXoffset = 0; // Offset-Byte auf 0
CursorXbit = 0; // Bit auf 0
CursorXdataTmp = Pixel[CursorY][0]; // Erstes Datenbyte in Temp laden
TCNT2 = 0; // Pixel-Timer Reset,
PixelIRQ_ein; // und starten
lineFinished = FALSE; // Flag löschen
}
else
{
CursorY = 0xFF;
OCR1A = 0x8000;
PixelIRQ_aus;
Laser_aus;
}
}
SIGNAL (TIMER2_COMP_vect) // Timer-Clock:
{
// Test-Code:
if (CursorXbit == 0) // Erster Pixel?
{
Laser_ein; // dann Laser ein
CursorXbit++;
}
else
{ // zweiter Pixel:
PixelIRQ_aus;
Laser_aus;
lineFinished = TRUE;
CursorY++;
}
/** normaler Code:
if ((CursorXdataTmp & 1) == 0) // Bit prüfen, Laser ein oder aus?
Laser_aus;
else
Laser_ein;
if (++CursorXbit == 8) // letzte Bit in Temp-Byte?
{
if (++CursorXoffset == colBytes) // und letztes Temp-Byte?
{
PixelIRQ_aus; // dann Pixel-Timer aus
lineFinished = TRUE; // Flag setzen
CursorY++; // und nächste Zeile...
}
else
{
CursorXdataTmp = Pixel[CursorY][CursorXoffset]; // ansonsten nächstes Temp-Byte laden
CursorXbit = 0; // und Bit wieder auf 0
}
}
else
CursorXdataTmp = CursorXdataTmp>>1; // ansonsten Bits im Temp-Register weiter schieben...
**/
}
SIGNAL (USART_RXC_vect) // USART-Datenempfang:
{
char value = UDR; // USART-Datenregister auf jeden Fall einlesen
if (USART_Pointer < USART_BufferSize); // wenn Buffer noch nicht voll, dann Zeichen anhängen
USART_data[USART_Pointer++] = value;
}
int main(void)
{
DDRB = 0b00000011; // Ports initialiseren:
PORTB = 0b00000000;
DDRC = 0b00000000;
PORTC = 0b01000000;
DDRD = 0b11000000;
PORTD = 0b00001100;
LED_ein; // Einschalt-Reset LED anzeige:
_delay_ms (1000);
#ifdef LCD
LCD_Init (&PORTB, &DDRB, PB3, PB4, PB5); // wenn LCD, dann initialisieren und Begrüßung anzeigen:
LCD_Clear ();
LCD_Text ("LaserText");
#endif
MCUCR = (1<<ISC01); // INT0 konfigurieren: fallende Flanke
GICR = (1<<INT0); // INT0 aktivieren
TCCR0 = (1<<CS01) | (1<<CS00); // Timer 0 konfigurieren: Prescaler 64
TCCR1A = 0; // Timer 1 konfigurieren: normal port operation und CTC-Mode
TCCR1B = (1<<WGM12) |(1<<CS11); // CTC-Mode und Prescaler 8
OCR1A = 0x8000; // OCR1A auf Mittelwert
TCCR2 = (1<<WGM21) | (1<<CS21); // Timer 2 konfigurieren: normal port operation und CTC-Mode und Prescaler 8
OCR2 = 0x80; // OCR2 auf Mittelwert
PixelIRQ_aus; // Timer 0 Overflow und Timer 1 OCIE1 Interrupt aktivieren
UCSRB = (1<<RXCIE) | (1<<TXEN) | (1<<RXEN); // USART konfigurieren: Sender und Empfänger ein und Receive-Interrupt ein
UCSRC = (1<<UCSZ1) | (1<<UCSZ0); // 8 Bit, 1 Stop-Bit, kein Parity
UBRRH = (U8)(UBRRValue>>8); // BAUD-Rate setzen
UBRRL = (U8)UBRRValue;
Pixel_clearScreen (); // alle Pixel löschen
// Test-Ausgaben:
//Pixel_lineX (0, 0, 96, TRUE);
//Pixel_lineX (0, 1, 96, TRUE);
//Pixel_set (0, 0, TRUE);
//Text (0, 0, "Hallo");
sei (); // los gehts, Interrupts freigeben
LED_aus; // Einschalt-Reset-LED aus
while (1)
{
#ifdef LCD // wenn LCD, dann Zeitwerte anzeigen:
LCD_Cursor (1, 2);
LCD_Text ("L ");
LCD_Zahl (lineTime, 5, FALSE);
LCD_Text ("/ P ");
LCD_Zahl (pixelTime, 3, FALSE);
LCD_Text ("/ Y ");
if (CursorY <lines)
LCD_Zahl (CursorY, 1, FALSE);
else
LCD_Zeichen ('-');
#endif
/**
#ifdef LCD // wenn LCD, dann USART anzeigen:
LCD_Cursor (1, 3);
LCD_Zahl (USART_Pointer, 2, FALSE);
if (USART_Pointer > 0)
{
LCD_Cursor (1, 4);
U8 i1;
for (i1 = 0; i1 < USART_Pointer; i1++)
LCD_Zeichen (USART_data[i1]);
}
#endif
**/
if (Taste) // Taste gedrückt?
{
if (Motor_Status)
{ // wenn Motor eingeschaltet, dann abschalten:
PixelIRQ_aus;
Motor_aus;
LS_aus;
Laser_aus;
OCR1A = 0x8000;
OCR2 = 0x80;
lineTime = 0;
pixelTime = 0;
CursorY = 0xFF;
}
else
{ // ansonsten einschalten:
Motor_ein;
LS_ein;
}
while (Taste) // warten, bis Taste losgelassen wurde:
_delay_ms (10);
}
}
return(0);
}
Und hier das Ergebnis als Video: http://www.car-mp3.de/RNetz/Lasertext.avi
Wie im Video zu sehen ist klappt das gar nicht. Die Berechnungen der Zeiten im LCD passen. Nur irgendwo scheine ich einen Fehler bei der Konfiguration der Timer zu haben. Aber ich bin schon zwei Tage am Suchen und finde ihn nicht.
Ich weiß, dass dieses Projekt sehr umfangreich ist. Aber vielleicht hat ja jemand Lust bekommen und denkt sich durch und findet meinen Fehler.
Viele Grüße
Andreas
- - - Aktualisiert - - -
Hier noch der Schaltplan zur Vollständigkeit. Ist nichts besonderes. Manche Bauteile sind etwas überdimensioniert. Das liegt daran, das ich sie rumliegen hatte.
- die Schaltung wird über ein 12V Netzteil versorgt.
- davon wird über einen IRF5305 der Lüfter geschaltet (125mA).
- ebenfalls an 12V hängt die Lichtschranke über einen BC557 geschaltet (10mA). Danach noch ein Pegel-Wandler für den Atmel-Eingang.
- der Standard 7805 für die 5V für den Atmel
- eine USART-Schnittstelle und eine ISP-Schnittstelle, über die auch das LCD angeschlossen werden kann
- dann ein LM317 für die Erzeugung der Spannung des Lasers (4V). Der Laser wird dann über einen BD240 getaktet. (später 300mA, derzeit 30mA). Den IRF5305 konnte ich dafür nicht nehmen, da die Spannung zu niedrig war um das Gate durchzusteuern.
- Quarz, Taster und LED
- was noch fehlt ist der Pegelwandler für den USART um die Schaltung später mit Daten zu füttern. Was das wird habe ich noch nicht entschieden. Vermutlich ein USB-RS232-Wandler. (oder Luxus: ein Bluetooth- oder WLAN-Modul)
26967
Insgesamt musste ich für das Projekt nur die Spiegel für ca. 5€ kaufen. Alles andere war in der Bastelkiste. Die Halterung für die Spiegel hat mir ein Kumpel ausgelasert und gekanntet. Ist praktisch, wenn man auf sowas Zugriff hat. ;-)
Wenn alles läuft möchte ich die Spiegelhalterung noch 3D-Drucken lassen um Gewicht zu sparen. (höhere Refresh-Rate) Obwohl die Linien zur Zeit auch flakerfrei dargestellt werden. Das Flackern im Video kommt von der Kamera und vom falschen Timing.
Außerdem soll der Laser durch einen stärkeren ersetzt werden, damit man auch am Tag gut was erkennen kann.
Viele Grüße
Andreas