Nano Every PWM Timer TCA

Hallo,

ich nehme immer Verbesserungen vor wenn mir was auffällt. Kann sein das eine Formel fehl schlug wegen Standard Integer Rechnung (Wertebereichsüberlauf) was ich mit korrigiert habe.

Pin 10 hatte ich im Sketch #26 schon eingebaut. :wink:
Demnach sind wir damit fertig?
Der Vorteil bei Verwendung vom MegaCoreX Package ist, es funktioniert millis und delay weiterhin korrekt.

Ich habe den Code aus #26 in meinen Sketch übernommen.

Das habe ich schon gesehen, danke.

Leider nein, theoretisch ja, praktisch nein.
MeinCode funktioniert so nicht mehr.
Ich bekomme total hohe Frequenzen am Pin 9 und 10 obwohl mir die serielle Ausgabe was ganz anderes sagt.

Hallo,

es funktioniert nichts mehr wenn du daran rumfummelst? :joy:
Dann musste mir deinen Sketch zeigen mit Werten die zum Problem führen.

Hart aber wahr.

Der Code ist sicher noch nicht schön aber hat schon Mal gemacht was er sollte.

Stroboskop2.zip (2,6 KB)

Hallo,

und welchen Wert gibts du ein der dann zu hoch ist?

Das ist egal, 1 Hz oder 33 Hz, er reagiert nicht drauf.

Jetzt habe ich mal alles auskommentiert außer :

#include "globals.h"
#include "frequency.h"


//Variablen
float zherz = 330;
bool taster = false;
bool hrz = false;
unsigned long milli = 0;

//Deklarationen
TM1637 tm(CLK, DIO);

void setup() {
#ifdef debug
  Serial.begin(9600);
  Serial.println(F("\nuC Reset ####"));
#endif
  pinMode(9, OUTPUT);  // PortMux '0-WO0'
  pinMode(10, OUTPUT);  // PortMux '0-WO1'
  // digitalWrite(10, HIGH);
  resetTCA0();
  initTCA0();

   setFrequency(0.48);  // 0,48Hz ... 15'625Hz
   //setPeriode(128);   // 32µs ... 2'097'120µs
   setDuty(10000);         // µs, Vielfache von 32µs
   readOutTCA0();
}

void loop() {
}

Dann fuktioniert es

Hallo,

ich guck mir das in Ruhe an ...

Der Fehler kommt aus der void loop() Schleife.

Der Fehler kommt sobald ich auf schalter2 zugreife. !!!

alle Stellen mit schalter2 auskommentiert, geht wohl.

Ich habe mal pin 5 durch pin 6 ersetzt. Das funktioniert.

#define schalter1 4
#define schalter2 6

Es liegt also ein Problem vor wenn Pin 5 benutzt wird.

Hallo,

ja, es gibt mehrere Probleme. Eines davon ist das du in der Funktion loop return verwendest. Das darf man nicht machen. Dabei wird loop verlassen und sofort wieder aufgerufen. Sodass du eine Endlosschleife mit der ersten Abfrage hast. Man benötigt auch kein return an der Stelle. Setze Klammern, dann wird der Code wenn if ungültig übersprungen. Ich empfehle dir jedoch dringend die Basics zur Tasterabfrage zu lernen. Die IDE bringt Bsp. mit ohne Delay. Die Bounce2 Lib kann ich auch empfehlen.

Deine Lernkurve ist derzeit extrem steil, ich weiß, nimm dir Zeit.

Desweiteren kollidieren die Variablen min und max wenn man das MegaCoreX Package verwendet. Wenn ich beide zu echten Variablen mache, keine defines, sehe ich das in den Fehlermeldungen. Ohne Grund keine defines verwenden!

Auch solltest du die Datentypen ändern. Du verwendest zu viel int was meinstens zu klein ist.
Bsp.: const int eeprom_signatur = 231166;
Welcher Wertebereich passt in ein int?

Trotz allen verstehe ich nicht warum im Zusammenspiel des Tastencodes und MegaCoreX die Frequenz falsch eingestellt wird obwohl die Übergabeparameter stimmen. Vielleicht ein Zusammenspiel was ich noch nicht überblicke. Irgendwie ist das alles eine Zeitbombe.

1 Like

Ich danke Dir für die vielen, sicher sehr richtigen Hinweise.

Wie schon gesagt, habe ich den Code nur schnell mal zusammengeflickt.
War ja mehr erstmal zum experimentieren, lernen und zum Verbessern gedacht.

Ich werde also all das ändern.
Vermutlich ist der wichtigste Hinweis der auf return.
Das habe ich tatsächlich so nicht bedacht.

VG Jürgen

Hallo,

was ich bestätigen kann ist das etwas mit Pin 5 nicht stimmt. Der gehört zwar zum Timer TCA zum 3. Compareausgang 0-WO2, aber den habe ich gar nicht aktiviert. Da sollte gar nichts passieren bzw. sich negativ auswirken. Was ist hier nur los. Kopfkratz. Es bleibt spannend.

Also halten wir fest. Mit MegaCoreX erstmal nicht Pin 5 verwenden. Vielleicht ist das auf allen 0-WO2 Pins so egal auf welchen Port man umroutet. Das muss ich mir näher anschauen. Damit verschone ich dich.

Machen wir allerdings alles ohne MegaCoreX weiter läuft millis und delay um Faktor 4 falsch.

Tipp. Bounce2 Lib Bsp. change anschauen und anwenden. Das räumt dein Code auf.

Die kleinen Sachen wie return und der überfüllte int, hab ich schon mal heraus gemacht.

Mit der Taster lib werde ich mal am WE etwas rumspielen.
Da muss ich mir noch überlegen wie ich das dazu bringe einmal drücken und lange drücken und drücken beider tasten zugleich unterscheiden kann.

Das sollte aber zu machen sein.

Das Schöne daran, das Pin 5 quer schlägt ist, das wir das so gefunden haben.
Das ist sicher ein Käfer :slight_smile:

VG Jürgen

Hallo,

sehr schön.

Das hast du herausgefunden. Auch sehr schön. Ich habs nur bestätigt und spiele aktuell Detektiv.

Aktueller Stand ist, pinMode hat auf Pin 5 noch keine negative Wirkung auf den Timer. Erst wenn man den Pin 5 einliest/abfragt kommt Mist raus. Weil sich damit kurioserweise der Timermode ändert. Von meinen DualSlopePwmTop auf Frequency-Mode. Damit stimmt natürlich nichts mehr. Spannend. :thinking:
Man hört sich ...

1 Like

Hallo,

Problem erkannt, Gefahr gebannt. :slight_smile:

Ich hole einmal etwas weiter aus. Es gibt zwischen dem originalen Arduino Framework und dem MegaCoreX Package verschiedene Ansätze den Timer TCA für analogWrite richtig vorzukonfigurieren und bei Verwendung von digital... Funktionen den Pin vom Timer zu trennen. Beiden Ansätzen gemeinsam ist das sie den 8 Bit Splitmodus von TCA konfigurieren. Unterschiede gibt es beim "löschen" der Timer Compare-Ausgänge.
Das Arduino Framework nimmt das mit der Registerbedeutung, ob Single- oder Splitmode, nicht ganz so genau und schaltet einfach die Compareausgänge ab, als ob es sich im Singlemode befinden würde. Das macht keine Probleme. Dadurch werden wirklich nur die Compare Enable Bits und nicht die WGM Bits im CTRLB Register geändert. Das Arduino Framework hat ja den Vorteil das es sich nicht mit Abhängigkeiten anderer Controller-Konfigurationen rumschlagen muss.

Letzteres Problem hat das MegaCoreX zu bewerkstelligen, weil es die gesamte Controllerfamilie unterstützt. Deswegen nimmt es das mit der Single- bzw. Splitmode Bedeutung der Register ganz genau und dementsprechend kollidiert es, wenn man eigenhändig den Timer anders konfiguriert. Das MegaCoreX Package weiß nicht das ich den Timer anders konfiguriert habe und denkt es wäre noch im 8 Bit Splitmode. Ich habe aber den Splitmode abgeschalten und der Singlemode ist aktiv. Deswegen löscht es bei Verwendung von digital... Funktionen im CTRLB Register aus seiner Sicht die richtigen Bits. Da sich TCA jedoch im Single Mode befindet löscht es nicht wie gedacht die Low Byte Compare Enable Bits sondern die WGM Mode Bits im CTRLB Register. Dadurch läuft TCA Amok sobald man man digitalRead verwendet womit das löschen der Bits ausgelöst wird.

Abhilfe kann digitalReadFast() sein. Das prüft nicht die Pin Konfiguration. Damit kann man es umgehen wenn man keine fremden Libs verwendet oder nur Eigene schreibt. Fremde Libs wissen aber nichts von digitalReadFast() und verwenden ausschließlich die Standard Arduino Funktionen wenn sie maximal kompatibel bleiben wollen. Deswegen nützt einem das leider nichts wenn man bspw. Bounce2 verwendet. Man stünde vor dem gleichen Problem.

Deswegen muss ein Workaround an der Basis herhalten der sich nur um das originale Nano Every Board dreht im MegaCoreX Package. Ich habe eine Abfrage eingebaut ob das Every Board mit Every Pinout aktiv ist und entsprechend das löschen der Bits beeinflusst, abhängig davon ob der Splitmode aktiv ist oder nicht. Dazu muss man die Funktion turnOffPWM() in der Datei wiring_digital.c ändern. Wer das machen will sichert sich vorher am Besten die gesamte Datei und ändert die Funktion wie folgt ab.

Bsp. IDE 1.8.19 Portable
Pfad: ...\arduino-1.8.19\portable\packages\MegaCoreX\hardware\megaavr\1.1.2\cores\coreX-corefiles\wiring_digital.c

Bsp. IDE 2.1.0 installiert
Pfad: C:\Users\xyz\AppData\Local\Arduino15\packages\MegaCoreX\hardware\megaavr\1.1.2\cores\coreX-corefiles\wiring_digital.c

Nach Besten Wissen und Gewissen getestet. Dennoch ohne Gewähr.

wiring_digital.c - turnOffPWM()
static void turnOffPWM(uint8_t pin)
{
  /* Actually turn off compare channel, not the timer */

  /* Get pin's timer */
  uint8_t timer = digitalPinToTimer(pin);
  if (timer == NOT_ON_TIMER)
    return;

  uint8_t bit_pos;
  TCB_t *timerB;

  switch (timer)
  {
    /* TCA0 */
    case TIMERA0:
      /* Bit position will give output channel */
      bit_pos = digitalPinToBitPosition(pin);
      
      /* Disable corresponding channel */
      #if defined (NANO_EVERY_PINOUT) && defined (NONA4809_PINOUT) && defined (__AVR_ATmegax09__)
          if ( 0x01 == (TCA0.SPLIT.CTRLD & 0x01) ) {                      // is Splitmode activ?
            if (bit_pos >= 3) ++bit_pos; /* Upper 3 bits are shifted by 1 */
            TCA0.SPLIT.CTRLB &= ~(1 << (TCA_SPLIT_LCMP0EN_bp + bit_pos)); // #define TCA_SPLIT_LCMP0EN_bp  0  /* Low Compare 0 Enable bit position. */
          } else {
            TCA0.SPLIT.CTRLB &= ~(1 << (TCA_SINGLE_CMP0EN_bp + bit_pos)); // #define TCA_SINGLE_CMP0EN_bp  4  /* Compare 0 Enable bit position. */
          }
      #else
          if (bit_pos >= 3) ++bit_pos; /* Upper 3 bits are shifted by 1 */
          TCA0.SPLIT.CTRLB &= ~(1 << (TCA_SPLIT_LCMP0EN_bp + bit_pos));   // #define TCA_SPLIT_LCMP0EN_bp  0  /* Low Compare 0 Enable bit position. */
      #endif
      break;

    /* TCB - only one output */
    case TIMERB0: /* FALLTHRU */  // requires gcc 7, modern syntax is [[fallthrough]];
    case TIMERB1: /* FALLTHRU */
    case TIMERB2: /* FALLTHRU */
    case TIMERB3: /* FALLTHRU */

      timerB = (TCB_t *)&TCB0 + (timer - TIMERB0);

      /* Disable TCB compare channel */
      timerB->CTRLB &= ~(TCB_CCMPEN_bm);

      break;
    default:
      break;
  }
}
2 Likes

Hallo @Doc_Arduino ,

da hast Du Dir ja richtig Arbeit gemacht.
Für diejenigen die das verstehen, ist das sicher auch sehr von Nutzen.

Ich würde an der Stelle einfach weiterhin Pin 5 nicht benutzen.
Ob das Problem noch an anderen Pins auftritt weis ich nicht aber die benutze ich ja in meinem Projekt auch nicht.

Vieleicht wäre das für die Zukunft interessant für die Entwickler der IDE.

Habe ich mir angesehen.
Ich habe mich aber für OneButton entschieden.
Ja, das erleichter einiges und sieht auch viel schöner aus.
IdR. gilt ja: "Schöner Code funktioniert besser :slight_smile:

VG Jürgen

Hallo Jürgen,

Hallo,

dann hälst du Pin 5 frei für den 3. verfügbaren Kanal für eine mögliche 3. Led. :wink:

Im 8 Bit Splitmode stehen zwar bis zu 6 Kanäle bereit, aber vom Port B werden nur 3 Pins herausgeführt, was die Pins 9, 10 und 5 sind (PB0, PB1 und PB2). Da besteht also keine Gefahr.

Man müßte den Portmux ändern, dass machen wir aber nicht. Wer es wissen möchte, mit Port.D hätte man alle 6 Kanäle. Das müßte man separat testen.

 Ardu      |   TCA0    |  
  Pin Port |  PortMux  | 
  14  PD3  |   0-WO3   | 
  15  PD2  |   0-WO2   | 
  16  PD1  |   0-WO1   | 
  17  PD0  |   0-WO0   | 
  20  PD4  |   0-WO4   | 
  21  PD5  |   0-WO5   |

Vieleicht wäre das für die Zukunft interessant für die Entwickler der IDE.

Das muss ich mit MCUdude besprechen ob er dafür eine Notwendigkeit sieht und sein MegaCoreX ändert.

Ja, das erleichter einiges und sieht auch viel schöner aus.
IdR. gilt ja: "Schöner Code funktioniert besser :slight_smile:

Das gilt immer. Aufgeräumter Code erhöht den Überblick und Fehler sind schneller sichtbar. Mir geht es nicht anders.

Dann wäre das erstmal alles soweit geklärt das du alleine weitermachen kannst. Ansonsten einfach im Forum fragen. Im Eingangsthread immer gleich angewöhnen den Namen des Arduinoboards und die IDE Version angeben. Das erleichtert den Einstieg in die Frage für alle.

Bis die Tage ...

Die Idee ist sehr gut.
Werde ich bei der Hardware einfließen lassen.
Ich muss die LED ja nicht bestücken.

Ebenfalls und nochmals vielen Dank für die Hilfe!

VG Jürgen

PS:

Dazu müsstest Du aber in #26 Deinen Sketch nochmal ändern und die 3. LED aktivieren.

1 Like

Hallo,

hatte ich übersehen. Sorry. Ohne weiteren Test ergänzt. Alle 3 PMW Kanäle haben immer den gleichen Wert. Siehe Funktion setDuty(). Weitere notwendige Änderung in Funktion initTCA0() Zeile 83. Damit werden die Timerausgänge überhaupt erstmal aktiviert. pinMode() ist selbsterklärend.

Sketch
/*
  Doc_Arduino - german Arduino Forum
  IDE 1.8.19
  Arduino Nano Every
  Core Package: https://github.com/MCUdude/MegaCoreX
  21.06.2023
  Thread: https://forum.arduino.cc/t/anfangerfrage-pwm-h/1137557

  Frequenzbereich: 0,5Hz ... 15,6kHz
  Pulsweite Minimum 32µs (Timebase)
  Duty Parameter Vielfache von 32µs
  eingestellter TCA0 Prescaler: 256
*/

void setup (void)
{
   Serial.begin(250000);
   Serial.println(F("\nuC Reset ####"));
   pinMode( 9, OUTPUT); // 0-WO0 PortB
   pinMode(10, OUTPUT); // 0-WO1 PortB
   pinMode( 5, OUTPUT); // 0-WO2 PortB 
   resetTCA0();
   initTCA0();
   setFrequency(0.48);  // 0,48Hz ... 15'625Hz
   //setPeriode(128);   // 32µs ... 2'097'120µs
   setDuty(10000);         // µs, Vielfache von 32µs
   readOutTCA0();
}

void loop()
{
  
}

void setFrequency (const float freq)
{
  if ( (freq >= 1) && (freq <= 15625) ) {
    const uint16_t top {static_cast<uint16_t>((F_CPU / 2.0 / 256 / freq) + 0.5)};
    TCA0.SINGLE.PERBUF = top;
  }
}

void setPeriode (const uint32_t periode) // in [µs]
{
  const uint32_t timebase {static_cast<uint32_t>(62.5 * 256 * 2 / 1000)}; // mit Prescaler 256 = 32µs
  if ( (periode >= timebase) && (periode <= timebase*65535UL) ) {
    const uint16_t top {static_cast<uint16_t>((1.0 * periode / timebase) + 0.5)};
    TCA0.SINGLE.PERBUF = top;
  }
}

void setDuty (const uint32_t duty)  // in [µs]
{
  const uint32_t timebase {static_cast<uint32_t>(62.5 * 256 * 2 / 1000)}; // mit Prescaler 256 = 32µs
  if ( (duty >= timebase) && (duty <= TCA0.SINGLE.PERBUF*timebase) ) {
    const uint16_t compare {static_cast<uint16_t>(duty/timebase)};
    TCA0.SINGLE.CMP0BUF = compare;  // 0-WO0
    TCA0.SINGLE.CMP1BUF = compare;  // 0-WO1
    TCA0.SINGLE.CMP2BUF = compare;  // 0-WO2
  }
}

void resetTCA0 (void)
{
  TCA0.SINGLE.CTRLA = 0;                          // disable the timer and delete prescaler
  TCA0.SINGLE.CTRLB = 0;   
  TCA0.SINGLE.CTRLC = 0;
  TCA0.SINGLE.CTRLD = 0;   
  TCA0.SINGLE.INTCTRL = 0;       
  TCA0.SPLIT.CTRLESET = TCA_SINGLE_CMD_RESET_gc;  // hard reset, recommended when changing normal<>split the mode
  TCA0.SINGLE.CNT = 0; 
  TCA0.SINGLE.PER = 65535;  
  TCA0.SINGLE.CMP0 = 0;
  TCA0.SINGLE.CMP1 = 0;
  TCA0.SINGLE.CMP2 = 0;
}

void initTCA0 (void)
{  
  // Dual Slope PWM Mode Top - Channel 0/1/2
  TCA0.SINGLE.CTRLA |= TCA_SINGLE_CLKSEL_DIV256_gc;
  TCA0.SINGLE.CTRLB |= TCA_SINGLE_WGMODE_DSTOP_gc;
  TCA0.SINGLE.CTRLB |= TCA_SINGLE_CMP0EN_bm | TCA_SINGLE_CMP1EN_bm | TCA_SINGLE_CMP2EN_bm;
  PORTMUX.TCAROUTEA = PORTMUX_TCA0_PORTB_gc;    // '0-WOx' auf Port.B
  TCA0.SINGLE.CTRLA |= TCA_SINGLE_ENABLE_bm;
}

void readOutTCA0 (void)
{
  Serial.print(F("TCA0 PERBUF:    ")); Serial.println(TCA0.SINGLE.PERBUF);
  Serial.print(F("TCA0 CMP0BUF    ")); Serial.println(TCA0.SINGLE.CMP0BUF);
  Serial.print(F("real Freq: ")); Serial.print(F_CPU / 2.0 / 256 / TCA0.SINGLE.PERBUF); Serial.println(F(" Hz"));
  Serial.print(F("Periode: ")); Serial.print(TCA0.SINGLE.PERBUF * 32UL); Serial.println(F(" us"));
}

PS:
Was du noch machen könntest ist, die Betreffzeile im Eingangsthread ändern. Im Nachgang vielleicht für die Suche hilfreich. Meinetwegen "Nano Every PWM Timer TCA" oder so ähnlich. :wink:

Ich werde es heute Abend testen.

Danke!

VG Jürgen

Edit: funktioniert!!! :+1:

Hier mal ein paar Bilder vom Baufortschritt: