Redakcja: Dondu
na początek wypadało by się przedstawić. Mam na imię Kamil, na co dzień studiuję informatykę - elektronika to moje hobby, ale również z nią wiążę moją przyszłość.
O naszym świątecznym konkursie dowiedziałem się dopiero wczoraj, mniej więcej 24h przed końcem zgłoszeń ;) Jednak to mnie nie zniechęciło do wzięcia udziału, wręcz przeciwnie - była to dla mnie motywacja.
Projekt wymyśliłem w nocy, założeniem było zbudować małą choinkę z diodami LED [smd], sterowanymi poprzez programowe PWM mikrokontrolera atmega8. Rano od razu zabrałem się za jego realizację.
Najpierw napisałem program, który generował impulsy o dowolnie zmiennej szerokości na wybranym przez nas porcie uC.
Mikrokontroler został ustawiony na wewnętrzne 8Mhz.
Wykorzystałem 6 portów w zakresie od PC0 do PC5 oraz 8 bitowy Timer/Counter0, który przy przepełnieniu wyzwalał przerwanie. Prescaler licznika został ustawiony na 1, czyli "No prescaling".
W przerwaniu ustalana jest szerokość aktualnego impulsu, który ma się pojawić na danym wyjściu/wyjściach.
Pętla główna while(1) zawiera tylko naszą wizualizację, czyli szereg zmian wartości wypełnienia wraz z zadanymi odstępami czasowymi.
Program jest stosunkowo prosty, więc nawet początkujący nie powinni mieć problemów ze zrozumienie kodu ( dodane komentarze powinny wszystko rozjaśnić ).
Kod programu (bez wizualizacji):
#include <avr/io.h> #include <avr/interrupt.h> #include <util/delay.h> volatile uint8_t PWM_0, PWM_1, PWM_2, PWM_3, PWM_4, PWM_5; // zmienne globalne 8-bitowe // oznaczenie gałęzi choinki ( PWM_x ): //\\ 5,2 // \\ 4,1 // \\ 3,0 int main(void) { DDRC |=(1<<PC0)|(1<<PC1)|(1<<PC2)|(1<<PC3)|(1<<PC4)|(1<<PC5); // porty I/O PORTC|=(1<<PC0)|(1<<PC1)|(1<<PC2)|(1<<PC3)|(1<<PC4)|(1<<PC5); // porty I/O TCCR0 |= (1<<CS00); // zegar bez preskalera, czyli CLK/1 (Fcpu) TIMSK |= (1<<TOIE0); // przerwanie przy przepełnieniu sei(); // globalna obsługa przerwań while (1) // pętla główna { // tu będzie nasza animacja } } ISR(TIMER0_OVF_vect) // nasze przerwanie { static uint8_t cnt; // 8-bitowy licznik wypełnienia if(cnt<=PWM_0){ // wypełnienie impulsu w przedziale 0-255 PORTC |= (1<<PC0); }else{ PORTC &= ~(1<<PC0); } if(cnt<=PWM_1){ PORTC |= (1<<PC1); }else{ PORTC &= ~(1<<PC1); } if(cnt<=PWM_2){ PORTC |= (1<<PC2); }else{ PORTC &= ~(1<<PC2); } if(cnt<=PWM_3){ PORTC |= (1<<PC3); }else{ PORTC &= ~(1<<PC3); } if(cnt<=PWM_4){ PORTC |= (1<<PC4); }else{ PORTC &= ~(1<<PC4); } if(cnt<=PWM_5){ PORTC |= (1<<PC5); }else{ PORTC &= ~(1<<PC5); } cnt++; // inkrementacja licznika }
Po napisaniu programu zająłem się naszym drzewkiem. Wykonane ono zostało z drutu miedzianego 3mm, wygiętego na wcześniej przygotowany wzór:
Wzór |
Diody katodą zostały przylutowane do ramki, a anody z każdej gałęzi zostały połączone ze sobą (przewodem w izolacji) i wyprowadzone na dół ( tylko delikatnie :P ).
Ramka i diody |
Dodałem podstawkę z której została wyprowadzona taśma ( 6 gałęzi + masa ).
Masa została podłączona do GND, reszta przewodów taśmy przez rezystory 330ohm do portów uC od PC0 do PC5.
Miedziana ramka została owinięta niebieską nitką. Całość prezentuje się tak:
Całość |
Montaż ( z braku czasu ) został wykonany na płytce stykowej:
Płytka |
Po dodaniu ozdóbek na koniec przedstawiam zdjęcie i filmik prezentujący efekt końcowy:
... i koniec :-) |
Cały program wraz z przykładową animacją ( w pętli głównej ):
main.c
#include <avr/io.h> #include <avr/interrupt.h> #include <util/delay.h> volatile uint8_t PWM_0, PWM_1, PWM_2, PWM_3, PWM_4, PWM_5; // zmienne globalne 8-bitowe // oznaczenie gałęzi ( PWM_x ): //\\ 5,2 // \\ 4,1 // \\ 3,0 int main(void) { DDRC |=(1<<PC0)|(1<<PC1)|(1<<PC2)|(1<<PC3)|(1<<PC4)|(1<<PC5); // porty I/O PORTC|=(1<<PC0)|(1<<PC1)|(1<<PC2)|(1<<PC3)|(1<<PC4)|(1<<PC5); // porty I/O TCCR0 |= (1<<CS00); // zegar bez preskalera, czyli CLK/1 (Fcpu) TIMSK |= (1<<TOIE0); // przerwanie przy przepełnieniu sei(); // globalna obsługa przerwań while (1) // pętla główna { for(int i=0; i<=255; i++) { PWM_0 = i; PWM_1 = i; PWM_2 = i; PWM_3 = i; PWM_4 = i; PWM_5 = i; _delay_ms(25); } for(int i=255; i>0; i--) { PWM_0 = i; PWM_1 = i; PWM_2 = i; PWM_3 = i; PWM_4 = i; PWM_5 = i; _delay_ms(25); } _delay_ms(2000); for(int i=0; i<=255; i++) { PWM_2 = i; PWM_5 = i; _delay_ms(10); } for(int i=0; i<=255; i++) { PWM_1 = i; PWM_4 = i; _delay_ms(10); } for(int i=0; i<=255; i++) { PWM_0 = i; PWM_3 = i; _delay_ms(10); } _delay_ms(1000); for(int i=0; i<=20; i++) { PWM_0 = 0; PWM_1 = 0; PWM_2 = 0; PWM_3 = 0; PWM_4 = 0; PWM_5 = 0; _delay_ms(100); PWM_0 = 255; PWM_1 = 255; PWM_2 = 255; PWM_3 = 255; PWM_4 = 255; PWM_5 = 255; _delay_ms(100); } _delay_ms(1000); PWM_0 = 0; PWM_1 = 0; PWM_2 = 0; PWM_3 = 0; PWM_4 = 0; PWM_5 = 0; for(int i=0; i<=255; i++) { PWM_3 = i; PWM_4 = i; PWM_5 = i; _delay_ms(5); } for(int i=0; i<=5; i++) { for(int i=0; i<=255; i++) { PWM_0 = i; PWM_1 = i; PWM_2 = i; PWM_3 --; PWM_4 --; PWM_5 --; _delay_ms(5); } for(int i=0; i<=255; i++) { PWM_0 --; PWM_1 --; PWM_2 --; PWM_3 = i; PWM_4 = i; PWM_5 = i; _delay_ms(5); } } _delay_ms(1000); PWM_0 = 255; PWM_1 = 255; PWM_2 = 255; PWM_3 = 255; PWM_4 = 255; PWM_5 = 255; _delay_ms(1000); PWM_5 = 0; _delay_ms(1000); PWM_3 = 0; _delay_ms(1000); PWM_1 = 0; _delay_ms(1000); PWM_2 = 0; _delay_ms(1000); PWM_4 = 0; _delay_ms(1000); PWM_0 = 0; _delay_ms(2000); } } ISR(TIMER0_OVF_vect) // nasze przerwanie { static uint8_t cnt; // 8-bitowy licznik wypełnienia if(cnt<=PWM_0){ // wypełnienie impulsu w przedziale 0-255 PORTC |= (1<<PC0); }else{ PORTC &= ~(1<<PC0); } if(cnt<=PWM_1){ PORTC |= (1<<PC1); }else{ PORTC &= ~(1<<PC1); } if(cnt<=PWM_2){ PORTC |= (1<<PC2); }else{ PORTC &= ~(1<<PC2); } if(cnt<=PWM_3){ PORTC |= (1<<PC3); }else{ PORTC &= ~(1<<PC3); } if(cnt<=PWM_4){ PORTC |= (1<<PC4); }else{ PORTC &= ~(1<<PC4); } if(cnt<=PWM_5){ PORTC |= (1<<PC5); }else{ PORTC &= ~(1<<PC5); } cnt++; // inkrementacja licznika }
Artykuł skończyłem pisać na styk, kilka sekund przed końcem czasu ;)
Mam nadzieje, że mój projekt komuś się spodoba oraz że spełnia założenia konkursu i nadaje się na Świąteczny Prezent. Chyba o niczym nie zapomniałem? ;)
Uwagi redakcji
Prosty projekt więc uwag technicznych niewiele:
1. łączenie równoległe diod LED nie powinno się stosować, ale rozumiemy pewien kompromis zastosowany przez autora tym bardziej, że jak sądzimy wykorzystał diody o wysokim współczynniku emisji strumienia światła. W takim przypadku należy ustalić za pomocą multimetru wartość napięcia przewodzenia każdej diody i dobrać je w ten sposób, aby każda grupa miała diody o prawie takim samym napięciu. To zapobiegnie problemom opisanym tutaj: Diody LED - Źródło prądowe i łączenie równoległe.
2. łączny pobór prądu przez wszystkie zapalone diody prawdopodobnie (autor nie podał wartości prądów) zbliża się lub przekracza wartość prądu jaką może dostarczać łącznie pin, port lub nawet cały mikrokontroler. Jeżeli prąd przekracza wartości dopuszczalne dla pojedynczego pinu, to należy odpowiednio zwiększyć wartość rezystora np. kalkulator diod LED: Dioda LED - Jak obliczyć wartość rezystora?
Jeżeli przekroczona zostaje łączna wartość prądów portu (w projekcie autor podłączył wszystkie diody portu C), to należy część z nich podłączyć do pinów innych portów D i/lub B.
Jeżeli przekraczamy sumaryczny prąd dopuszczalny dla danego mikrokontrolera, to należy zwiększyć rezystory (jak piszemy wyżej) ponieważ w tym projekcie w efekcie końcowym nieco słabsze świecenie diod nie wpłynie na jego piękno :-)
Więcej na temat prądów vs mikrokontroler: Mikrokontroler vs prądy pinów
3. Program niezbyt optymalny i w takich przypadkach warto wykorzystywać tablice przechowywane w pamięci FLASH. Mało komentarzy.
pomysł OK - wykonanie również, ode mnie 6 pkt za zaangażowane
OdpowiedzUsuń