Geschwindigkeit bei PWM via Schieberegister

Hey Leute, ich habe mal wieder ein kleineres Problem. Ich möchte ein Lauflicht aus 14 LED-Stripes bauen. Dazu habe ich einen Pro Mini mit 16MHz hergenommen, der bedient 2 Schieberegister 74HC595 welche dann meine 14 12V-Stripes via BC368 anlenken. Soweit so gut, die Hardware funktioniert, ich kann auch die einzelnen Stripes wunderbar ansteuern, auch als Lauflicht. Jetzt möchte ich die aber mit PWM dimmen. Probeweise mit ShiftPWM klappt das auch sehr schön, aber das möchte ich eigentlich nicht verwenden sondern selbst was basteln. Ich hab mir auch schon was zusammengetippt, aber so richtig funktioniert das nicht, die LEDs bekommen damit nur eine PWM-Frequenz von geschätzten 5Hz, also eher ein Stroboskop-Effekt... Das Lauflicht selbst funktioniert mit gewollter Geschwindigkeit, nur die jeweils aktive LED blinkt wie blöd.

Hinweise zum Code: Er ist noch nicht fertig und schön, kann sein dass ein paar ausgemusterte Variablen rumschwirren, ich hab viel rumprobiert ;) Ich hab auch vor dem Timer1 es ganz simpel mit einer if(micros()...-Abfrage gelöst, aber wegen o.g. Problem hab ich es dann mit Timer1 versucht - Ergebnis war gleich. Die shiftMe() ist eine angepasste Version der shiftOut() aus den Beispielen. Die sache mit step_order[16] dient nur dazu, die Ausgänge des Schieberegisters zu ordnen, die ich aus Layouttechnischen Gründen anders angeordnet hab ;) Gedimmt werden die LEDs momentan nur mit dem Wert 50 in else {state_step[active_led] = 50;} Das wäre 50% helligkeit. Das wird natürlich noch anders geregelt wenn alles funktioniert. Ich bin ja noch beim experimentieren...

#include 

int latchPin = 2;
int clockPin = 3;
int dataPin = 4;

int i=0;
int pinState;
int this_led = 0;
byte state_step[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0};
byte step_order[16] = {7,8,9,10,11,12,13,14, 0,1,2,3,4,5,6,15};
byte active_led = 15;
unsigned long interval_act = 500; // millis()
unsigned long last_act = 0;
int dimmer_step = 0;
boolean pulseme_now = 0;
void setup()
  {
  Timer1.initialize(100);
  Timer1.attachInterrupt(pulseme);

  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
  }

void pulseme()
  {
  pulseme_now = 1;
  }
  
void loop()
  {

  if(millis() - last_act >= interval_act)
    {
    state_step[active_led] = 0;
    if(active_led >12) {active_led = 0;}
    else               {active_led++;}
    if(state_step[active_led] >= 1) {state_step[active_led] = 0;}
    else                            {state_step[active_led] = 50;}
    last_act = millis();
    }

  if(pulseme_now == 1);
    {
    shiftMe();
    if(dimmer_step >= 100)
      {
      dimmer_step = 0;
      }
    else
      {
      dimmer_step++;
      }
    pulseme_now = 0;
    }

  }

void shiftMe()
  {
  digitalWrite(latchPin, 0);

  digitalWrite(dataPin, 0);
  digitalWrite(clockPin, 0);

  for (i=0; i<16; i++)
    {
    digitalWrite(clockPin, 0);
    this_led = step_order[i];
    if (state_step[this_led] > dimmer_step)
      {
      pinState= 1;
      }
    else
      {
      pinState= 0;
      }

    digitalWrite(dataPin, pinState);
    digitalWrite(clockPin, 1);
    digitalWrite(dataPin, 0);
    }

  digitalWrite(clockPin, 0);
  digitalWrite(latchPin, 1);
  }

Ich hoffe jemand kann mit meinem Code was anfangen, und mir einen heißen Tipp geben warum das nicht so klappt. Der Arduino ist jedenfalls schnell genug, mit ShiftPWM gehts ja auch (Ohne SPI!) Vielleicht hab ich auch nur einen Knoten im Kopf ;) Ich sollte jetzt schlafen gehen, vielleicht klappts morgen. Ich bedanke mich schonmal für jeden sachdienlichen Hinweis :) Matthias

Ein paar Kommentare im Code wären gut. Was macht z.B. Timer1.initialize(100)?

Bei

   digitalWrite(dataPin, pinState);
   digitalWrite(clockPin, 1);
   digitalWrite(dataPin, 0);

stört mich das letzte digitalWrite, zumindest ist es überflüssig.

Das letzte digitalWrite hab ich aus den den Beispielen mitübernommen, im Original stand im Kommentar "zero the data pin after shift to prevent bleed through". Ob das wirklich nötig ist weiß ich nicht, aber es stört doch sicher nicht ;)

Timer1.initialize(100) initialisiert die Timer1-lib mit einem Wert von 100 micros, mit dem Interrupt dazu wird dann praktisch alle 100 micros die pulseme() aufgerufen.

100 micros ergibt bei 16MHz 1600 Takte, ich dachte eigentlich dass das reichen sollte. Bei 100Hz Wiederholrate und 100 möglichen Helligkeitsabstufungen bräuchte ich die Geschwindigkeit ja, oder hab ich einen Denkfehler?

Edit: Ich hab jetzt den Timer1 wieder rausgeworfen und arbeite wieder mit micros(). Da weiß ich besser was ich tue ^^

int latchPin = 2;
int clockPin = 3;
int dataPin = 4;

int i=0;
int pinState;
int this_led = 0;
byte state_step[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0};
byte step_order[16] = {7,8,9,10,11,12,13,14, 0,1,2,3,4,5,6,15};
byte active_led = 15;
unsigned long interval_act = 500; // millis()
unsigned long last_act = 0;
int dimmer_step = 0;
unsigned long interval_pulse = 100; // micros()
unsigned long last_pulse = 0;

void setup()
  {
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
  }
  
void loop()
  {

  if(millis() - last_act >= interval_act)
    {
    state_step[active_led] = 0;
    if(active_led >12) {active_led = 0;}
    else               {active_led++;}
    if(state_step[active_led] >= 1) {state_step[active_led] = 0;}
    else                            {state_step[active_led] = 80;}
    last_act = millis();
    }

  if(micros() - last_pulse >= interval_pulse)
    {
    shiftMe();
    if(dimmer_step >= 100)
      {
      dimmer_step = 0;
      }
    else
      {
      dimmer_step++;
      }
    last_pulse = micros();
    }

  }

void shiftMe()
  {
  digitalWrite(latchPin, 0);

  digitalWrite(dataPin, 0);
  digitalWrite(clockPin, 0);

  for (i=0; i<16; i++)
    {
    digitalWrite(clockPin, 0);
    this_led = step_order[i];
    if (state_step[this_led] > dimmer_step)
      {
      pinState= 1;
      }
    else
      {
      pinState= 0;
      }

    digitalWrite(dataPin, pinState);
    digitalWrite(clockPin, 1);
    digitalWrite(dataPin, 0);
    }

  digitalWrite(clockPin, 0);
  digitalWrite(latchPin, 1);
  }

Interessant ist, wenn ich den Helligkeitswert bei state_step[active_led] = 80; anpasse, leuchtet auch die LED entsprechend, also bei einem Wert von 10 blitzt sie nur kurz auf, und bei 90 bleibt sie fast durchgehend an. Das Prinzip scheint zu funktionieren, aber halt alles zu langsam. Irgendwo hab ich einen Haken drin. Wenn ich unsigned long interval_pulse = 100; anpasse, ändert sich auch die Flackerfrequenz, so we es sein sollte. wenn ich den Wert auf 50 ändere, wird die ganze Sache schneller, also hab ich an sich noch Leistungsreserven, daran kann es also nicht liegen (Wenn ich 10 einstelle wirds zu viel, dann ist das Blinken schlagartig langsamer). Theoretisch sollte aber bei einem Wert von 100 die LED 100x pro Sekunde an und wieder aus gehen... bei einem Wert von 50 sogar 200x. Tatsächlich schafft sie geschätzte 5-10x...

100µs bei 16MHz gibt bei mir 160 auch 1600 Takte - sorry für den Rechenfehler. Vielleicht solltest Du da nochmal dran drehen?

DrDiettrich: 100µs bei 16MHz gibt bei mir 160 Takte. Vielleicht solltest Du da nochmal dran drehen?

Also laut meiner Rechnung:

16MHz entspricht 16000000 Takte/Sekunde 1 Sekunde entspricht 1000000 Microsekunden 16000000 / 1000000 = 16, d.h. 16 Takte/Microsekunde 16 * 100 = 1600, also 1600 Takte/100 Microsekunden

Oder hab ich da einen Denkfehler?

Hallo,

100µs * 1000 / 62,5ns = 1600 Takte :)

Aber ich denke du überschätzt den 8Bit µC in seiner Rechenleistung. 1600 Takte sind für etwas rechnen schnell verbraucht. Deine digitalWrites kosten je ca. 4µs. Das könntest du noch mit direkten Portzugriffen optimieren. Bei Zeitkritischen Sachen ist das sowas mindestens erforderlich. http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial

Vielleicht kommt er einfach nicht hinterher momentan, weil deine ISR zu kurz ist. Fang mal langsamer an und schau bis er sich "überschlägt".

Grrr, so leicht kann man sich verrechnen :-(

Irgendwie war ich auf 100kHz fixiert, und das ist nach meinen Experimenten so ziemlich das Maximum, was man mit fast leeren ISR und loop() erreichen kann. Dann wird es aber auch mit den 10kHz (100µs) knapp, mit denen die Anzeige aktualisiert werden soll. Deshalb bleibe ich dabei, wie auch Doc_Ardino vorschlägt, die Schlagzahl zumindest testhalber zu verringern.

hi,

also wenn Du die shiftPWM nicht verwenden willst (was ich nicht verstehe, aber akzeptiere), mußt Du über optimierungen nachdenken. eine möglichkeit wäre, einen port mit mindestens 6 pins zu verwenden und diese direkt per port-manipulation zu schalten. also je 3 pins für einen 74HC595, die register also nicht "hintereinander" zu beschicken, sondern parallel. von dem vorteil, daß alles doppelt so schnell geht abgesehen, ist digitalWrite eben langsam und eigentlich auch leicht zu umgehen.

ich muß aber nochmal auf die shiftPWM zurückkommen: es ist einerseits lobenswert, daß Du etwas "eigenes" bauen willst, aber Dein digitalWrite zeigt, daß Du nicht genug weißt (noch nicht) und deshalb vielleicht doch über den gebrauch der library nachdenken solltest.

oder alternativ: beschäftige Dich mit der library, und wenn Du verstehst, was sie macht, DANN bau was eigenes. wäre zumindes mein ansatz. sieh' sie als "beispiel", von dem Du lernst.

gruß stefan

PS.: hab' ich gerade in einem anderen thread gelesen:

Guck mal bei den Aquarianern, die machen sowas auch. Man muß ja nicht jedesmal das Fahrrad neu erfinden, es langt, wenn man sich Sattel und Lenker passend einstellt ;)

OK - ich hab eine Anpassung gemacht...

die shiftMe sieht jetzt folgendermaßen aus:

void shiftMe()
  {
  PORTD &= ~(1< dimmer_step)
      {
      PORTD |= (1<

(Die PIN_LATCH usw. sind per #define als PD2 usw definiert)

Jetzt hab ich die intervallzeit auf 10micros runtergedreht und hab völlig flackerfrei gedimmte LEDs! :o Ich hätte nie gedacht dass digitalWrite() so langsam ist! Vielen dank,das hat mir jetzt auf jeden Fall weitergeholfen :) In den nächsten Schritten werd ich mir wohl anlesen müssen, wie genau sowas: PORTD |= (1<

Ich seh schon, ich werd mich da wohl noch mit den tiefergehenden Dingen befassen müssen...

wie genau sowas: PORTD |= (1<

Tu du das!

Tipp: Die erste Vorbereitung ist so OK!

PORTD &= ~(1<

Aber danach ist der Zustand ja bekannt, dann lässt sich auch dieses

    PORTD &= ~(1<

so umformen

    PIND = (1<

Nochmal:

//PORTD |= (1<

Das halbiert die Ausgabezeiten nochmal.

Siehe auch: Schnelle Ein-/Ausgabe

Super, vielen Dank :) Das werd ich wohl nutzen ;)