This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

AD conversion resets PWM settings

Other Parts Discussed in Thread: MSP430G2231

Hi,

I use the 2231 on the launchpad to read 2 A/D ch. on P1.4 and P1.5 and set PWM on P1.6 (green LED).

When the A/D reads the ports the PWM goes on and off with no reason.

How can we separate the PWM and the AD operations?

 

I use the following code:

Init code:

// init system ports
void init_sys(void){
    WDTCTL = WDTPW + WDTHOLD; // Stop WDT
    P1DIR |= BIT0 + BIT6; // Set P1.0 and P1.6 to output direction
}

// init A/D global parameters
void init_AD(void){
    ADC10CTL0 = SREF_1 + ADC10SHT_2 + REFON + ADC10ON + ADC10IE;
}

// init PWM global parameters
void init_PWM(){
    P1SEL |= BIT6; // P1.6 to TA0.1 PWM
    CCR0 = period; // PWM period
    CCTL1 = OUTMOD_3; // CCR1 reset/set
    CCR1 = period - 1; // CCR1 duty cycle
    TACTL = TASSEL_2 + MC_1; // SMCLK, up mode
}

Run code:

// set PWM duty cycle
void set_PWM(int duty_cycle){
    CCR1 = duty_cycle; // set new on time
    TACTL = TASSEL_2 + MC_1; // SMCLK, up mode
    __delay_cycles (10); // delay program execution 
}

// get A/D reading from channel ch after avergng 2^avg counts, return result as integer
int get_ad_ch(int ch, int avg)
{
    long int VolSum;
    int i, count, ad_ch;
  
    switch(ch){  // input channel
        case 0:     {ADC10CTL1 = INCH_0;    break;}
        case 1:     {ADC10CTL1 = INCH_1;    break;}
        case 2:     {ADC10CTL1 = INCH_2;    break;}
        case 3:     {ADC10CTL1 = INCH_3;    break;}
        case 4:     {ADC10CTL1 = INCH_4;    break;}
        case 5:     {ADC10CTL1 = INCH_5;    break;}
        case 6:     {ADC10CTL1 = INCH_6;    break;}
        case 7:     {ADC10CTL1 = INCH_7;    break;}
        case 8:     {ADC10CTL1 = INCH_8;    break;}
        case 9:     {ADC10CTL1 = INCH_9;    break;}
        case 10: {ADC10CTL1 = INCH_10;    break;}
        case 11: {ADC10CTL1 = INCH_11;    break;}
        case 12: {ADC10CTL1 = INCH_12;    break;}
        case 13: {ADC10CTL1 = INCH_13;    break;}
        case 14: {ADC10CTL1 = INCH_14;    break;}
        case 15: {ADC10CTL1 = INCH_15;    break;}
    } // switch.
    ad_ch = 0x01 << ch;
    ADC10AE0 |= ad_ch;                  // PA.1 ADC option select
    count = 0x01 << avg;
    VolSum = 0;
    for (i=0; i < count; i++){
        ADC10CTL0 |= ENC + ADC10SC;         // Sampling and conversion start
          VolSum += ADC10MEM;
      } // for i
      // VolSum = VolSum >> avg;                    // divide by 2^avg
    return VolSum >> avg;
}

  • Sinay Goldberg said:
    the PWM goes on and off with no reason.

    Oh, there is a reason. You just don't know it :)

    You didn't post the code that actually calls the get_ad_ch function.

    On other MSPs (and on pin 1.4) the schematic shows that settign the ADC10AE0.y bit will disable the output driver. On P.6 the diagram only shows that the bus keeper is enabled, but this makes no sense, so I suspect a bug in the diagram here (teh shortcut between input and output next to the bus keeper seems to be nonsense too)

    My best guess is that when you set the ADC10AE0.6 bit, and the channel is sampled, the digital PWM output is disabled.

    Sinay Goldberg said:
    ADC10AE0 |= ad_ch;                  // PA.1 ADC option select

    This sets the bt for the currently use dchannel, but it never clears it  ever again.

    Also, ADC10AE0 is an 8 bit register, only covering the controls for channel 0..7. Your switch, however, indicates that (external) channels 12..15 should be supported too. Those control bits go into ADC10AE1.

    Sinay Goldberg said:
    case 14: {ADC10CTL1 = INCH_14;    break;}

    Hmmm, I never tried... Does the break break off the case or does it just break off the code block in the brackets, falling through to the next case? I never enclosed teh case code in a code block. the case:...break; already does the proper enclosure.

     

  • Hello Jens-Michael,

    Thank you for a very detailed answer.

    regarding the points you mentioned:

    get_ad_ch is listed and posted in the first post, here it is again:

    // get A/D reading from channel ch after avergng 2^avg counts, return result as integer
    int get_ad_ch(int ch, int avg)
    {
        long int VolSum;
        int i, count, ad_ch;
      
        switch(ch){  // input channel
            case 0:     {ADC10CTL1 = INCH_0;    break;}
            case 1:     {ADC10CTL1 = INCH_1;    break;}
            case 2:     {ADC10CTL1 = INCH_2;    break;}
            case 3:     {ADC10CTL1 = INCH_3;    break;}
            case 4:     {ADC10CTL1 = INCH_4;    break;}
            case 5:     {ADC10CTL1 = INCH_5;    break;}
            case 6:     {ADC10CTL1 = INCH_6;    break;}
            case 7:     {ADC10CTL1 = INCH_7;    break;}
            case 8:     {ADC10CTL1 = INCH_8;    break;}
            case 9:     {ADC10CTL1 = INCH_9;    break;}
            case 10: {ADC10CTL1 = INCH_10;    break;}
            case 11: {ADC10CTL1 = INCH_11;    break;}
            case 12: {ADC10CTL1 = INCH_12;    break;}
            case 13: {ADC10CTL1 = INCH_13;    break;}
            case 14: {ADC10CTL1 = INCH_14;    break;}
            case 15: {ADC10CTL1 = INCH_15;    break;}
        } // switch.
        ad_ch = 0x01 << ch;
        ADC10AE0 |= ad_ch;                  // PA.1 ADC option select
        count = 0x01 << avg;
        VolSum = 0;
        for (i=0; i < count; i++){
            ADC10CTL0 |= ENC + ADC10SC;         // Sampling and conversion start
              VolSum += ADC10MEM;
          } // for i
          // VolSum = VolSum >> avg;                    // divide by 2^avg
        return VolSum >> avg;
    }

    The case nn: { ... break;} seems to work properly, at least at the debugger.

    I'll look into you suggestions and see which one works.

    Do you have a suggestion how to read few A/D without "cross-talking" the PWM?

    Sinay

  • Sinay Goldberg said:
    get_ad_ch is listed and posted in the first post

    Yes, but not the code that calls it. SO the snipped above are all fine and well, bu tI don't knwo whether there is somehting connectign them in a order that can cause trouble.

    One more thing I noticed:

    Sinay Goldberg said:
            ADC10CTL0 |= ENC + ADC10SC;         // Sampling and conversion start
              VolSum += ADC10MEM;


    Here you start the conversiona nd immediately read teh conversion result. 2^avg times. But which result? The conversion takes some time. It isn't finished when you read the result register. So you're reading the last conversions result over and over again, startign new conversions while the previous one isn't finished (which doe sno harm and is just ignored), and your average value consists of 2^avg reading but from only a few conversions. For avg=0, you 'll even read and return a previous result and perhaps the next call of this function will return the value you expected to get on the last call.
    YOu need to clear the interrupt flag, then start the new conversion and then wait until the interrupt flag comes up again, signalling that the converison is complete.

  • Hello Jens-Michael,

    I implemented your comments (at least those I know how). Your last comment as well, which explained correlation I found in the voltage and current (see below) readings.

    My application is solar panel MPPT algorithm, and it requires setting PWM duty cycle, followed by reading voltage and current (using shunt resistor) on 2 A/D ports.

    I tried different ways. It turned out, by experimenting,  that I need to "flip" both channel dirction and go via ch. 0 to make the A/D work the way I wanted. Same for the PWM operation. It works, but I'm not happy with the solution and I'll try "clean" extra lines (memory is low too).

    I'm still a C novice (started with turbo pascal 3), and my electronics design is still noisy (reason for measurements averaging), so the I'm not sure how good is the algorithm (in find_MPPT) which is the goal of this project.

    Sinay

    Below is my recent A/D reading function followed by the complete source:

    //************************************************************************
    int get_v(unsigned int ch){
        int res;
        res = 0x01 << ch; // mark meas. ch. bit
        P1DIR |= res; // set ch. for output
        res = 0xFF ^ res; // invert meas. ch.
        P1DIR &= res; // set ch. for input
        ADC10CTL0 = 0x00; // reset ADC10CTL0
        ADC10CTL1 = (0*0x1000u); // select ch. 0
        ADC10CTL1 = (ch*0x1000u); // select meas. ch.
        ADC10CTL0 |= ADC10ON + ADC10SHT_0; // AD on + single ch conversion
        ADC10CTL0 |= REF2_5V + SREF_1 + REFON;    // 2.5 V internal ref
        ADC10CTL0 |= ENC + ADC10SC;             // Sampling and conversion start
        while (ADC10CTL1 & ADC10BUSY);          // wait for ADC10BUSY?
        res = ADC10MEM;  // read ad results
        return res;
    }

    Complete source (main.c):

    //******************************************************************************
    //  Perform MPPT on small solar cell (2.3 V 25 mA) using full scan of
    //  Input:
    //  P1.4 measure cell output voltage
    //  P1.5 measure current via 10 ohm resistor in serial to switching transistor
    //  Output:
    //  P1.6 switches the transistor using PWM
    //  P1.0 On/Off to show activity
    //  Other settings:
    //  ADC10, 2.5V Ref
    //******************************************************************************

    #include "msp430G2231.h"

    #define span 13        // around peak search span
    #define h_span 7    // half (span+1)
    #define PWM_p 16328    // PWM period

    void init(void);
    void init_pwm(unsigned int period);
    void set_pwm_dc(unsigned int duty_cycle);
    int get_v(unsigned int ch);
    double get_p(int avg);
    int find_mppt(void);
    int find_mppt2(double start_p);

    double curr, volt, power;
    double r[span];

    void main(void)
    {
        int indx2; // lim <= period
        double i, vv, p;
           
        init();
        init_pwm(PWM_p);
        for (; ; ){
            indx2 = find_mppt2(0.77);
            p = r[h_span];
            p = power;
            p = 1.0*indx2/PWM_p;  // save last 8 results
            i = curr;
            vv = volt;
              __delay_cycles (1000000); // delay program execution
        }  // for
        CCR1 = 0; // set new on time
        CCTL1 = OUTMOD_7; // CCR1 reset/set
        P1OUT ^= BIT0;
    }

    //******************************************************************************
    void init(void){
        WDTCTL = WDTPW + WDTHOLD; // Stop WDT
        P1DIR = BIT0 + BIT6; // Set P1.0 and P1.6 to output direction
        P1IE = 0x00; // interupt disable
        P1OUT = BIT0 + BIT6; // outputs to on
        P1OUT = 0x00; // output off   
    }

    //******************************************************************************
    void init_pwm(unsigned int period){
        P1DIR |= BIT6; // P1.6 as output
        P1SEL |= BIT6; // P1.6 to TA0.1 PWM
         TACTL = MC_1 + TASSEL_2; // SMCLK, up mode
        CCR0 = period; // set PWM period
         CCR1 = 0; // CCR1 duty cycle set to min
         CCTL1 = OUTMOD_7; // CCR1 reset/set
         P1OUT ^= BIT6; // P1.6 output
    }
    //************************************************************************
    int get_v(unsigned int ch){
        int res;
        res = 0x01 << ch; // mark meas. ch. bit
        P1DIR |= res; // set ch. for output
        res = 0xFF ^ res; // invert meas. ch.
        P1DIR &= res; // set ch. for input
        ADC10CTL0 = 0x00; // reset ADC10CTL0
        ADC10CTL1 = (0*0x1000u); // select ch. 0
        ADC10CTL1 = (ch*0x1000u); // select meas. ch.
        ADC10CTL0 |= ADC10ON + ADC10SHT_0; // AD on + single ch conversion
        ADC10CTL0 |= REF2_5V + SREF_1 + REFON;    // 2.5 V internal ref
        ADC10CTL0 |= ENC + ADC10SC;             // Sampling and conversion start
        while (ADC10CTL1 & ADC10BUSY);          // wait for ADC10BUSY?
        res = ADC10MEM;  // read ad results
        return res;
    }

    //************************************************************************
    void set_pwm_dc(unsigned int duty_cycle){
        CCR1 = duty_cycle; // set new on time
          CCTL1 = OUTMOD_7; // CCR1 reset/set
         P1OUT ^= BIT6;
        __delay_cycles (PWM_p*2); // delay 3 PWM cycles
    }

    //************************************************************************
    void get_power(int avg){
        int k;

        power=0;
        for (k=0; k<avg; k++){
            volt = 0.002444*get_v(4); // 2.5V / 1023 = 0.002444
            curr = 0.0002444*get_v(5); // 2.5V / 10ohm / 1023 = 0.0002444;
            power += volt*curr; // p=v*i
    //        power += 5.972e-6*get_v(4)*get_v(5);
        }
        power = power/avg;
    }

    //************************************************************************
    int find_mppt2(double start_p){ //
        int pos, i, j, step, lim = 64; // lim <= period
        int peak_pos;

        P1OUT ^= BIT0;            // red led on - measuring
        step = PWM_p / 256;        // 256 steps resolution   
        pos = PWM_p * start_p;    // start position
        for (j=0; j<lim; j++){    // loop max 64 scans
            for (i=0; i<span; i++){                    // scan near center point
                set_pwm_dc((pos-(i-h_span)*step));    // set PWM
                get_power(16);                         // read power resuling power
                r[i] = power*1000.0;                 // calculate power in mW
            } // for i
            peak_pos = get_peak();
            if (peak_pos != 0) {
                pos = pos + step * peak_pos;
            }
            else {
                power = r[7];
                break;  // peak found
            } // if peak
            if ((pos > PWM_p) || (pos < 0)){        // boundry checking
                pos = PWM_p * start_p;
            }
        }
        set_pwm_dc(pos);
        P1OUT ^= BIT0;            // red led off - end measuring
        return pos;
    }

    //************************************************************************
    int get_peak(void) { // find peak in array
        int k, pos;
        double max=0;
        for (k=0; k<span; k++){ // find max point
            if (r[k] > max){
                max = r[k];
                pos = k;
            } // if r[k]
        } // for k
        return pos-h_span;
    }
           
    //************************************************************************
    //  End
    //************************************************************************

     

  • Hi,

    Below is my solution to reading different A/D channels without "cross talking". Smaller memory foot print and efficient.

    I can publish a test code to show the function operation.

    Jens-Michael, you comments help setting the direction for the solution. THX.

    Sinay

    //************************************************************************
    int get_ad(int ch){
        ADC10CTL0 = 0x00; // AD off = reset?
        ADC10CTL0 |= ADC10ON + ADC10SHT_0; // AD on + single ch conversion
        ADC10CTL0 |= REF2_5V + SREF_1 + REFON;    // 2.5 V internal ref
        ADC10CTL1 = (ch*0x1000u);                  // select channel
        ADC10CTL0 |= ENC + ADC10SC;             // Sampling and conversion start
        while (ADC10CTL1 & ADC10BUSY);          // wait for ADC10BUSY?
        return ADC10MEM;
    }

     

  • Sinay Goldberg said:
        P1DIR |= res; // set ch. for output
        res = 0xFF ^ res; // invert meas. ch.
        P1DIR &= res; // set ch. for input

    I don't really get what this is for. You set the pin for output, then you set it for input. The P1DIR|=res; is superfluous.
    Also, you don't need to invert res. Just use "P1DIR&=~res;" and you're done.

    Same for setting ADC10CTL1. No need for resetting it to 0x00 first.
    Next is, you should configure ADC10CTL0 (or at least set ADC10ON) before setting ADC10CTL1.
    After selecting the internal reference and setting REFON, you must wait some time to let the reference settle.
    Selecting ADC10SHT_0 is quite short. Use it only if you have 1) a low-impedance signal source and 2) a slowly changing signal, or the internal buffer capacitor doesn't have enough time to charge/discharge to the signal level with the required precision. Keep in mind that the inrush current of the SHT unit will affect your reading, so give it time to settle. ADC10SHT_0 on ADC10OSC is less than a microsecond and will likely cause an error of some LSBs. A capacitor of some nF parallel to the shunt will relax things a bit, if you're really in such a hurry :)

    In general, it is a good idea to init the ADC10 once (especially selecting the reference and switch it on) and just clear ENC bit, set a new channel and set ENC and ADC10SC when reading a value.

    For your power calculations, you should stay away from floating point operations. They are slooooooooooow. Same for divisions. Better use millicounts (multiply a fractions by 1000 and do plain integer math) and/or use fixed point arithmetics. Also, try to multiply with a factor so you can turn your divisions into bit shifts, if ever possible.

    The for loop in your main code never exits. The instructions behind it are unreachable.

    Don't use things like '__delay_cycles', especially for long delays. Use a timer and perhaps low power modes instead. You're wasting energy. Also, __delay_cycles depends on the current CPU clock speed. If this changes, all delay intrinsics change their behaviour. If you use a timer, you only need to adjust the timer tick speed on a central space. It is unimportant for such a small project, but usefulness will increase with the project size.

    in init() you let P1.0 and P1.6 blink for one instruction (@1MHz it is a 4µs pulse). Why? You should set P1OUT before setting the pins to output with P1DIR. Unbless, of course, you want this pulse.

    in init_pwm, CCR1=0 won't give the desired result. Both actions, set and reset, are exeuted on the same tick, resulting in a race condition. It is uncertain, which one wins. For a DC of 0%, you should set the OUT bit to 0 and the OUTMOD to 0.
    Also, toggling a port bitis only advised if you really wanbt to toggle. If you want to set it, you should explicitely set it. It nto only confuses people reading the code (or stepping through it), subsequent calls to the init function (whyever happening) will produce an unwanted and unexpected result.
    However, P1.6 is the PWM output? So why do you manually toggle this pin at all? It is under PWM control once P1SEL.6 is set.

    Same in set_pwm_dc. Also, the delay is two cycles, not three. However, you set the cycle totally unsynchronized to the current position of the cycle, possibly resulting in a single cycle with a DC of >100%.

    In get_power, there's much FP math. Terribly slow.  Also, just for readability: a function that is called 'get_something' should return 'something' and not set a global variable. If the caller wants to store the result globally, fine.

    If I read your whole code right, all you want to do is to find the moment (DC setting, servo position) of maximum power output. It is unimportant if the values are int eh range 0.00001 to 0.0001 or between 1000 and 10000. Just a linear factor that is negligible. But the math is done by magnitudes faster. On the bottom line, for your purpose it is completely sufficient to just multiply current and voltage reading as it comes from the ADC10 to get a relative power and the relative maximum. Think of it as normalized power units and not Watts.
    power +=get_v(4)*get_v(5); is completely sufficient. power can then be a simple long int.
    And for the dividion (average): if your averaging size is a power of two (16, 8 etc.) you can just do a shift, which is by magnitudes faster than a division.
    "power >>=4;" // equals power /=16. Pass the power of two as "avg" argument and in the loop use "k<(1<<avg)"

**Attention** This is a public forum