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