Hallo liebe Community,
seit kurzem bin ich dabei mir eine kleine Klasse zu schreiben, welche die LED-Strings mit den WS2812B Mikrokontrollern steuern kann. Mir ist bewusst, dass es dafür bereits supertolle vorgefertigte Klassen, wie z.B. von Adafruit gibt, meine Intention lag jedoch darin mich mit den Timern auseinanderzusetzen und zu verstehen, wie diese funktionieren.
Bauteile:
- Arduino Uno
- LED-strip mit 40LEDs, welche jeweils einen WS2812B integriert haben
Meine Interpretation des Datenblattes:
Datenblatt des WS2812B:https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf.
Laut dem Datenblatt interpretiert der WS2812B ein Signal mit ...
- ... hoher Taktflanke von 0.8us und darauffolgender niedriger Taktflanke von 0.45us als 1.
- ... hoher Taktflanke von 0.4us und darauffolgender niedriger Taktflanke von 0.85us als 0.
Der Einfachheit halber habe ich in meinem Code nur 0.4us und 0.85us genommen, da diese dennoch im Toleranzbereich von 150ns liegen.
Die einzige Zeit, bei der ich noch zweifle, ob ich diese richtig interpretiert habe ist die RES-time:
Ich interpretiere das so, dass sobald der x * 24Bit lange Stream in die µCs geschrieben worden ist und daraufhin eine pause von 50us auftritt die µCs sozusagen "aktiviert" werden und die Daten, welche sie davor durch den Stream bekommen haben. Das x soll hierbei die anzahl an LEDs bezeichnen.
Bitte korrigiert mich hier schon einmal, ob ich mit dieser Interpretation falsch liege.
Im folgenden nun mein Code:
/* Calculate the time for the timers
*
* 0code T0H = 0.4us, T0L = 0.85us
* 1code T1H = 0.85us, T1L = 0.4us
* RESET = >50us
* Toleranz +- 150ns
*
* TIMER2 = 8Bit zaehler -> 256zyklen
*
* prescale = 1
*
* 0.85us: COMPCOUNT = 256 - 0.00000085 * 16000000 = 242.4
* -> COMPCOUNT = 242 -> OCR_count = 14
*
* 0.4us: COMPCOUNT = 256 - 0.0000004 * 16000000 = 249.6
* -> COMPCOUNT = 250 -> OCR_count = 6
*
* prescale 1024
* 50us: COMPCOUNT = 256 - 0.00005 * 16000000/1024 = 255.2
* -> COMPCOUNT = 0 -> lasse ueberlaufen
*/
#define WRITE_HIGH_0 0 //the index for writing a high flank for the 0
#define WRITE_LOW_0 1 //the index for writing a low flank for the 0
#define WRITE_HIGH_1 2 //the index for writing a high flank for the 1
#define WRITE_LOW_1 3 //the index for writing a low flank for the 1
//bistate indicates whether I want to write 1 or 0 to the chip and
//if its the high or low flank
volatile byte bitstate = 0;
#define COMPCOUNT_HIGH_0 6 //the value for the comparecounter, if we want to send a 0 and its the high flank
#define COMPCOUNT_LOW_0 14 //the value for the comparecounter, of we want to send a 0 and its the low flank
#define COMPCOUNT_HIGH_1 14 //the value for the comparecounter, if we want to send a 1 and its the high flank
#define COMPCOUNT_LOW_1 6 //the value for the comparecounter, if we want to send a 1 and its the low flank
auto led_string_write(unsigned int, unsigned int, unsigned int) -> void;
auto create_stream(unsigned int, unsigned int, unsigned int) -> byte*;
auto led_show(byte*) -> void;
//loopvariables for waiting that the timers end up with their work
volatile bool wait_for_high_1 = false;
volatile bool wait_for_low_1 = false;
volatile bool wait_for_high_0 = false;
volatile bool wait_for_low_0 = false;
volatile bool wait_for_reset = false;
//this interrupt writes the low flank to the chips
ISR(TIMER2_COMPA_vect){
if(bitstate == WRITE_HIGH_1){
wait_for_high_1 = false;
bitstate = WRITE_LOW_1;
OCR0A = COMPCOUNT_LOW_1; //auf 0.4us stellen
TCNT0 = 0;
PORTD = B00000000; //pin2 fuer 0.4us auf low stellen
}
else if(bitstate == WRITE_LOW_1){
wait_for_low_1 = false;
TCNT0 = 0;
}
else if(bitstate == WRITE_HIGH_0){
wait_for_high_0 = false;
bitstate = WRITE_LOW_0;
OCR0A = COMPCOUNT_LOW_0; //auf 0.85us stellen
TCNT0 = 0;
PORTD = B00000000; //pin 2 fuer 0.85us auf low stellen
}
else if(bitstate == WRITE_LOW_0){
wait_for_low_0 = false;
TCNT0 = 0;
}
}
ISR(TIMER2_OVF_vect){
wait_for_reset = false;
}
void setup() {
DDRD = B00000100;
PORTB = B00000000; //use pin2 for the tests
cli();
TCNT2 = 0;
TCCR2A = 0;
TCCR2B = 0;
TCCR2B |= (1 << CS20);
TIMSK2 |= (1 << OCIE2A);
}
void loop() {
led_write(0, 0, 0);
}
void led_write(unsigned int red, unsigned int green, unsigned int blue){
byte *stream = create_stream(&red, &green, &blue);
led_show(stream); //write the stream to the leds
//wait or writing the whole stream
cli();
wait_for_reset = true;
TCCR2A = 0;
TCCR2B = 0;
TCCR2B |= (1 << CS20);
TCCR2B |= (1 << CS22); //prescale auf 1024 setzen, damit der overflow nach 50us ausgeloest wird
TIMSK2 = 0;
TIMSK2 |= (1 << TOIE2); //soll auf overflow reagieren
TCNT0 = 0; //auf 0 setzen
sei();
//hier sollen auch die ganzen 50us gewartet werden, bis der overflow interrupt aufgerufen wird und fertig gelaufen ist
while(wait_for_reset);
cli();
TIMSK2 = 0;
TIMSK2 |= (1 << OCIE2A);
TCCR2B = 0;
TCCR2B = (1 << CS20); //prescale wieder auf 1 setzen
delete[] stream;
}
//creates a stream for the data the leds should hold
byte* create_stream(unsigned int r, unsigned int g, unsigned int b){
byte *stream;
byte red, green, blue;
const byte colour_bitlength = 8;
byte bitmask = B00000001;
stream = new byte[24]; //3 * 8 Bit, da jede LED 8Bit hat
if(r > 255) r = 255;
if(g > 255) g = 255;
if(b > 255) b = 255;
red = r;
green = g;
blue = b;
//Beachte, dass das highbit zuerst gesendet wird und mit green angefangen wird
//deshalb muss stream[0] = rechtestes bit von green sein und so weiter
//green
for(int i = 0; i < colour_bitlength; ++i){
stream[i] = (green & bitmask);
green = (green >> 1);
}
//red
for(int i = 0; i < colour_bitlength; ++i){
stream[i + colour_bitlength] = red & bitmask;
red = (red >> 1);
}
//blue
for(int i = 0; i < colour_bitlength; ++i){
stream[i + (2 * colour_bitlength)] = (blue & bitmask);
blue = (blue >> 1);
}
return stream;
}
void led_show(byte *stream){
for(unsigned int i = 0; i < 24; ++i){
if(stream[i]){
cli();
wait_for_high_1 = true;
wait_for_low_1 = true;
bitstate = WRITE_HIGH_1;
OCR2A = COMPCOUNT_HIGH_1; //set to 0.85us
TCNT2 = 0;
PORTD = B00000100; //set pin2 to high
sei();
//wait until the signal was completely sent to the microchip
while(wait_for_high_1);
while(wait_for_low_1);
} else{
cli();
wait_for_high_0 = true;
wait_for_low_0 = true;
bitstate = 0;
OCR2A = COMPCOUNT_HIGH_0; //set to 0.4us
TCNT2 = 0;
PORTD = B00000100; //set pin2 to high
sei();
//wait until the signal was completely sent to the microchip
while(wait_for_high_0);
while(wait_for_low_0);
}
}
cli();
}
Auftretende Probleme:
Ich habe an meinen Arduino, wie oben bereits beschrieben, 40LEDs anhängen, von denen nur die erste Leuchten sollte. Sobald ich den Code hochlade leuchte 4.
Eigentlich sollten durch den Bytestream die entsprechenden Farben in die µC geschrieben werden.
Egal, welche Werte ich in die Funktion gebe, ich bekomme immer nur die Farbe weiß heraus.
Ich habe mir mal per Serieller Schnittstelle den Bytestream ausgeben lassen under ist immer korrekt.
Daraus folgere ich, dass irgendwas mit den Zeiten falsch sein muss, da die µCs der LEDs diese immer als 1 interpretieren.
Was die Zeiten angeht kann ich mir leider nicht sicher sein, ob diese jetzt eingehalten werden oder nicht, da ich leider kein Oszilloskop besitze und somit schlecht beurteilen kann, wie ich diese anpassen soll.
Ich bedanke mich schon einmal für jegliche Hilfe
Grüße Tyrone_Montana