Teensy 4 synchronize ADC sampling with flexPWM timing

Status
Not open for further replies.

The Law

Member
Hello everyone, this is the first time im posting a thread on the pjrc forum so please excuse any mistakes i make.
I have a Teensy 4.0 which i am planning on using with my high powered ebike to control mosfets to provide 7.2kw of power using an FOC algorithm. I have found a github post which does foc for pmsm on teensy 3: https://github.com/jakeread/tesc
Now my difficulty comes with converting this to the teensy 4 as it uses low level register manipulaton for pwm and adc. I have managed with allot of difficulty and lack of documentation on the flexpwm to produce the proper pwm outputs, now coming to the part where i need the ADC to work the magic for me, I have no clue how i am supposed to enable ADC readings at the pwm val 4 or 5 counter which seems to be the proffesional way of doing adc conversion at mosfet on switching. Do you think i could get by using pevide's ADC library and readcontiniously in a ringbuffer or is there anyone who could guide me into taking properly flexpwm timed adc readings?

Kind Regards,

Daniel Tabrizian
 
I thought I would answer here instead of PM as hopefully others may have additional information...
i have flexpwm up and running generating the pwm signals i want, and i have the adc module taking readings using a xbar connected quadtimer, but when i set the xbar to for example flexpwm4_2 and trying out several tctrl options i am not able to get flexpwm to trigger the adc.

It always helps in cases like this if you post an example sketch that you have tried, as it hopefully helps to fill in the gaps on what you have tried and the like.

Things like is the XBar setup properly? How is the flexPWM4_2?
Looks like there are at least two different triggers?
Code:
#define XBARA1_IN_FLEXPWM4_PWM1_OUT_TRIG0 52
#define XBARA1_IN_FLEXPWM4_PWM1_OUT_TRIG1 52

I assume that it would have to go to one of the ADC_ETC COCOn outputs, which implies that needs to be setup. Then you need to setup ADC to use ADC_ETC...

And you may need to confirm that there are no input select registers involved which may need to be set...

So again I would start off with what we did to get the ADC working with Quad Timer and post example sketch...
 
So currently i have several flexpwm registers set to output a proper and working pwm signal, i wasnt able to get complementary mode working but by using registers 4 and 5 i was able to generate the signals i need anyways. To enable flexpwm triggers i run the following code:
Code:
flexpwm4->SM[2].TCTRL |= FLEXPWM_SMTCTRL_PWAOT0 | FLEXPWM_SMTCTRL_OUT_TRIG_EN(1); 


    flexpwm3->SM[1].TCTRL |= (1<<15) | (1<<14)  | (1<<5) | (1<<4) | (1<<3) | (1<<2) | (1<<1) | (1<<0); 

    flexpwm1->SM[3].TCTRL |= (1<<15) | (1<<14)  | (1<<5) | (1<<4) | (1<<3) | (1<<2) | (1<<1) | (1<<0);

And in my adc class i use the following to enable xbar, you can see the commented line of code, if im using that with the quadtimer setup it works wonderfully but changing it to flexpwm4_2 doesnt trigger an interrupt anymore:
Code:
void xbar_init() {
  CCM_CCGR2 |= CCM_CCGR2_XBAR1(CCM_CCGR_ON);   //turn clock on for xbara1
  xbar_connect(XBARA1_IN_FLEXPWM4_PWM2_OUT_TRIG0 , XBARA1_OUT_ADC_ETC_TRIG01);  
  // xbar_connect(XBARA1_IN_QTIMER4_TIMER3 , XBARA1_OUT_ADC_ETC_TRIG01);
}

I think by using the quadtimer succesfully i have narrowed down the problem to the trigger from flexpwm to the adc etc signal somewhere not working correctly. Which is weird as id think that wouldnt be so complicated.


Edit: i didnt want to add the entire sketch because that would be very long and split up in several files as im using platform.io instead of arduino ide
 
Here is it anyway:


Adc class:
Code:
#include <T3/t3adc.h>
#include "Drive/foc.h"
#define PRREG(x) Serial.print(#x" 0x"); Serial.println(x,HEX)
ADC *adc = new ADC();
T3ADC* T3ADC::instance;
T3ADC::T3ADC(){
  instance = this;
}


void adc0_isr(){
    T3ADC::instance->rbaw->push((uint16_t)adc->adc0->readSingle()); // adc result data register
}


void adc1_isr(){
    T3ADC::instance->rbaw->push((uint16_t)adc->adc1->analogReadContinuous()); // adc result data register
}

extern "C" {
    extern void xbar_connect(unsigned int input, unsigned int output);
    extern void quadtimer_init(IMXRT_TMR_t *p);
    extern void quadtimerWrite(IMXRT_TMR_t *p, unsigned int submodule, uint16_t val);
    extern void quadtimerFrequency(IMXRT_TMR_t *p, unsigned int submodule, float frequency);
}

void adcetc0_isr() {
  ADC_ETC_DONE0_1_IRQ |= 1;   // clear
  uint16_t x = ADC_ETC_TRIG0_RESULT_1_0 & 4095;
  T3ADC::instance->rbav->push(x);
  asm("dsb");
}

void adcetc1_isr() {
  ADC_ETC_DONE0_1_IRQ |= 1 << 16;   // clear
  uint16_t x = (ADC_ETC_TRIG0_RESULT_1_0 >> 16) & 4095;
  T3ADC::instance->rbaw->push(x);
  asm("dsb");
}

void adc_init() {
  // init and calibrate with help from core
  analogReadResolution(10);
  analogRead(A4);
  analogRead(A5);
  ADC1_CFG |= ADC_CFG_ADTRG;   // hardware trigger
  // ADC1_CFG = 0x200b;
  ADC1_HC0 = 5;   // ADC_ETC channel
  ADC1_HC1 = 6;

  
}

void adc_etc_init() {
  ADC_ETC_CTRL &= ~(1 << 31); // SOFTRST
  IMXRT_ADC_ETC.CTRL |= 
      (ADC_ETC_CTRL_TSC_BYPASS | ADC_ETC_CTRL_DMA_MODE_SEL | ADC_ETC_CTRL_TRIG_ENABLE(1 << 0) | ADC_ETC_CTRL_TRIG_ENABLE(1 << 1)); // start with trigger 0
  ADC_ETC_TRIG0_CTRL = ADC_ETC_TRIG_CTRL_TRIG_CHAIN(1);
  ADC_ETC_TRIG1_CTRL = ADC_ETC_TRIG_CTRL_TRIG_CHAIN(1);    // chainlength -1
  //  ADC_ETC_TRIG0_CHAIN_1_0 = 0x50283017;   // ADC1 7 8, chain channel, HWTS, IE, B2B
  
  IMXRT_ADC_ETC.TRIG[0].CHAIN_1_0 =
            ADC_ETC_TRIG_CHAIN_IE0(2) /*| ADC_ETC_TRIG_CHAIN_B2B0 */
            | ADC_ETC_TRIG_CHAIN_HWTS0(1) | ADC_ETC_TRIG_CHAIN_CSEL0(5) | ADC_ETC_TRIG_CHAIN_B2B0 | ADC_ETC_TRIG_CHAIN_B2B1|
                        ADC_ETC_TRIG_CHAIN_IE1(1) /*| ADC_ETC_TRIG_CHAIN_B2B0 */
            | ADC_ETC_TRIG_CHAIN_HWTS1(1) | ADC_ETC_TRIG_CHAIN_CSEL1(6) ;
  attachInterruptVector(IRQ_ADC_ETC0, adcetc0_isr);
  NVIC_ENABLE_IRQ(IRQ_ADC_ETC0);
  attachInterruptVector(IRQ_ADC_ETC1, adcetc1_isr);
  NVIC_ENABLE_IRQ(IRQ_ADC_ETC1);

}


void xbar_init() {
  CCM_CCGR2 |= CCM_CCGR2_XBAR1(CCM_CCGR_ON);   //turn clock on for xbara1
  xbar_connect(XBARA1_IN_FLEXPWM4_PWM2_OUT_TRIG0 , XBARA1_OUT_ADC_ETC_TRIG00);  
  // xbar_connect(XBARA1_IN_QTIMER4_TIMER3 , XBARA1_OUT_ADC_ETC_TRIG01);
}


void T3ADC::init(){
  this->rbav = new RingBuffer();
  this->rbaw = new RingBuffer();
  



  adc0_flag = false;
  adc1_flag = false;

  // adc->adc0->enableInterrupts(adc0_isr);
  // adc->adc1->enableInterrupts(adc1_isr);

  // adc->adc0->setResolution(10);
  // adc->adc1->setResolution(10);

  // adc->adc0->setHardwareTrigger();
  // adc->adc1->setHardwareTrigger();


  // adc->adc0->setAveraging(4);
  // adc->adc1->setAveraging(4);
  
  // adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED);
  // adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED);

  // adc->adc1->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED);
  // adc->adc1->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED);
  
  // adc->adc0->wait_for_cal();
  // adc->adc1->wait_for_cal();

  // adc->adc0->enableCompare(100,true);
  // adc->adc1->enableCompare(100,true);
  
  // adc->adc0->startSingleRead(A5);
  // adc->adc1->startContinuous(A4);

  // adc->adc0->startSingleRead(A5); // call this to setup everything before the Timer starts, differential is also possible
  xbar_init();
  adc_init();
  adc_etc_init();
  // adc->adc0->startflexpwmTrigger(XBARA1_IN_FLEXPWM4_PWM2_OUT_TRIG0); //frequency in Hz

    // Now init the QTimer.
    // Extracted from quadtimer_init in pwm.c but only the one channel...
    // Maybe see if we have to do this every time we call this.  But how often is that? 
    IMXRT_TMR4.CH[3].CTRL = 0; // stop timer
    IMXRT_TMR4.CH[3].CNTR = 0;
    IMXRT_TMR4.CH[3].SCTRL = TMR_SCTRL_OEN | TMR_SCTRL_OPS | TMR_SCTRL_VAL | TMR_SCTRL_FORCE;
    IMXRT_TMR4.CH[3].CSCTRL = TMR_CSCTRL_CL1(1) | TMR_CSCTRL_ALT_LOAD;
    // COMP must be less than LOAD - otherwise output is always low
    IMXRT_TMR4.CH[3].LOAD = 24000;   // low time  (65537 - x) - 
    IMXRT_TMR4.CH[3].COMP1 = 0;  // high time (0 = always low, max = LOAD-1)
    IMXRT_TMR4.CH[3].CMPLD1 = 0;
    IMXRT_TMR4.CH[3].CTRL = TMR_CTRL_CM(1) | TMR_CTRL_PCS(8) |
        TMR_CTRL_LENGTH | TMR_CTRL_OUTMODE(6);

    quadtimerFrequency(&IMXRT_TMR4, 3, 500);
    quadtimerWrite(&IMXRT_TMR4, 3, 5);
}



flexpwm:

Code:
#include "t3pwm.h"
#include <imxrt.h>

uint16_t DEADTIME = 1000; 
#define FLEXPWM_SMTCTRL_OUT_TRIG_ENbeta(n)		((uint16_t)(((n) & 0x3F) << 0))


T3PWM::T3PWM() {
  // save it for init
}

//      {1, M(4, 2), 1, 1},  // FlexPWM4_2_A   2  // EMC_04
//      {1, M(4, 2), 2, 1},  // FlexPWM4_2_B   3  // EMC_05
//  	{1, M(3, 1), 2, 1},  // FlexPWM3_1_B  28  // EMC_32
//  	{1, M(3, 1), 1, 1},  // FlexPWM3_1_A  29  // EMC_31
//  	{1, M(1, 3), 2, 6},  // FlexPWM1_3_B   7  // B1_01
//  	{1, M(1, 3), 1, 6},  // FlexPWM1_3_A   8  // B1_00

    IMXRT_FLEXPWM_t *flexpwm4;
    IMXRT_FLEXPWM_t *flexpwm3;
    IMXRT_FLEXPWM_t *flexpwm1;
    uint16_t newdiv;
    uint16_t MAX_DUTY;
    const uint16_t phaseShiftV = 21485;
    const uint16_t phaseShiftW = 42970;

    IntervalTimer phaseTimer;

    bool phaseUset = false;
bool phaseVset = false;

void startPhasing(){
           // flexpwm4->SM[2].TCTRL =  FLEXPWM_SMTCTRL_OUT_TRIG_EN(0) ;
    // FLEXPWM4_SM2TCTRL |= FLEXPWM_SMTCTRL_PWAOT0 | FLEXPWM_SMTCTRL_OUT_TRIG_EN(3) ;
    flexpwm4->SM[2].TCTRL |= FLEXPWM_SMTCTRL_PWAOT0 | FLEXPWM_SMTCTRL_OUT_TRIG_EN(1); 


    flexpwm3->SM[1].TCTRL |= (1<<15) | (1<<14)  | (1<<5) | (1<<4) | (1<<3) | (1<<2) | (1<<1) | (1<<0); 

    flexpwm1->SM[3].TCTRL |= (1<<15) | (1<<14)  | (1<<5) | (1<<4) | (1<<3) | (1<<2) | (1<<1) | (1<<0); 

    
    if(!phaseUset){
        flexpwm4->MCTRL |= FLEXPWM_MCTRL_LDOK(1 << 2);
        flexpwm4->OUTEN |= FLEXPWM_OUTEN_PWMA_EN(1 << 2) | FLEXPWM_OUTEN_PWMB_EN(1 << 2);
        phaseUset = true;
        return;
    }
    if(!phaseVset){
        flexpwm3->MCTRL |= FLEXPWM_MCTRL_LDOK(1 << 1);
        flexpwm3->OUTEN |= FLEXPWM_OUTEN_PWMA_EN(1 << 1) | FLEXPWM_OUTEN_PWMB_EN(1 << 1);
        phaseVset = true;
        return;
    }
    
    flexpwm1->MCTRL |= FLEXPWM_MCTRL_LDOK(1 << 3);
    flexpwm1->OUTEN |= FLEXPWM_OUTEN_PWMA_EN(1 << 3) | FLEXPWM_OUTEN_PWMB_EN(1 << 3);
    phaseTimer.end();
}
void T3PWM::init(){
    newdiv  = (64454);
    uint32_t prescale = 0;
    flexpwm4  = &IMXRT_FLEXPWM4;
    flexpwm3  = &IMXRT_FLEXPWM3;
    flexpwm1  = &IMXRT_FLEXPWM1;

    flexpwm4->FCTRL0 = FLEXPWM_FCTRL0_FLVL(15); // logic high = fault
	flexpwm4->FSTS0 = 0x000F; // clear fault status
	flexpwm4->FFILT0 = 0;

    flexpwm3->FCTRL0 = FLEXPWM_FCTRL0_FLVL(15); // logic high = fault
	flexpwm3->FSTS0 = 0x000F; // clear fault status
	flexpwm3->FFILT0 = 0;

    flexpwm1->FCTRL0 = FLEXPWM_FCTRL0_FLVL(15); // logic high = fault
	flexpwm1->FSTS0 = 0x000F; // clear fault status
	flexpwm1->FFILT0 = 0;

    
    flexpwm1->MCTRL |= FLEXPWM_MCTRL_CLDOK(1 << 3);
    flexpwm3->MCTRL |= FLEXPWM_MCTRL_CLDOK(1 << 1);
    flexpwm4->MCTRL |= FLEXPWM_MCTRL_CLDOK(1 << 2);


    flexpwm1->SM[3].CTRL = FLEXPWM_SMCTRL_FULL | FLEXPWM_SMCTRL_COMPMODE | FLEXPWM_SMCTRL_PRSC(prescale);
    flexpwm3->SM[1].CTRL = FLEXPWM_SMCTRL_FULL | FLEXPWM_SMCTRL_COMPMODE | FLEXPWM_SMCTRL_PRSC(prescale);
    flexpwm4->SM[2].CTRL = FLEXPWM_SMCTRL_FULL | FLEXPWM_SMCTRL_COMPMODE | FLEXPWM_SMCTRL_PRSC(prescale);

    flexpwm1->SM[3].CTRL2 |= FLEXPWM_SMCTRL2_FORCE_SEL(1)  | FLEXPWM_SMCTRL2_FRCEN  | FLEXPWM_SMCTRL2_FORCE;
    flexpwm3->SM[1].CTRL2 |= FLEXPWM_SMCTRL2_FORCE_SEL(1)  | FLEXPWM_SMCTRL2_FRCEN  | FLEXPWM_SMCTRL2_FORCE;
    flexpwm4->SM[2].CTRL2 |= FLEXPWM_SMCTRL2_FORCE_SEL(1)  | FLEXPWM_SMCTRL2_FRCEN  | FLEXPWM_SMCTRL2_FORCE;
       

	flexpwm1->SM[3].VAL1 = newdiv - 1;
    flexpwm3->SM[3].VAL1 = newdiv - 1;
    flexpwm4->SM[2].VAL1 = newdiv - 1;


    flexpwm4->SM[2].VAL2 = DEADTIME;
    flexpwm3->SM[1].VAL2 = DEADTIME;
    flexpwm1->SM[3].VAL2 = DEADTIME;


    flexpwm4->SM[2].VAL3 = DEADTIME;//this sets pwm for flexpwm1
    flexpwm3->SM[1].VAL3 = DEADTIME;//this sets pwm for flexpwm1
    flexpwm1->SM[3].VAL3 = DEADTIME;//this sets pwm for flexpwm1
    


    flexpwm3->SM[1].VAL4 = flexpwm3->SM[1].VAL3 + DEADTIME;
    flexpwm1->SM[3].VAL4 = flexpwm1->SM[3].VAL3 + DEADTIME;
    flexpwm4->SM[2].VAL4 = flexpwm4->SM[2].VAL3 + DEADTIME;

    MAX_DUTY  = (uint16_t)(newdiv - DEADTIME);


    //Reenable clocks



 
     *(portConfigRegister(2)) = 1;
    *(portConfigRegister(3)) = 1;
    *(portConfigRegister(28)) = 1;
    *(portConfigRegister(29)) = 1;
    *(portConfigRegister(7)) = 6;
    *(portConfigRegister(8)) = 6;


 


    phaseTimer.priority(0);
    phaseTimer.begin(startPhasing,  1630);


    
    


}


const short FTM0_MIDPOINT = 2048;

void T3PWM::setPhases(unsigned short phase_u, unsigned short phase_v, unsigned short phase_w) { // uint16_t's ? 0 - 2048 -> shouldn't you just have to write to one ch, as other is compliment?

    if(phase_u <= DEADTIME){phase_u = DEADTIME;}
    if(phase_v <= DEADTIME){phase_v = DEADTIME;}
    if(phase_w <= DEADTIME){phase_w = DEADTIME;}

    if(phase_u >= MAX_DUTY){phase_u = MAX_DUTY;}
    if(phase_v >= MAX_DUTY){phase_v = MAX_DUTY;}
    if(phase_w >= MAX_DUTY){phase_w = MAX_DUTY;}
    
    flexpwm4->SM[2].VAL3 = phase_u;//this sets pwm for flexpwm1
    flexpwm3->SM[1].VAL3 = phase_v;//this sets pwm for flexpwm1
    flexpwm1->SM[3].VAL3 = phase_w;//this sets pwm for flexpwm1


    flexpwm4->SM[2].VAL4 = flexpwm4->SM[2].VAL3 + DEADTIME;    
    flexpwm3->SM[1].VAL4 = flexpwm3->SM[1].VAL3 + DEADTIME;
    flexpwm1->SM[3].VAL4 = flexpwm1->SM[3].VAL3 + DEADTIME;

    if(flexpwm3->SM[1].VAL4 >= newdiv){flexpwm3->SM[1].VAL4 = newdiv-10;}
    if(flexpwm1->SM[3].VAL4 >= newdiv){flexpwm1->SM[3].VAL4 = newdiv-10;}
    if(flexpwm4->SM[2].VAL4 >= newdiv){flexpwm4->SM[2].VAL4 = newdiv-10;}

    flexpwm1->MCTRL |= FLEXPWM_MCTRL_LDOK(1 << 3) ;
    flexpwm3->MCTRL |= FLEXPWM_MCTRL_LDOK(1 << 1) ;
    flexpwm4->MCTRL |= FLEXPWM_MCTRL_LDOK(1 << 2) ;
}



void T3PWM::setBrake(uint16_t strength)  // PWM abschalten
{

}

void T3PWM::setPhasesLow()  // PWM abschalten
{
    setPhases(0,0,0);
}



Main:

Code:
#include <Arduino.h>
#include "T3/t3pwm.h"
#include "T3/t3adc.h"

  T3PWM *t3pwm;
  T3ADC *t3adc;
void setup() {
  // put your setup code here, to run once:
  t3pwm= new T3PWM();
  t3pwm->init();
  t3adc = new T3ADC();
  t3adc->init();
  pinMode(13,OUTPUT);
  pinMode(A5,INPUT_PULLDOWN);
  pinMode(A4,INPUT_PULLDOWN);
  
  delay(1000);
}

int x = 0;
bool a = 0;
void loop() {
  if(x == 64000 || x <= 0){
    a = !a;
    digitalWrite(13,!digitalRead(13));
  }
  if(a){
    x+= 10;
  }else{
    x-= 10;
  }
  delay(1);
  Serial.print(t3adc->rbav->latest());
  Serial.print(" : ");
  Serial.println(t3adc->rbaw->latest());
  
  t3pwm->setPhases(x,x,x);
    // flexpwmWrite(&IMXRT_FLEXPWM4,2,1,(uint16_t) 50);
    
  // put your main code here, to run repeatedly:
}


I have tried many things but none with much luck
 
Sorry, right now I am busy with some other stuff..

A couple of us did some stuff to figure out some of it back before @pedvide helped out and integrated some of it back into the main ADC library.

A couple of us had the starts of a WIP library ADCL_t4 (probably still up on my github account).
And then had several test sketches to figure things out...

Example sketch:
Code:
/*
    Warning this is T4 specific, although not sure how much...

    This one is really hacked up to try to use qtimer0/XBar/ADC_ETC to
    control the timing of the query!
*/

#include <ADCL_t4.h>
#include <DMAChannel.h>
#include <AnalogBufferDMA.h>
#include "imxrt_additions.h"

// This version uses both ADC1 and ADC2, try with only one Qtimer.
const int readPin_adc_0 = 14;
const int readPin_adc_1 = 26;

ADCL *adc = new ADCL(); // adc object

extern void dumpDMA_TCD(DMABaseClass *dmabc);
elapsedMillis elapsed_sinc_last_display;

// Going to try two buffers here  using 2 dmaSettings and a DMAChannel

const uint32_t buffer_size = 1600;
DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff1[buffer_size];
DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff2[buffer_size];
AnalogBufferDMA abdma1(dma_adc_buff1, buffer_size, dma_adc_buff2, buffer_size);

DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff2_1[buffer_size];
DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff2_2[buffer_size];
AnalogBufferDMA abdma2(dma_adc_buff2_1, buffer_size, dma_adc_buff2_2, buffer_size);

void setup() {
  while (!Serial && millis() < 5000) ;

  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(readPin_adc_0, INPUT); // Not sure this does anything for us
  pinMode(readPin_adc_1, INPUT); 

  Serial.begin(9600);
  Serial.println("Setup both ADCs");
  // reference can be ADC_REFERENCE::REF_3V3, ADC_REFERENCE::REF_1V2 (not for Teensy LC) or ADC_REF_EXT.
  //adc->setReference(ADC_REFERENCE::REF_1V2, ADC_0); // change all 3.3 to 1.2 if you change the reference to 1V2

  // Setup both ADCs
  adc->setAveraging(8); // set number of averages
  adc->setResolution(12); // set bits of resolution
  adc->setAveraging(8, ADC_1); // set number of averages
  adc->setResolution(12, ADC_1); // set bits of resolution


  // always call the compare functions after changing the resolution!
  //adc->enableCompare(1.0/3.3*adc->getMaxValue(ADC_0), 0, ADC_0); // measurement will be ready if value < 1.0V
  //adc->enableCompareRange(1.0*adc->getMaxValue(ADC_1)/3.3, 2.0*adc->getMaxValue(ADC_1)/3.3, 0, 1, ADC_1); // ready if value lies out of [1.0,2.0] V

  // enable DMA and interrupts
  Serial.println("before enableDMA"); Serial.flush();


  // setup a DMA Channel.
  // Now lets see the different things that RingbufferDMA setup for us before
  abdma1.init(adc, ADC_0/*, DMAMUX_SOURCE_ADC_ETC*/);
  abdma2.init(adc, ADC_1/*, DMAMUX_SOURCE_ADC_ETC*/);
  Serial.println("After enableDMA"); Serial.flush();

  // Start the dma operation..
  setup_adc_hardware_trigger(readPin_adc_0, readPin_adc_1);
  // Lets again try dumping lots of data.
  Serial.println("\n*** DMA structures for ADC_0 ***");
  dumpDMA_TCD(&(abdma1._dmachannel_adc));
  dumpDMA_TCD(&(abdma1._dmasettings_adc[0]));
  dumpDMA_TCD(&(abdma1._dmasettings_adc[1]));
  Serial.println("\n*** DMA structures for ADC_1 ***");
  dumpDMA_TCD(&(abdma2._dmachannel_adc));
  dumpDMA_TCD(&(abdma2._dmasettings_adc[0]));
  dumpDMA_TCD(&(abdma2._dmasettings_adc[1]));
  Serial.println("\n*** ADC and ADC_ETC ***");
  Serial.printf("ADC1: HC0:%x HS:%x CFG:%x GC:%x GS:%x\n", ADC1_HC0, ADC1_HS,  ADC1_CFG, ADC1_GC, ADC1_GS);
  Serial.printf("ADC2: HC0:%x HS:%x CFG:%x GC:%x GS:%x\n", ADC2_HC0, ADC2_HS,  ADC2_CFG, ADC2_GC, ADC2_GS);
  Serial.printf("ADC_ETC: CTRL:%x DMA: %x\n", IMXRT_ADC_ETC.CTRL, IMXRT_ADC_ETC.DMA_CTRL);
  for (uint8_t trig = 0; trig < 8; trig++) {
    Serial.printf("    TRIG[%d] CTRL: %x CHAIN_1_0:%x\n",
                  trig, IMXRT_ADC_ETC.TRIG[trig].CTRL, IMXRT_ADC_ETC.TRIG[trig].CHAIN_1_0);
  }

  Serial.println("End Setup");
  elapsed_sinc_last_display = 0;
}

char c = 0;

int average_value = 2048;

void loop() {

  // Maybe only when both have triggered?
  if ( abdma1.interrupted() && (abdma2.interrupted())) {
    if ( abdma1.interrupted()) {
      ProcessAnalogData(&abdma1, 0);
    }
    if ( abdma2.interrupted()) {
      ProcessAnalogData(&abdma2, 1);
    }
    Serial.println();
    elapsed_sinc_last_display = 0;
  }
  if (elapsed_sinc_last_display > 5000) {
    // Nothing in 5 seconds, show a heart beat.
    digitalWriteFast(13, HIGH);
    delay(250);
    digitalWriteFast(13, LOW);
    delay(250);
    digitalWriteFast(13, HIGH);
    delay(250);
    digitalWriteFast(13, LOW);
    elapsed_sinc_last_display = 0;
  }
}

void ProcessAnalogData(AnalogBufferDMA *pabdma, int8_t adc_num) {
  uint32_t sum_values = 0;
  uint16_t min_val = 0xffff;
  uint16_t max_val = 0;

  volatile uint16_t *pbuffer = pabdma->bufferLastISRFilled();
  volatile uint16_t *end_pbuffer = pbuffer + pabdma->bufferCountLastISRFilled();

  float sum_delta_sq = 0.0;
  if ((uint32_t)pbuffer >= 0x20200000u)  arm_dcache_delete((void*)pbuffer, sizeof(dma_adc_buff1));
  while (pbuffer < end_pbuffer) {
    if (*pbuffer < min_val) min_val = *pbuffer;
    if (*pbuffer > max_val) max_val = *pbuffer;
    sum_values += *pbuffer;
    int delta_from_center = (int) * pbuffer - average_value;
    sum_delta_sq += delta_from_center * delta_from_center;

    pbuffer++;
  }

  int rms = sqrt(sum_delta_sq / buffer_size);
  Serial.printf(" %d - %u(%u): %u <= %u <= %u %d ", adc_num, pabdma->interruptCount(), pabdma->interruptDeltaTime(), min_val,
                sum_values / buffer_size, max_val, rms);
  pabdma->clearInterrupt();
}

// try to use some teensy core functions...
// mainly out of pwm.c
extern "C" {
  extern void xbar_connect(unsigned int input, unsigned int output);
  extern void quadtimer_init(IMXRT_TMR_t *p);
  extern void quadtimerWrite(IMXRT_TMR_t *p, unsigned int submodule, uint16_t val);
  extern void quadtimerFrequency(IMXRT_TMR_t *p, unsigned int submodule, float frequency);
}

//----------------------------------------------------------------------
// xbar_init - does both ADC1 and ADC2 connection
//              as two different ADC_ETC channels
//----------------------------------------------------------------------
void xbar_init() {
  CCM_CCGR2 |= CCM_CCGR2_XBAR1(CCM_CCGR_ON);   //turn clock on for xbara1

  // See if I can setup XBAR with one source goint to two different destinations
  xbar_connect(XBARA1_IN_QTIMER4_TIMER0, XBARA1_OUT_ADC_ETC_TRIG10);   // pit to adc_etc
  xbar_connect(XBARA1_IN_QTIMER4_TIMER0, XBARA1_OUT_ADC_ETC_TRIG00);   // pit to adc_etc
}

//----------------------------------------------------------------------
// adc_init - sets both ADC1 and ADC2 to hardware trigger and
//            use ADC_ETC as the channel 0 and remove continuous
//----------------------------------------------------------------------
void adc_init() {
  // ADC1
  ADC1_CFG |= ADC_CFG_ADTRG;   // hardware trigger
  ADC1_HC0 = 16;   // ADC_ETC channel
  ADC1_GC &= ~ADC_GC_ADCO;

  // ADC2
  ADC2_CFG |= ADC_CFG_ADTRG;   // hardware trigger
  ADC2_HC0 = 16;   // ADC_ETC channel
  ADC2_GC &= ~ADC_GC_ADCO;
}

//----------------------------------------------------------------------
// adc_etc_init - initialize the adc_etc currently using different
//                channel for both pins
//----------------------------------------------------------------------
void adc_etc_init(uint8_t pin_0, uint8_t pin_1) {
  //  first do soft reset and/or let complete
  IMXRT_ADC_ETC.CTRL = ADC_ETC_CTRL_SOFTRST; // SOFTRST
  IMXRT_ADC_ETC.CTRL &= ~ADC_ETC_CTRL_SOFTRST; // SOFTRST
  delay(5);

  // Now lets process ADC1(ADC_0) pin...

  // ADC1 (ADC_0)
  uint8_t adc_pin_channel = ADCL::mapPinToChannel(pin_0, ADC_0);
  if (adc_pin_channel == 0xff) Serial.printf("ADC_ETC_INIT: pin:%d did not map to channnel on %d\n", pin_0, ADC_0);
  IMXRT_ADC_ETC.CTRL = (ADC_ETC_CTRL_DMA_MODE_SEL | ADC_ETC_CTRL_TRIG_ENABLE(1)); // 0x40000001;  // start with trigger 0
  Serial.printf("ADC_ETC_CTRL: %x %x\n",  IMXRT_ADC_ETC.CTRL, ADC_ETC_CTRL_TRIG_ENABLE(1));
  IMXRT_ADC_ETC.TRIG[0].CTRL = ADC_ETC_TRIG_CTRL_TRIG_CHAIN(0);   // chainlength -1 only us
  IMXRT_ADC_ETC.TRIG[0].CHAIN_1_0 =
    ADC_ETC_TRIG_CHAIN_IE0(1) /*| ADC_ETC_TRIG_CHAIN_B2B0 */
    | ADC_ETC_TRIG_CHAIN_HWTS0(1) | ADC_ETC_TRIG_CHAIN_CSEL0(adc_pin_channel) ;

  IMXRT_ADC_ETC.DMA_CTRL = ADC_ETC_DMA_CTRL_TRIQ_ENABLE(0);

  // Now lets do ADC2(ADC_1)
  adc_pin_channel = ADCL::mapPinToChannel(pin_1, ADC_1);
  if (adc_pin_channel == 0xff) Serial.printf("ADC_ETC_INIT: pin:%d did not map to channnel on %d\n", pin_1, ADC_1);
  // ADC2 (ADC_1)
  // Guess that 4 trigger is for ADC2?
  IMXRT_ADC_ETC.CTRL |= ADC_ETC_CTRL_TRIG_ENABLE(1 << 4); // Add in our trigger
  Serial.printf("ADC_ETC_CTRL: %x\n",  IMXRT_ADC_ETC.CTRL);
  IMXRT_ADC_ETC.TRIG[4].CTRL = ADC_ETC_TRIG_CTRL_TRIG_CHAIN(0);   // chainlength -1 only us
  IMXRT_ADC_ETC.TRIG[4].CHAIN_1_0 =
    ADC_ETC_TRIG_CHAIN_IE0(1) /*| ADC_ETC_TRIG_CHAIN_B2B0 */
    | ADC_ETC_TRIG_CHAIN_HWTS0(1) | ADC_ETC_TRIG_CHAIN_CSEL0(adc_pin_channel) ;

  IMXRT_ADC_ETC.DMA_CTRL |= ADC_ETC_DMA_CTRL_TRIQ_ENABLE(4);
}

//----------------------------------------------------------------------
// qtimer_init: init to specified frequency
//----------------------------------------------------------------------
void  qtimer_init(float freq)
{
  Serial.println("Try to init QTimer"); Serial.flush();
  quadtimer_init(&IMXRT_TMR4);
  quadtimerFrequency(&IMXRT_TMR4, 0, freq);
  quadtimerWrite(&IMXRT_TMR4, 0, 5);

  Serial.println("After Qtimer init"); Serial.flush();

}


//----------------------------------------------------------------------
// setup_adc_hardware_trigger - do the work to setup a timer to trigger
//          the DMA reads that happen at some specified frequency.
//----------------------------------------------------------------------
// We have both sets of data here.
void setup_adc_hardware_trigger(uint8_t pin_0, uint8_t pin_1)
{
  xbar_init();
  Serial.println("After XBAR"); Serial.flush();
  adc_init();
  adc_etc_init(pin_0, pin_1);

  Serial.println("After ADC/ADC_ETC"); Serial.flush();
  qtimer_init(3000.0);  // try at 3000 hz for test...


}

If it were me, I would try looking carefully at all of the flags for example you setup in ADC_ETC to see if they are correct.

Example in the above sketch we have:
Code:
  IMXRT_ADC_ETC.TRIG[0].CTRL = ADC_ETC_TRIG_CTRL_TRIG_CHAIN(0);   // chainlength -1 only us

But it looks like you have a ADC_ETC_TRIG_CTRL_TRIG_CHAIN(1);

Sorry I don't have the energy to go through and check the full thing out. It took a lot of trial and error to figure out what works.

Also during that time, there were others helping out as well, plus other test sketches, like the user @manitou has up in the project https://github.com/manitou48/teensy4 that gave us lots of hints.

Hope that helps a little.
 
So i have changed my adc class to the following using allot of your example code, which seems to be taked partially from the same sources as i had as i had indeed seen manitou's github page when i was trying to implement this.
My adc conversion works better than first but still the same problem occur's, I can get perfect working adc's conversions working when using the quadtimer everyone seems to be using, but as soon as i switch to flexpwm trigger, it does not work no matter how hard i try. Im offering 5$ for anyone who can help me get flexpwm to initiate a trigger, the rest of my code seems to work fine otherwise.
It does adc conversions when quadtimer pulses but when xbar is setup to read from any of my flexpwm modules it fails even though there isnt much configuring in the trigger output of flexpwm which is more confusing :(

new adc class:
Code:
#include <T3/t3adc.h>
#include "Drive/foc.h"
#define PRREG(x) Serial.print(#x" 0x"); Serial.println(x,HEX)
ADC *adc = new ADC();
T3ADC* T3ADC::instance;
T3ADC::T3ADC(){
  instance = this;
}


void adc0_isr(){
    T3ADC::instance->rbaw->push((uint16_t)adc->adc0->readSingle()); // adc result data register
}


void adc1_isr(){
    T3ADC::instance->rbaw->push((uint16_t)adc->adc1->analogReadContinuous()); // adc result data register
}

extern "C" {
    extern void xbar_connect(unsigned int input, unsigned int output);
    extern void quadtimer_init(IMXRT_TMR_t *p);
    extern void quadtimerWrite(IMXRT_TMR_t *p, unsigned int submodule, uint16_t val);
    extern void quadtimerFrequency(IMXRT_TMR_t *p, unsigned int submodule, float frequency);
}

void adcetc0_isr() {
  ADC_ETC_DONE0_1_IRQ |= 1;   // clear
  uint16_t x = ADC_ETC_TRIG0_RESULT_1_0 & 4095;
  T3ADC::instance->rbav->push(x);
  asm("dsb");
}

void adcetc1_isr() {
  ADC_ETC_DONE0_1_IRQ |= 1 << 16;   // clear
  // uint16_t x = (ADC_ETC_TRIG4_RESULT_1_0 >> 16) & 4095;
  uint16_t x = ADC_ETC_TRIG4_RESULT_1_0 & 4095;
  T3ADC::instance->rbaw->push(x);
  asm("dsb");
}

void adc_init() {
  // init and calibrate with help from core
  analogReadResolution(10);
  analogRead(A4);
  analogRead(A5);

  ADC1_CFG |= ADC_CFG_ADTRG;   // hardware trigger
  ADC1_HC0 = 16;   // ADC_ETC channel
  ADC1_GC &= ~ADC_GC_ADCO;

  // ADC2
  ADC2_CFG |= ADC_CFG_ADTRG;   // hardware trigger
  ADC2_HC0 = 16;   // ADC_ETC channel
  ADC2_GC &= ~ADC_GC_ADCO;
  
}

void adc_etc_init() {
  IMXRT_ADC_ETC.CTRL = ADC_ETC_CTRL_SOFTRST; // SOFTRST
  IMXRT_ADC_ETC.CTRL &= ~ADC_ETC_CTRL_SOFTRST; // SOFTRST
  delay(5);

  IMXRT_ADC_ETC.CTRL = (ADC_ETC_CTRL_DMA_MODE_SEL | ADC_ETC_CTRL_TRIG_ENABLE(1)); // 0x40000001;  // start with trigger 0
  Serial.printf("ADC_ETC_CTRL: %x %x\n",  IMXRT_ADC_ETC.CTRL, ADC_ETC_CTRL_TRIG_ENABLE(1));
  IMXRT_ADC_ETC.TRIG[0].CTRL = ADC_ETC_TRIG_CTRL_TRIG_CHAIN(0);   // chainlength -1 only us
  IMXRT_ADC_ETC.TRIG[0].CHAIN_1_0 =
    ADC_ETC_TRIG_CHAIN_IE0(1) /*| ADC_ETC_TRIG_CHAIN_B2B0 */
    | ADC_ETC_TRIG_CHAIN_HWTS0(1) | ADC_ETC_TRIG_CHAIN_CSEL0(5) ;

  IMXRT_ADC_ETC.DMA_CTRL = ADC_ETC_DMA_CTRL_TRIQ_ENABLE(0);


  // ADC2 (ADC_1)
  // Guess that 4 trigger is for ADC2?
  IMXRT_ADC_ETC.CTRL |= ADC_ETC_CTRL_TRIG_ENABLE(1 << 4); // Add in our trigger
  Serial.printf("ADC_ETC_CTRL: %x\n",  IMXRT_ADC_ETC.CTRL);
  IMXRT_ADC_ETC.TRIG[4].CTRL = ADC_ETC_TRIG_CTRL_TRIG_CHAIN(0);   // chainlength -1 only us
  IMXRT_ADC_ETC.TRIG[4].CHAIN_1_0 =
    ADC_ETC_TRIG_CHAIN_IE0(2) 
    | ADC_ETC_TRIG_CHAIN_HWTS0(1) | ADC_ETC_TRIG_CHAIN_CSEL0(6) ;

  IMXRT_ADC_ETC.DMA_CTRL |= ADC_ETC_DMA_CTRL_TRIQ_ENABLE(4);

  attachInterruptVector(IRQ_ADC_ETC0, adcetc0_isr);
  NVIC_ENABLE_IRQ(IRQ_ADC_ETC0);
  attachInterruptVector(IRQ_ADC_ETC1, adcetc1_isr);
  NVIC_ENABLE_IRQ(IRQ_ADC_ETC1);

}


void xbar_init() {
  CCM_CCGR2 |= CCM_CCGR2_XBAR1(CCM_CCGR_ON);   //turn clock on for xbara1
  // xbar_connect(XBARA1_IN_FLEXPWM4_PWM2_OUT_TRIG0 , XBARA1_OUT_ADC_ETC_TRIG00); 
    xbar_connect(XBARA1_IN_QTIMER4_TIMER3, XBARA1_OUT_ADC_ETC_TRIG10);   // pit to adc_etc
  xbar_connect(XBARA1_IN_QTIMER4_TIMER3, XBARA1_OUT_ADC_ETC_TRIG00);   // pit to adc_etc 
  // xbar_connect(XBARA1_IN_QTIMER4_TIMER3 , XBARA1_OUT_ADC_ETC_TRIG01);
}


void T3ADC::init(){
  this->rbav = new RingBuffer();
  this->rbaw = new RingBuffer();
  



  adc0_flag = false;
  adc1_flag = false;

  // adc->adc0->enableInterrupts(adc0_isr);
  // adc->adc1->enableInterrupts(adc1_isr);

  // adc->adc0->setResolution(10);
  // adc->adc1->setResolution(10);

  // adc->adc0->setHardwareTrigger();
  // adc->adc1->setHardwareTrigger();


  // adc->adc0->setAveraging(4);
  // adc->adc1->setAveraging(4);
  
  // adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED);
  // adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED);

  // adc->adc1->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED);
  // adc->adc1->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED);
  
  // adc->adc0->wait_for_cal();
  // adc->adc1->wait_for_cal();

  // adc->adc0->enableCompare(100,true);
  // adc->adc1->enableCompare(100,true);
  
  // adc->adc0->startSingleRead(A5);
  // adc->adc1->startContinuous(A4);

  // adc->adc0->startSingleRead(A5); // call this to setup everything before the Timer starts, differential is also possible
  xbar_init();
  adc_init();
  adc_etc_init();
  // adc->adc0->startflexpwmTrigger(XBARA1_IN_FLEXPWM4_PWM2_OUT_TRIG0); //frequency in Hz

    // Now init the QTimer.
    // Extracted from quadtimer_init in pwm.c but only the one channel...
    // Maybe see if we have to do this every time we call this.  But how often is that? 
    IMXRT_TMR4.CH[3].CTRL = 0; // stop timer
    IMXRT_TMR4.CH[3].CNTR = 0;
    IMXRT_TMR4.CH[3].SCTRL = TMR_SCTRL_OEN | TMR_SCTRL_OPS | TMR_SCTRL_VAL | TMR_SCTRL_FORCE;
    IMXRT_TMR4.CH[3].CSCTRL = TMR_CSCTRL_CL1(1) | TMR_CSCTRL_ALT_LOAD;
    // COMP must be less than LOAD - otherwise output is always low
    IMXRT_TMR4.CH[3].LOAD = 24000;   // low time  (65537 - x) - 
    IMXRT_TMR4.CH[3].COMP1 = 0;  // high time (0 = always low, max = LOAD-1)
    IMXRT_TMR4.CH[3].CMPLD1 = 0;
    IMXRT_TMR4.CH[3].CTRL = TMR_CTRL_CM(1) | TMR_CTRL_PCS(8) |
        TMR_CTRL_LENGTH | TMR_CTRL_OUTMODE(6);

    quadtimerFrequency(&IMXRT_TMR4, 3, 500);
    quadtimerWrite(&IMXRT_TMR4, 3, 5);
}
 
On page 3029(chip specific flexpwm info) of the imxrt1060 doc linked in the pjrc site it mentions this

In this device, PWMx_EXTB is not applicable. It also does not
support external ADC input. Also there is No NanoEdge
placement block in this device.

Does this mean the flexpwm does not support adc sample trigger as an input to enable pwm or does this mean pwm cant initiate an adc reading?
That would basically render this teensy 4 useless to me :(
 
Hi Bzaz,

I have read the doc you are talking about, actually i have list of docs you might find usefull too and i think we should co-operate on this since our end goal is somewhat similiar, Just different between 6 step and foc commutation.

Docs i found:

https://www.nxp.com/docs/en/application-note/AN4675.pdf
https://www.nxp.com/docs/en/nxp/application-notes/AN12200.pdf
https://github.com/PaulStoffregen/cores/blob/61750d5c1fb3795cac8ce55f10b0ccf8a8c5b8d3/teensy4/pwm.c
https://community.nxp.com/thread/469445
https://www.nxp.com/docs/en/application-note/AN12169.pdf

Now, what exactly do you mean with the source code is available from the nxp site? I havent found any source code?
 
I am building my own code from scratch and I don't need complementary outputs so didn't try.

By the way I have already posted few video on my project:
 
Oh thats really cool! i actually have made myself a cnc mill which could use your fabulous technology!
If you get around the adc sampling problem, please let me know as i have yet to uncifer the source code.
 
I have tried so much yet i always circle back to where i started, at this point i am going to manually using an intervaltimer synchronize a quadtimer with a flexpwm counter at hopefully the same frequency, the micro will be running at most 2 hours straight and i dont think the counters will desynchronize in the mean time. If anyone sees a problem with this janky solution, please explain to me what could go wrong. But at this stage, it has so far been the most accurate way i have managed to get adc triggering at pwm frequency. I really wish i had bought a T3.2 instead of the 4 now
 
Okay so with some testing, using an intervaltimer based delay which i currently manually adjusted till i got right readings, i am able to read the pwm output at peaks of 10/1024 which is actually quite incredible and since the quadtimer and flexpwm are called from the same clock source and using same math to get the frequency it should and as far as i can see, read stable at even very low outputs. The only problem with this janky solution is getting the synchronization right to read in the peak of the pwm signal. But even if this was done the correct flexpwm output trigger method, due to several causes of delay it would also not be exactly at peak, which is why i think my next goal will outperform the ordinary way of using flexpwm to trigger the adc, as i am pretty sure i am able to make an auto aligning code which at startup wil always center the reading on the pwm peak, no matter the external delays!
 
I have written this interval timer which will disable and enable the quadtimers until it reads a high pulse when the flexpwm generator is set to 5%, it works really well and since it is meassuring the actual current and matching that to the pwm signal so any external delays will be eliminated.
Code:
IntervalTimer phaseTimer;
void startPhasing(){
    if(calibratingv){
        uint16_t latestv = T3ADC::instance->rbav->latest();
        if(latestv >= 50 && latestv <= 2000){
            calibratingv = false;
        }
        IMXRT_TMR4.CH[3].CTRL = 0;
        IMXRT_TMR4.CH[3].CTRL = TMR_CTRL_CM(1) | TMR_CTRL_PCS(8) |
            TMR_CTRL_LENGTH | TMR_CTRL_OUTMODE(6);
        
    }else if(calibratingw){
        uint16_t latestw = T3ADC::instance->rbaw->latest();
        if(latestw >= 50 && latestw <= 2000){
            calibratingw = false;
        }
        IMXRT_TMR4.CH[2].CTRL = 0;
        IMXRT_TMR4.CH[2].CTRL = TMR_CTRL_CM(1) | TMR_CTRL_PCS(8) |
            TMR_CTRL_LENGTH | TMR_CTRL_OUTMODE(6);
    }else{
        phaseTimer.end();
    }
}
 
I will soon after cleaning up my code post a reply here with some more detailed info, currently i have still not yet been able to make flexpwm trigger the adc sample but i have managed to enable complementary mode, Somewhere in the code it sets the independant mode to enable, however there is no mask to disable it, so you have to edit the registers manually to set it back, which is done by the following line of code for anyone interested:

Code:
    //enable complementary signals
    flexpwm4->SM[2].CTRL2 &= ~(1UL << 13);
    flexpwm3->SM[1].CTRL2 &= ~(1UL << 13);
    flexpwm1->SM[3].CTRL2 &= ~(1UL << 13);
 
I manage to have something that seems to works. I just checked that commenting or playing with the 2 lines below trigger or not the ADC

FLEXPWM2_SM0TCTRL = FLEXPWM_SMTCTRL_OUT_TRIG_EN(4);// val 4 of Flexpwm sm0 as trigger
xbar_connect(XBARA1_IN_FLEXPWM2_PWM1_OUT_TRIG0,XBARA1_OUT_ADC_ETC_TRIG00); //FlexPWM to adc_etc


Code:
#include <Arduino.h>
#include "imxrt.h"

// from SDK PIT XBAR ADC_ETC ADC
// chain A0 A1   AD_B1_02  AD_B1_03   ADC1 IN7 8
#define PRREG(x) Serial.print(#x" 0x"); Serial.println(x,HEX)
volatile uint32_t val0, val1;
uint16_t PWM_SM0_Div = 0; // divider set to 0
uint16_t PWM_range = 2048; // PWM sweep from -2048 to 2047
uint16_t PWM_pulsewidth = 1024; //


void adcetc0_isr() {
  ADC_ETC_DONE0_1_IRQ |= 1;   // clear
  val0 = ADC_ETC_TRIG0_RESULT_1_0 & 4095;
  asm("dsb");
}

void adcetc1_isr() {
  ADC_ETC_DONE0_1_IRQ |= 1 << 16;   // clear
  val1 = (ADC_ETC_TRIG0_RESULT_1_0 >> 16) & 4095;
  asm("dsb");
}

void adc_init() {
  // init and calibrate with help from core
  analogReadResolution(12);
  analogRead(0);
  analogRead(1);
  ADC1_CFG |= ADC_CFG_ADTRG;   // hardware trigger
  // ADC1_CFG = 0x200b;
  ADC1_HC0 = 16;   // ADC_ETC channel
  ADC1_HC1 = 16;
}

void adc_etc_init() {
  ADC_ETC_CTRL &= ~(1 << 31); // SOFTRST
  ADC_ETC_CTRL = 0x40000001;  // start with trigger 0
  ADC_ETC_TRIG0_CTRL = 0x100;   // chainlength -1
  ADC_ETC_TRIG0_CHAIN_1_0 = 0x50283017;   // ADC1 7 8, chain channel, HWTS, IE, B2B
  attachInterruptVector(IRQ_ADC_ETC0, adcetc0_isr);
  NVIC_ENABLE_IRQ(IRQ_ADC_ETC0);
  attachInterruptVector(IRQ_ADC_ETC1, adcetc1_isr);
  NVIC_ENABLE_IRQ(IRQ_ADC_ETC1);
}

void xbar_connect(unsigned int input, unsigned int output)
{
  if (input >= 88) return;
  if (output >= 132) return;
  volatile uint16_t *xbar = &XBARA1_SEL0 + (output / 2);
  uint16_t val = *xbar;
  if (!(output & 1)) {
    val = (val & 0xFF00) | input;
  } else {
    val = (val & 0x00FF) | (input << 8);
  }
  *xbar = val;
}

void xbar_init() {
  CCM_CCGR2 |= CCM_CCGR2_XBAR1(CCM_CCGR_ON);   //turn clock on for xbara1
  //xbar_connect(56, 103);   // pit to adc_etc
  xbar_connect(XBARA1_IN_FLEXPWM2_PWM1_OUT_TRIG0,XBARA1_OUT_ADC_ETC_TRIG00); //FlexPWM to adc_etc
}

void flewpwm_init() {
  FLEXPWM2_MCTRL |= FLEXPWM_MCTRL_CLDOK(0x0F);//  Clear Load Okay LDOK(SM) -> no reload of PWM settings
  FLEXPWM2_SM0CTRL = FLEXPWM_SMCTRL_FULL | FLEXPWM_SMCTRL_PRSC(PWM_SM0_Div); //Full cycle update (@val1) and prescaler value
  FLEXPWM2_SM0CTRL2 = FLEXPWM_SMCTRL2_INDEP | FLEXPWM_SMCTRL2_CLK_SEL(0); //A & B independant
  //set PWM
  FLEXPWM2_SM0INIT = -PWM_range/2; // start for symetrical pulse around 0x00
  FLEXPWM2_SM0VAL0 = 0; // mid point for symetrical pulse around 0x00
  FLEXPWM2_SM0VAL1 = PWM_range/2-1; // end for symetrical pulse around 0x00
  //PWM A edges
  FLEXPWM2_SM0VAL2 = -PWM_pulsewidth; // start uf pulse on A
  FLEXPWM2_SM0VAL3 = PWM_pulsewidth-1; // end of pulse on A

  FLEXPWM2_SM0VAL4 = 0; // adc trigger

  FLEXPWM2_OUTEN |= FLEXPWM_OUTEN_PWMA_EN(0x0F); // Activate all A channels
  FLEXPWM2_OUTEN |= FLEXPWM_OUTEN_PWMB_EN(0x0F); // Activate all B channels
  FLEXPWM2_MCTRL |= FLEXPWM_MCTRL_LDOK(0x0F);// Load Okay LDOK(SM) -> reload setting again

  FLEXPWM2_SM0TCTRL = FLEXPWM_SMTCTRL_OUT_TRIG_EN(4);//  val 4 of Flexpwm sm0 as trigger
}

void pit_init(uint32_t cycles)
{
  CCM_CCGR1 |= CCM_CCGR1_PIT(CCM_CCGR_ON);
  PIT_MCR = 0;

  IMXRT_PIT_CHANNELS[0].LDVAL = cycles;
  IMXRT_PIT_CHANNELS[0].TCTRL = PIT_TCTRL_TEN;
}

void setup() {
  Serial.begin(9600);
  while (!Serial);
  delay(1000);
  xbar_init();
  adc_init();
  adc_etc_init();
  
  //pit_init(24 * 1000000);
  flewpwm_init();


  PRREG(ADC1_CFG);
  PRREG(ADC1_HC0);
  PRREG(ADC1_HC1);
  PRREG(ADC_ETC_CTRL);
  PRREG(ADC_ETC_TRIG0_CTRL);
  PRREG(ADC_ETC_TRIG0_CHAIN_1_0);
}

void loop() {
  Serial.printf("%d  %d\n", val0, val1);
  delay(2000);
}
 
Wonderful job. That saved me probably days of looking at the different registers. Routing the trigger and setting up the trigger to the ADC needs some steps, but allows many, many options.

Some small corrections:
The output for the PWM-signal is not configured.
The PWM ratio is 100%, so no modulation.
FLEXPWM_SMTCTRL_OUT_TRIG_EN(4) must be FLEXPWM_SMTCTRL_OUT_TRIG_EN(1<<4) to trigger on FLEXPWM2_SM0VAL4.
I made a version that shifts the trigger over the PWM cycle, showing the output.
There should be a connection from PIN 4 to both ADC's. The traces are not identical as the second conversion starts later.
Code:
#include <Arduino.h>
#include "imxrt.h"
#include <ADC.h>

// from SDK PIT XBAR ADC_ETC ADC
// chain A0 A1   AD_B1_02  AD_B1_03   ADC1 IN7 8
#define PRREG(x) Serial.print(#x" 0x"); Serial.println(x,HEX)
uint16_t PWM_SM0_Div = 2; // divider set to 0
uint16_t PWM_range = 500; // PWM range from 0 to 500
uint16_t PWM_pulsewidth = 250; // 50% duty cycle

volatile int New0 = 0, New1 = 0;
volatile uint32_t val0, val1;
// instantiate a new ADC object
ADC *adc = new ADC(); // adc object;

void adcetc0_isr() {
  ADC_ETC_DONE0_1_IRQ |= 1;   // clear
  val0 += ADC_ETC_TRIG0_RESULT_1_0 & 4095;
  New0++;
  asm("dsb");
}

void adcetc1_isr() {
  ADC_ETC_DONE0_1_IRQ |= 1 << 16;   // clear
  val1 += (ADC_ETC_TRIG0_RESULT_1_0 >> 16) & 4095;
  New1++;
  asm("dsb");
}

void adc_init() {
  // init and calibrate with help from core
  analogReadResolution(8);
  analogRead(0);
  analogRead(1);
  ADC1_CFG |= ADC_CFG_ADTRG;   // hardware trigger
  // ADC1_CFG = 0x200b;
  ADC1_HC0 = 16;   // ADC_ETC channel
  ADC1_HC1 = 16;
}

void adc_etc_init() {
  ADC_ETC_CTRL &= ~(1 << 31); // SOFTRST
  ADC_ETC_CTRL = 0x40000001;  // start with trigger 0; TSC_BYPASS: TSC will control ADC2 directly
  ADC_ETC_TRIG0_CTRL = 0x100;   // chainlength -1; TRIG chain length to the ADC Chain = 2
  ADC_ETC_TRIG0_CHAIN_1_0 = 0x50283017;   // ADC1 7 8, chain channel, HWTS, IE, B2B;
  // Chain 1: Finished Interrupt on Done1, Enable B2B, the next ADC trigger will be sent as soon as possible. ADC hardware trigger selection:2, ADC channel selection 8
  // Chain 0: Finished Interrupt on Done0, Enable B2B, the next ADC trigger will be sent as soon as possible. ADC hardware trigger selection:1, ADC channel selection 7
  attachInterruptVector(IRQ_ADC_ETC0, adcetc0_isr);
  NVIC_ENABLE_IRQ(IRQ_ADC_ETC0);
  attachInterruptVector(IRQ_ADC_ETC1, adcetc1_isr);
  NVIC_ENABLE_IRQ(IRQ_ADC_ETC1);
}

void xbar_connect(unsigned int input, unsigned int output)
{
  if (input >= 88) return;
  if (output >= 132) return;
  volatile uint16_t *xbar = &XBARA1_SEL0 + (output / 2);
  uint16_t val = *xbar;
  if (!(output & 1)) {
    val = (val & 0xFF00) | input;
  } else {
    val = (val & 0x00FF) | (input << 8);
  }
  *xbar = val;
}

void xbar_init() {
  CCM_CCGR2 |= CCM_CCGR2_XBAR1(CCM_CCGR_ON);   //turn clock on for xbara1
  //xbar_connect(56, 103);   // pit to adc_etc
  xbar_connect(XBARA1_IN_FLEXPWM2_PWM1_OUT_TRIG0, XBARA1_OUT_ADC_ETC_TRIG00); //FlexPWM to adc_etc
}

void flewpwm_init() {     //set PWM
  FLEXPWM2_MCTRL |= FLEXPWM_MCTRL_CLDOK(0x0F);//  Clear Load Okay LDOK(SM) -> no reload of PWM settings
  FLEXPWM2_SM0CTRL = FLEXPWM_SMCTRL_FULL | FLEXPWM_SMCTRL_PRSC(PWM_SM0_Div); //Full cycle update (@val1) and prescaler value
  FLEXPWM2_SM0CTRL2 = FLEXPWM_SMCTRL2_INDEP | FLEXPWM_SMCTRL2_CLK_SEL(0); //A & B independant
 
  FLEXPWM2_SM0INIT = 0 ; // start at 0x00
  FLEXPWM2_SM0VAL0 = 0; // mid point for symetrical pulse around 0x00 not used
  FLEXPWM2_SM0VAL1 = PWM_range-1; // end  range
  //PWM A edges
  FLEXPWM2_SM0VAL2 = FLEXPWM2_SM0INIT ; // start of pulse on A, hier bei 0
  FLEXPWM2_SM0VAL3 = FLEXPWM2_SM0INIT + PWM_pulsewidth -1; // end of pulse on A

  FLEXPWM2_SM0VAL4 = 0; // adc trigger

  FLEXPWM2_OUTEN |= FLEXPWM_OUTEN_PWMA_EN(0x0F); // Activate all A channels
  FLEXPWM2_OUTEN |= FLEXPWM_OUTEN_PWMB_EN(0x0F); // Activate all B channels
  FLEXPWM2_MCTRL |= FLEXPWM_MCTRL_LDOK(0x0F);// Load Okay LDOK(SM) -> reload setting again
  FLEXPWM2_SM0TCTRL = FLEXPWM_SMTCTRL_OUT_TRIG_EN(1 << 4); //  val 4 of Flexpwm sm0 as trigger; #define FLEXPWM_SMTCTRL_OUT_TRIG_EN(n)   ((uint16_t)(((n) & 0x3F) << 0))
}

void setup() {
  Serial.begin(250000);
  while (!Serial);
  delay(1000);
  xbar_init();
  adc_init();
  adc_etc_init();
  
  adc->adc0->setAveraging(1); // set number of averages
  adc->adc0->setResolution(8); // set bits of resolution
  adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED); // change the conversion speed
  adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED); // change the sampling speed
  flewpwm_init();
  *(portConfigRegister(4)) = 1;   // enable output pin 4
  
  PRREG(ADC1_CFG);
  PRREG(ADC1_HC0);
  PRREG(ADC1_HC1);
  PRREG(ADC_ETC_CTRL);
  PRREG(ADC_ETC_TRIG0_CTRL);
  PRREG(ADC_ETC_TRIG0_CHAIN_1_0);
}

void loop() {
  cli();      //  no interrupts during readout and clear  
  Serial.printf("  %d  %d\n", val0/New0, val1/New1);
  val0=0;     val1=0;   // Clear values for next measurement
  New0 = 0;   New1 = 0;
  sei();
  delay (5);    // allow time for averaging and output
  FLEXPWM2_MCTRL |= FLEXPWM_MCTRL_CLDOK(0x0F);//  Clear Load Okay LDOK(SM) -> no reload of PWM settings
  FLEXPWM2_SM0VAL4++; // shift adc trigger position
  if (FLEXPWM2_SM0VAL4 > PWM_range  - 1) FLEXPWM2_SM0VAL4 = 0;  // end reached  -> start at beginning
  FLEXPWM2_MCTRL |= FLEXPWM_MCTRL_LDOK(0x0F); // Load Okay LDOK(SM) -> reload setting again
}
 
Status
Not open for further replies.
Back
Top