2
\$\begingroup\$

I've been trying to make a program that would continuously read analog values from 3 different pins and then make a PWM signal corresponding to each of those 3 pins. So basically, 3 output PWM channels with different duty cycles corresponding to 3 different analog values. The problem is that all the PWM channels share the duty cycle from what i noticed, but if I ignore the ADC and just set duty cycle in code they work well. Here's the code:

#define F_CPU 16000000UL

#include <avr/io.h>
#include <avr/interrupt.h>

void selectADCchannel(uint8_t channel);
void initPWM();
void setupADC();

int main(void){

    sei();
    initPWM();
    //start with 0'th channel; no channel selected needed
    setupADC();

    while(1){

    }
}

ISR(ADC_vect){
    //get current channel
    uint8_t currentChannel = ADMUX & 0x0F;
    //set output compare registers for current channel
    switch(currentChannel){
        case 0x00: OCR0A = ADCH;
            break;
        case 0x01: OCR0B = ADCH;
            break;
        case 0x02: OCR2B = ADCH;
            break;
    }
    //loop through channels from 0 to 2 
    if(currentChannel == 2)
        selectADCchannel(0x00);
    else
        selectADCchannel(currentChannel+1);
    //restart conversion
    ADCSRA |= 1<<ADSC;
    }

void selectADCchannel(uint8_t channel){
    //0xE0 is 11100000
    //0x1F is 00011111
    ADMUX = (ADMUX & 0xE0) | (channel & 0x1F);
}

void setupADC(){
    //enable prescaler (128 for 16MHz => 125kHz)
    //would probably work with 250kHZ for 8 bits
    //set ADPS bits in ADCSRA to 111
    ADCSRA |= 1<<ADPS2 | 1<<ADPS1 | 1<<ADPS0;
    //enable result shift to ADCH to only read ADCH (8 bits)
    ADMUX |= 1<<ADLAR;
    //enable interrupt
    ADCSRA |= 1<<ADIE;
    //set voltage reference
    //reference is AVcc
    ADMUX |= 1<<REFS0;
    //turn on ADC
    ADCSRA |= 1<<ADEN;
    //start conversion
    ADCSRA |= 1<<ADSC;  
}

void initPWM(){
    //TIMER0 (8 bit timer)
    //set pins 5D(OC0B) and 6D(OC0A) as output
    DDRD |= 1<<DDD5 | 1<<DDD6;
    //set fast-pwm mode
    TCCR0A |= 1<<WGM00;
    //set non-inverting mode for both pins
    TCCR0A |= 1<<COM0A1 | 1<<COM0B1;
    //set prescaler to 256
    TCCR0B |= 1<<CS02;

    //TIMER2 (8 bit timer)
    //set pins 3D(OC2B) as output
    DDRD |= 1<<DDD3;
    //set fast-pwm mode
    TCCR2A |= 1<<WGM20;
    //set non-inverting mode for both pins
    TCCR2A |= 1<<COM2B1;
    //set prescaler to 256
    TCCR2B |= 1<<CS22 | 1<<CS21;
}

So, what the program should do, after initialization, read the value from one of the ADC pins, and then set the PWM compare register of the corresponding pin to the ADCH value. Thanks.

\$\endgroup\$
  • \$\begingroup\$ Which AVR are you using? \$\endgroup\$ – JimmyB Apr 2 '15 at 18:12
  • \$\begingroup\$ I'm using an atmega328p. I think this might be because the AVcc pin was unconnected, currently modifying the circuit. Will come back with answers. \$\endgroup\$ – user3808318 Apr 2 '15 at 18:22
1
\$\begingroup\$

The code is fine. The problem was that I was only testing the ADC on one pin and floating the other two. I was expecting only the one I was testing to work, but apparently there's a lot of crosstalk between ADC pins if they're floating. Also, a lot of noise, even with capacitors on ARef and AVcc. Thanks and sorry for posting without fully testing before.

\$\endgroup\$
  • \$\begingroup\$ Yes, inputs should never be left floating. The PWM switching could also interfere with the analog signals. \$\endgroup\$ – JimmyB Apr 2 '15 at 20:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.