Code:
#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:
Lesezeichen