Speicherdirektzugriff (DMA) mit dem ATxmega

Wechseln zu: Navigation, Suche

von Nicolas Göddel und Thomas Karwoth

Dieser Artikel nimmt am Artikelwettbewerb 2012/2013 teil.

Die ATxmega-Serie von Atmel stellt eine sehr leistungsfähige Plattform dar und bietet damit die Möglichkeit, auch dem privaten Anwender oder Bastler anspruchsvolle Projekte zu etablieren. Jeder ATxmega kommt mit einem DMA-Controller mit vier Kanälen und ermöglicht damit einen Speicherdirektzugriff asynchron zum Hauptprogramm. In diesem Artikel bekommt der Leser eine umfassende Einführung in die Nutzung des Controllers. Das Resultat dieses Artikels ist eine Klasse in C++, die für eigene Projekte genutzt werden kann.


Einführung

Mit DMA kann man Daten von einer Quellspeicheradresse zu einer Zielspeicheradresse kopieren ohne dabei den aktuellen Programmfluss zu stören. Dieser Prozess läuft dabei also asynchron ab. Insgesamt gibt es pro Mikrocontroller aus der XMega-Reihe 4 einzeln programmierbare DMA-Kanäle. Es kann genau festgelegt werden wie und wann das Kopieren von wo nach wo ablaufen soll. Darüber hinaus gibt es die Möglichkeit Double Buffering zu benutzen, das heißt, dass sich immer zwei DMA-Kanäle abwechseln um Daten von einem Puffer abwechselnd in zwei andere zu kopieren oder um Daten von zwei Puffern abwechselnd in einen anderen zu kopieren. Mit "Puffer" sind hier entweder Arrays, mit 'malloc' allozierte Speicherbereiche oder einfache Register zum Lesen und Schreiben gemeint, wie es sie bei SPI, UART, DAC, ADC und ähnlicher Peripherie gibt.

Nachdem Quell- und Zieladresse festgelegt wurden, muss konfiguriert werden wie groß der zu kopierende Puffer ist (Block Size), wie viele Bytes in einem Rutsch (Burst) kopiert werden sollen (Burst Length), ob der Pointer nach jedem Burst um die Burstlänge erhöht, verringert werden oder gleich bleiben soll (Source/Destination Direction), wann diese Pointer wieder auf die Ursprungsadresse zurück gesetzt werden sollen (Source/Destination Reload), wie oft ein kompletter Block nacheinander kopiert werden soll (Repeat Count) und vor allem was das Kopieren auslösen soll (Trigger Source).

Weil das alles etwas kompliziert klingt, findet sich im nächsten Abschnitt noch eine kurze Begriffserklärung um die nachfolgenden Abschnitte verständlicher zu machen.

Begriffserklärung

Trigger Source
Beim ATXMega kann man zwischen vielen verschiedenen Triggerquellen wählen oder auch gar keine benutzen, wenn man eine Transaction manuell starten will. Unter anderem das Eventsystem des ATXMega lässt es zu verschiedene Peripherie miteinander zu verbinden, sodass z.B. nach dem Einlesen eines Bytes über SPI ein Interrupt ausgelöst wird. Bei dem DMA-Controller kann man dieses System ebenfalls benutzen, sodass man z.B. eine Transaction zum Register des DA-Konverters starten kann, sobald dieser fertig mit seiner Konvertierung ist. Der ATXMega bietet für jede Peripherie, die er beinhaltet, einen Eventkanal, der einen bestimmten DMA-Kanal triggern kann.
Block Size
Die Blockgröße wird in Bytes angegeben und der Wert wird in einer 16-Bit Variablen gespeichert. Da eine Blockgröße von 0 Bytes keinen Sinn macht, definiert die 0 in diesem Fall genau 2^16 = 65536 Bytes. Bei jedem DMA-Kanal kann eine andere Blockgröße festgelegt werden. Sie bestimmt die Länge des Speicherbereichs ab der Quell- bzw- Zieladresse, der von dem DMA-Controller benutzt werden kann.
Transaction
Eine Transaction beginnt mit dem ersten Triggern eines Kanals nach dessen Konfiguration und Aktivierung oder einem erfolgreichen Beenden einer vorherigen Transaction.
Repeat Count
Eine Transaction besteht nicht nur aus der Abarbeitung eines Blocks, sondern es ist möglich die Blockgröße mit dem Repeat Count auf das maximal 255-fache seiner Größe zu setzen. Eine besondere Bedeutung hat ein Repeat Count von 0. Er führt dazu, dass eine Transaction nie beendet wird, weil ein Block sozusagen unendlich oft abgearbeitet wird.
Single Shot Mode
Der Single Shot Mode wird genutzt um bei einmaliger Triggerung eines Kanals genau einen Burst auszuführen und dann wieder auf die nächste Triggerung zu warten. So kann man sich schrittweise durch eine Transaction triggern. Ist der Modus deaktiviert, wird nach einmaligem Triggern eines Kanals eine komplette Transaction ausgeführt.
Burst und Burst Length
Pro DMA-Kanal kann die Länge eines Burst festgelegt werden. Dabei ist ein Burst ein nicht unterbrechbarer Kopiervorgang einer festgelegten Anzahl von Bytes von der aktuellen Quell- zur Zieladresse. Die Anzahl der Bytes entspricht hierbei der Burst Length und kann 1, 2, 4 oder 8 Bytes betragen. Während eines Bursts wird der Datenbus des ATXMega komplett vom DMA-Controller beansprucht und andere DMA-Kanäle können in dieser Zeit auch nichts kopieren.
Source/Destination Address
Pro DMA-Kanal muss ein Zeiger zum Quell- und zum Zielspeicherbereich festgelegt werden. Dabei kann jeweils ein Zeiger zu einer Variablen, einem Array, einem mit 'malloc()' allozierten Speicher oder einem Register einer entsprechenden Peripheriekomponente zeigen. Kombinationen von allem sind möglich.
Source/Destination Direction
Pro DMA-Kanal wird hiermit festgelegt, was nach jedem Burst mit dem Zeiger auf die anfänglich festgelegte Quell- oder Zieladresse passieren soll. Dabei gibt es drei Optionen. Entweder der Zeiger soll sich nicht ändern oder soll sich um eine Burstlänge nach vorne oder hinten im Speicher bewegen. Somit kann man z.B. immer von der selben Adresse lesen, aber jeden eingelesenen Wert nacheinander an eine andere Stelle im Speicher schreiben.
Source/Destination Reload
Pro DMA-Kanal kann man hier einstellen, wann der möglicherweise geänderte Zeiger im Quell- bzw. Zielspeicherbereich wieder auf seine ursprüngliche Speicheradresse gesetzt werden soll. Dafür gibt es vier Möglichkeiten: Entweder erst nach der aktuellen Transaction, nach jedem abgearbeiteten Block, nach jedem Burst oder niemals. Konfiguriert man einen Reload nach jedem Burst ist das gleichzusetzen mit einer fixierten Direction. Ob der Zeiger nach jedem Burst zurück gesetzt wird oder er sich gar nicht erst bewegt macht in dem Fall nämlich keinen Unterschied.

Beispiel

Auslesen eines ADC

DMA kann das Einlesen von Werten mit dem ADC in ein Array wunderbar vereinfachen, da das Beenden jedes AD-Konvertierungsvorgangs einen DMA-Kanal triggern kann. Dazu legt man einen ausreichend großen Puffer fest, z.B. ein Array des Typs uint16_t und der Größe 64, wenn man die vollen 12 Bit des ADC nutzen möchte. Dann setzt man als Eingangspuffer für den DMA das Empfangsregister des entsprechenden ADC und als Ausgangspuffer das Array selbst fest. Die Blockgröße entspricht somit 64 · 2 Bytes = 128 Bytes.

Wenn man den entsprechenden Kanal in den SingleShot-Modus versetzt, wird nach jedem fertigen Konvertierungsvorgang seitens des ADC der entsprechende DMA-Kanal getriggert und der fertige Wert in das Array übertragen. Damit auch ein kompletter 16-Bit-Wert an einem Stück kopiert wird, muss die Burst-Länge auf 2 Bytes gestellt werden.

Außerdem muss noch festgelegt werden, was nach jedem Kopiervorgang mit den DMA-eigenen Pointern passieren soll, die auf den Eingangs- und Ausgangspuffer zeigen. Da sich das ADC-Register mit den konvertierten Analogwerten nicht in seiner Position im Speicher ändert, muss die Source Direction fixiert werden. Anders ist es mit der Destination Direction, die sich nach jedem Burst um zwei Bytes verschieben muss, damit das nächste Element im Array gefüllt wird.

Wenn unser Puffer gefüllt ist, also die vollen 128 Bytes nach 64 konvertierten Werten in das Array geschrieben wurden, muss der Pointer zum Ausgangspuffer zurück auf seinen Anfangswert, das erste Element im Array, gesetzt werden. Ansonsten würden eventuell ungültige Speicherbereiche überschrieben werden, die dann unvorhergesehenes bewirken. Um das gewünschte Verhalten zu erreichen, muss der Destination Reload auf die Blockgröße gesetzt werden. Das heißt unser Array wird immer von vorne bis hinten mit Werten aus dem ADC befüllt und wenn es voll ist, wird wieder von vorne begonnen.

Was noch nicht erwähnt wurde ist der Repeat Count. Dieser legt fest wie oft ein kompletter Block nacheinander mit den gegebenen Parametern kopiert werden soll. Steht diese Einstellung auf 0, wird ein Block unendlich oft kopiert. Und diese Unendlich-Einstellung wird an dieser Stelle benötigt.

Invertieren eines Strings

Man kann mit dem DMA Controller festlegen in welcher Richtung er sich im Speicher nach einem Burst bewegen soll. So ist es sehr einfach einen String im Hintergrund zu invertieren.

Dazu ein Beispiel mit der unten gezeigten Klasse:

#include "DMAController.h"

void inline set32MHz() {
	OSC.CTRL = OSC_RC32MEN_bm; //aktiviere 32MHz Oszillator
	while (!(OSC.STATUS & OSC_RC32MRDY_bm));   //auf Staibiltät warten
	CCP = CCP_IOREG_gc; //sicherer Zugriff
	CLK.CTRL = 0x01; //Wähle diesen Osc als Quelle für die Clock
}

int main() {
	set32MHz();
	_delay_ms(100);
	DMAController::reset();

	char* a = "Hallo Welt!12345";
	char* b = "----------------";

	// DMA Controller aktivieren
	DMAController::enable();
	
	// Kanal zum Benutzen auswählen
	DMAController::useChannel(0);
	
	// Blockgröße wählen, in dem Fall ist der String 16 Zeichen lang
	DMAController::setBlockSize(0, 16);

	/* Quelladresse und Richtung wählen
	 * Wir beginnen mit dem letzten Zeichen in a und laufen dann rückwärts durch den Speicher
	 */
	DMAController::setSource(0, a + 15, DMAController::SrcDirectionDec, DMAController::SrcReloadBlock);

	/* Zieladresse und Richtung wählen
	 * Wir beschreiben den Zielspeicher von vorne und laufen dann vorwärts
	 */
	DMAController::setDestination(0, b, DMAController::DestDirectionInc, DMAController::DestReloadBlock);

	// Wir wollen nur einen Block kopieren
	DMAController::setRepeatCount(0, 1);

	// Und wir wollen byteweise kopieren, also die Burstlänge auf 1 setzen
	DMAController::setBurstLength(0, DMAController::BurstLength1Byte);
	
	/* Nach einmaligem Triggern soll eine komplette Transaktion ausgeführt werden, d.h.
	 * ein Block ohne Wiederholung.
	 */
	DMAController::setSingleShot(0, false);
	
	// Wir benötigen keine Triggerquelle, da wir das Kopieren manuell auslösen wollen.
	DMAController::setTriggerSource(0, DMA_CH_TRIGSRC_OFF_gc);

	// Nach der Konfiguration aktivieren wir den Kanal
	DMAController::enable(0);
	
	// Dann triggern wir den Kopiervorgang manuell
	DMAController::trigger(0);
	
	/* Hier könnten wir noch andere Dinge im Hauptprogramm tun, während der String
	 * im Hintergrund invertiert wird. 16 Bytes gehen allerdings sehr schnell, weswegen
	 * hier nicht viel Spielraum für weitere Berechnungen bleibt.
	 */

	// Wir warten auf das Ende der kompletten Transaktion
	while (!DMAController::isTransactionComplete(0));
	
	// Fertig. Der String b enthält nun den umgekehrten String von a.
}

Double Buffering

Double Buffering ist ein weiteres wichtiges Feature des DMA Controllers in der ATXMega-Reihe. Hierbei werden zwei DMA-Kanäle gebündelt um abwechselnd zwei verschiedene Speicherbereiche zu beschreiben oder von ihnen zu lesen.

Als Beispiel kann man wieder das Auslesen des ADC aus dem vorherigen Abschnitt nutzen und es entsprechend erweitern. Es ist ungünstig den ein und selben Puffer ständig neu zu beschreiben, wenn man an seinem Ende angekommen ist. So hätte der Prozessor eigentlich nie Zeit, den Puffer am Stück abzuarbeiten, ohne dass sich sein Inhalt wieder ändert. Hier kommt das Double Buffering ins Spiel. Man nimmt hier zwei Ausgangspuffer, die abwechselnd mit neuen Daten beschrieben werden. Dadurch kann man im Hauptprogramm den ersten Puffer komplett abarbeiten, sobald er voll geschrieben ist, ohne dass sich in dem Moment an seinem Inhalt etwas ändert. Man sollte allerdings darauf achten, dass das Abarbeiten des ersten Puffers trotzdem schneller von statten geht als, das Befüllen des zweiten Puffers mit ADC-Werten. Hat man den ersten Puffer schnell genug abgearbeitet, kann man sich dem Auslesen des zweiten Puffers widmen, sobald dieser befüllt ist. Danach geht das Spiel von vorne los, also der erste Puffer wird wieder befüllt.

Um herauszufinden, ob ein Puffer voll ist, gibt es diverse Flags, die die Beendigung einer Transaktion anzeigen bzw. ob noch ein Blocktransfer läuft.

Erweiterung zur Dokumentation von Atmel

Die originale Dokumentation von Atmel ist an manchen Stellen etwas ungenau, weshalb hier eine kleine Auflistung der Probleme gegeben wird.

Das Transaction-Complete-Flag

Obwohl man denken sollte, dass eine Transaktion beendet ist, sobald das entsprechende Bit in DMA.INTFLAGS gesetzt ist, ist dies unter bestimmten Bedingungen nicht der Fall. Angenommen man hat eine Transaktion auf Kanal 0 gestartet und wartet in einer while-Schleife auf das Bit, welches die erfolgreiche Transaktion ankündigen soll und deaktiviert sofort danach den Kanal, dann kann es passieren, dass doch nicht alles kopiert wurde. Das folgende Beispiel soll dies verdeutlichen. Wir wollen den String "Hallo Welt!12345" von a nach b kopieren.

// Die Transaktion wurde gestartet und wir warten
while (!(DMA.INTFLAGS & DMA_CH0TRNIF_bm));
DMA.INTFLAGS |= DMA_CH0TRNIF_bm;

// Wir deaktivieren den Kanal
DMA.CH0.CTRLA &= ~DMA_CH_ENABLE_bm;

Jetzt steht in b auch nach mehrmaligem Testen nur "Hallo Welt!". Die nachfolgende Zahlenreihe fehlt komplett. Damit das nicht passiert, sollte man nicht nur darauf warten, dass die Transaktion beendet ist, sondern sollte auch prüfen, ob noch ein Blocktransfer in Gange ist oder nicht. Das erreicht man dann mit folgendem Code:

// Die Transaktion wurde gestartet und wir warten
while (!(DMA.INTFLAGS & DMA_CH0TRNIF_bm) || (DMA.CHO.CTRLB & (DMA_CH_CHBUSY_bm | DMA_CH_CHPEND_bm)));
DMA.INTFLAGS |= DMA_CH0TRNIF_bm;

// Wir deaktivieren den Kanal
DMA.CH0.CTRLA &= ~DMA_CH_ENABLE_bm;

So ist sichergestellt, dass die Transaktion und der darin enthaltende Blocktransfer auch tatsächlich abgeschlossen sind.

Burstlänge passt nicht genau in die Blockgröße

Was passiert eigentlich, wenn die Blockgröße nicht durch die Burstlänge teilbar ist? Nehmen wir mal an wir haben lediglich eine Blockgröße von 1 Byte gewählt, aber eine Burstlänge von 4 Bytes. Dann könnte man meinen es werden trotzdem alle 4 Bytes als Einheit übertragen und somit über die Blockgröße hinweg geschrieben. Dies ist aber praktischerweise nicht so. Tatsächlich wird wirklich nur ein Byte übertragen.

Man muss sich also nicht unbedingt Gedanken darüber machen, ob die definierte Blockgröße durch die Burstlänge teilbar ist. Hat man es mal sehr eilig mit einer Kopieraktion kann man also einfach eine Burstlänge von 8 Bytes nehmen. Aber Vorsicht: Der interne Datenbus des ATXMega ist trotzdem nur 8 Bit breit, was bedeutet, dass die 8 Bytes nacheinander kopiert werden und solange der komplette Bus in Anspruch genommen wird und auch von keinem anderen DMA-Kanal in Anspruch genommen werden kann.

Singleton Klasse zum einfachen Konfigurieren des DMA Controllers

Die in diesem Artikel vorgestellte C++ Klasse soll das Konfigurieren des vorhandenen DMA-Moduls im ATXMega vereinfachen. Gerade hinsichtlich des "double buffering" finden sich hier einige wesentliche Vereinfachungen.

Im Folgenden sind Header- und C++-Datei zu finden, die das Konfigurieren des DMA Controllers vereinfachen sollen. Die Beschreibungen zu jeder einzelnen Methode finden sich in der Header-Datei.

Anwendung

Ein paar kurze Hinweise zum Umgang mit der DMAController-Klasse.

Zu Beginn des Programmablaufs sollte einmalig DMAController::reset() aufgerufen werden, um den Controller in den Grundzustand zu versetzen und die internen Variablen auf Null zu setzen.

Zum Verwenden des DMA muss einmal DMAController::enable() aufgerufen werden. Soll im Folgenden ein bestimmter Kanal genutzt werden, wird entsprechend DMAController::useChannel(0..3) aufgerufen. Möchte man DoubleBuffering nutzen, reicht ein Aufruf von DMAController::useDoubleBuffering(0..3). Dabei können immer entweder Kanal 0 und 1 oder Kanal 2 und 3 zusammen verwendet werden. Es reicht immer einen der beiden Kanäle anzugeben, der andere wird dann automatisch mit aktiviert. Das Nutzen von DMAController::useChannel() ist in diesem Fall dann auch nicht mehr notwendig.

Ab diesem Punkt kann dann jeder Kanal für sich konfiguriert werden. Einige Befehle führen die Konfiguration automatisch für beide Kanäle aus, wenn DoubleBuffering verwendet wird. Das sind die folgenden:

void DMAController::setBlockSize(uint8_t channel, uint16_t blockSize)
void DMAController::setSource(uint8_t channel, void* source, SrcDirection_enum direction, SrcReload_enum mode)
void DMAController::setSource(uint8_t channel, void* source1, void* source2, SrcDirection_enum direction, SrcReload_enum mode)
void DMAController::setSourceDirection(uint8_t channel, SrcDirection_enum direction)
void DMAController::setSourceReload(uint8_t channel, SrcReload_enum mode)
void* DMAController::setDestination(uint8_t channel, void* destination, DestDirection_enum direction, DestReload_enum mode) //mit Ausnahme von source
void DMAController::setDestination(uint8_t channel, void* destination1, void* destination2, DestDirection_enum direction, DestReload_enum mode)
void DMAController::setDestDirection(uint8_t channel, DestDirection_enum direction)
void DMAController::setDestReload(uint8_t channel, DestReload_enum mode)
void DMAController::setBurstLength(uint8_t channel, BurstLength_enum burstLength)
void DMAController::enable(uint8_t channel) //aktiviert immer den ersten von zwei Kanälen bei aktiviertem DoubleBuffering
void DMAController::disable(uint8_t channel)
void DMAController::setRepeatCount(uint8_t channel, uint8_t repeatCount)
void DMAController::useSingleShot(uint8_t channel, bool use)
void DMAController::setTriggerSource(uint8_t channel, uint8_t triggerSource)

Eine Besonderheit mit DoubleBuffering stellt auch die Funktion DMAController::useChannel() dar. Wenn sie genutzt wird um einen Kanal zu deaktivieren, der für DoubleBuffering verwendet wird, wird auch gleichzeitig das DoubleBuffering deaktiviert.

Nach der Konfiguration der Kanäle muss einmalig DMAController::enable(0..3) aufgerufen werden um sie sozusagen in Standby zu setzen. Ab diesem Zeitpunkt wird nach jedem Triggern, sei es manuell oder durch andere Peripherie ausgelöst, entweder eine komplette Transaktion ausgeführt oder im Falle des SingleShot-Modus ein einzelner Burst in der definierten Länge.

Wird ein DMA-Kanal nicht mehr weiter verwendet, sollte er mittels DMAController:useChannel(0..3, false) als "ungenutzt" markiert werden. Damit wird er auch gleichzeitig deaktiviert.

Fertige Methoden

Es gibt bis jetzt zwei kleine Methoden zum Kopieren von Daten und Füllen eines Speicherbereichs mit einem bestimmten Wert, die bereits fertig implementiert sind. Diese sind DMAController::copyMemory() und DMAController::fillMemory().

copyMemory()

copyMemory() ist in zwei Versionen verfügbar. Die synchrone Version benötigt keinen Parameter für die Kanalzuordnung, sondern sucht sich selbst einen nicht benutzten Kanal heraus, kopiert die Daten dann von a nach b und gibt nach dem erfolgreichen Kopiervorgang true zurück. Die asynchrone Version benötigt dahingegen einen freien Kanal, der zum Kopieren benutzt werden soll, bereitet alle Einstellungen vor, startet den Vorgang und gibt bei Erfolg true zurück. Ab diesem Zeitpunkt läuft der Kopiervorgang im Hintergrund ab, sodass man im Hauptprogramm mit isTransactionComplete(channel) überprüfen muss, wann das Kopieren beendet ist.

fillMemory()

Diese Methode stellt eine Möglichkeit dar einen beliebigen Speicherbereich byteweise mit einem Wert zu befüllen, z.B. um einen Speicher zu nullen. Die implementierte Version läuft synchron ab, d.h. der Vorgang ist mit Beendigung der Methode ebenfalls beendet.

DMAController.h

/*
 * DMAController.h
 *
 *  Created on: 16.01.2013
 *      Author: Nicolas Göddel
 */

#include <stdlib.h>
#include <avr/io.h>

#ifndef DMAController_H_
#define DMAController_H_

/*
 * Singleton Klasse für den DMAController.
 */
class DMAController {

	private:
		DMAController() {}
		virtual ~DMAController() {}

		// Ein Bit pro genutztem Kanal
		static uint8_t usedChannels;
		// Ein Bit pro zwei genutzen Kanälen
		static uint8_t doubleBuffered;
		static uint8_t* buffer[4];
		static uint16_t blockSizes[4];
		static uint8_t autoDestBuffer;

		/**
		 * Ändert die Puffergröße eines Puffers auf die gewünschte Größe, falls
		 * das automatische Allozieren gewünscht ist.
		 */
		static uint8_t* updateAutoBuffer(uint8_t channel, uint16_t blockSize);

		static inline void setBuffer(uint8_t channel);

	public:

		enum SrcReload_enum {
			SrcReloadNone = DMA_CH_SRCRELOAD_NONE_gc,  /* No reload */
			SrcReloadBlock = DMA_CH_SRCRELOAD_BLOCK_gc,  /* Reload at end of block */
			SrcReloadBurst = DMA_CH_SRCRELOAD_BURST_gc,  /* Reload at end of burst */
			SrcReloadTransaction = DMA_CH_SRCRELOAD_TRANSACTION_gc,  /* Reload at end of transaction */
			SrcReload_bm = SrcReloadNone | SrcReloadBlock | SrcReloadBurst | SrcReloadTransaction
		};

		enum DestReload_enum {
			DestReloadNone = DMA_CH_DESTRELOAD_NONE_gc,  /* No reload */
			DestReloadBlock = DMA_CH_DESTRELOAD_BLOCK_gc,  /* Reload at end of block */
			DestReloadBurst = DMA_CH_DESTRELOAD_BURST_gc,  /* Reload at end of burst */
			DestReloadTransaction = DMA_CH_DESTRELOAD_TRANSACTION_gc,  /* Reload at end of transaction */
			DestReload_bm = DestReloadNone | DestReloadBlock | DestReloadBurst | DestReloadTransaction
		};

		enum SrcDirection_enum {
		    SrcDirectionFixed = DMA_CH_SRCDIR_FIXED_gc,  /* Fixed */
		    SrcDirectionInc = DMA_CH_SRCDIR_INC_gc,  /* Increment */
		    SrcDirectionDec = DMA_CH_SRCDIR_DEC_gc,  /* Decrement */
		    SrcDirection_bm = SrcDirectionFixed | SrcDirectionInc | SrcDirectionDec
		};

		enum DestDirection_enum {
		    DestDirectionFixed = DMA_CH_DESTDIR_FIXED_gc,  /* Fixed */
		    DestDirectionInc = DMA_CH_DESTDIR_INC_gc,  /* Increment */
		    DestDirectionDec = DMA_CH_DESTDIR_DEC_gc,  /* Decrement */
		    DestDirection_bm = DestDirectionFixed | DestDirectionInc | DestDirectionDec
		};

		enum BurstLength_enum {
		    BurstLength1Byte = DMA_CH_BURSTLEN_1BYTE_gc,  /* 1-byte burst mode */
		    BurstLength2Byte = DMA_CH_BURSTLEN_2BYTE_gc,  /* 2-byte burst mode */
		    BurstLength4Byte = DMA_CH_BURSTLEN_4BYTE_gc,  /* 4-byte burst mode */
		    BurstLength8Byte = DMA_CH_BURSTLEN_8BYTE_gc,  /* 8-byte burst mode */
		    BurstLength_bm = BurstLength1Byte | BurstLength2Byte | BurstLength4Byte | BurstLength8Byte
		};

		/**
		 * Resettet den DMA Controller. Automatisch allozierte Puffer werden zurück
		 * gesetzt.
		 */
		static void reset();

		/**
		 * Setzt die Blockgröße eines Kanals auf eine bestimmte Größe. Wird der
		 * Kanal doppelt gepuffert, wird automatisch der zweite entsprechende Kanal
		 * auch aktualisiert. Beim Ändern der Größe können sich die Zeiger auf
		 * die Ausgangspuffer ändern, falls sie automatisch alloziert wurden.
		 */
		static void setBlockSize(uint8_t channel, uint16_t blockSize);

		/**
		 * Gibt an, ob ein bestimmter Kanal genutzt werden soll.
		 * Wenn use = false ist, wird der Kanal nicht mehr benutzt. Wenn ein Kanal,
		 * der zum DoubleBuffering gehört, deaktiviert wird, wird auch das
		 * DoubleBuffering deaktiviert.
		 */
		static void useChannel(uint8_t channel, uint8_t use);

		/**
		 * Definiert, dass der entsprechende Kanal genutzt werden soll.
		 */
		static inline void useChannel(uint8_t channel) {
			usedChannels |= _BV(channel);
		}

		/**
		 * Wenn channel gleich 0 oder 1 ist, wird Doublebuffering für Kanal 0 und 1 aktiviert
		 * und die Buffergröße von beiden auf die des größeren Buffers gesetzt.
		 * Für Kanal 2 und 3 gilt das selbe.
		 */
		//TODO Andere Parameter automatisch auf beide Kanäle anwenden, wenn DoubleBuffering aktiviert wird.
		static void useDoubleBuffering(uint8_t channel, bool use) {
			channel >>= 1;

			if (use) {
				DMA.CTRL |= (channel + 1) << 2;	//DMA_DBUFMODE_CH01_gc oder DMA_DBUFMODE_CH23_gc
				usedChannels |= _BV(2 * channel) | _BV(2 * channel + 1);
				doubleBuffered = channel;
			} else {
				DMA.CTRL &= ~((channel + 1) << 2);
				doubleBuffered &= ~_BV(channel);
			}
		}

		/**
		 * Setzt den Eingangspuffer eines Kanals. Der Pointer zum Buffer sollte
		 * gültig sein.
		 */
		static void setSource(uint8_t channel, void* source);

		/**
		 * Vereint das Festlegen des Quellpuffers, seiner Bewegungsrichtung nach jedem Burst
		 * und des Zeitpunkts, an dem der interne Pointer zurück gesetzt werden soll.
		 */
		static void setSource(uint8_t channel, void* source, SrcDirection_enum direction, SrcReload_enum mode);

		/**
		 * Vereint das Festlegen der beiden Quellpuffer für Doublebuffering, der Bewegungsrichtung
		 * nach jedem Burst und des Zeitpunkts, an dem der interne Pointer zurück gesetzt werden soll.
		 */
		static void setSource(uint8_t channel, void* source1, void* source2, SrcDirection_enum direction, SrcReload_enum mode);

		/**
		 * Legt fest, in welche Richtung sich der Pointer des Quellpuffers nach jedem Burst
		 * bewegen soll oder ob er fixiert bleiben soll. Wird DoubleBuffering verwendet, wird
		 * der Wert für beide beteiligten Kanäle auf einmal geändert.
		 */
		static void setSourceDirection(uint8_t channel, SrcDirection_enum direction);

		/**
		 * Legt fest, wann wieder angefangen werden soll vom Anfang des Eingangsbuffers zu lesen.
		 * Falls die SourceDirection auf Fixed gestellt ist, hat diese Einstellung keine Wirkung.
		 * Wird DoubleBuffering verwendet, wird der Wert für beide beteiligten Kanäle auf einmal
		 * geändert.
		 */
		static void setSourceReload(uint8_t channel, SrcReload_enum mode);

		/**
		 * Setzt manuell den Ausgangspuffer eines Kanals. Wird als Puffer ein
		 * Nullpointer übergeben, wird der Ausgangspuffer automatisch alloziert.
		 */
		static void* setDestination(uint8_t channel, void* destination);

		/**
		 * Vereint das Festlegen des Zielpuffers, seiner Bewegungsrichtung nach jedem Burst
		 * und des Zeitpunkts, an dem der interne Pointer zurück gesetzt werden soll. Bei
		 * DoubleBuffering wird die Zieladresse beider Kanäle gesetzt. Der Rückgabewert
		 * entspricht bei automatischer Allozierung dem erstellten Puffer für den übergebenen Kanal.
		 * Um bei DoubleBuffering den Pointer zum zweiten Kanal zu bekommen, muss getAutoBuffer()
		 * genutzt werden.
		 */
		static void* setDestination(uint8_t channel, void* destination, DestDirection_enum direction, DestReload_enum mode);

		/**
		 * Vereint das Festlegen der beiden Zielpuffer für Doublebuffering, der Bewegungsrichtung
		 * nach jedem Burst und des Zeitpunkts, an dem der interne Pointer zurück gesetzt werden soll.
		 */
		static void setDestination(uint8_t channel, void* destination1, void* destination2, DestDirection_enum direction, DestReload_enum mode);

		/**
		 * Legt fest, in welche Richtung sich der Pointer des Zielpuffers nach jedem Burst
		 * bewegen soll oder ob er fixiert bleiben soll. Wird DoubleBuffering verwendet, wird
		 * der Wert für beide beteiligten Kanäle auf einmal geändert.
		 */
		static void setDestDirection(uint8_t channel, DestDirection_enum direction);

		/**
		 * Legt fest, wann wieder angefangen werden soll vom Anfang des Zielpuffers zu lesen.
		 * Falls die DestinationDirection auf Fixed gestellt ist, hat diese Einstellung keine Wirkung.
		 * Wird DoubleBuffering verwendet, wird der Wert für beide beteiligten Kanäle auf einmal
		 * geändert.
		 */
		static void setDestReload(uint8_t channel, DestReload_enum mode);

		/**
		 * Legt fest wie viele Bytes mit einem Burst, d.h. einer Triggerung, vom Quell- in den
		 * Zielpuffer kopiert werden sollen. Gleichzeitig bestimmt diese Einstellung um wie
		 * viele Bytes sich die internen Zeiger innerhalb des Quell- bzw. Zielpuffers bewegen sollen,
		 * falls ihre Direction nicht auf Fixed gestellt ist.
		 */
		static void setBurstLength(uint8_t channel, BurstLength_enum burstLength);

		/**
		 * Aktiviert einen Kanal bzw. den ersten von zwei, wenn DoubleBuffering verwendet wird.
		 * Bei DoubleBuffering wird der zweite Kanal automatisch aktiviert, wenn eine komplette
		 * Transaktion des ersten beendet ist und umgekehrt.
		 */
		static void enable(uint8_t channel);

		/**
		 * Deaktiviert einen Kanal oder zwei, wenn DoubleBuffering verwendet wird.
		 */
		static void disable(uint8_t channel);

		/**
		 * Aktiviert den DMA Controller.
		 */
		static inline void enable() {
			DMA.CTRL |= DMA_CH_ENABLE_bm;
		}

		/**
		 * Deaktiviert den DMA Controller.
		 */
		static inline void disable() {
			DMA.CTRL &= ~DMA_CH_ENABLE_bm;
		}

		/**
		 * Gibt true zurück, wenn eine Transaktion auf einem Kanal vollendet wurde
		 * und gleichzeitig kein Blocktransfer mehr läuft.
		 */
		static bool isTransactionComplete(uint8_t channel);

		/**
		 * Gibt true zurück, wenn ein Blocktransfer auf dem übergebenen Kanal noch
		 * aktiv ist oder auf seine Ausführung wartet.
		 */
		static inline bool isBlockTransferBusy(uint8_t channel);

		/**
		 * Wartet bis das Trigger-Bit des übergebenen Kanals auf 0 gesetzt wurde
		 * und setzt es dann wieder auf 1 um den nächsten Transfer zu starten.
		 */
		static void trigger(uint8_t channel);

		/**
		 * Kann benutzt werden um einen Kanal zu finden, der gerade nicht benutzt
		 * wird. copyMemory und fillMemory machen davon ebenfalls Gebrauch. Die
		 * Variable channel wird mit der Nummer eines freien Kanals gefüllt, wenn
		 * ein Kanal frei ist und der Rückgabewert wird true sein. Andernsfalls
		 * ändert sich an channel nichts und der Rückgabewert ist false.
		 */
		static bool getFreeChannel(uint8_t& channel);

		/**
		 * Kopiert einen Speicherbereich synchron in einen anderen. Dabei wird ein
		 * aktuell nicht genutzter Kanal verwendet. Wenn der Rückgabewert der
		 * Methode true ist, wurde der Speicher erfolgreich kopiert, andernfalls
		 * war kein Kanal frei.
		 */
		static bool copyMemory(void* source, void* destination, uint16_t size);

		/**
		 * Startet einen asynchronen Kopiervorgang von einem Speicherbereich in einen
		 * anderen. Der Kopiervorgang ist beendet, wenn isTransactionComplete(channel)
		 * true zurück gibt. Danach sollte der Kanal wieder deaktiviert werden.
		 */
		static bool copyMemory(uint8_t channel, void* source, void* destination, uint16_t size);

		/**
		 * Füllt einen Speicherbereich mit einem bestimmten Wert. War das Füllen
		 * des Speichers erfolgreich, wird true zurückgegeben, andernfalls war kein
		 * freier Kanal verfügbar.
		 */
		static bool fillMemory(void* destination, uint16_t size, uint8_t byte);

		/**
		 * Legt fest wie oft der Puffer von einem Kanal (oder zwei bei DoubleBuffering)
		 * nacheinander kopiert werden soll. Wird 'repeatCount' auf 0 gesetzt, wird
		 * unendlich oft kopiert.
		 */
		static void setRepeatCount(uint8_t channel, uint8_t repeatCount);

		/**
		 * Wird SingleShot für einen Kanal (zwei bei DoubleBuffering) aktiviert,
		 * wird immer nur ein Burst pro Triggerung kopiert. Für den SingleShot-Modus
		 * bietet sich ein RepeatCount von 0 an, damit unendlich oft getriggert werden
		 * kann.
		 */
		static void useSingleShot(uint8_t channel, bool use);

		/**
		 * Setzt die Triggerquelle für einen bzw. zwei Kanäle (bei DoubleBuffering) fest.
		 * Alle Triggerquellen des verwendeten ATXMega finden sich in der entsprechenden
		 * io-Header-Datei in der Enumeration 'DMA_CH_TRIGSRC_enum'. Für den ATXMega128A1
		 * findet man sie z.B. in /usr/lib/avr/include/avr/iox128a1.h
		 */
		static void setTriggerSource(uint8_t channel, uint8_t triggerSource);

		/**
		 * Gibt den automatisch allozierten Zielpuffer eines Kanals zurück, falls diese
		 * Option genutzt wurde. Sonst 0.
		 */
		static void* getAutoBuffer(uint8_t channel) {
			return buffer[channel];
		}
};

#endif /* DMAController_H_ */

DMAController.cpp

/*
 * DMAController.cpp
 *
 *  Created on: 16.01.2013
 *      Author: Nicolas Göddel
 */

#include "DMAController.h"

#define DOUBLE_BUFFERED(channel) (doubleBuffered & _BV((channel) >> 1))
#define CHANNEL(channel) (((DMA_CH_t*)&DMA.CH0)[channel])

uint8_t DMAController::usedChannels = 0;
uint8_t DMAController::doubleBuffered = 0;
uint8_t DMAController::autoDestBuffer = 0;
uint16_t DMAController::blockSizes[4] = {0, 0, 0, 0};
uint8_t* DMAController::buffer[4] = {0, 0, 0, 0};

uint8_t* DMAController::updateAutoBuffer(uint8_t channel, uint16_t blockSize) {
	/*
	 * Falls bei diesem Kanal der Buffer nicht automatisiert alloziert werden soll
	 * oder blockSize = 0 ist, lösche ihn.
	 */

	if (!(autoDestBuffer & _BV(channel)) || blockSize == 0) {
		if ( buffer[channel]) {
			free(buffer[channel]);
			buffer[channel] = 0;
		}
	} else {
		/*
		 * Ist schon ein Buffer mit anderer Größe alloziert, lösche den alten
		 * und alloziere einen neuen.
		 */
		if ((buffer[channel]) && (blockSizes[channel] != blockSize)) {
			free(buffer[channel]);
			blockSizes[channel] = blockSize;
			buffer[channel] = (uint8_t*) malloc(blockSize);
			if (buffer[channel] == 0) {
				return 0;
			}
		}
	}

	return buffer[channel];
}

void DMAController::setBuffer(uint8_t channel) {
	if (autoDestBuffer & _BV(channel)) {
		CHANNEL(channel).DESTADDR0 = ((uintptr_t) buffer[channel] & 0xff) ;
		CHANNEL(channel).DESTADDR1 = ((uintptr_t) buffer[channel] >> 8) & 0xff;
		CHANNEL(channel).DESTADDR2 = ((uintptr_t) buffer[channel] >> 16) & 0xff;
	}
}

void DMAController::reset() {
	// reset DMA controller
	DMA.CTRL = 0;
	DMA.CTRL = DMA_RESET_bm;
	while ((DMA.CTRL & DMA_RESET_bm) != 0);
	usedChannels = 0;
	doubleBuffered = 0;
	autoDestBuffer = 0;
	for (uint8_t i = 0; i < 3; i++) {
		blockSizes[i] = 0;
		if (buffer[i])
			free(buffer[i]);
		buffer[i] = 0;
	}
}

void DMAController::setBlockSize(uint8_t channel, uint16_t blockSize) {
	if (DOUBLE_BUFFERED(channel)) {
		channel &= ~1;
		updateAutoBuffer(channel, blockSize);
		updateAutoBuffer(channel + 1, blockSize);
		setBuffer(channel);
		setBuffer(channel + 1);
		CHANNEL(channel).TRFCNT = blockSize;
		CHANNEL(channel + 1).TRFCNT = blockSize;
	} else {
		updateAutoBuffer(channel, blockSize);
		setBuffer(channel);
		CHANNEL(channel).TRFCNT = blockSize;

	}
}

void DMAController::useChannel(uint8_t channel, uint8_t use) {
	usedChannels = (usedChannels & ~_BV(channel)) | (use << channel);
	if (!use) {
		disable(channel);
		if (DOUBLE_BUFFERED(channel)) {
			doubleBuffered &= ~_BV(channel >> 1);
			useDoubleBuffering(channel, false);
		}
	}
}

void DMAController::setSource(uint8_t channel, void* source) {
	CHANNEL(channel).SRCADDR0 = (uintptr_t) source & 0xff;
	CHANNEL(channel).SRCADDR1 = ((uintptr_t) source >> 8) & 0xff;
	CHANNEL(channel).SRCADDR2 = ((uintptr_t) source >> 16) & 0xff;
}

void DMAController::setSource(uint8_t channel, void* source, SrcDirection_enum direction, SrcReload_enum mode) {
	setSource(channel, source, source, direction, mode);
}

void DMAController::setSource(uint8_t channel, void* source1, void* source2, SrcDirection_enum direction, SrcReload_enum mode) {
	if (DOUBLE_BUFFERED(channel)) {
		channel &= ~1;

		CHANNEL(channel).SRCADDR0 = (uintptr_t) source1 & 0xff;
		CHANNEL(channel).SRCADDR1 = ((uintptr_t) source1 >> 8) & 0xff;
		CHANNEL(channel).SRCADDR2 = ((uintptr_t) source1 >> 16) & 0xff;
		CHANNEL(channel + 1).SRCADDR0 = (uintptr_t) source2 & 0xff;
		CHANNEL(channel + 1).SRCADDR1 = ((uintptr_t) source2 >> 8) & 0xff;
		CHANNEL(channel + 1).SRCADDR2 = ((uintptr_t) source2 >> 16) & 0xff;

		CHANNEL(channel).ADDRCTRL = (CHANNEL(channel).ADDRCTRL & ~SrcDirection_bm) | direction;
		CHANNEL(channel + 1).ADDRCTRL = (CHANNEL(channel + 1).ADDRCTRL & ~SrcDirection_bm) | direction;

		CHANNEL(channel).ADDRCTRL = (CHANNEL(channel).ADDRCTRL & ~SrcReload_bm) | mode;
		CHANNEL(channel + 1).ADDRCTRL = (CHANNEL(channel + 1).ADDRCTRL & ~SrcReload_bm) | mode;

	} else {
		CHANNEL(channel).SRCADDR0 = (uintptr_t) source1 & 0xff;
		CHANNEL(channel).SRCADDR1 = ((uintptr_t) source1 >> 8) & 0xff;
		CHANNEL(channel).SRCADDR2 = ((uintptr_t) source1 >> 16) & 0xff;
		CHANNEL(channel).ADDRCTRL = (CHANNEL(channel).ADDRCTRL & ~SrcReload_bm) | mode;

		CHANNEL(channel).ADDRCTRL = (CHANNEL(channel).ADDRCTRL & ~SrcDirection_bm) | direction;
	}
}

void DMAController::setSourceDirection(uint8_t channel, SrcDirection_enum direction) {
	if (DOUBLE_BUFFERED(channel)) {
		channel &= ~1;
		CHANNEL(channel).ADDRCTRL = (CHANNEL(channel).ADDRCTRL & ~SrcDirection_bm) | direction;
		CHANNEL(channel + 1).ADDRCTRL = (CHANNEL(channel + 1).ADDRCTRL & ~SrcDirection_bm) | direction;
	} else {
		CHANNEL(channel).ADDRCTRL = (CHANNEL(channel).ADDRCTRL & ~SrcDirection_bm) | direction;
	}
}

void DMAController::setSourceReload(uint8_t channel, SrcReload_enum mode) {
	if (DOUBLE_BUFFERED(channel)) {
		channel &= ~1;
		CHANNEL(channel).ADDRCTRL = (CHANNEL(channel).ADDRCTRL & ~SrcReload_bm) | mode;
		CHANNEL(channel + 1).ADDRCTRL = (CHANNEL(channel + 1).ADDRCTRL & ~SrcReload_bm) | mode;
	} else {
		CHANNEL(channel).ADDRCTRL = (CHANNEL(channel).ADDRCTRL & ~SrcReload_bm) | mode;
	}
}

void* DMAController::setDestination(uint8_t channel, void* destination) {
	if (destination) {
		autoDestBuffer &= ~_BV(channel);
		updateAutoBuffer(channel, blockSizes[channel]);
		CHANNEL(channel).DESTADDR0 = (uintptr_t) destination & 0xff;
		CHANNEL(channel).DESTADDR1 = ((uintptr_t) destination >> 8) & 0xff;
		CHANNEL(channel).DESTADDR2 = ((uintptr_t) destination >> 16) & 0xff;
		return destination;

	} else {
		autoDestBuffer |= _BV(channel);
		if (updateAutoBuffer(channel, blockSizes[channel])) {
			setBuffer(channel);
			return buffer[channel];
		}
		return 0;
	}
}

void* DMAController::setDestination(uint8_t channel, void* destination, DestDirection_enum direction, DestReload_enum mode) {
	setDestination(channel, destination, destination, direction, mode);
	if (autoDestBuffer & _BV(channel)) {
		return buffer[channel];
	}
	return destination;
}

void DMAController::setDestination(uint8_t channel, void* destination1, void* destination2, DestDirection_enum direction, DestReload_enum mode) {
	if (DOUBLE_BUFFERED(channel)) {
		channel &= ~1;
		CHANNEL(channel).ADDRCTRL = (CHANNEL(channel).ADDRCTRL & ~DestDirection_bm) | direction;
		CHANNEL(channel + 1).ADDRCTRL = (CHANNEL(channel + 1).ADDRCTRL & ~DestDirection_bm) | direction;

		CHANNEL(channel).ADDRCTRL = (CHANNEL(channel).ADDRCTRL & ~DestReload_bm) | mode;
		CHANNEL(channel + 1).ADDRCTRL = (CHANNEL(channel + 1).ADDRCTRL & ~DestReload_bm) | mode;

		if (destination1) {
			autoDestBuffer &= ~_BV(channel);
			updateAutoBuffer(channel, blockSizes[channel]);
			CHANNEL(channel).DESTADDR0 = (uintptr_t) destination1 & 0xff;
			CHANNEL(channel).DESTADDR1 = ((uintptr_t) destination1 >> 8) & 0xff;
			CHANNEL(channel).DESTADDR2 = ((uintptr_t) destination1 >> 16) & 0xff;
		} else {
			autoDestBuffer |= _BV(channel);
			if (updateAutoBuffer(channel, blockSizes[channel])) {
				setBuffer(channel);
			}
		}

		if (destination2) {
			autoDestBuffer &= ~_BV(channel + 1);
			updateAutoBuffer(channel + 1, blockSizes[channel + 1]);
			CHANNEL(channel + 1).DESTADDR0 = (uintptr_t) destination2 & 0xff;
			CHANNEL(channel + 1).DESTADDR1 = ((uintptr_t) destination2 >> 8) & 0xff;
			CHANNEL(channel + 1).DESTADDR2 = ((uintptr_t) destination2 >> 16) & 0xff;
		} else {
			autoDestBuffer |= _BV(channel + 1);
			if (updateAutoBuffer(channel + 1, blockSizes[channel + 1])) {
				setBuffer(channel + 1);
			}
		}


	} else {
		CHANNEL(channel).ADDRCTRL = (CHANNEL(channel).ADDRCTRL & ~DestDirection_bm) | direction;

		CHANNEL(channel).ADDRCTRL = (CHANNEL(channel).ADDRCTRL & ~DestReload_bm) | mode;

		if (destination1) {
			autoDestBuffer &= ~_BV(channel);
			updateAutoBuffer(channel, blockSizes[channel]);
			CHANNEL(channel).DESTADDR0 = (uintptr_t) destination1 & 0xff;
			CHANNEL(channel).DESTADDR1 = ((uintptr_t) destination1 >> 8) & 0xff;
			CHANNEL(channel).DESTADDR2 = ((uintptr_t) destination1 >> 16) & 0xff;
		} else {
			autoDestBuffer |= _BV(channel);
			if (updateAutoBuffer(channel, blockSizes[channel])) {
				setBuffer(channel);
			}
		}
	}
}

void DMAController::setDestDirection(uint8_t channel, DestDirection_enum direction) {
	if (DOUBLE_BUFFERED(channel)) {
		channel &= ~1;
		CHANNEL(channel).ADDRCTRL = (CHANNEL(channel).ADDRCTRL & ~DestDirection_bm) | direction;
		CHANNEL(channel + 1).ADDRCTRL = (CHANNEL(channel + 1).ADDRCTRL & ~DestDirection_bm) | direction;
	} else {
		CHANNEL(channel).ADDRCTRL = (CHANNEL(channel).ADDRCTRL & ~DestDirection_bm) | direction;
	}
}

void DMAController::setDestReload(uint8_t channel, DestReload_enum mode) {
	if (DOUBLE_BUFFERED(channel)) {
		channel &= ~1;
		CHANNEL(channel).ADDRCTRL = (CHANNEL(channel).ADDRCTRL & ~DestReload_bm) | mode;
		CHANNEL(channel + 1).ADDRCTRL = (CHANNEL(channel + 1).ADDRCTRL & ~DestReload_bm) | mode;
	} else {
		CHANNEL(channel).ADDRCTRL = (CHANNEL(channel).ADDRCTRL & ~DestReload_bm) | mode;
	}
}

void DMAController::setBurstLength(uint8_t channel, BurstLength_enum burstLength) {
	if (DOUBLE_BUFFERED(channel)) {
		channel &= ~1;
		CHANNEL(channel).CTRLA = (CHANNEL(channel).CTRLA & ~BurstLength_bm) | burstLength;
		CHANNEL(channel + 1).CTRLA = (CHANNEL(channel + 1).CTRLA & ~BurstLength_bm) | burstLength;
	} else {
		CHANNEL(channel).CTRLA = (CHANNEL(channel).CTRLA & ~BurstLength_bm) | burstLength;
	}
}

void DMAController::enable(uint8_t channel) {
	if (DOUBLE_BUFFERED(channel)) {
		channel &= ~1;
		CHANNEL(channel).CTRLA |= DMA_CH_ENABLE_bm;
		CHANNEL(channel + 1).CTRLA &= ~DMA_CH_ENABLE_bm;
	} else {
		CHANNEL(channel).CTRLA |= DMA_CH_ENABLE_bm;
	}
}

void DMAController::disable(uint8_t channel) {
	if (DOUBLE_BUFFERED(channel)) {
		channel &= ~1;
		CHANNEL(channel).CTRLA &= ~DMA_CH_ENABLE_bm;
		CHANNEL(channel + 1).CTRLA &= ~DMA_CH_ENABLE_bm;
	} else {
		CHANNEL(channel).CTRLA &= ~DMA_CH_ENABLE_bm;
	}
}

bool DMAController::isTransactionComplete(uint8_t channel) {
	bool result = (DMA.INTFLAGS & _BV(channel)) == _BV(channel);
	if (result) {
		DMA.INTFLAGS |= _BV(channel);
	}
	return result && !isBlockTransferBusy(channel);
}

bool DMAController::isBlockTransferBusy(uint8_t channel) {
	return (CHANNEL(channel).CTRLB & (DMA_CH_CHBUSY_bm | DMA_CH_CHPEND_bm));
}

void DMAController::trigger(uint8_t channel) {
	while (CHANNEL(channel).CTRLA & DMA_CH_TRFREQ_bm);
	CHANNEL(channel).CTRLA |= DMA_CH_TRFREQ_bm;
}

bool DMAController::getFreeChannel(uint8_t& channel) {
	uint8_t c = 0;
	while (c < 4) {
		if (usedChannels & _BV(c)) break;
		c++;
	}
	if (c == 4) return false;
	channel = c;
	return true;
}

bool DMAController::copyMemory(void* source, void* destination, uint16_t size) {
	uint8_t channel;
	if (!getFreeChannel(channel)) return false;

	if (!copyMemory(channel, source, destination, size))
		return false;

	while (!isTransactionComplete(channel));
	useChannel(channel, false);

	return true;
}

bool DMAController::copyMemory(uint8_t channel, void* source, void* destination, uint16_t size) {
	if (usedChannels & _BV(channel)) return false;

	disable(channel);

	useChannel(channel);
	useDoubleBuffering(channel, false);

	setBlockSize(channel, size);

	setSource(channel, source, SrcDirectionInc, SrcReloadTransaction);

	setDestination(channel, destination, DestDirectionInc, DestReloadTransaction);

	setBurstLength(channel, BurstLength1Byte);
	setRepeatCount(channel, 1);
	useSingleShot(channel, false);
	setTriggerSource(channel, DMA_CH_TRIGSRC_OFF_gc);

	enable(channel);
	trigger(channel);

	return true;
}

bool DMAController::fillMemory(void* destination, uint16_t size, uint8_t byte) {
	uint8_t channel;
	if (!getFreeChannel(channel)) return false;

	disable(channel);

	useChannel(channel);
	useDoubleBuffering(channel, false);

	setBlockSize(channel, size);

	setSource(channel, &byte);
	setSourceDirection(channel, SrcDirectionFixed);
	setSourceReload(channel, SrcReloadBurst);

	setDestination(channel, destination);
	setDestDirection(channel, DestDirectionInc);
	setDestReload(channel, DestReloadTransaction);

	setBurstLength(channel, BurstLength1Byte);
	setRepeatCount(channel, 1);
	useSingleShot(channel, false);
	setTriggerSource(channel, DMA_CH_TRIGSRC_OFF_gc);

	enable(channel);
	trigger(channel);

	while (!isTransactionComplete(channel));
	useChannel(channel, false);

	return true;
}

void DMAController::setRepeatCount(uint8_t channel, uint8_t repeatCount) {
	if (repeatCount != 1) {
		if (DOUBLE_BUFFERED(channel)) {
			channel &= ~1;
			CHANNEL(channel).REPCNT = repeatCount;
			CHANNEL(channel + 1).REPCNT = repeatCount;
			CHANNEL(channel).CTRLA |= DMA_CH_REPEAT_bm;
			CHANNEL(channel + 1).CTRLA |= DMA_CH_REPEAT_bm;
		} else {
			CHANNEL(channel).REPCNT = repeatCount;
			CHANNEL(channel).CTRLA |= DMA_CH_REPEAT_bm;
		}
	} else {
		if (DOUBLE_BUFFERED(channel)) {
			channel &= ~1;
			CHANNEL(channel).CTRLA &= ~DMA_CH_REPEAT_bm;
			CHANNEL(channel + 1).CTRLA &= ~DMA_CH_REPEAT_bm;
		} else {
			CHANNEL(channel).CTRLA &= ~DMA_CH_REPEAT_bm;
		}
	}
}

void DMAController::useSingleShot(uint8_t channel, bool use) {
	uint8_t iUse = (use) ? 1 : 0;
	if (DOUBLE_BUFFERED(channel)) {
		channel &= ~1;
		CHANNEL(channel).CTRLA = (CHANNEL(channel).CTRLA & ~DMA_CH_SINGLE_bm) | (iUse << DMA_CH_SINGLE_bp);
		CHANNEL(channel + 1).CTRLA = (CHANNEL(channel + 1).CTRLA & ~DMA_CH_SINGLE_bm) | (iUse << DMA_CH_SINGLE_bp);
	} else {
		CHANNEL(channel).CTRLA = (CHANNEL(channel).CTRLA & ~DMA_CH_SINGLE_bm) | (iUse << DMA_CH_SINGLE_bp);
	}
}

void DMAController::setTriggerSource(uint8_t channel, uint8_t triggerSource) {
	if (DOUBLE_BUFFERED(channel)) {
		channel &= ~1;
		CHANNEL(channel).TRIGSRC = triggerSource;
		CHANNEL(channel + 1).TRIGSRC = triggerSource;
	} else {
		CHANNEL(channel).TRIGSRC = triggerSource;
	}
}


#undef CHANNEL
#undef DOUBLE_BUFFERED

Ausblick

Leider ist es in dieser Version noch nicht möglich sämtliche Triggerevents komfortabel mit der restlichen Peripherie zu verbinden. Für die Zukunft sind aber schon verschiedene Singleton-Klassen für SPI, ADC, DAC, UART, TIMER und das ATXMega-eigene Eventsystem geplant, die dann nahtlos mit dem DMAController zusammenarbeiten können.

So soll es möglich werden beispielsweise über SPI Daten zu empfangen, die dann direkt über einen DAC wieder ausgegeben werden. Dieses Szenario ist sehr einfach mit DMA zu realisieren und kostet bis auf die anfängliche Konfiguration der Peripherie keinerlei weiteren Aufwand für die CPU, womit der Programmfluss des Hauptprogramms nicht weiter beeinträchtigt wird.

Downloads

Siehe auch