Timer 1 mit eigner Frequenz für SSR Relais

Hallo,

Ich bin gerade dabei einen Brutschrank zu programmieren.
Angesteuert wird das ganze mit 2 Heizkabeln die mit jeweils einem SSR geschalten werden.

Dabei dachte ich mir, dass ich die ansteuerung mit 2 PID Reglern mache und dann den Output mit einem 0,5Hz PWM Signal auf Pin 9 & 10 ausgebe.

0,5Hz wegen: Periodendauen = 2 Sekunden
Bei einer Ausgabe vom PID Regler von 0-100, ergibt das dann als kleinstes Intervall 20ms was der Typischen ansprechzeit von einem SSR entspricht
Aber leider funktioniert das nicht. Und ich weiß nicht woren es liegt, bzw ob das auch überhaupst so möglich ist?

Hier wäre der Code ausschnitt, ohne den Methoden zusätzlichen Methoden,
Wäre toll wenn mir jemand weiterhelfen könnte, Danke :wink:

#include <LiquidCrystal_I2C.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <DHT.h>
#include <QuickPID.h>

#define POTI A0
#define LEDPIN LED_BUILTIN
#define RELAISPIN9 9
#define RELAISPIN10 10
#define TuningTopPIN5 5
#define TuningBottomPIN6 6

//Für Regelung verwendet
int pPotiInput, pAnalogInput;
float pSoll, pIstTop, pIstBottom;
float pOutTop, pOutBottom;

float tKp = 2.0, tKi = 5.0, tKd = 1.0;
QuickPID TopPID(&pIstTop, &pOutTop, &pSoll, tKp, tKi, tKd,
               TopPID.pMode::pOnError,
               TopPID.dMode::dOnMeas,
               TopPID.iAwMode::iAwClamp,
               TopPID.Action::direct);
bool pTuningTopActiv, pTuningTopStart;

float bKp = 1.0, bKi = 2.0, bKd = 0.0;
QuickPID BottomPID(&pIstBottom, &pOutBottom, &pSoll, bKp, bKi, bKd,
               BottomPID.pMode::pOnError,
               BottomPID.dMode::dOnMeas,
               BottomPID.iAwMode::iAwClamp,
               BottomPID.Action::direct);

//Temperaturen der Sensoren
float pT1, pT2, pT3, pT4, pT5, pH6, pT3T4Average;
int pReferenzCounter = 2; //Anzahl wie viele Sensoren zum Regeln verwendet werden (Im Regelfall T1 & T2 ==> pReferenzCounter = 2)

//Timer
const unsigned long cIntervalAnalogInput = 500; //Poti eingang lesen
const unsigned long cIntervalTempRead = 2000; //Temperaturwerte lesen
unsigned long pCurrentMillisAnalogInput, pLastMillisAnalogInput;
unsigned long pCurrentMillisTempRead, pLastMillisTempRead;

//Display
LiquidCrystal_I2C lcd(0x27,20,4);
char charString[20];

//Definition des OneWire Buses
#define OWB_DS18B20 2
#define OWB_DHT11 3
// Initiat DHT11 Sensor
DHT SensorDHT(OWB_DHT11, DHT11);
// Initiate DS18B20
OneWire oneWire(OWB_DS18B20);
DallasTemperature SensorsDS(&oneWire);

// Addresses of 5 DS18B20s
uint8_t adT1[8] = { 0x28, 0xCB, 0x2D, 0x81, 0xE3, 0xE1, 0x3C, 0xB4 };
uint8_t adT2[8] = { 0x28, 0x92, 0x2A, 0x81, 0xE3, 0xE1, 0x3C, 0x5D };
uint8_t adT3[8] = { 0x28, 0xB0, 0x1E, 0x81, 0xE3, 0xE1, 0x3C, 0x1E };
uint8_t adT4[8] = { 0x28, 0x72, 0x9C, 0x81, 0xE3, 0xE1, 0x3C, 0xA7 };
uint8_t adT5[8] = { 0x28, 0xC2, 0x4B, 0x81, 0xE3, 0xE1, 0x3C, 0xAF };

void setup()
{
  lcd.init();
  lcd.backlight();

  // Start up the Sensors
  SensorsDS.begin();
  SensorDHT.begin();

  //PID Regler
  TopPID.SetOutputLimits(0.0, 100.0);
  TopPID.SetMode(TopPID.Control::automatic);

  BottomPID.SetOutputLimits(0.0, 100.0);
  BottomPID.SetMode(BottomPID.Control::automatic);

  // Set PWM frequency to ~0.5 Hz on pins 9 and 10 (Timer 1)
  TCCR1A = 0b10100000; // Clear OC1A/OC1B on compare match, set at BOTTOM (non-inverting mode)
  TCCR1B = 0b00011011; // Set prescaler to 1024 Fast PWM with ICR1 as TOP
  ICR1 = 31249; // Set TOP value for ~0.5 Hz frequency
  //PWM Frequency =16000000 / {256×(31249+1)} ​≈ 1Hz

  pinMode(LEDPIN, OUTPUT);
  pinMode(RELAISPIN9, OUTPUT);
  pinMode(RELAISPIN10, OUTPUT);
}
void loop()
{
  //Timer Funktion aufrufen
  CallTimers();

  //PID Regler aufruf
  TopPID.Compute();
  BottomPID.Compute();

  //Poti Skalieren + auf Display schreiben
  if ((pCurrentMillisAnalogInput - pLastMillisAnalogInput) >= cIntervalAnalogInput) {
    pLastMillisAnalogInput = pCurrentMillisAnalogInput; //letzte Zeit speichern

    pAnalogInput = analogRead(POTI);
    pPotiInput = map(pAnalogInput, 0, 1023,200, 300);    //300 - 400
    pSoll = (float)pPotiInput / 10.0;
    //Display Ausgabe
    lcd.setCursor(0,0);
    lcd.setCursor(0,0);
    lcd.print("S:");
    lcd.print(pSoll, 1);
    lcd.write(0xDF);
    sprintf(charString,"  O:%03d U:%03d",(int)pOutTop,(int)pOutBottom);
    lcd.print(charString);
  }

  if ((pCurrentMillisTempRead - pLastMillisTempRead) >= cIntervalTempRead) {
    pLastMillisTempRead = pCurrentMillisTempRead; //letzte Zeit speichern

    //Aktuellen Temperaturen und Feuchtigkeit auslesen
    ReadTemperatures();

    //Ist Temperatur bestimmen je nach gesteckten Fühlern
    if (pReferenzCounter > 0) {
      //Aktuelle Temperatur bestimmen, nur wenn mindestens 1 Sensor von T1 | T2 gesteckt
      pIstTop = (pT1 + pT2) / (float)pReferenzCounter;
    } 
    else {
      //Sonst wird Sensor T5 verwendet, egal ob er verwendet wird oder nicht
      pIstTop = pT5;
    }
    pIstBottom = pT4;

    //Aktuellen Temperaturen und Feuchtigkeit auf Display schreiben
    PrintTemperatures();
  }

  //Ausgänge schreiben
  OCR1A = (uint16_t)(31249.0 * constrain(pOutTop, 0.0, 100.0) / 100.0); //Für Pin 9 Heizkabel Oben
  OCR1B = (uint16_t)(31249.0 * constrain(pOutBottom, 0.0, 100.0) / 100.0); //Für Pin 10 Heizkabel Unten
  digitalWrite(LEDPIN, HIGH);
}

Im englischen Teil des Forum müssen die Beiträge und Diskussionen in englischer Sprache verfasst werden. Deswegen wurde diese Diskussion in den deutschen Teil des Forums verschoben.

mfg ein Moderator.

Teste doch erst einmal ohne PID, Display, Sensor. Also nur PWM und SSR

Wer sagt das?
Welches SSR Relais hast Du?
Ein normales AC SSR oder ein Zero cross?

Was Funktioniert nicht? Das Programm bzw der PID Regler oder die Hardware?

Grüße Uwe

Es funktioniert alles der PID Regler die Display ausgabe und das lesen der Temperaturen.

Nur die Ausgabe an PIN 9 & 10 funktioniert nicht. als PWM Signal.
Ist es möglich auf 0,5Hz zu reduzieren vom Timer 1?

Im Prinzip diese Zeilen im loop

  //Ausgänge schreiben
  OCR1A = (uint16_t)(31249.0 * constrain(pOutTop, 0.0, 100.0) / 100.0); //Für Pin 9 Heizkabel Oben
  OCR1B = (uint16_t)(31249.0 * constrain(pOutBottom, 0.0, 100.0) / 100.0); //Für Pin 10 Heizkabel Unten

Und diese Zeilen im Setup

  // Set PWM frequency to ~0.5 Hz on pins 9 and 10 (Timer 1)
  TCCR1A = 0b10100000; // Clear OC1A/OC1B on compare match, set at BOTTOM (non-inverting mode)
  TCCR1B = 0b00011011; // Set prescaler to 1024 Fast PWM with ICR1 as TOP
  ICR1 = 31249; // Set TOP value for ~0.5 Hz frequency
  //PWM Frequency =16000000 / {256×(31249+1)} ​≈ 0,5Hz

  pinMode(LEDPIN, OUTPUT);
  pinMode(RELAISPIN9, OUTPUT);
  pinMode(RELAISPIN10, OUTPUT);

hier weiß ich nicht ob das so stimmt, bzw so möglich ist

Hallo,

ja das geht.
Timer Einstellungen
Alles durchlesen, verstehen, anwenden. :wink:
Bei Fragen - fragen.
Nimm Modi Phase Correct oder Phase Frequency Correct, haste mehr Luft zum einstellen, weil bedingt vom Modi schon der max. Takt halbiert wird.
Eine Bitte, gewöhne dir die komplette Zuweisung mit einer Bitschreibweise ab. Ich lese das nicht und du kannst es in 2 Wochen auch nicht mehr lesen ohne langwierig im Manual zu blättern welches Bit was bedeutet.

Also mit diesem Code hast du 0,5 Hz phasenkorrektes PWM mit 50% Tastrate. Pin 10 ist invertiert. Wenn der OCR1B das gleiche ausgeben soll, wie OCR1A dann muss die auskommentierte Zeile verwendet werden.

#include <util/atomic.h>

namespace gc {
constexpr uint8_t pwm_out[] {9, 10};
constexpr uint16_t icr1_top {15625};   // 16MHz
constexpr uint16_t duty {icr1_top / 2};
}   // namespace gc

// --------------------------------------------------------------------------------------------------------------------

void setup() {
  Serial.begin(115200);

  pinMode(gc::pwm_out[0], OUTPUT);
  pinMode(gc::pwm_out[1], OUTPUT);
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    TCCR1B = 0;                                                      // Stop Timer first, for safety
    TCCR1A = 0;
    TCCR1B = _BV(WGM13) | _BV(CS12) | _BV(CS10);                     // Prescaler 1024
    TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(COM1B0) | _BV(WGM11);   // phase correct, clear ocr1a on compare match
    // TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM11);                 // OCR1B not inverted
    ICR1 = gc::icr1_top;                                             // TOP VAlue for 0,5Hz
    OCR1A = gc::duty;
    OCR1B = gc::duty;
  }
}

void loop() {}

Hallo,

bitte immer erst ganz pauschal nach der kompletten Konfiguration den Timer mittels CS Bits einschalten. Alles andere Bedarf paar Tests, es kann gut gehen muss aber nicht, mit der Gefahr das der Timer irgendwelchen Mist macht.
Ausgenommen sind die gepufferten OCR Register. Aber auch hier kann man vorher eine saubere Startbedingung setzen, wenn man zum Schluss startet.
Der Grund für den namespace erschließt sich mir allerdings nicht.
Den Atomic Block benötigt man auch nicht.
Einfach Timer stoppen und dann umkonfigurieren.
cli / sei benötigt auch nicht, abweichend vom Thread.

Danke für die Hinweise. Aber da würde ich mich über etwas weitergehende Erklärungen freuen.

Ich konnte bisher keine Probleme feststellen, wenn es so gemacht wurde, wie ich es in #7 gemacht habe. Also warum soll man das so machen, wie Du schreibst? Bzw. was ist der Grund, dass das so nicht funktionieren soll?

Muss es nicht. Ist meine Art globale Konstanten zu kennzeichnen. Auch wenn das bei so einem Paarzeiler übertrieben sein mag.

Im Datenblatt steht:

It is important to notice that accessing 16-bit registers are atomic operations. If an interrupt occurs between the two instructions accessing the 16-bit register, and the interrupt code updates the temporary register by accessing the same or any other of the 16-bit Timer Registers, then the result of the access outside the interrupt will be corrupted.

Therefore, when both the main code and the interrupt code update the temporary register, the main code must disable the interrupts during the 16-bit access.

The following code examples show how to do an atomic write of the TCNT1 Register contents. Writing any of the OCR1A/B or ICR1 Registers can be done by using the same principle. […]

Der folgende Code nutzt dann _CLI() und sichert/restored SREG beim Setzen eines Wertes für TCNT1. Aus diesem Grund habe ich die Operationen in den Atomic-Block gesetzt. Weil es im DB so vorgemacht wurde.

Warum wird es dann doch nicht benötigt?

Weil bei dir keine ISR zwischendurch die Register befummelt.

Die AVR Timer sind etwas, naja, hakelig.
z.B. beim beschreiben des CNT Registers kann ohne anhalten des Timers durchaus ein Missgeschick passieren, so das hinterher nicht das da drin steht, was du dir erwünscht hast.

Am Rande:
Ugly Timer Trap: CTC - Timer 2 - TCCR2A - gezielter Reset notwendig - Warum? - #24 by combie

Gilt übrigens für alle AVR Timer mit Schattenregister

Es macht also schon Sinn eine gewisse Reihenfolge beim Beschreiben einzuhalten.
Ist nicht in jeder Situation erforderlich, aber schadet nicht, wenn man es generell tut
So auch der ATOMIC Block. Muss nicht unbedingt, kann man aber machen.

1 Like

Vielen Dank für den Link und die Erklärung. Anscheinend war das Problem im verlinkten Thread noch ein wenig anders gelagert. So wie ich @Doc_Arduino verstanden habe, muss der Prescaler als letztes gesetzt werden. Das hat er in seinem damaligen "Problemcode" ja bereits schon so gemacht.

Wenn ich seinen Kommentar richtig verstanden habe, wäre die korrekte Reihenfolge ja erst mal alle Parameter außer dem Prescaler zu setzen und als letztes durch das Setzen des PreScalers den Timer zu starten:

#include <Arduino.h>
#include <util/atomic.h>

namespace gc {
constexpr uint8_t pwm_out[] {9, 10};
constexpr uint16_t icr1_top {15625};   // 16MHz
constexpr uint16_t duty {icr1_top / 2};
}   // namespace gc


void setup() {
  Serial.begin(115200);

  pinMode(gc::pwm_out[0], OUTPUT);
  pinMode(gc::pwm_out[1], OUTPUT);
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    TCCR1B = 0;                                                      // Stop Timer first, for safety
    TCCR1A = 0;
    TCCR1B = _BV(WGM13);
    TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(COM1B0) | _BV(WGM11);   // phase correct, clear ocr1a on compare match
    // TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM11);                 // OCR1B not inverted
    ICR1 = gc::icr1_top;              // TOP VAlue for 0,5Hz
    OCR1A = gc::duty;
    OCR1B = gc::duty;                 // Inverted Output
    TCCR1B |= _BV(CS12) | _BV(CS10);   // Start Timer with prescaler 1024
  }
}

void loop() {}

Ich habe im DB nichts dazu gefunden. Aber da steht so viel drin, vielleicht habe ich es auch übersehen. Wenn es nicht explizit im DB steht, ist das wohl ein Erfahrungswert... ?
Den Link auf den AVR Freaks Thread im von Dir verlinkten Thread gibt es leider nicht mehr.

Hallo,

Vielen Dank für die Antworten,
Ich habe versucht beide Lösungen in einem zusammenzuarbeiten von @Kai-R & @Doc_Arduino

Eine Frage dazu hätte ich noch @Kai-R Warum wählst du Mode 10: PWM, Phase
Correct.

Und Pin 9 (OCR1A) & 10 (OCR1B) sollen unterschiedliche Werte ausgeben.
Hier wäre mein Vorschlag, aber die LED blinkt einmal nicht so wie erhofft, da muss ich erst mit dem Oszi kontrollieren was jetzt so wirklich ausgegeben wird:

void setup()
{
  // Set PWM frequency to ~0.5 Hz on pins 9 and 10 (Timer 1)
  TCCR1A = 0;    // Reset TCCR1A Register 
  TCCR1B = 0;    // Reset TCCR1B Register
  TCCR1B = _BV(WGM13)  | _BV(WGM12); //Mode 14 Fast PWM (ICR1 as TOP Value)
  TCCR1A = _BV(COM1B1) | _BV(COM1A1) | _BV(WGM11);  // (Non-inverting mode)
  ICR1 = 31249; // Set TOP value for ~0.5 Hz frequency 
  //Fast PWM # Frequency = 16000000Hz / {1024×(31249+1)} ​≈ 0,5Hz

  OCR1A = 3125; //Init mit 10% Duty Cycle
  OCR1B = 3125; //Init mit 10% Duty Cycle
  TCCR1B = _BV(CS12) | _BV(CS10);  //PreScaler 1024 

  pinMode(LEDPIN, OUTPUT);
  pinMode(RELAISPIN9, OUTPUT);
  pinMode(RELAISPIN10, OUTPUT);
}
void loop()
{

 ...Code ...

  //Ausgänge schreiben
  OCR1A = (uint16_t)(31249.0 * constrain(pOutTop, 0.0, 100.0) / 100.0); //Für Pin 9 Heizkabel Oben
  OCR1B = (uint16_t)(31249.0 * constrain(pOutBottom, 0.0, 100.0) / 100.0); //Für Pin 10 Heizkabel Unten
  digitalWrite(LEDPIN, HIGH);
}

Wie der genannte Thread nachweist, ist das DB in Sachen Timer mindestens unvollständig.

Erkannte Probleme führen manchmal zu Pauschalisierungen.
Pauschalisierungen werden gerne zur Doktrin.
Auf dem langen Weg geht dann gerne auch mal der Kontext verloren.
z.B. Goto ist böse. Immer.

Hallo,
Die Eier werden ja nicht direkt auf dem Heizdraht liegen, da kann man sich schon auch eine Zykluszeit von 1s leisten, oder was auch immer. Zudem denke ich auch wenn die Heizleistung nicht zu sehr überdimensioniert ist dann geht das auch mit einer reinen Thermostat Funktion mit kleiner Hysterese.

warum willst Du das so kompliziert machen , das geht doch recht einfach auch mit millis(). Die "Einzeit" entspricht der Stellgrösse y , die "Auszeit dem Rest , alles bei einer festen Zykluszeit.
Bei der Lib PID Regler gibt es ein Beispiel um die Stellgröße y des Reglers auf einen Digitalen Ausgang zu legen. Ich hatte das mal in eine Function gepackt.
Aus meiner Bastelkiste

/*Analog-Relais 0-100%
 * 
 */
byte const pinrel = 13;
int stell=50;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  pinMode(pinrel, OUTPUT);

}

void loop() {
  // put your main code here, to run repeatedly:
  digitalWrite(pinrel,analogrelay(stell));
}

bool analogrelay( int wert) {
  static uint32_t altzeit = 0;
  const uint32_t gesamt = 1000; // zykluszeit

  if (wert <= 0) wert = 0;
  uint32_t ein = wert * gesamt / 100; // 0-100% Skalierung
  if (millis() - altzeit >= gesamt) altzeit = millis(); // Zeittakt
  if (millis() - altzeit < ein)return (true);
  return (false);
}

Danke das Beispiel habe ich gesehn vom der BasicPID Libary aber ich dachte ich könnte es auch mit den Timmern lösen.

void setup() {

  lcd.init();
  lcd.backlight();

  // Start up the Sensors
  SensorsDS.begin();
  SensorDHT.begin();

  //PID Regler
  TopPID.SetOutputLimits(0.0, 100.0);
  TopPID.SetMode(TopPID.Control::automatic);

  BottomPID.SetOutputLimits(0.0, 100.0);
  BottomPID.SetMode(BottomPID.Control::automatic);

  // Set PWM frequency to ~0.5 Hz on pins 9 and 10 (Timer 1)
  TCCR1A = 0;    // Reset TCCR1A Register 
  TCCR1B = 0;    // Reset TCCR1B Register
  TCCR1B = _BV(WGM13)  | _BV(WGM12); //Mode 14 Fast PWM (ICR1 as TOP Value)
  TCCR1A = _BV(COM1B1) | _BV(COM1A1) | _BV(WGM11);  // (Non-inverting mode)
  ICR1 = 31249; // Set TOP value for ~0.5 Hz frequency 
  //Fast PWM # Frequency = 16000000Hz / {1024×(31249+1)} ​≈ 0,5Hz

  OCR1A = 3125; //Init mit 10% Duty Cycle
  OCR1B = 3125; //Init mit 10% Duty Cycle
  TCCR1B = _BV(CS12) | _BV(CS10);  //PreScaler 1024 

  pinMode(LEDPIN, OUTPUT);
  pinMode(RELAISPIN9, OUTPUT);
  pinMode(RELAISPIN10, OUTPUT);
}

void loop() {
  
  ..Code...

  //PID Regler aufruf
  TopPID.Compute();
  pValueOCR1A = (uint16_t)(31249.0 * constrain(pOutTop, 0.0, 100.0) / 100.0); //Für Pin 9 Heizkabel Oben
  BottomPID.Compute();
  pValueOCR1B = (uint16_t)(31249.0 * constrain(pOutBottom, 0.0, 100.0) / 100.0); //Für Pin 10 Heizkabel Unten


  //Ausgänge schreiben
  OCR1A = pValueOCR1A; //Für Pin 9 Heizkabel Oben
  OCR1B = pValueOCR1B; //Für Pin 10 Heizkabel Unten
  digitalWrite(LEDPIN, HIGH);
}

Die letzte Zeile macht Probleme:
Es sieht so aus als würde das OCR1A nicht korrekt beschrieben, pValueOCR1A wird korrekt beschrieben als (uint16_t)
Aber nicht korrekt übergeben an das OCR1 Register

Ich nutze PWM in der Hauptsache zur Steuerung von Motoren. Ich habe gelesen, dass phasenkorrektes PWM wegen seiner symmetrischen Eigenschaften besser dazu geeignet ist. Außerdem ist es damit „einfacher“ niedrige Frequenzen zu erzeugen, weil zwei statt einer Impulsflanke gesetzt werden.

In meinem Beispiel haben OCR1A und OCR1B den gleichen Wert, weil es ein Beispiel ist. Wenn du den Registern unterschiedliche Werte verpasst, hast du auch unterschiedliche Duty Cycle ( nicht invertierend).

Diese Zeile ist falsch. Ich habe es inzw. in meinem Posting korrigiert. Es muss


TCCR1B |= _BV(CS12) | _BV(CS10);  //PreScaler 1024 

sein. Ohne das „OR“ vor dem Gleichheitszeichen wird das gesetzte WGM13 Bit wieder gelöscht.

Hallo,

Ergänzung von mir. :wink:

Das Ziel ist doch einen Timer mit ganz bestimmten Eigenschaften zu konfigurieren.
In deinem Fall konfigurierst du erst

TCCR1B = _BV(WGM13) | _BV(CS12) | _BV(CS10);
PWM, Phase & Frequency Correct mit ICRn und startest den Timer,

TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(COM1B0) | _BV(WGM11);
danach wechselst der Modi auf PWM, Phase Correct mit ICRn inkl. Duty Verhalten,

ICR1 = gc::icr1_top;
und danach erst wird TOP für die Frequenz geändert, alter Wert ist ggf. unbekannt.

Das man hier nichts negatives mitbekommt ist Zufall. Anderer gewünschter Modi mit anderen verteiltes Bits, vielleicht noch andere Reihenfolge usw. und der Timer läuft kurzzeitig nicht so wie er soll und man wundert sich vielleicht warum das angeschlossene Gerät was den Takt bekommt hin und wieder rumzickt nach Reset etc., weil kein sauberer Start erfolgt. Meine Logik ist immer alles vorbereiten und wenn alles passt erst starten. Egal ob Timer konfigurieren oder eine Schaltung in Betrieb nehmen. Warum soll ein Timer schon laufen wenn er noch nicht fertig konfiguriert ist.

Wegen den 16 Bit Registern und dem Datenblattauszug.

Das man pauschal betrachtet ein 16 Bit Register Atomic sicher behandelt ist so gesehen klar.
Man muss jetzt nur unterscheiden was man wann macht bzw. was wann erforderlich ist.
Schaden tut dein Atomic im setup() nicht, bewirkt jedoch auch nichts.
Nur wenn der Timer nicht läuft, kann auch einer seiner Interrupts nicht dazwischen funken.
Bei den OCR Registern kommt es auf den Modi an. In dem Modi hier sind die OCR Register gepuffert. Die kann man jederzeit einfach so ändern.
Nur bei einer Frequenzänderung mit ICR Register muss man aufpassen.
Wert vergrößern ist kein Problem. Mit verkleinern kann es Frequenz/Duty Sprünge geben wenn man den falschen Zeitpunkt erwischt.

Also hier geht es um den sauberen Start. Danach wird hier nichts mehr geändert. Möchte man im laufenden Betrieb etwas ändern, kommt es auf den Fall drauf an. Will man es sich einfach machen, verwendet man einen Modi mit gepufferten OCR Registern für alles.

Den Thread den combie mit dem Timer 2 zeigt hatte ich schon einmal gesucht. An dem Timer 2 sieht man dummerweise einen Hardwarebug mit falscher Konfigurationsreihenfolge. Timer 2 sollte man nie einfach so im laufenden Betrieb umkonfigurieren. Zumindestens muss das vorher gründlich getestet werden.

Also bei Timer 2 ist das generelle Problem, dass man den gar nicht in die gewünschte Konfiguration bekommt, wenn die Reihenfolge nicht stimmt. Timer 1 dagegen verhält sich kurzzeitig anders wie gewünscht. Das sind die 2 Unterschiede. Wobei das was für Timer 1 gilt auch für Timer 2 zutrifft, auch wenn er keine Konfigmacke hätte.

Das hier hatte ich einmal für jemanden vorbereitet.

Timer 1 25kHz
/*
  Doc_Arduino - german Arduino Forum
  IDE 1.8.19
  Arduino Mega2560 & UNO (wobei UNO nicht getestet)
*/

#include <Streaming.h>  // https://github.com/janelia-arduino/Streaming
Stream &out {Serial};

const byte pinFan {11};

void setup(void)
{
  Serial.begin(250000);
  Serial.println(F("\nuC Reset ####"));

  //readT1Register(Serial);   // Debug 
  resetTimer1();
  //readT1Register(Serial);   // Debug 
  initPwmPin(pinFan);     
  //readT1Register(Serial);   // Debug 
  initTimer1To25kHz();
  //readT1Register(Serial);   // Debug 
  changeT1Duty(pinFan, 33);   // Pin, Duty [%]
  //readT1Register(Serial);   // Debug 
}

/* Default
   TCCR1A 1010.0001  // COM1A1, COM1B1, WGM10 (Clear on Compare, 8Bit PWM Phase Correct))
   TCCR1B 0000.0011  // CS11, CS10 (Prescaler 64)
   TCCR1C 0
   ICR1   0
   OCR1A  0
   OCR1B  0
*/

void loop(void)
{

}

void initPwmPin (const uint8_t pin)
{  
  switch(pin)
  {
    #if defined(__AVR_ATmega328P__)
      case  9: TCCR1A |= _BV(COM1A1); pinMode(pin, OUTPUT); break;  // UNO Pin  9
      case 10: TCCR1A |= _BV(COM1B1); pinMode(pin, OUTPUT); break;  // UNO Pin 10
    #endif
    #if defined(__AVR_ATmega2560__) 
      case 11: TCCR1A |= _BV(COM1A1); pinMode(pin, OUTPUT); break;  // Mega2560 Pin 11
      case 12: TCCR1A |= _BV(COM1B1); pinMode(pin, OUTPUT); break;  // Mega2560 Pin 12
    #endif 
    default: break;
  }
}

void changeT1Duty (const uint8_t pin, uint16_t duty)
{
  //constexpr uint16_t TOP {320}; // macht mit Prescaler 1 -> 25kHz
  if (duty > 100) duty = 100;
  // duty = TOP*duty/100;
  duty = ICR1*duty/100;
  
  switch(pin)
  { 
    #if defined(__AVR_ATmega328P__)
      case  9: OCR1A = duty; break;   // UNO Pin  9
      case 10: OCR1B = duty; break;   // UNO Pin 10
    #endif
    #if defined(__AVR_ATmega2560__) 
      case 11: OCR1A = duty; break;   // Mega2560 Pin 11
      case 12: OCR1B = duty; break;   // Mega2560 Pin 12
    #endif 
    default: break;
  }
}

void initTimer1To25kHz (void)
{
  changeTimer1Mode(10);
  ICR1 = 320;     // macht mit Prescaler 1 -> 25kHz
  startTimer1(1); // Prescaler 1
}

void stopTimer1 (void)
{
  // delete Clock Select Bits, Timer is stopped
  TCCR1B &= ~( _BV(CS12) | _BV(CS11) | _BV(CS10) );
}

void startTimer1 (const unsigned int prescaler)
{
  // set new Prescaler Clock Select Bits
  switch (prescaler) {
    case    1 : TCCR1B |= _BV(CS10);              break;
    case    8 : TCCR1B |= _BV(CS11);              break;
    case   64 : TCCR1B |= _BV(CS11) | _BV(CS10);  break;
    case  256 : TCCR1B |= _BV(CS12);              break;
    case 1024 : TCCR1B |= _BV(CS12) | _BV(CS10);  break;
    default :   break;  
  }
}

void changeTimer1Mode (const uint8_t mode)
{
  if ((mode <=15) && (mode != 13)) {
    stopTimer1();
    deleteTimer1Mode();
    switch (mode) {
      case  0 :                                                                     ; break;  // Normal
      case  1 :                                    TCCR1A |=              _BV(WGM10); break;  // PWM, Phase Correct  8-Bit 
      case  2 :                                    TCCR1A |= _BV(WGM11)             ; break;  // PWM, Phase Correct  9-Bit 
      case  3 :                                    TCCR1A |= _BV(WGM11) | _BV(WGM10); break;  // PWM, Phase Correct 10-Bit 
      case  4 : TCCR1B |=              _BV(WGM12);                                  ; break;  // CTC, OCRnA
      case  5 : TCCR1B |=              _BV(WGM12); TCCR1A |=              _BV(WGM10); break;  // Fast PWM  8 Bit
      case  6 : TCCR1B |=              _BV(WGM12); TCCR1A |= _BV(WGM11)             ; break;  // Fast PWM  9 Bit
      case  7 : TCCR1B |=              _BV(WGM12); TCCR1A |= _BV(WGM11) | _BV(WGM10); break;  // Fast PWM 10 Bit
      case  8 : TCCR1B |= _BV(WGM13)             ;                                  ; break;  // PWM, Phase & Frequency Correct, ICRn
      case  9 : TCCR1B |= _BV(WGM13)             ; TCCR1A |=              _BV(WGM10); break;  // PWM, Phase & Frequency Correct, OCRnA
      case 10 : TCCR1B |= _BV(WGM13)             ; TCCR1A |= _BV(WGM11)             ; break;  // PWM, Phase Correct, ICRn
      case 11 : TCCR1B |= _BV(WGM13)             ; TCCR1A |= _BV(WGM11) | _BV(WGM10); break;  // PWM, Phase Correct, OCRnA
      case 12 : TCCR1B |= _BV(WGM13) | _BV(WGM12);                                  ; break;  // CTC, ICRn
      case 13 : break;                                                                        // Reserved
      case 14 : TCCR1B |= _BV(WGM13) | _BV(WGM12); TCCR1A |= _BV(WGM11)             ; break;  // Fast PWM, ICRn
      case 15 : TCCR1B |= _BV(WGM13) | _BV(WGM12); TCCR1A |= _BV(WGM11) | _BV(WGM10); break;  // Fast PWM, OCRnA                                                        
      default : break; // otherwise no change of the configuration
    }
  }  
}

void deleteTimer1Mode (void)
{
  // delete all WGM Bits
  TCCR1A &= ~( _BV(WGM11) | _BV(WGM10) );
  TCCR1B &= ~( _BV(WGM13) | _BV(WGM12) );
}

void resetTimer1 (void)
{
  // delete all
  TCCR1B = 0;
  TCCR1A = 0;
  TCCR1C = 0;
  TIMSK1 = 0;
  ICR1   = 0;
  OCR1A  = 0;
  OCR1B  = 0;
}

void readT1Register (Stream &out)
{
  out << "TCCR1A " << _BIN(TCCR1A) << endl;
  out << "TCCR1B " << _BIN(TCCR1B) << endl;
  out << "ICR1   " << ICR1  << endl;
  out << "OCR1A  " << OCR1A << endl;
  out << "OCR1B  " << OCR1B << endl;
  out.println();
}  
1 Like

Ich hatte das weiter getestet, als im Thread zu finden ist.
Das gilt für alle von mir getesteten AVR Timer.
Von 328p,2560 über 32U4,16U2 auch Tiny85, Tiny88
Alle mit dem Schattenregister zeigen das Symptom.

Über die ganzen neueren Mega und Tiny kann ich keine Aussage treffen.

Ich halte es auch für eine gute Idee, wenigstens beim Moduswechsel, den Timer anzuhalten. Alles(?) andere kann man im Betrieb ändern.

Hallo,

aha interessant. Bei den neuen AVRs, neue ATtinys habe ich keine, sind mir noch keine Timer Macken aufgefallen.