Ten post chciałbym poświęcić na opisanie sposobu generowania sygnału PWM w oparciu o mikrokontroler ATmega328p.
Do wykorzystania są następujące wyprowadzenia:
- PD3 - OC2B;
- PD5 - OC0B;
- PD6 - OC0A;
- PB3 - OC2A;
- PB2 - OC1B;
- PB1 - OC1A;
Współczynnik wypełnienia = [Czas wysokiego sygnału / (Czas wysokiego sygnału * Czas sygnału niskiego)] * 100
Napięcie wyjściowe = Współczynnik wypełnienia * Napięcie wejściowe
PWM może działać w trzech różnych trybach Fast PWM, Phase Corrected PWM oraz Phase and Frequency Phase Corrected PWM.
W przypadku trybu Fast PWM 8 bitowy. Pozwala na regulację wypełnienia od 0 do 255. Działa on jak zwykły licznik tzn. zlicza do wartości ustalonej następnie wywołuje przerwanie, ustawienie flagi, po czym następuje jego wyzerowanie i ponowne odliczanie.
Drugi tryb działa na zasadzie zliczania od wartości 0 do ustawionej, po czym zaczyna odliczać w drugą stronę. Gdy wartości się pokryją to flaga zostanie ustawiona, po czym jej wartość zostanie wykorzystana do zmiany stanu na pinie. Sygnał generowany jest tutaj dla dwóch zboczy. Dzięki temu można go wykorzystywać do sterowania silnikami.
Na samym początku należy włączyć pin jako wyjście. Kanał pierwszy PWM na pinie PB1, kanał drugi natomiast odpowiada wyprowadzeniu PB2.
Aby wybrać tryb pracy należy się posłużyć odpowiednim ustawieniem rejestru TCCR1A oraz TCCR1B - dla timera 1 (Datasheet strona 131). Aby wybrać odpowiedni tryb należy we właściwy sposób ustawić bity WGM
Źródło: Datasheet strona 132
Tak więc aby odpalić tryb fast PWM 8 bitowy dla timera pierwszego należy ustawić bit WGM12 oraz WGM10. Działanie w trybach Fast PWM jest takie samo dla pozostałych ustawień czyli 9 oraz 10 bitowych. Jedyną różnicą jest ilość próbek oraz różnica w częstotliwości, którą należy sobie przeliczyć przed korzystaniem z nich. Tak więc dla trybu 9 bitowego ilość próbek wynosi 512, natomiast tryb 10 bitowy pozwala na ustawienie do 1024 próbek.
Następnym krokiem jest konfiguracja PWM dla odpowiedniej nogi mikrokontrolera. Pozwoli to na określenie jakimi stanami sterujemy podczas podawania danych. Czyli np. podczas normalnej pracy jest stan wysoki po czym po przepełnieniu licznika zostaje wywołane przerwanie i ściąga całość do stanu niskiego.
Do tego celu ustawia się odpowiednie bity w tym samym rejestrze. Poniżej są dwie tabelki górna dla trybu Fast PWM, dolna dla pozostałych.
Źródło: Datasheet strona 132
Litera A oraz B określa, który kanał będzie używany. W celu standardowej pracy, czyli podana wartość odpowiada wartości sygnału wysokiego, należy ustawiony bit COM1x1 ( w zależności od wybranego kanału bądź przy wykorzystywaniu obu).
Ostatnim krokiem przed włączeniem jest ustawienie odpowiedniego dzielnika dla częstotliwości taktowania.
Jeżeli nie chcemy używać dzielnika wtedy należy ustawić bit CS10, który pozwoli na maksymalne dopuszczalne taktowanie sygnału PWM. Natomiast jeśli chce się zmniejszyć tą prędkość to należy ustawić odpowiednie bity w celu uzyskania odpowiednich wartości.
Wzór ogólny potrzebny do wykonania tych obliczeń wygląda następująco:
Częstotliwość PWM = Częstotliwość taktowania [Hz]/Dzielnik[]/(Ilość próbek dla PWM + 1)[]
Tak więc jeśli mikrokontroler jest taktowany częstotliwością 8MHz i chcemy uzyskać wartość około 500 Hz to należy wybrać: 8 000 000 [ Hz ]/64[]/256[] = około 488Hz co w przybliżeniu wynosi 500Hz. Aby uzyskać taki wynik bity CS11 oraz CS10 muszą zostać ustawione.
- #define F_CPU 8000000UL
- #include <avr/io.h>
- #include <avr/interrupt.h>
- #include <util/delay.h>
- //--------------------------------------------------
- static void SetTim1PWM(uint8_t channel);
- //--------------------------------------------------
- int main (void) {
- uint8_t pwm = 0x00;
- SetTim1PWM(0x01);
- while(1){
- if(pwm == 255){
- pwm = 0;
- }
- else{
- pwm++;
- }
- OCR1A = pwm;
- _delay_ms(100);
- }
- }
- //---------------------------------------------
- static void SetTim1PWM(uint8_t channel)
- {
- if(channel == 1){ //Turn on channel 1 on PB1
- DDRB |= _BV(PB1);
- /*
- * Set WGM10 and WGM12 for fast PWM 8-bit
- */
- TCCR1A |= (1<<WGM10);
- TCCR1B |= (1<<WGM12);
- TCCR1A |= (1<<COM1A1); // Use only one channel on pin PB1
- //Prescaler config, for full speed
- TCCR1B |= (1<<CS10);
- //For 1kHz from 8Mhz
- //TCCR1B |= (1<<CS11) | (1<<CS10);
- //Set value
- OCR1A = 0x00;
- }
- else if(channel == 2){ //Turn on channel 2 on PB2
- DDRB |= _BV(PB2);
- TCCR1A |= (1<<WGM10);
- TCCR1B |= (1<<WGM12);
- TCCR1A |= (1<<COM1B1); // Use only one channel on pin PB1
- //Prescaler config, for full speed
- TCCR1B |= (1<<CS10);
- //For 1kHz from 8Mhz
- //TCCR1B |= (1<<CS11) | (1<<CS10);
- //Set value
- OCR1A = 0x00;
- }
- }