Using the PDB on Teensy 3

Status
Not open for further replies.
Hi,
I have plans to use the PDB (Programmable Delay Block) on my Teensy 3 for some sound sampling, but I'm having trouble getting it to work.

As I understand it you can trigger the PDB from software and have it run an interrupt service routine. This is what I set out to do as a first test.

This is my sketch where I attempt to turn on the pin 13 LED from the PDB isr:

Code:
void setup() {
  pinMode(13, OUTPUT);
  
  // Enables the PDB clock bit
  SIM_SCGC6 |= SIM_SCGC6_PDB;

  // PDB Interrupt Enable
  PDB0_SC |= PDB_SC_PDBIE;
  
  // Modulus Register
  PDB0_MOD = 9600; // 1/96 MHz * 9600 = 100 µs
  
  // PDB Enable
  PDB0_SC |= PDB_SC_PDBEN;
  
  // Software trigger
  PDB0_SC |= PDB_SC_TRGSEL(0xf);				
  PDB0_SC |= PDB_SC_SWTRIG;
  
  // Load OK
  PDB0_SC |= PDB_SC_LDOK;
}

void loop() {}

void pdb_isr() {
  digitalWrite(13, HIGH);
}

So far I've had no success and I would really appreciate any guidance! Thanks!
 
Thanks for the tip! I now have a PDB blink example going :)

The NVIC_ENABLE_IRQ(IRQ_PDB); is certainly needed, but apart from that I'm not quite sure what makes my previous code fail. The LED turns on if I remove PDB0_SC |= PDB_SC_LDOK; (and PDB0_MOD = 9600; since it does nothing in that case I think). However, the ISR will then run over and over again. To prevent that I think you need to clear the interrupt flag in the ISR.

Here's a minimal example turning on the LED and clearing the flag in the ISR:

Code:
void setup() {
	pinMode(13, OUTPUT);
	SIM_SCGC6 |= SIM_SCGC6_PDB;
	PDB0_SC = PDB_SC_TRGSEL(0xf) | PDB_SC_PDBEN | PDB_SC_PDBIE;
	PDB0_SC |= PDB_SC_SWTRIG;
	NVIC_ENABLE_IRQ(IRQ_PDB);
}

void loop() {}

void pdb_isr() {
	digitalWrite(13, HIGH);
	PDB0_SC &= ~PDB_SC_PDBIF; // clear interrupt flag
}

Using the continuous mode I also made a blink sketch:

Code:
uint8_t ledOn = 0;

/*
	PDB_SC_TRGSEL(15)        Select software trigger
	PDB_SC_PDBEN             PDB enable
	PDB_SC_PDBIE             Interrupt enable
	PDB_SC_CONT              Continuous mode
	PDB_SC_PRESCALER(7)      Prescaler = 128
	PDB_SC_MULT(1)           Prescaler multiplication factor = 10
*/
#define PDB_CONFIG (PDB_SC_TRGSEL(15) | PDB_SC_PDBEN | PDB_SC_PDBIE \
	| PDB_SC_CONT | PDB_SC_PRESCALER(7) | PDB_SC_MULT(1))

void setup() {
	pinMode(13, OUTPUT);

	// Enable the PDB clock
	SIM_SCGC6 |= SIM_SCGC6_PDB;

	// Modulus Register, 1/(48 MHz / 128 / 10) * 37500 = 1 s
	PDB0_MOD = 37500;

	// Interrupt delay
	PDB0_IDLY = 0;

	// PDB status and control
	PDB0_SC = PDB_CONFIG;

	// Software trigger (reset and restart counter)
	PDB0_SC |= PDB_SC_SWTRIG;

	// Load OK
	PDB0_SC |= PDB_SC_LDOK;

	// Enable interrupt request
	NVIC_ENABLE_IRQ(IRQ_PDB);
}

void loop() {}

void pdb_isr() {
	digitalWrite(13, (ledOn = !ledOn));
	PDB0_SC = PDB_CONFIG | PDB_SC_LDOK; // (also clears interrupt flag)
}
 
So, the interrupts are working great but now I would like to actually trigger the ADC with the PDB timer. Controlling the ADC and its interrupt separately works fine. The trouble I'm having is hooking it up to the PDB.

I've enabled the hardware trigger mode of the ADC (ADC_SC2_ADTRG) and selected a channel. For the PDB I've enabled the pre-trigger and set some delay on that.

I'm not sure how to configure the pre-trigger though. What does the delay mean? Also do I need to use ADC0_SC1B or can I work with only ADC0_SC1A if I want?

Here is my current code. Any ideas how to make it work is very welcome :)

Code:
/*
	PDB_SC_TRGSEL(15)        Select software trigger
	PDB_SC_PDBEN             PDB enable
	PDB_SC_PDBIE             Interrupt enable
	PDB_SC_CONT              Continuous mode
	PDB_SC_PRESCALER(7)      Prescaler = 128
	PDB_SC_MULT(1)           Prescaler multiplication factor = 10
*/
#define PDB_CONFIG (PDB_SC_TRGSEL(15) | PDB_SC_PDBEN | PDB_SC_PDBIE \
	| PDB_SC_CONT | PDB_SC_PRESCALER(7) | PDB_SC_MULT(1))

uint8_t ledOn = 0;

void setup() {
	Serial.begin(9600);
	while (!Serial);

	adcInit();
	pdbInit();
}

void loop() {}

static const uint8_t channel2sc1a[] = {
	5, 14, 8, 9, 13, 12, 6, 7, 15, 4,
	0, 19, 3, 21, 26, 22
};

void adc0_isr() {
	Serial.print("adc isr: ");
	Serial.println(millis());
	uint16_t result = ADC0_RA;
	Serial.println(result);
}

void pdb_isr() {
	Serial.print("pdb isr: ");
	Serial.println(millis());
	digitalWrite(13, (ledOn = !ledOn));
	PDB0_SC = PDB_CONFIG | PDB_SC_LDOK;
}

void adcInit() {
	// clock select = bus/2, divide ratio = 2, mode = single ended 10 bit, long sample time
	// F_BUS 48 MHz => ADCK 12 MHz
	ADC0_CFG1 = ADC_CFG1_ADICLK(1) | ADC_CFG1_ADIV(1) | ADC_CFG1_MODE(2) | ADC_CFG1_ADLSMP;
	// select channels ADxxb, select shortest long sample time
	ADC0_CFG2 = ADC_CFG2_MUXSEL | ADC_CFG2_ADLSTS(3);
	// voltage ref: vcc/ext, hardware trigger
	ADC0_SC2 = ADC_SC2_REFSEL(0) | ADC_SC2_ADTRG;
	// enable avaraging, 4 samples
	ADC0_SC3 = ADC_SC3_AVGE | ADC_SC3_AVGS(0);

	adcCalibrate();
	Serial.println("calibrated");

	// enable ADC interrupt, configure pin
	ADC0_SC1A = ADC_SC1_AIEN | channel2sc1a[3];
	NVIC_ENABLE_IRQ(IRQ_ADC0);

	// ADC0_SC1B // ?
}

void adcCalibrate() {
	uint16_t sum;

	ADC0_SC3 = ADC_SC3_CAL; // begin calibration
	while (ADC0_SC3 & ADC_SC3_CAL); // wait for calibration

	// plus side gain
	sum = ADC0_CLPS + ADC0_CLP4 + ADC0_CLP3 + ADC0_CLP2 + ADC0_CLP1 + ADC0_CLP0;
	sum = (sum / 2) | 0x8000;
	ADC0_PG = sum;

	// minus side gain (not used in single-ended mode)
	sum = ADC0_CLMS + ADC0_CLM4 + ADC0_CLM3 + ADC0_CLM2 + ADC0_CLM1 + ADC0_CLM0;
	sum = (sum / 2) | 0x8000;
	ADC0_MG = sum;
}

void pdbInit() {
	pinMode(13, OUTPUT);

	// Enable the PDB clock
	SIM_SCGC6 |= SIM_SCGC6_PDB;
	// Modulus Register, 48 MHz / 128 / 10 / 1 Hz = 37500
	PDB0_MOD = F_BUS / 128 / 10 / 1;
	// Interrupt delay
	PDB0_IDLY = 0;
	// PDB status and control
	PDB0_SC = PDB_CONFIG;
	// pre-trigger enable
	PDB0_CH0C1 = 1 | 2;
	PDB0_CH0DLY0 = 1000; // ?
	// PDB0_CH0DLY1 // ?
	// Software trigger (reset and restart counter), load OK
	PDB0_SC |= PDB_SC_SWTRIG | PDB_SC_LDOK;
	// Enable interrupt request
	NVIC_ENABLE_IRQ(IRQ_PDB);
}
 
Here is the ADC initialization code from upcoming audio library. It uses the PDB to trigger the ADC, and DMA to collect the data.

Code:
void AudioInputAnalog::begin(unsigned int pin)
{
        uint32_t i, sum=0;

        // pin must be 0 to 13 (for A0 to A13)
        // or 14 to 23 for digital pin numbers A0-A9
        // or 34 to 37 corresponding to A10-A13
        if (pin > 23 && !(pin >= 34 && pin <= 37)) return;

        pinMode(2, OUTPUT);
        pinMode(3, OUTPUT);
        digitalWriteFast(3, HIGH);
        delayMicroseconds(500);
        digitalWriteFast(3, LOW);

        // Configure the ADC and run at least one software-triggered
        // conversion.  This completes the self calibration stuff and
        // leaves the ADC in a state that's mostly ready to use
        analogReadRes(16);
        analogReference(INTERNAL); // range 0 to 1.2 volts
        //analogReference(DEFAULT); // range 0 to 3.3 volts
        analogReadAveraging(8);
        // Actually, do many normal reads, to start with a nice DC level
        for (i=0; i < 1024; i++) {
                sum += analogRead(pin);
        }
        dc_average = sum >> 10;

        // testing only, enable adc interrupt
        //ADC0_SC1A |= ADC_SC1_AIEN;
        //while ((ADC0_SC1A & ADC_SC1_COCO) == 0) ; // wait
        //NVIC_ENABLE_IRQ(IRQ_ADC0);

        // set the programmable delay block to trigger the ADC at 44.1 kHz
        SIM_SCGC6 |= SIM_SCGC6_PDB;
        PDB0_MOD = PDB_PERIOD;
        PDB0_SC = PDB_CONFIG | PDB_SC_LDOK;
        PDB0_SC = PDB_CONFIG | PDB_SC_SWTRIG;
        PDB0_CH0C1 = 0x0101;

        // enable the ADC for hardware trigger and DMA
        ADC0_SC2 |= ADC_SC2_ADTRG | ADC_SC2_DMAEN;

        // set up a DMA channel to store the ADC data
        SIM_SCGC7 |= SIM_SCGC7_DMA;
        SIM_SCGC6 |= SIM_SCGC6_DMAMUX;
        DMA_CR = 0;
        DMA_TCD2_SADDR = &ADC0_RA;
        DMA_TCD2_SOFF = 0;
        DMA_TCD2_ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1);
        DMA_TCD2_NBYTES_MLNO = 2;
        DMA_TCD2_SLAST = 0;
        DMA_TCD2_DADDR = analog_rx_buffer;
        DMA_TCD2_DOFF = 2;
        DMA_TCD2_CITER_ELINKNO = sizeof(analog_rx_buffer) / 2;
        DMA_TCD2_DLASTSGA = -sizeof(analog_rx_buffer);
        DMA_TCD2_BITER_ELINKNO = sizeof(analog_rx_buffer) / 2;
        DMA_TCD2_CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
        DMAMUX0_CHCFG2 = DMAMUX_DISABLE;
        DMAMUX0_CHCFG2 = DMAMUX_SOURCE_ADC0 | DMAMUX_ENABLE;
        //update_responsibility = update_setup();
        DMA_SERQ = 2;
        NVIC_ENABLE_IRQ(IRQ_DMA_CH2);
}

Edit: ignore the leftover stuff with pin 2 and 3... just leftover stuff from when I was verifying the timing with an oscilloscope.
 
Last edited:
Thank you for sharing! Your code was very helpful.

Now I got it to work! My mistake was writing the TOS and EN settings to the PDB_CH0C1 register in the wrong way.

Wrong: PDB0_CH0C1 = 1 | 2;
Correct: PDB0_CH0C1 = 0x0101;

I was actually going to dive in to the DMA chapter in the manual next. I'm sure your code will come in handy :)

Thanks again!
 
A quick update and a question

I've now gotten the DMA to work together with the ADC and PDB! The DMA code is essentially the same Paul shared, but I will post it below in case anyone finds it useful. Currently the PDB triggers the ADC every second. Once a conversion is finished the ADC requests a data transfer from the DMA and it stores the value in a buffer. The interrupts are enabled just to see some progress in the serial monitor.

Now the question. Suppose you are sampling continuously. How would you access the values? Is it safe to read from the DMA destination buffer without pausing the while thing? What happens if you try to read at the same time the DMA is writing?

Also, an other thing. In the manual (p 371) it says "If BITER is set, do not use INTHALF. Use INTMAJOR instead". In your code, Paul, it seems both are used. What do they even have to do with each other?

The code, with comments just for my reference:

Code:
#define PDB_CH0C1_TOS 0x0100
#define PDB_CH0C1_EN 0x01

uint8_t ledOn = 0;
uint16_t samples[16];

void setup() {
	Serial.begin(9600);
	while (!Serial);

	adcInit();
	pdbInit();
	dmaInit();
}

void loop() {}

static const uint8_t channel2sc1a[] = {
	5, 14, 8, 9, 13, 12, 6, 7, 15, 4,
	0, 19, 3, 21, 26, 22
};

/*
	ADC_CFG1_ADIV(2)         Divide ratio = 4 (F_BUS = 48 MHz => ADCK = 12 MHz)
	ADC_CFG1_MODE(2)         Single ended 10 bit mode
	ADC_CFG1_ADLSMP          Long sample time
*/
#define ADC_CONFIG1 (ADC_CFG1_ADIV(1) | ADC_CFG1_MODE(2) | ADC_CFG1_ADLSMP)

/*
	ADC_CFG2_MUXSEL          Select channels ADxxb
	ADC_CFG2_ADLSTS(3)       Shortest long sample time
*/
#define ADC_CONFIG2 (ADC_CFG2_MUXSEL | ADC_CFG2_ADLSTS(3))

void adcInit() {
	ADC0_CFG1 = ADC_CONFIG1;
	ADC0_CFG2 = ADC_CONFIG2;
	// Voltage ref vcc, hardware trigger, DMA
	ADC0_SC2 = ADC_SC2_REFSEL(0) | ADC_SC2_ADTRG | ADC_SC2_DMAEN;

	// Enable averaging, 4 samples
	ADC0_SC3 = ADC_SC3_AVGE | ADC_SC3_AVGS(0);

	adcCalibrate();
	Serial.println("calibrated");

	// Enable ADC interrupt, configure pin
	ADC0_SC1A = ADC_SC1_AIEN | channel2sc1a[3];
	NVIC_ENABLE_IRQ(IRQ_ADC0);
}

void adcCalibrate() {
	uint16_t sum;

	// Begin calibration
	ADC0_SC3 = ADC_SC3_CAL;
	// Wait for calibration
	while (ADC0_SC3 & ADC_SC3_CAL);

	// Plus side gain
	sum = ADC0_CLPS + ADC0_CLP4 + ADC0_CLP3 + ADC0_CLP2 + ADC0_CLP1 + ADC0_CLP0;
	sum = (sum / 2) | 0x8000;
	ADC0_PG = sum;

	// Minus side gain (not used in single-ended mode)
	sum = ADC0_CLMS + ADC0_CLM4 + ADC0_CLM3 + ADC0_CLM2 + ADC0_CLM1 + ADC0_CLM0;
	sum = (sum / 2) | 0x8000;
	ADC0_MG = sum;
}

/*
	PDB_SC_TRGSEL(15)        Select software trigger
	PDB_SC_PDBEN             PDB enable
	PDB_SC_PDBIE             Interrupt enable
	PDB_SC_CONT              Continuous mode
	PDB_SC_PRESCALER(7)      Prescaler = 128
	PDB_SC_MULT(1)           Prescaler multiplication factor = 10
*/
#define PDB_CONFIG (PDB_SC_TRGSEL(15) | PDB_SC_PDBEN | PDB_SC_PDBIE \
	| PDB_SC_CONT | PDB_SC_PRESCALER(7) | PDB_SC_MULT(1))

// 48 MHz / 128 / 10 / 1 Hz = 37500
#define PDB_PERIOD (F_BUS / 128 / 10 / 1)

void pdbInit() {
	pinMode(13, OUTPUT);

	// Enable PDB clock
	SIM_SCGC6 |= SIM_SCGC6_PDB;
	// Timer period
	PDB0_MOD = PDB_PERIOD;
	// Interrupt delay
	PDB0_IDLY = 0;
	// Enable pre-trigger
	PDB0_CH0C1 = PDB_CH0C1_TOS | PDB_CH0C1_EN;
	// PDB0_CH0DLY0 = 0;
	PDB0_SC = PDB_CONFIG | PDB_SC_LDOK;
	// Software trigger (reset and restart counter)
	PDB0_SC |= PDB_SC_SWTRIG;
	// Enable interrupt request
	NVIC_ENABLE_IRQ(IRQ_PDB);
}

void dmaInit() {
	// Enable DMA, DMAMUX clocks
	SIM_SCGC7 |= SIM_SCGC7_DMA;
	SIM_SCGC6 |= SIM_SCGC6_DMAMUX;

	// Use default configuration
	DMA_CR = 0;

	// Source address
	DMA_TCD1_SADDR = &ADC0_RA;
	// Don't change source address
	DMA_TCD1_SOFF = 0;
	DMA_TCD1_SLAST = 0;
	// Destination address
	DMA_TCD1_DADDR = samples;
	// Destination offset (2 byte)
	DMA_TCD1_DOFF = 2;
	// Restore destination address after major loop
	DMA_TCD1_DLASTSGA = -sizeof(samples);
	// Source and destination size 16 bit
	DMA_TCD1_ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1);
	// Number of bytes to transfer (in each service request)
	DMA_TCD1_NBYTES_MLNO = 2;
	// Set loop counts
	DMA_TCD1_CITER_ELINKNO = sizeof(samples) / 2;
	DMA_TCD1_BITER_ELINKNO = sizeof(samples) / 2;
	// Enable interrupt (end-of-major loop)
	DMA_TCD1_CSR = DMA_TCD_CSR_INTMAJOR;

	// Set ADC as source (CH 1), enable DMA MUX
	DMAMUX0_CHCFG1 = DMAMUX_DISABLE;
	DMAMUX0_CHCFG1 = DMAMUX_SOURCE_ADC0 | DMAMUX_ENABLE;

	// Enable request input signal for channel 1
	DMA_SERQ = 1;

	// Enable interrupt request
	NVIC_ENABLE_IRQ(IRQ_DMA_CH1);
}


void adc0_isr() {
	Serial.print("adc isr: ");
	Serial.println(millis());
	for (uint16_t i = 0; i < 16; i++) {
		if (i != 0) Serial.print(", ");
		Serial.print(samples[i]);
	}
	Serial.println(" ");
}

void pdb_isr() {
	Serial.print("pdb isr: ");
	Serial.println(millis());
	digitalWrite(13, (ledOn = !ledOn));
	// Clear interrupt flag
	PDB0_SC &= ~PDB_SC_PDBIF;
}

void dma_ch1_isr() {
	Serial.print("dma isr: ");
	Serial.println(millis());
	// Clear interrupt request for channel 1
	DMA_CINT = 1;
}
 
PBD0 With EXTERNAL TRIGGER

Has anyone tried to use the PDB0 timer with an external trigger.
It best I can figure is that it can be setup to trigger when pin 15 (or maybe pin 11) goes high.
(I'm trying to create a high resolution variable delay circuit, sort of like a spark advance on a distributor (for a 1 to 35 Hz input)

I've tried the following modification to danieljohansson code:
Code:
/*
  danieljohansson 
  https://forum.pjrc.com/threads/24492-Using-the-PDB-on-Teensy-3
  
  PDB_SC_TRGSEL(15)        Select software trigger
  PDB_SC_TRGSEL(0)         Select external trigger
  PDB_SC_PDBEN             PDB enable
  PDB_SC_PDBIE             Interrupt enable
  PDB_SC_CONT              Continuous mode
  PDB_SC_PRESCALER(7)      Prescaler = 128
  PDB_SC_MULT(1)           Prescaler multiplication factor = 10
*/

uint8_t ledOn = 0;
#define PDB_CONFIG (PDB_SC_TRGSEL(00) | PDB_SC_PDBEN | PDB_SC_PDBIE \
  | PDB_SC_CONT | PDB_SC_PRESCALER(7) | PDB_SC_MULT(1))

void setup() {
  pinMode(13, OUTPUT);

  // Enable the PDB clock
  SIM_SCGC6 |= SIM_SCGC6_PDB;

  // Modulus Register, 1/(48 MHz / 128 / 10) * 37500 = 1 s  
  // Modulus Register, 1/(60 MHz / 128 / 10) * 46875 = 1 s  @120MHz
  PDB0_MOD = 46875-9;

  // Interrupt delay
  PDB0_IDLY = 0;

  // PDB status and control
  PDB0_SC = PDB_CONFIG;

  // Software trigger (reset and restart counter)
 // PDB0_SC |= PDB_SC_SWTRIG;

  // enable External Trigger Input Source
  PDB0_SC |= PDB0_EXTRG;
 

  // Load OK
  PDB0_SC |= PDB_SC_LDOK;

  // Enable interrupt request
  NVIC_ENABLE_IRQ(IRQ_PDB);
}

void loop() {}

void pdb_isr() {
  digitalWrite(13, (ledOn = !ledOn));
  //PDB0_MOD = PDB0_MOD - 1;
  //if (PDB0_MOD == 0) PDB0_MOD = 100;
  PDB0_SC = PDB_CONFIG | PDB_SC_LDOK; // (also clears interrupt flag)
}


But I get the following compile errors using Teensy3.2, Arduino 1.6.6 with Teensyduino 1.26
Code:
Arduino: 1.6.6 (Windows 10), TD: 1.26, Board: "Teensy 3.2 / 3.1, Serial, 96 MHz optimized (overclock), US English"

C:\Users\Bob\AppData\Local\Temp\arduino_5c7a06531c50f6118c01c721860e74db\PDB_Timer_V100.ino: In function 'void setup()':
PDB_Timer_V100:36: error: 'PDB0_EXTRG' was not declared in this scope
PDB0_SC |= PDB0_EXTRG;
^
exit status 1
'PDB0_EXTRG' was not declared in this scope

  This report would have more information with
  "Show verbose output during compilation"
  enabled in File > Preferences.

Thanks,
Wozzy
 
Has anyone tried to use the PDB0 timer with an external trigger.
It best I can figure is that it can be setup to trigger when pin 15 (or maybe pin 11) goes high.
(I'm trying to create a high resolution variable delay circuit, sort of like a spark advance on a distributor (for a 1 to 35 Hz input)

Code:
  // enable External Trigger Input Source
  PDB0_SC |= PDB0_EXTRG;

PDB0_EXTRG is not a field into PDB0_SC but a pin of the chip (e.g. PTC0 (ALT3) aka pin15, or PTC6(Alt3) aka pin11)
 
Last edited:
PDB with external trigger

test with PWM 23 jumpered to PDB input pin 11 (could use pin 15)

Code:
#define PRREG(x) Serial.print(#x" 0x"); Serial.println(x,HEX)

#define PDB_CONFIG (PDB_SC_TRGSEL(00) | PDB_SC_PDBEN | PDB_SC_PDBIE )

volatile uint32_t ticks;
void pdb_isr() {
  PDB0_SC = PDB_CONFIG | PDB_SC_LDOK; // (also clears interrupt flag)
  ticks++;
}

void pdb_init() {
  SIM_SCGC6 |= SIM_SCGC6_PDB;    // Enable the PDB clock
  CORE_PIN11_CONFIG = PORT_PCR_MUX(3);   // pin 11 alt for PDB

  PDB0_IDLY = 0;      // Interrupt delay
  PDB0_SC = PDB_CONFIG;
  PDB0_SC |= PDB_SC_LDOK;

  NVIC_ENABLE_IRQ(IRQ_PDB);     // Enable interrupt request
}

void setup() {
  Serial.begin(9600);
  while (!Serial);

  analogWriteFrequency(23, 1024);  // default 488 hz
  analogWrite(23, 128);
  pdb_init();
  PRREG(PDB0_SC);
}

void loop() {
  static uint32_t prev = 0;
  Serial.println(ticks - prev);
  prev = ticks;
  delay(1000);
}
 
Hi all,
I have been trying to get the code posted on here to work but without any luck. The PDB triggers nicely as can be seen from the LED flashing.
The adc_isr() however kept triggering at the clockrate of the teensy.
After a few days of digging into the manual (and learning what registers are and how to use them) , i realized that after the conversion of the ADC is finished, it is put into continuous mode (see page 983 of the MCU manual).
it now works if i include :
ADC0_SC1A = ADC_SC1_AIEN ;
into the adc_isr();
I did not do any testing with the dma part yet, since before it would only return 0000000 and the adc_isr() kept triggering.

Is this something that was only happening to me? I cannot imagine this to not have been an issue for other users.
 
Okay scratch the previous post.
To test whether or not the DMA actually works, i enabled it again (i commented it previously just to test if the ADC was actually working.
I now uncommented it and it still returned samples all 0 to the serial monitor.
Upon this i commented the :
for (uint16_t i = 0; i < 16; i++) {
if (i != 0) Serial.print(", ");
Serial.print(samples);
}

And replaced it with
Serial.println(ADC0_RA, DEC);
which does print a value. When i then again commented:
ADC0_SC1A = ADC_SC1_AIEN ;
The ADC still only triggers when the PDB triggers instead of continously, so it appears that despite implementing my change (not even sure if it is correct since i should probably have used ADC0_SC1A |= ADC_SC1_AIEN ; instead of ADC0_SC1A = ADC_SC1_AIEN ;).

Not sure what is going on, can aybody shed any light?
 
I have been trying to get the code posted on here to work but without any luck.
Which code? The code from post #8?

The adc_isr() however kept triggering at the clockrate of the teensy.

After a few days of digging into the manual (and learning what registers are and how to use them) , i realized that after the conversion of the ADC is finished, it is put into continuous mode (see page 983 of the MCU manual).
Not really. Continuous conversion is only kicked off, if you set "Continuous Conversion Enable" in ADCx_SC3. The default is off. The ISR only retriggers continuously, if you don't acknowledge the interrupt.

You didn't post your code. You must have done something wrong.
 
yes, copying the code exactly from post #8 prodces (on Teensy 3.6) something like this:


Basically i just found out that i can change this behavior by adding any random read/write to one of the SC registers inside of the adc_isr().
For instance adding
Serial.println(ADC0_RA, DEC)
which basically does not change anything, returns me with a pretty looking serial monitor like:
 
yes, copying the code exactly from post #8 prodces (on Teensy 3.6) something like this:


Basically i just found out that i can change this behavior by adding any random read/write to one of the SC registers inside of the adc_isr().
For instance adding
Serial.println(ADC0_RA, DEC)
which basically does not change anything, returns me with a pretty looking serial monitor like:
Okay so printing the result register contents also clears the COCO bit thereby acknowledging the interrupt, presenting me only with a single output from adc_isr().
However, i am not seeing the dma_isr() triggering, which is probably also the reason why the interrupt from the adc is not acknowledged. If the dma would work, it would also read ADC0_RA and thereby clearing ADC0_SC1A_COCO.

So next issue would be to find out why the DMA is not performing any transfers.
 
Solved!

if i comment out this section:
// Use default configuration
DMA_CR = 0;

not sure why, but it works, and the DMA triggers :)
 
The code from post #8 works on Teensy 3.2. On Teensy 3.6, DMA_CR is initialized by the boot code as:
DMA_CR = DMA_CR_GRP1PRI| DMA_CR_EMLM | DMA_CR_EDBG;

For DMA, you should really use DMAChannel:
Code:
#include <DMAChannel.h>

DMAChannel dma;

void dmaInit() {
    dma.source(*(uint16_t*) &ADC0_RA);
    dma.destinationBuffer(samples, sizeof(samples));
    dma.attachInterrupt(dma_isr);
    dma.interruptAtCompletion();
    dma.triggerAtHardwareEvent(DMAMUX_SOURCE_ADC0);
    dma.enable();
}

void dma_isr() {
    Serial.print("dma isr: ");
    Serial.println(millis());
    dma.clearInterrupt();
}
 
Thanks a lot for the replies tni!

I got working what i wanted to accomplish; start the PDB at random frequency, let the PDB trigger the ADC, use DMA to move the samples to a memory location and hault conversion by stopping pdb and dma.
The goal was to sample continously in the background one full sampling cycle while the mcu gets to perform other tasks, mission accomplished.
In the end i used pedvide's example provided with the ADC library and added DMA functionality after i saw there was a library for it (did not know yet, thanks!).

In case anybody wants to use some higher level library coding than the register level given above; presented code below is tried and tested on Teensy 3.6
Note i commented ADC_1 since i only needed to use ADC_0
Code:
/* Adapted code take from Pedvides ADC library:Example for triggering the ADC with PDB
    DMA channel added to store samples directy.
    DMA isr changed such that only one single write cycle to samples[] is performed
*/


#include <ADC.h>
#include <DMAChannel.h>

const int MeasurementPin = A4; // ADC0
//const int MeasurementPin2 = A2; // ADC1
uint16_t samples[16384];

ADC *adc = new ADC(); // adc object;
DMAChannel dma;       // dma Channel

void setup() {

  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(MeasurementPin, INPUT);
  //pinMode(MeasurementPin2, INPUT);

  Serial.begin(9600);
  while (!Serial);

  Serial.println("Begin setup");

  dmaInit();

  ///// ADC0 ////
  // reference can be ADC_REF_3V3, ADC_REF_1V2 (not for Teensy LC) or ADC_REF_EXT.
  //adc->setReference(ADC_REF_1V2, ADC_0); // change all 3.3 to 1.2 if you change the reference to 1V2
  adc->setReference(ADC_REF_3V3, ADC_0);
  adc->setAveraging(4); // set number of averages
  adc->setResolution(12); // set bits of resolution
  // it can be ADC_VERY_LOW_SPEED, ADC_LOW_SPEED, ADC_MED_SPEED, ADC_HIGH_SPEED_16BITS, ADC_HIGH_SPEED or ADC_VERY_HIGH_SPEED
  // see the documentation for more information
  adc->setConversionSpeed(ADC_HIGH_SPEED_16BITS); // change the conversion speed
  // it can be ADC_VERY_LOW_SPEED, ADC_LOW_SPEED, ADC_MED_SPEED, ADC_HIGH_SPEED or ADC_VERY_HIGH_SPEED
  adc->setSamplingSpeed(ADC_HIGH_SPEED_16BITS); // change the sampling speed
  adc->enableInterrupts(ADC_0); // it's necessary to enable interrupts for PDB to work (why?)
  adc->enableDMA(ADC_0); // enable DMA requrest;    ADC0_SC2 |= | ADC_SC2_ADTRG | ADC_SC2_DMAEN;
  adc->analogRead(MeasurementPin, ADC_0); // call this to setup everything before the pdb starts

  ////// ADC1 /////
  //#if ADC_NUM_ADCS>1
  //  adc->setReference(ADC_REF_3V3, ADC_1);
  //  adc->setAveraging(4, ADC_1); // set number of averages
  //  adc->setResolution(12, ADC_1); // set bits of resolution
  //  adc->setConversionSpeed(ADC_HIGH_SPEED_16BITS, ADC_1); // change the conversion speed
  //  adc->setSamplingSpeed(ADC_HIGH_SPEED_16BITS, ADC_1); // change the sampling speed
  //  adc->enableInterrupts(ADC_1);
  //  adc->enableDMA(ADC_1); // enable DMA requrest;    ADC1_SC2 |= | ADC_SC2_ADTRG | ADC_SC2_DMAEN;
  //  adc->analogRead(MeasurementPin2, ADC_1); // call this to setup everything before the pdb starts
  //#endif
  Serial.println("End setup");

}

char c = 0;
int value;
int value2;

void loop() {

  if (Serial.available()) {
    c = Serial.read();
    if (c == 's') { // start pdb, before pressing enter write the frequency in Hz
      uint32_t freq = Serial.parseInt();
      Serial.print("Start pdb with frequency ");
      Serial.print(freq);
      Serial.println(" Hz.");
      adc->adc0->stopPDB();
      adc->adc0->startPDB(freq); //frequency in Hz
      //#if ADC_NUM_ADCS>1
      //      adc->adc1->stopPDB();
      //      adc->adc1->startPDB(freq); //frequency in Hz
      //#endif
    } else if (c == 'p') { // pbd stats
      Serial.print("Prescaler:");
      Serial.println( (PDB0_SC & 0x7000) >> 12 , HEX);
      Serial.print("Mult:");
      Serial.println( (PDB0_SC & 0xC) >> 2, HEX);
    }    else if (c == 'r') { // reset
      adc->adc0->stopPDB();
      //#if ADC_NUM_ADCS>1
      //      adc->adc1->stopPDB();
      //#endif
    dma.detachInterrupt();
    dma.disable();
    }    else if (c == 'd') { // send Data to serial monitor, 16 samples just to see some data.
      for (int i = 0; i < 16; i++) {
        Serial.println(samples[i]);
      }
      Serial.println("");
    }
  }


  /* fail_flag contains all possible errors,
      They are defined in  ADC_Module.h as

      ADC_ERROR_OTHER
      ADC_ERROR_CALIB
      ADC_ERROR_WRONG_PIN
      ADC_ERROR_ANALOG_READ
      ADC_ERROR_COMPARISON
      ADC_ERROR_ANALOG_DIFF_READ
      ADC_ERROR_CONT
      ADC_ERROR_CONT_DIFF
      ADC_ERROR_WRONG_ADC
      ADC_ERROR_SYNCH

      You can compare the value of the flag with those masks to know what's the error.
  */

  if (adc->adc0->fail_flag) {
    Serial.print("ADC0 error flags: 0x");
    Serial.println(adc->adc0->fail_flag, HEX);
    if (adc->adc0->fail_flag == ADC_ERROR_COMPARISON) {
      adc->adc0->fail_flag &= ~ADC_ERROR_COMPARISON; // clear that error
      Serial.println("Comparison error in ADC0");
    }
  }
  //#if ADC_NUM_ADCS>1
  //  if (adc->adc1->fail_flag) {
  //    Serial.print("ADC1 error flags: 0x");
  //    Serial.println(adc->adc1->fail_flag, HEX);
  //    if (adc->adc1->fail_flag == ADC_ERROR_COMPARISON) {
  //      adc->adc1->fail_flag &= ~ADC_ERROR_COMPARISON; // clear that error
  //      Serial.println("Comparison error in ADC1");
  //    }
  //  }
  //#endif

  //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN));
  //delay(100);
}

void dmaInit() {
  dma.source(*(uint16_t*) &ADC0_RA);
  dma.destinationBuffer(samples, sizeof(samples));
  dma.attachInterrupt(dma_isr);
  dma.interruptAtCompletion();
  dma.triggerAtHardwareEvent(DMAMUX_SOURCE_ADC0);
  dma.enable();
}

void adc0_isr() {
  Serial.print("adc0 isr: ");
  Serial.println(millis());
}

//#if ADC_NUM_ADCS>1
//void adc1_isr() {
//  Serial.print("adc1 isr: ");
//  Serial.println(millis());
//}
//#endif

// pdb interrupt is enabled in case you need it.
void pdb_isr(void) {
  PDB0_SC &= ~PDB_SC_PDBIF; // clear interrupt
  Serial.print("pdb isr: ");
  Serial.println(millis());
  digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) );
}

//void dma_isr() {
//  Serial.print("dma isr: ");
//  Serial.println(millis());
//  dma.clearInterrupt();
//  for (int i = 0; i < 16; i++) {
//    Serial.println(samples[i]);
//  }
//  Serial.println("");
//}

void dma_isr() {
  adc->adc0->stopPDB();
  //#if ADC_NUM_ADCS>1
  //      adc->adc1->stopPDB();
  //#endif
    dma.detachInterrupt();
    dma.disable();
}
 
Last edited:
One final question;
I just implemented the code into the main code i am using. It does not work.
The issue seems to be with pins A10 and A11.
if i use pin A9 for instance, everything works fine, but with A10,A11,A12 or A13 (the differential pairs) it returns Error 0x4 for ADC_ERROR_ANALOG_READ.
PDB starts fine but of course the adc_isr() never triggers due to the error.
I am not sure why this is happening. I know A10 and A11 can only be accessed in single ended mode but that should be okay since it is initialized as such using
Code:
  adc->analogRead(MeasurementPin, ADC_0); // call this to setup everything before the pdb starts
Gonna dive in the definitions of A10 and A11 in the ADC library to see if antyhing pops up but maybe somebody can point me in the proper direction?

Ow, i really need to be able to use A11 since i designed my electronics the teensy plugs into to sample using A11.
 
Last edited:
A10 should work with ADC0. A11 is only available on ADC1.

The manual has the pin / ADC channel routing, e.g. "39.1.3.1 ADC0 Channel Assignment for K66 Subfamily in the 144-Pin Package". A11 is BGA pin M2 (ADC1_DM0 / ADC0_DM3).

Error 4 means you used an invalid pin.
 
A11 is indeed connected to ADC1, A10 to ADC0, i thought it was reversed.
I checked the ADC library i had and saw it did not have definitions for Teensy 3.6
I updated the library from the github repository and all is good now. A12 and A13 also working like they should.
 
In the K66 Manual i found the single endet pins of A10 and A11 are not the same as the differential pins and they are not in the teensy3.6 schematic. So i can not use them single endet?

The names of the teensyPins Ax are different of the names in the K66 Manual Ay_SE .
 
Status
Not open for further replies.
Back
Top