Zwiększ rozdzielczość bitową PWM

10

Chciałbym zwiększyć rozdzielczość bitową PWM Arduino Uno. W tej chwili jest to 8-bit, co uważam za zbyt niskie. Czy jest to możliwe bez utraty zdolności przerwania i opóźnień?

Koen

EDYCJA Ta konfiguracja zapewnia wynik 16-bitowy

void setupPWM16() {
    DDRB |= _BV(PB1) | _BV(PB2);        /* set pins as outputs */
    TCCR1A = _BV(COM1A1) | _BV(COM1B1)  /* non-inverting PWM */
        | _BV(WGM11);                   /* mode 14: fast PWM, TOP=ICR1 */
    TCCR1B = _BV(WGM13) | _BV(WGM12)
        | _BV(CS11);                    /* prescaler: clock / 8 */
    ICR1 = 0xffff;                      /* TOP counter value (freeing OCR1A*/
}
/* Comments about the setup
Changing ICR1 will effect the amount of bits of resolution.
ICR1 = 0xffff; (65535) 16-bit resolution
ICR1 = 0x7FFF; (32767) 15-bit resolution
ICR1 = 0x3FFF; (16383) 14-bit resolution etc....

Changing the prescaler will effect the frequency of the PWM signal.
Frequency[Hz}=CPU/(ICR1+1) where in this case CPU=16 MHz
16-bit PWM will be>>> (16000000/8)/(65535+1)=30.5175Hz
*/

/* 16-bit version of analogWrite(). Works only on pins 9 and 10. */
void analogWrite16(uint8_t pin, uint16_t val)
{
    switch (pin) {
        case  9: OCR1A = val; break;
        case 10: OCR1B = val; break;
    }
}
KoenR
źródło

Odpowiedzi:

16

Arduino Uno jest oparty na mikrokontrolerze ATmega382P. Ten układ ma dwa 8-bitowe timery, napędzające dwa kanały PWM każdy i jeden 16-bitowy timer, sterujący dwoma ostatnimi kanałami.

Nie można zwiększyć rozdzielczości 8-bitowych timerów. Możesz jednak ustawić 16-bitowy zegar w trybie 16-bitowym zamiast w trybie 8-bitowym używanym przez bibliotekę rdzeni Arduino. Otrzymasz dwa 16-bitowe kanały PWM o zmniejszonej częstotliwości 244 Hz (maksimum). Prawdopodobnie będziesz musiał samodzielnie skonfigurować licznik czasu i nie skorzystasz z łatwej w użyciu analogWrite()funkcji. Aby uzyskać szczegółowe informacje, zobacz sekcję Timer 1 w arkuszu danych ATmega328P .

Aktualizacja : Oto implementacja 16-bitów analogWrite(). Działa tylko na stykach 9 i 10, ponieważ są to jedyne styki podłączone do 16-bitowego timera.

/* Configure digital pins 9 and 10 as 16-bit PWM outputs. */
void setupPWM16() {
    DDRB |= _BV(PB1) | _BV(PB2);        /* set pins as outputs */
    TCCR1A = _BV(COM1A1) | _BV(COM1B1)  /* non-inverting PWM */
        | _BV(WGM11);                   /* mode 14: fast PWM, TOP=ICR1 */
    TCCR1B = _BV(WGM13) | _BV(WGM12)
        | _BV(CS10);                    /* no prescaling */
    ICR1 = 0xffff;                      /* TOP counter value */
}

/* 16-bit version of analogWrite(). Works only on pins 9 and 10. */
void analogWrite16(uint8_t pin, uint16_t val)
{
    switch (pin) {
        case  9: OCR1A = val; break;
        case 10: OCR1B = val; break;
    }
}

Możesz zauważyć, że górna część sekwencji liczników jest skonfigurowana jawnie. Możesz zmienić to na mniejszą wartość, aby PWM był szybszy, kosztem zmniejszonej rozdzielczości.

A oto przykładowy szkic ilustrujący jego użycie:

void setup() {
    setupPWM16();
}

/* Test: send very slow sawtooth waves. */
void loop() {
    static uint16_t i;
    analogWrite16(9, i);
    analogWrite16(10, 0xffff - i);
    i++;
    delay(1);
}
Edgar Bonet
źródło
Wow bardzo dziękuję, właśnie tego potrzebuję. Chcę, aby mój wynik PWM był taki sam jak moja rozdzielczość czujnika. Jeśli zmienię kod na <spójrz na moją edycję>, czy byłby to wynik 13-bitowy? Jeśli tak, jaka byłaby częstotliwość? Będę nim napędzał silnik prądu stałego, więc 244Hz będzie nieco mniej
KoenR
@KoenR: Nie, prescaler nie ma wpływu na rozdzielczość, jego celem jest spowolnienie liczenia. Ustawienie preskalera na 8 da częstotliwość PWM 30,5 Hz. Jeśli chcesz rozdzielczość 13 bitów, ustaw ICR1na 0x1fff, wtedy twoja częstotliwość będzie wynosić 1953 Hz (F_CPU / (TOP + 1)) z preskalerem na 1.
Edgar Bonet
Dziękuję za wyjaśnienie. Zredagowałem moje pytanie, aby obejmowało te błędy. Aby inni mogli to zobaczyć bezpośrednio. Dziękuję Ci!
KoenR
1
@Edgar Bonet To jest świetne, jednak nie mogę całkowicie wyłączyć diody LED. Używam, ICR1 = 0x03FFa przy 0 widzę niewielki puls na lunecie wystarczający do zapalenia diody LED. Jakieś pomysły?
David
1
@davivid: Tak, nie możesz mieć zerowego cyklu pracy. analogWrite16(pin, val)daje cykl pracy (val + 1) / ICR1. Jako obejście tego problemu analogWrite()działa Arduino if (val == 0) digitalWrite(pin, LOW); else if (val == 255) digitalWrite(pin, HIGH);. Ale wtedy nie można uzyskać cyklu pracy 1 / ICR1 ...
Edgar Bonet
3

Po pewnej kalibracji można zsumować wyniki dwóch kanałów PWM z różnymi rezystorami ważącymi. Skrajnie możesz użyć jednego wyjścia, aby zapewnić 8 bitów rozdzielczości, a skalować drugie do 1/256 poziomu i dodać je, aby drugi kanał obejmował jeden bit zakresu, a ty (znowu hipotetycznie) uzyskasz 16 bitów rozdzielczości. Bez ogromnej opieki i dostosowania wszystko, co dostaniesz, byłoby bałaganem.
Jednak dzieląc drugi kanał przez 16 lub 32, można dodać kilka dodatkowych bitów rozdzielczości PWM. Po dodaniu 2 kanałów PWM filtrowanych wyjść analogowych dodajesz dodatkowy bit (ponieważ potencjalny zakres jest podwojony dla niezmienionego mV / bit).
Po raz kolejny (ponownie) za każdy dodatkowy podział przez 2 otrzymujesz dodatkową odrobinę rozdzielczości, ale można to zrobić tylko dla 4 lub 5 lub 6 dodatkowych bitów, przy rosnących wymaganiach dotyczących dokładności rezystorów skalujących oraz trudniejszej kalibracji i podatności na błędy .

Krótki przykład
Jeśli jeden PWM zostanie przeskalowany w dół, aby dać powiedzmy 0 - 255 mV w krokach 1 mV, wówczas zsumowanie dwóch PWM o równej amplitudzie dałoby zakres 0 - 510 mV w krokach 1 mV.
Jeśli jeden PWM zostanie zmniejszony o współczynnik 32, to zamiast dodać 255 mV do początkowego zakresu PWM, dodałby tylko 8 mV do górnego końca (0,2566,32 = 8 mV, ale rozdzielczość byłaby w 0,03125 (1/32 ) kroki mV.

Podczas gdy być może można to osiągnąć wyłącznie za pomocą sumowania rezystorów i filtrowania RC, użycie letniego wzmacniacza operacyjnego znacznie poprawiłoby wyniki.

Również tętnienia PWM można filtrować za pomocą prostego filtra RC, ale użycie jednego opampa jako bufora (lub nawet pojedynczego tranzystora jako elementu śledzącego emiter) dałoby 3 lub 5 biegunów filtrowania dolnoprzepustowego i znacznie większą szansę na uzyskanie dodatkowego PWM rozkład. Nie sprawdziłem „koherencji fazowej” wyjść PWM, ale oczekuję, że poruszają się one w sposób względny, aby nie uzyskać korzyści wygładzania przez dodanie dwóch nieskorelowanych przebiegów.

W razie potrzeby można dodać więcej komentarzy. Zapytaj, jeśli jesteś zainteresowany.

Russell McMahon
źródło
To jest sprytne! Wydaje się, że biblioteka syntezy dźwięku Mozzi wykorzystuje tę sztuczkę, ponieważ jest to tak zwany tryb „HIFI”.
Edgar Bonet,
To wspaniałe, że z PWM. Ale czy nie wygładziłoby to fali? Pytam o to, ponieważ używasz filtra RC. Nie wspomniałem o tym w moim pytaniu, ale prowadzę z nim silnik prądu stałego <zawstydzony>. Dziękuję za wkład!
KoenR
@KoenR (fwiw: Nie widzę się czego wstydzić.) Nie wiem, jakiej odpowiedzi częstotliwości / tempa zmian chcesz na wyjściu ADC. Lub dlaczego chcesz N bitów lub jak duża jest wystarczająca. Silniki zwykle nie będą użytecznie kontrolowane przez więcej niż 8 bitów - zależy to od precyzji posiadanej aplikacji. Silnik działa jako część filtra wygładzającego z powodu indukcyjności. Musisz powiedzieć, jaki rodzaj silnika i jak napędzany. A schemat połączeń jest niezbędny. Jeśli silnik nie jest mały, masz sterownik. Szczotkowany silnik zasilany PWM musi mieć diodę zabezpieczającą, aby przepuszczać prąd silnika, gdy PWM jest wyłączony. Dodanie dwóch ...
Russell McMahon
... PWM są tutaj wykonalne, ale szczegóły obwodu muszą być znane.
Russell McMahon,
Strzec się! W niektórych przypadkach wygładzanie PWM za pomocą dolnoprzepustowego RC nie jest pożądane. Na przykład, jeśli podłączysz wyjście Arduino do bramki MOSFET, MOSFET pozostanie zimny, dopóki będzie napędzany czystym PWM. Ale jeśli go wygładzisz, MOSFET zacznie rozpraszać znacznie więcej ciepła. Czasami to nie jest dobra rzecz.
Florin Andrei,