PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Projekt "LaserText": Bitte um Denkhilfe



Bumbum
18.12.2013, 18: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

radbruch
18.12.2013, 19:45
Hallo

Zwei Tage ist doch noch kein Aufwand ;)

Solange wird wohl allein schon das "Reindenken" in deinen Code dauern.

https://www.roboternetz.de/community/threads/34293-Einfacher-Laserprojektor

Gruß

mic

Andree-HB
18.12.2013, 20:21
...ich kann mir aber gar nicht vorstellen, dass die Laserdiode das langfristig so problemlos mitmachen wird.
Wie pulst Du denn ? Einfaches an/aus ?

Bumbum
19.12.2013, 16:57
Hallo,

ich hatte heute und jetzt nur kurz Zeit. (Ein Streß immer mit den Weihnachtsmärkten und Weihnachtsfeiern...)

Aber ich möchte kurz was zu den Antworten schreiben:

@Radbruch: Die Software war in ca. 1 Stunde geschrieben. Die komplette Projektplanung inkl. Berechnungen und zusammenlöten des Prototyen und Software habe ich in 2 Tagen (ein Wochenende) geschafft. Dann war ich weitere 2 Tage (noch ein Wochenende) am Timing-Problem gesessen. Ich halte deshalb 2 Tage fürs einarbeiten für Übertrieben. Aber du hast recht: Zumuten kann man das niemand. Deshalb habe ich bereits in meinem ersten Post geschrieben, dass es nur für Leute gedacht ist, die Lust auf sowas haben. Vielleicht baut es ja jemand nach?
Ich kann mir mal die Mühe machen den relevanten Teil des Codes für das Laser-Timing hervorzuheben. Im Endeffekt ist es vermutlich ca. "ein Bildschirm" mit Variablen-Deklarationen und Initialisierung und noch ein weiterer mit dem Timer-Code.
Der Rest ist schon alles Luxes (Pixel-Speicher, Font, USART, etc.) der für das jetztige Problem noch gar nicht benötigt wird.

Den verlinkten Laser-Projekter muss ich mir ein anderes mal ansehen. Wie gesagt möchte ich alles selbst entwickeln und erlernen und mich aus den Fehlern weiterbilden.

Ich habe diesen Thread auch erstellt und geschrieben um meine Gedanken zu sortieren. Ich glaube es hat geholfen. Heute früh hatte ich kurz Zeit und habe die Timer mithilfe von Zählvariablen überprüft. Das passt alles. Ich habe ein paar Kleinigkeiten geändert und nun erhalte ich zumindest ein (fast) stehendes Bild. Es sind ab und zu kurze Aussetzer drin und das Bild hat noch nicht viel mit dem Inhalt im Pixel-Speicher zu tun. Das könnte aber auch daran liegen, dass die Kippwinkel der Spiegel noch nicht ausgerichtet sind. Auf jeden Fall bin ich durch mein Gedankensortieren ein gutes Stück weiter gekommen. :-)

@Andree-HB: Dies ist mein erstes Projekt mit einem Laser. Ich habe mich vorher informiert und im Netz nichts gefunden, das pulsen für Laser-Dioden schädlich sein soll. Ich schalte den Laser über einen PNP-Transistor ein und aus. Er ist übrigens inklusive Elektronik zur Strombegrenzung. Den Laser habe ich von Pollin. Die genaue Bezeichnung müsste ich heraussuchen. Ich habe einfach an die Kontakte wo die Batterien angeklemmt werden den Ausgang meines Transistors gelötet. Was könnte da schädlich für den Laser sein?

Viele Grüße
Andreas