Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 22 of 22

Thread: Using the PDB on Teensy 3

  1. #1

    Using the PDB on Teensy 3

    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!

  2. #2
    I've also tried using
    Code:
    NVIC_ENABLE_IRQ(IRQ_PDB);
    but with no luck.

  3. #3
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    14,069
    The Servo library uses the PDB, with interrupts. Maybe looking at Servo.cpp could help?

  4. #4
    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)
    }

  5. #5
    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);
    }

  6. #6
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    14,069
    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 by PaulStoffregen; 11-13-2013 at 01:29 AM.

  7. #7
    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!

  8. #8

    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;
    }

  9. #9
    Senior Member Wozzy's Avatar
    Join Date
    Jan 2013
    Location
    Philadelphia, Pennsylvania USA
    Posts
    307

    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

  10. #10
    Senior Member
    Join Date
    Jul 2014
    Posts
    1,090
    Quote Originally Posted by Wozzy View Post
    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 by WMXZ; 11-30-2015 at 07:36 AM.

  11. #11
    Senior Member manitou's Avatar
    Join Date
    Jan 2013
    Posts
    863
    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);
    }

  12. #12
    Junior Member
    Join Date
    Apr 2017
    Posts
    8
    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.

  13. #13
    Junior Member
    Join Date
    Apr 2017
    Posts
    8
    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[i] 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[i]);
    }
    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?

  14. #14
    Senior Member
    Join Date
    Jan 2013
    Posts
    564
    Quote Originally Posted by teunos View Post
    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.

  15. #15
    Junior Member
    Join Date
    Apr 2017
    Posts
    8
    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:

  16. #16
    Junior Member
    Join Date
    Apr 2017
    Posts
    8
    Quote Originally Posted by teunos View Post
    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.

  17. #17
    Junior Member
    Join Date
    Apr 2017
    Posts
    8
    Solved!

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

    not sure why, but it works, and the DMA triggers

  18. #18
    Senior Member
    Join Date
    Jan 2013
    Posts
    564
    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();
    }

  19. #19
    Junior Member
    Join Date
    Apr 2017
    Posts
    8
    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 by teunos; 04-21-2017 at 11:43 AM.

  20. #20
    Junior Member
    Join Date
    Apr 2017
    Posts
    8
    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 by teunos; 04-21-2017 at 02:15 PM.

  21. #21
    Senior Member
    Join Date
    Jan 2013
    Posts
    564
    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.

  22. #22
    Junior Member
    Join Date
    Apr 2017
    Posts
    8
    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.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •