I have written a sample sketch that shows how to transfer more than 32KB in a single DMA transfer. To do this you have to bypass the functions of the dmaChannel object, which does not allow transfers larger than 32KB. In the following code I set up a transfer of 200KBytes to the lower byte of PortD, as I have done for other video frame transfers.
There are two issues with this code:
1. I hard-coded the transfer to work only with DMA channel zero when I setup the DMA control register.
2. The transfer happens at full bus speed because it all occurs as a single minor-loop transfer. Since it is a single transfer, I cannot time the output using the PDB to get transfers at a desired PDB clock rate. On a Teensy3.6 at 180MHz, the bytes transfer at about 36MB/second
Code:
.
/************************************************************************
*
* This sketch shows how to transfer a large memory buffer to the
* low byte of GPIO Port D.
* The teensy dmaChannel device won't handle buffers larger than 32KB,
* so you have to directly interact with the TCD and DMA control
* register.
* M. Borgerson 9/5/19
*
*************************************************************************/
#include <DMAChannel.h>
#include "kinetis.h"
DMAChannel dma0; //dma for DAC channel 0
#define HSYNCPIN 35
#define VSYNCPIN 33
// Pin 33 is used to generate a pulse for oscilloscope trigger
#define VSLOW digitalWriteFast(33, LOW);
#define VSHIGH digitalWriteFast(33,HIGH);
uint8_t dmabuff[200*1024];
uint32_t buffsize = sizeof(dmabuff);
void dumpTCD(void){
uint32_t *csptr; Serial.printf("DMA TCD at: 0x%08lX\n",(uint32_t)&dma0.TCD);
Serial.printf("SADDR: 0x%08lX\n", (int)dma0.TCD->SADDR);
Serial.printf("SOFF: %d\n", dma0.TCD->SOFF);
Serial.printf("ATTR: 0x%08lX\n", dma0.TCD->ATTR);
Serial.printf("NBYTES: %ld\n", dma0.TCD->NBYTES_MLOFFNO);
Serial.printf("SLAST: 0x%08lX\n", dma0.TCD->SLAST);
Serial.printf("DADDR: 0x%08lX\n", (int)dma0.TCD->DADDR);
Serial.printf("DOFF: %d\n", dma0.TCD->DOFF);
Serial.printf("CITER: 0x%08lX\n", dma0.TCD->CITER);
Serial.printf("DLASTSGA: 0x%08lX\n", dma0.TCD->DLASTSGA);
Serial.printf("CSR: 0x%08lX\n", (uint32_t)dma0.TCD->CSR);
Serial.printf("BITER: 0x%08lX\n", dma0.TCD->BITER);
Serial.println("--------------");
}
// initialize the dma buffer to a ramp waveform
void RampInitDmaBuff(void){
uint32_t i;
for(i= 0; i<buffsize; i++) dmabuff[i] = i & 0x07;
}
// initialize the dma buffer to a square wave for easier measurment of frequency
// use half of full amplitude for output
void SquareInitDmaBuff(void){
uint32_t i;
for(i= 0; i<buffsize; i++) dmabuff[i] = (i&1) * 4;
}
// The definition for the PDOR in kinetis.h is for a 32-bit port, but we want
// the DMA setup to recognize the port as an 8-bit port
#define GPIOD_PDORBL (*(volatile uint8_t *)0x400FF0C0) // Port Data Output Register low byte
// Set up DMA to transfer dmabuff to low byte of GPIOD
void InitDMA(void){
uint8_t *lptr;
dma0.disable();
lptr = &dmabuff[0];
dma0.TCD->DADDR = (&GPIOD_PDORBL);
dma0.TCD->SADDR = (uint8_t *)lptr; // source data buffer
dma0.TCD->SOFF = 1; // advance by 1 bytes (8 bits) per read
dma0.TCD->DOFF = 0; // Do not change destination after write
dma0.TCD->ATTR_SRC = 0;
dma0.TCD->NBYTES = sizeof(dmabuff);
dma0.TCD->SLAST = -sizeof(dmabuff);
dma0.TCD->BITER = 1; // for one large minor loop BITER and CITER = 1
dma0.TCD->CITER = 1;
dma0.disableOnCompletion(); // Just do one transfer at a time
// To make sure that the large transfer works, you have to set the DMA_CR_EMLM
// bit to zero in the DMA control register. The following command sets the
// other bits, but not the DMA_CR_EMLM bit.
DMA_CR = DMA_CR_GRP1PRI| DMA_CR_EDBG;
// Use the PDB to trigger each DMA transfer
dma0.triggerManual();
dma0.enable();
}
void setup() {
//Start serial
Serial.begin(9600);
delay(200);
delay(100);
SquareInitDmaBuff(); // initialize buffer to
pinMode(HSYNCPIN, OUTPUT);
pinMode(VSYNCPIN, OUTPUT);
Serial.println("PDB DMA Test. Starting...");
// these are the lower 3 bits of GPIOD
// used for the 3-bit R2R ladder DAC
pinMode(2, OUTPUT);
pinMode(14, OUTPUT);
pinMode(7, OUTPUT);
}
#define LOOPDELAY 100
void loop(){
char ch;
while (Serial.available() ) {
ch = Serial.read();
Serial.printf("Command = <%c> \n", ch);
if(ch == 'r'){
RampInitDmaBuff();
}
if(ch == 's'){
SquareInitDmaBuff();
}
if(ch == 'd'){
dumpTCD();
}
}
VSHIGH;
InitDMA(); // Initialize dma0 for transfer to GPIOD
delay(LOOPDELAY);
VSLOW;
}