ADC syncronized with PWM

Hello,

So I searched the forums and found multiple threads on this, but I just cannot make it work.

I am making a small motor controller which I want to be current controlled. I am using a MC33926 H-bridge and a Due to to this. Using pins C3 (35) and C5 (37) for PWM signals (PWM0H and PWM1H) and arduino-analog 0 (A7) for current measurement.

The idea is to use an event line to trigger ADC conversions in sync with PWM. As far as I understand the datasheet, this is the way it works: A comparison trigger (ref page 993 sam3x8e) can be set to trigger at a given value for PWM channel 0. This trigger can be used to trigger the ADC to measure the motor current at this point. I am using PWM0H for one channel and PWM1H for the other channel. My idea is to use 4 comparison triggers per period of the PWM (symmetrical pwm). One for duty cycle 1 and one for duty cycle 2 when the counter is incrementing and the same when it is decrementing. I have verified (with an oscilloscope) that the current signal I measure is correct.

Problem: I have configured one comparison trigger and it triggers about 12 times every period. This gives me a good description of the current, but I would like to measure the minimum and maximum value and take the average of the two to represent the current. Now I get way to many readings, and I just can't see why.

I have attached a plot of the current I measure and a plot showing my switching pattern and where to measure current (Red circles).

My Code:

/* PWM is connected to pins C3 and C5. 
 * PWMH0: C3 (peripheral B)
 * PWMH1: C5 (peripheral B)
 * 
 * 
 */

 #define DATASIZE 100
 
 uint16_t testduty = 8000;

 volatile uint16_t testdata[DATASIZE] = {0};
 volatile uint16_t testdataIndex = 0;
 volatile bool dataReady = false;

 volatile uint16_t testdata2[DATASIZE] = {0};
 volatile uint16_t lastData = 0;



void setup() {
  Serial.begin(9600);

  // ************ Setup PWM ******************

  // disable PIO for C3 and C5
  REG_PIOC_PDR = REG_PIOC_PSR | PIO_PER_P3 | PIO_PER_P5;

  //B101000, Peripheral AB Select Register (select b)
  REG_PIOC_ABSR = REG_PIOC_ABSR | PIO_ABSR_P3 | PIO_ABSR_P5 ; 

  //Peripheral Clock Enable Register 1 (activate clock for PWM, id36, bit5 of PMC_PCSR1)
  REG_PMC_PCER1 = REG_PMC_PCER1 | 16; 

  // Set channel 0 and 1 as synchrnous channels
  REG_PWM_SCM = PWM_SCM_SYNC0 | PWM_SCM_SYNC1;

  REG_PWM_CMR0 |= PWM_CMR_CALG; // Set symmetrical pwm alignment

  //*********** PWM EVENT LINES ******************

  // Initial value for event line compares
  REG_PWM_CMPV0 = 8000; 

  // Set CTR to 0 and  CPR to 0
  REG_PWM_CMPM0 |= PWM_CMPM_CTR(0);
  REG_PWM_CMPM0 |= PWM_CMPM_CPR(0);

  // Select comparision 0 for event line 1
  PWM->PWM_ELMR[1] = PWM_ELMR_CSEL0;  

  // Enable event lines
  REG_PWM_CMPM0 |= PWM_CMPM_CEN;

 
  //*********************************************************
  
  REG_PWM_ENA = REG_PWM_SR | PWM_ENA_CHID0 | PWM_ENA_CHID1;     //PWM Enable Register | PWM Status Register (activate channels 0,1)

  REG_PWM_CPRD0 = 16000; //Channe0 Period Register (84mhz/4200/2 = 20kHz)  

  set_duty_cycle(testduty);

  // ************ Setup ADC ******************

    PMC->PMC_PCER1 |= PMC_PCER1_PID37;                    // ADC power on
    ADC->ADC_CR = ADC_CR_SWRST;                           // Reset ADC
    
    ADC->ADC_MR |= ADC_MR_TRGEN_EN;         // Hardware trigger selected by TRGSEL field is enabled
    ADC->ADC_MR |= ADC_MR_TRGSEL_ADC_TRIG5; // Set trigger to pwm event line 1
    ADC->ADC_MR |= 0x7f00;                  // Speed 

    ADC->ADC_IER |= ADC_IER_EOC7;   // Enable interrupt for A7
    ADC->ADC_CHER |= ADC_CHER_CH7;  //enable ADC on pin A7 
    
    NVIC_EnableIRQ (ADC_IRQn) ;   // enable ADC interrupt vector
}

 
void loop() {


  // *** Serial control routine ****
  if(Serial.available() > 0){
    char c = Serial.read();
    switch(c){
      case 'q': testduty+= 200; Serial.println(testduty); break;
      case 'w': testduty-= 200; Serial.println(testduty); break;
      case 'a': testduty = 8000; break;
      case 'z': turn_on_pwm(); break;
      case 'x': turn_off_pwm(); break;

      case 'c': testduty = 12000; break;
      case 'v': testduty = 3000; break;

      case 'o': doADCtest(); Serial.println(" ** DO test ** "); break;
      
      
    }
    set_duty_cycle(testduty);
    Serial.println(c);
  }

  if(dataReady){
    printTestData();
  }
}

void turn_on_pwm(){
  REG_PWM_ENA = REG_PWM_SR | PWM_ENA_CHID0 | PWM_ENA_CHID1; 
}

void turn_off_pwm(){
  REG_PWM_DIS = REG_PWM_SR | PWM_ENA_CHID0 | PWM_ENA_CHID1; 
}

void set_duty_cycle(uint16_t duty){
  REG_PWM_CDTY0 = duty; 
  REG_PWM_CDTY1 = 16000-duty; 

  // Update ADC Triggers
  REG_PWM_CMPVUPD0 = duty & 0xFFFFFF;
  //REG_PWM_CMPVUPD1 = (16000-duty) & 0xFFFFFF;
}

void ADC_Handler(void)  {
  if (ADC->ADC_ISR & ADC_ISR_EOC7){
    int val = *(ADC->ADC_CDR+7);
    
    testdata[testdataIndex++] = val;
    
    if(testdataIndex == DATASIZE){
      dataReady = true;
      testdataIndex = 0;
      ADC->ADC_IDR |= ADC_IER_EOC7; // Disable adc interrupts
    }      
  }
  
}

void doADCtest(){
  ADC->ADC_IER |= ADC_IER_EOC7; // enable interrupts for adc 0
  
}

void printTestData(){
  dataReady = false;

  Serial.println("------------- Raw Data ---------------");
  for (int i = 0; i<DATASIZE; i++){
    Serial.println(testdata[i]);
  }
  Serial.println("------------- Done ---------------");
}

1 Like

There are a lot of confusions in your code.

Here is an example sketch to trigger ADC conversions with PWM event line 1:

/***********************************************************************************************************/
/*        ADC conversions of 1 analog input triggered by PWM Event Line 1 at a 20 KHz frequency             */
/***********************************************************************************************************/


void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);
  adc_setup();
  pwm_setup();
}

void loop()
{

}

/*************  Configure ADC function  *******************/

void adc_setup() {

  PMC->PMC_PCER1 |= PMC_PCER1_PID37;                    // ADC power on

  ADC->ADC_CR = ADC_CR_SWRST;                           // Reset ADC
  ADC->ADC_MR =  ADC_MR_TRGEN_EN                       // Hardware trigger select
                  | ADC_MR_TRGSEL_ADC_TRIG5            // Trigger by PWM Event Line 1
                  | ADC_MR_PRESCAL(1);
                  
  ADC->ADC_IER = ADC_IER_EOC7;                          // End Of Conversion interrupt enable for channel 7
  NVIC_EnableIRQ(ADC_IRQn);                             // Enable ADC interrupt
  ADC->ADC_CHER = ADC_CHER_CH7;                         // Enable Channel 7 = A0

}
void ADC_Handler() {
  static uint32_t Count;
  ADC->ADC_CDR[7];  // Read and clear status register

 // For debugging only
  if (Count++ > 20000) {  // For 20 KHz
    Count = 0;
    PIOB->PIO_ODSR ^= PIO_ODSR_P27;    // Toggle LED every 1 Hz
  }

}
/*************  Configure PWM Event Line 1  ************/

void pwm_setup () {

  PMC->PMC_PCER1 |= PMC_PCER1_PID36;                             // PWM controller power on

  // Set the PWM Reference channel 0 i.e. : Clock/Frequency/Alignment
  PWM->PWM_CLK = PWM_CLK_PREB(0b0000) | PWM_CLK_DIVB(1);         // Set the PWM clock rate to MCK = 84 MHz
  PWM->PWM_CH_NUM[0].PWM_CMR = PWM_CMR_CPRE_CLKB;                // The period is left aligned, clock source as CLKB on channel 0
  PWM->PWM_CH_NUM[0].PWM_CPRD = 4200;                            // Set the PWM frequency to trigger ADC : Mck/CPRD/DIVB/PREB = F ;

  PWM->PWM_CMP[0].PWM_CMPV = PWM_CMPV_CV(200);                   //Define the comparison value in channel 0 to be compared with the counter of the channel 0.
                                                                 // Any value between 1 and PWM_CPRD
  PWM->PWM_CMP[0].PWM_CMPM = PWM_CMPM_CEN;                       // Comparison enable

  PWM->PWM_ELMR[1] = PWM_ELMR_CSEL0;                             // Event line 1 trigger according to CMPV of channel 0

  PWM->PWM_ENA = PWM_ENA_CHID0;                                  // Fire !!

}