Redakcja: Dondu
Program przesłany przez Macieja jest najbardziej zbliżony do wymagań jakie postawiliśmy w zadaniach nr 1 i 2 konkurs dot. generowania obwiedni.
Maciej zastosował metodę obliczenia współczynnika nachylenia bez używania operacji zmiennoprzecinkowych. Zastosował także definicje preprocesora. Dzięki definicjom łatwo zmieniać parametry obwiedni oraz reakcję programu na naciśnięcie przycisku.
Maciej wykonał także eliminację drgań styków przycisku wyzwalającego. Opóźnienie niezbędne do eliminacji drgań styków oparł o timer wykorzystany do generowania ADSR, czyli tak jak to powinno się robić w przypadku większości projektów.
Dodatkowo opracował algorytm powtarzający przebieg dopóki przycisk jest wciśnięty. Nie było to jednak brane pod uwagę, przy ocenianiu przesłanych materiałów.
Jego rozwiązanie zostało przeze mnie sprawdzone i prawidłowo generuje obwiednię ADSR.
Programy działają wyłącznie w oparciu o przerwania, pozostawiając pustą pętlę główną. To najbardziej pożądany sposób realizowania tego typu zadań, pozwalający na równoległe wykonywanie innych czynności przez mikrokontroler lub jego uśpienie.
Schemat
Ponieważ do generowania obwiedni Maciej używa Timer2, stąd wyjście sygnału PWM jest na pinie OC2, czyli PB3. Do niego podłączony jest filtr RC według schematu w tym artykule.
Rozwiązanie zadania nr 1
Generowanie powtarzającej się obwiedni ADSR:
/* * Obwiednia.c * * Created: 2013-10-06 09:49:30 * Author: mlodedrwale (Maciej Łukaszewicz) * * Program dla Atmega8(a) częstotliwość 8mhz * częstotoliwość pwm = częstotliwość PWM 31250Hz wyższa niż pasmo akustyczne * Wykorzystano Timer0 do generowania przerwania co 1ms * oraz Timer2 do generowania sygnału pwm. * */ //######## USTAWIENIA ######### #define ATTACK_MAX 100 //w procentach #define ATTACK_TIME 250 //w ms #define DECAY_MAX 60 #define DECAY_TIME 100 #define SUSTAIN_MAX 60 #define SUSTAIN_TIME 350 #define RELEASE_TIME 300 //############################# #define OUTPUT PB3 //wyjście PWM #include <avr/io.h> #include <avr/interrupt.h> volatile uint16_t current_time = 1; // licznik przerwań volatile uint8_t last_pwm_value = 0; // zmienna potrzebna do sprawdzenia, //czy wartość pwm uległa zmianie // funkcja oblicza odpowiednią wartość wypełnienia pwm (rozdielczość 1%) // na podstawie wartości początkowej i końcowej funkcji liniowej, całkowitego // czasu jej trwania oraz czasu, który minął od jej rozpoczęcia, // następnie sprawdzane jest, czy wartość pwm uległa zmianie i jeśeli // uległa wprowadzana jest nowa wartość void set_pwm(uint8_t pwm_start, uint8_t pwm_stop, uint16_t length, uint16_t my_current_time) { uint8_t pwm_value; if(pwm_start < pwm_stop) { pwm_value = (0xFF*(pwm_start+((pwm_stop - pwm_start) *my_current_time)/length))/100; } else if(pwm_start > pwm_stop) { pwm_value = (0xFF*(pwm_start-((pwm_start-pwm_stop) *my_current_time)/length))/100; } else { pwm_value = (0xFF * pwm_start) / 100; } if(last_pwm_value != pwm_value) { // jeśli wartość PWM uległa zmianie zostanie zapisana nowa wartość OCR2 = pwm_value; last_pwm_value = pwm_value; } } ISR(TIMER0_OVF_vect) // timer0 overflow interrupt { TCNT0 = 131; //załadowanie licznika tak by przerwanie było //co 8000 cykli zegara if(current_time <= ATTACK_TIME) { //ATTACK set_pwm(0, ATTACK_MAX, ATTACK_TIME, current_time); } else if(current_time <= ATTACK_TIME + DECAY_TIME) { //DECAY set_pwm(ATTACK_MAX, DECAY_MAX, DECAY_TIME, current_time - ATTACK_TIME); } else if(current_time <= ATTACK_TIME + DECAY_TIME + SUSTAIN_TIME) { //SUSTAIN set_pwm(DECAY_MAX, SUSTAIN_MAX, SUSTAIN_TIME, current_time - (ATTACK_TIME + DECAY_TIME)); } else if(current_time <= ATTACK_TIME + DECAY_TIME + SUSTAIN_TIME + RELEASE_TIME) { //RELEASE set_pwm(SUSTAIN_MAX, 0, RELEASE_TIME, current_time - (ATTACK_TIME + DECAY_TIME + SUSTAIN_TIME)); } else { current_time = 0; // zerujemy przepełniony licznik przerwan :) } current_time += 1; } int main(void) { //Inicjujemy //PWM DDRB |= (1<<OUTPUT); //wyjście PORTB |= (1<<OUTPUT); //pullup OCR2 = 0; //po starcie pwm ma zerowe wypełnienie TCCR2 |=(1<<COM21) | (1<<CS20) //brak preskalera - częstotliwość PWM 31250Hz | (1<< WGM20) | (1<< WGM21); //fast PWM //Przerwania TCCR0 |= (1<< CS01) | (1<< CS00); //preskaler 64 TIMSK |= (1<<TOIE0); //włączamy przerwanie przepełnienia Timera0 sei(); //włączamy przerwania while(1) { //pętla główna //na razie wszystko w przerwaniach :) } }
AVR Memory Usage
----------------
Device: atmega8
Program: 586 bytes (7.2% Full)
(.text + .data + .bootloader)
Data: 3 bytes (0.3% Full)
(.data + .bss + .noinit)
Build succeeded with 0 Warnings...
----------------
Device: atmega8
Program: 586 bytes (7.2% Full)
(.text + .data + .bootloader)
Data: 3 bytes (0.3% Full)
(.data + .bss + .noinit)
Build succeeded with 0 Warnings...
A tak działa w praktyce:
Rozwiązanie zadania nr 2
Wersja z przyciskiem podłączonym do pinu PD3:
/* * Obwiednia_przycisk.c * * Created: 2013-10-06 09:49:30 * Author: mlodedrwale (Maciej Łukaszewicz) * * Program dla Atmega8(a) częstotliwość 8mhz * częstotoliwość pwm = częstotliwość PWM 31250 Hz - wyższa niż * pasmo akustyczne * Wykorzystano Timer0 do generowania przerwania co 1ms * oraz Timer2 do generowania sygnału pwm. * */ //######## USTAWIENIA ######### #define ATTACK_MAX 100 //w procentach #define ATTACK_TIME 250 //w ms #define DECAY_MAX 60 #define DECAY_TIME 100 #define SUSTAIN_MAX 60 #define SUSTAIN_TIME 350 #define RELEASE_TIME 300 #define BUTTON_TIME 50 // czas w ms po którym uznajemy, że przycisk // został wciśnięty #define BUTTON_REPEAT_TIME 1050 // czas w ms po którym uznajemy że wciśnięty // przycisk został ponownie wciśnięty. // Dla wartości 0 wyłączamy tę funkcję. //definiujemy przycisk #define BUTTON_BIT PD3 #define BUTTON_DDR DDRD #define BUTTON_PORT PORTD #define BUTTON_PIN PIND //############################# #define OUTPUT PB3 //wyjście PWM #include <avr/io.h> #include <avr/interrupt.h> // licznik przerwań zainicjowany warością wyższą niż wartość właściwa //dla obwiedni volatile uint16_t current_time = ATTACK_TIME + DECAY_TIME + SUSTAIN_TIME + RELEASE_TIME + 1; volatile uint8_t last_pwm_value = 0; // zmienna potrzebna do sprawdzenia, //czy wartość pwm uległa zmianie volatile uint16_t button_time = 0; // licznik czasu wciśnięcia przycisku //---------------------------------------------------------------------- //funkcja sprawdza, czy został wciśnięty przycisk i podejmuje odpowiednie //działania void button() { if((BUTTON_PIN & (1<<BUTTON_BIT)) == 0) { //przycisk wciśnięty button_time++; if(button_time == BUTTON_TIME) { current_time = 1; // jeśli przycisk został wciśnięty przez odpowiedni // czas zaczynamy generować obwiednię } if(BUTTON_REPEAT_TIME == 0) { if(button_time > BUTTON_TIME) { // jeśli zdecydowaliśmy, że przycisk nie jest powtarzalny button_time = BUTTON_TIME; } // musimy zadbać aby przy wciśniętym ciągle przycisku // jego licznik nie uległ przepełnieniu } else if(button_time == BUTTON_REPEAT_TIME) { //zerowanie licznika przycisku, gdy ma nastąpić powtórzenie button_time = 0; } } else { button_time = 0; // gdy nie wciśnięty zerujemy! } } //---------------------------------------------------------------------- // funkcja oblicza odpowiednią wartość wypełnienia pwm (rozdzielczość 1%) // na podstawie wartości początkowej i końcowej funkcji liniowej, całkowitego // czasu jej trwania oraz czasu, który minął od jej rozpoczęcia, // następnie sprawdzane jest, czy wartość pwm uległa zmianie i jeśeli uległa // wprowadzana jest nowa wartość void set_pwm(uint8_t pwm_start, uint8_t pwm_stop, uint16_t length, uint16_t my_current_time) { uint8_t pwm_value; if(pwm_start < pwm_stop) { pwm_value = (0xFF*(pwm_start+((pwm_stop - pwm_start) *my_current_time)/length))/100; } else if(pwm_start > pwm_stop) { pwm_value = (0xFF*(pwm_start-((pwm_start-pwm_stop) *my_current_time)/length))/100; } else { pwm_value = (0xFF * pwm_start) / 100; } if(last_pwm_value != pwm_value) { // jeśli wartość PWM uległa zmianie zostanie zapisana nowa wartość OCR2 = pwm_value; last_pwm_value = pwm_value; } } //---------------------------------------------------------------------- ISR(TIMER0_OVF_vect) // timer0 overflow interrupt { TCNT0 = 131; //załadowanie licznika tak by przerwanie było //co 8000 cykli zegara if(current_time <= ATTACK_TIME) { //ATTACK set_pwm(0, ATTACK_MAX, ATTACK_TIME, current_time); } else if(current_time <= ATTACK_TIME + DECAY_TIME) { //DECAY set_pwm(ATTACK_MAX, DECAY_MAX, DECAY_TIME, current_time - ATTACK_TIME); } else if(current_time <= ATTACK_TIME + DECAY_TIME + SUSTAIN_TIME){ //SUSTAIN set_pwm(DECAY_MAX, SUSTAIN_MAX, SUSTAIN_TIME, current_time - (ATTACK_TIME + DECAY_TIME)); } else if(current_time <= ATTACK_TIME + DECAY_TIME + SUSTAIN_TIME + RELEASE_TIME) { //RELEASE set_pwm(SUSTAIN_MAX, 0, RELEASE_TIME, current_time - (ATTACK_TIME + DECAY_TIME + SUSTAIN_TIME)); } if(current_time <= ATTACK_TIME + DECAY_TIME + SUSTAIN_TIME + RELEASE_TIME) { //jeśli jesteśmy w trakcie generowania obwiedni zwiększamy licznik current_time += 1; } button(); } //---------------------------------------------------------------------- int main(void) { //Inicjujemy: //Przycisk BUTTON_DDR &= ~(1<<BUTTON_BIT); //wejście BUTTON_PORT |= (1<<BUTTON_BIT); //wewnętrzny pullup //PWM DDRB |= (1<<OUTPUT); //wyjście PORTB |= (1<<OUTPUT); //pullup OCR2 = 0; //po starcie pwm ma zerowe wypełnienie TCCR2 |=(1<< CS20) //brak preskalera - częstotliwość PWM 31250 Hz | (1<< WGM20) | (1<< WGM21) //fast PWM | (1<< COM21); //sygnał pwm na wyjściu OC2 //Przerwania TCCR0 |= (1<< CS01) | (1<< CS00); //preskaler 64 TIMSK |= (1<<TOIE0); //włączamy przerwanie przepełnienia Timera0 sei(); //włączamy przerwania while(1) { //pętla główna //na razie wszystko w przerwaniach :) } }
Program zajmuje:
AVR Memory Usage
----------------
Device: atmega8
Program: 676 bytes (8.3% Full)
(.text + .data + .bootloader)
Data: 5 bytes (0.5% Full)
(.data + .bss + .noinit)
Build succeeded with 0 Warnings...
----------------
Device: atmega8
Program: 676 bytes (8.3% Full)
(.text + .data + .bootloader)
Data: 5 bytes (0.5% Full)
(.data + .bss + .noinit)
Build succeeded with 0 Warnings...
Wygenerowana obwiednia wygląda następująco (włączona opcja powtarzania obwiedni podczas trzymania przycisku):
Zwycięzca!
Jako zwycięzca Maciej otrzymał nagrodę w postaci książki w wersji eBook ufundowanej przez eBookPoint:
za pomocą platformy Arduino, systemu Android i zwykłego komputera
Brak komentarzy:
Prześlij komentarz