A/D prevodník - Meranie bez blokovania

Zápisník experimentátora

Hierarchy: A/D prevodník

Funkcia analogRead je naprogramovaná tak, že počas merania zablokuje mikrokontrolér. Počas toho času nemôžete robiť nič iné. Ak potrebujete lepšie využiť čas mikrokontroléra na spracovanie iných úloh, je vhodné upraviť analógové meranie tak, aby neblokovalo beh programu. V tomto príklade si takýto program navrhneme.

analogRead

Takto vyzerá funkcia analogRead z aktuálnej verzie Arduina. Budem popisovať iba podstatné časti kódu.

  • if (pin >= 14) pin -= 14; - Na doske má Arduino analógové piny označené symbolmi A0-A6. Definície sa nachádzajú v súbore pins_arduino.h. Tento výpočet zabezpečuje, aby sa symboly posunuli na hodnoty indexu od 0 a dali sa následne ľahko použiť vo funkcii na výber konkrétneho pinu, na ktorom sa bude robiť analógové meranie.
  • sbi(ADCSRA, ADSC); - Štart konverzie A/D prevodníka. Samotný štart sa ale nezačne okamžite. Na meranie sa využíva nastavenie deličky voči hlavnej frekvencii mikrokontroléra a bežne je nastavená na hodnotu 128. Analógové meranie používa vlastný hodinový signál, ktorý je spomalený a konverzia sa začne pri zmene logickej úrovne. Takže sa môže stať, že v skutočnosti bude program čakať ešte 128 inštrukcií, kým sa konverzia naozaj začne. Toto je ďalší dôvod na to, aby sme použili neblokujúce meranie.
  • while (bit_is_set(ADCSRA, ADSC)); - Toto je ten kritický riadok, ktorý blokuje celý program. Kým nie je splnená podmienka, program bude čakať. Preto je jasné, že sa budeme sústreďovať v našom zdrojovom kóde najmä na vylepšenie tohto riadku.
int analogRead(uint8_t pin)
{
    uint8_t low, high;

#if defined(analogPinToChannel)
#if defined(__AVR_ATmega32U4__)
    if (pin >= 18) pin -= 18; // allow for channel or pin numbers
#endif
    pin = analogPinToChannel(pin);
#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
    if (pin >= 54) pin -= 54; // allow for channel or pin numbers
#elif defined(__AVR_ATmega32U4__)
    if (pin >= 18) pin -= 18; // allow for channel or pin numbers
#elif defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega644__) || defined(__AVR_ATmega644A__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644PA__)
    if (pin >= 24) pin -= 24; // allow for channel or pin numbers
#else
    if (pin >= 14) pin -= 14; // allow for channel or pin numbers
#endif

#if defined(ADCSRB) && defined(MUX5)
    // the MUX5 bit of ADCSRB selects whether we're reading from channels
    // 0 to 7 (MUX5 low) or 8 to 15 (MUX5 high).
    ADCSRB = (ADCSRB & ~(1 << MUX5)) | (((pin >> 3) & 0x01) << MUX5);
#endif
 
    // set the analog reference (high two bits of ADMUX) and select the
    // channel (low 4 bits).  this also sets ADLAR (left-adjust result)
    // to 0 (the default).
#if defined(ADMUX)
#if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
    ADMUX = (analog_reference << 4) | (pin & 0x07);
#else
    ADMUX = (analog_reference << 6) | (pin & 0x07);
#endif
#endif

    // without a delay, we seem to read from the wrong channel
    //delay(1);

#if defined(ADCSRA) && defined(ADCL)
    // start the conversion
    sbi(ADCSRA, ADSC);

    // ADSC is cleared when the conversion finishes
    while (bit_is_set(ADCSRA, ADSC));

    // we have to read ADCL first; doing so locks both ADCL
    // and ADCH until ADCH is read.  reading ADCL second would
    // cause the results of each conversion to be discarded,
    // as ADCL and ADCH would be locked when it completed.
    low  = ADCL;
    high = ADCH;
#else
    // we dont have an ADC, return 0
    low  = 0;
    high = 0;
#endif

    // combine the two bytes
    return (high << 8) | low;
}

Neblokujúce meranie napätia

Toto je upravený kód na meranie napätia. Príklad je pre pin A0 a Arduino Uno. Vo funkcii setup povolíme analógové meranie a nastavíme deličku hodinového signálu na 128 (rovnako ako to robí aj Arduino). Potom nastavíme referenciu napätia na AVCC a pin A0.

Vo funkcii loop využívame premennú adc_conversion_working. V nej máme nastavené, či práve vykonávame analógové meranie. Ak meranie nie je aktívne, spustíme analógové meranie. Potom pravidelne kontrolujeme nastavený bit ADSC a v okamihu, ako nie je nastavený, je analógové meranie skončené. Odmeranú hodnotu vypíšeme na sériový port a počkáme 500 ms.

Iba keď je podmienka splnená, vykonáme akciu po skončení merania. Zvyšok času sa môže mikrokontrolér venovať inej činnosti.

const byte adcPin = A0; // = 14 (pins_arduino.h)

bool adc_conversion_working = false;

void setup() {
  Serial.begin(115200);
  Serial.println("ADC without blocking");

  ADCSRA = bit(ADEN) // Turn ADC on
           | bit(ADPS0) | bit(ADPS1) | bit(ADPS2); // Prescaler of 128
  ADMUX  = bit(REFS0) // AVCC
           | ((adcPin - 14) & 0x07); // Arduino Uno to ADC pin
}

void loop() {
  if (!adc_conversion_working) {
    bitSet(ADCSRA, ADSC);  // Start a conversion
    adc_conversion_working = true;
  }

  // The ADC clears the bit when done
  if (bit_is_clear(ADCSRA, ADSC)) {
    int value = ADC;  // Read result
    adc_conversion_working = false;
    Serial.println(value);
    delay(500);
  }
}

Zdrojový kód

Zdrojový kód programu sa nachádza na serveri GitHub.

 


21.09.2018


Menu