Präzise Pulse aus Arduino Leonardo

OP #7395414
Lesenswert?

Hallo zusammen,

ich suche eine Möglichkeit um bei einem Druck auf entsprechende Taster folgende Pulsformen an einem Ausgang eines Leonardos zu erzeugen:

Taster 1: 2us off, 198us on, 2us off usw..

Taster 2: 40us off, 160us on, 40us off usw..

Taster 3: 1ms off, 99ms on, 1ms off usw..

Taster 4: dauerhaft off

Kein Taster gedrückt: dauerhaft on

Ich habe mich nun schon ein bisschen zu fast PWM eingelesen und hatte irgendwo einen Verweis auf das TOP-Register der Timer zum Erreichen spezieller PWM-Frequenzen gefunden. Allerdings war das soweit ich weiß auf den Arduino Uno mit dem Atmega328 statt dem Leonardo mit dem 32u4 bezogen und der Link ist leider unauffindbar..

Naja, wenn ich mich richtig entsinne, müsste ich für

Taster 1 das PWM Signal invertieren, die 16MHz durch 8 teilen, 4 Takte "on" und das TOP-Register auf 400 stellen

Taster 2 das PWM Signal invertieren, die 16MHz durch 8 teilen, 80 Takte "on" und das TOP-Register auf 400 stellen

Taster 3 das PWM Signal invertieren, die 16MHz durch 64 teilen, 250 Takte "on" und das TOP-Register auf 25000 stellen

Taster 4 den Ausgang dauer-LOW setzen

und ansonsten den Ausgang dauer-HIGH setzen

Für Taster 1 und 2 bräuchte ich dann mindestens einen 9bit-Timer, für Taster 3 einen mit mindestens 15 Bit. Zum Glück enthält der 32u4 zwei 16bit-Timer, von dem einer aber wohl auch von der Arduino-Library genutzt wird, was dank Timer-Interrupts zu Problemen führen könnte?

Da ich leider absolut keine Ahnung von den entsprechenden Registern habe, könnte mir jemand einen passenden Code in Arduino schreiben oder mir entsprechende Hilfestellung leisten? Der Leonardo muss nichts anderes tun, aber die Timings müssen so präzise wie nur irgend möglich sein.

Danke und beste Grüße

Dominic

#7395429
Lesenswert?

Brüno schrieb:

Zum Glück enthält der 32u4 zwei 16bit-Timer, von dem einer aber wohl auch von der Arduino-Library genutzt wird, was dank Timer-Interrupts zu Problemen führen könnte?

Es gibt nicht "die Arduibo-Library". Welche Timer benutzt werden, hängt von den von dir in deinem Programm verwendeten Libraries ab.

Wie genau müssen die angegebenen Zeiten eingehalten werden?

#7395495
Lesenswert?

Solche Geschichten bedingen ein gnadenloses Einarbeiten in die Register, denn mit einer library hat schon jemand vorgedacht, was der Benutzer denn allenfalls moechten koennte. Du wuerdes dann alls diese Modi durchprobieren, und allenfalls feststellen, dass ein Mode knapp so waere wie gewuenscht, und mit zwei Tagen Verzoegerung das Kapitel zu den Registern durchlesen.

Und vergiss mal Pulse aufgrund von Tasten... die koennen Prellen, und was soll dann geschehen, resp funktioniert das Konzept dann immer noch ?

#7395665
Lesenswert?

Wenn die Taster nur zum Einschalten der Impulsfolge dienen, ists kein Problem. Soll aber der Impuls direkt vom Taster ausgelöst werden, würde ich durch den Taster ein externes Flipflop triggern, was dann quasi vom 1. Prellschlag des Tasters gesetzt wird und vom Prozzi dann zurückgesetzt wird. Du mußt aber die entsprechenden Interruptroutinen selbst schreiben und optimieren.

mfg

#7395666
Lesenswert?

Hallo,

ich sehe da stumpf 2 Möglichkeiten, wenn die Tasten nicht parallel genutzt werden sollen.

Entweder den CTC Timer Mode nutzen und die Pulslängen aufaddieren lassen und entsprechend den Pin umschalten. Wobei das Pin schalten direkt im zugehörigen Register erfolgen muss, digitalWrite ist mit 4µs Verzögerung zu langsam bei deinen Anforderungen.

Oder wenn es blockieren darf, dann jeweils eine Sequence schreiben mit for Schleifen und NOPs. Entsprechend geduldig anpassen bis die Zeiten stimmen.

#7395825
Lesenswert?

Hallo,

Georg hast recht, mit PWM geht das, mittels Timer Hardwarepin ist das absolut präzise. Hatte nicht gleich geschnallt das die Pulszeiten gleich bleiben.

Habe das einmal ungetestet für die erste Pulsfolge geschrieben. 2µs + 198µs = 200µs Periodendauer = 5000Hz Die Formel für Phase Correct Mode stellt man nach TOP um und erhält für Prescaler 1 die Einstellung 1600. 200µs = 1600 TOP Demzufolge entsprechen 2µs = 16 für Compare

Jetzt kannst du dir für den Timer verschiedene Settings anlegen und entsprechend je Taster aufrufen usw.. Den Rest kannste bestimmt selbst.

1
const byte pulsPin {10};   // OC1B bzw. PB6 nicht invertiert, IRFxxxx Mosfet 
2
  
3
void setup (void)
4
{ 
5
  initTimer1();
6
  digitalWrite(pulsPin, HIGH);    // OC1B bzw. PB6 nicht invertiert
7
  pinMode(pulsPin, OUTPUT); 
8
  changeTimer1(16, 1600);
9
}
10

11
void loop (void)
12
{         
13
 
14
} 
15
 
16
void changeTimer1 (const uint16_t duty, const uint16_t periode)  
17
{         
18
  stopTimer1();
19
  TCNT1 = 0;        // initialize counter value to 0
20
  OCR1A = periode;  // TOP Wert bestimmt Auflösung und mit Prescaler den PWM Takt bzw. die Periode
21
  OCR1B = duty;     // Pulsweite, OCR1B <= OCR1A 
22
  runTimer1(1);     // Prescaler 1
23
}  
24

25
void initTimer1 (void) // PWM, Phase Correct, Mode 11
26
{
27
  TCCR1B = 0;    // Reset, stop timer first
28
  TCCR1A = 0;    // Reset
29
  TIMSK1 = 0;    // Reset (disable Timer Compare Interrupts)
30
  TCNT1  = 0;    // initialize counter value to 0
31
  TCCR1A = _BV(COM1B1) | _BV(WGM11) | _BV(WGM10);  
32
  TCCR1B = _BV(WGM13);                             
33
}
34

35
void runTimer1 (const unsigned int prescaler)
36
{
37
  // set new Prescaler Clock Select Bits
38
  switch (prescaler) {
39
    case    1 : TCCR1B |= _BV(CS10);              break;
40
    case    8 : TCCR1B |= _BV(CS11);              break;
41
    case   64 : TCCR1B |= _BV(CS11) | _BV(CS10);  break;
42
    case  256 : TCCR1B |= _BV(CS12);              break;
43
    case 1024 : TCCR1B |= _BV(CS12) | _BV(CS10);  break;
44
    default :   break;  // otherwise timer remains stopped
45
  }
46
}
47

48
void stopTimer1 (void)
49
{
50
  TCCR1B &= ~( _BV(CS12) | _BV(CS11) | _BV(CS10) );
51
}
Angehängte Dateien:
OP #7395956
Lesenswert?

Vielen Dank für das Schreiben des Beispielcodes!

Die Taster sollen nur zur Auswahl des Ausgangssignals dienen. Die entprelle ich mit mehrfachem Auslesen mit kurzem Delay und priorisiere sie sodass sie sich nicht gegenseitig überschreiben.

Veit D. schrieb:

OC1B bzw. PB6 nicht invertiert

Bedeutet das, dass der Ausgang während der vorgegebenen Pulsweite high ist? Und wenn ja, wie ändere ich das?

Ich werde das morgen einmal testen! :)

#7396039
Lesenswert?

Hallo,

ja genau, du möchtest es gedreht haben, auch kein Problem. Führen wir dich mal ans Manual heran.

Kapitel 14 - Timer 1. Table 14-3. Es muss das Bit COM1B0 zusätzlich gesetzt werden. Die erste '1' steht für Timer 1 >> 'n' 'B' für genutzten Kanal, es soll ja Pin OC1B schalten, deswegen auch OCR1B als Compare-Match-Register. '0/1' am Ende für das Bit

In initTimer1 folgende Zeile ändern

1
  TCCR1A = _BV(COM1B1) | _BV(COM1B0) | _BV(WGM11) | _BV(WGM10);

Dann sollte das invertiert laufen. Für 100ms Periodendauer wirst du den Prescaler auf 64 ändern müssen. Baue die Funktion "changeTimer1" auf einen 3. Parameter um und übergebe den passenden Prescaler gleich mit. Dann ist jede Änderung weiterhin nur mittels Parameterübergabe lösbar. Ansonsten erstmal rumspielen, kräftig ausprobieren, auch mal im Manual lesen und mit allen warm werden.

OP #7396729
Lesenswert?

Leider waren die Tests heute nicht erfolgreich. Die einzige Zeile, die einen Einfluss auf den Ausgangszustand hat, ist

digitalWrite(pulsPin, HIGH);

Da millis und andere Arduino- Bibliotheken wohl die ersten Timer nutzen, habe ich den Code dann auf Ausgang 6 (PC6) und Timer 3 umgeschrieben. Auch das war leider erfolglos.

Leider finde ich auch keinen Beispielcode online. Wie kann ich weiter vorgehen?

Beste Grüße

Dominic

#7397281
Lesenswert?

Hallo,

bin den Sketch nochmal durchgegangen, ich kann erstmal kein Problem feststellen. Teste mal wie gesagt den IDE Blinksketch, da müßte die Board Led 13 blinken. Danach kannste Pin 10 testen ob der Pin blinkt. Wird fehlerfrei kompiliert? Wird das Programm übertragen? Irgendwelche Fehlermeldungen? In der IDE > Datei > Voreinstellungen:

Ausführliche Ausgabe während: beide Haken rein alle Compiler Warnungen einschalten

millis nutzt Timer0. Hat damit nichts zu tun.

OP #7397421
Lesenswert?

Vielen Dank nochmal für deine Unterstützung! Auch bei mir hat es nach einem Umsortieren im setup geklappt.

Mit folgendem Code tut der Leonardo nun das, was er soll:

1
const byte pulsPin {10};   // OC1B bzw. PB6
2
const byte IN1     {0};
3
const byte IN2     {1};
4
const byte IN3     {2};
5
const byte IN4     {3};
6

7
void setup() {
8
  // put your setup code here, to run once:
9
  pinMode(pulsPin, OUTPUT);
10
  digitalWrite(pulsPin, HIGH);    // OC1B bzw. PB6
11
  
12
  pinMode(IN1, INPUT_PULLUP);
13
  pinMode(IN2, INPUT_PULLUP);
14
  pinMode(IN3, INPUT_PULLUP);
15
  pinMode(IN4, INPUT_PULLUP);
16

17
  pinMode(13, OUTPUT);
18
  digitalWrite(13, HIGH);         // VCC close to pulsPin for indicator LED active LOW
19

20
}
21

22
void loop() {
23
  // put your main code here, to run repeatedly:
24
  if(!digitalRead(IN1))
25
  {
26
    delay(10);
27
    if(!digitalRead(IN1))
28
    {
29
      initTimer1(16, 1600, 1);
30
      while(!digitalRead(IN1))
31
      {
32
        
33
      }
34
    }
35
  }
36
  
37
  else if(!digitalRead(IN2))
38
  {
39
    delay(10);
40
    if(!digitalRead(IN2))
41
    {
42
      initTimer1(32, 1600, 1);
43
      while(!digitalRead(IN2))
44
      {
45
        
46
      }
47
    }
48
  }
49

50
  else if(!digitalRead(IN3))
51
  {
52
    delay(10);
53
    if(!digitalRead(IN3))
54
    {
55
      initTimer1(125, 12500, 64);
56
      while(!digitalRead(IN3))
57
      {
58
        
59
      }
60
    }
61
  }
62

63
  else if(!digitalRead(IN4))
64
  {
65
    delay(10);
66
    if(!digitalRead(IN4))
67
    {
68
      stopTimer1();
69
      digitalWrite(pulsPin, LOW);
70
      while(!digitalRead(IN4))
71
      {
72
        
73
      }
74
    }
75
  }
76
  
77
  stopTimer1();
78
  digitalWrite(pulsPin, HIGH);
79
  delay(10);
80
}
81

82
void initTimer1 (const uint16_t duty, const uint16_t periode, const uint16_t prescaler)
83
{
84
  stopTimer1();
85
  TCCR1A = _BV(COM1B0) | _BV(COM1B1) | _BV(WGM11) | _BV(WGM10);  
86
  TCCR1B = _BV(WGM13);
87

88
  // set new Prescaler Clock Select Bits
89
  switch (prescaler) {
90
    case    1 : TCCR1B |= _BV(CS10);              break;
91
    case    8 : TCCR1B |= _BV(CS11);              break;
92
    case   64 : TCCR1B |= _BV(CS11) | _BV(CS10);  break;
93
    case  256 : TCCR1B |= _BV(CS12);              break;
94
    case 1024 : TCCR1B |= _BV(CS12) | _BV(CS10);  break;
95
    default :   break;  // otherwise timer remains stopped
96
  }
97

98
  OCR1A = periode;  // TOP Wert bestimmt Auflösung und mit Prescaler den PWM Takt bzw. die Periode
99
  OCR1B = duty;     // Pulsweite, OCR1B <= OCR1A 
100
}
101

102
void stopTimer1 ()
103
{
104
  TCCR1B = 0;    // Reset, stop timer first
105
  TCCR1A = 0;    // Reset
106
  TIMSK1 = 0;    // Reset (disable Timer Compare Interrupts)
107
  TCNT1  = 0;    // initialize counter value to 0
108
}

Allerdings ist das Konstrukt mit den while-Schleifen nicht wirklich elegant. Wie würde man so etwas lösen wenn der MC während dem Warten auf ein Loslassen etwas anderes tun soll?

[Mod: C-Formatierung korrigiert]

#7397445
Lesenswert?

Hallo,

schön zu hören das es funktioniert.

Zur Frage. Lässt sich mit Blockadefreier Programmierung lösen. Schau dir das IDE Bsp. BlinkWithoutDelay an. Man verwendet den Arduino typischen Millisekundenzähler den man mit millis() jederzeit abfragen kann. Vergleiche das mit Differenzzeitenbildung mit der Armbanduhr. Damit kann man 24h bzw. nur das Ziffernblatt betrachtet max. 12h an Zeitdifferenzen bilden. Also wieviel Zeit vom letzten draufgucken vergangen ist. millis() zählt 32Bit und das reicht für Differenzen von 48 Tagen. Man muss nur für alle Variablen die mit Zeiten und millis zu tun haben unsigned long bzw. uint32_t verwenden. Dann gibt es keine Probleme.

Und wegen deinem Spaghetti Code, so nennt man das mit deinen 4 Einzeltaster, man hat Code Dopplungen die alle gepflegt werden müssen, kannst du dich mit Arrays und for Schleifen befassen. Ggf. schon mit struct. Motto: Gleiche Dinge zusammenfassen.

Übrigens wird Arduino in C++ programmiert. Falls du in Büchern nachschlagen möchtest. Da reicht erstmal ein C++11 Buch.

Beitrag #7397461 wurde von einem Moderator gelöscht.
OP #7397884
Lesenswert?

Heute habe ich mir den Code mit dem Oszilloskop angesehen. Dabei viel als erstes auf, das der Puls immer mit einer Periodendauer Verzögerung ausgelöst wurde. Das konnte ich über Umschalten in den Fast PWM Mode 15 lösen.

Danach habe ich noch ziemlich viel rumgefummelt, weil ich sporadische Pulse im Bereich von 250ns während initTimer und diverse andere Problemchen hatte und bin nun beim Code im Anhang angelangt.

Wie man sieht, werden in initTimer1 während D0 am Oszilloskop high ist diverse Variablen gesetzt, während D1 die Compare Output (OC1B) Options gesetzt und während D2 der Timer gestartet.

Bei einem geringen Duty Cycle wie in Case 1 geht das zu 99% problemlos und der Ausgang geht nach dem Starten des Timers mit der richtigen Pulsdauer auf low (Anhang O1.png).

Wenn der Duty Cycle aber deutlich höher wird (Case 2), geht der Ausgang bei rund 50% der Tasterbetätigungen bereits während dem Setzen der Compare Output (OC1B) Options auf low und der erste Puls ist zu lang (Anhang O2.png).

Verschiebe ich das Setzen der Compare Output (OC1B) Options hinter das Starten des Timers, wird der erste Puls komplett weggelassen (Anhang O3.png) oder der Ausgang geht noch während dem Setzen los und ist etwas zu kurz (Anhang O4.png).

Woher kommt das? Wie überrede ich den Timer dazu, diese beiden Fehlerfälle nicht zu zeigen?

Ich habe es schon mit [C] PORTF = 2; //D1 high TCNT1 = periode - 10 ; TCCR1A |= _BV(COM1B0) | _BV(COM1B1); [/C] nach dem Starten des Timers versucht um immer die Startbedingung zu treffen, was das Verschlucken des ersten Pulses zwar beseitigt, aber dann ist bei etwa 50% der Betätigungen der erste Puls etwas Verzögert, was zu erwarten und verschmerzen ist (Anhang O5.png) und bei den anderen 50% zu lang (Anhang O6.png), was vermieden werden muss.

Angehängte Dateien:
#7398009
Lesenswert?

Hallo,

@ Falk: Ich habe gestern nicht gesehen das die Änderungen der Funktionen schon vom TO kamen. Entschuldigung.

@ TO: Dieses Zusammengewürfel von unterschiedlichen Funktionen macht man nicht. Der Code wird unwartbar. Außerdem erwartet man das die Funktion das macht was ihr Name aussagt. Nicht weniger aber auch nicht mehr. Jetzt haste den Salat mit den ewigen Inits. Bevor du die Funktionen verbiegst solltest du den Timer verstehen. Ich habe bewusst den Mode 11 ausgewählt. Hat Doppel Bufferung der Register und macht keinen Mist wie Fast-PWM. Fast-PWM erzeugt mit Compare 0 immer Spikes. Außerdem macht es gerade Sinn Doppel Bufferung zu verwenden, damit er zum richtigen Zeitpunkt seine Updates macht. Warum das jetzt im Tasterbetrieb stören sollte wenn es noch eine Periode dauert weiß ich nicht. Da gibt es anderes Verbesserungspotential.

Bspw. Wenn es dauerhaft High sein soll einfach Compare auf 0. Wenn es dauerhaft Low sein soll einfach Compare = Periode. Allein damit ist sichergestellt das der letzte Puls Dank Doppel Bufferung sauber ausgeführt wird.

Andere Idee. Damit das umschalten der Pulsfolgen harmonisch erfolgt, setzt man Compare auf 0, vergleicht den Counter mit dem alten Compare, wenn >= stoppt man den Timer. Oder man wartet eine komplette Periode. Bis hierher ist sichergestellt das der letzte Puls immer sauber ausgeführt wird und das Signal danach auf High wechselt und bleibt. Im Schatten dessen Compare und Top ändern, Counter nullen und Prescaler setzen.

Dafür kann man sich eine schöne State Maschine bauen.

Du solltest das Timer Kapitel lesen und beim rumspielen verstehen lernen.

OP #7399613
Lesenswert?

Veit D. schrieb:

Hat Doppel Bufferung der Register und macht keinen Mist wie Fast-PWM. Fast-PWM erzeugt mit Compare 0 immer Spikes.

Compare (ich vermute du meinst damit OCR1B) ist wenn der Spike auftritt nie 0 und wird erst nach OCR1A gesetzt. Macht man es andersrum, kriegt man massive Probleme.

In Timer Mode 11 kommen ebenfalls während dem Setzen von COM1B0 und COM1B1 sporadische Pulse aus dem Ausgang, siehe Anhang O7.png.

Wenn ich einen Duty Cycle von 25% auswähle, kommt der zusätzliche Puls in Mode 11 wie auch der zu lange Puls in Timer Moder 15 mit einer Wahrscheinlichkeit von ~25%. Das liegt wohl am Zustand von OC1B beim Reseten des Timers. Das habe ich über ein force match mit ausgeschaltetem Ausgang in resetTimer1 lösen können. Den Code gibt's im Anhang.

Nun tut die Kiste was sie soll, siehe Anhang O8.png.

Angehängte Dateien:
#7399672
Lesenswert?

Brüno schrieb:

Den Code gibt's im Anhang.

Naja. So einen Käse macht man nicht!

1
initTimer1(32-1, 3200-1, 1); break;

Die -1 schreibt man in die Funktion, dort gehört sie hin!

1
PORTF  = 32;                          // D3 high

Das schreibt man auch nicht so, vor allem da man es noch kommentieren muss. Zumal dein Kommentar und die C-Anweisung NICHT übereinstimmen! D3 wäre 8!

Eine selbsterklärende Schreibweise ist das Mittel der Wahl. Außerdem setzt du damit ALLE Bits in dem Port, was in den meisten Fällen nicht gewünscht ist. Siehe Bitmanipulation.

1
PORTF  |= (1<<PF5);
1
  DDRB &= 0 << 6;                       // Disable OUTPUT B6

Falsch! Das macht nicht das, was der Kommentar sagt! Siehe Bitmanipulation. Eher so.

1
  DDRB &= ~(1 << PB6);                       // Disable OUTPUT B6
1
  TCCR1A |= _BV(COM1B0) | _BV(COM1B1);  // Set at match
2
  TCCR1C = _BV(FOC1B);                  // Force match

Set a match ist irreführend. Es muss set on match heißen. Und hier ist die Zuweisung |= zwar möglich, aber nicht nötig und auch eher ungünstig. Das passt nur, weil vorher das Register komplett auf 0 gesetzt wurde. Hier wäre eine einfache, komplette Zuweisung mit = sinnvoll.

Allerdings ist dein Konzept nicht korrekt. Wenn du TCC1A auf 0 setzt, wird das IO-Pin vom Timer entkoppelt und das Bit aus PORTB auf das IO-Pin geschaltet. Das wird in deinem Programm aber nie beschrieben, ist also ab Reset 0. Damit geht dein Ausgang auf LOW. Ist das gewollt? Eher nicht. Deine Signale sollen doch meist HIGH sein. Wenn man es richtig machen will und sicher ohne Glitch, sprich Störpulse umschalten will, muss man den Zähler synchronisiert anhalten. D.h. man darf ihn nur anhalten, wenn man weiß daß sich der Zählerstand in einem unkritischen Bereich befindet, wo sich der Ausgang nicht ändert. Bei deinen Einstellungen wäre das immer im oberen Bereich des Zählers, aber nicht ganz oben. Sagen wir zwischen 75 und 95%. Etwa so. Die Konstanten mit Endung L sind nötig, damit die Rechnung in 32 Bit gemacht wird, wegen des Überlaufs.

1
uint16_t min = (OCR1A * 96L) >> 7;   // 75%
2
uint16_t max = (OCR1A * 115L) >> 7;   // 90%
3
while ((TCNT1 < min) || (TCNT1 > max); // warte auf Fenster
4
TCCR1B = 0;  // Timer stop, output HIGH

Dann kann man auch mittels des richtigen IO-Modes

1
TCCR1A = _BV(COM1B1);
2
TCCR1C = _BV(FOC1B);

den Pin ohne Glitch auf LOW setzen und dort lassen, bis die neue Konfiguration erfolgt.

#7399761
Lesenswert?

Hallo,

@ TO: wenn du die Timereinstellungen so verbiegst kann das nichts werden.

Es tut mir förmlich weh sowas anzuschauen. Willkürlicher Reset ohne jede Kontrolle über den Timerzustand. Irgendwelche wilden Subtraktionen.

1
switch (taste) {
2
      case 1:  initTimer1(32-1, 3200-1, 1); break;
3
      case 2:  initTimer1(640-1, 3200-1, 1); break;
4
      case 3:  initTimer1(250-1, 25000-1, 64); break;
5
      case 4:  resetTimer1(); digitalWrite(pulsPin, LOW); break;
6
      default: resetTimer1(); digitalWrite(pulsPin, HIGH); break;
7
    }

Überlege doch einmal. Der Timer befindet sich in irgendeinem Zustand den man nicht kennt. Jetzt muss man ihn zum nächstmöglichen Zeitpunkt sicher anhalten. Mit willkürlichen sofortigen Stopp wird das nichts. Nutze die Möglichkeiten die der Timer mitbringt.

Ich hatte es schon beschrieben was du machen kannst/musst. Das erfordert das Verständnis vom Timer und dessen Arbeitsweise. Force Match usw. braucht man nicht. Pin IO extra schalten muss man auch nicht.

Wofür ist das überhaupt? Was machst du mit den Pulsen?

Wegen Fast-PWM und Spikes. Haste falsch verstanden. Vergiss einmal alles mit deinem Programm. Konfiguriere Fast-PWM, irgendeine Periodendauer und Compare auf 0. Schau dir das Signal an.

Wegen dem Umschalten. Ich bin das jetzt Hundert mal gedanklich durchgegangen. Wenn man das umschalten zwischen den Pulsformen mit dem Zwischenschritt Compare auf 0 macht und zusätzlich danach TCNT auf noch aktuelle Pulslänge abwartet kann nichts schief gehen. Dabei spielt es auch keine Rolle ob TCNT hoch oder runter zählt. Danach ist das Signal ohne Faxen sicher auf Dauer-High und man kann den Timer gefahrlos stoppen, Compare + Top + Prescaler umkonfigurieren, starten. Die Automatik mit dem Update mittels gepuffertem Register nimmt einem alle Probleme ab.

OP #7399874
Lesenswert?

Falk B. schrieb:

Naja. So einen Käse macht man nicht!

initTimer1(32-1, 3200-1, 1); break;

Die -1 schreibt man in die Funktion, dort gehört sie hin!

Da hast du Recht mit, habe ich korrigiert.

PORTF = 32; // D3 high

Das schreibt man auch nicht so, vor allem da man es noch kommentieren muss. Zumal dein Kommentar und die C-Anweisung NICHT übereinstimmen! D3 wäre 8!

Eine selbsterklärende Schreibweise ist das Mittel der Wahl. Außerdem setzt du damit ALLE Bits in dem Port, was in den meisten Fällen nicht gewünscht ist. Siehe Bitmanipulation.

Das bezieht sich auf D3 am Oszilloskop und war nur für die Veranschaulichung im Code. Da der PORTF im Moment für nichts anderes genutzt wird, war das die einfachste und schnellste Möglichkeit um an den entsprechenden Stellen die Ausgänge zu setzen und wieder zurück zu setzen.

Es muss set on match heißen. Und hier ist die Zuweisung |= zwar möglich, aber nicht nötig und auch eher ungünstig. Das passt nur, weil vorher das Register komplett auf 0 gesetzt wurde. Hier wäre eine einfache, komplette Zuweisung mit = sinnvoll.

Ist angepasst, danke für den Hinweis.

Allerdings ist dein Konzept nicht korrekt. Wenn du TCC1A auf 0 setzt, wird das IO-Pin vom Timer entkoppelt und das Bit aus PORTB auf das IO-Pin geschaltet. Das wird in deinem Programm aber nie beschrieben, ist also ab Reset 0. Damit geht dein Ausgang auf LOW. Ist das gewollt?

Auch damit hast du Recht, das Bit wird im setup auf High gesetzt, aber ob es im weiteren Programmverlauf auch High bleibt, ist dem Zufall überlassen. Das habe ich dementsprechend geändert indem ich den Ausgang während jedem Reset auf High setze.

Dass der Puls unterbrochen wird wenn der Taster losgelassen wird, ist gewollt. Wenn man mal wirklich lange Pulse einstellt, aber der Taster früher losgelassen wird, ist das in der Anwendung ein ziemlich praktisches Feature.

OP #7400366
Lesenswert?

Einen Match zu forcen während der Timer gestoppt aber Compare Output noch an ist, führt auch dazu, dass der Ausgang sauber auf HIGH geht. Dementsprechend muss ich nicht mehr PB6 kurzzeitig auf Input umschalten.

Ich wollte nun noch eine Begrenzung der Pulsanzahl pro Knopfdruck einbauen. Dank Timerinterrupt sollte das ja eigentlich nicht so schwer sein. Leider braucht es wiederholbare 2,6µs vom Erreichen von OCR1B bis zum Auslösen von ISR(TIMER1_COMPB_vect) [D0 Oszilloskop high], was in Case 1 (5µs LOW, 10µs Periode, 2 Pulse) noch einen kleinen dritten Puls bis der Match geforced wird [D2 Oszilloskop high] ergibt. Kann man das noch schneller hinbiegen?

Angehängte Dateien:
#7400377
Lesenswert?

Brüno schrieb:

Einen Match zu forcen während der Timer gestoppt aber Compare Output noch an ist, führt auch dazu, dass der Ausgang sauber auf HIGH geht. Dementsprechend muss ich nicht mehr PB6 kurzzeitig auf Input umschalten.

Sagte ich das nicht bereits?

Ich wollte nun noch eine Begrenzung der Pulsanzahl pro Knopfdruck einbauen. Dank Timerinterrupt sollte das ja eigentlich nicht so schwer sein. Leider braucht es wiederholbare 2,6µs vom Erreichen von OCR1B bis zum Auslösen von ISR(TIMER1_COMPB_vect) [D0 Oszilloskop high],

Dann schreib das auch so in deinen Kommentar!

was in Case 1 (5µs LOW, 10µs Periode, 2 Pulse) noch einen kleinen dritten Puls

Wo kommt plötzlich die 10us Periode her?

bis der Match geforced wird [D2 Oszilloskop high] ergibt. Kann man das noch schneller hinbiegen?

Ja, mit einer ISR in Assembler. Frag mal unseren Spezi Bernd Stein, der kennt sich damit gut aus ;-)

Dein counter muss volatile sein, sonst funktioniert das nicht so, oder nur durch Zufall. Siehe Interrupt. Muss der 16 Bit sein? Oder reichen 8 Bit? Man kann das auch mit einer ISR in C versuchen, dann muss man aber wissen was man tut! Vor allem darf man in der ISR keine Funktionen aufrufen, denn die kosten in der ISR am Anfang viel Zeit, weil viele Register gesichert werden müssen. Weiterhin muss man das Abschalten möglichst an den Anfang der ISR schreiben, logisch. Etwa so.

1
volatile uint8_t tccr1b_setup=0;
2

3
....
4
counter = 5;  // Pulse -1 
5
tccr1b_setup = (1 << CS11) | (1 << CS10);  // prescaler for timer 1
6
TCCR1B = tccr1b_setup;
7
....
8

9
// COMPB match interrupt
10
ISR(TIMER1_COMPB_vect) {                    
11
  TCCR1B = tccr1b_setup;
12
  PORTF |= (1<<PF0);     //D0 am Oszi high
13
  if (counter !=0) {
14
    counter--;
15
    if(counter == 0) {
16
      tccr1b_setup = 0;  // timer stop in next ISR
17
    }
18
  }
19
  PORTF &= ~(1<<PF0);
20
}
OP #7400417
Lesenswert?

Falk B. schrieb:

Sagte ich das nicht bereits?

Das hatte ich wohl nicht verstanden.

Falk B. schrieb:

Wo kommt plötzlich die 10us Periode her?

Ich habe getestet, bis zu welchen Werten das noch funktioniert, bei 5µs LOW und 10µs Periode war reproduzierbar jedes mal ein dritter Puls dabei.

Falk B. schrieb:

Vor allem darf man in der ISR keine Funktionen aufrufen, denn die kosten in der ISR am Anfang viel Zeit, weil viele Register gesichert werden müssen.

Der Hinweis war Gold wert, nun dauert es nurnoch 1,5µs vom Erreichen von OCR1B bis zum Auslösen von ISR(TIMER1_COMPB_vect) und ohne die Bewegungen an Port F geht sogar 4µs/8µs problemlos.

Gehe ich richtig in der Annahme, dass dein Vorschlag nur für zwei oder mehr Pulse funktioniert? Dann würde ich mich nämlich mit meinem Code im Anhang zufrieden geben, außer jemand will sich wirklich noch an Assembler versuchen, das übersteigt meine Kenntnisse bei weitem.

Vielen Dank für eure Unterstützung, ich habe in den letzten Tagen sehr viel dazu gelernt :)

Angehängte Dateien:
Beitrag #7400485 wurde von einem Moderator gelöscht.
#7400519
Lesenswert?

Hallo,

ich verstehe eure Herangehensweise nicht mehr. Buffer-Register werden ignoriert. Keine State Maschine geschrieben um die sich hier eigentlich alles dreht. Dafür wird mit brutalsten Mitteln der Timer umgebogen. Ihr baut euch Sackgassen. Ich sehe nur noch Stückwerk was einst saubere Funktionen waren.

Ich habe getestet, bis zu welchen Werten das noch funktioniert, bei 5µs LOW und 10µs Periode war reproduzierbar jedes mal ein dritter Puls dabei.

Das wundert mich nicht. Leider liegt der Fokus auf die Probleme falsch. Da mir das Herz dabei blutet bin ich komplett raus.

OP #7400541
Lesenswert?

Veit D. schrieb:

Das wundert mich nicht.

Na dann zeig doch mal, wie du das umsetzen würdest.

Ich hätte gerne low-Pulse auf Knopfdruck mit einstellbarer Länge, Periode und optional auch Anzahl pro Knopfdruck. Wird der Taster losgelassen, soll der Ausgang wieder high werden, selbst wenn ein langer Puls oder die bestellte Pulsanzahl noch nicht vorbei sind.

Diese Aufgaben bewältigt der Code ziemlich gut.

Entschuldige meine forsche Antwort - vorallem da dein Beispielcode bei meinen Startschwierigkeiten sehr hilfreich war - aber die letzten Kommentare waren absolut nicht konstruktiv.

Naja, BTT: Bevor man OCR1A und OCR1B auf 16bit-Werte setzen kann, muss man den entsprechenden Modus auch in TCCR1B wählen. Ich habe also unter Zeile 65 noch ein TCCR1B = tccr1b_setup; eingefügt und die Zeile 85 in TCCR1B |= tccr1b_tmp; geändert.

#7400585
Lesenswert?

Veit D. schrieb:

ich verstehe eure Herangehensweise nicht mehr. Buffer-Register werden ignoriert. Keine State Maschine geschrieben um die sich hier eigentlich alles dreht. Dafür wird mit brutalsten Mitteln der Timer umgebogen. Ihr baut euch Sackgassen. Ich sehe nur noch Stückwerk was einst saubere Funktionen waren.

Was ist denn mit dir los? Nur weil nicht alles oberlehrbuchhaft nach deiner Vorstellung läuft verlierst du die Nerven?

#7400630
Lesenswert?

Hallo,

ich renne nicht den sich laufend veränderten Anforderungen hinterher. Mit sofortigen Pulsstopp und Pulszähler und vielleicht noch Pulsstopp nach Vorgabe müßte ich mein Gesamtkonzept überdenken. Logisch das nun einige Aussagen von mir nicht mehr passen. Ich ändere dafür aber nicht wild drauflos. Wie meine Lösung aussah bis zu den neuen Anforderungen kann ich dir zeigen.

Ich weiß das sich Falk mit Programmierung und Timern auskennt. Aber wir haben unterschiedliche Herangehensweisen. Grundlegend er C und ich C++. Von daher macht es keinen Sinn wenn wir uns ins Gehege kommen und du dann zwischen den Stühlen stehst. Da ziehe ich mich lieber zurück. Außerdem möchte ich mit Falk u.a. diesen Sommer noch ein Bier trinken gehen. :-)

Angehängte Dateien:
Beitrag #7400686 wurde von einem Moderator gelöscht.
#7400721
Lesenswert?

Brüno schrieb:

Es läuft nun sogar mit 2,5µs LOW und 5µs Periode, vielen Dank nochmal für die Hilfe! :)

Naja, für den Anfang und deine Ausgangsposition schon ganz gut, aber ausbaufähig.

1
TCCR1A = 0 | (1 << WGM11) | (1 << WGM10) | (1 << COM1B0) | (1 << COM1B1);

das 0 | ist Unsinn, das schreibt keiner, denn 0 | x ist x.

1
  TCCR1A = 0;                                       // Reset
2
  OCR1A  = 0;                                       // Reset periode
3
  OCR1B  = 0;                                       // Reset duty
4
  TCNT1  = 0;                                       // initialize counter value to 0
5

6
  switch (prescaler) {
7
    case    1 : tccr1b_setup |= (1 << CS10);                break;  // prescaler 1
8
    case    8 : tccr1b_setup |= (1 << CS11);                break;  // prescaler 8
9
    case   64 : tccr1b_setup |= (1 << CS11) | (1 << CS10);  break;  // prescaler 64
10
    case  256 : tccr1b_setup |= (1 << CS12);                break;  // prescaler 256
11
    case 1024 : tccr1b_setup |= (1 << CS12) | (1 << CS10);  break;  // prescaler 1024

Diese Kommentare sind ein Papagei, der alles nachplappert. Sinnlos!

1
  sei();                          // allow interrupts

Das brauchst du nicht bei Arduino, denn die Interrupts laufen beim Aufruf von Setup schon.

Ich hab deinenm Programm mal den letzten Schliff verpaßt. Damit wird es kompakter und doch besser lesbar und schneller! Das IO-Pin wird NIE vom Timer getrennt! Siehe Anhang.

Angehängte Dateien:

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren