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

Thread: Teensy 4 synchronize ADC sampling with flexPWM timing

  1. #1
    Junior Member
    Join Date
    Apr 2020
    Posts
    13

    Teensy 4 synchronize ADC sampling with flexPWM timing

    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

  2. #2
    Senior Member+ KurtE's Avatar
    Join Date
    Jan 2014
    Posts
    7,649
    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...

  3. #3
    Junior Member
    Join Date
    Apr 2020
    Posts
    13
    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

  4. #4
    Junior Member
    Join Date
    Apr 2020
    Posts
    13
    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

  5. #5
    Senior Member+ KurtE's Avatar
    Join Date
    Jan 2014
    Posts
    7,649
    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.

  6. #6
    Junior Member
    Join Date
    Apr 2020
    Posts
    13
    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);
    }

  7. #7
    Junior Member
    Join Date
    Apr 2020
    Posts
    13
    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

  8. #8
    Junior Member
    Join Date
    Apr 2020
    Posts
    5
    Hi The Law,

    I am also working on a BLCD driver (for a linear motor) and I plan to use the ADC synchronised with flexPWM.
    I am not at this point in my project but here are some doc that you might be interrested in:

    Application note from NXP:
    https://www.nxp.com/docs/en/applicat...te/AN12214.pdf

    The source code is also available on NXP web site.

    ++

    Bzaz

    EDIT:
    link to the source code:
    https://www.nxp.com/docs/en/applicat.../AN12214SW.zip

  9. #9
    Junior Member
    Join Date
    Apr 2020
    Posts
    13
    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/appl...es/AN12200.pdf
    https://github.com/PaulStoffregen/co.../teensy4/pwm.c
    https://community.nxp.com/thread/469445
    https://www.nxp.com/docs/en/applicat...te/AN12169.pdf

    Now, what exactly do you mean with the source code is available from the nxp site? I havent found any source code?

  10. #10

  11. #11
    Junior Member
    Join Date
    Apr 2020
    Posts
    13
    The url doesnt work, i think you accidentally made the ... permanent

  12. #12

  13. #13
    Junior Member
    Join Date
    Apr 2020
    Posts
    13
    Have you otherwise been able to enable complementary pwm signals instead of using val4-5 for second channel?

  14. #14
    Junior Member
    Join Date
    Apr 2020
    Posts
    5
    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:

  15. #15
    Junior Member
    Join Date
    Apr 2020
    Posts
    13
    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.

  16. #16
    Junior Member
    Join Date
    Apr 2020
    Posts
    13
    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

  17. #17
    Junior Member
    Join Date
    Apr 2020
    Posts
    13
    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!

  18. #18
    Junior Member
    Join Date
    Apr 2020
    Posts
    13
    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();
        }
    }

  19. #19
    Junior Member
    Join Date
    Apr 2020
    Posts
    13
    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);

  20. #20
    Junior Member
    Join Date
    Apr 2020
    Posts
    5
    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,XBA RA1_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);
    }

  21. #21
    Junior Member
    Join Date
    May 2020
    Posts
    3
    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
    }

Posting Permissions

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