Auf Grund mehrerer Nachfragen bezüglich Quellcode der Ansteuerung hier die wichtigsten Teile des Programmcodes.

In meinem Fall hat der Strip ( LPD8806 Chip ) insgesamt 614 RGB-LED`s und da jede LED die Information für rot, grün und blau erhalten muss, ist ein Puffer von 614×3 also 1.842 Bytes nötig.

Für einfache Ausgaben braucht man im Prinzip gar keinen Puffer sondern kann alles live rausschreiben, will man aber kompliziertere Muster anzeigen erleichtert der Puffer die Arbeit ungemein. Bei 8-bit Mikrocontrollern gerät man da allerdings schon an die Grenzen da der verfügbare RAM stark begrenzt ist. Hier verrichtet ein Atmega644PU die Arbeit.

Grundsätzlich läuft es so dass ich die Muster nacheinander in den Puffer schreibe und sobald alle Muster geschrieben sind, den Puffer an den RGB-Strip ausgebe. Das kann man normalerweise einfach per SPI machen, da der Strip allerdings kein vollwertiges SPI-Gerät ist und keine eigene Chipselect-Leitung hat kann das Probleme verursachen wenn man noch weitere Geräte per SPI ansprechen will. Aus diesem Grunde habe ich mich entschieden die Ausgabe komplett in der Software zu machen, was den Vorteil bietet dass ich die Ports und Pins zur Ausgabe frei wählen kann und somit keine Kollision mit anderen SPI-Geräten mehr besteht.

Die Funktion zur Ausgabe an den RGB-Strip sieht wie folgt aus:

void lpd8806_write_bitbang(const uint8_t *data, uint16_t length)
{
	if(isRgbEnabled != 1)
	{
		//RgbTransmission disabled!
		setOrangeLed(1);
		delayWithWatchdog(10);
		setOrangeLed(0);
		return;	
	}
	
	//PC6 clock
	//PC7 data
	const uint8_t *endofdata;
	uint8_t nextbyte;
	
	endofdata = data+length;
	
	for (;;) {
		nextbyte = *data++;
		if(data > endofdata)
		{
			break;
		}
		lpd8806_transmit_byte(nextbyte);
	}
	
	//now send latchbytes 1 zerobyte for every 64 leds in the strip
	LPD8806_PORT_DATA &= ~(1<<LPD8806_PIN_DATA); //clear data line uint8_t latchbytes = ((length+63)/64)*3*8; //calculate needed latchbytes while(latchbytes > 0)
	{
		lpd8806_pulse_clock_line();
		latchbytes--;
	}
}

void lpd8806_pulse_clock_line()
{
	LPD8806_PORT_CLOCK |= (1<<LPD8806_PIN_CLOCK);
	LPD8806_PORT_CLOCK &= ~(1<<LPD8806_PIN_CLOCK);
}

void lpd8806_transmit_byte(uint8_t currentByte)
{
	uint8_t pos = 1;
	const uint8_t LED_DATA_MASK = 1 << LPD8806_PIN_DATA;
	LPD8806_PORT_DATA |= LED_DATA_MASK;
	lpd8806_pulse_clock_line();
	while(pos<8)
	{
		// Set the data bit
		if (currentByte & 0x80)
		{
			LPD8806_PORT_DATA |= LED_DATA_MASK;
		}
		else
		{
			LPD8806_PORT_DATA &= ~LED_DATA_MASK;
		}
	
		// Pulse the clock line
		//PulseClockLine<ClockPin>(ClockRegister);
		lpd8806_pulse_clock_line();
	
		// Advance to the next bit to transmit
		currentByte = currentByte << 1;
		pos++;
	}
}

Auf Grund der horizontalen Montage des LED-Strips musste eine Visualisierung her die dort auch etwas her macht.

Musikpegel wie üblich vertikal anzuzeigen war somit nicht möglich, blieb noch die Möglichkeit horizontal oder durch Helligkeitsunterschiede. Beim Experimentieren entstand dann die Idee alles irgendwie zu kombinieren in einem horizontalen Scrolleffekt mit lautstärkeabhängiger Helligkeitsänderung.

Später kam auch noch die Idee hinzu den Scrolleffekt zu beschleunigen wenn die Lautstärke hoch ist und zu verringern wenn die Lautstärke abnimmt. Auch die Kombination die bestimmte Farben bestimmten Frequenzbereichen zuzuordnen ergab einen interessanten Effekt.

Da ich zwei MSGEQ7-Equalizer-Chips im Einsatz habe verfüge ich also über 14 unterschiedliche Pegelwerte. Jeweils 7 Frequenzbänder pro Kanal. Den Kanal für die höchsten Töne verwende ich nirgends da diese Töne in Musik recht selten vorkommen, dafür habe ich einen per Software erzeugten zusätzlichen Pegel generiert der quasi die Summe der Bänder eines Kanals darstellt. Hauptsächlich verwende ich zu dessen Berechnung aber nur die tiefen und mittleren Frequenzbereiche da diese optisch am Wirksamsten sind und subjektiv eher zur gehörten Musik passen.

Da ich nur einen durchgehenden ca, 19 Meter langen LED-Strip habe musste eine Lösung her wie ich hier sinnvoll beide Kanäle darstellen kann 🙂 Die Überlegungen endeten darin dass ich in der Mitte mit der Ausgabe anfange und dann für den linken Kanal nach links scrolle und für den rechten Kanal nach rechts.

Nachfolgend der Code der Funktion die den Scrolleffekt erzeugt wobei die Funktion mit einer variablen Verzögerung in einer Schleife aufgerufen wird. Jeder Aufruf bewirkt eine Verschiebung der LED`s nach rechts bzw. links.

Übergeben wird die LED-Postion im Strip an welcher gestartet und wieviele LED`s ab dieser Position verwendet werden sollen. Weiters die Position der Grenze zw. linkem und rechtem Kanal sowie die Farbinformation die für den jeweiligen Kanal ausgegeben werden soll.

void displayBidirectionalWaterfall(uint16_t fromLed, uint16_t useLed, uint16_t border, uint8_t red, uint8_t green, uint8_t blue, uint8_t red2, uint8_t green2, uint8_t blue2)
{
	uint16_t i = fromLed*3;
	uint16_t toLed = fromLed+useLed;
	uint8_t backupred = 0;
	uint8_t backupgreen = 0;
	uint8_t backupblue = 0;
	border = border*3;

	while(i<border)
	{
		tBuffer[i] = tBuffer[i+3];
		tBuffer[i+1] = tBuffer[i+4];
		tBuffer[i+2] = tBuffer[i+5];
		i=i+3;
	}
	
	tBuffer[i] = green;
	tBuffer[i+1] = red;
	tBuffer[i+2] = blue;
	i=i+3;
	backupgreen = tBuffer[i];
	backupred = tBuffer[i+1];
	backupblue = tBuffer[i+2];	
	tBuffer[i] = green2;
	tBuffer[i+1] = red2;
	tBuffer[i+2] = blue2;
	i=i+3;
	
	i=toLed*3;
	while(i>border)
	{
		tBuffer[i] = tBuffer[i-3];
		tBuffer[i+1] = tBuffer[i-2];
		tBuffer[i+2] = tBuffer[i-1];
		i=i-3;
	}
	
	tBuffer[i] = backupgreen;
	tBuffer[i+1] = backupred;
	tBuffer[i+2] = backupblue;
}

Keine Hexerei soweit, aber der Effekt lässt sich sehen. Die Farbinformation die dieser Funktion übergeben werden lässt sich beliebig Musikabhängig verändern sowie in der erweiterten Version der Verzögerungswert, der bestimmt in welchem Intervall die Funktion aufgerufen wird.

Eine andere Variante ist die anzeige eines VU-Meters. Hier gibt es zwei Funktionen die die Anzeige des VU-Meters übernehmen, einmal normal und einmal invertiert(Pegelausschlag von rechts nach links). Nachfolgend der Code der normalen Version welcher ein VU-Meter erzeugt welches ein leichtes blau als Hintergrundbeleuchtung hat und wie man es kennt grün, dann gelb und im letzten Teil also bei „Vollausschlag“ rote LED`s anzeigt:

void displayVuMeter(uint16_t fromLed, uint16_t useLeds, uint16_t value) //value 0-1280
{
	uint16_t i = fromLed;
	uint16_t toLed = fromLed+useLeds;
	uint16_t bufferPos = fromLed*3;
	uint16_t step = 1280/useLeds;
	uint16_t lastLedToMark = 0;
	uint16_t redTreshold = 0;
	uint16_t yellowTreshold = 0;
	if(step > 0)
	{
		lastLedToMark = (value/step)+fromLed;
		yellowTreshold = ((useLeds*6)/10)+fromLed;
		redTreshold = ((useLeds*8)/10)+fromLed;
	}
	
	while(i<toLed)
	{
		if(i <= lastLedToMark)
		{
			if(i>redTreshold)
			{
				tBuffer[bufferPos] = 0;
				tBuffer[bufferPos+1] = 50;
				tBuffer[bufferPos+2] = 0;
			}
			else if(i>yellowTreshold)
			{
				tBuffer[bufferPos] = 50;
				tBuffer[bufferPos+1] = 50;
				tBuffer[bufferPos+2] = 0;
			}
			else
			{
				tBuffer[bufferPos] = 50;
				tBuffer[bufferPos+1] = 0;
				tBuffer[bufferPos+2] = 0;
			}
		}
		else
		{
			tBuffer[bufferPos] = 0;
			tBuffer[bufferPos+1] = 0;
			tBuffer[bufferPos+2] = 2;
		}
		i++;
		bufferPos+=3;
	}
}

Um nun z.b. pro Kanal einen 6-Band Equalizer (jeweils 9 LED`s pro Frequenzband mit einem Abstand von 1 Led) anzuzeigen ist nun lediglich noch folgender Code nötig:

void displayVuMeters(uint16_t start)
{
	displayVuMeter(start,9,scaleValue(msqData[0]));
	start += 10;
	displayVuMeter(start,9,scaleValue(msqData[1]));
	start += 10;
	displayVuMeter(start,9,scaleValue(msqData[2]));
	start += 10;
	displayVuMeter(start,9,scaleValue(msqData[3]));
	start += 10;
	displayVuMeter(start,9,scaleValue(msqData[4]));
	start += 10;
	displayVuMeter(start,9,scaleValue(msqData[5]));
	start += 10;
	displayVuMeterInverted(start,9,scaleValue(msqData2[5]));
	start += 10;
	displayVuMeterInverted(start,9,scaleValue(msqData2[4]));
	start += 10;
	displayVuMeterInverted(start,9,scaleValue(msqData2[3]));
	start += 10;
	displayVuMeterInverted(start,9,scaleValue(msqData2[2]));
	start += 10;
	displayVuMeterInverted(start,9,scaleValue(msqData2[1]));
	start += 10;
	displayVuMeterInverted(start,9,scaleValue(msqData2[0]));
}

Das Array msqData enthält die aktuellen Werte der 7 Frequenzbänder des linken und msqData2 die des rechten Kanals. Die Funktion scaleValue() ist dazu da die Werte zu skalieren damit die Anzeige besser aussieht. Hier wird z.b ein gewisser Mindestpegel kontrolliert, unter welchem generell 0 zurück gegeben wird was das „Rauschen“ bei pausierter oder sehr leiser Musik verhindert. In dieser Funktion können auch noch weitere Anpassungen vorgenommen werden, wie z.b den Pegelverlauf anhand Durchschnittswerten zu modifizieren damit Pegel auch noch sichtbar sind wenn die Musik plötzlich sehr leise Passagen hat.

Das ist natürlich VUMeter-technisch nicht wirklich korrekt, aber optisch eindeutig besser 😉

Willkommen auf meiner Webseite