wtorek, 22 marca 2011

Obwiednia ADSR: Zadania 1 i 2 by Maciej Łukaszewicz

Autor: Maciej Łukaszewicz
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...

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...


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:


Inteligentny dom. Automatyzacja mieszkania za pomocą platformy Arduino, systemu Android i zwykłego komputera.

Inteligentny dom. Automatyzacja mieszkania
za pomocą platformy Arduino, systemu Android i zwykłego komputera


Brak komentarzy:

Prześlij komentarz