Teensy 3.6 16bit ADC returning impossible values, please help!

Status
Not open for further replies.

RRdae

Active member
Issue: Teensy 3.6 ADC is returning incorrect values with 3.3v reference and impossible values with internal 1.2v reference (returned values greater than 16 bit). I have built a BLDC motor controller that utilizes a MAX40201FAUA amplifier IC (50V/V) to read the current across a 0.001ohm current sense resistor. The chip and resistor were sized to return the full 3.3v ADC range for a current through the resistor of up to 50 Amperes. For this issue, I was putting no more than 1.3 Amperes, verified by NI ADC, across the resistor, meaning that the voltage that the Teensy 3.6 sees should be no more than 0.0625v for this amperage. Yet the Teensy 3.6 is telling me that the MAX40201FAUA is reporting a voltage of around 1.8 volts for a 1.25A current, which should be impossible. With the Teensy ADC reference changed to 1.2v internal, the Teensy ADC reports values well in excess of 16 bit (100,000+), which as far as I know should not happen.

Even stranger, the waveform that the Teensy reports LOOKS correct when compared to the waveform that the NI ADC is seeing at the power supply, just that the magnitude is way off! Graphs of these effects are shown at the very bottom of this post.

Error Messages: None.
Hardware: Teensy 3.6 linked to and powering a MAX40201FAUA current sense amplifier, connected to a 0.001ohm high precision resistor (low impedance).
Wiring: Common ground between the Teensy 3.6 and the MAX40201. Signal out from the MAX40201FAUA connects to pin 16/A2 on the teensy. The Vdd for the MAX40201 is provided via the Teensy 3.3v supply pin. Full schematic available upon request (I think posting it up front might clutter the issue). No hardware filtering between the Teensy and the MAX40201.
Software: ADC portions attached below. I excluded most of the much larger class so that you guys aren't looking over 20 pages of code.
About the software: To synchronize the ADC capture with the PWM motor drive phases, the PWM FTM clock activates a PDB timer at the start of each PWM cycle that delays for a period of time (<PWM modulus), then triggers the ADC to read. This makes it so that the ADC is reading ONLY when current is flowing through the BLDC motor.

Code:
bool SETTING_CURRENT_SENSE_DEBUG = false;
volatile unsigned int MD_CS_raw_value = 0; //Current sense raw value carrier

#define ADC_CONFIG1 (ADC_CFG1_ADIV(0) | ADC_CFG1_MODE(3))
#define ADC_CONFIG2 (ADC_CFG2_ADHSC)

void setup() {
  // put your setup code here, to run once:

  InitializeMotor();

}

void loop() {
  // put your main code here, to run repeatedly:

}


void MD_1v0::InitializeMotor(){ //Initializes Motor and calibrates Current Sense ADC Interrupts
    ADC_Init();
    InitMotor();
    InitPDB();
}

void MD_1v0::ADC_Init(){ //Initializes ADC hardware for Current Sense Pin
    ADC0_CFG1 = ADC_CONFIG1;
    ADC0_CFG2 = ADC_CONFIG2;
    // Voltage ref vcc, hardware trigger, DMA
    ADC0_SC2 = ADC_SC2_REFSEL(1) | ADC_SC2_ADTRG; // | ADC_SC2_DMAEN;

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

    ADC_Cal(); //Calibrate ADC hardware

    // Enable ADC interrupt, configure pin
    ADC0_SC1A = ADC_SC1_AIEN | 0x8; //0x8 corresponds to Pin 16/A2
    NVIC_ENABLE_IRQ(IRQ_ADC0);
}

void MD_1v0::ADC_Cal(){  //Hardware Calibration. This is called ONLY by ADC_Init
    uint16_t ADCC_sum;

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

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

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

void MD_1v0::InitMotor(){ //Initializes Motor and Performs Current Sense ADC calibration
    //Initialize Centertap PWM at desired frequency
    analogWriteFrequency(PIN_MA,SETTING_MD_PWM_FREQUENCY);
    analogWriteFrequency(PIN_MB,SETTING_MD_PWM_FREQUENCY);
    analogWriteFrequency(PIN_MC,SETTING_MD_PWM_FREQUENCY);
    analogWriteResolution(SETTING_MD_PWM_RESOLUTION);
    analogWrite(PIN_MA,pow(2,SETTING_MD_PWM_RESOLUTION)/2);
    analogWrite(PIN_MB,pow(2,SETTING_MD_PWM_RESOLUTION)/2);
    analogWrite(PIN_MC,pow(2,SETTING_MD_PWM_RESOLUTION)/2);

    //Initialize FTM (PWM) interrupts for PDB
    //NVIC_SET_PRIORITY(IRQ_FTM0, 62); //Use to enable PWM interrupt for debug
    //NVIC_ENABLE_IRQ(IRQ_FTM0);
    FTM0_EXTTRIG = FTM_EXTTRIG_INITTRIGEN; //Enable external trigger on FTM0 Channel 0 for PDB -> corresponds to pin 22 on 3.6
    FTM0_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0);// | FTM_SC_TOIE; //Uncomment the last item to enable FTM ISR. Leave commented to only enable EXT PDB interrupt. // update status register > sys clock | div by 1 | enable TOF interrupt
}

void MD_1v0::InitPDB(){ //Initialize PDB Hardware for current sense
  //PDB initialization
  uint16_t IPDB_FTM_Period = FTM0_MOD; // /2 + 0.06*FTM0_MOD;
  SIM_SCGC6 |= SIM_SCGC6_PDB;
  PDB0_IDLY = 0; //IPDB_FTM_Period; //Delay. Corresponds to 1/2 PWM period.
  PDB0_MOD = IPDB_FTM_Period; //Timer period, defaults to 65536 (max for 16bit int
  PDB0_CH0DLY0 = 900; //IPDB_FTM_Period/2; //Formerly IPDB_FTM_Period ======See ISR calibration procedure=====
  PDB0_CH0C1 = 0x0101;

  if(SETTING_CURRENT_SENSE_DEBUG){
    //PDB interrupt enabled
    PDB0_SC |= PDB_SC_PRESCALER(0x0) | PDB_SC_TRGSEL(0x8) | PDB_SC_PDBEN | PDB_SC_PDBIE | PDB_SC_MULT(0x0) | PDB_SC_LDOK;
  }else{
    //PDB interrupt disabled
    PDB0_SC |= PDB_SC_PRESCALER(0x0) | PDB_SC_TRGSEL(0x8) | PDB_SC_PDBEN | PDB_SC_MULT(0x0) | PDB_SC_LDOK;
  }

  //For testing PDB trigger timing
  NVIC_ENABLE_IRQ(IRQ_PDB);
  //NVIC_SET_PRIORITY(IRQ_PDB, 64);
}


//Hardware Interrupts
void adc0_isr() { //Interrupt to record current sense value to variable
  if(SETTING_CURRENT_SENSE_FIR){ //FIR filtering
    MD_CS_raw_value = (1-SETTING_CURRENT_SENSE_ALPHA)*MD_CS_raw_value+SETTING_CURRENT_SENSE_ALPHA*ADC0_RA; // read ADC result to clear interrupt and store to variable
  }else{ //direct update
    MD_CS_raw_value = ADC0_RA;
  }
  if(SETTING_CURRENT_SENSE_DEBUG){
    GPIOE_PTOR = 1UL << 24; //flip pin 33 for scope diagnostic
  }
}

void pdb_isr() { //Current sense PDB interrupt. For debugging purposes only
  PDB0_SC &= ~PDB_SC_PDBIF; // clear interrupt flag
  if(SETTING_CURRENT_SENSE_DEBUG){
    GPIOE_PTOR = 1UL << 24; //flip pin 33 for scope diagnostic
  }
}


The top plot is the TOTAL current consumption for the driver (Teensy, motor and all other components). The motor drive consumes all but about 30MA of this power. It was captured via a NI ADC Card connected to a 1 ohm power resistor for current measurement. This is RMS current. I can post the raw current plot, but it is very similar with no major spikes that would imply the teensy is seeing surges of any kind.
The bottom plot is the data recorded by the Teensy 3.6 connected to a MAX40201FAUA+ 50V/V amplifier IC and 0.001 current sense resistor. What is measured here should be identical to the top plot once converted, but the ADC output values make NO sense for a current of ~1 Ampere. This data was processed through a FIR filter with alpha = 0.1 to remove some of the high frequency noise.
Capture.PNG
 
I can't tell if your ADC clock is in the required range of 2Mhz to 12MHz but it appears that you are using the bus clock which typically runs much faster than that.
 
For ADIV, I have 00 selected: "The divide ratio is 1 and the clock rate is input clock".

So ADCK is set to the same as the hardware clock. I am new to this low level stuff, so can I ask where you got the 2Mhz to 12Mhz requirement from for ADCK?
 
It is in the device data sheet, which I grabbed before I started. Most people use the library code and let it worry about such things.
 
Honestly, I would love to be able to use the library code, I would be done with this part by now, but neither the core libraries or any of the addon libraries have the functionality to sychronize the ADC to a particular point on a PWM waveform, reliably.

Regarding this topic, I tried using the core libraries for initialization of the ADC in lieu of my own code, since these three commands execute code for initialization and calibration when run. I amended the above code as follows:
Code:
void MD_1v0::ADC_Init(){ //Initializes ADC hardware for Current Sense Pin
        //Method 2
    analogReadRes(16);
    analogReadAveraging(0);
    analogReference(0);
    ADC0_CFG1 = ADC_CFG1_16BIT + ADC_CFG1_MODE(3); //*****
    ADC0_CFG2 = ADC_CFG2_MUXSEL; //*****
    ADC0_SC2 |= ADC_SC2_ADTRG; //enable interrupt


        //Method 1
    //--ADC0_CFG1 = ADC_CONFIG1;
    //--ADC0_CFG2 = ADC_CONFIG2;
    // Voltage ref vcc, hardware trigger, DMA
    //--ADC0_SC2 = ADC_SC2_REFSEL(0) | ADC_SC2_ADTRG; // | ADC_SC2_DMAEN;

    // Enable averaging, 4 samples
    //ADC0_SC3 = ADC_SC3_AVGE | ADC_SC3_AVGS(1);
    //--ADC0_SC3 = 0; //Disable Averaging

    //--ADC_Cal(); //Calibrate ADC hardware

        //Common
    // Enable ADC interrupt, configure pin
    ADC0_SC1A = ADC_SC1_AIEN | 0x8; //0x8 corresponds to Pin 16/A2
    NVIC_ENABLE_IRQ(IRQ_ADC0);
}

This new code should put my code in cohesion with what normally happens during an analogRead(), but the results were still scaled incorrectly. The result was that the values output by the Teensy dropped significantly, into the realm of 20000 / 65536 instead of 40000/65536, previously, both with and without the ***** lines enabled. The ***** lines change the sample time from the analogRead() default "long sample time" to "short sample time".

I am pulling my hair out over this one. I cannot get my scope onto the teensy so I can verify what values it is seeing, but if I disable all this analog read stuff and insert a basic Serial.println(analogRead(A2)) into the operating loop, no values exceed 4000/65536 over 12,000 datapoints, which is in line with what is theoretically expected. I would think that if the Teensy were actually seeing a peak of 1.5volts from the AMP, then at least a few of these random values would exceed 5000/65536 output!
 
Interestingly enough, I disabled analogReadAveraging and analogReference, suddenly the values are reading right where they should be for the level of current being measured (2000-4000 out of 65536). I will look into why one of those was causing an issue tomorrow, unless someone knows?
 
Kind of missing my point. Which was to find out what the bus clock is and set the ADC clock divider appropriately. (I am not an Arduino kind of guy so I have no idea if you used the analog library correctly. I do know that your write to CFG1 still sets the ADC clock equal to the bus clock.)
 
Your point was not missed.

The line:
Code:
ADC0_CFG1 = ADC_CFG1_16BIT + ADC_CFG1_MODE(3); //*****

Uses the same defines specified by Paul in the Teensy core code for the basic analogRead function set. Specifically, these are the ADC clock related specs defined in my case for a 60 Mhz F_BUS:

Code:
#define ADC_CFG1_16BIT  ADC_CFG1_ADIV(2) + ADC_CFG1_ADICLK(1) // 7.5 MHz

But, I do not think the ADC clock settings were the issue. I seems like if I do so much as look at the ADC averaging settings the measured values go haywire and I have no idea why that is. Regardless, your initial comment helped me look at this in a new light, so I thank you.
 
Status
Not open for further replies.
Back
Top