Hallo,
diesmal beleuchte ich die CCL zusammen mit dem Eventsytem genauer. Die CCL bietet mit dem ATmega4809 insgesamt 4 Logikeinheiten, abgekürzt mit LUTn (LUT0...LUT3). Trägt man alle Informationen zusammen die sich mit dem Every Board und Pinverfügbarkeit ergeben kommt man auf folgende Grafik. Anzumerken ist, das die Pinbelegung zwischen Nano Every und Uno Wifi Rev2 nicht zu 100% gleich ist. Ich beziehe mich hier wirklich nur auf das Nano Every Board.
> ____
> (18)(PF2)(3-IN2)---| |
> ---|LUT3|---(19)(PF3)(3-OUT)
> ---|____|
> ____
> (15)(PD2)(2-IN2)---| |
> (16)(PD1)(2-IN1)---|LUT2|---(14)(PD3)(2-OUT)
> (17)(PD0)(2-IN0)---|____|
> ____
> ---| |
> ---|LUT1|---(4)(PC6)(1-OUT)
> ---|____|
> ____
> | |
> (7)(PA1)(0-IN1)---|LUT0|---
> (2)(PA0)(0-IN0)---|____|
Sieht auf dem ersten Blick ernüchternd aus. LUT2 ist vollständig verfügbar und beim Rest fehlt immer irgendwas. Das entmutigt jedoch in keinster Weise. Denn es gibt noch das Eventsystem womit man alle fehlenden Pins ersetzen bzw. darüber den LUTs zuführen kann.
Vorweg noch eine Übersicht wie man die LUTs kaskadiert. Das geht nur von einer "niederwertigen" zu einer "höherwertigen' laut ihrer Nummerierung. Das hat nichts mit Minderwertig zu tun. Wenn man sich mit dem Manual beschäftigt sieht man das man schon erstaunlich viele Möglichkeiten hat. Irgendwann ist immer Schluss, aber das Ende ist ziemlich dehnbar. Die Übersicht soll wie gesagt darstellen das man immer irgendeinen Eingang mit dem Ausgang der nächsten LUT verbinden kann. Und zwar nur mit dem Ausgang der nächsten LUT. Das geht nicht anders. Dafür steht im Manual das LUTn+1. Falls das jemand liest.
Was ich jedoch trotz der Beschreibungen hier jeden an Herz lege möchte, will man alle Möglichkeiten erkunden und ausschöpfen. Das CCL und Eventsystem steht ja in der Arduinowelt erstmals zur Verfügung mittels dem ATmega4809.
> ____
> ---| |
> ---|LUT3|---+
> ---|____| |
> | ____
> +---| |
> ----------------|LUT2|---+
> ----------------|____| |
> | ____
> +---| |
> -----------------------------|LUT1|---+
> -----------------------------|____| |
> | ____
> +---| |
> ------------------------------------------|LUT0|---
> ------------------------------------------|____|
Zum warm werden gibt es ein Bsp. wo alle 4 LUTs kaskadiert werden und alle LUTs eine ODER Logik verpasst bekommen. Dafür werden alle fehlenden Pins über das Eventsystem zugeführt. Das heißt sobald einer der 9 Eingänge logisch '1' hat, ist der Ausgang von LUT0 ebenfalls logisch '1'.
> ____
> (18)(PF2)(3-IN2)---| >= |
> (10)(PB1)(Event)---| 1 |---+
> (9)(PB0)(Event)---|____| |
> LUT3 |
> | ____
> +---| >= |
> (16)(PD1)(2-IN1)----------------| 1 |---+
> (17)(PD0)(2-IN0)----------------|____| |
> LUT2 |
> | ____
> +---| >= |
> (12)(PE1)(Event)-----------------------------| 1 |---+
> (11)(PE0)(Event)-----------------------------|____| |
> LUT1 |
> | ____
> +---| >= |
> (7)(PA1)(0-IN1)------------------------------------------| 1 |---(Event EVOUTB)(5)(PB2)
> (2)(PA0)(0-IN0)------------------------------------------|____|
> LUT0
Wie macht man das jetzt am dümmsten? Beginnen wir mit LUT0.
Für LUT0 stehen uns auf dem Board zwei der direkten Hardware-Logikeingänge zur Verfügung. Das wären 0-IN0 und 0-IN1. Diese muss man nur im entsprechenden Register aktivieren. Die LUT I/Os n-IN0 und n-IN1 werden immer Register 'LUT0CTRLB' und n-IN2 immer Register LUT0CTRLC verwaltet.
Generell wird der Gattereingang n-IN0 im unteren Nibble und n-IN1 im oberen Nibble des Registers LUT0CTRLB zugewiesen. Der Gattereingang n-IN2 wird im unteren Nibble des Registers LUT0CTRLC zugewiesen. Am Besten ihr schaut dazu einmal ins Manual Kapitel 27.5.7 und 27.5.8.
Wer die konkreten Bits wissen möchte liest bitte ebenfalls im Manual. Ich arbeite gern mit den fertig definierten Bitnamen die man dem Controller Headerfile entnehmen kann. Im Arduino Ordner nach der Datei 'iom4809.h' suchen lassen. Oder man schreibt, wenn man Bits einzeln verordern und zuweisen möchte, leserlicher _BV(bitnumber). Die Schreibweise bspw. 0b01010101 scheint nur auf dem ersten Blick einfacher. Man muss jedesmal die Bits zählen um zu wissen welches gesetzt ist. Kann Sinn machen, ist aber nur selten der Fall. Zumindestens muss man es kommentieren und damit ist der Vorteil dahin. Ist meine persönliche Meinung.
Zurück zum Thema. Damit wären die untersten Pins der LUT0 als Eingang aktiviert.
CCL.LUT0CTRLB = CCL_INSEL1_IO_gc; // LUT0-IN1 input source from Pin
CCL.LUT0CTRLB |= CCL_INSEL0_IO_gc; // LUT0-IN0 input source from Pin
Hier erkennt man eine gewisse Namenslogik.
CCL_INSEL2... -> n-IN2
CCL_INSEL1... -> n-IN1
CCL_INSEL0... -> n-IN0
Man muss sie nur den richtigen Registern zuweisen. 
Jetzt muss man noch den 3. Eingang 0-IN2 dem Ausgang 1-OUT der LUT1 zuführen. Das macht man mit einer anderen Bitkombination im Register LUT0CTRLC. Namentlich mit 'CCL_INSEL2_LINK_gc'. Damit weiß der Eingang das er auf den LUT1 Ausgang reagieren soll.
CCL.LUT0CTRLC = CCL_INSEL2_LINK_gc; // LUT0-IN2 input source from LUT1-OUT
Dieses Spiel wiederholt sich bis zum letzten Ausgang nach gleichem Schema. Es spielt übrigens keine Rolle welchen Eingang man der nächsten LUT zuweist. Macht das zeichnen der ASCII Grafik jedoch einfacher.
Jetzt fehlt noch die Logikkonfiguration aller LUT Einheiten. Hier hat man alle Freiheiten der Kombinationen. Es gibt keine fertige Gatterlogik zur Auswahl und das ist gut so. Schaut man sich die Wahrheitstabelle an muss man sich das zusammensuchen was man benötigt.
Wahrheitstabelle:
IN2 | IN1 | IN0 | LUTnOUT [bit number in CCL.TRUTHn]
0 | 0 | 0 | TRUTHn[0]
0 | 0 | 1 | TRUTHn[1]
0 | 1 | 0 | TRUTHn[2]
0 | 1 | 1 | TRUTHn[3]
1 | 0 | 0 | TRUTHn[4]
1 | 0 | 1 | TRUTHn[5]
1 | 1 | 0 | TRUTHn[6]
1 | 1 | 1 | TRUTHn[7]
Jede Zeile bzw. jede Bitkombination entspricht im Register 'TRUTHn' genau einem Bit. Weil alles einer ODER Logik folgen soll benötigt man alle Bits außer 0. Anders gesagt, man stellt sich die Frage auf was alles der Ausgang mit logisch '1' reagieren soll. In dem Fall schreibe ich Ausnahmsweise die 0b1111'1110 Schreibweise, weil inkl. Kommentar kürzer.
Nebeninfo. Würde man im Register 'TRUTHn' nur die Bits 1, 2 und 4 setzen hätte man eine Exklusiv ODER (XOR) Logik für diese 3 Eingänge.
Zum Schluss muss man jede LUTn einzeln aktivieren, eine Art Unterverteilungsschalter und die gesamte CCL muss ebenfalls aktiviert werden wie ein Art Hauptschalter. Für weitere Steuerungszwecke nützlich. Einen LUTn Logikausgang muss man nur aktivieren wenn man genau diesen Pin als Hardwareausgangspin haben möchte. Dafür muss er natürlich auf dem Every Board verfügbar sein. Gilt also schon einmal nicht für '0-OUT'. Für interne Verknüpfungen ist das nicht notwendig.
Die komplett konfigurierte LUT0 sieht in unserem Bsp. wie folgt aus.
void initLUT0CCL(void)
{
CCL.LUT0CTRLC = CCL_INSEL2_LINK_gc; // LUT0-IN2 input source from LUT1-OUT
CCL.LUT0CTRLB = CCL_INSEL1_IO_gc; // LUT0-IN1 input source from Pin
CCL.LUT0CTRLB |= CCL_INSEL0_IO_gc; // LUT0-IN0 input source from Pin
CCL.TRUTH0 = 0b1111'1110; // Configure Truth Table, complete OR
CCL.LUT0CTRLA |= CCL_ENABLE_bm; // enable LUT0 Unit
}
Das alles ist bei allen anderen LUTs ähnlich. Ausnahmen sind die fehlenden Pins die wir über das Eventsystem zuführen. Dafür hat man je LUTn Einheit zwei Events (A,B) für zwei Eingänge zur Verfügung. Man kann nicht alle 3 Eingänge frei wählbar mittels Eventsystem zuführen. Man könnte aber noch Ausgänge anderer Hardwareeinheiten nutzen. Für frei wählbare Pins stehen wie gesagt nur zwei Möglichkeiten zur Verfügung. Das reicht uns im Bsp. jedoch, weil ein Pin sowieso mit dem Ausgang der nächsten LUTn+1 verbunden wird. Und bei LUT3 steht ein Hardware Eingangspin auf dem Board zur Verfügung. Damit ist alles in Butter.
Wie funktioniert das nun mit dem Eventsystem für den Pin Ersatz?
Dafür benötigt man die Details des Pins für das Eventsystem. Ich habe das für das Everyboard in einer Tabelle zusammengetragen. Die Informationen stammen aus dem Manual Kapitel 14.5.2. Sieht schlimmer aus wie es ist. Dazu muss man erstmal verinnerlichen was ein Event-Generator und was ein Event-User ist.
Ein Event-Generator ist immer das was ein Signal erzeugt, also die Signalquelle. Das kann ein Boardpin oder ein Ausgang einer Hardwareeinheit oder ein Softwareevent sein. Letzeres habe ich noch nicht ausprobiert, ist aber ein Trigger und kein statisches Signal. Das Signal eines Event-Generators muss also immer von irgendwo herkommen.
Hier sieht man das die Signale vom RTC Overflow, RTC Compare, alle LUTn Ausgänge, ADC Ready, Timer Compare etc. auf allen Kanälen zur Verfügung stehen. Aber wie gesagt, man kann nur einen Event-Generator auf einen Kanal legen. Ansonsten gebe es bildlich gesprochen einen Signalkurzschluss. Bei den Portpins sollte man Einschränkungen in der Kanalwahl erkennen und es gibt eine Doppelbelegung der Kanäle. Dabei sind alle Portpins von Port A, C und E dem Logikport.0 und die Portpins vom Port B, D und F dem Logikport.1 zugeordnet. Desweiteren geht daraus hervor das man die Pins von PORTA und PORTB nur auf Kanal 0 und 1 legen kann. Für PORTC und PORTD nur auf Kanal 2 oder 3. Für PORTE und PORTF nur auf Kanal 4 oder 5.
Ein Event-User ist dagegen immer der Nutzer eines bzw. dieser Signale. Sprich das ist immer irgendein Eingang der diese Signale der Event-Generatoren verarbeitet. Warum schreibe ich bei Event-User in der Mehrzahl? Weil man mehrere Event-User auf einen Event-Generator aufschalten kann. Diese Verknüpfungen erfolgen über sogenannte Channels (Kanäle). Diese liegen zwischen einem Event-Generator und dem Event-User(n). Man kann immer einen Event-Generator einen Kanal zuweisen. Jedoch kann man einen Kanal mehreren Event-Usern zuweisen. Das mache ich mir im nächsten Bsp. zu Nutze. Soviel zum Hintergrund.
Was heißt das nun für unsere Pinzuführung übers Eventsystem?
Ich habe das natürlich schon vorbereitet sodass es zu keinen Überschneidungen der Kanäle kommt.
Die verwendeten Ersatzlogikeingangspins liegen auf PORT E / B und damit auf unterschiedlichen Kanälen. Sprich Kanal 4/5 und 0/1.
Wobei PORT.E auf dem CCL-Logikport.0 und PORT.B auf dem CCL-Logikport.1 liegt.
Zuerst legen wir die Generatoren, also die Portpins auf die erforderlichen Kanäle. Bsp. von LUT1. Im Bild unten blau eingekreist.
EVSYS.CHANNEL5 = EVSYS_GENERATOR_PORT0_PIN1_gc; // verbinde Kanal 5 mit Generator 'Pin D12' (PE1)
EVSYS.CHANNEL4 = EVSYS_GENERATOR_PORT0_PIN0_gc; // verbinde Kanal 4 mit Generator 'Pin D11' (PE0)
Dabei ist die Nummer x des vordefinierten Bitnamens EVSYS_GENERATOR_PORT0_PINx_gc immer die Bitnummer vom Port-Pin, egal welcher Port. Also in der ersten Zeile die 1 von PE1 ist die 1 in EVSYS_GENERATOR_PORT0_PIN1_gc. Danach weisen wir die Kanäle den LUT1 Eventeingängen zu. Die Buchstaben A und B sind hier wichtig. Das ist noch nicht die endgültige Zuordnung auf die Logikeingänge. Das ist ein notwendiger Zwischenschritt.
EVSYS.USERCCLLUT1B = EVSYS_CHANNEL_CHANNEL5_gc; // verbinde User 'CCL-LUT1B' mit Kanal 5
EVSYS.USERCCLLUT1A = EVSYS_CHANNEL_CHANNEL4_gc; // verbinde User 'CCL-LUT1A' mit Kanal 4
Die endgültige Zuweisung erfolgt in den LUTn eigenen Registern. Im Bsp. werden die Events A/B den Logikeingängen 0 und 1 zugewiesen.
CCL.LUT1CTRLB = CCL_INSEL1_EVENTB_gc; // LUT1-IN1 input source from Event.B
CCL.LUT1CTRLB |= CCL_INSEL0_EVENTA_gc; // LUT1-IN0 input source from Event.A
Man kann sie also an 2 Stellen mittels A/B vertauschen. Entweder in der obigen Kanalzuweisung oder hier in der Eingangszuweisung. Stellt euch ein Logikgatter vor. Die Gattereingänge sind die Event-User. An dieses Gatter führen mittels der Kanäle mehrere Eingänge/Events ran. In der gesamten Code Betrachtung sieht das unspektakulärer aus. 
Zusammengefasst benötigt man mindestens 2 Zuweisungen. Einmal weist man einem Kanal einen Event-Generator zu. Danach weist man einem Event-User einen Kanal zu. Hierbei darf man wie erwähnt mehreren Event-Usern den selben Kanal zuweisen. Dabei gibt es keine Beschränkungen.
Ich habe nochmal in einer Grafik versucht die grundlegenden Zusammenhänge bei der Event-Generator Auswahl herauszustellen.
Blau:
Portpin Kanalauswahl auf 2 begrenzt, bedeutet praktisch man kann nur je 2 Pins der Portgruppen A/B oder C/D oder E/F für Event Generatoren verwenden.
Rot:
Der Logikport 0 oder 1 legt die PORT Gruppe fest die man meint. Damit weiß die CCL welcher PORTx auf dem Kanal gemeint ist.
Grün:
Die Bitnummer vom Port-Pin gehört mit zum roten Logikport. Das ist immer die Bitnummer vom Port-Pin. Möchte man zum Bsp. den Arduino Pin D21 verwenden, der auf dem Port-Pin PD5 liegt, dann ist das PORT D und Port-Bit 5. Laut Tabelle kann man PD5 auf Kanal 2 oder 3 als Generator verwenden und liegt auf Logikport.1 Bit 5. Sprich der vordefinierte Bitname laut Controllerheaderfile lautet EVSYS_GENERATOR_PORT1_PIN5_gc.
Gelb:
Die LUTn Ausgänge, sofern vorhanden, kann man frei auf alle Kanäle legen. In unserem Bsp. habe ich LUT0 auf Kanal 3 gelegt.
Orange:
An Eventausgängen bezüglich Port-Pins gibt es nur einen pro gesamten Port namens EVOUTx. Im Bsp. habe ich mich entschieden den fehlenden LUT0 Hardwareausgang Ersatzweise auf Pin PB2 sprich Arduinopin 5 zulegen. Über Kanal 3 ist dieser Pin mit dem Logikausgang der LUT0 verbunden.
Letzte Antwort zur vielleicht gestellten Frage. Warum aktiviere ich die Pullups an den Logik-Eingängen und invertiere diese? Damit ich einfach per Taster den Pin auf Masse ziehen kann. Invertieren korrigiert wiederum dessen Logik. Taster nicht gedrückt bedeutet direkt am Pineingang logisch '1' bedingt durch den Pullup. Invertiert liegt dahinter für die gesamte weitere Logik im µC logisch wiederum '0' an. Das gleiche kann man mit Ausgängen machen falls notwendig. Wer das näher betrachten möchte liest im Manual Kapitel 16.
Zum Schluss das fertige Beispiel.
CCL_Logic_4xOR__LUTs_kaskadiert_with_Event_D5_pinPB2.zip (3,0 KB)
Ich habe mir erlaubt statt der Arduino Pinnummern die echten PortPin Namen zu verwenden. Damit fällt die Zuordnung am Anfang leichter für das Verständnis trotz Tabelle. Könnt ihr für euch jederzeit auf gewohnte pinMode(n) umstellen.
Ich hoffe ich habe nichts vergessen.