Hi Leute,
nachdem ich hier nun schon fleißig mitgelesen habe und die eine oder andere Frage losgeworden bin, wird es Zeit, dass ich mein erstes großes Projekt hier mal vorstelle. Seit einiger Zeit verfolge ich diesen Beitrag, da sich dort im Moment allerdings nicht viel Neues tut, habe ich mich entschlossen, mein eigenes Ambilight für meinen PC zu entwickeln:
Im Netz habe ich Boblight gefunden, welches über die RS232 Schnittstelle die RGB Daten für den rechten, oberen und linken Teil des Bildschirms bereitstellt. Somit musste ich mich um die PC-seitige Software nicht kümmern. Dies hätte ich eh nicht hingekriegt. Ich musste also nur noch einen µC so programmieren, dass er die Daten empfängt und als PWM auf die LED-Leisten gibt. Aber erstmal zu den LED-Leisten selbst:
Bild hier
Bild hier
Als RGB Leisten habe ich welche von Ebay genommen. Auf einem Strip sind drei Cluster mit je 3x3 Superflux Leds und den entsprechenden Widerständen für einen direkten Betrieb an 12V angebracht. Die Strips lassen sich zwischen den Clustern trennen. Für rechts und links an meinem 22" Monitor passt genau ein Strip, für oben habe ich zwei Strips zusammengefügt, so dass dieser aus 5 Clustern besteht. Die Streifen sind beweglich auf einem, bzw. zwei Alustreifen (Baumarkt-Meterware) gelagert, so dass ich die Strips ausrichten kann. Dazu habe ich 3mm Messingdraht an den Alustreifen geklebt und auf die überstehenden Enden Messingrohrstücke gesteckt, die ich vorher an den Enden etwas zusammengequetscht habe. An diesen Rohrstücken sind die Strips festgeklebt. So lassen sich die Strips ausrichten, ohne dass sie ihre Position aufgrund der Schwerkraft ändern. Die Alustreifen habe ich mit Tesa-Powerstrips auf dem Monitor befestigt.
Bild hier
Bild hier
Die Hardware besteht aus zwei Mega88, einer empfängt die Daten über UART und bedient die linke Seite, sowie den blauen Kanal, Mitte. Die übrigen Daten werden an den zweiten Mega88 per UART weitergesendet, dieser dient nur als PWM-Chip. Es ist vorgesehen, über ein Poti die Helligkeit zu regeln, des Weiteren soll es ebenfalls möglich sein, anstatt der Steuerung über den PC, mit einem Poti die Farbe einzustellen, so dass man eine einheitliche Hintergrundfarbe für den Monitor einstellen kann. Dies hab ich aber noch nicht implementiert.
Boblight sendet ein Paket von 9 Bytes, die die Farbinformationen halten. Die einzelnen Bytes kommen in einem Abstand von ca. 0.001 Sekunden an, zwischen den Paketen ist ein Abstand von ca. 0,01 Sekunden (einstellbar). Die Software für die Megas funktioniert bereits sehr gut, normaler Desktopbetrieb, Videos und DVD(mit dem VLC-Player) und Spiele, alles ist möglich. Da ich leider keine Videos aufnehmen kann, hier nur ein paar Standbilder, die vor allem die Ausleuchtung und Farbe der RGB-Leisten demonstrieren:
Bild hier
Bild hier
Bild hier
Bild hier
Bild hier
Bild hier
Bild hier
Bild hier
Bild hier
Bild hier
Nur Gelb hat meine Kamera irgendwie nicht gemocht, das sah total komisch aus (also aufm Bild, in Echt ist es super)
Da die Leisten keine RGB Leds, sondern rote, grüne und blaue Leds nebeneinandern haben, sieht man bei Mischfarben ein paar Streifen oben und unten, aber das stört nicht wirklich.
Mit einem Problem habe ich allerdings noch zu kämpfen, ab und zu geht die Beleuchung kurz aus und sofort wieder an. Dieses flackern ist recht unangenehm, erklären kann ich es mir aber nicht. Vielleicht könnt ihr ja mal einen Blick über den Code werfen, vielleicht fällt euch ja was auf, worans liegen kann. Dies ist übrigens die "schnelle" Version, ich habe auch noch eine langsamere Version, die vor der Weitergabe der Daten die empfangene Anzahl an Bytes pro Paket checkt, da taucht das Flackern nicht auf. Da aber die Leds auch bei der schnellen Version aktiv ausgeschaltet werden müssen (also per 0 0 0 0 0 0 0 0 0 von Boblight), kann ich mir das Flackern echt nicht erklären.
Hier der Code für den Master: (an dem hängts, dass es flackert....)
ambilight.c:
Code:
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdavr.h>
#include "fifo.h"
#include "uart.h"
#define F_CPU 14745600
#define BAUDRATE 9600
void initialize_IO(void);
void initialize_IRQ(void);
void initialize_TIMER0(void);
void initialize_TIMER1(void);
void initialize_TIMER2(void);
//void initialize_ADC(void);
int main(void)
{
initialize_IO();
initialize_IRQ();
//initialize_ADC();
initialize_TIMER0();
initialize_TIMER1();
initialize_TIMER2();
initialize_UART(BAUDRATE);
// enable global interrupts
sei();
while(1);
return 0;
}
void initialize_IO(void)
{
// PWM-Pins als Output setzen
DDRD |= (1<<PD3)|(1<<PD5)|(1<<PD6);
DDRB |= (1<<PB3);
}
void initialize_IRQ(void)
{
// enable Timer1 Overflow Interrupt
TIMSK1 = (1<<TOIE1);
// enable AD-Converter Interrupt
//ADCSRA |= (1<<ADIE);
}
void initialize_TIMER0(void)
{
// Timer0-Register
TCNT0 = 0;
// Output Compare Pin Behaviour:
// clear OC0A and OC0B on compare match and set on TOP
TCCR0A |= (1<<COM0A1)|(1<<COM0B1);
// Wave Form Generation:
// Fast PWM with TOP at 0xFF.
TCCR0A |= (1<<WGM01)|(1<<WGM00);
// Initialy set the Ouput Compare Registers to zero:
OCR0A = 0x00;
OCR0B = 0x00;
// Start Timer with Prescaler of 256 --> f_Timer0 = 225Hz
TCCR0B |= (1<<CS02);
}
void initialize_TIMER1(void)
{
// Timer1-Register initialisieren
TCNT1H = 0x00;
TCNT1L = 0x00;
}
void initialize_TIMER2(void)
{
// Timer0-Register
TCNT2 = 0;
// Output Compare Pin Behaviour:
// clear OC02A and OC2B on compare match and set on TOP
TCCR2A |= (1<<COM2B1)|(1<<COM2A1);
// Wave Form Generation:
// Fast PWM with TOP at 0xFF.
TCCR2A |= (1<<WGM21)|(1<<WGM20);
// Initialy set the Ouput Compare Registers to zero:
OCR2A = 0x00;
OCR2B = 0x00;
// Start Timer with Prescaler of 256 --> f_Timer2 = 225Hz
TCCR2B |= (1<<CS22);
}
/*void initialize_ADC(void)
{
// Voltage Reference = AREF --> no Bit need to be set
// PC0 as first AD-Conversion --> no Bit need to be set
// Set ADC Prescaler to 128 (--> F_ADC = 14745.6/128 = 115.2kHz)
// Set ADC Prescaler to 64 (--> F_ADC = 8000/128 = 125kHz)
ADCSRA |= (1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
// Set Auto Trigger Source to Timer1 Overflow --> at each new Datablock the ADC will be run
ADCSRB |= (0<<ADTS2)|(0<<ADTS1);
//Digital Input Disable
//### write to 1, when digital input on this pin is not needed ###
//DIDR0 = (1<<ADC0D)|(1<<ADC1D)|(1<<ADC2D)|(1<<ADC3D)|(1<<ADC4D)|(1<<ADC5D);
// Auto Trigger Enable
ADCSRA |= (1<<ADATE);
// Left-adjust result for 8-bit accuracy
ADMUX |= (1<<ADLAR);
// Enable ADC
ADCSRA |= (1<<ADEN);
//Start Conversion
ADCSRA |= (1<<ADSC);
}*/
/*ISR(SIG_ADC)
{
if(BIT_IS_SET(ADMUX, MUX0)) { // if ADC1 was selected
adc_value[COLOUR] = ADCH;
CLEAR_BIT(ADMUX, MUX0); // select ADC0 as new ADC-Channel
}
else { // if ADC0 was selected
adc_value[BRIGHTNESS] = ADCH;
SET_BIT(ADMUX, MUX0); // select ADC1 as new ADC-Channel
}
}*/
ISR(SIG_OVERFLOW1)
{
/* Timer1 läuft nach 0,00444 Sekunden über. Da er in der UART-
Empfangs-ISR immer wieder zurückgesetzt und neu gestartet wird,
läuft Timer1 nur zwischen zwei Datenpaketen über. Dann wird
der Counter der UART-Empfangs-ISR zurückgesetzt, um die richtige
Zuordnung der PWM-Werte sicherzustellen */
uart_clear_counter();
}
uart.c:
Code:
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdavr.h>
#include "uart.h"
#include "fifo.h"
#define START_TIMER1 SET_BIT(TCCR1B, CS10) //prescaler = 1
#define STOP_TIMER1 TCCR1B &= ~((1<<CS12)|(1<<CS11)|(1<<CS10))
#define CLEAR_TIMER1 TCNT1 = 0
// FIFO-Objekt und Puffer für die Ausgabe
#define BUFSIZE_OUT 5
uint8_t outbuf[BUFSIZE_OUT];
fifo_t outfifo;
// Counter-Variable
volatile uint8_t k = 0;
void initialize_UART(const uint16_t baudrate)
{
uint8_t sreg = SREG;
//uint16_t ubrr = (uint16_t) ((uint32_t) F_CPU/(16L*baudrate) - 1);
uint16_t ubrr = (uint16_t) ((uint32_t) 14745600/(16L*baudrate) - 1);
UBRR0H = (uint8_t) (ubrr>>8);
UBRR0L = (uint8_t) (ubrr);
// Interrupts kurz deaktivieren
cli();
// UART Receiver und Transmitter anschalten, Recieve-Interrupt aktivieren
// Data mode 8N1, asynchron
UCSR0B = (1<<RXEN0) | (1<<TXEN0) | (1<<RXCIE0);
UCSR0C = (1<<USBS0) | (3<<UCSZ00);
//Flush Receive-Buffer (entfernen evtl. vorhandener ungültiger Werte)
do
{
UDR0;
}
while (UCSR0A & (1<<RXC0));
// Rücksetzen von Receive und Transmit Complete-Flags
UCSR0A = (1<<RXC0)|(1<<TXC0);
// Global Interrupt-Flag wiederherstellen
SREG = sreg;
// FIFO für Ausgabe initialisieren
fifo_init (&outfifo, outbuf, BUFSIZE_OUT);
}
int uart_putc(const uint8_t c)
{
int ret = fifo_put(&outfifo, c);
UCSR0B |= (1<<UDRIE0);
return ret;
}
void uart_clear_counter(void)
{
k = 0;
}
ISR(SIG_USART_RECV)
{
STOP_TIMER1;
CLEAR_TIMER1;
START_TIMER1;
switch (k++)
{
case 0:
OCR0B = UDR0; // Rot, Links
break;
case 1:
uart_putc(UDR0); // Weitersenden der Daten zum Slave (Rot, Mitte)
break;
case 2:
uart_putc(UDR0); // Weitersenden der Daten zum Slave (Rot, Rechts)
break;
case 3:
OCR0A = UDR0; // Grün, Links
break;
case 4:
uart_putc(UDR0); // Weitersenden der Daten zum Slave (Grün, Mitte)
break;
case 5:
uart_putc(UDR0); // Weitersenden der Daten zum Slave (Grün, Rechts)
break;
case 6:
OCR2B = UDR0; // Blau, Links
break;
case 7:
OCR2A = UDR0; // Blau, Mitte
break;
case 8:
uart_putc(UDR0); // Weitersenden der Daten zum Slave (Blau, rechts)
break;
}
}
// Ein Zeichen aus der Ausgabe-FIFO lesen und ausgeben
// Ist das Zeichen fertig ausgegeben, wird ein neuer SIG_UART_DATA_IRQ getriggert
// Ist die FIFO leer, deaktiviert die ISR ihren eigenen IRQ.
ISR(SIG_USART_DATA)
{
if (outfifo.count > 0)
UDR0 = _inline_fifo_get(&outfifo);
else
UCSR0B &= ~(1<<UDRIE0);
}
Der Rest vom Code ist im Anhang.
So, ich hoffe, euch gefällts!
Markus
Lesezeichen