Dieses Dokument ist eine Zusammenfassung der Beiträge von Doc_Arduino im Thread zum Nano Every, deren Veröffentlichung mir vom Autor genehmigt wurde.
Nano Every - ATmega4809 - am Anfang stand der ... Anfang
Hallo,
hiermit möchte ich euch den aktuellen Top Controller der megaAVR0 Reihe etwas genauer vorstellen der auf dem Arduino Nano Every Board leider ungeachtet dahin schlummert. Es handelt sich um den ATmega4809. Ich möchte die Scheu vorm Neuen abmildern. Zugegeben, er ist intern etwas anders aufgebaut, logisch, sonst wäre es kein neuer Controller, hat damit einen anderen Registeraufbau, der jedoch logischer aufgebaut ist, und bietet viele neue Möglichkeiten. Die meisten neuen Möglichkeiten beziehen sich darauf das die einzelnen Einheiten mehr Funktionalität bieten und mittels Eventsystem ohne CPU agieren können. Jeder Timertyp hat spezielle Fähigkeiten u.v.vm..
Die größeren Verwandten der Controllerserie sind die AVRxxxDA und AVRxxxDB Serien. Wobei die DB Serie konkret mit dem AVR128DB64 aktuell die Spitze dessen darstellt. Das Nebenbei erwähnt. Ein neuer Arduino Mega mit einem AVR128DB64 wäre optimal. Das wäre 'Mega'. :)
Hier gehts aber nun um den Nano Every mit ATmega4809. Dafür habe ich eine Pin- und Timer TCB Lib erstellt, die in den folgenden Beispielen genutzt werden. Ich habe versucht verständliche Texte zu verfassen. Ob es mir gelungen ist wird sich zeigen. Ich habe alles mit besten Wissen und Gewissen erstellt, kann jedoch keine Gewähr übernehmen. Falls etwas unklar ist einfach nachfragen.
Eine Eigenheit hat das Nano Every Board was man vielleicht als technisch Interessierter wissen sollte aber erstmal nicht beunruhigen darf. Die Arduino Pins 18 und 19 sind auf dem Board doppelt belegt. Die Pins PF2/PF3 dienen als Digital/Analog Pins und PA2/PA3 kommen zum Einsatz wenn man I2C benötigt. Das schaltet die Arduino IDE intern um. Meine Pin Lib, die nur digital fähig ist, berücksichtigt das auch in der Form, dass bei Initialisierung von Pin 18 oder 19 die Portpins PA2/PA3 bewusst nochmal "abgeschalten" und auf ungefährliche Eingänge konfiguriert werden. Das heißt falls jemand träumt und I2C aktiviert und danach die gleichen Pins digital initialisiert, wird I2C usw. abgeschalten. Damit sich nicht 2 Ausgänge um die Vorherrschaft streiten. ;)
Was ich auch nicht unerwähnt lassen möchte ist, es gibt von MCUdude ein "MegaCoreX" Package für die IDE.
MCUdude MegaCoreX
Alle Dokumente zum ATmega4809 findet man beim Chip Hersteller unter Documents. Die App Notes kann ich auch zum reindenken empfehlen.
Herstellerlink: https://www.microchip.com/wwwproducts/en/ATMEGA4809
Bevor es nun wirklich los geht muss noch eine kleine Änderung an der IDE gemacht werden, weil ich meine Libs nicht bis aufs Letzte für den C++11 Standard umschreibe der in der IDE defaultmäßig eingestellt ist, obwohl der mitgelieferte avr-gcc 7.3.0 locker C++14 und auch C++17 versteht.
Dafür gehen wir im IDE Installationspfad nach ...\packages\arduino\hardware\megaavr\1.8.7\
und fügen eine neue Datei namens platform.local.txt hinzu. Ob nun C++14 oder C++17 ist erstmal egal.
Befindet sich mit im .zip File. Wer darüber mehr wissen möchte kann sich hier einlesen. Arduino IDE Feinheiten und Tricks
Im Anhang sind alle benötigten Libs.
Pin Lib:
Wer die Combie Pin Lib schon kennt, sollte sich auch mit meiner schnell zurecht finden. Die megaAVR0 Serie bietet als Feature zusätzlich jedes I/O Signal einzeln zu invertieren. Zusätzlich kann man für alle Pins pro gesamten Port "Slewrate" aktivieren. Das macht die Schaltflanke weniger steil. Desweiteren hat jeder Pin echte Interrupt Fähigkeit mit vielen Einstellmöglichkeiten. Die Interrupt Routine gilt für den gesamten Port. Zur Abfrage ob der konfigurierte Pin einen Interrupt ausgelöst hat, dafür soll die Methode isActive() dienen. Zum löschen des Interrupt Flag dient deleteFlag().
Damit sind wir bei einem kleinen feinen Unterschied zum bisher gewohnten mit den "alten" Controllern. Bisher musste jedes Interrupt Flag nur von Hand gelöscht werden, wenn man ohne Interrupt-Routine programmiert hat. Mit ISR wurde es automatisch gelöscht. Das ist nun nicht mehr der Fall. Jedes Interrupt Flag muss immer manuell gelöscht werden. Es gibt aber Ausnahmen bei den Timern. Pauschal kann man sich manuelles Flag löschen merken und ist prinzipiell erstmal nicht falsch.
Eine Übersicht über alle Methoden für die Objekte findet man im .pdf.
Die Beispiele dafür sind unter Beispiele der Lib zu finden.
Zusätzlich gibt es ein weiteres Headerfile 'NanoEveryPinSpecial.h'. Das ist eher eine Spielerei, gedacht für binäre Pin Statusausgaben zum Bsp. auf einem Display um eine Übersicht über die untere und obere Pinreihe zubekommen. Zum debuggen oder andere Tests kann man sich noch die verwendete Registeradresse und Registerinhalt anzeigen lassen.
Desweiteren gibt es noch ein Headerfile NanoEveryPinNamen.h mit alternativen Pinnamen. Das muss bei Bedarf extra inkludiert werden. Damit ist man der Original IC Pin Bezeichnung näher. Hilfreich wenn man viel mit dem Controller Manual programmiert.
Nano Every -Pin Beschriftung
Hallo,
da auf dem Board leider kein Platz für eine Pinbeschriftung vorhanden ist, habe ich das bei mir durch Ausdruck und aufkleben eines Stück Papiers gelöst. Im .zip ist ein Excelfile zum ausdrucken. Beim Drucken muss ich 95% Größe einstellen, dann passt das 2,54 Raster sehr gut. Innen noch 2 Löcher für die seriellen Leds und Resettaster ausschneiden und auf die größeren Teile mit kleinen Leimklecks kleben.
Nano Every - Timer TCB und auch etwas TCA
Hallo,
diese Beispiele sollen als Einstieg für den Timer TCBn stehen. Timertyp B kann man als Messtimer betrachten. Er stellt verschiedene Möglichkeiten zur Verfügung was man von einem Signal messen kann. Unter anderem auch eine "Timeout" Methode, wenn sich innerhalb einer Zeit das Signal nicht ändert. Vom Typ TCB stehen insgesamt 4 Einheiten zur Verfügung.
Für Timer TCB gibts eine Lib und die Übersicht über alle Methoden findet man im Example "_TCBnMethodenUebersicht".
Vorab.
Falls man einen TCB zugehörigen Ausgangspin (WO - Wave Output) vom Timer schalten lassen möchte, dann stehen auf dem Every Board nur 2 Pins zur Verfügung. Von TCB0 Pin 6 und von TCB1 Pin 3. Die müßte man per PORTMUX Register "aktivieren" bzw. umlegen, weil diese Pins nicht die default Ausgangspins sind. Kurzum, mittels PORTMUX kann man Pins intern umrouten. Dazu später mehr. Benötigen wir hier im Beispiel nicht. Zum benötigten Eventsystem gehe ich in den Beispielen um den ADC näher ein.
1.
In diesem Beispiel wollen wir die bekannte Led D13 in der loop mittels millis takten lassen. Von diesem Takt messen wir die Periodendauer und Pulsweite mit dem Timer TCB0.
Damit wir beim experimentieren nicht alles ständig umkonfigurieren müssen, habe ich den Prescaler von Timer TCB0 auf den Prescaler von Timer TCA0 konfiguriert und mit 64 festgelegt. Dieser ist in den Arduino Einstellungen default auf 64 eingestellt. Damit haben wir ohne größeren Aufwand erstmal mehr zeitlichen Spielraum zum experimentieren.
Man muss jetzt nur in dem Beispiel die maximale Messzeit von TCB0 beachten. Die sich einfach aus Eingangstakt-Periodendauer und Wertebereich zusammensetzt. Also 62,5ns * 64 * 65536 = 262,144ms
Damit die Led D13 blinkt, muss man der Funktion heartbeat(n) nur die gewünschte Periodendauer als Parameter mitgeben. Wie gesagt hier in dem Bsp. max. 262ms. Bei größeren Werten blinkt die Led dennoch wie gewünscht, aber die Periodedauer liegt für unseren Timer TCB0 außerhalb seines Wertebereiches. Man bekäme nur Überlaufreste angezeigt. Mit TCB0 eigenen Prescaler 1 und 2 verkleinert sich der heartbeat Parameter entsprechend auf 4 bzw. 8ms.
Am Ende werden wir feststellen, dass die Messwerte etwas schwanken, was immer "schlimmer" wird je höher Led D13 taktet. Das liegt daran das die Led zur Programmlaufzeit getaktet wird. Sprich immer wenn die loop dafür Zeit hat. Für Led Geblinke sicherlich vollkommen egal. Zur Demonstration mit TCB0 aber sehr anschaulich.
2.
Das zweite Bsp. soll für den Umgang mit TCBn und auch TCA0 dienen. Diesmal soll der Led D13 Takt genauer werden. Deswegen konfigurieren wir den Timer TCA0 für unsere Zwecke um und lassen die Led in dessen Interrupt Routine toggeln. Weil durch die TCA0 Umkonfiguration millis() beeinflusst wird, müssen wir das korrigieren. Dafür dient die Funktion correctedMillis().
Timer TCA ist im Normal Mode konfiguriert. In dem Modi ist das PER Register Frequenz bestimmend. Vorweg. Es gibt für alle wichtigen Register immer ein Buffer Register die immer verfügbar sind im Gegensatz zu den bekannten älteren Familien mit Typen wie ATmega328P und ATmega2560 & Co. Man sollte laut meiner Meinung immer das Buffer Register verwenden, weil damit die Timereinstellungen immer zum richtigen Zeitpunkt übernommen werden. Außer es gibt andere Gründe.
Für den Timer TCA0 habe ich keine Lib erstellt, vielleicht auch nicht schlecht, sieht man doch die gesamte Konfiguration. Defaultmäßig wird TCA0 von Arduino für millis und PWM verwendet und arbeitet im 8Bit Modus. Das alles setzen wir anfangs zurück und konfigurieren ihn komplett neu. Wir müssen ihn für das Bsp. nur im Normalmode konfigurieren und den Overflow Interrupt aktivieren. Desweiteren einen Wert ins Perioden Register (PER/PERBUF) schreiben. Sobald der Timerzähler diesen Wert erreicht hat, löst er einen Overflow Interrupt aus, die Programmabarbeitung (loop) springt in dessen Interrupt Routine, toggelt die Led macht in loop weiter.
Am Ende seht ihr hoffentlich wie ich an Hand der Messwerte, dass der Takt schon stabiler ist im Vergleich zu vorher. Das übrig gebliebene zappeln der Messwerte ist bedingt durch die Interruptunterbrechungen der seriellen Ausgabe.
3.
Aufbauend auf dem Beispiel "TCB FRQPW with TCA ISR" und etwas größer ausgelegt.
Um den Einfluss von Interrupts rauszunehmen, lassen wir diesmal den Taktpin vom Timer TCA0 selbst schalten. Das sind die sogenannten 'WOn' Pins. Wave Output. Davon hat der Timer TCA0 insgesamt 3 im normalen 16Bit Modus oder 6 im 8Bit Splittmodus. Entweder hat man 0-WO0...0WO2 oder 0-WO0...WO5 zur Verfügung.
Die vorangestellte '0-' steht übrigens für die Null von TCA0. Da der verwendete ATmega4809 vom Timertyp TCA nur einen hat, gibts nur TCA0 und keinen TCA1 oder TCA2.
Ich beschränke mich hier auf den TCA0 Frequency 16Bit Modus. Schaut man sich die Controller Pin-Mux Tabelle an, sieht man das in Default PORTMUX Einstellung zwei Pins zur Verfügung stehen die nach außen geführt sind. 0-WO0 auf Pin 2 und 0-WO1 auf Pin 7. Als Bsp. könnte man alle 0-WOn Pins komplett auf Port E verlegen. Dafür muss man nur das PORTMUX Register ändern. In dem Fall PORTMUX.TCAROUTEA = PORTMUX_TCA0_PORTE_gc. Damit liegt 0-WO0 auf Pin 11 und 0-WO1 auf Pin 12. Nicht vergessen die geänderten Pins auf Ausgang zu konfigurieren.
Wir nutzen wie gesagt in dem Bsp. den Frequency Modus, da im Normal Modus kein 0-WOn Pin zur Verfügung steht. Der Grund ist, dass die WOn Pins nur auf aktivierte CMPnEN und damit nur auf Vergleichswerte in den CMPn/CMPnBUF Registern reagieren. Das Perioden/Frequenz bestimmende Register im Frequency Mode ist also immer CMPn. In unserem Bsp. CMP0. Alle erzeugten Frequenzen haben ein Duty Cycle von 50% und sind auf allen Ausgängen gleich. Zusätzlich lasse ich den TCA0 Timerausgang 0-WO1 takten.
Wie schon einmal erwähnt, sollten soweit verfügbar die gepufferten Timerregister verwenden. Das sind in unserem Fall CMP0BUF und CMP1BUF statt CMP0 und CMP1. Mittels Bufferregister ist sichergestellt das Timeränderungen immer zum richtigen Zeitpunkt erfolgen und damit zum Zeitpunkt der Änderung keine unerwünschten Signalverläufe entstehen.
Wir wissen das im Bsp. CMP0 für den gesamten Timer TCA0 das Frequenz bestimmende Register ist. Das bedeutet CMP1 (oder CMP2) darf nie größer sein wie CMP0, ansonsten kann kein Vergleich (Compare) stattfinden. Das bedeutet weiter das ein Signal mit CMP1 im Vergleich zu CMP0 'nur' nach vorn verschoben ist. Ansonsten sind sie immer identisch.
Der Rest ist wie vorher, dass Signal von 0-WO0 wird mittels Timer TCB0 ausgemessen und seriell ausgegeben.
Der Messpin 21 wird mit Kanal 2 des Eventsystems verbunden und der wiederum gibt das Signal im Eventsystem an den Timer TBC0 weiter.
Nano Every - CCL – Configurable Custom Logic
Hallo,
hiermit möchte ich euch das Logik Modul vorstellen.
Das Board stellt allerdings nur eine komplette Logikeinheit nach außen zur Verfügung. Konkret LUT2 auf den Arduino Pins 14-17. Damit wollen wir uns etwas näher befassen. Ist am Ende gar nicht so schwer.
Dafür habe ich die Pins wie folgt vorbereitet.
An den Logik Eingangspins 15-17 sitzen Taster die nach Masse schalten.
An dem Logik Ausgangspin 14 sitzt eine Led.
Damit das alles funktioniert, aktivieren wir für die Eingangspins die Pullups und invertieren das Signal, damit am Ende die CLL Logik korrekt arbeiten kann. Vorweg, ihr müßt dazu nicht zwingend meine NanoEveryPin Lib verwenden. Die konfiguriert hier auch nur die Pins als gewöhnliche Ein- und Ausgänge.
Jetzt gehts los. Jede Logikeinheit hat maximal 3 nutzbare Eingänge und einen Ausgang.
Jeder dieser 3 Eingänge kann von irgendwoher sein Signal bekommen. Das muss nicht ein physischer Eingangspin sein wie im folgenden Beispiel. Man könnte auch einen Timerausgang, Usart TX Signal etc. oder den Ausgang einer Logikeinheit auf einen Eingang einer anderen Logikeinheit konfigurieren. Siehe Manual 27.2.2.1.
Das alles wird in den LUTn Registern CLTRB und CTRLC konfiguriert. In unserem Fall LUT2.
Wir bleiben bei den nach außen verfügbaren Pins.
Das heißt für uns wir müssen IN0, IN1 und IN2 von LUT2 konfigurieren.
In den Tabellen 27.5.7 und 27.5.8 haben diese immer den Wert 0x05. Das ist nur der Wert der Bitgruppe INSELx, nicht der benötigte Registerwert. Irgendwelche Bitschubserei können wir uns sparen, weil es vordefnierte Bitnamen für alles gibt. Man muss sie nur finden. :-) Dazu schaut man das Controller Headerfile om4809.h einmal in Ruhe durch.
Wir benötigen die vordefinierten Bitgruppen CCL_INSEL0_IO_gc, CCL_INSEL1_IO_gc und CCL_INSEL2_IO_gc und müssen diese nur noch den richtigen Registern zuweisen.
Eingang LUTn-IN0 und LUTn-IN1 werden immer im LUTn.CLTRB Register konfiguriert.
Eingang LUTn-IN2 wird immer im LUTn.CLTRC Register konfiguriert.
Also verordert man ggf. die Bitnamen und weißt das Ergebnis dem entsprechenden Register zu.
CCL.LUT2CTRLB = CCL_INSEL0_IO_gc; // pin LUT2-IN0 input source, PD0
CCL.LUT2CTRLB |= CCL_INSEL1_IO_gc; // pin LUT2-IN1 input source, PD1
CCL.LUT2CTRLC = CCL_INSEL2_IO_gc; // pin LUT2-IN2 input source, PD2
Das der Logikausgang auf einen Pin mündet konfiguriert man im Register CTRLA und schaltet darin auch diese Logikeinheit scharf.
CCL.LUT2CTRLA = CCL_OUTEN_bm; // enable Output LUT2, PD3
CCL.LUT2CTRLA |= CCL_ENABLE_bm; // enable LUT2
Zusätzlich muss man noch den "Hauptschalter" der gesamten CCL Einheit scharf schalten.
CCL.CTRLA = CCL_ENABLE_bm; // general enable CCL module
Und nun kommen wir zum wichtigsten Teil, der Wahrheitstabelle, also wie der Ausgang auf Eingänge reagieren soll. Das wäre Tabelle 27.3.1.3 Jede Zeile bzw. jede Eingangs Bit Kombination entspricht einem bestimmtes Bit im TRUTHn Register. Diese kann man beliebig kombinieren sprich addieren.
UND Logik: Vermutlich der einfachste Einstieg in den Aufbau.
Der Ausgang darf nur auf '1' schalten, wenn alle Eingänge '1' haben.
Das entspricht der letzten Zeile in der Tabelle.
Das bedeutet man muss im TRUTHn Register nur das Bit 7 setzen und hat seine UND Funktion. Dezimal 128.
ODER Logik:
Der Ausgang darf auf '1' schalten, wenn mindestens ein Eingang '1' hat, egal welcher.
Hierbei werden sicherlich die meisten Leute, wie ich anfangs auch, alle Zeilen zusammensuchen wo jeder Eingang einzeln logisch '1' ist. Also Bit 1, 2 und 4 im TRUTHn Register setzen und sich wundern das es nicht so richtig funktioniert. Das entspricht nämlich dem XOR. Für ein ODER müssen wir alle Kombinationen addieren außer Bit 0. Dezimal 254. Dann klappt das auch mit dem ODER.
XOR Logik:
Bei nur einer einzigen Änderung eines Eingangs schaltet der Ausgang auf '1'.
Die Antwort habe ich nun schon vorweg genommen. Man kombiniert alle Bits der Zeilen wo nur ein Eingang für sich alleine '1' hat. Das wären hier Bit 1, 2 und 4. Dezimal 22.
Das wars für den Einstieg. Ein hilfreiches Dokument außer dem Manual ist noch die App Note TB3218 oder das Dokument zum ATMEGA4809
PS:
Man könnte auch die anderen Logikeinheiten verwenden, da laut meiner Recherche die nicht nach außen geführten Pins auf dem Board nicht beschalten sind. Das heißt für alle internen Eventsignale sind diese theoretisch nutzbar. Ich habs noch nicht getestet.
Besondere Beachtunng sollte allerdings Arduino Pin 18 und 19 geschenkt werden. Die haben Board intern eine Doppelbelegung mit PF2/PF3 und PA2/PA3. Zwischen den Ports wird umgeschalten je nachdem ob die Pins digital, analog oder für I2C verwendet werden. Die ICs Pins sind dabei auf dem Board direkt verbunden. Ich möchte damit nur sagen, falls man selbst an den Registern fummelt, sollte man dafür Sorge tragen das die zusammengelegten Pins nicht zeitgleich Ausgänge sind.
Beispiele
Nano Every - Eventsystem, ADC, RTC
Hallo,
hier soll es um das Eventsystem gehen. Gibts schon länger in den XMEGA Controllern, die ich aber nicht kenne. Das Eventsystem hat nun auch in die neuen Controller der ATmega und ATtiny Serie Einzug gehalten. Der grundlegende Sinn dahinter ist, dass man Aufgaben ohne zutun der eigentlichen MCU bzw. Code erledigen kann. Ein Timer bspw. der seinen eigenen Timerausgang ohne ISR schaltet arbeitet auch selbstständig ohne das die MCU bzw. der Code eingreifen muss. Der arbeitet also autark. Mit dem Eventsystem kann man nun solche Einheiten verbinden ohne das man im Code irgendwelche Signale abfragen und Daten zwischen den Einheiten weitergeben muss. In den gezeigten Bsp. reduziert man damit die CPU bzw. Interruptlast.
Beim Eventsystem gibts es mehrere Channels über die man verschiedene Dinge miteinander verbinden kann. Sprich man legt fest wer auf was reagieren soll. Dabei gibts immer einen Eventgenerator. Dieser sorgt dafür das ein Event überhaupt ausgelöst wird. Bspw. ein Zählerüberlauf nach bestimmter Zeit um irgendwas zyklisch zu machen. Auf der anderen Seite eines Channels gibts immer einen oder mehrer User. Das sind die Empfänger und reagieren auf das Eventereignis. Daraufhin kann irgendeine Aktion gestartet werden. Bspw. sorgt ein Timer Event Generator dafür das zyklisch der ADC ausgelesen wird. Oder ein Signal an einem Pineingang startet einen Timer der das Signal vermisst. Der Phantasie sind keine Grenzen gesetzt.
Das Thema wird im Manual Kapitel 14 beschrieben. Von 14.5.2 ausgehend zeige ich für das Nano Every Board zugeschnitten eine kleine Übersicht welchen Arduino Pin als Event Generator man mit welchen Event Channel verbinden kann. Man kann einen Pin nicht mit jedem x-beliebigen Kanal verbinden. Event User kann man dagegen beliebig anbinden. Anders ausgedrückt ein Event Ausgang (Generator) kann man mit beliebigen Event Eingängen (User) über vorgesehene Kanäle verbinden.
| Event |
Pin Port | Port | Channel |
0 PC5 | 0 2 or 3 |
1 PC4 | 0 2 or 3 |
2 PA0 | 0 0 or 1 |
3 PF5 | 1 4 or 5 |
4 PC6 | 0 2 or 3 |
5 PB2 | 1 0 or 1 |
6 PF4 | 1 4 or 5 |
7 PA1 | 0 0 or 1 |
8 PE3 | 0 4 or 5 |
9 PB0 | 1 0 or 1 |
10 PB1 | 1 0 or 1 |
11 PE0 | 0 4 or 5 |
12 PE1 | 0 4 or 5 |
13 PE2 | 0 4 or 5 |
14 PD3 | 1 2 or 3 |
15 PD2 | 1 2 or 3 |
16 PD1 | 1 2 or 3 |
17 PD0 | 1 2 or 3 |
18 PF2 | 1 4 or 5 |
19 PF3 | 1 4 or 5 |
20 PD4 | 1 2 or 3 |
21 PD5 | 1 2 or 3 |
Am Bsp. mit der Timer TCB0 Messung verbinden wir Messpin 21 mit dem Timer Eingang. Der Pin 21 ist damit der Eventgenerator. Er erzeugt mit seinem Signal ein Ereignis worauf reagiert werden soll. Laut Tabelle können wir für Pin 21 den Kanal 2 oder 3 verwenden. Zusätzlich ist Eventport 1 damit festgelegt.
Wir haben in dem Fall die Wahl zwischen folgenden Zuweisungen.
EVSYS.CHANNEL2 = EVSYS_GENERATOR_PORT1_PIN5_gc; // connect Pin 'PD5' to channel 2
oder
EVSYS.CHANNEL3 = EVSYS_GENERATOR_PORT1_PIN5_gc; // connect Pin 'PD5' to channel 3
'PORT1' im Bitnamen sollte klar sein warum. Die 5 von PIN5 im Namen ist die Bitnummer vom Pin. PD5 ist die orignale Pinbezeichnung des Arduino Pin 21.
PD5 sagt aus, dass der Pin am Port.D und Bit.Nummer 5 angebunden ist. Das am Rande.
Wenn der Kanal ausgewählt ist, muss man auf der anderen Seite, bildlich gesprochen, noch den User anbinden. Bspw. den Timer TCB0.
EVSYS.USERTCB0 = EVSYS_CHANNEL_CHANNEL2_gc;
Damit wandert das Signal am Pin 21 mittels Kanal 2 oder 3 zum Timer TCB0. Zusätzlich kann man mehrere User an einen Kanal anbinden, falls mehrere User auf Pin 21 reagieren sollen. Dabei gibts keine Einschränkung. Soviel zur grundlegenden Übersicht.
Kommen wir zu einem Beispiel womit 2 Event Generatoren und 2 Event Usern mit jeweils einem Kanal verbunden werden.
Wir wollen das der ADC0 zyklisch aller einer Sekunde eine Messung macht. Das Zeitintervall erzeugen wir diesmal nicht mit millis sondern machen uns vom Timer TCB0 den Timeout Modus zu nutze.
Der TCB Timeout Modus funktioniert wie folgt. Er überwacht eine Signaldauer und wenn diese eine eingestellte Zeit überschreitet, dann reagiert er mit dem Interrupt Capture Flag. Auf welche Flanke er reagieren soll kann man im EVCTRL Register einstellen. Manual 21.5.3. Wir benötigen also nur ein Signal an einem Pin welches wir einschalten aber nicht gleich wieder ausschalten. Damit geht TCB0 in den Timeout und liefert uns eine Reaktion das die Zeit um ist. Die Konfiguration sieht man in der Funktion 'initTCB0()'. Damit wir auf verfolgbare Zeiten kommen im seriellen Monitor lassen wir aller 1s eine Messung machen. Weil wir millis nicht benötigen ändern wir einfach den Prescaler von TCA0 auf 1024 und konfigurieren auch TCB0 darauf. Damit haben wir eine Taktzeit von TCB0 von 64µs. Damit wir auf 1s kommen multiplizieren wir den Wert mit 15625. Das ist unser Vergleichswert damit TCB0 aller 1s auslöst.
Damit das alles klappt wie gewünscht verbinden wir mittels Eventsystem das benötigte 'Timeout' Signal mit dem TCB0 Eventeingang und das TCB0 Capture Signal mit dem ADC0 Eventeingang. Also fangen wir an. Pin 2 (PA0) können wir laut Tabelle (bzw. Manual 14.5.2) nur mittels Kanal 0 oder 1 auf Eventport 0 mit irgendeinem User verbinden. Diesen Event Generator verbinden weiter über Kanal 1 an den Event User Timer TCB0. Damit der ADC0 daraufhin zyklisch seine Messung starten kann, verbinden wir das TCB0 Generator Event über Kanal 0 an den Event User ADC0. Hier hätten wir laut Manual jeden Kanal nehmen können. Außer Kanal 1, der ist von uns schon belegt.
Das Programm folgt nun dem einfachen Prinzip. Setze Pin 13 auf 'High', warte bis Timer TCB0 seinen Timeout auslöst, daraufhin startet ADC0 seine Messung an Pin 21, setzt eine bool Variable, auf die gepollt wird. Wenn diese 'true' ist wird der Messwert seriell ausgegeben. Gleichzeitig setzt der ADC0 nach seiner Messung den Pin13 zurück auf 'Low'. Wenn der Messwert seriell ausgegeben wurde wird Pin 13 erneut auf 'High' gestetz und das Spiel beginnt von vorn. Das 'High' setzen kann man auch am Ende der ISR von ADC0 schreiben, dann wäre die Zykluszeit genauer. Lasse ich außen vor.
Zum Aufbau muss man folgende Pins verbinden. D13 mit D2 und an Pin 21 kommt der Schleiferkontakt eines Potis.
Die Pinkonfiguration für ADC0 ist auch schnell erklärt. Wir schauen im Manual in der Pin Multiplex Übersicht 4.1 welcher Pin davon auf unserem Board zur Verfügung steht. Ich habe mir einfach 'AIN5' rausgesucht der auf Arduino Pin 21 (pinPD5) liegt. Das wird im ADC0.MUXPOS Register konfiguriert. Dann schalten wir noch den Interrupt ein und sagen ihm das er auf das Eventsignal reagieren soll. Zusätzlich aktivieren wir den Sample Akkumulator und wählen den Faktor 32. Der dient zum Oversampling bzw. Rauschunterdrückung und arbeitet ADC intern ohne äußeres zutun. Als ADC Rückgabewert erhalten wir den aufsummierten Akku-Wert. Wir teilen diesen durch 32 und haben den üblichen 10 Bit Messwert als Durchschnitt von 32 Einzelmessungen.
Was hat es mit der inkludierten Lib ForwardCompatibility.h auf sich?
Da ich immer auf dem neuesten Stand sein möchte, verwende unter anderem auch immer die aktuelle Version vom Controller Headerfile. Das Headerfile wird fast immer parallel zum Manual verwendet, damit ich möglichst die eh schon vordefinierten Bitnamen verwenden kann. Nun hat sich aber seit Veröffentlichung vom Nano Every Board das Headefile aktualisiert. Sprich ein paar Bitdefinitionen haben sinnvollere Namen bekommen. Damit ich trotz aktueller Namen zur Standard Arduino IDE kompatibel bin, habe ich ein Headerfile erstellt, was niemanden stören wird.
Die anderen beiden ADC Bsp. verwenden die interne RTC statt dem Timer TCB und basieren auf der App Note von Microchip. Einmal mit Interrupt einmal ohne. Der ADC Eingangstaktteiler wird auf Arduino Default Einstellung 128 und Vref auf Vdd belassen. Aktiviert ist er auch.
Beispiele
Ein weiterer Beitrag von Doc_Arduino.
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.
Hier noch der Beitrag #13
Hallo,
jetzt noch ein Bsp. mit mehr Praxisnutzen für den Einen oder Anderen. Es geht um die Ansteuerung einer H-Brücke mit BTS7960 (BTN7960). Gibts bei ebay als fertiges kleines Modul. Eine gute Beschreibung findet man zum Bsp. hier:
BTS7960 Motor Driver (1).pdfA (1,1 MB)
Die 43A sollte man allerdings für das Modul nicht so ernst nehmen.
Das Modul hat:
- 2 Stromüberwachungsausgänge (R_IS und L_IS)
- 2 Enable Eingänge (R_EN und L_EN)
- 2 PWM Eingänge (R_PWM und L_PWM)
Meine Überlegung war wie ich den einen PWM Takt vom µC kommend beiden Eingängen zukommen lasse und gleichzeitig die Richtung vorgebe ohne irgendwelche Signale mit Relais o.ä. umschalten zu müssen. Das hatte ich bisher mit einem externen 4 fach NAND IC gelöst.
> ____ ____
> (CW)------| | +---| |
> | & |o--+ | & |o---> BTS (R_PWM)
> +---|____| +---|____|
> |
> (PWM)--+
> | ____ ____
> +---| | +---| |
> | & |o--+ | & |o---> BTS (L_PWM)
> (CCW)------|____| +---|____|
CW .... Clockwise, Uhrzeigersinn
CCW ... Counterclockwise, Gegenuhrzeigersinn
Es muss dabei allerdings in der Software beachtet werden, dass CW und CCW nicht gleichzeitig ihr Richtungssignal rausgeben. Ansonsten gebe es in der H-Brücke einen Kurzschluss. Das muss man per Software verriegeln. Solche Sicherheits relevanten Sachen sollte man wenn irgendwie möglich in Hardware lösen. Man müßte entweder noch eine XOR Logik davor schalten was so aussehen könnte.
> +---------------------+
> | ____ | ____
> (CW)--+--| | +---| |
> |XOR |--+ | & |---> BTS (R_PWM)
> (CCW)--+--|____| | +---|____|
> | | ____ |
> | +--| | | ____
> | | & |--+---| |
> | (PWM)-----|____| | & |---> BTS (L_PWM)
> +-------------------------|____|
Oder man nutzt die CCL vom ATmega4809. Logisch. Genau hier steig ich ein, weil die CCL Gatter jeweils 3 Eingänge zur Verfügung stellen, kann ich das Eingangs XOR und AND mit einem Gatter erschlagen und kombinieren. Das Gesamtkunstwerk sieht nach stundenlanger Überlegung, wegen der Pinauswahl, vollbeschriftet wie folgt aus.
> ____
> (D4)(PC6)(Event)>---+------------------------>(EventA)--| |
> CW Freigabe | | & |---> (CW)(D15)(PD2)(EVOUTD) --> BTS (R_PWM)
> | +-->(EventB)--|____|
> | ____ | LUT1
> +--(EventA)--| | |
> (D2)(PA0)(0-IN0)>----------------| |---+
> PWM Takteingang +--(EventB)--|____| |
> | LUT0 |
> | | ____
> | +-->(EventA)--| |
> | | & |---> (CCW)(D14)(PD3)(2-OUT) --> BTS (L_PWM)
> (D7)(PA1)(Event)>---+------------------------>(EventB)--|____|
> CCW Freigabe LUT2
Mein Ziel war es die Pins von I2C, SPI und Timerausgänge frei zu halten was letztlich gelungen ist. Ich hätte am Liebsten noch einen Timerausgang µC intern übers Eventsystem einem LUT0 Eingang zugewiesen, aber wir wissen ja, man kann nur 2 Eingänge über das Eventsystem einer LUTn zuführen. Genau diese zwei A/B Eventmöglichkeiten benötigen wir schon für die Aufteilung (Parallelschaltung) der CW/CCW Richtungsfreigaben. Dafür liegen die Pins der Timerausgänge und der PWM Eingang auf der gleichen Boardseite und können per kurzer Drahtbrücke verbunden werden. Auch nicht schlecht.
Die endgültige Pinbelegung reduziert sich durch das Eventsystem auf das Wesentliche.
Ardu | Event | EVSYS | CCL-LUTn |
Pin Port | Port | Channel | | |
2 PA0 | | | 0-IN0 | PWM-IN
4 PC6 | 0 2 or 3 | | | Freigabe CW
7 PA1 | 0 0 or 1 | | | Freigabe CCW
15 PD2 | | EVOUTD | | PWM-OUT CW
14 PD3 | | | 2-OUT | PWM-OUT CCW
Ich habe für mich festgelegt das ich Pin D4 über Kanal 2 und Pin D7 über Kanal 1 im Eventsystem verlege. Das sind jeweils die Signal Generatoren im Eventsystem. Diesen beiden Kanälen weise ich jeweils 2 Event-Usern zu. Nämlich einmal einen Eingang von LUT0 und einen Eingang von LUT1 bzw. LUT2.
// Enable-CW auf 2 Eingänge verteilen
EVSYS.CHANNEL2 = EVSYS_GENERATOR_PORT0_PIN6_gc; // PC6
EVSYS.USERCCLLUT1A = EVSYS_CHANNEL_CHANNEL2_gc;
EVSYS.USERCCLLUT0A = EVSYS_CHANNEL_CHANNEL2_gc;
// Enable-CCW auf 2 Eingänge verteilen
EVSYS.CHANNEL1 = EVSYS_GENERATOR_PORT0_PIN1_gc; // PA1
EVSYS.USERCCLLUT0B = EVSYS_CHANNEL_CHANNEL1_gc;
EVSYS.USERCCLLUT2B = EVSYS_CHANNEL_CHANNEL1_gc;
Mit dem LUT0 Ausgang mache ich das Gleiche. Hier habe ich freie Kanalwahl, außer das Kanal 1 und 2 schon belegt sind. Ich verwende hierfür Kanal 0, der ist für andere Pins sowieso nicht mehr verwendbar. Auch diesem Kanal 0 weise ich 2 Eingänge, sprich "User" zu. Einer von LUT1 und einer von LUT2. Welcher Gattereingang das später konkret wird, wird hier noch nicht festgelegt. Man legt nur fest, dass es zu einer bestimmten LUTn gehören soll samt deren A/B Zuordnung. A/B muss man sich wie eine weitere Unterverteilung vorstellen. A/B ist LUTn spezifisch und nicht Kanal spezifisch.
// LUT0-OUT auf 2 Eingänge verteilen
EVSYS.CHANNEL0 = EVSYS_GENERATOR_CCL_LUT0_gc;
EVSYS.USERCCLLUT1B = EVSYS_CHANNEL_CHANNEL0_gc;
EVSYS.USERCCLLUT2A = EVSYS_CHANNEL_CHANNEL0_gc;
Für LUT2 kann ich direkt den Gatterausgang 2-OUT am Pin (D14) verwenden. Den anderen Ausgang von LUT1 muss ich übers Eventsystem rausführen. Dafür stehen im Eventsystem die User EVOUTx zur Verfügung. EVOUTD liegt auf Pin 15 und ich verwende dafür den freien Kanal 3.
// LUT1-OUT zum Pin rausführen
EVSYS.CHANNEL3 = EVSYS_GENERATOR_CCL_LUT1_gc;
EVSYS.USEREVOUTD = EVSYS_CHANNEL_CHANNEL3_gc; // Pin 15 (PD2)
Bleibt noch die LUTn Konfiguration und ich beginne mit dem Eingangsgatter LUT0 an.
void initLUT0CCL(void)
{
CCL.LUT0CTRLB = CCL_INSEL0_IO_gc; // LUT0-IN0 input pin
CCL.LUT0CTRLB |= CCL_INSEL1_EVENTA_gc; // LUT0-IN1 input source from Event
CCL.LUT0CTRLC = CCL_INSEL2_EVENTB_gc; // LUT0-IN2 input source from Event
CCL.TRUTH0 = _BV(5) | _BV(3); // Configure Truth Table, (IN0&IN1) XOR (IN0&IN2)
CCL.LUT0CTRLA |= CCL_ENABLE_bm; // enable LUT0
}
Hierfür noch eine Nebeninformation. Der Gattereingang n-IN0 wird im unteren Nibble und n-IN1 im oberen Nibble im Register LUT0CTRLB zugewiesen. Der Gattereingang n-IN2 wird im unteren Nibble im Register LUT0CTRLC zugewiesen. Am Besten ihr schaut dazu einmal ins Manual Kapitel 27.5.7 und 27.5.8.
Pin D2 (0-IN0) ist ein direkter Gatterpin ohne weiteren Umweg.
Gatterpin 0-IN1 weise ich vom Eventsystem den "Unterverteiler" A zu.
Gatterpin 0-IN2 weise ich vom Eventsystem den "Unterverteiler" B zu.
Danach wird die Logik die eigentliche Aufgabe des Gatters konfiguriert.
Ich möchte ja eine Verriegelung zwischen CW und CCW haben, sprich ein Exklusiv ODER (XOR). Und ich möchte das der PWM Takt nur dann durchgeleitet wird, wenn das XOR logisch '1' liefert.
Das heißt ich verknüpfe das XOR mit PWM per UND. Wie in der zweiten Zeichnung am Anfang dargestellt. In der Logiktabelle muss ich mir dafür nur die Logikzustände aussuchen die dieser Logik entsprechen. Der LUT0 Ausgang soll nur schalten wenn eine logische '1' von PWM anliegt UND entweder von CW oder von CCW eine Freigabe anliegt. Auf keinen Fall darf der LUT0 Ausgang reagieren wenn CW und CCW zeitgleich anliegen. Die gewünschte Logik wird mit diesen beiden Bitkombinationen erreicht.
Wahrheitstabelle:
IN2 | IN1 | IN0 | LUTnOUT [bit number in CCL.TRUTHn]
0 | 1 | 1 | TRUTHn[3]
1 | 0 | 1 | TRUTHn[5]
Entweder IN0 und IN1 oder IN0 und IN2. Dafür setze ich Bit 3 und 5 im entsprechenden LUT0 Register 'TRUTH0'.
Für LUT1 und LUT2 gilt ähnliches in der Pin Zuführung mittels Eventkonfiguration.
// Enable-CW auf 2 Eingänge verteilen
EVSYS.CHANNEL2 = EVSYS_GENERATOR_PORT0_PIN6_gc; // PC6
EVSYS.USERCCLLUT1A = EVSYS_CHANNEL_CHANNEL2_gc;
EVSYS.USERCCLLUT0A = EVSYS_CHANNEL_CHANNEL2_gc;
// Enable-CCW auf 2 Eingänge verteilen
EVSYS.CHANNEL1 = EVSYS_GENERATOR_PORT0_PIN1_gc; // PA1
EVSYS.USERCCLLUT0B = EVSYS_CHANNEL_CHANNEL1_gc;
EVSYS.USERCCLLUT2B = EVSYS_CHANNEL_CHANNEL1_gc;
Dessen Gattereingänge werden jeweils UND verknüpft. Dafür sorgt Bit 3 im entsprechenden LUTn Register 'TRUTHn'. Eine zusätzliche Konfiguration ist für LUT2 erforderlich. Es wird ja der Gatterausgang direkt verwendet. Deswegen muss dieser Ausgang aktiviert werden.
CCL.LUT2CTRLA = CCL_OUTEN_bm; // enable Output LUT2
Das wars. Mehr ist das nicht. Man muss sich nur die Zeit nehmen, sich in Ruhe reindenken, die App Notes lesen und ausprobieren.
Bleibt noch die Frage was ich mit den beiden Enable Eingänge (R_EN und L_EN) vom H-Brückemmodul mache? Die kann ich entweder beide fest auf High-Pegel klemmen. Oder ich schließe einen gemeinsamen "Hauptschalter" an um nochmals losgelöst von allen anderen das Modul generell ein- und ausschalten zu können. Damit könnte man ein Not-Aus bauen. Oder ich führe die Pins gemeinsam dem µC zu und schalte es per Software ein/aus. Das wird sich noch zeigen was sinnvoller ist.
Das alles nochmal etwas anders grafisch dargestellt samt Zugehörigkeit sieht so aus.
Zum einfachen testen habe ich einen Led Blinker auf D13 erstellt und führe das simulierte Taktsignal dem PWM Eingang D2 per Drahtbrücke zu. An Pin 4 und 7 sitzen Taster zu GND. An Pin 14 und 15 sind Leds angeklemmt. Wenn alles richtig gemacht wurde blinkt immer nur eine der Leds syncron zum D13 Blinker. Drückt man beide Taster blinkt keine Led, außer D13 natürlich. Funktioniert bei mir wie gewünscht.
Lohn des kleinen Aufwandes ist, wenn man sich die loop anschaut, dass diese leer ist. Abgesehen vom Aufruf des D13 Blinkers. Das heißt der gesamte Ablauf belastet nicht die Recheneinheit des µC. Das funktioniert alles nebenbei. Ähnlich wie ein Timer nebenher funktioniert. Jetzt steht der H-Brücke Sicherheitstechnisch gesehen nichts im Weg und man kann unbekümmert einen DC Motor o.ä. per Pulsweite steuern und die Drehrichtung ändern. Mögliche Softwareseitige Programmierfehler sind eliminiert. Für mich dient das der Ansteuerung kleiner DC Spielzeugmotoren. Für größere Motoren sollte man mehr beachten. Dazu sollte mich jedoch niemand fragen.
https://www.mikrocontroller.net/articles/Motoransteuerung_mit_PWM
Ich hoffe das die Beschreibungen genügend Wissen vermitteln das man hinterher eigene Konfigurationen programmieren kann.
Der komplette Sketch:
CCL_Logic_BTN7960_Verriegelung.zip (2,7 KB)