Close

Page 3 of 3 FirstFirst 123
Results 51 to 68 of 68
  1. #51
    Join Date
    Nov 2009
    Location
    Simi Valley, CA
    Posts
    9,136
    Rep Points
    11,964.3
    Mentioned
    742 Post(s)
    Rep Power
    120


    Yes Reputation No
    Sorry I sort of dropped the ball on this project. Seems like a simple one with good volume potential. Click here to enlarge When our 135i is back from Vargas with the new engine I'll pick it back up.

    On the processor I'd probably look for something robust (e.g. native 5v). Doubt you'll have to run it faster than 1mhz for this or worst case 8mhz. Slow speed PWM is harder to program than high speed PWM based on how the timers and prescalers are normally setup. On the JB4 for example we run it at 40mhz and to get down to 30hz for a single turbo solenoid had to setup a software PWM using capture/compare registers.
    Last edited by Terry@BMS; 09-23-2017 at 04:55 PM.
    Burger Motorsports
    Home of the Worlds fastest N20s, N54s, N55s, N63s, S55s, and S63s!

    It is the sole responsibility of the purchaser and installer of any BMS part to employ the correct installation techniques required to ensure the proper operation of BMS parts, and BMS disclaims any and all liability for any part failure due to improper installation or use. It is the sole responsibility of the customer to verify that the use of their vehicle and items purchased comply with federal, state and local regulations. BMS claims no legal federal, state or local certification concerning pollution controlled motor vehicles or mandated emissions requirements. BMS products labeled for use only in competition racing vehicles may only be used on competition racing vehicles operated exclusively on a closed course in conjunction with a sanctioned racing event, in accordance with all federal and state laws, and may never be operated on public roads/highways. Please see http://www.burgertuning.com/emissions_info.html for more information on legal requirements related to use of BMS parts.

  2. #52
    Join Date
    Sep 2014
    Posts
    289
    Rep Points
    509.3
    Mentioned
    5 Post(s)
    Rep Power
    6



    1 out of 1 members liked this post. Yes Reputation No
    I ordered a Arduino M0, got the IDE up and running, I'll start writing code this week. Bought some shielded cable to run from the ECU box to the H bridge (controller box will be in the ECU box), looking into a level shifter/isolator right now for the 3.3v output to convert to 5v. Jake was kind enough to give me the LPF sensor voltage to pressure gradient. Found the ECU pinout and connector X60005 pin 34 is the LPF sensor input. So just need to wire to it, 12v power, use the 5v off the board for the level shifter, run the wire, write up some code, make a box, and should be good to go.

    I'm thinking of using a 2 or 3 stage P I gain setup, with no D. P and I gains will change with error, smaller the error, the smaller the gain, larger the error, larger the gain. I'll run a 20khz PWM with 1000 steps and sample as fast as the ADC will let me using an ISR on conversion completion to immediately request the next conversion and save the value off into an averaged sample buffer, then use a timer interrupt to compute the PID values and update the PWM output being controlled by another counter timer. That way its all precise and I'm not tying to do everything inside a loop.

  3. #53
    Join Date
    Nov 2012
    Posts
    2,492
    Rep Points
    1,939.2
    Mentioned
    199 Post(s)
    Rep Power
    0


    Yes Reputation No
    Any reason you went with the M0? There are arduino boards with a 5V operating voltage that may have been a better choice.

    Also, could all this be avoided if I took some time out to get you the lpfp PID stuff?

  4. #54
    Join Date
    Sep 2014
    Posts
    289
    Rep Points
    509.3
    Mentioned
    5 Post(s)
    Rep Power
    6



    1 out of 1 members liked this post. Yes Reputation No
    Its possible it could be avoided, but the only way to know would be to try.

    I was digging through some older info and found that Shiv seems to have thought that the EKP changed voltage and not duty cycle to control the pumps. That must not be the case because my H bridge's input is a CMOS logic input and if the output was say 2.5 volts, then its likely neither half bridge would turn on, and if it was anything above 3 volt then they would both turn full on, which I'd think would be obvious as I'd lose all fuel pressure at low load. (note there is a voltage divider on the EKP output, converts 14v to be about 5.4v, so if the EKP put out even as low as 7 volts the whole thing would shut off, and anything higher than 7.5 and it would be always on). Its possible the EKP is spiking up and down and up and down on its voltage instead of duty cycle because the pressure drops too low. Really should but a scope on the thing, but I dont have a portable one. All the ones at work are these $160k 20ghz sample rate monsters. Great if I can take the thing into the lab, $#@!ty if I need to take the scope to it.

    I went with the M0 because after doing some math I wanted to make sure I had a processor that had a fast enough clock to give me a 1000 steps at 20khz PWM output. A 1248P for example at 16mhz simply isnt fast enough to give me that resolution at that frequency (its counter timers couldnt do it as they couldnt run at 16mhz). Could I reduce that to 100 steps at 8khz? Yea I could have done that, but I wanted to make sure I was high enough frequency it would work the first time, I can always go lower, its hard to go higher. I also wanted enough processing power to make sure my Nyquist rate on the sampling was fast enough to try to prevent low frequency aliasing. I want it to be ADC limited and not processor limited.

    If this was a production run yea I'd go for lower cost with things closer to the minimum needed. But given I want it to work right the first time as its a project I like to go with the "there's no kill quite like overkill" mentality.

  5. #55
    Join Date
    Sep 2014
    Posts
    289
    Rep Points
    509.3
    Mentioned
    5 Post(s)
    Rep Power
    6



    1 out of 1 members liked this post. Yes Reputation No
    Arduino ADC calibration complete, I'm seeing .005 volt accuracy with a 32 sample windowed average, 250ksps. Found some data sheets for similar LPF sensors, response times tend to be around 1-2 ms, so I'm 4x over sampling, perfect Click here to enlarge.

    Sadly the ADC can only read up to 3.8 volts no matter what I do, even with a 5v external reference as the ADC unit its self is limited by Vdd of the chip. So I'm going to put a 5v to 3.3v voltage divider that will pull 1.5 mA on the LPF sensor output. Most data sheets show it should be able to handle a 3kohm load and maintain its voltage, but I'll have to test that out.

    I have the PID code written but with single stage gains, I'll write it to use dual stage gains and write the PWM hardware setup code next, then we'll be ready to test on car.

    Not bad for 3 days worth of work in my free time I suppose.

  6. #56
    Join Date
    Nov 2012
    Posts
    2,492
    Rep Points
    1,939.2
    Mentioned
    199 Post(s)
    Rep Power
    0


    Yes Reputation No
    Click here to enlarge Originally Posted by shushikiary Click here to enlarge
    Sadly the ADC can only read up to 3.8 volts no matter what I do, even with a 5v external reference
    That's really weird, I can read a full 5V ADC even on a $#@!ty uno / nano. You sure something else isn't going on?

  7. #57
    Join Date
    Sep 2014
    Posts
    289
    Rep Points
    509.3
    Mentioned
    5 Post(s)
    Rep Power
    6



    Yes Reputation No
    I played around with it for many hours.

    From the SAMD data sheet they seemed to suggest that if I used 5v on AREF that then it could read up to 5v. I tried and tried and tried and the voltage would always read 4096 if I got above 3.89 volts.

    Looked further into the data sheet and found that the hard limit on being able to read a voltage is at Vdd + .6 volts. This explains it, Vdd has to be at the same voltage as several other supplies all of which expected 3.3 volts. It can be run at lower voltage, but really not much higher.

    I also found that using an external ref for the ADC got me pretty poor ground voltage sensing. I switched to the internal reference of Vdd/2 then changed the gain to 2 so it would read 3.3v as 4096 and it stabilized things A LOT. So I decided to just stick with that, using a sampling time of 12 clocks (equal to the time needed to get a 12 bit number). Thankfully this ADC has a free run setup so I only have to read the ADC value in the ISR and it auto starts up the next conversion and clears the ISR flag when you read the result register, so at least that was nice. I even tried running the internal Vdd/2 reference but then using a multiplier of 4 so I could theoretically read up to 6.6 volts, and found the same thing, once I got to 3.89 volts the value would not go any higher. Even though the value given was 2414 (3.89 volts), it would go NO higher than that even at 5v input. So yes, I'm sure you can only read up to 3.89 volt on the ADC in those SAMD's.

    I've found the open source libraries to be highly inefficient though because they are designed to be used with so many different things. So I've mostly just written my own stuff and only used the libraries for references to the register locations.

  8. #58
    Join Date
    Sep 2014
    Posts
    289
    Rep Points
    509.3
    Mentioned
    5 Post(s)
    Rep Power
    6



    2 out of 2 members liked this post. Yes Reputation No
    Success!

    It works, and works fairy well, though tuning the PID took a lot of time.

    The problem is, when the engine is off it was easy to get very large oscillations in output because it took so little change in duty cycle to hit target pressure (engine off, ignition on), and similar while in very low load (like idle), however you need quick response if you mash the throttle. This means you need a high P gain, yet that means large oscillations at low load.

    So help with this (not perfect, but it works), I did add in some D gain, but to deal with it even more I went to a 6 step P gain, that linearly reduces as you get close to setpoint.

    I could likely tune it some more to get it to be even more precise, but it would take my hours of poking it and it works well right now.

  9. #59
    Join Date
    Sep 2014
    Posts
    289
    Rep Points
    509.3
    Mentioned
    5 Post(s)
    Rep Power
    6



    1 out of 1 members liked this post. Yes Reputation No
    Source code for Arduino Zero, M0, or M0 pro if anyone is interested:

    Code:
    /*Includes*/#include "Arduino.h"
    
    
    /*defines*/
    #define USE_MANUAL 0 // 0 is use PID, 1 is use manual method
    #define ADC_RESOLUTION_BITS ADC_CTRLB_RESSEL_12BIT_Val // must use values defined in adc.h
    #define ADC_OFFSET_CORRECTION 13 // these values were calibrated for this device using correctADCResponse.ino
    #define ADC_GAIN_CORRECTION 2068
    #define ADC_AVERAGING_SIZE 64 // this could be done in hardware, but this allows for a windowed average
    
    
    #if !USE_MANUAL
    //#define PROPORTIONAL_GAIN_MAJOR 1
    //#define PROPORTIONAL_GAIN_MINOR 2
    //#define PROPORTIONAL_GAIN_BREAK_POINT 225 // approximately 10 psi, value above or below setpoint where 
                                              // we switch from using the minor to the major proportional gain
    #define INTEGRAL_GAIN 8
    #define DERIVATIVE_GAIN 0.0001
    #endif
    #if USE_MANUAL
    #define SMALL_INCREASE_CHANGE_IN_DUTY_CYCLE 0.1 
    #define LARGE_INCREASE_CHANGE_IN_DUTY_CYCLE 0.5 
    #define VERY_LARGE_INCREASE_CHANGE_IN_DUTY_CYCLE 2 
    #define SMALL_DECREASE_CHANGE_IN_DUTY_CYCLE 0.1 
    #define LARGE_DECREASE_CHANGE_IN_DUTY_CYCLE 0.5 
    #define VERY_LARGE_DECREASE_CHANGE_IN_DUTY_CYCLE 2 
    #define USE_SMALL_CHANGE_DELTA 225 // these are in ADC counts 225 counts is about 10PSI of change
    #define USE_LARGE_CHANGE_DELTA 500 // if error is greater than this value then VERY_LARGE_CHANGE_IN_DUTY_CYCLE will be used
    #endif
    #define SAMPLE_TIME 100 // this is in micro seconds
    #define SETPOINT 1850 // This is the ADC count that is the desired PSI 
                          // The math on this is ugly, a voltage divider is needed to convert 5v to 3.3v
                          // due to the max readability of the ADC, The divider converts 5 to 3.3 linearly
                          // The sensor puts out 0psi at 0.498 volts and 145 psi at 4.6 volts, 2.5 volts being 72 psi
                          // 2.5 volts converts to 1.6131 with the voltage divider (measured values with ohm meter
                          // its slightly lower than 3.3 at 5v) which is 2002 of the 4096 ADC counts, so setting slightly
                          // lower to get around 70 psi
    #define PWM_TOP 1200 // 1200 counts for PWM output, aka 1200 steps for 100% duty cycle (creats 20khz output)
    #define OUT_MAX PWM_TOP // 100% duty cycle
    #define OUT_MIN 60 // minimum 5% duty cycle
    
    
    
    
    
    
    /*working variables*/
    unsigned long LastTime;
    #if !USE_MANUAL
    double ITerm, LastErr, LastInput;
    //double kpMajor, kpMinor, ki, kd;
    double ki, kd;
    double kp[6] = {0.5, 0.7, 0.9, 1.1, 1.3, 1.5};
    double kpBreak[5] = {66, 132, 198, 264, 331};
    #endif
    unsigned long ADCAccumulationCount;
    unsigned long ADCAccumulation[ ADC_AVERAGING_SIZE ];
    unsigned long AccumulationSum;
    double Output, Input, Error;
    
    
    
    
    /* debug code
    unsigned long OverFlowCount;
    unsigned long OverFlowMicros;
    unsigned long LastOverFlowMicros;
    unsigned long DeltaOverFlowMicros;
    */ 
    
    
    // ADC interrupt handler (already placed by IDE, just needed function definition)
    void ADC_Handler( void )
    {
       if( ADCAccumulationCount >= ADC_AVERAGING_SIZE )
       {
          ADCAccumulationCount = 0;
       }
    
    
       ADCAccumulation[ ADCAccumulationCount ] = (unsigned long)ADC->RESULT.reg; // note reading this reg clears the interrupt bit as well
       ADCAccumulationCount++;
    }
    
    
    /* Debug code to make sure timmer is running at desired frequency
    void TCC1_Handler( void )
    {
       OverFlowCount++;
       OverFlowMicros = micros( );
       DeltaOverFlowMicros = OverFlowMicros - LastOverFlowMicros;
       LastOverFlowMicros = OverFlowMicros;
       TCC1->INTFLAG.bit.OVF = 1; // clear interrupt flag
    }
    */
    #if !USE_MANUAL
    void Initialize( void )
    {
       LastInput = Input;
       ITerm = Output;
       if ( ITerm > OUT_MAX )
       {
          ITerm = OUT_MAX;
       }
       else if ( ITerm < OUT_MIN )
       {
          ITerm= OUT_MIN;
       }
    }
    #endif
    
    
    void Compute( void )
    {
       /*How long since we last calculated*/
       unsigned long now = micros( );
       unsigned long timeChange = ( now - LastTime ); // Note on overflow of micros this becomes a VERY large positive number
                                                      // thus causing us to compute immediately, so we might have
                                                      // a compute done with less than 100 micro seconds between them
                                                      // every 71 minutes. 
    #if !USE_MANUAL
       double error, dInput;
       double kpToUse = 0;
       unsigned int i;
    #endif
    
    
    
    
       if ( timeChange >= SAMPLE_TIME )
       {
          AccumulationSum = 0;
          // calculate input from average of sample buffer
          for ( unsigned int i = 0; i < ADC_AVERAGING_SIZE; i++ )
          {
    
    
             AccumulationSum += ADCAccumulation[i];
          }
    
    
          Input = AccumulationSum / ADC_AVERAGING_SIZE;
       #if USE_MANUAL
          if( SETPOINT > Input )
          {
             Error = SETPOINT - Input;
             
             if ( Error <= USE_SMALL_CHANGE_DELTA )
             {
                Output += SMALL_INCREASE_CHANGE_IN_DUTY_CYCLE;
             }
             else if ( Error <= USE_LARGE_CHANGE_DELTA )
             {
                Output += LARGE_INCREASE_CHANGE_IN_DUTY_CYCLE;
             }
             else
             {
                Output += VERY_LARGE_INCREASE_CHANGE_IN_DUTY_CYCLE;
             }
          }
          else if ( SETPOINT < Input )
          {
             Error = Input - SETPOINT;
    
    
             if ( Error <= USE_SMALL_CHANGE_DELTA )
             {
                Output -= SMALL_DECREASE_CHANGE_IN_DUTY_CYCLE;
             }
             else if ( Error <= USE_LARGE_CHANGE_DELTA )
             {
                Output -= LARGE_DECREASE_CHANGE_IN_DUTY_CYCLE;
             }
             else
             {
                Output -= VERY_LARGE_DECREASE_CHANGE_IN_DUTY_CYCLE;
             }
          }
    
    
          if ( Output > OUT_MAX ) 
          {
             Output = OUT_MAX;
          }
    
    
          if ( Output < OUT_MIN )
          {
             Output = OUT_MIN;
          }
       #endif
       #if !USE_MANUAL
    /* debug code
          SerialUSB.println("\r\n Input read as: ");
          SerialUSB.print( Input );
    */
          /*Compute all the working error variables*/
          error = SETPOINT - Input;
          ITerm += (ki * error);
          
          if ( ITerm > OUT_MAX ) 
          {
             ITerm = OUT_MAX;
          }
          else if ( ITerm < OUT_MIN )
          {
             ITerm = OUT_MIN;
          }
          
          dInput = ( Input - LastInput );
     
          /*Compute PID Output*/
    /*
          if ( abs( error ) > PROPORTIONAL_GAIN_BREAK_POINT )
          {
             kpToUse = kpMajor;
          }
          else
          {
    
    
             kpToUse = kpMinor;
          }
    */
          for ( i = 0; i < 5; i++ )
          {
             if ( abs( error ) <= kpBreak[i] )
             {
                kpToUse = kp[i];
                break;
             }
    
    
          }
    
    
          // if the error was larger than the last kpBreak[]
          if ( kpToUse = 0 )
          {
             // use the largest value
             kpToUse = kp[5];
          }
          
          Output = kpToUse * error + ITerm - kd * dInput;
          
          if ( Output > OUT_MAX )
          {
             Output = OUT_MAX;
          }
          else if ( Output < OUT_MIN )
          {
             Output = OUT_MIN;
          }
          
    /* debug code
          SerialUSB.println("\r\n Output PWM: ");
          SerialUSB.print( Output );
    
    
          SerialUSB.println("\r\n TCC1 Overflow count: ");
          SerialUSB.print( OverFlowCount );
    
    
          SerialUSB.println("\r\n TCC1 Overflow Micros: ");
          SerialUSB.print( DeltaOverFlowMicros );
    */
       #endif
    
    
          TCC1->CC[0].reg = TCC_CC_CC( ( unsigned long )Output ); // Set PWM output %
          while ( TCC1->SYNCBUSY.bit.CC0 );
    
    
       #if !USE_MANUAL
          /*Remember some variables for next time*/
          LastInput = Input;
          LastTime = now;
       #endif
       }
    }
    
    
    #if !USE_MANUAL
    //void SetTunings( double KpMajor, double KpMinor, double Ki, double Kd )
    void SetTunings( double Ki, double Kd )
    {
       double SampleTimeInSec = ( ( double )SAMPLE_TIME ) / 1000000; // divide by 1 million to get micro seconds
       //kpMajor = KpMajor;
       //kpMinor = KpMinor;
       ki = Ki * SampleTimeInSec;
       kd = Kd / SampleTimeInSec;
    }
    #endif
    
    
    void SetupADC( void )
    {
       // first disable DAC
       while ( DAC->STATUS.bit.SYNCBUSY );
       DAC->CTRLA.bit.ENABLE = 0x00; // Disable DAC
       while ( DAC->STATUS.bit.SYNCBUSY );
       
       while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY );
    
    
       GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID( GCM_ADC ) | // Generic Clock ADC
                           GCLK_CLKCTRL_GEN_GCLK0     | // Generic Clock Generator 0 is source
                           GCLK_CLKCTRL_CLKEN;          // Enable! Gen CLK 0 is set to 48mhz clk by samd setup
       while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY );
    
    
       // setup power manager for ADC
       PM->APBCMASK.reg |= PM_APBCMASK_ADC;
    
    
       while ( ADC->STATUS.bit.SYNCBUSY );          // Wait for synchronization of registers between the clock domains
       
       // Set correction values
       ADC->OFFSETCORR.reg = ADC_OFFSETCORR_OFFSETCORR( ADC_OFFSET_CORRECTION );
       ADC->GAINCORR.reg = ADC_GAINCORR_GAINCORR( ADC_GAIN_CORRECTION );
    
    
       // Enable digital correction logic, calibration was already setup by samd setup
       ADC->CTRLB.reg = ADC_CTRLB_RESSEL( ADC_RESOLUTION_BITS ) | // set resolution
                        ADC_CTRLB_CORREN | // enable correction
                        ADC_CTRLB_PRESCALER( ADC_CTRLB_PRESCALER_DIV16_Val ); // clock div 16
       while ( ADC->STATUS.bit.SYNCBUSY );
    
    
       ADC->REFCTRL.reg = ADC_REFCTRL_REFSEL( ADC_REFCTRL_REFSEL_INTVCC1_Val ) | // use internal 1.65 volt reference
                          ADC_REFCTRL_REFCOMP; // use reference compensation
       while ( ADC->STATUS.bit.SYNCBUSY );
    
    
       ADC->AVGCTRL.reg = 0; // no averaging (take 1 sample), divide by 1
       while ( ADC->STATUS.bit.SYNCBUSY );
    
    
       ADC->SAMPCTRL.reg = ADC_SAMPCTRL_SAMPLEN( ( 2 * ADC_RESOLUTION_BITS ) - 1 ); // sample length is equal to ADC clock cycles needed for conversion
       while ( ADC->STATUS.bit.SYNCBUSY );
    
    
       ADC->WINCTRL.reg = 0; // no windowing
       while ( ADC->STATUS.bit.SYNCBUSY );
    
    
       // setup PA02_AIN0 note: pin 3 on chip, A0 on arduino header
       PORT->Group[PORTA].PINCFG[2].bit.PMUXEN = 1; // Peripheral mux enable 
       PORT->Group[PORTA].PMUX[1].reg = PORT_PMUX_PMUXE_B;  // Enable PMUX group B Even pins
    
    
       ADC->INPUTCTRL.reg = ADC_INPUTCTRL_MUXPOS( ADC_INPUTCTRL_MUXPOS_PIN0_Val ) | // Selection for the positive ADC input (AIN0)
                            ADC_INPUTCTRL_MUXNEG( ADC_INPUTCTRL_MUXNEG_GND_Val ) | // set mux neg to internal ground
                            ADC_INPUTCTRL_GAIN( ADC_INPUTCTRL_GAIN_DIV2_Val ); // set gain to 1/2 for larger range (max sadly is 3.3 volts)
       while ( ADC->STATUS.bit.SYNCBUSY );
       
       ADC->CTRLA.bit.ENABLE = 0x01;             // Enable ADC
       while ( ADC->STATUS.bit.SYNCBUSY );
       
       // Start conversion
       ADC->SWTRIG.bit.START = 1; // first conversion has to be thrown way
    
    
       // Clear the Data Ready flag
       ADC->INTFLAG.reg = ADC_INTFLAG_RESRDY;
       while ( ADC->INTFLAG.bit.RESRDY == 0 ); // wait for conversion completion
    
    
       ADC->INTFLAG.reg = ADC_INTFLAG_RESRDY; // clear flag again
       while ( ADC->STATUS.bit.SYNCBUSY );
       
       // enbable interrupt on result ready
       ADC->INTENSET.bit.RESRDY = 1;
       while ( ADC->STATUS.bit.SYNCBUSY );
    
    
       NVIC_SetPriority( ADC_IRQn, 1 << __NVIC_PRIO_BITS ); // use the next highest priority not already used
       NVIC_EnableIRQ( ADC_IRQn );
    
    
       // Set for free run 
       ADC->CTRLB.bit.FREERUN = 1;
       while ( ADC->STATUS.bit.SYNCBUSY );
    
    
       ADC->SWTRIG.bit.START = 1; // Start up the free run, all samples will now be handled by ISR
       while ( ADC->STATUS.bit.SYNCBUSY );
    }
    
    
    void SetupPWM( void )
    {
       while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY );
       //GCLK->GENCTRL.reg = GCLK_GENCTRL_ID( 4 ) |     // Generic clock 4
       //                    GCLK_GENCTRL_SRC_DFLL48M | // Source is 48mhz clock
       //                    GCLK_GENCTRL_IDC | // 50/50 duty cycle on odd division 
       //                    GCLK_GENCTRL_GENEN; // enable
       //while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY );
       
       //GCLK->GENDIV.reg = GCLK_GENDIV_DIV( 1 ) | // div is 1
       //                   GCLK_GENDIV_ID( 4 ); // Generic Clock Generator 4
       //while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY );
       
       GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID( GCM_TCC0_TCC1 ) | // Generic Clock TCC1
                           GCLK_CLKCTRL_GEN_GCLK0     |       // Generic Clock Generator 0 is source
                           GCLK_CLKCTRL_CLKEN;                // Enable!
       while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY );
    
    
       PM->APBCMASK.reg |= PM_APBCMASK_TCC1; // set power manager for TCC1 peripheral
    
    
       TCC1->CTRLA.reg = TCC_CTRLA_SWRST; // disable/reset TCC1 so that the registers can be written
       while ( TCC1->SYNCBUSY.bit.SWRST );
    
    
       TCC1->WAVE.reg = TCC_WAVE_WAVEGEN( TCC_WAVE_WAVEGEN_NPWM_Val ); // set wave form generation for single slope PWM
       while ( TCC1->SYNCBUSY.bit.WAVE );
       
       TCC1->PER.reg = TCC_PER_PER( PWM_TOP ); // set PWM period to get us 20khz
       while ( TCC1->SYNCBUSY.bit.PER );
    
    
       TCC1->CC[0].reg = TCC_CC_CC( 0 ); // Start out at zero % on TCC1 channel 0
       while ( TCC1->SYNCBUSY.bit.CC0 );
    
    
       TCC1->COUNT.reg = 0; // init count to 0
       while ( TCC1->SYNCBUSY.bit.COUNT );
    
    
       // setup PA06_TCC1/WO[0] note: pin 11 on chip, "digigal PWM ~ 8" on arduino header
       PORT->Group[PORTA].PINCFG[6].bit.PMUXEN = 1; // Peripheral mux enable 
       PORT->Group[PORTA].PMUX[3].reg = PORT_PMUX_PMUXE_E;  // Enable PMUX group E Even pins
       
    /* Debug code to make sure timer is running at desired freqeuncy
       TCC1->INTENSET.bit.OVF = 1;          // enable overfollow
    
    
       // Enable InterruptVector
       NVIC_SetPriority( TCC1_IRQn, 1 << ( __NVIC_PRIO_BITS + 1 ) );
       NVIC_EnableIRQ( TCC1_IRQn );
    */
    
    
       TCC1->CTRLA.reg |= TCC_CTRLA_ENABLE | // Enable!
                          TCC_CTRLA_PRESCALER( TCC_CTRLA_PRESCALER_DIV2_Val ) | // divide GCLK by 2 for 24mhz
                          TCC_CTRLA_RUNSTDBY;
       while ( TCC1->SYNCBUSY.bit.ENABLE ); 
    }
    
    
    void setup( ) 
    {
       // init globals
       for ( ADCAccumulationCount = 0; ADCAccumulationCount < ADC_AVERAGING_SIZE; ADCAccumulationCount++ )
       {
          ADCAccumulation[ ADCAccumulationCount ] = 0;
       }
       ADCAccumulationCount = 0;
       AccumulationSum = 0;
       LastTime = 0;
       Input = 0; 
       Output = 0;
    #if !USE_MANUAL
       ITerm = 0; 
       LastErr = 0;
       LastInput = 0;
    #endif
    
    
    /* debug code
       OverFlowCount = 0;
       OverFlowMicros = 0;
       LastOverFlowMicros = 0;
       DeltaOverFlowMicros = 0;
    */
    
    
    #if !USE_MANUAL
       // Setup PID
       //SetTunings( PROPORTIONAL_GAIN_MAJOR, PROPORTIONAL_GAIN_MINOR, INTEGRAL_GAIN, DERIVATIVE_GAIN );
       SetTunings( INTEGRAL_GAIN, DERIVATIVE_GAIN );
       Initialize( );
    #endif
       
       // Setup ADC
       SetupADC( );
       
       // Setup PWM
       SetupPWM( );
    }
    
    
    void loop( ) 
    {
       // put your main code here, to run repeatedly:
       //remember to check ADC correction values!
       // compute PID values if the time has come and update
       // the PWM output
       Compute( );
    }

  10. #60
    Join Date
    Jan 2010
    Location
    SoCal
    Posts
    141,243
    Rep Points
    42,922.3
    Mentioned
    2458 Post(s)
    Rep Power
    430


    Yes Reputation No
    Click here to enlarge Originally Posted by shushikiary Click here to enlarge
    Source code for Arduino Zero, M0, or M0 pro if anyone is interested:
    I have no idea what you did but would you mind explaining it to those of us trying to understand the purpose?

  11. #61
    Join Date
    Sep 2014
    Posts
    289
    Rep Points
    509.3
    Mentioned
    5 Post(s)
    Rep Power
    6



    Yes Reputation No
    What level of detail are you looking for? The comments in the code are fairly explanatory. Are you looking for a general overview or a step by step explanation of each line of code?

    As an overview the arduino reads the voltage from the low pressure fuel sensor using its ADC, which it uses to feed into a multi P gain PID which then spits out what desired duty cycle for a PWM output to run the two walbro 450 pumps in parallel should need to hopefully meet the target PSI (which is a programmable value). If its not enough or too much the PID changes the output value to try to meet the desired set point, repeat continuously.

    Most DC motors respond very well to PWM control, usually with a linear RPM to duty cycle response, however pressure or flow to RPM response is dependent on pump design. A peristaltic pump would be purely linear for flow to RPM, where as a centrifugal pump would be non linear. I'm not sure of the response curve for the pumps so I just had to spend a ton of time tuning the PID values until I got decent results.

  12. #62
    Join Date
    Nov 2012
    Posts
    2,492
    Rep Points
    1,939.2
    Mentioned
    199 Post(s)
    Rep Power
    0


    Yes Reputation No
    Very nice work! Also, well done on the Arduino code, you are much better at that than I haha. Click here to enlarge I needed to rely on some helper libraries for timers and pwm.

  13. #63
    Join Date
    Sep 2014
    Posts
    289
    Rep Points
    509.3
    Mentioned
    5 Post(s)
    Rep Power
    6



    1 out of 1 members liked this post. Yes Reputation No
    I just found a bug

    if ( kpToUse = 0 ) should be == zero. I'll have to fix that.

    The data sheets on these microprocessors can be very confusing sometimes, so I wouldnt be down on your self for using the libraries at all.

    I had a heck of a time trying to write debug code to read the count register. Turns out that you have to set a request bit to load it, wait for it to clear, but because the APB bus is much slower than the processor you need a delay, then you have to wait for the count read status bit to clear then you can read the register, but I just threw out that debug code and chose to time the interrupts instead.

  14. #64
    Join Date
    Jan 2015
    Location
    UK
    Posts
    1,110
    Rep Points
    1,534.6
    Mentioned
    42 Post(s)
    Rep Power
    16


    Yes Reputation No
    Very impressive. I don't understand the PID stuff myself but clearly you've done a great job.
    Best:11.79@119mph on stock turbos.
    11.74@129 on GCs.
    FBO+Meth Port injection, GC Turbos, custom bucketless stage2, JB4, Trebila flash.

  15. #65
    Join Date
    Sep 2014
    Posts
    289
    Rep Points
    509.3
    Mentioned
    5 Post(s)
    Rep Power
    6



    2 out of 2 members liked this post. Yes Reputation No
    I found an issue where on cold start the pressure would drop to 0 during idle then spike back up. Its not a bug in the firmware, its output goes to max. Its either an issue with the megamoto at cold, or with the pumps at startup.

    The issue around it that's working so far is to run the pumps at 100% for the first 2.5 minutes after power on. It warms things up. I also further refined the PID tunings and added a multi step negative and positive gain for both P and I gains. It still oscilates at low load, but at high load it fallows about 62 psi +- 2 psi. The balance of low load stability vs high load quick response is hard to fix.

    One possible solution would be to tap into the throttle fly by wire and use its input to set a base duty cycle (essentially load based), where you have a base load at no throttle and just increase the duty cycle based on load request. I tried to look up which pin on the DME this is, but there are multiple pins labeled as the throttle so it was kind of confusing and didnt put much more time into it. If someone knows which pin it is, and if the throttle is just a pot or what so I can know its output signal then I'd be more than happy to try that.

    Anyways here's the cleaned up and working code, I'll change it more as needed if needed.

    Code:
    /*Includes*/#include "Arduino.h"
    
    
    /*defines*/
    #define ADC_RESOLUTION_BITS ADC_CTRLB_RESSEL_12BIT_Val // must use values defined in adc.h
    #define ADC_OFFSET_CORRECTION 13 // these values were calibrated for this device using correctADCResponse.ino
    #define ADC_GAIN_CORRECTION 2068
    #define ADC_AVERAGING_SIZE 64 // this could be done in hardware, but this allows for a windowed average
    
    
    #define DERIVATIVE_GAIN 0.0002
    
    
    #define SAMPLE_TIME 100 // this is in micro seconds
    #define SETPOINT 1950 // This is the ADC count that is the desired PSI 
                          // The math on this is ugly, a voltage divider is needed to convert 5v to 3.3v
                          // due to the max readability of the ADC, The divider converts 5 to 3.3 linearly
                          // The sensor puts out 0psi at 0.498 volts and 145 psi at 4.6 volts, 2.5 volts being 72 psi
                          // 2.5 volts converts to 1.6131 with the voltage divider (measured values with ohm meter
                          // its slightly lower than 3.3 at 5v) which is 2002 of the 4096 ADC counts, so setting slightly
                          // lower to get around 70 psi
    #define PWM_TOP 1200 // 1200 counts for PWM output, aka 1200 steps for 100% duty cycle (creats 20khz output)
    #define OUT_MAX 1200.0 // 100% duty cycle
    #define OUT_MIN 180.0 // minimum 15% duty cycle
    #define NUM_P 6
    #define NUM_I 6
    #define NUM_BREAK 5
    
    
    #define START_UP_TIME 120000000u // for the first 45 seconds the minimum output will be kept to START_UP_MIN
    #define START_UP_MIN 1200.0 // 100%
    #define FALSE 0
    #define TRUE 1
    
    
    
    
    
    
    /*working variables*/
    unsigned long LastTime;
    bool StartTimePassed;
    double ITerm, LastErr, LastInput;
    double kd;
    double kpPosErr[NUM_P] = {1, 1.4, 1.8, 2.2, 2.6, 3};
    double kpNegErr[NUM_P] = {0.5, 0.7, 0.9, 1.1, 1.3, 1.5};
    double kiPosErr[NUM_I] = {2, 3.2, 4.4, 5.6, 6.8, 8};
    double kiNegErr[NUM_I] = {1, 1.6, 2.2, 2.8, 3.6, 4};
    double kBreak[NUM_BREAK] = {66, 132, 198, 264, 331};
    unsigned long ADCAccumulationCount;
    unsigned long ADCAccumulation[ ADC_AVERAGING_SIZE ];
    unsigned long AccumulationSum;
    double Output, Input, Error;
    
    
    
    
    /* debug code
    unsigned long OverFlowCount;
    unsigned long OverFlowMicros;
    unsigned long LastOverFlowMicros;
    unsigned long DeltaOverFlowMicros;
    */ 
    
    
    // ADC interrupt handler (already placed by IDE, just needed function definition)
    void ADC_Handler( void )
    {
       if( ADCAccumulationCount >= ADC_AVERAGING_SIZE )
       {
          ADCAccumulationCount = 0;
       }
    
    
       ADCAccumulation[ ADCAccumulationCount ] = (unsigned long)ADC->RESULT.reg; // note reading this reg clears the interrupt bit as well
       ADCAccumulationCount++;
    }
    
    
    /* Debug code to make sure timmer is running at desired frequency
    void TCC1_Handler( void )
    {
       OverFlowCount++;
       OverFlowMicros = micros( );
       DeltaOverFlowMicros = OverFlowMicros - LastOverFlowMicros;
       LastOverFlowMicros = OverFlowMicros;
       TCC1->INTFLAG.bit.OVF = 1; // clear interrupt flag
    }
    */
    void Initialize( void )
    {
       LastInput = Input;
       ITerm = Output;
       if ( ITerm > OUT_MAX )
       {
          ITerm = OUT_MAX;
       }
       else if ( ITerm < OUT_MIN )
       {
          ITerm= OUT_MIN;
       }
    }
    
    
    void Compute( void )
    {
       /*How long since we last calculated*/
       unsigned long now = micros( );
       unsigned long timeChange = ( now - LastTime ); // Note on overflow of micros this becomes a VERY large positive number
                                                      // thus causing us to compute immediately, so we might have
                                                      // a compute done with less than 100 micro seconds between them
                                                      // every 71 minutes. 
       double error, dInput;
       double kpToUse = 0;
       double kiToUse = 0;
       unsigned int i;
    
    
    
    
       if ( timeChange >= SAMPLE_TIME )
       {
          AccumulationSum = 0;
          // calculate input from average of sample buffer
          for ( unsigned int i = 0; i < ADC_AVERAGING_SIZE; i++ )
          {
             AccumulationSum += ADCAccumulation[i];
          }
    
    
          Input = ( ( double ) AccumulationSum ) / ( ( double ) ADC_AVERAGING_SIZE );
    /* debug code
          SerialUSB.println("\r\n Input read as: ");
          SerialUSB.print( Input );
    */
          /*Compute all the working error variables*/
          error = ( ( double ) SETPOINT ) - Input;
    
    
          for ( i = 0; i < NUM_BREAK; i++ )
          {
             if ( error >= 0 )
             {
                if ( error <= kBreak[i] )
                {
                   kpToUse = kpPosErr[i];
                   kiToUse = kiPosErr[i];
                   break;
                }
             }
             else
             {
                if ( ( ( error ) > 0 ? ( error ) : -( error ) ) <= kBreak[i] )
                {
                   kpToUse = kpNegErr[i];
                   kiToUse = kiNegErr[i];
                   break;
                }
             }
          }
    
    
          // if the error was larger than the last kpBreak[]
          if ( error >= 0 )
          {
             if ( kpToUse == 0 )
             {
                // use the largest value
                kpToUse = kpPosErr[(NUM_P - 1)];
             }
    
    
             if ( kiToUse == 0 )
             {
                kiToUse = kiPosErr[(NUM_I - 1)];
             }
          }
          else
          {
             if ( kpToUse == 0 )
             {
                // use the largest value
                kpToUse = kpNegErr[(NUM_P - 1)];
             }
    
    
             if ( kiToUse == 0 )
             {
                kiToUse = kiNegErr[(NUM_I - 1)];
             }
          }
    
    
          ITerm += (kiToUse * error);
          
          if ( ITerm > OUT_MAX ) 
          {
             ITerm = OUT_MAX;
          }
          else if ( ITerm < OUT_MIN )
          {
             ITerm = OUT_MIN;
          }
          
          dInput = ( Input - LastInput );
     
          /*Compute PID Output*/
          
          Output = kpToUse * error + ITerm - kd * dInput;
          
          if ( Output > OUT_MAX )
          {
             Output = OUT_MAX;
          }
          else if ( Output < OUT_MIN )
          {
             Output = OUT_MIN;
          }
    
    
          if ( !StartTimePassed && ( now <= START_UP_TIME ) && ( Output <= START_UP_MIN ) )
          {
             //Output = START_UP_MIN;
             //ITerm = START_UP_MIN;
          }
          else if ( !StartTimePassed && ( now > START_UP_TIME ) )
          {
             StartTimePassed = TRUE;
          }
          
    /* debug code
          SerialUSB.println("\r\n Output PWM: ");
          SerialUSB.print( Output );
    
    
          SerialUSB.println("\r\n TCC1 Overflow count: ");
          SerialUSB.print( OverFlowCount );
    
    
          SerialUSB.println("\r\n TCC1 Overflow Micros: ");
          SerialUSB.print( DeltaOverFlowMicros );
    */
    
    
          TCC1->CC[0].reg = TCC_CC_CC( ( unsigned long ) Output ); // Set PWM output %
          while ( TCC1->SYNCBUSY.bit.CC0 );
    
    
          /*Remember some variables for next time*/
          LastInput = Input;
          LastTime = now;
       }
    }
    
    
    void SetTunings( double Kd )
    {
       double SampleTimeInSec = ( ( double )SAMPLE_TIME ) / 1000000; // divide by 1 million to get micro seconds
       unsigned long i;
    
    
       for( i = 0; i < NUM_P ; i++ )
       {
    
    
          kiPosErr[i] = kiPosErr[i] * SampleTimeInSec;
          kiNegErr[i] = kiNegErr[i] * SampleTimeInSec;
       }
       
       kd = Kd / SampleTimeInSec;
    }
    
    
    void SetupADC( void )
    {
       // first disable DAC
       while ( DAC->STATUS.bit.SYNCBUSY );
       DAC->CTRLA.bit.ENABLE = 0x00; // Disable DAC
       while ( DAC->STATUS.bit.SYNCBUSY );
       
       while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY );
    
    
       GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID( GCM_ADC ) | // Generic Clock ADC
                           GCLK_CLKCTRL_GEN_GCLK0     | // Generic Clock Generator 0 is source
                           GCLK_CLKCTRL_CLKEN;          // Enable! Gen CLK 0 is set to 48mhz clk by samd setup
       while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY );
    
    
       // setup power manager for ADC
       PM->APBCMASK.reg |= PM_APBCMASK_ADC;
    
    
       while ( ADC->STATUS.bit.SYNCBUSY );          // Wait for synchronization of registers between the clock domains
       
       // Set correction values
       ADC->OFFSETCORR.reg = ADC_OFFSETCORR_OFFSETCORR( ADC_OFFSET_CORRECTION );
       ADC->GAINCORR.reg = ADC_GAINCORR_GAINCORR( ADC_GAIN_CORRECTION );
    
    
       // Enable digital correction logic, calibration was already setup by samd setup
       ADC->CTRLB.reg = ADC_CTRLB_RESSEL( ADC_RESOLUTION_BITS ) | // set resolution
                        ADC_CTRLB_CORREN | // enable correction
                        ADC_CTRLB_PRESCALER( ADC_CTRLB_PRESCALER_DIV16_Val ); // clock div 16
       while ( ADC->STATUS.bit.SYNCBUSY );
    
    
       ADC->REFCTRL.reg = ADC_REFCTRL_REFSEL( ADC_REFCTRL_REFSEL_INTVCC1_Val ) | // use internal 1.65 volt reference
                          ADC_REFCTRL_REFCOMP; // use reference compensation
       while ( ADC->STATUS.bit.SYNCBUSY );
    
    
       ADC->AVGCTRL.reg = 0; // no averaging (take 1 sample), divide by 1
       while ( ADC->STATUS.bit.SYNCBUSY );
    
    
       ADC->SAMPCTRL.reg = ADC_SAMPCTRL_SAMPLEN( ( 2 * ADC_RESOLUTION_BITS ) - 1 ); // sample length is equal to ADC clock cycles needed for conversion
       while ( ADC->STATUS.bit.SYNCBUSY );
    
    
       ADC->WINCTRL.reg = 0; // no windowing
       while ( ADC->STATUS.bit.SYNCBUSY );
    
    
       // setup PA02_AIN0 note: pin 3 on chip, A0 on arduino header
       PORT->Group[PORTA].PINCFG[2].bit.PMUXEN = 1; // Peripheral mux enable 
       PORT->Group[PORTA].PMUX[1].reg = PORT_PMUX_PMUXE_B;  // Enable PMUX group B Even pins
    
    
       ADC->INPUTCTRL.reg = ADC_INPUTCTRL_MUXPOS( ADC_INPUTCTRL_MUXPOS_PIN0_Val ) | // Selection for the positive ADC input (AIN0)
                            ADC_INPUTCTRL_MUXNEG( ADC_INPUTCTRL_MUXNEG_GND_Val ) | // set mux neg to internal ground
                            ADC_INPUTCTRL_GAIN( ADC_INPUTCTRL_GAIN_DIV2_Val ); // set gain to 1/2 for larger range (max sadly is 3.3 volts)
       while ( ADC->STATUS.bit.SYNCBUSY );
       
       ADC->CTRLA.bit.ENABLE = 0x01;             // Enable ADC
       while ( ADC->STATUS.bit.SYNCBUSY );
       
       // Start conversion
       ADC->SWTRIG.bit.START = 1; // first conversion has to be thrown way
    
    
       // Clear the Data Ready flag
       ADC->INTFLAG.reg = ADC_INTFLAG_RESRDY;
       while ( ADC->INTFLAG.bit.RESRDY == 0 ); // wait for conversion completion
    
    
       ADC->INTFLAG.reg = ADC_INTFLAG_RESRDY; // clear flag again
       while ( ADC->STATUS.bit.SYNCBUSY );
       
       // enbable interrupt on result ready
       ADC->INTENSET.bit.RESRDY = 1;
       while ( ADC->STATUS.bit.SYNCBUSY );
    
    
       NVIC_SetPriority( ADC_IRQn, 1 << __NVIC_PRIO_BITS ); // use the next highest priority not already used
       NVIC_EnableIRQ( ADC_IRQn );
    
    
       // Set for free run 
       ADC->CTRLB.bit.FREERUN = 1;
       while ( ADC->STATUS.bit.SYNCBUSY );
    
    
       ADC->SWTRIG.bit.START = 1; // Start up the free run, all samples will now be handled by ISR
       while ( ADC->STATUS.bit.SYNCBUSY );
    }
    
    
    void SetupPWM( void )
    {
       while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY );
       
       GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID( GCM_TCC0_TCC1 ) | // Generic Clock TCC1
                           GCLK_CLKCTRL_GEN_GCLK0     |       // Generic Clock Generator 0 is source
                           GCLK_CLKCTRL_CLKEN;                // Enable!
       while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY );
    
    
       PM->APBCMASK.reg |= PM_APBCMASK_TCC1; // set power manager for TCC1 peripheral
    
    
       TCC1->CTRLA.reg = TCC_CTRLA_SWRST; // disable/reset TCC1 so that the registers can be written
       while ( TCC1->SYNCBUSY.bit.SWRST );
    
    
       TCC1->WAVE.reg = TCC_WAVE_WAVEGEN( TCC_WAVE_WAVEGEN_NPWM_Val ); // set wave form generation for single slope PWM
       while ( TCC1->SYNCBUSY.bit.WAVE );
       
       TCC1->PER.reg = TCC_PER_PER( PWM_TOP ); // set PWM period to get us 20khz
       while ( TCC1->SYNCBUSY.bit.PER );
    
    
       TCC1->CC[0].reg = TCC_CC_CC( 0 ); // Start out at zero % on TCC1 channel 0
       while ( TCC1->SYNCBUSY.bit.CC0 );
    
    
       TCC1->COUNT.reg = 0; // init count to 0
       while ( TCC1->SYNCBUSY.bit.COUNT );
    
    
       // setup PA06_TCC1/WO[0] note: pin 11 on chip, "digigal PWM ~ 8" on arduino header
       PORT->Group[PORTA].PINCFG[6].bit.PMUXEN = 1; // Peripheral mux enable 
       PORT->Group[PORTA].PMUX[3].reg = PORT_PMUX_PMUXE_E;  // Enable PMUX group E Even pins
       
    /* Debug code to make sure timer is running at desired freqeuncy
       TCC1->INTENSET.bit.OVF = 1;          // enable overfollow
    
    
       // Enable InterruptVector
       NVIC_SetPriority( TCC1_IRQn, 1 << ( __NVIC_PRIO_BITS + 1 ) );
       NVIC_EnableIRQ( TCC1_IRQn );
    */
    
    
       TCC1->CTRLA.reg |= TCC_CTRLA_ENABLE | // Enable!
                          TCC_CTRLA_PRESCALER( TCC_CTRLA_PRESCALER_DIV2_Val ) | // divide GCLK by 2 for 24mhz
                          TCC_CTRLA_RUNSTDBY;
       while ( TCC1->SYNCBUSY.bit.ENABLE ); 
    }
    
    
    void setup( ) 
    {
       // init globals
       for ( ADCAccumulationCount = 0; ADCAccumulationCount < ADC_AVERAGING_SIZE; ADCAccumulationCount++ )
       {
          ADCAccumulation[ ADCAccumulationCount ] = 0;
       }
       ADCAccumulationCount = 0;
       AccumulationSum = 0;
       LastTime = 0;
       Input = 0; 
       Output = OUT_MAX; // start at 100% output to prime pumps
       ITerm = 0; 
       LastErr = 0;
       LastInput = 0;
       StartTimePassed = FALSE;
    
    
    /* debug code
       OverFlowCount = 0;
       OverFlowMicros = 0;
       LastOverFlowMicros = 0;
       DeltaOverFlowMicros = 0;
    */
    
    
       // Setup PID
       SetTunings( DERIVATIVE_GAIN );
       Initialize( );
       
       // Setup ADC
       SetupADC( );
       
       // Setup PWM
       SetupPWM( );
    }
    
    
    void loop( ) 
    {
       // put your main code here, to run repeatedly:
       //remember to check ADC correction values!
       // compute PID values if the time has come and update
       // the PWM output
       Compute( );
    }

  16. #66
    Join Date
    Sep 2014
    Posts
    289
    Rep Points
    509.3
    Mentioned
    5 Post(s)
    Rep Power
    6



    1 out of 1 members liked this post. Yes Reputation No
    Upon further debug, the issue of shutting off was an aliasing issue with the TCC1 CC[0] register being updated strangely out of sink. The hardware has a double buffering capability to prevent update to the CC register until the next period start, I modified the code to utilize this and removed the 100% on timer (simply just commented the key code out). I also changed the PWM frequency to 10khz from 20khz to see if the pumps respond better. It did seem to smooth things out a little bit.

    Sorry for the $#@!ty revision control.

    Code:
    /*Includes*/#include "Arduino.h"
    
    
    /*defines*/
    #define ADC_RESOLUTION_BITS ADC_CTRLB_RESSEL_12BIT_Val // must use values defined in adc.h
    #define ADC_OFFSET_CORRECTION 13 // these values were calibrated for this device using correctADCResponse.ino
    #define ADC_GAIN_CORRECTION 2068
    #define ADC_AVERAGING_SIZE 64 // this could be done in hardware, but this allows for a windowed average
    
    
    #define DERIVATIVE_GAIN 0.0002
    
    
    #define SAMPLE_TIME 100 // this is in micro seconds
    #define SETPOINT 1950 // This is the ADC count that is the desired PSI 
                          // The math on this is ugly, a voltage divider is needed to convert 5v to 3.3v
                          // due to the max readability of the ADC, The divider converts 5 to 3.3 linearly
                          // The sensor puts out 0psi at 0.498 volts and 145 psi at 4.6 volts, 2.5 volts being 72 psi
                          // 2.5 volts converts to 1.6131 with the voltage divider (measured values with ohm meter
                          // its slightly lower than 3.3 at 5v) which is 2002 of the 4096 ADC counts, so setting slightly
                          // lower to get around 70 psi
    #define PWM_TOP 1200 // 1200 counts for PWM output, aka 1200 steps for 100% duty cycle (creats 20khz output)
    #define OUT_MAX 1200.0 // 100% duty cycle
    #define OUT_MIN 180.0 // minimum 15% duty cycle
    #define NUM_P 6
    #define NUM_I 6
    #define NUM_BREAK 5
    
    
    #define START_UP_TIME 150000000u // for the first 45 seconds the minimum output will be kept to START_UP_MIN
    #define START_UP_MIN 1200.0 // 100%
    #define FALSE 0
    #define TRUE 1
    
    
    
    
    
    
    /*working variables*/
    unsigned long LastTime;
    bool StartTimePassed;
    double ITerm, LastErr, LastInput;
    double kd;
    double kpPosErr[NUM_P] = {1, 1.4, 1.8, 2.2, 2.6, 3};
    double kpNegErr[NUM_P] = {0.5, 0.7, 0.9, 1.1, 1.3, 1.5};
    double kiPosErr[NUM_I] = {2, 3.2, 4.4, 5.6, 6.8, 8};
    double kiNegErr[NUM_I] = {1, 1.6, 2.2, 2.8, 3.6, 4};
    double kBreak[NUM_BREAK] = {66, 132, 198, 264, 331};
    unsigned long ADCAccumulationCount;
    unsigned long ADCAccumulation[ ADC_AVERAGING_SIZE ];
    unsigned long AccumulationSum;
    double Output, Input, Error;
    
    
    
    
    /* debug code
    unsigned long OverFlowCount;
    unsigned long OverFlowMicros;
    unsigned long LastOverFlowMicros;
    unsigned long DeltaOverFlowMicros;
    */ 
    
    
    // ADC interrupt handler (already placed by IDE, just needed function definition)
    void ADC_Handler( void )
    {
       if( ADCAccumulationCount >= ADC_AVERAGING_SIZE )
       {
          ADCAccumulationCount = 0;
       }
    
    
       ADCAccumulation[ ADCAccumulationCount ] = (unsigned long)ADC->RESULT.reg; // note reading this reg clears the interrupt bit as well
       ADCAccumulationCount++;
    }
    
    
    /* Debug code to make sure timmer is running at desired frequency
    void TCC1_Handler( void )
    {
       OverFlowCount++;
       OverFlowMicros = micros( );
       DeltaOverFlowMicros = OverFlowMicros - LastOverFlowMicros;
       LastOverFlowMicros = OverFlowMicros;
       TCC1->INTFLAG.bit.OVF = 1; // clear interrupt flag
    }
    */
    void Initialize( void )
    {
       LastInput = Input;
       ITerm = Output;
       if ( ITerm > OUT_MAX )
       {
          ITerm = OUT_MAX;
       }
       else if ( ITerm < OUT_MIN )
       {
          ITerm= OUT_MIN;
       }
    }
    
    
    void Compute( void )
    {
       /*How long since we last calculated*/
       unsigned long now = micros( );
       unsigned long timeChange = ( now - LastTime ); // Note on overflow of micros this becomes a VERY large positive number
                                                      // thus causing us to compute immediately, so we might have
                                                      // a compute done with less than 100 micro seconds between them
                                                      // every 71 minutes. 
       double error, dInput;
       double kpToUse = 0;
       double kiToUse = 0;
       unsigned int i;
    
    
    
    
       if ( timeChange >= SAMPLE_TIME )
       {
          AccumulationSum = 0;
          // calculate input from average of sample buffer
          for ( unsigned int i = 0; i < ADC_AVERAGING_SIZE; i++ )
          {
             AccumulationSum += ADCAccumulation[i];
          }
    
    
          Input = ( ( double ) AccumulationSum ) / ( ( double ) ADC_AVERAGING_SIZE );
    /* debug code
          SerialUSB.println("\r\n Input read as: ");
          SerialUSB.print( Input );
    */
          /*Compute all the working error variables*/
          error = ( ( double ) SETPOINT ) - Input;
    
    
          for ( i = 0; i < NUM_BREAK; i++ )
          {
             if ( error >= 0 )
             {
                if ( error <= kBreak[i] )
                {
                   kpToUse = kpPosErr[i];
                   kiToUse = kiPosErr[i];
                   break;
                }
             }
             else
             {
                if ( ( ( error ) > 0 ? ( error ) : -( error ) ) <= kBreak[i] )
                {
                   kpToUse = kpNegErr[i];
                   kiToUse = kiNegErr[i];
                   break;
                }
             }
          }
    
    
          // if the error was larger than the last kpBreak[]
          if ( error >= 0 )
          {
             if ( kpToUse == 0 )
             {
                // use the largest value
                kpToUse = kpPosErr[(NUM_P - 1)];
             }
    
    
             if ( kiToUse == 0 )
             {
                kiToUse = kiPosErr[(NUM_I - 1)];
             }
          }
          else
          {
             if ( kpToUse == 0 )
             {
                // use the largest value
                kpToUse = kpNegErr[(NUM_P - 1)];
             }
    
    
             if ( kiToUse == 0 )
             {
                kiToUse = kiNegErr[(NUM_I - 1)];
             }
          }
    
    
          ITerm += (kiToUse * error);
          
          if ( ITerm > OUT_MAX ) 
          {
             ITerm = OUT_MAX;
          }
          else if ( ITerm < OUT_MIN )
          {
             ITerm = OUT_MIN;
          }
          
          dInput = ( Input - LastInput );
     
          /*Compute PID Output*/
          
          Output = kpToUse * error + ITerm - kd * dInput;
          
          if ( Output > OUT_MAX )
          {
             Output = OUT_MAX;
          }
          else if ( Output < OUT_MIN )
          {
             Output = OUT_MIN;
          }
    
    
          if ( !StartTimePassed && ( now <= START_UP_TIME ) && ( Output <= START_UP_MIN ) )
          {
             //Output = START_UP_MIN;
             //ITerm = START_UP_MIN;
          }
          else if ( !StartTimePassed && ( now > START_UP_TIME ) )
          {
             StartTimePassed = TRUE;
          }
          
    /* debug code
          SerialUSB.println("\r\n Output PWM: ");
          SerialUSB.print( Output );
    
    
          SerialUSB.println("\r\n ITerm: ");
          SerialUSB.print( ITerm );
    
    
          SerialUSB.println("\r\n TCC1 Overflow count: ");
          SerialUSB.print( OverFlowCount );
    
    
          SerialUSB.println("\r\n TCC1 Overflow Micros: ");
          SerialUSB.print( DeltaOverFlowMicros );
     */
    
    
          // Write the TCC1 CC buffer register to update on the next period
          TCC1->CCB[0].reg = TCC_CCB_CCB( ( unsigned long ) Output ); // Set PWM output %
          while ( TCC1->SYNCBUSY.bit.CCB0 );
    
    
          /*Remember some variables for next time*/
          LastInput = Input;
          LastTime = now;
       }
    }
    
    
    void SetTunings( double Kd )
    {
       double SampleTimeInSec = ( ( double )SAMPLE_TIME ) / 1000000; // divide by 1 million to get micro seconds
       unsigned long i;
    
    
       for( i = 0; i < NUM_P ; i++ )
       {
          kiPosErr[i] = kiPosErr[i] * SampleTimeInSec;
          kiNegErr[i] = kiNegErr[i] * SampleTimeInSec;
       }
       
       kd = Kd / SampleTimeInSec;
    }
    
    
    void SetupADC( void )
    {
       // first disable DAC
       while ( DAC->STATUS.bit.SYNCBUSY );
       DAC->CTRLA.bit.ENABLE = 0x00; // Disable DAC
       while ( DAC->STATUS.bit.SYNCBUSY );
       
       while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY );
    
    
       GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID( GCM_ADC ) | // Generic Clock ADC
                           GCLK_CLKCTRL_GEN_GCLK0     | // Generic Clock Generator 0 is source
                           GCLK_CLKCTRL_CLKEN;          // Enable! Gen CLK 0 is set to 48mhz clk by samd setup
       while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY );
    
    
       // setup power manager for ADC
       PM->APBCMASK.reg |= PM_APBCMASK_ADC;
    
    
       while ( ADC->STATUS.bit.SYNCBUSY );          // Wait for synchronization of registers between the clock domains
       
       // Set correction values
       ADC->OFFSETCORR.reg = ADC_OFFSETCORR_OFFSETCORR( ADC_OFFSET_CORRECTION );
       ADC->GAINCORR.reg = ADC_GAINCORR_GAINCORR( ADC_GAIN_CORRECTION );
    
    
       // Enable digital correction logic, calibration was already setup by samd setup
       ADC->CTRLB.reg = ADC_CTRLB_RESSEL( ADC_RESOLUTION_BITS ) | // set resolution
                        ADC_CTRLB_CORREN | // enable correction
                        ADC_CTRLB_PRESCALER( ADC_CTRLB_PRESCALER_DIV16_Val ); // clock div 16
       while ( ADC->STATUS.bit.SYNCBUSY );
    
    
       ADC->REFCTRL.reg = ADC_REFCTRL_REFSEL( ADC_REFCTRL_REFSEL_INTVCC1_Val ) | // use internal 1.65 volt reference
                          ADC_REFCTRL_REFCOMP; // use reference compensation
       while ( ADC->STATUS.bit.SYNCBUSY );
    
    
       ADC->AVGCTRL.reg = 0; // no averaging (take 1 sample), divide by 1
       while ( ADC->STATUS.bit.SYNCBUSY );
    
    
       ADC->SAMPCTRL.reg = ADC_SAMPCTRL_SAMPLEN( ( 2 * ADC_RESOLUTION_BITS ) - 1 ); // sample length is equal to ADC clock cycles needed for conversion
       while ( ADC->STATUS.bit.SYNCBUSY );
    
    
       ADC->WINCTRL.reg = 0; // no windowing
       while ( ADC->STATUS.bit.SYNCBUSY );
    
    
       // setup PA02_AIN0 note: pin 3 on chip, A0 on arduino header
       PORT->Group[PORTA].PINCFG[2].bit.PMUXEN = 1; // Peripheral mux enable 
       PORT->Group[PORTA].PMUX[1].reg = PORT_PMUX_PMUXE_B;  // Enable PMUX group B Even pins
    
    
       ADC->INPUTCTRL.reg = ADC_INPUTCTRL_MUXPOS( ADC_INPUTCTRL_MUXPOS_PIN0_Val ) | // Selection for the positive ADC input (AIN0)
                            ADC_INPUTCTRL_MUXNEG( ADC_INPUTCTRL_MUXNEG_GND_Val ) | // set mux neg to internal ground
                            ADC_INPUTCTRL_GAIN( ADC_INPUTCTRL_GAIN_DIV2_Val ); // set gain to 1/2 for larger range (max sadly is 3.3 volts)
       while ( ADC->STATUS.bit.SYNCBUSY );
       
       ADC->CTRLA.bit.ENABLE = 0x01;             // Enable ADC
       while ( ADC->STATUS.bit.SYNCBUSY );
       
       // Start conversion
       ADC->SWTRIG.bit.START = 1; // first conversion has to be thrown way
    
    
       // Clear the Data Ready flag
       ADC->INTFLAG.reg = ADC_INTFLAG_RESRDY;
       while ( ADC->INTFLAG.bit.RESRDY == 0 ); // wait for conversion completion
    
    
       ADC->INTFLAG.reg = ADC_INTFLAG_RESRDY; // clear flag again
       while ( ADC->STATUS.bit.SYNCBUSY );
       
       // enbable interrupt on result ready
       ADC->INTENSET.bit.RESRDY = 1;
       while ( ADC->STATUS.bit.SYNCBUSY );
    
    
       NVIC_SetPriority( ADC_IRQn, 1 << __NVIC_PRIO_BITS ); // use the next highest priority not already used
       NVIC_EnableIRQ( ADC_IRQn );
    
    
       // Set for free run 
       ADC->CTRLB.bit.FREERUN = 1;
       while ( ADC->STATUS.bit.SYNCBUSY );
    
    
       ADC->SWTRIG.bit.START = 1; // Start up the free run, all samples will now be handled by ISR
       while ( ADC->STATUS.bit.SYNCBUSY );
    }
    
    
    void SetupPWM( void )
    {
       while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY );
       
       GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID( GCM_TCC0_TCC1 ) | // Generic Clock TCC1
                           GCLK_CLKCTRL_GEN_GCLK0     |       // Generic Clock Generator 0 is source
                           GCLK_CLKCTRL_CLKEN;                // Enable!
       while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY );
    
    
       PM->APBCMASK.reg |= PM_APBCMASK_TCC1; // set power manager for TCC1 peripheral
    
    
       TCC1->CTRLA.reg = TCC_CTRLA_SWRST; // disable/reset TCC1 so that the registers can be written
       while ( TCC1->SYNCBUSY.bit.SWRST );
    
    
       TCC1->WAVE.reg = TCC_WAVE_WAVEGEN( TCC_WAVE_WAVEGEN_NPWM_Val ); // set wave form generation for single slope PWM
       while ( TCC1->SYNCBUSY.bit.WAVE );
    
    
       TCC1->PER.reg = TCC_PER_PER( PWM_TOP ); // set PWM period to get us 20khz
       while ( TCC1->SYNCBUSY.bit.PER );
    
    
       TCC1->CC[0].reg = TCC_CC_CC( 0 ); // Start out at zero % on TCC1 channel 0
       while ( TCC1->SYNCBUSY.bit.CC0 );
    
    
       TCC1->CTRLBCLR.reg = TCC_CTRLBCLR_LUPD; // Enable double buffering so PWM output isnt updated until next bottom
       while ( TCC1->SYNCBUSY.bit.CTRLB );
    
    
       TCC1->COUNT.reg = 0; // init count to 0
       while ( TCC1->SYNCBUSY.bit.COUNT );
    
    
       // setup PA06_TCC1/WO[0] note: pin 11 on chip, "digigal PWM ~ 8" on arduino header
       PORT->Group[PORTA].PINCFG[6].bit.PMUXEN = 1; // Peripheral mux enable 
       PORT->Group[PORTA].PMUX[3].reg = PORT_PMUX_PMUXE_E;  // Enable PMUX group E Even pins
       
    /* Debug code to make sure timer is running at desired freqeuncy
       TCC1->INTENSET.bit.OVF = 1;          // enable overfollow
    
    
       // Enable InterruptVector
       NVIC_SetPriority( TCC1_IRQn, 1 << ( __NVIC_PRIO_BITS + 1 ) );
       NVIC_EnableIRQ( TCC1_IRQn );
    */
    
    
       TCC1->CTRLA.reg |= TCC_CTRLA_ENABLE | // Enable!
                          TCC_CTRLA_PRESCALER( TCC_CTRLA_PRESCALER_DIV2_Val ) | // divide GCLK by 2 for 24mhz
                          TCC_CTRLA_RUNSTDBY;
       while ( TCC1->SYNCBUSY.bit.ENABLE ); 
    }
    
    
    void setup( ) 
    {
       // init globals
       for ( ADCAccumulationCount = 0; ADCAccumulationCount < ADC_AVERAGING_SIZE; ADCAccumulationCount++ )
       {
          ADCAccumulation[ ADCAccumulationCount ] = 0;
       }
       ADCAccumulationCount = 0;
       AccumulationSum = 0;
       LastTime = 0;
       Input = 0; 
       Output = OUT_MAX; // start at 100% output to prime pumps
       ITerm = 0; 
       LastErr = 0;
       LastInput = 0;
       StartTimePassed = FALSE;
    
    
    /* debug code
       OverFlowCount = 0;
       OverFlowMicros = 0;
       LastOverFlowMicros = 0;
       DeltaOverFlowMicros = 0;
    */
    
    
       // Setup PID
       SetTunings( DERIVATIVE_GAIN );
       Initialize( );
       
       // Setup ADC
       SetupADC( );
       
       // Setup PWM
       SetupPWM( );
    }
    
    
    void loop( ) 
    {
       // put your main code here, to run repeatedly:
       //remember to check ADC correction values!
       // compute PID values if the time has come and update
       // the PWM output
       Compute( );
    }

  17. #67
    Join Date
    Sep 2014
    Posts
    289
    Rep Points
    509.3
    Mentioned
    5 Post(s)
    Rep Power
    6



    Yes Reputation No
    Long term update on this.

    Its working wonderfully. Under load my LPFP doesnt go under 60 PSI, and it bounces around by about +- 8 psi under idle.

    I did have to change my driving circuit as the LED on the input signal to the megamoto pulled too much current, so I switched to an open collector design using a darlington pair and a separate 5v supply that can easily supply several amps.

  18. #68
    Join Date
    Jun 2015
    Posts
    1
    Rep Points
    0.1
    Mentioned
    0 Post(s)
    Rep Power
    0


    Yes Reputation No
    Hi friend. Cool work. give me a wiring diagram. thanks

Page 3 of 3 FirstFirst 123

Tags for this Thread

Posting Permissions

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