Interrupt-driven ADC on more pins

Go To Last Post
11 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have two 4051 multiplexes. 8 pots are connected to each of them. They share S0, S1 and S2 lines. One of them is connected to pin 6 and second to pin 7 (on PORTC, ATmega328P). I want to read their values using interrupt-driven ADC, and I was only partially successful. The code I've got only works on one multiplexer at the time, that is, only on first, or only on second, but not both at the same time. I found some info about how I should throw away the first reading, but the values are still fluctuating. Here is the relevant code:

 

Setting up ADC:

volatile int16_t analogValueInterrupt;
volatile bool adcPinChange = false;

void setUpADC()   {

    cli();
    //clear bits in registers
    ADMUX = 0x00;
    ADCSRA = 0x0;

    //set analogue reference voltage to 5V
    ADMUX |= (1<<REFS0);

    //set prescaler to 64, enable ADC and enable ADC interrupt
    ADCSRA |= (1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(0<<ADPS0)|(1<<ADEN);
    sei();

}

void startAnalogConversion(uint8_t adcChannel)    {

    //check for valid channel
    if ((adcChannel < 0) || (adcChannel > 7))   return;

    ADMUX = (ADMUX & 0xF0) | (adcChannel & 0x0F);
    //add small delay after channel change
    NOP;

    adcPinChange = true;

    //single conversion mode
    ADCSRA |= (1<<ADSC);

}

bool analogConversionStopped() {

    return !bitRead(ADCSRA, ADSC);

}

ISR(ADC_vect)   {

    //if pin was changed, discard first reading
    //and start second one
    if (adcPinChange)   {
 
        ADCSRA |= (1<<ADSC);
        adcPinChange = false;

    }   else analogValueInterrupt = ADC;

}

int16_t getAnalogValueInterrupt(uint8_t adcChannel)   {

    uint8_t interruptFlag = SREG;
    cli();
    int16_t value = analogValueInterrupt;
    SREG = interruptFlag;
    return value;

}

 

And the code that actually deals with 4051 chips:

 

 

void OpenDeck::readPots()   {

    static uint8_t muxInput = 0;
    static uint8_t muxNumber = 0;

        if (analogConversionStopped()) {

            readPotsMux(muxInput, muxNumber);

            muxNumber++;

            if (muxNumber == _numberOfMux) {

                muxNumber = 0;
                muxInput++;
                if (muxInput == 8) muxInput = 0;
                setMuxInput(muxInput);

            }

            startAnalogConversion(adcConnected(muxNumber)); //adcConnected function returns pin on which mux is connected

        }

}


void OpenDeck::readPotsMux(uint8_t muxInput, uint8_t muxNumber)  {

        //calculate pot number
        uint8_t potNumber = muxNumber*8+muxInput;

        //don't read/process data from pot if it's disabled
        if (getPotEnabled(potNumber))   {

            //read analogue value from mux
            int16_t tempValue = getAnalogValueInterrupt(adcConnected(muxNumber));

            //if new reading is stable, send new MIDI message
            if (checkPotReading(tempValue, potNumber))
                processPotReading(tempValue, potNumber);

        }

}

 

 

So in short: with only one mux everything works fine, with two it fluctuates. Any help is appreciated.

Last Edited: Sun. Nov 16, 2014 - 08:01 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Why not just write a function that takes an input number, figures out what adc input and mux setting, starts the conversion and returns. Currently using interrupts for the adc is a waste of time - it adds no value. I cannot see where you select the 4051 input.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:
Currently using interrupts for the adc is a waste of time - it adds no value.

 

It does. I also have 8 column LED/button matrix. Without using interrupts on ADC it's really visible how they "tremble" (I don't know if this is the right word).

 

Kartman wrote:
I cannot see where you select the 4051 input.

 

Look at readPots() function again.

 

if (muxNumber == _numberOfMux) {

                muxNumber = 0;
                muxInput++;
                if (muxInput == 8) muxInput = 0;
                setMuxInput(muxInput);

            }
void OpenDeck::setMuxInput(uint8_t muxInput) {

        PORTC &= 0xF8;
        PORTC |= muxInput;

}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Without the interrupt does it work correctly - regardless of the led side effect?

If you use a timer tick to update your leds and scan the switches, i don't see how polling the adc would be an issue - you could also do it in the timer tick where you select the input and start the conversion, then read the result on the next tick.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The S/H cap need a bit of time to come to equilibrium with the signal on a newly selected MUX.  Initialise the ADC with ADATE and ADTS[2:0] to use a timer as an auto trigger source.  In the ADC ISR process/store the ADC result and change the MUX, then return.  Let the Timer event trigger the next conversion.  Chose a timer period that is as long as your app can tolerate to give the S/H cap as much time as you can to match the selected MUX.

 

Equally important is a low-impedance signal source for the ADC.  What are yours?

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

Last Edited: Mon. Nov 17, 2014 - 07:56 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

joeymorin wrote:

The S/H cap need a bit of time to come to equilibrium with the signal on a newly selected MUX.  Initialise the ADC with ADATE and ADTS[2:0] to use a timer as an auto trigger source.  In the ADC ISR process/store the ADC result and change the MUX, then return.  Let the Timer event trigger the next conversion.  Chose a timer period that is as long as your app can tolerate to give the S/H cap as much time as you can to match the selected MUX.

 

Equally important is a low-impedance signal source for the ADC.  What are yours?

This is almost certainly the problem.  You have two resistive channels between the ADC and the pot (which has its own equivalent resistance).  All of these slow down the time it takes for the S/H cap to be charged to the real input value.  As mentioned above, switch the MUX (both internal and external) ASAP after taking a reading.  Then delay as long as necessary (to get solid readings) before initiating another reading.  Do other useful work in that delay, if possible. wink

 

The classic test here is to have one channel set to a fixed reading (e.g. pot at 50%), then adjust the pot of the previous channel from 0 to 100%, up and down.  You need enough delay so that the previous channel has no effect on the current channel reading.  The previous channel is the channel that is active just before you switch the (internal or external) MUX to the 50% test channel.  That means that the previous channel will be one channel when testing the internal MUX delay time, and another channel when testing the external MUX delay time.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:

Without the interrupt does it work correctly - regardless of the led side effect?

 

Yes.

 

Kartman wrote:
If you use a timer tick to update your leds and scan the switches, i don't see how polling the adc would be an issue - you could also do it in the timer tick where you select the input and start the conversion, then read the result on the next tick.

 

Okay so I didn't use interrupt to switch matrix column, I was checking constantly for time difference (1 mS). However, I tried what you said. I've configured Timer1 to fire every 1 mS. Timer switches to next column and turns off LED rows, since I've got shared column setup.

 

Timer code:

 

void OpenDeck::setUpColumnTimer()   {

    TCCR1A = 0;
    TCCR1B = 0;
    TCNT1  = 0;

    //set compare match register to desired timer count
    OCR1A = 249;

    //turn on CTC mode
    TCCR1B |= (1 << WGM12);

    //set prescaler to 64
    TCCR1B |= (1 << CS11) | (1 << CS10);

    //enable CTC interrupt
    TIMSK1 |= (1 << OCIE1A);

}

ISR(TIMER1_COMPA_vect)  {

    uint8_t _column = column;

    if (_column == openDeck.getNumberOfColumns()) _column = 0;
    ////turn off all LED rows before switching to next column
    openDeck.ledRowsOff();
    openDeck.activateColumn(_column);
    _column++;

    column = _column;

}

void OpenDeck::ledRowsOff()   {

    switch (_board) {

        case SYS_EX_BOARD_TYPE_TANNIN:
        //turn all LED rows off
        PORTB &= 0xEF;
        break;
        
        case SYS_EX_BOARD_TYPE_OPEN_DECK_1:
        PORTB &= 0xF0;
        break;

        default:
        break;

    }

}

void OpenDeck::activateColumn(uint8_t column)  {

    switch (_board) {

        case SYS_EX_BOARD_TYPE_TANNIN:
        //there can only be one column active at the time, the rest is set to HIGH
        switch (column)  {

            case 0:
            PORTD = 0x7A;
            break;

            case 1:
            PORTD = 0x76;
            break;

            case 2:
            PORTD = 0x6E;
            break;

            case 3:
            PORTD = 0x5E;
            break;

            case 4:
            PORTD = 0x3E;
            break;

            default:
            break;

        }

        break;

        case SYS_EX_BOARD_TYPE_OPEN_DECK_1:
        //column switching is controlled by 74HC238 decoder
        PORTC &= 0xC7;
        switch (column) {

            case 0:
            PORTC &= 0xC7;
            break;

            case 1:
            PORTC |= (0xC7 | 0x20);
            break;
            
            case 2:
            PORTC |= (0xC7 | 0x10);
            break;
            
            case 3:
            PORTC |= (0xC7 | 0x30);
            break;
            
            case 4:
            PORTC |= (0xC7 | 0x08);
            break;
            
            case 5:
            PORTC |= (0xC7 | 0x28);
            break;
            
            case 6:
            PORTC |= (0xC7 | 0x18);
            break;
            
            case 7:
            PORTC |= (0xC7 | 0x38);
            break;
            
            default:
            break;

        }
        break;

        default:
        break;

    }

}

 

ADC code:

 

void setUpADC() {

    //clear bits in registers
    ADMUX = 0x00;
    ADCSRA = 0x0;

    //set analogue reference voltage to 5V
    ADMUX |= (1<<REFS0);

    //set prescaler to 64 and enable ADC
    ADCSRA |= (1<<ADPS2)|(1<<ADPS1)|(1<<ADEN);

    //set prescaler to 128 and enable ADC
    //ADCSRA |= (1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)|(1<<ADEN);

}

int16_t analogRead(uint8_t adcChannel)  {

    //check for valid channel
    if ((adcChannel < 0) || (adcChannel > 7))   return -1;

    //select ADC channel with safety mask
    ADMUX = (ADMUX & 0xF0) | (adcChannel & 0x0F);

    //single conversion mode
    ADCSRA |= (1<<ADSC);

    //wait until ADC conversion is complete
    while (ADCSRA & (1<<ADSC));
    return ADC;

}

 

Now my pot read code is a bit simpler:

 

void OpenDeck::readPots()   {

    if ((_board != 0) && (bitRead(hardwareFeatures, EEPROM_HW_F_POTS)))    {

        static uint8_t muxInput = 0;

        setMuxInput(muxInput);

        for (int muxNumber=0; muxNumber<_numberOfMux; muxNumber++)
            readPotsMux(muxInput, muxNumber);

        muxInput++;
        muxInput &= 7;

    }

}

void OpenDeck::readPotsMux(uint8_t muxInput, uint8_t muxNumber)  {

        //calculate pot number
        uint8_t potNumber = muxNumber*8+muxInput;

        //don't read/process data from pot if it's disabled
        if (getPotEnabled(potNumber))   {

            //read analogue value from mux
            int16_t tempValue = analogRead(adcConnected(muxNumber));

            //if new reading is stable, send new MIDI message
            if (checkPotReading(tempValue, potNumber))
                processPotReading(tempValue, potNumber);

        }

}

 

The problem: values are fluctuating again. If I do not configure Timer1, values are stable. Ugh!

 

 

Last Edited: Mon. Nov 17, 2014 - 12:25 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you don't turn on any leds, do you have the problem? I'm thinking the current drawn by the leds is upsetting the adc. Maybe poor grounding or bypassing.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:

If you don't turn on any leds, do you have the problem? I'm thinking the current drawn by the leds is upsetting the adc. Maybe poor grounding or bypassing.

 

None of the LEDs are turned on. In fact, in loop I'm only running readPots, I've commented out the matrix part.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

You are still not allowing time for the S/H capacitor to settle when changing MUX.     You only need a few microseconds.   e.g. 10us

 

If you have a regular Timer IRQ,    you could always change MUX and start ADC in consecutive Timer slots.

 

In fact,   you have many ways to achieve your objectives.    

Personally,   I would think it should be easiest to choose an AVR (or other MCU) with 16 ADC channels.

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:

You are still not allowing time for the S/H capacitor to settle when changing MUX.     You only need a few microseconds.   e.g. 10us

 

If you have a regular Timer IRQ,    you could always change MUX and start ADC in consecutive Timer slots.

 

Okay, so far I've got interrupt configured to switch to next column in matrix. Whatever I do if that interrupt is enabled, the pots don't work, that is, they fluctuate a lot. So I tried this:

 

void OpenDeck::readPots()   {

        static uint8_t muxInput = 0;
        static uint8_t muxNumber = 0;
        static bool switchOrRead = false;

        if (!switchOrRead)  {

            //switch
            setADCchannel(adcConnected(muxNumber));
            NOP; NOP;
            switchOrRead = true;
            return;

        }   else {

                //read
                readPotsMux(muxInput, muxNumber);
                muxNumber++;

                if (muxNumber == _numberOfMux) {

                    muxNumber = 0;
                    muxInput++;
                    if (muxInput == 8) muxInput = 0;
                    setMuxInput(muxInput);

                }

                switchOrRead = false;

    }

}

Same thing. I don't get it.

 

 

Quote:
In fact,   you have many ways to achieve your objectives.    

Personally,   I would think it should be easiest to choose an AVR (or other MCU) with 16 ADC channels.

 

David.

 

That is not an option, since I already have few assembled boards with ATmega328 and those 4051 chips.

Last Edited: Mon. Nov 17, 2014 - 04:32 PM