DMA Buffer Table change at runtime T3.1

Status
Not open for further replies.

ctec

Member
Hi,

i've been using a modified version of the dac/dma sample from (https://forum.pjrc.com/threads/28101-Using-the-DAC-with-DMA-on-Teensy-3-1) to generate arbitrary waveforms with a teensy 3.1/3.2 using different lookuptables, changing the frequency of the waveform by modifying the PDB0_Mod at runtime, and was pretty happy with that.
However, i do need to change the DAC's output from the waveform to a simple 4095 on the DAC0, thus have a simple constant voltage output for a set time period.

But i did face some trouble handling the change of the dma.sourceBuffer at runtime. I might be doing a simple conversion error, maybe i do have another problem. The trouble is that i'm not very fluent in arduino with handling data conversions.

The concept is: serial input delivers a number for a frequency, this frequency is applied via setFreq(uint16_t freq) by means of changing PDB0_Mod, and if the serial reads a 0 as input, it triggers setDc to change the dma.sourceBuffer from the sinetable32 to dclut[], which is a simple high for the DAC. I was hoping this would be a convenient way of changing the waveform or, in this case, to apply a 100% Vref Voltage to the DAC0.

The code compiles fine and of course works fine until i trigger the change of the lookup-table via a zero on the serial input. It then seems to successfully disable the DMA resulting in a 50% Vref output on the DAC0 and does neither set the correct output level, nor change the dma.sourceBuffer to something that works (at all).

The trouble i'm facing is that setting the dma.sourceBuffer at complile-time requires a different input than doing it at runtime (outside of the setup()-scope).

All suggestions are welcome!
Thank you!

Code:
#include <DMAChannel.h>
#include "pdb.h"

DMAChannel dma(false);
char buf[80] = {};

int readline(int readch, char *buffer, int len) {
  static int pos = 0;
  int rpos;

  if (readch > 0) {
    switch (readch) {
      case '\r': // Ignore CR
        break;
      case '\n': // Return on new-line
        rpos = pos;
        pos = 0;  // Reset position index ready for next time
        return rpos;
      default:
        if (pos < len - 1) {
          buffer[pos++] = readch;
          buffer[pos] = 0;
        }
    }
  }
  return 0;
}
static volatile uint16_t sinetable32[] = {
  2047, 2459, 2854, 3217, 3531, 3785, 3967, 4071, 4092, 4029, 3885, 3666, 3381, 3041, 2660, 2254,
  1840, 1434, 1053,  713,  428,  209,   65,    2,   23,  127,  309,  563,  877, 1240, 1635, 2047,
};
static volatile uint16_t sinetable32_o[] = { // offset by +1000
  2547, 2859, 3157, 3431, 3669, 3860, 3998, 4077, 4093, 4045, 3936, 3771, 3555, 3298, 3010, 2704,
  2390, 2084, 1796, 1539, 1323, 1158, 1049, 1001, 1017, 1096, 1234, 1425, 1663, 1937, 2235, 2547,
};
static volatile uint16_t dclut[] = {
  4095,
};

static void setFreq(uint16_t freq) { //should work for 1...65KHz
  change_sr(F_BUS / (freq * 32)); //32 for LUT32
}

static void change_sr(uint16_t srate) {
  PDB0_MOD =  srate - 1;
  PDB0_SC = PDB_CONFIG | PDB_SC_LDOK; // load registers from buffers
}

static void setDc(DMAChannel dma) {
  changeLUT(dclut, dma);
  delay(5);
  changeLUT(sinetable32, dma);
}
static void changeLUT(volatile uint16_t *lut, DMAChannel dma) {
  dma.disable();
  dma.sourceBuffer(lut, sizeof(lut));
  dma.enable();
}

void setup() {
  dma.begin(true); // allocate the DMA channel first
  Serial.begin(115200);

  SIM_SCGC2 |= SIM_SCGC2_DAC0; // enable DAC clock
  DAC0_C0 = DAC_C0_DACEN | DAC_C0_DACRFS; // enable the DAC module, 3.3V reference
  // slowly ramp up to DC voltage, approx 1/4 second
  for (int16_t i = 0; i < 2048; i += 8) {
    *(int16_t *)&(DAC0_DAT0L) = i;
    delay(1);
  }

  // set the programmable delay block to trigger DMA requests
  SIM_SCGC6 |= SIM_SCGC6_PDB; // enable PDB clock
  PDB0_IDLY = 0; // interrupt delay register
  PDB0_MOD = 1499; // modulus register, sets period
  PDB0_SC = PDB_CONFIG | PDB_SC_LDOK; // load registers from buffers
  PDB0_SC = PDB_CONFIG | PDB_SC_SWTRIG; // reset and restart
  // PDB0_CH0C1 = 0x0101; // channel n control register-> Back to back enable, pre trigger output select to bypass, pre trigger enable
  PDB0_CH0C1 = 0x0100; // channel n control register-> Back to back enable, pre trigger output select to bypass, pre trigger disable

  dma.sourceBuffer(sinetable32, sizeof(sinetable32)); //32 is the best LUT
  dma.destination(*(volatile uint16_t *) & (DAC0_DAT0L));
  dma.triggerAtHardwareEvent(DMAMUX_SOURCE_PDB);
  dma.enable();
}

void loop() {
  if (readline(Serial.read(), buf, 80) > 0) {
    if (atoi(buf) == 0) {
      setDc(dma);
    }
    setFreq(atoi(buf));
    Serial.println("setFreq to: ");
    Serial.println(atoi(buf), DEC);
  }
}
 
My alternative idea would be to implement the initialization of two distinct dma channels, one with the sourceBuffer of sinetable32, one with dclut instead, and try to DMAChannel.swap(dma1, dma2), judging from https://github.com/PaulStoffregen/cores/blob/master/teensy3/DMAChannel.cpp, swap should be able to swap the channels, whilst changing the configuration of the new one to the configuration that the other had before the swap. But there is a possibility that i am thinking this wrong.

Unfortunately i've not found a single sample for DMA which uses a sourceBuffer change at runtime. All i can find are doing it in setup() which means, that i'd have to reset the teensy in order to apply a new sourceBuffer, or is there another option that i didn't notice yet?
 
Sorry I have not studied all of your code in details. For other reasons like display drivers and the like, at times I update the Source or Destination at run time.
But I have not typically done so directly or while the DMA is operating on the same buffer...
But one difference is I often need to use the DMASettings in addition to DMAChannel. As for example DMAChannel has limit of 32767 elements transferred and our displays have more pixels than that.

So what I typically do is to use multiple of the DMASettings. Where each one does a Replace On Completion to the next one, which more or less just copies the Settings into the DMAChannel structure. I might also do an Interrupt on completion on one or more of these.

Then when I wish to switch which one to use, I might setup that I get an interrupt and during the interrupt it will continue to process into the other settings. So I simply update that setting to new source or destination and when the other one finishes and chains back to me, it will have the new setting... May have to repeat to update other.

You can probably also do it with tricks with one spare settings object, which you setup to use and then on the fly set replace...

But there might be a quick and dirty solution. Simply your actual table on the fly. That is simply have another piece of memory the size of the two tables, and simply copy the one you wish to be the active one into the memory your DMA is using... Yes it adds to your memory usage. Note: if your variables like: static volatile uint16_t sinetable32
don't change I am not sure volatile is needed. Likewise if they don't change you might mark them as const in which case it will stay in the program space and not eat into your data space.
 
Hi KurtE,
thanks for taking the time to respond. Can you post some code snippets regarding how exactly you're updating the DMA Source or Destination at runtime? I don't see a problem disabling the DMAChannel object first, thats what i'm intending to do anyways. I'm mainly having trouble pointing DMAChannel.sourceBuffer towards my new datasource. DMAChannel.h (https://github.com/PaulStoffregen/cores/blob/master/teensy3/DMAChannel.h) shows the functions as
Code:
// Use a buffer (array of data) as the data source.  Typically a
	// buffer for transmitting data is used.
	void sourceBuffer(volatile const signed char p[], unsigned int len) {
		sourceBuffer((volatile const uint8_t *)p, len); }
	void sourceBuffer(volatile const unsigned char p[], unsigned int len) {
		TCD->SADDR = p;
		TCD->SOFF = 1;
		TCD->ATTR_SRC = 0;
		TCD->NBYTES = 1;
		TCD->SLAST = -len;
		TCD->BITER = len;
		TCD->CITER = len;
	}
Which shows that it actually takes the input data, dereferences it, and somewhat recursively launches itself with the (actual) memory address of the data. As mentioned, i'm not a professional pointer artist, there is a possibility im reading that wrong. What confuses me however is, that while setup() can easily run DMAChannel.sourceBuffer(data, sizeof(data)), while any function call at runtime yields a compiler error saying that sourceBuffer() needs a different Input. ("no matching function for call to 'sourceBuffer(volatile uint16_t&, unsigned int)'"). Thus i was suspecting i'm doing the pointing towards the data in the runtime function call wrong.
Anyway, the suggestion of overwriting the table at runtime might be an option, although seeminly hacky.
 
Hi KurtE,
i worked it out. My suspicion was correct, i did the whole lookuptable-parameter thing a bit wrong. Due to it being C it required passing sizeof(lookuptable) along with the table itself for the DMAChannel.sourceBuffer change, also i didn't notice that my function
Code:
static void changeLUT(volatile uint16_t *lut, DMAChannel dma) {
Needed uint16_t lut[] instead of uint16_t *lut.
Anyway, corrected code below if anyone needs a function generator with the Teensy3.2 in future:
Code:
#include <DMAChannel.h>
#include "pdb.h"

DMAChannel dma(false);
char buf[80] = {};

int readline(int readch, char *buffer, int len) {
  static int pos = 0;
  int rpos;

  if (readch > 0) {
    switch (readch) {
      case '\r': // Ignore CR
        break;
      case '\n': // Return on new-line
        rpos = pos;
        pos = 0;  // Reset position index ready for next time
        return rpos;
      default:
        if (pos < len - 1) {
          buffer[pos++] = readch;
          buffer[pos] = 0;
        }
    }
  }
  return 0;
}
static  uint16_t sinetable32[] = {
  2047, 2459, 2854, 3217, 3531, 3785, 3967, 4071, 4092, 4029, 3885, 3666, 3381, 3041, 2660, 2254,
  1840, 1434, 1053,  713,  428,  209,   65,    2,   23,  127,  309,  563,  877, 1240, 1635, 2047,
};
static  uint16_t sinetable32_o[] = { // offset by +1000
  2547, 2859, 3157, 3431, 3669, 3860, 3998, 4077, 4093, 4045, 3936, 3771, 3555, 3298, 3010, 2704,
  2390, 2084, 1796, 1539, 1323, 1158, 1049, 1001, 1017, 1096, 1234, 1425, 1663, 1937, 2235, 2547,
};
static  uint16_t dclut[] = {
  4095, 4095, 4095, 4095
};

static void setFreq(uint16_t freq) { //should work for 1...65KHz
  change_sr(F_BUS / (freq * 32)); //32 for LUT32
}

static void change_sr(uint16_t srate) {
  PDB0_MOD =  srate - 1;
  PDB0_SC = PDB_CONFIG | PDB_SC_LDOK; // load registers from buffers
}

static void setDc() {
  changeLUT(dclut, sizeof(dclut));
  delay(5000);
  changeLUT(sinetable32, sizeof(sinetable32));
}
static void changeLUT(uint16_t lut[], size_t arr_size) {
  dma.disable();
  Serial.println(arr_size);
  dma.sourceBuffer(lut, arr_size);
  dma.enable();
}

void setup() {
  dma.begin(true); // allocate the DMA channel first
  Serial.begin(115200);

  SIM_SCGC2 |= SIM_SCGC2_DAC0; // enable DAC clock
  DAC0_C0 = DAC_C0_DACEN | DAC_C0_DACRFS; // enable the DAC module, 3.3V reference
  // slowly ramp up to DC voltage, approx 1/4 second
  for (int16_t i = 0; i < 2048; i += 8) {
    *(int16_t *)&(DAC0_DAT0L) = i;
    delay(1);
  }

  // set the programmable delay block to trigger DMA requests
  SIM_SCGC6 |= SIM_SCGC6_PDB; // enable PDB clock
  PDB0_IDLY = 0; // interrupt delay register
  PDB0_MOD = 1499; // modulus register, sets period
  PDB0_SC = PDB_CONFIG | PDB_SC_LDOK; // load registers from buffers
  PDB0_SC = PDB_CONFIG | PDB_SC_SWTRIG; // reset and restart
  // PDB0_CH0C1 = 0x0101; // channel n control register-> Back to back enable, pre trigger output select to bypass, pre trigger enable
  PDB0_CH0C1 = 0x0100; // channel n control register-> Back to back enable, pre trigger output select to bypass, pre trigger disable

  dma.sourceBuffer(sinetable32, sizeof(sinetable32)); //32 is the best LUT
  dma.destination(*(volatile uint16_t *) & (DAC0_DAT0L));
  dma.triggerAtHardwareEvent(DMAMUX_SOURCE_PDB);
  dma.enable();
}

void loop() {
  if (readline(Serial.read(), buf, 80) > 0) {
    if (atoi(buf) == 0) {
      setDc();
    }
    setFreq(atoi(buf));
    Serial.println("setFreq to: ");
    Serial.println(atoi(buf), DEC);
  }
}
 
Status
Not open for further replies.
Back
Top