This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

ADC and create PWM pulses

Hello
i want to drive a RGB led with three PWM pulses, i use 3 potentiometers to control the PWM duty cycle, timer0 is used to trigger an A2D conversion and when the interrupt arrives i save the result into a buffer and configure the next channel for a conversion. I write the following code and the led is blinking... there is an error but i am unable to find it.

unsigned char t0_reload_value = 0x05;
unsigned char d2a_output = 0xff;

typedef enum SCH {ch0 = 0x00,ch1,ch2} A2DCHANNEL;
A2DCHANNEL channel = ch0;
data volatile unsigned char a2d[3];



void init_pca(void)
{
        CMOD = 0x00;            //cps1=0,cps0=0 (fosc/12),ecf=0
        CH = 0x00;                      //clear PCA timer CH_CL
        CL = 0x00;

        CCAPM0 = 0x42;          //mode 8bit PWM
        CCAPM1 = 0x42;          //mode 8bit PWM
        CCAPM2 = 0x42;          //mode 8bit PWM


        CCAP0H = d2a_output;            //0.4% PWM Duty Cycle=(256-ccap2h)/256
        CCAP1H = d2a_output;            //0.4% PWM Duty Cycle=(256-ccap2h)/256
        CCAP2H = d2a_output;            //0.4% PWM Duty Cycle=(256-ccap2h)/256
        CR = 1;                                 // Turn PCA timer on
}

void init_timer0(void)
{
        TMOD &= 0xF0;                             /* Timer 0 mode 2 with software gate */
        TMOD |= 0x02;           /* GATE0=0; C/T0#=0; M10=1; M00=0; */

        TL0 = t0_reload_value;        /* init values */
        TH0 = t0_reload_value;        /* reload value */

        ET0=1;                                  /* enable timer0 interrupt */
        EA=1;                                                           /* enable interrupts */
        TR0=1;                                                    /* timer0 run */
}

void init_adc()
{
        unsigned char i;
        //Configure P1[0.2] as analog channels
        ADCF = 0x07;

/* Configure ADC clock
                Fadc=Fclk/4*PRS, PRS=5 Fclk=12Mhz then
                Fadc=600Khz and tconv=11/600=18.33usec
*/

        ADCLK = 0x05;

        ADCON = MSK_ADCON_ADEN;         //ADC enable (bit ADEN )

        ADCON &= ~MSK_ADCON_SCH;    //Clear the channel field adcon[2:0]
        ADCON |= channel;                                       //Select channel0 to convert

        ADCON &= ~MSK_ADCON_PSIDLE;         //Standard mode (Disables PSIDLE)

        EADC = 1;               //Enable adc interrupt
        EA = 1;                 //Enable interrupts

        IPH1 = 0x00;
        IPL1 = 0x02;    //Set interrupt level priority at state 1

  ADCON &= ~MSK_ADCON_ADEOC;        //Clear the End of conversion flag

        for(i=0;i<3;++i)
                a2d[i] = 0x00;                  //clean array
}


void main()
{
        unsigned char  r,g,b;
        init_pca();
        init_adc();
        init_timer0();
        while(1)
        {

                r = a2d[ch0];   //get a copy of most recent value
                g = a2d[ch1];   //get a copy of most recent value
                b = a2d[ch2];   //get a copy of most recent value

                CCAP0H = r;
                CCAP1H = g;
                CCAP2H = b;
        }
}

void irq_t0_overflow(void) interrupt 1 using 1
{

                ADCON |= MSK_ADCON_ADSST;       //start A2D conversion

}

void irq_a2d() interrupt 8 using 2
{

        a2d[channel]= ADDH;

        ++channel;
        if(channel == 0x03)
                channel = 0x00;

        ADCON &= ~MSK_ADCON_SCH;            //clear the channel field ADCON[2:0]
        ADCON |= channel;       //Select next channel for convertion
        ADCON &= ~MSK_ADCON_ADEOC;          //clear the End of conversion flag

}

Any ideas? Thanks

  • " there is an error but i am unable to find it."

    You didn't think it would be an idea to try to elaborate exactly why you think there is an error?

    What does happen?
    What did you expect should happen?
    What have you done to try to figure out what is wrong?

  • C B,

    I am assuming that your 'problem' is that it is blinking and not smoothly changing values as you vary the adc values.

    #1 Dump the 'using' directive if possible: bank switching is not needed in this example as it isn't time-critical. Be careful when using that directive otherwise.

    #2 Assuming that your SFR configurations are correct ('init_XXX'), does the timer isr reset the timer 0 interrupt flag? (I can't remember if it does this automatically or not)

    #3 Are your ADC values compatible with the capture/PWM values (signed values)? Are they properly scaled?

    #4 I don't recall if 'interrupt 1' versus 'interrupt 0' is correct for timer 0 or not. Just double-check those.

    #5 Per's questions are valid. You should explain them so we can help.

    --Cpt. Vince Foster
    2nd Cannon Place
    Fort Marcy Park, VA

  • I think the problem is my buffer communication between A2D_ISR and main(), somehow i do the job wrong.
    With the following even simpler code (when an A2D conversion ends i start a new one) the led seems to work fine(color is stable). But i update the PCA registers inside the A2D interrupt and timer0 is not used.

    unsigned char d2a_output = 0xff;
    typedef enum SCH {ch0 = 0x00,ch1,ch2} A2DCHANNEL;
    A2DCHANNEL channel = ch0;
    data volatile unsigned char a2d[3];
    
    
    
    void init_pca(void)
    {
            CMOD = 0x00;            //cps1=0,cps0=0 (fosc/12),ecf=0
            CH = 0x00;                      //clear PCA timer CH_CL
            CL = 0x00;
    
            //module 1-2 (P1.7)
            CCAPM0 = 0x42;          //mode 8bit PWM
            CCAPM1 = 0x42;          //mode 8bit PWM
            CCAPM2 = 0x42;          //mode 8bit PWM
    
    
            CCAP0H = d2a_output;            //0.4% PWM Duty Cycle=(256-ccap2h)/256
            CCAP1H = d2a_output;            //0.4% PWM Duty Cycle=(256-ccap2h)/256
            CCAP2H = d2a_output;            //0.4% PWM Duty Cycle=(256-ccap2h)/256
            CR = 1;                                 // Turn PCA timer on
    }
    
    
    
    
    void init_adc()
    {
            unsigned char i;
            //Configure P1[0.2] as analog channels
            ADCF = 0x07;
    
    /* Configure ADC clock
                    Fadc=Fclk/4*PRS, PRS=5 Fclk=12Mhz then
                    Fadc=600Khz and tconv=11/600=18.33usec
    */
    
            ADCLK = 0x01;           //<-0x00
    
            ADCON = MSK_ADCON_ADEN;         //ADC enable (bit ADEN )
    
            ADCON &= ~MSK_ADCON_SCH;    //Clear the channel field adcon[2:0]
            ADCON |= channel;                                       //Select channel0 to convert
    
            ADCON &= ~MSK_ADCON_PSIDLE;         //Standard mode (Disables PSIDLE)
    
            EADC = 1;               //Enable adc interrupt
            EA = 1;                 //Enable interrupts
    
            IPH1 = 0x00;
            IPL1 = 0x02;    //Set interrupt level priority at state 1
    
      ADCON &= ~MSK_ADCON_ADEOC;        //Clear the End of conversion flag
            ADCON |= MSK_ADCON_ADSST;               //Start 8bit conversion
    
            for(i=0;i<3;++i)
                    a2d[i] = 0x00;                  //clean array
    }
    
    
    void main()
    {
            init_pca();
            init_adc();
            while(1);
    }
    
    
    void irq_a2d() interrupt 8
    {
    
            a2d[channel]= ADDH;
    
            switch(channel)
            {
                    case 0:
                            CCAP0H = a2d[channel];
                            break;
                    case 1:
                            CCAP1H = a2d[channel];
                            break;
                            case 2:
                            CCAP2H = a2d[channel];
                            break;
            }
            ++channel;
            if(channel == 0x03)
                    channel = 0x00;
    
            ADCON &= ~MSK_ADCON_SCH;            //clear the channel field ADCON[2:0]
            ADCON |= channel;       //Select next channel for convertion
            ADCON &= ~MSK_ADCON_ADEOC;          //clear the End of conversion flag
            ADCON |= MSK_ADCON_ADSST;               //start conversion once again
    
    }
    

    thanks once again !!

  • Cpt. Vince its hard for me to explain it, but i will give a try. When the potentiometers are at a stable position i expect a stable color too. Lets say for a given pot position i have Vpot = Va2dREF = 0xff so this means when i send this value to PCA will generates a stable 0.4% PWM. When i test the code on my breadboard i notice LED brightness changes, this means my PWM pulses are not very accurate. I feel like i missing something very important.

  • C B,

    I hope your problem was fixed with your new code rendition. I like it better than the first code: ISR driven.

    A point about your source: don't use tabs. Use space-fill instead. It makes the code cleaner when changing editors.

    Also, the enumeration should be more explicit:

    
    #define CHAN0   0x00    // selects channel 0 in the ADCON register
    #define CHAN1   0x01    // selects channel 1 in the ADCON register
    #define CHAN3   0x02    // selects channel 2 in the ADCON register
    
    typedef enum SCH {
                        ch0 = CHAN0,
                        ch1 = CHAN1,
                        ch2 = CHAN2
    
                     } A2DCHANNEL;
    
    A2DCHANNEL channel = ch0;
    
    

    Since you use 'channel' to OR in to the SFR to convert the next channel, be careful that those values are compatible with the SFR specs and the ability to simply increment the ++channel value. The SFR can have different bit-values than what the compiler assigns the enum values.

    I'd suggest you separate the channel selection enum values from the switch( case ) and incrementing just to de-link the SFR channel assignment from the 'portable' "C" code.

    
    void irq_a2d() interrupt 8
    {
            static unsigned char chan = 0; // or use a 'global' for syncing to rest of code
    
            a2d[ chan ] = ADDH; // Get converted ADC value
    
            /*--------------------------------------------------------------.
            ;  Transfer the ADC values into the PWM registers               ;
            '--------------------------------------------------------------*/
            switch( chan )
            {
                    case 0:
                            CCAP0H = a2d[ chan ]; // Transfer ADC to PWM 0
                            break;
    
                    case 1:
                            CCAP1H = a2d[ chan ]; // Transfer ADC to PWM 1
                            break;
    
                    case 2:
                            CCAP2H = a2d[ chan ]; // Transfer ADC to PWM 2
                            break;
                    default:
                            chan = 2; // force an error to become the last channel
                            CCAP2H = a2d[ chan ];
                            break;
            }
    
            /*--------------------------------------------------------------.
            ;  Manage index                                                 ;
            '--------------------------------------------------------------*/
            chan++;
    
            if( chan > 2 ) // safer than an '==' in case chan gets a random value
            {
                chan = 0;
            }
    
    
            /*--------------------------------------------------------------.
            ;  Update the ADC conversion for next iteration                 ;
            '--------------------------------------------------------------*/
            channel++;  // bump the enum value
    
            ADCON &= ~MSK_ADCON_SCH;    // clear the channel field ADCON[2:0]
            ADCON |=  channel;          // Select next channel for conversion
            ADCON &= ~MSK_ADCON_ADEOC;  // clear the End of conversion flag
            ADCON |=  MSK_ADCON_ADSST;  // start conversion once again
    
    }
    
    

    --Cpt. Vince Foster
    2nd Cannon Place
    Fort Marcy Park, VA