Arduino Nano Every – ein Deep Dive

Über den Beitrag

Falls ihr bei einem Projekt einen Arduino Nano oder Arduino UNO R3 einsetzt und dabei mit dem SRAM oder Flash an Grenzen stoßt, könnte das Arduino Nano Every Board eine geeignete Alternative sein. Sofern ihr euch auf die Funktionen der „Arduino Sprache“ beschränkt und keine Register direkt ansprecht, könnt ihr eure Sketche problemlos übertragen. Oberflächlich sind die Unterschiede also gering.

Schaut ihr jedoch tiefer, dann werdet ihr feststellen, dass sich der zugrundeliegende ATmega4809 (Datenblatt ATmega4808/4809) als Mitglied der megaAVR®-Serie ganz wesentlich vom ATmega328P unterscheidet. Und genau um diesen Blick „unter die Haube“ geht es in diesem Beitrag.

Neben dem originalen Arduino Nano Every betrachte ich hier auch einen interessanten Verwandten aus China, der auf dem ATmega4808 basiert und unter dem Namen „Nano Thinary“ oder „Nano 4808“ bekannt ist. Der ATmega4808 ist dem ATmega4809 sehr ähnlich. Deswegen hat der Hersteller Microchip beide Mikrocontroller gemeinsam in einem Datenblatt beschrieben. Und genauso macht es Sinn, beide Boards in einem Beitrag zu behandeln.

Das kommt auf euch zu:

Eigenschaften des Arduino Nano Every / „Nano 4808“

Was die Boards können und wo es Einschränkungen gibt, hängt von drei Faktoren ab:

  • dem Mikrocontroller,
  • der Boardarchitektur (vor allem, welche MCU Pins als Board Pins ausgeführt sind)
  • dem verwendeten Boardpaket. 

Technische Daten der Boards

Hier zunächst einige Eckdaten des Arduino Nano Every Boards:

  • Max. 20 MHz Taktfrequenz (abhängig vom Boardpaket)
  • Betriebsspannung: 5 Volt
    • VIN: 7- 21 Volt
  • Max. Strom per I/O Pin: 20 Milliampere
  • Flash: 48 KB
  • SRAM: 6 KB
  • EEPROM: 256 Byte
  • Schnittstellen: 1x SPI, 1x I2C, max. 4x USART (abhängig vom Boardpaket)
  • I/O Pins: 22 (+2)
    • zu den „+2“ komme ich später.
  • PWM Pins: 5
  • Externe Interrupts an allen I/O Pins
  • Eventsystem 
  • Configurable Customer Logic
  • 1x 16-Bit Timer A, 4x 16-Bit Timer B

Für den „Nano 4808“ (Nano Thinary) gilt dasselbe, mit folgenden Ausnahmen:

  • max. 3x USART
  • PWM Pins: 8
  • I/O Pins: 25  (+1)
  • 1x 16-Bit Timer A, 3x 16-Bit Timer B

Bezugsquellen

Den Arduino Nano Every bekommt ihr im Arduino Store. Ich habe dort 12.50 € plus Steuern und Versand bezahlt, was für die Originalboards relativ günstig ist. Zum Vergleich: Der Arduino Nano kostet im Arduino Store 20.50 € (Stand: 10/23).

Den „Nano 4808“ habe ich für ca. 7.50 € bei AliExpress gefunden. Plant ein paar Wochen Lieferzeit ein, zumindest wenn ihr in Europa wohnt.

Pinout

Der Arduino Nano Every verwendet die 48-Pin Version des ATmega4809, der „Nano 4808“ basiert auf der 32-Pin Version des ATmega4808:

Die Basis des Nano Every / Nano 4808: 32-Pin ATmega4808 und 48-Pin ATmega4809
32-Pin ATmega4808 und 48-Pin ATmega4809

Beide Mikrocontroller gehören zu der megaAVR®0-Serie, die sich grundlegend von der „traditionellen“ AVR®-Serie unterscheidet. Beispielsweise erfolgt die Programmierung über UPDI (Unified Program Debug Interface) und nicht wie gewohnt über ISP oder mithilfe des Optiboot Bootloaders über die UART Schnittstelle. Deshalb haben die hier besprochenen Boards auch keinen USB-zu-Seriell Adapter, sondern einen UPDI-Programmer. Bei UPDI handelt es sich um eine 1-Wire Schnittstelle, die ihr vielleicht von meinem Beitrag über das megaTinyCore Paket von Spence Konde her kennt. 

Pinout Arduino Nano Every

Pinout Arduino Nano Every

Wie ihr seht, werden die Pins des ATmega4809 nur zum Teil auf dem Arduino Nano Every Board ausgeführt. Der Rest geht verloren. Zum Teil könnt ihr aber mithilfe von PORTMUX die Funktionen nicht ausgeführter Pins auf vorhandene Pins umleiten.

Die Pinbezeichnungen und -nummern (hellblau / rosa) entsprechen im Wesentlichen denen des ATmega328P-basierten Arduino Nano. Unterschiede gibt es hier:

  • A6 und A7 können als I/O Pins genutzt werden.
  • A4 und A9 haben zwei Pinnummern, nämlich 18/22 und 19/23.
  • AREF hat die Pinnummer 39, aber nur bei Verwendung des MegaCoreX Paketes (deswegen nicht im Pinout).

Die den Arduino Pins zugrundeliegenden Portpins Pxy unterscheiden sich komplett von denen des Arduino Nano oder Arduino UNO. Sketche, die die Ports direkt ansprechen, müssen (nicht nur) deswegen umgeschrieben werden, wenn ihr sie auf den Arduino Nano Every übertragt.

Zu den weiteren Funktionen, wie TCA0-x oder EVENTy, kommen wir im Verlauf dieses Beitrags.

Pinout „Nano 4808“

Pinout "Nano 4808" (Thinary)
Pinout „Nano 4808“ (Thinary)

Das Pinout Diagramm des „Nano 4808“ unterscheidet sich vom Arduino Nano Every wie folgt:

  • Pin 22 und 23 sind separat ausgeführt, Pin 24 kommt dazu.
  • Die PWM Pins sind anders zugeordnet.
  • Es ist ein UPDI Pin vorhanden. Ihr könnt also anstelle des vorhandenen On-Board Programmers einen UPDI-fähigen Programmer wie den Atmel ICE nutzen, sofern das Boardpaket den Programmer implementiert hat.
  • Die den Arduino Pins zugrundeliegenden Portpins sind wieder andere.

Boardpakete

Ich werde hier nicht im Detail erklären, wie man Boardpakete installiert. Eine Anleitung gibt es z. B. hier.

Boardpakete für den Arduino Nano Every

Am einfachsten ist es, das „Arduino megaAVR Boards“ Paket zu installieren, da ihr keine zusätzliche Boardverwalter-URL in den Voreinstellungen eintragen müsst. Nach der Installation wählt ihr den Arduino Nano Every als Board und stellt unter Werkzeuge → „Registers emulation“ die Option „None (ATMEGA4809)“ ein.

Als Alternative lege ich euch das Paket MegaCoreX ans Herz, da es Bibliotheken für viele der ATmega4809 -Funktionen enthält, die das Standardpaket nicht hat. Das ist insbesondere sinnvoll für diejenigen, die sich nicht mit Registern auseinandersetzen wollen. Außerdem könnt ihr mit diesem Paket viel mehr Einstellungen vornehmen, wie z. B. den Resetpin sehr bequem in einen gewöhnlichen I/O-Pin umfunktionieren oder die Taktrate ändern. Die Dokumentation auf GitHub ist vorbildlich. Eine Installationsanleitung findet ihr hier. Keine Sorge, das ist in wenigen Minuten erledigt.

Nach der Installation nehmt ihr in dem Menüpunkt Werkzeuge folgende Einstellungen vor:

  • Board: MegaCoreX → ATmega4809
  • Pinout: Nano 4809
  • Programmer: JTAG2UPDI

Den Rest lasst ihr am besten erst einmal unverändert.

Boardpakete für den „Nano 4808“ (Thinary)

Für den „Nano 4808“ habe ich zwei Optionen probiert. Zum einen war das wieder das Paket MegaCoreX, zum anderen das Paket ThinaryArduino. Tut euch einen Gefallen und nehmt das MegaCoreX Paket!

Das ThinaryArduino Paket empfehle ich nicht, da es anscheinend nicht wirklich gepflegt wird. Prinzipiell funktioniert es, hat aber einige Besonderheiten:

  • Serial müsst ihr durch Serial1 ersetzen.
  • analogWrite() gibt es nur auf den Pins A6 und A7.
  • Die Dokumentation lässt zu wünschen übrig.

Reset- und AREF-Pin als GPIO nutzen

Den Pin AREF könnt ihr ohne weitere Maßnahmen als GPIO nutzen. Einzige Einschränkung: Er besitzt eine höhere Kapazität als andere Pins und ist dadurch etwas reaktionsträger. Das MegaCoreX Paket hat dem Pin eine eigene Nummer spendiert, und zwar 39 auf dem Arduino Nano Every und 25 auf dem „Nano 4808“. Bei Verwendung des megaAVR Paketes könntet ihr direkt über den Port gehen (PD7).

Da der ATmega4808/4809 per UPDI programmiert wird, braucht ihr den Reset-Pin nicht zum Hochladen der Sketche. Das MegaCoreX erlaubt es euch, den Pin über das Menü Werkzeuge → Reset als GPIO einzustellen. Aber Vorsicht: Wenn ihr den Pin als OUTPUT konfiguriert und die Resettaste drückt, erzeugt ihr einen Kurzschluss!

Beim megaAVR-Paket könnt ihr die Einstellung in der Datei boards.txt vornehmen. Bei mir liegt sie unter: „C:\Users\Ewald\AppData\Local\Arduino15\packages\arduino\hardware\megaavr\1.8.8“. In der Datei ersetzt ihr die Zeile:

nona4809.bootloader.SYSCFG0=0xC9 durch:

nona4809.bootloader.SYSCFG0=0xC1.

Dadurch löscht ihr das RSTPINCFG Bit (Nr. 3) im Register SYSCFG0 beim nächsten Upload. Da das Register nur über UPDI beschreibbar ist, müsst ihr den Umweg über boards.txt gehen. Geht da aber nur dran, wenn ihr genau wisst, was ihr tut!

Erste Schritte

Sofern ihr euch auf die Arduino Funktionen beschränkt, sollte eure Sketche wie gewohnt funktionieren. Es fühlt sich alles nach Arduino Nano an.

Damit könnte der Beitrag hier enden. Aber es gibt unter der Arduino Oberfläche noch die spannende Welt der megaAVR0-Serie zu entdecken. Gleichwohl ist ein wenig Umgewöhnung erforderlich, auch wenn ihr schon Erfahrung mit der Registerprogrammierung von AVR Mikrocontrollern haben solltet. Hier ein einfacher Blinksketch als Vorgeschmack (Arduino Nano Every):

void setup() {
    PORTE.DIRSET = PIN2_bm; // set PE2 (D13, LED_BUILTIN) to OUTPUT
}
   
void loop() {
    PORTE.OUTSET = PIN2_bm;  // set PE2 to HIGH 
    delay(200);
    PORTE.OUTCLR = PIN2_bm;  // set PE2 to LOW
    delay(200);
}

Für den „Nano 4808“ ersetzt ihr PORTE durch PORTC, um die Board-LED blinken zu lassen.

Eine Schreibweise à la PORTE |= (1<<PE2) erzeugt eine Fehlermeldung, dass „PE2“ nicht definiert ist.

Registerprogrammierung in C für die megaAVR®-Serie

Ich werde nun versuchen, die Registerprogrammierung in kurzen Worten zu erklären. Wer es ausführlich haben möchte, der schaue in die Application Note TB3262 von Microchip.

Das Registerkonzept der megaAVR-Serie findet sich auch bei der megaTiny®-Serie wieder. Wenn ihr diesen Beitrag durchgearbeitet habt, könnt ihr euer Wissen auch dort nutzen.

Register

Die Register sind als Strukturen (Datentyp struct) organisiert, deshalb seht ihr in dem Blink-Sketch die Punkt-Schreibweise. Der Name der Struktur ist das Modul, die Register sind Elemente dieser Struktur, z.B.

  • PORTE.DIRSET:
    • Modul: PORTE
    • Register: DIRSET

Bit Masks und Bit Group Configuration Masks

Um Bit Masks und Bit Group Configuration Masks zu verstehen, schauen wir uns als Beispiel das Control A Register (CTRLA) des Timer B1 an. Das Modul heißt TCB. Da der ATmega4809 aber vier identische Timer B hat (0, 1, 2, 3), wird dem Modulnamen die Nummer angehängt, allgemein: „MODULNAMEn.REGISTERNAME“. Das CTRLA Register des Timer B1 heißt somit: TCB1.CTRLA.

Die TCBn.CTRLA Register besitzen verschiedene Bits und die Bitgruppe CLKSEL:

Arduino Nano Every / Nano 4808 Register: TCBn.CTRLA
TCBn.CTRLA Register

Zum Setzen einzelner Bits kommen die Bit Masks zum Einsatz. Sie werden nach folgender Struktur gebildet:

  • MODUL_BIT_bm

Dabei wird die Nummer des Moduls weggelassen. Um das ENABLE Bit im Timer B1 zu setzen, schreibt ihr also:

TCB1.CTRLA |= TCB_ENABLE_bm;

Bei Bitgruppen (auch Bitfelder genannt) kommen die Bit Group Configuration Masks zu Einsatz, die sich folgendermaßen zusammensetzen:

  • MODUL_BITGRUPPE_CONFIGURATION_gc

Als Beispiel legen wir fest, dass der Timer B1 die TCA Clock benutzt:

TCB1.CTRLA |= TCB_CLKSEL_CLKTCA_gc;

CLKTCA entspricht dem CLKSEL-Wert 0x2 bzw. 0b10. Im Register befindet sich CLKSEL an Bitposition 1 und 2, ist also um 1 Bit nach links verschoben. Deshalb hat TCB_CLKSEL_CLKTCA_gc den Wert 0x4 bzw. 0b100.

Wenn ihr die Bit Group TCB_CLKSEL maskieren wollt, gibt es dafür die Bit Group Mask. Hier die allgemeine Form:

  • MODUL_BITGRUPPE_gm

TCB_CLKSEL_gm hat den Wert 0x6 bzw. 0b110, wie ihr einfach mit Serial.println(TCB_CLKSEL_gm, BIN) prüfen könnt.

Bit Positions und Bit Group Positions

Dann gibt es noch die Bit und Bit Group Positions:

  • MODUL_BIT_bp
  • MODUL_BITGRUPPE_gp

Sie geben, wenig überraschend, die Position des Bits oder der Bitgruppe im Register an. Wenn ihr also den aktuellen CLKSEL Wert des Timers TCB1 ermitteln wollt, könntet ihr das so machen:

Serial.println( ((TCB1.CTRLA  &  TCB_CLKSEL_gm) >> TCB_CLKSEL_gp) );

Das klingt vielleicht alles erst einmal ein wenig kompliziert, aber nach kurzer Zeit gewöhnt man sich an diese Schreibweise. Es ist ein gewisser Schreibaufwand, es zahlt sich aber aus, denn dadurch ist der Code auch nach einigen Monaten noch gut verständlich.

Falls ihr euch mal anschauen wollt, wie die Strukturen für die Module und Register aufgebaut sind, dann schaut mal in „C:\Program Files (x86)\Arduino\hardware\tools\avr\avr\include\avr\iom4809.h“ bzw. „…iom4808.h“.

Orientierung im Datenblatt

Diese neue Art Register zu programmieren ist anfangs gewöhnungsbedürftig. Was bei der Orientierung hilft, ist das Datenblatt des ATmega4808/4809. Es ist nach den Modulen geordnet und zu jedem Modul gibt es eine „Register Summary“ mit Links zu den einzelnen Registern. 

Arduino Nano Every / Nano 4808 PORTx Register Summary
PORTx Register Summary

I/O Steuerung – Modul PORTx

PORTx Register

DIR-, OUT- und IN-Register

Wie ihr in der Register Summary oben seht, gibt es eine Reihe von Registern, deren Bezeichnung mit „DIR“ oder „OUT“ beginnt. Die „DIR …“ Register steuern, welche Pins als INPUT oder OUTPUT dienen. Mithilfe der „OUT …“ Register legt ihr den Pinlevel fest, also HIGH oder LOW. Der zweite Namensteil bedeutet:

  • SET: setzt Bits in OUT und DIR → OUTPUT bzw. HIGH
  • CLR: löscht die Bits in OUT und DIR → INPUT bzw. LOW
  • TGL: toggelt den Zustand, d. h. kehrt ihn um.

Ihr könnt auch direkt in das DIR und das OUT Register schreiben, aber das Schöne an OUTSET, OUTCLR, OUTTGL und den „DIR“-Pendants ist, dass die Anweisungen Pin-selektiv erfolgen. Beispiel:

  • PORTx.OUT = PINy_bm; setzt den Pin y des Port x auf HIGH und alle anderen Pins des Port x auf LOW.
  • PORTx.OUTSET = PINy_bm; wirkt nur auf den Pin y, d. h. die Anweisung entspricht PORTx.OUT |= PINy_bm;.

In den IN Registern könnt ihr den Zustand der Pins auslesen (digitalRead(), sozusagen). Wenn ihr hingegen ein Bit in das PORTx.IN Register schreibt, dann wird das entsprechende Bit in PORTx.OUT getoggelt. Das IN Register ist das Pendant zum PINx Register der traditionellen AVR Mikrocontroller.

Kontrollregister PINxCTRL

Jeder Pin besitzt ein eigenes Kontrollregister, nämlich PINxCTRL:

Arduino Nano Every / Nano 4808 Register - PORTn.PINxCTRL
PORTn.PINxCTRL Register

Das INVEN Bit ermöglicht euch den INPUT und OUTPUT Wert zu invertieren. PULLUPEN aktiviert den internen Pull-Up Widerstand. Mit ISC legt ihr das Interruptverhalten fest oder schaltet die Digitalfunktion des Pins ganz ab (INPUT_DISABLE):

ISC[2:0] Bit Group Configurations
ISC[2:0] Bit Group Configuration

Beispielsketche für PORTx

Pins schalten

Ich komme noch einmal auf den Blink Sketch zurück und möchte zeigen, dass es noch andere Möglichkeiten gibt, um dieselbe Wirkung zu erreichen:

void setup() {
    PORTE.DIRSET = PIN2_bm; // set PE2 (D13) to OUTPUT
    // PORTE.DIR |= (1<<2);
    // PORTE.DIR |= (1<<PIN2_bp); // bp = bit position
}

void loop() {
    PORTE.OUTSET = PIN2_bm;  // set PE2 to HIGH
    // PORTE.OUT |= PIN2_bm;
    delay(200);
    PORTE.OUTCLR = PIN2_bm;
    // PORTE.OUT &= ~PIN2_bm; 
    delay(200);
   
    // Alternative: toggle pin:
    // PORTE.OUTTGL = PIN2_bm;
    // delay(200);  
}

Ich denke, die Alternativen sind einigermaßen selbsterklärend. Und wie schon erwähnt, um das ganze auf einen „Nano 4808“ zu übertragen, tauscht PORTE gegen PORTC. 

Pinlevel auslesen

Hier ein Beispiel, wie ihr einzelne Pins auslesen könnt:

void setup() {
    Serial.begin(115200);
    PORTB.DIRCLR = PIN2_bm; // PB2 = D5 (for the Nano4808 select a different pin)
}

void loop(){
    if(PORTB.IN & PIN2_bm){
        Serial.println("HIGH");
    }
    else{
        Serial.println("LOW");
    }
    delay(1000);
}

Interrupt einrichten

Es folgt ein einfaches Beispiel für einen Interrupt an PB2 (=D5 auf dem Nano Every).  Beachtet, dass der Interrupt nicht automatisch durch den Aufruf der ISR gelöscht wird. Ihr müsst das Bit im zuständigen Interruptflagregister „manuell“ löschen.

volatile bool event = false;

ISR(PORTB_PORT_vect){
    PORTB.INTFLAGS = PIN2_bm; // clear interrupt
    event = true;
}

void setup() {
    Serial.begin(115200);
    PORTB.DIRCLR = PIN2_bm;  // redundant in this case
    PORTB.PIN2CTRL = PORT_PULLUPEN_bm | PORT_ISC_BOTHEDGES_gc; 
}

void loop(){
    if(event){
        Serial.println("Interrupt!");
        event = false;
    }
}

Virtuelle Ports

Wenn ihr Portmanipulation à la PORTB.OUT = (1 << 2); einsetzt, solltet ihr anstelle der Portregister die virtuellen Portregister nutzen. Dazu setzt ihr einfach ein „V“ vor den Port, also: VPORTB.OUT = (1 << 2);. Die virtuellen Ports sind Kopien der Ports im unteren Adressraum. Die Portmanipulationen werden mit den virtuellen Ports schneller durchgeführt. Die Ausführung des folgenden Codes benötigte 629 Millisekunden:

for(unsigned long i=0; i<1000000;i++){
        PORTE.OUT = 0;
        PORTE.OUT = (1<<2);
}

Derselbe Code, nur mit VPORTE anstelle PORTE benötigte 503 Millisekunden. Das sind keine riesigen Unterschiede, aber immerhin.

Die zur Verfügung stehenden VPORT Register sind DIR, OUT, IN und INTFLAGS.

Ein- / Ausgänge neu zuordnen – Modul PORTMUX

Die Register des Moduls PORTMUX erlauben es euch, eine Reihe von Ein- bzw. Ausgängen alternativen Pins zuzuordnen. Hier die Registerzusammenfassung:

Register Summary PORTMUX
Register Summary PORTMUX

Als Beispiel leiten wir die Timer A Ausgänge um. Das zuständige PORTMUX Register heißt TCAROUTEA. Als Bit Group Configuration Mask für TCA0[2:0] steht euch PORTn mit n = A bis F zur Verfügung. Eine Zuordnung zu Port B würdet ihr also so vornehmen:

  • PORTMUX.TCAROUTEA = PORTMUX_TCA0_PORTB_gc;

Und genau das ist auf dem Arduino Nano Every so implementiert, und zwar sowohl beim megaAVR als auch beim MegaCoreX Paket. Beim „Nano 4808“ sind die Timer A Ausgänge auf den Port D umgeleitet.

Falls ihr andere Ein- oder Ausgänge umleiten wollt, dann müsst ihr nur die entsprechenden Bit Masks bzw. Bit Group Configuration Masks heraussuchen.

Das Eventsystem – Modul EVSYS

Das Eventsystem ist richtig cool! Es erlaubt den Peripherie-Einheiten („Peripherals“), also Timern, ADC, USART, usw., Signale an andere Peripherie-Einheiten zu senden und das ohne Beteiligung der CPU. Später zeige ich ein Beispiel, wie ein Timer regelmäßig ADC Messungen auslösen kann. Normalerweise würdet ihr das über delay(), millis() oder Interrupts realisieren. Alle diese Optionen kosten eine gewisse Zeit und können den Code komplex machen.

Zur Übertragung der Signale stehen 8 Kanäle (Channel0 bis Channel7) zur Verfügung. Den Sender des Signals nennt man den Generator, der Empfänger ist der User. Die meisten User haben ein eigenes Event Control Register, in dem weitere Einstellungen vorgenommen werden. Diese Register sind Teil der Peripherie-Module.

EVSYS Register

Register CHANNELn – Definition des Generators

In den Registern CHANNELn (mit n = 0 bis 7) legt ihr den Generator fest:

Arduino Nano Every / Nano 4808 Register: CHANNELn
Register EVSYS.CHANNELn

Die meisten Generatoren können alle Channel nutzen. Es gibt aber auch Ausnahmen. Beispielsweise können die Pins des Port A nur die Channel 0 und 1 benutzen. Hier ein kleiner Auszug aus der Tabelle im Datenblatt (Abschnitt 14.5.2):

Event Generatoren des Arduino Nano Every / Nano 4808 - einige Beispiele
Event Generatoren – einige Beispiele

Register USERn – Definition der Nutzer

Die Event-User legt ihr im Register USERn fest:

Register EVSYS.USERn
Register EVSYS.USERn

Das „n“ in USERn ersetzt ihr durch den Namen des Users. Die vollständige Tabelle findet ihr im Datenblatt in Abschnitt 14.3.2.4. Hier ein Ausschnitt:

Event-User
Event-User – einige Beispiele

Ein einfaches Eventsystem Beispiel

Zum besseren Verständnis schauen wir uns ein kleines Beispiel an. Für den Arduino Nano Every hängt ihr einen Taster an PB0 (D9), der beim Drücken ein LOW Signal gibt. An PB2 (D5) hängt ihr eine geeignete LED.

Jetzt nutzen wir den PB0 als Event Generator und EVOUTB (PB2 / D5) als User. Für den Port B stehen uns die Channel 0 und 1 zur Verfügung. Im Beispiel nutzen wir den Channel 0.

void setup() {
    PORTB.DIRSET = PIN2_bm; // PB2 (D5) as OUTPUT
    PORTB.DIRCLR = PIN0_bm; // PB0 (D9) as INPUT
    PORTB.PIN0CTRL = PORT_PULLUPEN_bm;
   
    EVSYS.CHANNEL0 = EVSYS_GENERATOR_PORT1_PIN0_gc;  // PB0 is event generator for channel 0
    EVSYS.USEREVOUTB = EVSYS_CHANNEL_CHANNEL0_gc;  // EVOUTB is user of event channel 0
}

void loop(){}

PB5, bzw. EVOUTB, spiegelt den Zustand von PB0 wider. Im Normalzustand leuchtet die LED, beim Drücken des Tasters geht sie aus. Das mag auf den ersten Blick nicht beeindrucken, auf den zweiten aber schon. Weder fragen wir den Level von PB0 ab, noch haben wir einen externen Interrupt dafür eingerichtet. Überdies findet ihr keine Anweisung im laufenden Programm, dass die LED an- oder ausgehen soll. Die Vorgänge werden im Hintergrund gesteuert, ohne dass euer laufendes Programm irgendetwas dafür tun muss. Später folgen sinnvollere Anwendungen. 

Variante für den „Nano 4808“

Den Sketch für den „Nano 4808“ umzuschreiben ist nicht schwer. Dieses wäre eine Option:

void setup() {
    PORTA.DIRSET = PIN2_bm; // PA2 (D4)
    PORTA.DIRCLR = PIN0_bm;  // PA0 (D2)
    PORTA.PIN0CTRL = PORT_PULLUPEN_bm;
        
    EVSYS.CHANNEL0 = EVSYS_GENERATOR_PORT0_PIN0_gc; 
    EVSYS.USEREVOUTA = EVSYS_CHANNEL_CHANNEL0_gc;
}

void loop(){}

Die Timer des Arduino Nano Every / „Nano 4808“

Der ATmega4809 besitzt:

  • Einen 16-Bit Timer A (TCA0)
  • vier 16-Bit Timer B (TCB0, TCB1, TCB2, TCB3),
  • einen Real-Time Counter (RTC),
  • einen Periodic Interrupt Timer (PIT),
  • und einen Watchdog Timer (WDT).

Der ATmega4808 unterscheidet sich nur dadurch, dass er lediglich drei Timer B besitzt.

Timer A – Modul TCAn

Zunächst ein paar allgemeine Anmerkungen zum Timer A:

  1. Der Timer A ist an den Systemtakt gekoppelt, er kann aber im Register TCA0.SINGLE.CTRLA mit einem Teiler verlangsamt werden.
  2. Das megaAVR Boardpaket stellt den Teiler des Timer A auf 64 ein und nutzt den Timer A Takt für den Timer B. Der Timer B3 ist wiederum für zeitabhängige Funktionen wie millis() oder delay() zuständig. Ihr solltet den Teiler also nicht verstellen, wenn ihr das Arduino megaAVR Paket verwendet. Der Timer A Takt ist damit auf 16000000 MHz / 64 = 250 kHz festgelegt. Auch das MegaCoreX Paket nutzt den Timer B3 (bzw. B2 beim „Nano 4808“) für delay() und millis(). Allerdings verwendet der Timer B3 den Systemtakt, sodass ihr den Timer A Teiler bedenkenlos verstellen könnt.
  3. Der Timer A zählt im normalen (Single) Modus bis zum Maximalwert PER. Es stehen drei Compare Channel zur Verfügung. Die Compare-Werte heißen CMP0, CMP1 und CMP2.
  4. Im geteilten Modus (Split Mode) werden aus dem 16-Bit Timer zwei 8-Bit Timer mit je 3 Compare-Werten.
  5. Die Register für den normalen Modus sprecht ihr mit TCA0.SINGLE.Registername an, die Register für den Split Modus mit TCA0.SPLIT.Registername.
  6. Standardmäßig sind die Ausgänge für den Timer A beim Arduino Nano Every dem PORT B zugeordnet. Eine Zuordnung zu anderen Ports ist möglich, aber die Umleitung gilt immer für alle Ausgänge. Der Pin 0 ist für den Channel 0 zuständig (TCA0-0), Pin 1 für Channel 1 (TCA0-1) und Pin 2 für Channel 2 (TCA0-2). Im Split Modus geht es entsprechend weiter mit Pin 3 für Channel 3 (TCA0-3), usw. Beim „Nano 4808“ sind die Timer A Ausgänge dem Port D zugeordnet.

Timer A Register für den normalen (Single) Modus

Einige Register sind im normalen und Split Modus identisch. Das habe ich dann entsprechend vermerkt.

Dieser Beitrag ist keine vollständige Registerdokumentation. Ich empfehle euch, parallel in das Datenblatt zu schauen.

Control A Register TCAn.CTRLA

Im Control Register CTRLA stellt ihr den Takt bzw. den Teiler ein und aktiviert den Timer Counter A.

TCAn.CTRLA Register
TCAn.CTRLA Register

Die Bitgruppe CLKSEL (Clock Select) ist für den Teiler zuständig. Die folgenden Bit Group Configuration Masks stehen zur Auswahl:

Arduino Nano Every: CLKSEL Optionen für TCA0 im Normal Mode
CLKSEL – Optionen

Mit dem ENABLE Bit aktiviert ihr den Timer TCA0. Ihr könnt ja mal die Einstellung von CTRLA mit Serial.println(TCA0.SINGLE.CTRLA) prüfen. Das Ergebnis sollte 0b1011 sein, d. h. der Timer ist aktiviert und der Teiler ist 64. Ihr braucht den Timer also nicht selbst zu aktivieren. Und noch einmal: Bei Verwendung des megaAVR Paketes solltet ihr die Einstellung nicht ändern.

Control B Register TCAn.CTRLB

TCAn.CTRLB Register
TCAn.CTRLB Register

Mit den Compare n Enable Bits (CMPnEN) aktiviert ihr die Ausgänge der Compare Channel. Auf das Auto Lock Update Bit (ALUPD) gehe ich hier nicht ein. Die Bitgruppe WGMODE[2:0] legt den Wave Form Generation Modus fest:

Arduino Nano Every: WGMODE Optionen für TCA0 im Normal Mode
Wave Form Generation Mode Optionen

Nehmt das erst einmal so hin, später wird es klarer.

Control D Register TCAn.CTRLD

Im Control D Register stellt ihr den Split Modus ein. Dabei ist es egal, ob ihr die Einstellung über TCA0.SINGLE.CTRLD oder TCA0.SPLIT.CTRLD vornehmt.

Arduino Nano Every / Nano 4808 Register: TCAn.SINGLE.CRTLD
Register TCAn.SINGLE.CTRLD / TCAn.SPLIT.CTRLD

Interrupt Control Register TCAn.INTCTRL

Im Interrupt Control Register stellt ihr ein, welche Interrupts aktiviert werden sollen. Es stehen Interrupts für die Compare Matches und den Overflow zur Verfügung:

Arduino Nano Every / Nano 4808 Register: TCAn.SINGLE.INTCTRL / TCAn.SINGLE.INTFLAGS
TCAn.SINGLE.INTCTRL / TCAn.SINGLE.INTFLAGS

Zum Anzeigen der Interrupts gibt es das „baugleiche“ Interrupt Flagregister INTFLAGS, das dieselben Bitnamen verwendet.

Event Control Register TCAn.EVCTRL

Ihr könnt den Timer A so einstellen, dass er nicht über den Systemtakt, sondern externe Events gesteuert wird. Dazu setzt ihr das „Enable Count On Event Input“ Bit CNTEI im Event Control Register EVCTRL.

Arduino Nano Every / Nano 4808 Register: TCAn.SINGLE.EVCTRL
TCAn.SINGLE.EVCTRL

Mithilfe der Bit Group Configuration EVACT stellt ihr ein, welche Events gezählt werden, bzw. wie gezählt wird:

EVACT Bit Group Configurations
EVACT Bit Group Configurations

Mit den ersten beiden Einstellungen zählt ihr die Flanken, d.h. die Anzahl der Events. Mit den beiden anderen Einstellungen zählt ihr so lange über den Systemtakt (ggf. mit Divider), wie der durch das Event verursachte Zustand anhält.

Weitere Register

Dann gibt es noch eine Reihe weitere Register, die lediglich Zahlen enthalten:

  • CNT (16 Bit): ist das Counter-Register zum Timer A.
  • PER (16 Bit): ist der TOP-Wert für die meisten Wave Form Generation Modes.
  • CMP0, CMP1, CMP2 (16 Bit): enthalten die Compare-Werte.
  • PERBUF: ihr könnt PER direkt in das PER-Register schreiben. Ein Update von PER kann aber zu ungünstigen Zeitpunkten stattfinden, die dann zu undefinierten Zuständen führen. Bei der Übertragung von PERBUF nach PER werden entsprechende Maßnahmen getroffen. Der Weg ist also sicherer.
  • CMP0BUF, CMP1BUF, CMP2BUF: siehe PERBUF.

Im normalen (Single) Modus hat der Timer A Counter eine Größe von 16 Bit. Er lässt sich aber auch in zwei 8-Bit Timer teilen (Split Modus). Im normalen Modus stehen drei Compare Channels zur Verfügung, im Split Modus sind es sechs. Entsprechend lassen sich 3 bzw. 6 PWM Ausgänge mithilfe des Timer A realisieren. 

Die hier besprochenen Boardpakete für den Arduino Nano Every ordnen die Timer A Ausgänge PORTB zu und nutzen sie für analogWrite(). Da nur PB0 (Pin 9), PB1 (Pin 10) und PB2 (Pin 5) als Board Pins ausgeführt sind, gibt es auch nur diese drei analogWrite() Pins, die über den Timer A gesteuert werden.

Das Boardpaket MegaCoreX hat die Timer A Ausgänge für den „Nano 4808“ dem Port D zugeordnet. Da PD0 bis PD5 ausgeführt sind und der Split Mode aktiviert ist, stehen sechs PWM Pins zur Verfügung (über Timer A).

Beispielsketche für den Timer A im normalen Modus

Nach der ganzen Theorie gibt es endlich wieder Beispielsketche.

Overflow Interrupt

Wir beginnen ganz einfach mit einem Overflow Interrupt. Den nutzen wir, um die Board-LED des Arduino Nano Every zu toggeln.

ISR(TCA0_OVF_vect){  // ISR für den Timer A over
    TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm; // Clear the interrupt flag (needed!)
    PORTE.OUTTGL = PIN2_bm; // Toggle PE2 (Board-LED) 
}

void setup() {
    PORTE.DIRSET = PIN2_bm; // PE2 auf OUTPUT
    // TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV64_gc | TCA_SINGLE_ENABLE_bm; // redundant
    // TCA0.SINGLE.CTRLB = 0; // no output, wave form: normal; redundant
    TCA0.SINGLE.PERBUF = 49999; // Set TOP for Timer A 
    TCA0.SINGLE.INTCTRL = TCA_SINGLE_OVF_bm; // enable Timer A overflow interrupt
}

void loop() {}

Bei einer Takrate von 16 MHz, einem Teiler von 64 und einem PER von 49999 (= 50000 Schritte) ist die Überlauffrequenz 16000000 / 64 / 50000 = 5 Hz.

Wollt ihr den Sketch auf den „Nano 4808“ übertragen, müsst ihr lediglich PORTE durch PORTC ersetzen. 

Single Slope PWM

Im nächsten Beispiel erzeugen wir mit dem Arduino Nano Every ein PWM Signal mit einer Frequenz von 250 Hz und einem Duty Cycle von 20 % an PB1 / D10. Die 250 Hz erreichen wir, indem wir PER auf 999 setzen (bei 16 MHz). PB1 ist der Ausgang vom Compare Channel 1. Entsprechend müssen wir das CMP1EN Bit setzen. Der Duty Cycle ist CMPn / (PER +1).

void setup() {
    PORTB.DIRSET = PIN1_bm;  // PB1 (D10)
    TCA0.SINGLE.CTRLD &= ~TCA_SINGLE_SPLITM_bm; // needed for the MegaCoreX board package
    TCA0.SINGLE.PERBUF = 999;
    TCA0.SINGLE.CMP1BUF = 200;
    TCA0.SINGLE.CTRLB = TCA_SINGLE_CMP1EN_bm | TCA_SINGLE_WGMODE_SINGLESLOPE_gc;
}

void loop() {}

Um den Sketch auf das „Nano 4808“ Board zu übertragen, ersetzt ihr PORT B durch D und erhaltet das PWM-Signal am Pin A1. Wenn euch nicht klar ist, warum das so ist, dann schaut noch einmal auf die Pinout Diagramme.

Wichtig ist noch, dass das MegaCoreX Paket den Split Modus als Voreinstellung aktiviert hat. Ihr müsst ihn also deaktivieren. Darauf komme ich gleich noch einmal zurück.

Events zählen

In diesem Beispiel für den Arduino Nano Every steuern wir den Timer A über Events. Als Event dient eine steigende Flanke an PB1 (Pin 10). Dazu verbindet ihr PB1 über einen Taster mit GND. PB1 zieht ihr über den internen Pull-Up Widerstand auf HIGH Niveau. Das Event wird also beim Loslassen des Tasters erzeugt. Ihr könnt die Logik auch umdrehen, braucht dann aber noch einen externen Pull-Down Widerstand.

Der folgende Sketch informiert uns, wenn der Taster 10 bzw. 20 Male gedrückt wurde:

volatile bool tca0Ovf = false;  // Flag für TCA OVF
volatile bool cmp0Match = false;  // Flag für CMP0 Match

ISR(TCA0_OVF_vect){
    TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm; /* Clear the interrupt flag */
    tca0Ovf = true;
}

ISR(TCA0_CMP0_vect){
    TCA0.SINGLE.INTFLAGS = TCA_SINGLE_CMP0_bm; /* Clear the interrupt flag */
    cmp0Match = true;
}

void setup() {
    Serial.begin(115200);
    PORTB.DIRCLR = PIN1_bm; // PB1 (Pin 10) as input 
    PORTB.PIN1CTRL = PORT_PULLUPEN_bm; // Pullup enable
    EVSYS.CHANNEL1 = EVSYS_GENERATOR_PORT1_PIN1_gc; // PB1 as event generator
    EVSYS.USERTCA0 = EVSYS_CHANNEL_CHANNEL1_gc;  // TCA0 as event user
    TCA0.SINGLE.CTRLD &= ~TCA_SINGLE_SPLITM_bm;  // Set single mode
    TCA0.SINGLE.CTRLA = 0;  // switch off the timer, set clock divider to DIV1 
    TCA0.SINGLE.PER = 19;  // TCA0 counter limit
    TCA0.SINGLE.CMP0 = 9;  // Compare match value
    TCA0.SINGLE.CNT = 0;  // Set TCA0 counter to 0
    TCA0.SINGLE.EVCTRL = TCA_SINGLE_EVACT_POSEDGE_gc | TCA_SINGLE_CNTEI_bm; // count pos. edge of events (RISING) & enable event count
    TCA0.SINGLE.CTRLB = TCA_SINGLE_WGMODE_NORMAL_gc; // normal mode => TOP = PER
    TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm; // clear the overflow interrupt flag
    TCA0.SINGLE.INTCTRL = TCA_SINGLE_CMP0_bm | TCA_SINGLE_OVF_bm; // enable CMP0 and OVF interrupts 
    TCA0.SINGLE.CTRLA = TCA_SINGLE_ENABLE_bm;
}

void loop(){
    if(cmp0Match){
        Serial.println("Key has been pressed 10 times");
        cmp0Match = false;
    }
    
    if(tca0Ovf){
        Serial.println("Key has been pressed another 10 times");
        Serial.println("Timer/Counter A restarts");
        tca0Ovf = false;
    }
    // Serial.println(TCA0.SINGLE.CNT);
    // delay(100); // delay won't work with the Arduino board package!
}

 

Ich habe versucht, den Code durch Kommentare zu erklären. Ich hoffe, dass das ausreichend verständlich ist. Ein paar Anmerkungen sind aber trotzdem noch notwendig:

  • Das MegaCoreX Paket hat den Split Modus voreingestellt. Deshalb löschen wir das SPLITM Bit in CTRLD.
  • Damit der Timer A uns während der Einstellungen nicht stört, schalten wir ihn ab.
  • Wir lassen uns per Compare 0 Interrupt und Overflow Interrupt informieren, wenn der Taster zehn bzw. zwanzig Male gedrückt wurde.
    • Die Interrupts werden erst ausgelöst, wenn CMP0 und PER überschritten werden. Deswegen hat CMP0 den Wert 9 und PER ist 19.
  • Das Einstellen des Compare 0 Registers und des PER Registers über die Buffer Register (CMP0BUF, PERBUF) funktioniert hier nicht. Warum das so ist, kann ich nicht sagen.
  • Wenn ihr das megaAVR Paket verwendet, funktioniert aus den schon mehrfach genannten Gründen kein delay() mehr. MegaCoreX hat das Problem nicht.

Um den Sketch auf den „Nano 4808“ umzuschreiben, könntet ihr von PB1 auf PA1 (Pin 3) wechseln und müsstet dann noch den Event Generator auf EVSYS_GENERATOR_PORT0_PIN1_gc umstellen.

Timer A Register im Split Modus

Im Split Modus wird der Timer A in einen High- und einen Low-Teil geteilt. Entsprechend gibt es anstelle des 16-Bit CNT Registers ein 8-Bit HCNT und ein 8-Bit LCNT Register. Diese beiden Counter Register zählen unabhänging voneinander. Auch das PER Register und die Compare Register werden geteilt. Aus PER wird HPER und LPER, aus CMPn wird HCMPn und LCMP usw.

Die Ausgänge für den Timer A im Split Modus aktiviert ihr im Control B Register (TCA0.SPLIT.CTRLB):

Arduino Nano Every / Nano 4808 Register: TCAn.SPLIT.CTRLB
Register TCAn.SPLIT.CTRLB

Die LCMPnEN Bits sind für die Ausgänge TCA0-n zuständig, die HCMPnEN Bits für die Ausgänge TCA0-(n+2).

Im Control D Register könnt ihr Interrupts für die Low Compare Channels setzen. Für die High Compare Channels stehen keine Interrupts zur Verfügung.

Im Split Modus zählen das HCNT und das LCNT Register ausschließlich abwärts. Entsprechend gibt es keine Overflow-, sondern nur Underflow-Interrupts („UNF“). Die Bits zum Aktivieren der Interrupts sind HUNF und LUNF.

Arduino Nano Every / Nano 4808 Register: TCAn.SPLIT.CTRLB
Register TCAn.SPLIT.CTRLB

Die zugehörigen Namen der Interruptvektoren bildet ihr nach dem Schema TCA0_Interruptbit_vector, also z. B. TCA0_LCMP1_vector.

Beispielsketche für den Timer A im Split Modus

Split Mode Timer A – Arduino Nano Every

Dann probieren wir den Timer A im Split Modus einmal aus und beginnen mit dem Arduino Nano Every. Ziel ist es, ein PWM Signal mit Duty-Cycle von 25 % an PB0 (TCA0-0) zu erzeugen. Dafür ist der Low Counter des Compare Channel 0 zuständig.

void setup() {
    PORTB.DIRSET = PIN0_bm;  // PB0 (D9 = Output)
    TCA0.SPLIT.CTRLD = TCA_SPLIT_SPLITM_bm; // needed for the Arduino board package
    TCA0.SPLIT.LPER = 255;
    TCA0.SPLIT.LCMP0 = 64; // Duty cycle = 64 / (255 + 1) * 100 = 25 %
    TCA0.SPLIT.CTRLB = TCA_SPLIT_LCMP0EN_bm;
}

void loop() {}

Da PB3, PB4 und PB5 nicht als Board Pins ausgeführt sind, könnt ihr nur den LCNT für PWM benutzen, sofern ihr nicht die TCA0 Ausgänge mit PORTMUX.TCAROUTEA = PORTMUX_TCA0_PORTn_gc; einem anderen Port zuordnet.

Split Mode Timer A – „Nano 4808“

Der „Nano 4808“ hat die TCA0 Ausgänge dem Port D zugewiesen. Hier können wir direkt alle sechs PWM Kanäle des Timer A im Split Modus nutzen. Hier ein Beispiel für ein PWM Signal an PD4 (TCA0-4 = D22):

void setup() {
    PORTD.DIRSET = PIN4_bm;  // PD4 (D22 = Output)
    // TCA0.SPLIT.CTRLD = TCA_SPLIT_SPLITM_bm;
    TCA0.SPLIT.HPER = 255;
    TCA0.SPLIT.HCMP1 = 64;
    TCA0.SPLIT.CTRLB = TCA_SPLIT_HCMP1EN_bm;
}

void loop() {}

analogWrite() auf den 4808 / 4809 Boards

Der Arduino Nano Every und der „Nano 4808“ bzw. die Boardpakete tun im Prinzip das, was wir eben getan haben, um analogRead() Pins bereitzustellen. Der Arduino Nano Every nutzt TCA0-0, TCA0-1 und TCA0-2 und der Nano 4808 TCA0-0 bis TCA0-5. Die anderen analogRead() Pins werden über Timer B Ausgänge realisiert.

Falls ihr eigene PWM Kreationen an bestimmten Pins mit analogRead() an anderen Pins in einem Sketch mischen wollt, dann müsst ihr aufpassen, dass das Ganze verträglich ist. Wenn ihr PER, HPER oder LPER setzt, kann das, je nach Board oder Boardpaket, direkte Auswirkungen auf analogRead() haben. Gleiches gilt, wenn ihr auf den normalen oder den Split Modus wechselt oder gar die TCA-Ausgänge einem anderen Port zuordnet.

Timer B – Modul TCBn

Der Arduino Nano Every, bzw. der zugrundeliegende ATmega4809 besitzt vier 16-Bit Timer B (TCB0 bis TCB3). Der „Nano 4808“ verfügt über drei dieser Timer (TCB0 bis TCB2). Die Unterschiede zum Timer A werden bei der Besprechung der Register deutlich.

Für Zeitmessungen (millis(), delay())  benutzen die Boardpakete den TCB3 (Arduino Nano Every) bzw. den TCB2 („Nano 4808“). Änderungen an den Einstellungen dieser Timer führen zu Problemen mit den genannten Funktionen.

TCB0 und TCB1 werden von allen hier besprochenen Boards und Bordpaketen zur Bereitstellung von analogWrite() Pins genutzt. Wenn ihr diese Timer für andere Zwecke nutzt oder die Einstellungen ändert, verliert ihr die analogWrite() Funktion der betroffenen Pins.

Für TCB2 und TBC3 sind auf dem Arduino Nano Every keine Ausgänge verfügbar, da die zuständigen Pins PC0/PB4 bzw. PC1/PB5 nicht ausgeführt sind.

Timer B Register

Auch hier möchte ich noch einmal darauf hinweisen, dass mein Beitrag nicht alle vorhandenen Register abdeckt.

Control A Register TCBn.CTRLA

Die wichtigsten Einstellungen in den Control A Registern der Timer B sind die Taktrate (CLKSEL[2:0]) und Enable. Für die Taktrate stehen der Systemtakt, der halbe Systemtakt oder der Takt des Timer A zur Auswahl.

Arduino Nano Every / Nano 4808 Register TCBn.CTRLA
Register TCBn.CTRLA

Control B Register TCBn.CTRLB

Im Control B Register legt ihr einen der acht Count Modes fest, die die Timer B zu einem vielseitig einsetzbarem Werkzeug machen. Ich liste die Count Modes hier erst einmal nur auf, denn erklären lassen sie sich am besten anhand von Beispielen.

Arduino Nano Every / Nano 4808 Register TCBn.CTRLB
Register TCBn.CTRLB
CTNMODE[2:0] Bit Group Configurations
CTNMODE[2:0] Bit Group Configurations

Die weiteren Bits bewirken:

  • ASYNC (Asynchronous Enable): erlaubt asynchrone Updates des TCB Output Signals im Single Shot Modus.
  • CCMPINIT (Compare / Capture Pin Initial Value): setzt das anfängliche Level des Output Pins (0 = LOW, 1 = HIGH).
  • CCMPEN (Compare / Capture Output Enable): aktiviert den Compare / Capture Output. Im Gegensatz zum Timer A gibt es nur einen Compare Output pro Timer B, dafür habt ihr aber eine größere Anzahl an Timern.

Interrupt Control Register TCBn.INTCTRL

Das Interrupt Control Register ist sehr übersichtlich. Ihr könnt dort den Capture Interrupt aktivieren. Die Bedingung für Auslösung des Interrupts hängt vom Count Mode ab. Ich komme darauf in den Beispielen zurück. Eine Übersicht findet ihr im Datenblatt im Abschnitt 21.5.5.

Arduino Nano Every / Nano 4808 Register: TCBn.INTCTRL
TCBn.INTCTRL / TCBn.INTFLAGS

Der Interruptvektor heißt TCBn_INT_vect und nicht – wie man erwarten würde – TCBn_CAPT_vect. Das ist leider etwas verwirrend.

Event Control Register TCBn.EVCTRL

Auch die Wirkung des EDGE Bits im Event Control Register EVCTRL hängt vom Count Mode ab. Wartet auf die Beispiele oder schaut euch die Übersicht im Datenblatt im Abschnitt 21.5.3 an.

Das FILTER Bit aktiviert einen Rauschfilter, der dafür sorgt, dass der Eventzustand vier Takte lang konstant sein muss, bevor das Event als valide gilt.

Das Capture Event Input Enable Bit CAPTEI aktiviert den Event Eingang. Im Gegensatz zum Timer A kann der Timer B keine Flanken (Events) zählen, sondern die Flanke startet oder stoppt den Zähler.

Arduino Nano Every / Nano 4808 Register: 
TCBn.EVCTRL
TCBn.EVCTRL

Weitere Register

Das 16-Bit Register CNT enthält – wenig überraschend – den Zählerstand des Timer B. Genau genommen handelt es sich um zwei Register, die ihr gemeinsam über TCBn.CNT oder getrennt über TCBn.CNTL und TCBn.CNTH ansprechen könnt.

Das Capture / Compare Register CCMP hat, wie der Name schon vermuten lässt, verschiedene Aufgaben. Als Compare Register steuert es Interrupts und PWM, als Capture Register nimmt es Counterstände als Messwerte auf.

Beispielsketche für die Timer B

Wir werden uns jetzt für jeden Count Mode einen Beispielsketch anschauen. Die Sketche sind für den Arduino Nano Every geschrieben. Die Übertragung auf den „Nano 4808“ sollte euch aber nicht schwerfallen.

Periodic Interrupt

Im Periodic Interrupt Mode zählt der Timer bis CCMP, löst den CAPT Interrupt aus (sofern aktiviert) und beginnt wieder von vorn. In folgenden Beispielsketch nutzen wir den CAPT Interrupt, um die Board-LED zu toggeln. Da wir den Timer A Takt (Voreinstellung DIV64) verwenden und als CCMP den Wert 50000 einstellen, ist die Toggle-Frequenz: 16000000 / (64 * 50000) = 5 Hertz. 

ISR(TCB0_INT_vect){
    TCB0.INTFLAGS = TCB_CAPT_bm; /* Clear the interrupt flag */
    PORTE.OUTTGL = PIN2_bm; /* Toggle PE2 (D13 / LED-BUILTIN) */
}

void setup() {
    PORTE.DIRSET = PIN2_bm;
    PORTE.OUTSET = PIN2_bm;
    // TCA0.SINGLE.CTRLA |= TCA_SINGLE_CLKSEL_DIV1024_gc;
    TCB0.CCMP = 50000; // Toggle frequency = 5 Hz
    TCB0.INTCTRL = TCB_CAPT_bm;
    TCB0.CTRLA = TCB_CLKSEL_CLKTCA_gc | TCB_ENABLE_bm; // use TCA clock / enable
    TCB0.CTRLB = TCB_CCMPEN_bm;
}

void loop() {}

Wenn ihr die Zeile 9 entkommentiert, verringert ändert ihr den Clock Divider von 64 auf 1024, also um den Faktor 16. Die Toggle-Frequenz verringert sich damit auf 0.3125 Hz (→ Periode 3.2 s).  Aber nicht vergessen: das Ändern des Timer A Taktes führt bei Verwendung des Arduino megaAVR Boardpaketes für den Arduino Nano Every zu Problemen mit delay() & Co. Kein Problem ist das hingegen für das MegaCoreX Paket.

8 Bit PWM Modus

PWM Signale sind mithilfe der Timer B sehr einfach darstellbar. Das untere Byte des Compare Registers CCMP, nämlich CCMPL, legt zusammen mit dem Timer B Takt die PWM Frequenz fest. Der Duty Cycle ergibt sich aus dem Verhältnis von CCMPH, dem oberen Byte, zu CCMPL. Die Register CCMPH und CCMPL könnt ihr getrennt oder zusammen beschreiben.

DutyCycle = \frac{\text{CCMPH}}{\text{CCMPL + 1}} \cdot 100\;\; [\%]
void setup() {
    PORTF.DIRSET = PIN5_bm; // PF5 = D3
    TCB1.CCMP = 0x40FF;
    /* Alternative:
    TCB1.CCMPL = 0xFF; // 255
    TCB1.CCMPH = 0x40; */ // 64
    TCB1.CTRLB |= TCB_CCMPEN_bm | TCB_CNTMODE_PWM8_gc;
    TCB1.CTRLA = TCB_CLKSEL_CLKTCA_gc | TCB_ENABLE_bm;
}

void loop(){}

In diesem Beispiel verwenden wir den Timer A Takt. D.h. die Frequenz beträgt 16 MHz / (64 * 256) = ~976,56 Hz. Der Duty Cycle ist 25 %.

Single Shot Modus

Im Single Shot Modus zählt der Timer bis CCMP und stoppt dann, bis er wieder auf einen Wert kleiner als CCMP zurückgesetzt wird. Um den folgenden Sketch auszuprobieren, hängt ihr eine LED an den TCB0 Ausgang PF4 (= Pin 6).

void setup() {
    PORTF.DIRSET = PIN4_bm; // PF4 (D6) as output
    TCB0.CTRLA = 0; // disable TCB0
    TCB0.CCMP = 50000; // 0.2s signal length
    TCB0.CTRLB = TCB_CCMPEN_bm | TCB_CNTMODE_SINGLE_gc; // enable output / single shot mode
    TCB0.CTRLA = TCB_CLKSEL_CLKTCA_gc | TCB_ENABLE_bm; // enable TCB0, use TCA clock
}

void loop(){
    delay(1000);
    TCB0.CNT = 0; // restarts the counter
}

Sofern euer Arduino Nano Every bei 16 MHz läuft und ihr den Timer A Takt nicht verändert habt, wird die LED im Sekundentakt für 0.2 Sekunden aufleuchten.

Jetzt verbinden wir das noch mit dem Event System. Dazu hängt ihr einen Taster an PB1 (Pin 10), den ihr wiederum mit GND verbindet. Ein Tasterdruck (Event Generator) soll den Timer TCB0 (Event User) starten.

ISR(TCB0_INT_vect){
    TCB0.INTFLAGS = TCB_CAPT_bm; /* Clear the interrupt flag */
}

void setup() {
    PORTF.DIRSET = PIN4_bm; // PF4 (D6) as output
    PORTB.DIRCLR = PIN1_bm; // PB1 (D10) as input
    PORTB.PIN1CTRL = PORT_PULLUPEN_bm;
    TCB0.CTRLA = 0; // disable TCB0
    EVSYS.CHANNEL1 = EVSYS_GENERATOR_PORT1_PIN1_gc; // PB1 as event generator
    EVSYS.USERTCB0 = EVSYS_CHANNEL_CHANNEL1_gc; // TCB0 as event user
    TCB0.CCMP = 50000; // 0.2s signal length
    TCB0.CTRLB = TCB_CCMPEN_bm | TCB_CNTMODE_SINGLE_gc; // single shot mode
    TCB0.EVCTRL = TCB_EDGE_bm | TCB_CAPTEI_bm; // counter starts at both edges / enable capture event input
    TCB0.INTCTRL = TCB_CAPT_bm; // enable interrupt
    TCB0.CNT = 50000; // prevents an unwanted, initial signal at PF4
    TCB0.CTRLA = TCB_CLKSEL_CLKTCA_gc | TCB_ENABLE_bm; // enable TCB0, use TCA clock
}

void loop(){}

Laut Datenblatt, Kapitel 21.5.3, sollte der Timer bei gesetztem EDGE Bit nur bei einer fallenden Flanke, sprich beim Drücken des Taster und nicht beim Loslassen, erfolgen. Die LED leuchtet aber bei beiden Ereignissen auf. Vielleicht einfach ein Fehler im Datenblatt. Bei nicht gesetztem EDGE Bit macht die LED, was sie soll: sie leuchtet nur beim Loslassen auf (steigende Flanke). Jedenfalls gilt das, sofern euer Taster nicht prellt.

Vielleicht ist euch die Zeile 16 aufgefallen. Wenn ihr die Zeile auskommentiert, dann werdet ihr sehen, dass die LED bei jedem Reset kurz aufleuchtet. Das Starten des Timers lässt ihn bei 0 los zählen und die LED leuchtet entsprechend. Setzt ihr den Timer Counter auf CCMP, passiert das nicht. 

Input Capture Time-Out Check Mode

Der Time-Out Check Mode macht nur Sinn in Kombination mit dem Eventsystem. In diesem Modus startet ein Event Signal den Timer bei 0, das nächste Signal stoppt ihn. Wenn EDGE gesetzt ist, startet die fallende Flanke den Timer und die steigende Flanke stoppt ihn. Ist EDGE nicht gesetzt, ist es umgekehrt. Erreicht der Timer Counter CCMP, wird ein Interrupt ausgelöst und der Timer Counter läuft weiter. 

Das probieren wir folgendermaßen aus: ein Taster an PB2 (D5) dient als Event Generator, der TCB0 im Time-Out Modus startet. Solange der Taster gedrückt ist, zählt TCB0 hoch. CCMP und den Timer Takt stellen wir so ein, dass CCMP nach einer Sekunde erreicht ist. Wenn dieser Fall eintritt, wird ein Interrupt ausgelöst und die ISR toggelt die Board-LED. Wird der Taster vor Erreichen von CCMP losgelassen, gibt es keinen Interrupt und die Board-LED toggelt nicht. Beim nächsten Tasterdruck startet der Timer wieder bei 0.

Damit das Ganze nicht zu schnell geht, habe ich den Timer A entschleunigt und als Takt für den Timer B genommen. Und zum x-ten Mal: nutzt ihr das Arduino Board Paket für den Nano Every, dann macht euch das die Zeitmessung kaputt. Trotzdem könnt ihr den Sketch natürlich einfach mal ausprobieren, da er kein delay() oder millis() enthält. Für das MegaCoreX Paket stellt das ohnehin kein Problem dar.

Hier der auf das Wesentliche reduzierte Sketch:

#define TCB_TIMEOUT_VALUE (15625) // = 1s with the settings below

ISR(TCB0_INT_vect){
    TCB0.INTFLAGS = TCB_CAPT_bm; /* Clear the interrupt flag */
    PORTE.OUTTGL = PIN2_bm; /* Toggle PE2 (D13, LED_BUILTIN)*/
}

void setup() {
    Serial.begin(115200);
    PORTE.DIRSET = PIN2_bm; // PE2 (D13) as output
    PORTB.DIRCLR = PIN2_bm; // PB2 (D5) as input
    PORTB.PIN2CTRL = PORT_PULLUPEN_bm; // enable pull-up
        
    EVSYS.CHANNEL0 = EVSYS_GENERATOR_PORT1_PIN2_gc; // PE2 is event generator
    EVSYS.USERTCB0 = EVSYS_CHANNEL_CHANNEL0_gc; // TCB0 is event user

    TCB0.CCMP = TCB_TIMEOUT_VALUE;
    TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1024_gc | TCA_SINGLE_ENABLE_bm; // slow down Timer A
    TCB0.CTRLB = TCB_CCMPEN_bm | TCB_CNTMODE_TIMEOUT_gc; // enable compare/capture output, time-out mode
    TCB0.INTCTRL = TCB_CAPT_bm; // enable capture interrupt
    TCB0.EVCTRL = TCB_EDGE_bm | TCB_CAPTEI_bm; // start counter on negative edge
    TCB0.CTRLA = TCB_CLKSEL_CLKTCA_gc | TCB_ENABLE_bm; // use timer A clock, enable TCB0
}

void loop(){}

Mit einem 16 MHz Systemtakt, einem Teiler von 1024 und einem CCMP von 15625 erreicht der Counter CCMP nach 15625 * 1024 / 16000000 = 1 Sekunde. Wenn ihr den Taster dauerhaft gedrückt haltet, läuft der Counter immer weiter und erreicht alle 65536 * 1024 / 16000000 = ~4.2 Sekunden CCMP.

Damit ihr besser verfolgen könnt, bei welchem Counterstand ihr den Taster losgelassen habt, habe ich hier noch eine Komfortvariante:

#define TCB_TIMEOUT_VALUE (15625) // = 1s with the settings below
volatile bool keyReleased = false;

ISR(TCB0_INT_vect){
    TCB0.INTFLAGS = TCB_CAPT_bm; /* Clear the interrupt flag */
    PORTE.OUTTGL = PIN2_bm; /* Toggle PE2 (D13, LED_BUILTIN)*/
}

ISR(PORTB_PORT_vect){ // ISR for button release
    PORTB.INTFLAGS = PIN2_bm;
    keyReleased = true;
}

void setup() {
    Serial.begin(115200);
    PORTE.DIRSET = PIN2_bm; // PE2 (D13) as output
    PORTB.DIRCLR = PIN2_bm; // PB2 (D5) as input
    PORTB.PIN2CTRL = PORT_PULLUPEN_bm | PORT_ISC_RISING_gc; // pull-up / interrupt
        
    EVSYS.CHANNEL0 = EVSYS_GENERATOR_PORT1_PIN2_gc; // PE2 is event generator
    EVSYS.USERTCB0 = EVSYS_CHANNEL_CHANNEL0_gc; // TCB0 is event user

    TCB0.CCMP = TCB_TIMEOUT_VALUE;
    TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1024_gc | TCA_SINGLE_ENABLE_bm; // slow down Timer A
    TCB0.CTRLB = TCB_CCMPEN_bm | TCB_CNTMODE_TIMEOUT_gc; // enable compare/capture output, time-out mode
    TCB0.INTCTRL = TCB_CAPT_bm; // enable capture interrupt
    TCB0.EVCTRL = TCB_EDGE_bm | TCB_CAPTEI_bm; // start counter on negative edge
    TCB0.CTRLA = TCB_CLKSEL_CLKTCA_gc | TCB_ENABLE_bm; // use timer A clock, enable TCB0
}

void loop(){
    if(keyReleased){
        Serial.println(TCB0.CNT);
        keyReleased = false;
    }
}

 

Input Capture On Event Mode

Jetzt kommen noch vier Input Capture Modi auf euch zu, mit denen ihr Pulsweiten und / oder Frequenzen messen könnt. Als zu vermessendes Objekt verwenden wir für alle Beispiele ein PWM-Signal mit einem Duty Cycle von 75 % an Pin 10 (PB1), welches wir mit analogWrite(10, 192) erzeugen. Pin 10 ist der Event Generator, der Timer B ist der Event User.

Als Erstes betrachten wir den Input Capture On Event Mode. In diesem Modus zählt der Counter permanent von 0 bis 65535, läuft über und beginnt wieder von vorn. Je nachdem, ob ihr das EDGE Bit gesetzt habt oder nicht, schreibt der Timer bei einer fallenden oder steigenden Flanke den aktuellen Counter Wert in das CCMP Register und löst einen Interrupt aus. In der ISR des Capture Interrupts lesen wir das CCMP Register aus. Die Differenz zum letzten Wert ist die Periode des PWM Signals in Timer Counter Zählschritten.

Im Falle eines Counter Überlaufes zwischen zwei Interrupts ist die Differenz negativ und wir müssen 65636 addieren.

volatile long cnt = 0;
volatile long lastCnt = 0;

ISR(TCB0_INT_vect){
    cnt = TCB0.CCMP - lastCnt; // Read the period
    lastCnt = TCB0.CCMP;
    if(cnt < 0){   // period is neg. when TCB0 was overflowed
        cnt += 65536;
    }
    TCB0.INTFLAGS = TCB_CAPT_bm; /* Clear the interrupt flag */

}

void setup() {
    Serial.begin(115200);
    analogWrite(10, 192); // the event source 
    EVSYS.CHANNEL1 = EVSYS_GENERATOR_PORT1_PIN1_gc; // PB1 (Pin 10) is event generator
    EVSYS.USERTCB0 = EVSYS_CHANNEL_CHANNEL1_gc; // TCB0 is event user
    TCB0.CTRLA = 0; // Stop TCB0
    TCB0.CTRLB = TCB_CNTMODE_CAPT_gc;  // use input capture on event mode
    TCB0.EVCTRL =  TCB_CAPTEI_bm; // enable capture event input
    TCB0.INTCTRL = TCB_CAPT_bm;  // enable capture interrupt
    TCB0.CTRLA = TCB_CLKSEL_CLKDIV1_gc | TCB_ENABLE_bm; // use system clock, enable TCB0
    delay(10);
}

void loop(){
    Serial.println(cnt);
    delay(1000);
}

Das Ergebnis bei Verwendung des megaAVR Boardpaketes war 16384. analogWrite() wird über den Timer A Takt mit einem Teiler von 64 gesteuert. Unabhängig vom Duty Cycle wird alle 256 Zähler eine steigende Flanke und eine fallende Flanke erzeugt. Das PWM Signal hat also eine Frequenz von 16 MHz / (64 * 256) = 976.5626 Hz bzw. eine Periode von 0.001024 Sekunden. Der Timer B zählt im Systemtakt. Damit entsprechen 16384 Zählschritte einer Frequenz von 16 MHz / 16384 = 976.5625 Hz. Passt also perfekt!

Mit dem MegaCoreX habe ich eine Periode von 16320 (= ~980.4 Hz) ermittelt, was mich zunächst sehr wunderte. Des Rätsels Lösung: die analogWrite() Funktion des MegaCoreX Paketes umfasst nur 255  Schritte: 16 MHz / (64 * 255) = ~980 Hz. Das macht eigentlich auch mehr Sinn als 256 Schritte, denn mit analogWrite(pin, 255) erhaltet ihr so einen Duty Cycle von 100 %. Beim Arduino Board Paket erzeugt analogWrite(pin, 255) einen Duty Cycle von 255/256 * 100 %, d. h. 100 % sind nicht erreichbar. 

Input Capture Frequency Measurement Mode

Der Input Capture Frequency Measurement Mode tut im Prinzip dasselbe wie der Input Capture On Event Mode, nur dass der Zähler nach dem Erhalt des Eventsignals wieder auf 0 gesetzt wird. Das hat den Vorteil, dass uns CCMP direkt und ohne Differenzberechnungen die Periode liefert.

Eigentlich ist die Bezeichnung „Frequency Measurement“ falsch. Es müsste „Period Measurement“ heißen.

ISR(TCB0_INT_vect){
    TCB0.INTFLAGS = TCB_CAPT_bm; /* Clear the interrupt flag */
}

void setup() {
    Serial.begin(115200);
    analogWrite(10, 192); // the event source
    EVSYS.CHANNEL1 = EVSYS_GENERATOR_PORT1_PIN1_gc; // PB1 (Pin 10) is event generator
    EVSYS.USERTCB0 = EVSYS_CHANNEL_CHANNEL1_gc; // TCB0 is event user
    TCB0.CTRLA = 0;
    TCB0.CTRLB = TCB_CNTMODE_FRQ_gc; // enable input capture frequency mode
    TCB0.EVCTRL = TCB_EDGE_bm | TCB_CAPTEI_bm; // enable capture event input 
    TCB0.INTCTRL = TCB_CAPT_bm;  // enable capture event input
    TCB0.CTRLA = TCB_CLKSEL_CLKDIV1_gc | TCB_ENABLE_bm; // use system clock, enable TCB0
  
}

void loop(){
    Serial.print("Period in TCB0 counts: ");
    Serial.println(TCB0.CCMP + 1);
    float frq = 16000000.0 / ((TCB0.CCMP + 1) * 1.0);
    Serial.print("Frequency [Hz]: ");
    Serial.println(frq);
    delay(1000);
}

In diesem Beispielsketch habe ich die Berechnung der Frequenz in den Sketch integriert. Interessant ist, dass man hier noch 1 zum CCMP Wert addieren muss, um auf das richtige Ergebnis zu kommen. Ein CCMP Wert von 16383 sind 16384 Schritte – das macht ja eigentlich auch Sinn. Nur, warum man beim Input Capture On Event Mode 1 nicht addieren muss, ist zumindest mir bislang nicht wirklich klar geworden.

Ausgabe timer_b_input_capture_frq.ino, links: megaAVR Paket, rechts: MegaCoreX Paket
Ausgabe timer_b_input_capture_frq.ino, links: megaAVR Paket, rechts: MegaCoreX Paket

Input Capture Pulse-Width Measurement Mode

Der Input Capture Pulse-Width Measurement Mode ermöglicht euch die Messung der Pulsweite von Signalen. Im Gegensatz zum Frequency Measurement Mode, bei dem nur die steigende oder die fallende Flanke einen Effekt hatte, wird bei diesem Modus der Zähler bei einer Flanke „genullt“ und bei der anderen Flanke erfolgt der Input Capture, also das Schreiben des Zählerstandes in CCMP.

ISR(TCB0_INT_vect){
    TCB0.INTFLAGS = TCB_CAPT_bm; /* Clear the interrupt flag */
}

void setup() {
    Serial.begin(115200);
    analogWrite(10, 192); // the event source
    EVSYS.CHANNEL1 = EVSYS_GENERATOR_PORT1_PIN1_gc; // PB1 (Pin 10) is event generator
    EVSYS.USERTCB0 = EVSYS_CHANNEL_CHANNEL1_gc;  // TCB0 is event user
    TCB0.CTRLA = 0;
    TCB0.CTRLB = TCB_CNTMODE_PW_gc; // enable pulse width measurement mode
    TCB0.EVCTRL = TCB_CAPTEI_bm; // enable capture event input
    TCB0.INTCTRL = TCB_CAPT_bm; 
    TCB0.CTRLA = TCB_CLKSEL_CLKDIV1_gc | TCB_ENABLE_bm; // use system clock, enable TCB0
    delay(10);
}

void loop(){
    Serial.print("High Period in TCB0 counts: ");
    unsigned int highCnt = (TCB0.CCMP + 1);
    Serial.println(highCnt);
    float pw = (highCnt * 1.0) / 16.0;  // cnt divided by 16 MHz
    Serial.print("Pulse width [µs]: ");
    Serial.println(pw);
    Serial.println();
    delay(1000);
}

 

Als Ergebnis erhielt ich sowohl mit dem megaAVR Paket als auch mit MegaCoreX:

Ausgabe timer_b_input_capture_pw.ino
Ausgabe timer_b_input_capture_pw.ino

Das entspricht exakt den Erwartungen. Die PWM Periode war 16384 Zähler lang (megaAVR Paket). 75 % davon sind 12288. Mit MegaCoreX war die Periode 16320. 16320 / 255 * 192 = 12288 → passt auch.

Wenn ihr das EDGE Bit im Event Control Register setzt (Zeile 12), dann wertet der Sketch die LOW Phase aus. 

Input Capture Frequency and Pulse-Width Measurement Mode

Der Input Capture Frequency and Pulse-Width Measurement Mode kombiniert die Frequenz- und die Pulsweitenmessung. Dabei können natürlich nicht beide Werte in CCMP gespeichert werden. Stattdessen funktioniert der Modus folgendermaßen (bei nicht gesetztem EDGE-Bit):

  • Erste positive Flanke (Pulse beginnt): Der Counter wird zurückgesetzt und startet.
  • Negative Flanke (Pulse endet): Input Capture, d.h. der Counterstand wird in CCMP geschrieben.
  • Zweite positive Flanke (Periode endet): Counter stoppt, Interrupt wird ausgelöst.

Da wir die Periode (aus CCMP) und die Pulsweite (Counterstand bei Interrupt) kennen, können wir den Duty-Cycle ausrechnen. Und genau das macht der nächste Sketch:

volatile unsigned int period = 0;

ISR(TCB0_INT_vect){
    period = TCB0.CNT + 1; // Read the period
    TCB0.INTFLAGS = TCB_CAPT_bm; /* Clear the interrupt flag */
}

void setup() {
    Serial.begin(115200);
    analogWrite(10, 192);
    EVSYS.CHANNEL1 = EVSYS_GENERATOR_PORT1_PIN1_gc;
    EVSYS.USERTCB0 = EVSYS_CHANNEL_CHANNEL1_gc;
    TCB0.CTRLA = 0;
    TCB0.CTRLB = TCB_CNTMODE_FRQPW_gc;
    TCB0.EVCTRL = TCB_CAPTEI_bm; // TCB_EDGE_bm | TCB_CAPTEI_bm;
    TCB0.INTCTRL = TCB_CAPT_bm;
    TCB0.CTRLA = TCB_CLKSEL_CLKDIV1_gc | TCB_ENABLE_bm; // use system clock, enable TCB0
}

void loop(){
    Serial.print("Period in TCB0 counts: ");
    Serial.println(period);
    Serial.print("High Period in TCB0 counts: ");
    Serial.println(TCB0.CCMP + 1);
    float frq = 16000000.0 / (period * 1.0);
    Serial.print("Frequency [Hz]: ");
    Serial.println(frq);
    float dc = (TCB0.CCMP + 1) * 1.0 / (period * 1.0) * 100.0;
    Serial.print("Duty Cycle [%]: ");
    Serial.println(dc);
    Serial.println();
    delay(1000);

 

Hier das Ergebnis:

Output timer_b_input_capture_frq_and_pw.ino, megaAVR und MegaCoreX
Output timer_b_input_capture_frq_and_pw.ino, links: megaAVR, rechts: MegaCoreX

Real-Time Counter – Modul RTC

Das RTC Modul bietet zwei Funktionen, nämlich den 16-Bit Real-Time Counter (RTC) und den Periodic Interrupt Timer (PIT). Beide greifen auf denselben Taktgeber zurück, ansonsten können die Funktionen unabhängig voneinander genutzt werden. Der Takt liegt maximal bei 32768 Hz, ihr steuert mit dem Modul also eher langsamere Prozesse.

Die Register des RTC Moduls

Clock Selection Register CLKSEL

Arduino Nano Every / Nano 4808: Register RTC.CLKSEL
Register RTC.CLKSEL

Im CLKSEL Register stellt ihr den Taktgeber ein. Es stehen folgende Optionen zur Verfügung:

  • INT32K_gc: Liefert 32768 Hz durch den internen Ultra Low-Power Oszillator OSCULP32K.
  • INT1K_gc: Wie INT32K, aber mit Teiler 32, also 1024 Hz.
  • TOSC32K_gc: Liefert 32768 Hz durch XOSC32K oder einen externen Taktgeber an TOSC1.
  • EXTCLK_gc: Externer Taktgeber an EXTCLK.

Crystal Frequency Calibration Register CALIB

Eine richtig tolle Eigenschaft des RTC Moduls ist, dass ihr den zugrundeliegenden Taktgeber mithilfe des CALIB Registers kalibrieren könnt.

Arduino Nano Every / Nano 4808 Register: RTC.CALIB
Register RTC.CALIB

ERROR[6:0] ist die Abweichung in ppm. Ist SIGN gesetzt, wird der Takt entsprechend schneller. In diesem Fall müsst ihr den Prescaler mindestens auf DIV2 setzen. Ist SIGN nicht gesetzt, wird der Takt langsamer. 

RTC Control Register A CTRLA

Im CTRLA Register legt ihr fest, ob das RTC Modul im Stand-by-Modus weiterlaufen soll (RUNSTDBY). Überdies (de-)aktiviert ihr die Kalibrierung (CORREN) und ihr schaltet das Modul ein (RTCEN).

Arduino Nano Every / Nano 4808 Register: RTC.CTRLA
Register RTC.CTRLA

Über den PRESCALER könnt ihr den RTC bis zu einem Faktor von 32768 verlangsamen:

PRESCALER[3:0] Bit Group Configurations
PRESCALER[3:0] Bit Group Configurations

RTC Interrupt Control Register INTCTRL

Für den RTC stehen euch ein Compare und ein Overflow Interrupt zur Verfügung. Das korrespondierende Interrupt Flag Register INTFLAG ist „baugleich“.

Arduino Nano Every / Nano 4808 Register: RTC.INTCTRL / RTC.INTFLAGS
Register RTC.INTCTRL / RTC.INTFLAGS

PIT Control Register A CTRLA

Der Periodic Interrupt Timer macht schlicht das, was sein Name vermuten lässt: Er löst regelmäßig einen Interrupt aus. Im PITCTRLA Register aktiviert ihr den PIT und stellt die Periode ein.

Arduino Nano Every / Nano 4808 Register: RTC.PITCTRLA
Register RTC.PITCTRLA
PERIOD[3:0] Bit Group Configurations
PERIOD[3:0] Bit Group Configurations

PIT Interrupt Control Register PITINTCTRL

In PITINTCRTL aktiviert ihr den PIT Interrupt. PITINTFLAGS ist wiederum „baugleich“.

Arduino Nano Every / Nano 4808 Register: RTC.PITINTCTRL / RTC.PITINTFLAGS
Register RTC.PITINTCTRL / RTC.PITINTFLAGS

Beispielsketche für RTC und PIT

RTC Beispielsketch

Als einfaches Beispiel für die Programmierung des RTC folgt ein Sketch, der ein „Zeitlupen-PWM“ mit einer Periode von 2 Sekunden und einem Duty Cycle von 75 % an PE2 (= D13 = Board-LED) erzeugt.

ISR(RTC_CNT_vect){
    if(RTC.INTFLAGS & RTC_CMP_bm){ // check for CMP interrupt
        RTC.INTFLAGS = RTC_CMP_bm; // delete CMP interrupt flag
        PORTE.OUTCLR = PIN2_bm; // PE2 = LOW
    }
    if(RTC.INTFLAGS & RTC_OVF_bm){ // check for OVF interrupt
        RTC.INTFLAGS = RTC_OVF_bm; // delete OVF interrupt flag
        PORTE.OUTSET = PIN2_bm;  // PE2 = HIGH
    }
}

void setup() {
    delay(1);
    PORTE.DIRSET = PIN2_bm;
    RTC.CLKSEL = RTC_CLKSEL_INT32K_gc; // set internal clock @ 32768 Hz
    RTC.PER = 16383;  // TOP = 16383
    RTC.CMP = 12288;  // Compare value = 12288
    RTC.INTCTRL = RTC_CMP_bm | RTC_OVF_bm;  // enable CMP and OVF interrupts
    RTC.CTRLA = RTC_PRESCALER_DIV4_gc | RTC_RTCEN_bm; // Prescaler = 4 and enable RTC
}

void loop(){}

Da für den Overflow- und den Compare-Interrupt nur ein Interruptvektor (RTC_CNT_vect) zur Verfügung steht, müssen wir über die Interrupt Flags prüfen, welches Ereignis den Interrupt ausgelöst hat.

Als Prescaler ist 4 eingestellt, d.h. der RTC zählt mit einer Frequenz von 32768 / 4 = 8192 Hz. PER ist 16383 und damit läuft der RTC alle 16384 Schritte über, was einer Überlauffrequenz von 2 Sekunden entspricht. Der Compare Value beträgt 12288, also 75 % von PER +1.

PIT Beispielsketch

Der Beispielsketch für den PIT ist noch schlichter. Mit einer Periode von 16384 und einem Takt von 32768 Hz wird alle 0.5 Sekunden ein Interrupt ausgelöst. Wir nutzen ihn hier, um die Board-LED zu toggeln.

ISR(RTC_PIT_vect){
    RTC.PITINTFLAGS = RTC_PI_bm; // clear PIT interrupt flag
    PORTE.OUTTGL = PIN2_bm; // toggle PE2
}

void setup() {
    PORTE.DIRSET = PIN2_bm;
    RTC.CLKSEL = RTC_CLKSEL_INT32K_gc; // set internal clock @ 32768 Hz
    RTC.PITINTCTRL = RTC_PI_bm;  // enable PIT interrupt
    RTC.PITCTRLA = RTC_PERIOD_CYC16384_gc | RTC_PITEN_bm; // interrupt frq: 2 Hz
}

void loop(){}

A/D-Wandler – Modul ADCn

Eigentlich hätte der A/D-Wandler des Arduino Nano Every / „Nano 4808“ einen separaten Beitrag verdient. Ich halte dieses Kapitel aber kurz und verweise wieder einmal auf das Datenblatt. Hier die Registerzusammenfassung:

Register Summary ADCn
Register Summary ADCn

Einige ausgewählte Registereinstellungen möchte ich erwähnen:

  • RUNSTBY: Erlaubt den Betrieb des ADC im Stand-by-Modus.
  • RESSEL: Setzt ihr das Bit, wird die Auflösung von 10 auf 8 Bit gesenkt.
  • FREERUN: Kontinuierlicher Modus.
  • SAMPNUM: Ihr könnt bis zu 64 Ergebnisse pro Messung aufsummieren. 2^6 Ergebnisse mal 2^10 Auflösung ergibt ein Ergebnis mit einer Größe von 2^16, das noch in das Resultregister RES passt.
  • PRESC: Teiler für die Taktrate des ADC.
  • REFSEL: Auswahl der Referenzspannung. Zur Auswahl stehen: INTREF (Internal Reference), VDDREF und VREFA (External Reference). Fälschlicherweise steht im Datenblatt „INTERNAL“ und „VDD“ anstelle von INTREF bzw. VDDREF.
  • SAMPLEN: Verlängert die Messzeit bis zum maximalen Faktor 31.
  • MUXPOS: Legt den Eingang für den ADC fest. Die Tabelle mit den Bit Group Configurations findet ihr im Datenblatt.
  • STARTCONV: Startet eine Messung.

Die interne Referenz wird im Register VREF.CTRLA spezifiziert. Dazu stellt ihr die Bit Group ADC0REFSEL[2:0] ein. Die Bit Group Configurations heißen VREF_ADC0REFSEL_xxx_gc mit xxx = 0V55, 1V1, 2V5V, 4V34 V oder 1V5 für 0.55, 1.1, 2.5, 4.34 bzw. 1.5 Volt.

Beispielsketche für den ADC

Einfache A/D Messung

Im folgenden Beispielsketch messen wir die Spannung an A2 (PD1 auf dem Arduino Nano Every). Dabei nutzen wir die interne 4.34 V Referenzspannung.

void setup() {
    Serial.begin(115200);
    PORTD.PIN2CTRL = PORT_ISC_INPUT_DISABLE_gc; // Disable digital buffer
    ADC0.CTRLA = ADC_ENABLE_bm; // Enable ADC
    ADC0.CTRLC = ADC_REFSEL_INTREF_gc | ADC_PRESC_DIV16_gc; // use internal reference / prescaler: 16
    VREF.CTRLA = VREF_ADC0REFSEL_4V34_gc; // use internal 4.34 V reference 
    ADC0.MUXPOS = ADC_MUXPOS_AIN1_gc; // use A2 (PD1) as input
    ADC0.SAMPCTRL = 0;
}

void loop(){
    ADC0.COMMAND = ADC_STCONV_bm; // Start Conversion
    while((ADC0.INTFLAGS & ADC_RESRDY_bm) == 0){} // Waiting for the result
    float res = ADC0.RES * 4340.0 / 1024.0;
    Serial.println(res);
    delay(1000);
}

ADC0 – RTC controlled

Ich komme noch einmal auf das Eventsystem zurück. Der folgende Sketch nutzt den RTC, um alle zwei Sekunden eine A/D-Wandlung zu initiieren.

volatile bool adcReady = false;

ISR(ADC0_RESRDY_vect){
    adcReady=true;
}

void setup() {
    Serial.begin(115200);
    PORTD.PIN2CTRL = PORT_ISC_INPUT_DISABLE_gc;
    ADC0.CTRLA = ADC_ENABLE_bm;
    ADC0.CTRLC = ADC_SAMPCAP_bm | ADC_REFSEL_INTREF_gc | ADC_PRESC_DIV16_gc;
    VREF.CTRLA = VREF_ADC0REFSEL_4V34_gc;
    ADC0.MUXPOS = ADC_MUXPOS_AIN1_gc; // A2 (PD1)
    ADC0.SAMPCTRL = 0;
    ADC0.EVCTRL = ADC_STARTEI_bm; // initiate measurement by event
    ADC0.INTCTRL = ADC_RESRDY_bm; // enable result ready interrupt
    RTC.CLKSEL |= RTC_CLKSEL_INT32K_gc; // use internal 32 kHz clock
    RTC.PER = 63; // overflow after 63 (64 steps) 
    RTC.CTRLA = RTC_PRESCALER_DIV1024_gc | RTC_RTCEN_bm; // OVF frequency = 0.5 Hz
    
    EVSYS.CHANNEL0 = EVSYS_GENERATOR_RTC_OVF_gc; // RTC overflow is event generator
    EVSYS.USERADC0 = EVSYS_CHANNEL_CHANNEL0_gc; // ADC0 is event user
}

void loop(){
    if(adcReady){ 
        float res = ADC0.RES * 4340L / 1024.0;
        Serial.println(res);
        adcReady = false;
    }
}

Ohne Eventsystem müsstet ihr einen Timer Interrupt einrichten oder eine Konstruktion mit delay() oder millis() wählen, um Messungen im 2-Sekundentakt zu starten. Dann müsstet ihr noch warten oder euch über einen weiteren Interrupt informieren lassen, wenn das Messergebnis vorliegt. Im obigen Beispiel hingegen reicht ein einziger Interrupt, alles andere läuft im Hintergrund.

Watchdog Timer – Modul WDT

Die Register des WDT Moduls

Insbesondere zwei Eigenschaften unterscheiden den WDT des ATmega4808/4809 vom Arduino Nano Every und „Nano 4808“:

  • Der WDT hat keinen Interrupt. 
  • Ein WDT Timeout führt auf jeden Fall zum Reset

Seinen Takt erhält der Watchdog Timer (WDT) vom Ultra Low-Power Oscillator, OSCULP32K.

Control A Register CTRLA

Die zeitlichen Vorgaben für den WDT nehmt ihr im Kontrollregister A, CTRLA, vor.

Arduino Nano Every / Nano 4808 Register: WDT.CTRLA
Register WDT.CTRLA

Für PERIOD und WINDOW könnt ihr folgende Einstellungen vornehmen:

WINDOW[3:0] / PERIOD[3:0] Bit Group Configurations
WINDOW[3:0] / PERIOD[3:0] Bit Group Configurations

Wenn ihr nur einen Wert für PERIOD festlegt und WINDOW auf 0 lasst, dann läuft der WDT im normalen Modus. Erfolgt in der Zeitspanne, die ihr mit PERIOD festlegt, kein Watchdog Reset, wird ein Systemreset ausgeführt.

Ist WINDOW ungleich null, dann befindet ihr euch im Window Modus. Der ist ein wenig schwieriger zu erklären und wird vielleicht erst durch den Beispielsketch richtig klar. Im Window Modus gilt:

  • Nach der Initialisierung oder einem wdt_reset() befindet sich der WDT in der durch WINDOW festgelegten Periode. Das Datenblatt nennt das die „closed window time-out period“ (TOWDTW). In dieser Periode kann der WDT nicht zurückgesetzt werden.  Ein wdt_reset() innerhalb dieser Periode führt zu einem sofortigen Systemreset.
  • Nach der TOWDTW beginnt die „normal window time-out period“ (TOWDT), deren Länge ihr mit PERIOD festlegt. Erfolgt in dieser Zeit ein wdt_reset(), dann beginnt das Spiel wieder von vorn mit der TOWDTW.
  • Erfolgt im Zeitraum TOWDTW + TOWDT kein wdt_reset(), wird ein Systemreset ausgeführt.

Status Register STATUS

Arduino Nano Every / Nano 4808 Register: WDT.STATUS
Register WDT.STATUS

Durch das Setzen des LOCK-Bits schützt ihr die Einstellungen des WDT vor Veränderungen. Ihr könnt es nur im Debug Modus wieder löschen.

Wenn ihr Daten in das CTRLA-Register schreibt, müssen diese zur WDT Clock Domain übertragen werden. Für die Zeit der Synchronisierung wird das SYNCBUSY-Bit gesetzt.

Beispielsketch für den WDT

Der Normal Mode sollte keinen Beispielsketch benötigen. Deswegen schauen wir uns nur einen Sketch für den Window Modus an. Die Einstellung für den WDT ist: TOWDTW = TOWDT = 4 Sekunden.

#include <avr/wdt.h>
void setup(){
    Serial.begin(115200);
    Serial.println("*******************");
    Serial.println("Watchdog Timer Test");
    Serial.println("Sketch starts...");
    CPU_CCP = CCP_IOREG_gc; // allow changes
    WDT.CTRLA = WDT_WINDOW_4KCLK_gc | WDT_PERIOD_4KCLK_gc; // 4s period and window
    CPU_CCP = CCP_IOREG_gc;
    WDT.STATUS = WDT_LOCK_bm;
    while(WDT.STATUS & WDT_SYNCBUSY_bm){} // wait until WDT is synchronized
    wdt_reset();
    int seconds = 0;
    while(seconds < 100){
        // if(seconds == 5){  // try also 3
        //     wdt_reset();
        // }
        Serial.print(seconds);
        Serial.println(" seconds");
        delay(1000);
        seconds++;
    }
}

void loop(){}

Wenn ihr den Sketch unverändert laufen lasst, zählt die while() Schleife hoch, bis TOWDTW + TOWDT = 8 Sekunden vergangen sind. Die letzte Ausgabe ist „7 seconds“, weil mit dem Erreichen der achten Sekunde keine Zeit mehr für die Ausgabe bleibt.

Wenn ihr die Zeilen 15 bis 17 entkommentiert, gibt es bei Sekunde fünf, also in der „erlaubten“ TOWDT Periode ein wdt_reset(). Danach läuft der WDT weitere acht Sekunden durch, bis der Systemreset durchgeführt wird.

Wenn ihr dann noch Zeile 15 so abändert, dass der wdt_reset() nach drei Sekunden erfolgt, gibt es einen sofortigen Systemreset, da ihr euch in der „verbotenen“ TOWDTW Periode befindet.

Output wdt.ino  -  links: unverändert, Mitte: "if (seconds == 5)", rechts: "if(seconds ==3)"
Output wdt.ino – links: unverändert, Mitte: „if (seconds == 5)“, rechts: „if(seconds ==3)“

Und was soll das Ganze? Normalerweise beißt der Watchdog, wenn der Sketch irgendwo hängen bleibt. Mit der Window Methode könnt ihr den Systemreset herbeiführen, wenn eine Aktion zu früh ausgeführt wird, weil beispielsweise irgendein anderer Schritt, aus welchen Gründen auch immer, nicht ausgeführt wurde.

Configurable Custom Logic – Modul CCL

Mit dem Configurable Custom Logic Modul CCL bekommt der Arduino Nano Every bzw. „Nano4808“ Fähigkeiten, für die ihr sonst externe Logic ICs einsetzen müsstet. Es geht aber noch weit darüber hinaus.

Das CCL Modul ist in vier sogenannten Look-Up Tables (LUTs) organisiert. Zu jeder LUT gehören drei Eingänge und ein Ausgang.

In meinen Pinout Diagrammen habe ich die Eingänge als x-INy bezeichnet, mit x = 0 bis 4 (Nummer der LUT) und y = 0 bis 2. Die Ausgänge sind als x-OUT gekennzeichnet mit x = 0 bis 3. 

Register des CCL Moduls

Ich gebe zu diesem Modul nur einen groben Überblick. Hier erst einmal die Registerübersicht:

Register Summary Modul CCL
Register Summary Modul CCL

Jede LUT hat ihr eigenes TRUTH Register. Dort legt ihr fest, welche Input Level Kombination wahr (true) ist. Beispiel: Für LUT2 definieren wir, dass 2-IN2 = HIGH, 2-IN1 = LOW und 2-IN0 = LOW der Zustand „true“ sein soll. D. h.: HIGH/LOW/LOW ⇒ 0b100 = 4. Das wiederum bedeutet, dass in TRUTH2 das Bit Nr. 4 zu setzen ist (und nicht, dass TRUTH2 = 4 ist!).

Jetzt kommt das Geniale: Die Inputs müssen keine I/O Pins sein, sondern es kann sich beispielsweise auch um Events, Timer, USARTs oder den Ausgang einer anderen LUT handeln. Wenn also bestimmte Zustände verschiedener Module in eurem Programm eine Aktion erfordern, dann könnt ihr euch mit der CCL die Abfrage dieser Zustände und einiges an if- und switch-Kombinationen sparen. Die Inputs legt ihr über die Input Select Bitgroups (INSELn[3:0]) fest.

Tritt der Zustand „true“ ein, könnt ihr euch über einen Interrupt informieren lassen. Alternativ oder zusätzlich könnt ihr das OUTEN Bit setzen, sodass bei „true“ der zugehörige Ausgang von LOW auf HIGH geht.

Über die SEQSELn[3:0] Bit Groups könnt ihr 2 LUTs (LUT0 und LUT1 bzw. LUT2 und LUT3) miteinander kombinieren.

Beispielsketch für das CCL-Modul

Als einfaches Beispiel prüfen wir den Zustand der LUT2 zugeordneten I/O Pins. Für den Arduino Nano Every gilt:

  • 2-IN2 = PD2 = A1
  • 2-IN1 = PD1 = A2
  • 2-IN0 = PD0 = A3
  • 2-OUT = PD3 = A0

Unsere True-Bedingung soll sein: 2-IN2 = HIGH, 2-IN1 = LOW und 2-IN0 = LOW. Die Eingänge ziehen wir mit den internen Pull-Up Widerständen auf HIGH. Ein Tasterdruck soll sie auf LOW bringen. Werden die Taster an 2-IN1 und 2-IN0 gedrückt, ist die True-Bedingung erfüllt und der Ausgang 2-OUT geht auf HIGH. Das lassen wir uns mit einer LED anzeigen. Hier die Schaltung:

Schaltung zu ccl_basic.ino
Schaltung zu ccl_basic.ino

Und hier der zugehörige Sketch:

void setup() {
    PORTD.DIRCLR = PIN2_bm | PIN1_bm | PIN0_bm; // PD0 = A3 = 2-IN0 as input, etc.
    PORTD.PIN0CTRL = PORT_PULLUPEN_bm; // PULLUP for PD0 = A3 = 2-IN0
    PORTD.PIN1CTRL = PORT_PULLUPEN_bm; // PULLUP for PD1 = A2 = 2-IN1
    PORTD.PIN2CTRL = PORT_PULLUPEN_bm; // PULLUP for PD2 = A1 = 2-IN2
    CCL.LUT2CTRLB = CCL_INSEL0_IO_gc | CCL_INSEL1_IO_gc; // select I/O as input
    CCL.LUT2CTRLC = CCL_INSEL2_IO_gc; // select I/O as Input
    CCL.TRUTH2 = 0x10; // 2-IN2 = HIGH, 1-IN2 = LOW, 0-IN2 = LOW ==> TRUE
    CCL.LUT2CTRLA = CCL_OUTEN_bm | CCL_ENABLE_bm; // OUT Enable / LUT2 enable
    CCL.CTRLA = CCL_ENABLE_bm; // CCL_ENABLE
}

void loop(){}

Das MegaCoreX Boardpaket hat für das CCL Modul eine eigene Bibliothek namens Logic implementiert.

Der Arduino Nano Every geht schlafen – Modul SLPCTRL

Schlafen gehen – das passt doch als letztes Kapitel!

Registereinstellungen für SLPCTRL

Das Modul SLPCTRL besitzt lediglich das Register CTRLA, in dem ihr den Schlafmodus auswählt (SMODE[1:0]) und das SLPCTRL-Modul durch das SEN-Bit aktiviert. Das Setzen des SEN-Bits alleine lässt den Mikrocontroller aber nicht einschlafen. Dafür nutzt ihr sleep_cpu() aus avr/sleep.h.

Arduino Nano Every / Nano 4808 Register: SLPCTRL.CTRLA
Register: SLPCTRL.CTRLA

Als Bit Group Configurations für SMODE[1:0] habt ihr folgende Optionen:

  • IDLE: Idle – der leichteste Schlaf; alles aktiv, bis auf die CPU.
  • STANDBY: Stand-by – die mittlere Einstellung.
  • PDOWN: Power Down – Tiefschlaf.

Je tiefer der Schlaf, desto geringer der Stromverbrauch und desto limitierter die Weckmethoden. Welche Peripherien in welchen Schlafmodus zur Verfügung stehen, findet ihr im Datenblatt, Kapitel 11.3.2.1. Der Stand-by-Modus ist dabei am flexibelsten. Viele Module besitzen ein RUNSTBY-Bit, das festlegt, ob das Modul im Stand-by-Modus zur Verfügung stehen soll oder nicht.

Beispielsketch für SLPCTRL

Im folgenden Beispiel schicken wir den Arduino Nano Every / „Nano4808“ in den Power Down Modus und wecken ihn nach einer Sekunde mithilfe des Periodic Timer Interrupts (PIT).

 #include<avr/sleep.h>

ISR(RTC_PIT_vect){
    RTC.PITINTFLAGS = 1;
}

void setup(){
    Serial.begin(115200);
    RTC.CLKSEL = RTC_CLKSEL_INT32K_gc;
    RTC.PITCTRLA = RTC_PERIOD_CYC32768_gc | RTC_PITEN_bm;
    RTC.PITINTCTRL = RTC_PI_bm;   
    SLPCTRL.CTRLA = SLPCTRL_SMODE_PDOWN_gc | SLPCTRL_SEN_bm;  
}

void loop(){
    Serial.println("Going to sleep...");
    Serial.flush();
    sleep_cpu();
}

7 thoughts on “Arduino Nano Every – ein Deep Dive

  1. Guten Tag Wolfgang, sag mal bitte, womit erstellst du deine Pinout Grafiken? Kennst du eine undokumentierte Möglichkeit die interne Bandgap mittels PortMux zu aktivieren damit man die eigene Betriebsspannung mittels ADC messen kann? Danke.

    1. Hallo Marko, die Pinoutgrafiken sind eine Mischung aus Inkscape und Photoshop. Photoshop ist nicht sonderlich komfortabel für Vektorgrafiken und Inkscape ist nicht so gut für Pixelgrafik.

      Die zweite Frage ist schwierig. Mir war gar nicht klar, dass der Nano Every bzw. der ATmega4809 anscheinend seine interne Referenzspannung am VREF Pin nicht preisgibt. Also spontan habe ich leider keine Lösung. Vielleicht wäre das mal eine Frage, die du auf Github mal beim MegaCoreX-Paket als Issue aufwerfen könntest:
      https://github.com/MCUdude/MegaCoreX

      Wen mir noch etwas Gescheites einfällt, dann melde ich mich.

      1. Hallo Wolfgang, Danke erstmal. Vielleicht ein Missverständnis. Die gewählte Referenzspannung kann man schon am AREF Pin messen. Nur kann man laut meiner Lesung des Manuals nicht wie bspw. beim ATmega328P die „Bandgap“ per Portmux wählen um dann mittels ADC hinten durch die Brust ins Auge die eigene UB messen. Das fiel mir heute auf wo ich das probieren wollte. Ansonsten frage ich später bei MCUdude an.

  2. Hallo Wolfgang,
    mal wieder ein äußerst interessanter Artikel! Hatte den NANO Every so gar nicht auf dem Schirm, hat aber mit dem größeren Speicher bei zwei meiner Projekte was. Gerade auch das CCL Modul ist klasse. Habe mir neben dem NANO4808 den ATmega4808 als TQFP zum Experementieren bestellt. (Der Weihnachtsurlaub kann kommen…)

    Viele Grüße

    Frank

  3. Guten Morgen Herr Ewald,

    ein sehr interessanter Artikel.
    Es wäre natürlich spitze, wenn der Chip auch noch 256 KB EEPROM hätte.
    Laut Datenblatt leider nur 256 Byte.

    Mit freundlichen Grüßen
    Ralf

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert