Skocz do zawartości

[Kurs] Programowanie mikrokontrolerów AVR w języku assembler - część 3


kermit

Pomocna odpowiedź

Cześć 3- PWM(Pulse width modulation)

Witam w 3 częśći kursu programowania mikrokontrolerów AVR w języku assembler. Dzisjaj zajmiemy się konfiguracją PWM, ale zanim to zrobimy wypadało by dowiedzieć się co to jest to całe Pwm-z otóz angielskiego pulse width modulation, czyli modulacja szerokością impulsu jest to tryb timera wbudowanego w nasz mikrokontroler (w naszym wypadku wykorzystamy timer0, czyli 8-bitowy)która pozwala nam na generowanie impulsów o konkretnej długości. Mówiąc prościej jest to zmienianie stanu wyjścia z 0 na 1 lub odwrotnie z określoną częstotliwościa .My użyjemy tzw. Sprzętowego PWM, ale można je także zrealizować programowo, wyglądało by to tak, że co określony czas zmienialibyśmy stan wyjścia na którym miałoby być generowane nasze PWM

Jak to działa?

Otóż po uruchomieniu timera0 jeden z jego rejestrów ,a dokładniej TCNT0 jest inkrementowany(powiększany o 1) w każdym takcie sygnału zegarowego, sygnał ten może pochodzić z wewnętrznego lub zewnętrznego oscylatora w zależności czym taktujemy nasz mikrokontroler, może on być również dzielony za pomocą tzw. Preskalera, co powoduje, że TCNT0 nie będzie inkrementowany w każdym takcie, tylko co ilość ustawioną w naszym preskalerze(do wyboru mamy 8,64,256,1024).Jako, że timer0 jest timerem 8-bitowym to rejestr TCNT0 jest inkrementowany, aż osiągnie wartośći 255, gdy ją osiągnie jest zerowany i odliczanie zaczyna się od nowa.W trybie PWM który nas interesuje, rejestr TCNT0 po każdej inkrementacji jest porównywany z wartością rejestru OCR0 i jeśli TCNT0 zrówna się z wartością jaką zapiszemy do OCR0 to wyjście tego timera -PB3 zostaje wyzerowane lub ustawione (w zależności od tego co ustawiliśmy w rejestrze TCCR0 ) i pozostaje w tym stanie(logiczne 1 lub 0) aż rejestr TCNT0 osiągnie wartość maksymalną(255).

Jak to uruchomić?

Może już sie domyślacie, bo w podobny sposób ustawialiśmy nasze wejśćia/wyjścia, czyli ustawialiśmy bity w rejestrach odpowiedzialnych za ich działanie, więc musimy w rejestrze odpowiadającym za działanie timera0 ustawić odpowiednie bity, rejestrem przeznaczonym do konfiguracji timera0 jest TCCR0, (zachęcam teraz do otworzenia noty aplikacyjnej naszej atmegi32 i poszukania informacij nt. Tego rejestru -na stronie nr. 327 noty aplikacyjnej możemy znalezć tabelkę z nazwami wszystkich rejestrów i numerami stron na których możemy znależć o nich informacje)

tak wygląda struktura naszego rejestru TCCR0:

bit numer 0 – pozostawiamy go w spokoju po przeczytaniu pierwszego zdania w nocie katalogowej nt. Tego bitu(strona 80) .

bity numer 6,3(WGM00 , WGM01)odpowiadają za tryb działania timera, w naszym wypadku będzie to fast PWM, więc ustawiamy oba te bity na 1

bity numer 5,4 – wybieramy z ich pomocą jaka ma być reakcja kiedy TCCR0 = OCR0 tzn. Czy po zrównaniu się wartości tych dwóch rejestrów, wyjście OC0(PB3) ma być ustawione, (tryb odwracający) czy wyzerowane(tryb nieodwracający) – w naszym wypadku ustawimy tryb nieodwracający, czyli ustawiamy tylko bit COM01.

bity numer 2,1,0 – za ich pomocą ustawiamy wartośc preskalera o którym wspomniałem już wcześniej. Dla naszych zastosowań najlepiej wybrać jak najmniejszy dzielnik(za chwilę dowiecie się dlaczego), czyli wartośc preskalera ustawimy na 0, poprzez ustawienie bitu CS00

Czas na przejście do konkretów:

ćwiczenie 1 – regulowanie jasności świecenia diody LED

układ jest bardzo prosty , dioda LED katodą do PC0 ,a anodą przez rezystor do wyjścia naszego timera, czyli do PB3(w nocie aplikacyjnej przy opisie wyprowadzeń przy "PB3", możemy zobaczyć napis "OC0" ). Aby dioda świeciła na PC0 musimy ustawić stan niski, na PB3 będzie generowane PWM.

gdy zmontujemy już nasz układzik pora na, przejście do programu, co będzie on robił?

Zadaniem naszego programu będzie zapalenie diody LED tyle ,że nasza dioda będzie świeciła z jasnością taką jaką ustawimy w programie, a dokładniej z wartością jaką zapiszemy do rejestru OCR0(jako ,że timer jest 8bitowy ,wartośc ta może wynosić 0-255)i tak np. Jeśli załadujemy do tego rejestru 127 to nasza dioda będzie świeciła z 127/255 czyli około 1/2 swojej mocy Może niektórzy mają teraz wątpliwości bo jak to możliwe -regulować jasnośc świecenia diody LED za pomocą wyjścia cyfrowego, otóż wykorzystamy jedną z właściwości ludzkiego oka ,które nie jest wstanie rejestrować zmian które następują z częstotliwością większą niż 50Hz, jeśli dioda byłaby włączana i wyłączana z mniejszą częstotliwośćią, wiedizlibysmy jedynie jej mruganie, gdy częstotliwość będzie większa ,bądź równa 50hz nie będziemy wstanie dostrzec mrugania diody, tylko wydawało nam się będzie, że dioda świeci światłem ciągłym.Dlatego też dla takich zastosowań ustawilismy jak najmienszy preskaler, ponieważ nczym wyższa jest częsotliwość przełączania tym światło diody jest bardziej "jednolite"

Tak będzie wyglądał program w całej okazałości

.nolist
.include "m32def.inc"
.list

.cseg
.org 0

cli						//wyłączenie przerwań
ldi R16, HIGH(RAMEND)	//zaimpelementowanie stosu
out SPH, R16
ldi R16, HIGH(RAMEND)
out SPL, R16

ldi R16, (1<<PC0)|(1<<PC1)|(1<<PC2)		//ustawienie linii do których podłączone są diody jako wyjściowe
out DDRC, R16							//ustawienie linii PC0,PC1,PC2 jako wyjściowe
out PORTC, R16							//wygaszenie diod na liniach PC0,PC1,PC2 poprzez ustawienie na nich stanu wysokiego(diody podłączone są katodami //,więc ustawienie stanu niskiego zapala je ,a wysokiego wygasza)

//ustawienia pwm
sbi DDRB, 3								//ustawiennie lini PB3 jako wyjście (na tej linii bedzie generowany sygnał pwm)
ldi R16, (1<<WGM00)|(1<<WGM01)|(1<<COM01)|(1<<CS00)	//załadowanie do R16 ,wartości ktora posłuży do ustawienia pwm(poprzez załadowanie tej wartośći do TCCR0)
out TCCR0, R16							//załadowanie R16 do TCCR0


cbi PORTC, 0					//włączenie diody,w tym momencie dioda nie świeci ,ponieważ OCR0=0,czyli na PB3 panuje stan niski
ldi R16, 125
out OCR0, R16
main:			//pętla
nop				//nic nie rób
rjmp main

Program starałem się jak najdokładniej opisać komentarzami, więc mam nadzieję, że nie będzie większych problemów z jego zrozumieniem.

jeśli chcemy obliczyć częstotliwość pwm jaka generowana jest na wyjściu naszego timera ,możemy skorzystać ze wzoru, który można znależć w nocie aplikacyjnej atmegi32 na stronie 75

F_clock i/o to częstotliwośc naszego mikrokontrolera , w moim wypadku jest ona ustawiona na częstoliwość domyślną, czyli 1mhz. N to wartośc preskalera, jaką ustawiliśmy za pomocą rejestru TCCR0(bity CS00,CS01,CS02).

Dla zapoznania się trochę z tematem radzę, pobawić się troche wartością rejestru OCR0 oraz ustawieniami timera(np. spróbujcie zmienić tryb generowania PWM z nieodwracającego na odwracający i zaobserwować co się wtedy jak wtedy ma się jasność świecenia diody do wartości rejestru OCR0, spróbujcie też zwiększyć wartość preskalera np. Na 1024)

Ćwiczenie 2 – zabawy z diodą RGB:

Jeśli już trochę obeznaliście się z tematem pwm, pora przejśc do następnego programu który przygotowałem, tym razem wykorzystamy diodę RGB ze wspolną anodą i jak widzimy aby zapalić jeden z kolorów naszej diody musimy ustawić stan niski na katodzie odpowiadającej za dany kolor .

montujemy układ wg. Powyższego schematu i przystępujemy do pisania programu. Tym razem zadanie jest trochę bardziej skomplikowane spowodujemy, aby każdy kolor naszej diody był po kolei najpierw powoli rozjaśniany, następnie powoli wygaszany.

Program będzie wyglądał tak:

.nolist
.include "m32def.inc"
.list

.cseg
.org 0

cli						//wyłączenie przerwań
ldi R16, HIGH(RAMEND)	//zaimpelementowanie stosu
out SPH, R16
ldi R16, HIGH(RAMEND)
out SPL, R16

ldi R16, (1<<PC0)|(1<<PC1)|(1<<PC2)		//ustawienie linii do których podłączone są diody jako wyjściowe
out DDRC, R16							//ustawienie linii PC0,PC1,PC2 jako wyjściowe
out PORTC, R16							//wygaszenie diod na liniach PC0,PC1,PC2 poprzez ustawienie na nich stanu wysokiego(diody podłączone są katodami //,więc ustawienie stanu niskiego zapala je ,a wysokiego wygasza)

//ustawienia pwm
sbi DDRB, 3								//ustawiennie lini PB3 jako wyjście (na tej linii bedzie generowany sygnał pwm)
ldi R16, (1<<WGM00)|(1<<WGM01)|(1<<COM01)|(1<<CS00)	//załadowanie do R16 ,wartości ktora posłuży do ustawieni pwm(poprzez załadowanie tej wartośći do TCCR0)
out TCCR0, R16							//załadowanie R16 do TCCR0



main:
cbi PORTC, 0							//zapalenie diody podłączonej pod PC0
rcall wave								//wywołanie podprogramu który spowoduje -najpierw powolne rozjaśnienie,a następnie powolne wygaszenie zapalonej diody
sbi PORTC,0								//zgaszenie diody podłączonej pod PC0
cbi PORTC,1								//zapalenie diody podłączonej pod PC1
rcall wave								//wywołanie podprogramu wave
sbi PORTC,1								//zgasenie diody podłączonej pod PC1
cbi PORTC,2								//zapalenie diody podłączonej pod PC2			
rcall wave								//wywołanie podprogramu wave
sbi PORTC,2								//zgaszenie diody podłączonej pod PC2
rjmp main



wave:									//poprogram powodujący powolne rozjaśnianie diody led ,a następnie powolne wygaszanie
ldi R16, 0								//ładujemy do R16, 0
wave_up:								//część programu odpowiadająca za rozjaśnianie diody
inc R16									//zwiększenie zawartości R16 
out OCR0, R16							//załadownie zawartości R16 do OCR0
rcall delay								//wywołanie podprogramu opózniającego
cpi R16,255								//porówaninie zawartości R16 z maksymalną wartością jaka może znajdować się w rejestrze OCR0
brlo wave_up							//jeśli R16<255 następuje skok do Wave_up ,następnie ponowna inkrementacja OCR0 co powoduje rozjaśnienie diody

wave_down:								//poprogram odpowiadający za powolne wygaszanie diody
dec R16									//dekrementacja R16(zmniejszenie o 1)
out OCR0, R16							//załadowanie R16, do OCR0
rcall delay								//opóznienie
cpi R16,1								//porówanienie zawartości R16 z 1
brsh wave_down							//jeśli R16>1(czyli dioda nadal świeci) następuje skok do etykiety wave_dowm
ret										//instrukcja wykonywana ,gdy R16<1(czyli kiedy R16 wynosi 0 ,wcześniej ładowalismy zawartośc R16 do OCR0 ,więc wart. w tym rejestrze tez równa jest 0)

delay:									//poprogram opóżniający o 10ms
; ============================= 
;    delay loop generator 
;     10000 cycles:
; ----------------------------- 
; delaying 9999 cycles:
         ldi  R17, $21
WGLOOP0:  ldi  R18, $64
WGLOOP1:  dec  R18
         brne WGLOOP1
         dec  R17
         brne WGLOOP0
; ----------------------------- 
; delaying 1 cycle:
         nop
; ============================= 
ret

tak jak poprzednio program starałem się ubrać w jak najdokładniejsze komentarze. Chciałbym jedynie trochę powiedzieć o podprogramie wave, otóż powoduje on najpierw płynne rozjaśnianie diody Led, poprzez zwiększanie wartości rejestru OCR0(poprzez inkrementacje R16 i następnie zaladowania wartości z R16 do OCR0), jeśli wartość OCR0 osiągnie maximum (sprawdzana jest wartośc R16 która w poprzednich cyklach została załadowana do OCR0)następuje wykonywanie częsci programu("wave_down"), która powoduje powolne wygaszanie diody, działa to analogicznie do poprzedniej częsci z tym, że wartość OCR0 jest teraz dekrementowana. Potem następuje powrót do etykiety "main" i ustawienie stanu wysokiego na katodzie koloru który był aktualnie włączony(poprzez ustawienie stanu wysokiego wyłączamy daną diode/kolor) potem następuje ustawienie stanu niskiego(włączanie jej) na katodzie kolejnego koloru oraz wykonanie podprogragmu wave.

W poprzednim programie nie pojawiły sie żadne nowe instrukcje za to tym już tak, są to:

cpi - "Compare with Immediate" -porównaj bezpośrednio"nazwa rejestru(np. R16 lub R20)" "liczba z którą ma byc porównana wartośc rejestru"

brlo - "Branch if Lower" – skocz jeśli równe "do etykiety"

brsh - "Branch if same or higher" – skocz jeśli równe bądź większe"do etykiety"

Na zakończenie jeszcze słowo o innych timerach, ponieważ w atmedze32 mamy ich 3(timer0,timer1,timer2), my konfigurowaliśmy timer0, który jest 8-bitowy i wg. Nomenklatury atmela timery zawsze są numerowane tak: timer0 - zawsze jest 8-bitowy ,timer1- zawsze jest 16-bitowy,timer2-zawsze jest 8-bitowy i tak dalej.Jeśli już dowiedzieliście się, że w naszej atmedze dysponujemy timerem 16-bitowym(jego rejestr – czyli TCNT1 zlicza nie do 255(8-bitów) tylko do 65536(16-bitów),czyli możemy uzyskać większą rozdzielczośc generowanej częstotliwośći PWM. Do tego mamy sporo więcej trybów działania tego timera ) to jako zadanie domowe dla tych ambitniejszych, proponuję, aby spróbować go skonfigurować .

P.s tak jak ostatnio starałem sie, aby do artykuły nie wkradły się błędy,ale jeśli ktoś jakieś zauważy, czy to faktyczne, czy językowe proszę śmiało pisać.

Pozdrawiam 🙂

__________

Komentarz dodany przez: Treker

Kolejny raz zwracam na interpunkcje i spacje.

asm.jpg.66b5a718c7c4f6bcefa0b3e497c7c485.jpg

  • Lubię! 1
Link do komentarza
Share on other sites

@up ,ze względu na organizację pamięci SRAM ,nie możemy bezpośrednio załadować danej do rejestrów systemowych ,musimy użyć rejestrów pośredniczących jakimi są rejestry ogólnego przeznaczenia(w naszym wypadku R16)

Link do komentarza
Share on other sites

Swietna seria artykułów, oby było takich więcej.Tylko proponuje dodać więcej teorii

Co do pytani @up:

nie można odrazu ponieważ pamięc SRAM dzieli się na 3 "warstwy":

1)Rejestry robote(jest ich 32)

2)rejestry we/wy(np DDRC)

3)Pamiec RAM

ldi r18, (1<

natomiast gdybyśmy chceli wpisać tak;

out DDRC, (1<

to odwołujemy sie do dwóch warstw jednoczesnie(rejestry robocze i rejestry we/wy), czyli w tym przypadku jest to nie możliwe ponieważ nie mozna wpisac surowej wartości do 2 warstwy. Wszystko musi sie odbywac poprzez rejestry.

Trzeba sobie zapamiętać, że Rejestry robocze (np. R16, R17)są takim pośrednikiem pomiedzy tymi warstwami.Taki Listonosz 😃

Aby jeszcze bardziej rozjaśnić wyobraśmy sobie 3 pietrowy blok((zakladamy ze nie ma windy 😃):

naszym zadaniem jest przeniesienie książki z 2 piętra na 3.

jeśli otworzymy dzwi i rzucimy ją na schody, to niestety, ale sama nie dostanie nóg i nie pójdzie sobie piętro wyżej. Dlatego mamy w domu drugą osobe w postacii naszego brata(i to własnie on jest tym Rejestrem roboczym R16). Wręczamy mu książke(ldi r16, ksiazka), a potem dajemy mu polecenie "zanieś ksiązke na 3 piętro"

(out DDRA, r16).

Myśle, że wytłumaczyłem w miare zrozumiale i teraz każdy kapuje o co chodzi 😋

Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

brlo - "Branch if Lower" – skocz jeśli równe "do etykiety"

brsh - "Branch if same or higher" – skocz jeśli równe bądź większe"do etykiety"

brlo to skok jeśli "mniejsze", a nie "równe" - taki drobiazg

Assemblera na x86 znam dobrze, na x51 czy Motorolę 68 używałem na studiach, ale pod Atmegi nie miałem wcześniej styczności.. aż tu nagle potrzebuję przeanalizować to https://github.com/sim-/tgy/blob/master/tgy.asm i mały fragment przeportować na C dodając nieco inną funkcjonalność. Pot na czoło występuje suto.

Korzystam sobie z Twoich artykułów do tego i owego, więc dla mnie pojawiły się na czasie.

Link do komentarza
Share on other sites

W linijce:

ldi R16, (1<<WGM00)|(1<<WGM01)|(1<<COM01)|(1<<CS00)

kompilator wypluwa, że nie ma czegoś takiego jak COM01, gdy to wywalam jest ok, ale dioda nie świeci.

Podobnie jest w :

out SPL, R16 

Oto cały kod:

.nolist 
.include "tn13def.inc"
.list 

.cseg 
.org 0 
cli
;konfiguracja portów
ldi R16, HIGH(RAMEND)    

ldi R16, HIGH(RAMEND) 
sbi DDRB, 1
sbi DDRB, 2
sbi PORTB, 2
;pwm
ldi R17, (1<<WGM00)|(1<<WGM01)|(1<<CS00)
out TCCR0B, R17 
cbi PORTB, 2
ldi R16, 125
out OCR0B, R16

main:
nop
rjmp main

Czym to zastąpić. Dodaje, że mój uc to attiny13.

Ps. seria artykułów całkiem fajna 😉

Link do komentarza
Share on other sites

witam!

jeżeli ktoś czyta: uczę się z tego poradnika- do tej pory fajnie szło, ale teraz się zaciąłem...

nie kupiłem diody rgb więc posłużyłem się 3 różnymi diodami spiętymi anodą do siebie...

problem polega na tym że nie świeci mi się 3 kolor- to znaczy działa mi dioda na PC0 i na PC1 ale ta z PC2 już nie

czy kod jest 100% dobrze? i ja coś zachrzaniam(choć nie wiem co)

ogólnie wygląda to tak: ze zapala sie i gaśnie fajnie pierwsza z diod, później 2, a przez okres gdy powinna się świecić i gasnąć dioda na PC2 nic się nie dzieje...dlaczego?dodam żę posiadam identyczny mikrokontroler (atmega 32 16pu)

Link do komentarza
Share on other sites

Bo na porcie C od pinu 2 do ostatniego masz JTAG włączonego i piny te nie pracują jako normalne piny. Albo go wyłączasz przez fuse bity albo w kodzie na początku dajesz takie coś:

MCUCSR |=(1<<JTD);
MCUCSR |=(1<<JTD); 

Koniecznie dwa razy musi być to i potem już możesz normalnie korzystać z całego już portu C.

Link do komentarza
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.