Forum: Mikrocontroller und Digitale Elektronik Präzise Pulse aus Arduino Leonardo


von Brüno (dominic_m833)


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

: Bearbeitet durch User
von Rainer W. (rawi)


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?

von Purzel H. (hacky)


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 ?

von Oliver R. (orb)


Lesenswert?

Rainer W. schrieb:
> Wie genau müssen die angegebenen Zeiten eingehalten werden?

Brüno schrieb:
> aber die Timings müssen so präzise wie nur irgend möglich
> sein.

Schön, die Forderung bedeutet, daß die Lösung so viel Geld wie nur 
irgendwie möglich kosten wird.

von Lotta  . (mercedes)


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

von Veit D. (devil-elec)


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.

von Georg M. (g_m)


Angehängte Dateien:

Lesenswert?

Ich würde den Modus "PWM, Phase and Frequency Correct (OCRnA)" wählen, 
aber vielleicht gibt es auch andere Meinungen.

von Christoph S. (mr9000)


Lesenswert?

Ist das immer derselbe Ausgang? Was passiert, wenn zwei Knöpfe gedrückt 
werden?

Billige, schnelle Lösung: Timer mit 1 uS-Interrupt zählt Variable hoch, 
Interruptroutine setzt je nach Wert Pin hoch oder niedrig oder den Wert 
zurück.

von Veit D. (devil-elec)


Angehängte Dateien:

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
}

von Brüno (dominic_m833)


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! :)

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ja genau, du möchtest es gedreht haben, auch kein Problem.
Führen wir dich mal ans Manual heran.
- complete datasheet : 
https://www.microchip.com/en-us/product/ATmega32U4

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.

von Brüno (dominic_m833)


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

von Veit D. (devil-elec)


Lesenswert?

Hallo,

teste einmal den Blink Sketch aus den IDE Beispielen. Blinkt es?

von Veit D. (devil-elec)


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.

: Bearbeitet durch User
von Brüno (dominic_m833)


Lesenswert?

Blink wie auch ganz normales PWM über analogWrite geht problemlos.

https://arduino-projekte.webnode.at/registerprogrammierung/fast-pwm/

Hier hatte ich noch folgendes gefunden:

//PWM-Pin 9 als Ausgang definieren

  DDRB |= (1 << DDB1);

Übersetzt auf Pin 10 vom Leonardo wäre das dann DDRB |= _BV(DDB6) ?

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Hallo,

Direction macht pinMode.

Ändere das mal. Ist nur die Reihenfolge im setup geändert.
1
void setup (void)
2
{ 
3
  digitalWrite(pulsPin, HIGH);    // OC1B bzw. PB6 invertiert
4
  pinMode(pulsPin, OUTPUT); 
5
  initTimer1();
6
  changeTimer1(16, 1600);
7
}

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich habe das jetzt auf meinem Mega2560 getestet, da ist PB6 Pin 12. Man 
muss tatsächlich das initTimer1() nach pinMode ausführen. Da 
überschreibt die DDRx Einstellung die vorherige Timer-Pin Änderung. 
Hatte ich auch nicht mehr dran gedacht.

: Bearbeitet durch User
von Brüno (dominic_m833)


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]

: Bearbeitet durch Moderator
von Veit D. (devil-elec)


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.

: Bearbeitet durch User
Beitrag #7397461 wurde vom Autor gelöscht.
von Falk B. (falk)


Angehängte Dateien:

Lesenswert?

Brüno schrieb:
> Wie würde man so etwas lösen wenn der MC während dem Warten auf
> ein Loslassen etwas anderes tun soll?

Etwa so. Siehe auch Multitasking.

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Hallo,

jetzt sind die Funktionen verstümmelt. Nur wegen den Änderungen muss man 
nicht den kompletten Timer jedesmal neu initialisieren. Die 
Funktionsnamen machen auch nicht mehr das für was sie gedacht waren. Mir 
gefällt das ehrlich gesagt nicht.

von Falk B. (falk)


Lesenswert?

Veit D. schrieb:
> Hallo,
>
> jetzt sind die Funktionen verstümmelt.

Wen meinst du?

von Veit D. (devil-elec)


Lesenswert?

Hallo,

Dich. Deine gezeigte .ino.

von Falk B. (falk)


Lesenswert?

Veit D. schrieb:
> Dich. Deine gezeigte .ino.

Was ist da verstümmelt? Die Formatierung ist ein wenig verkorkst, weil 
ich echte TABs drin hab. OK. Die restlichen Funktionen sind UNVERÄNDERT!

von Falk B. (falk)


Angehängte Dateien:

Lesenswert?

Hier schön formatiert, ohne TABs.

von Brüno (dominic_m833)


Lesenswert?

Veit D. schrieb:
> jetzt sind die Funktionen verstümmelt

Die Funktionen waren in meinem Code schon so, weil ich sie so als 
sinnvoll empfunden habe.

Falk B. schrieb:
> Etwa so.

Faszinierend wie einfach das geht, vielen Dank!

von Brüno (dominic_m833)


Angehängte Dateien:

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
1
PORTF = 2;     //D1 high
2
TCNT1  = periode - 10 ;
3
TCCR1A |= _BV(COM1B0) | _BV(COM1B1);
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.

von Steve van de Grens (roehrmond)


Lesenswert?

Brüno schrieb:
> Wie würde man so etwas lösen wenn der MC während dem Warten auf
> ein Loslassen etwas anderes tun soll?

https://www.mikrocontroller.net/articles/Statemachine

Man kann mehrere solcher State machines quasi-parallel ausführen.
1
int main()
2
{
3
  while( 1 ) {
4
    stateMachine1();
5
    stateMachine2();
6
    stateMachine3();
7
  }
8
}

: Bearbeitet durch User
von Veit D. (devil-elec)


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.

: Bearbeitet durch User
von Brüno (dominic_m833)


Angehängte Dateien:

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.

von Falk B. (falk)


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.

von Veit D. (devil-elec)


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.

von Brüno (dominic_m833)


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.

von Brüno (dominic_m833)


Angehängte Dateien:

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?

von Falk B. (falk)


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
}

von Brüno (dominic_m833)


Angehängte Dateien:

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 :)

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

Brüno schrieb:
> Gehe ich richtig in der Annahme, dass dein Vorschlag nur für zwei oder
> mehr Pulse funktioniert?

Nö. Geht auch mit einem Puls.
1
// Einzelpuls
2
counter = 0;
3
tccr1b_setup = 0;
4
TCCR1B = (1 << CS11) | (1 << CS10);

Beitrag #7400485 wurde vom Autor gelöscht.
von Brüno (dominic_m833)


Angehängte Dateien:

Lesenswert?

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

von Veit D. (devil-elec)


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.

von Brüno (dominic_m833)


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.

von Falk B. (falk)


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?

von Falk B. (falk)


Lesenswert?

Brüno schrieb:
> Diese Aufgaben bewältigt der Code ziemlich gut.

Naja. pulselimit muss auch volatile sein. Das gilt für ALLE Variablen, 
welche sowohl in einer ISR als auch im Hauptprogramm benutzt werden, 
egal ob lesen oder schreiben.

von Veit D. (devil-elec)


Angehängte Dateien:

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.  :-)

von Falk B. (falk)


Lesenswert?

Veit D. schrieb:
> Da ziehe ich mich lieber zurück.
> Außerdem möchte ich mit Falk u.a. diesen Sommer noch ein Bier trinken
> gehen.  :-)

Willst du deine Beerware-Lizensen bezahlen? ;-)

von Veit D. (devil-elec)


Lesenswert?

Hallo,

naja, da müßte ja wohl eher Brüno an mich ...
Ich kann dir aber gern ein Bier ausgeben, daran soll es nun nicht 
scheitern. Selbst wenn ich vorher Flaschen sammeln gehen muss.  :-)

Beitrag #7400686 wurde vom Autor gelöscht.
von Falk B. (falk)


Angehängte Dateien:

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.

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.