Autor:
Dondu
Niniejszy artykuł pokazuje przykład generowania obwiedni za pomocą mikrokontrolera. Przykład oparłem o obwiednię typu
ADSR nawiązując do
konkursu ogłoszonego w październiku 2013r.
Na końcu artykułu znajdziesz nowe zadanie do rozwiązania oraz termin.
Dla przypomnienia obwiednia wygląda następująco:
Zauważ, że obwiednia
ADSR jest złożona z czterech odcinków będących
liniami prostymi, co znacząco wpływa na prostotę algorytmu programu. W artykule konkursowym podałem
prosty algorytm generowania takiej obwiedni i nie będę go tutaj powtarzał. Wskazałem tam także jak podzielić zadania pomiędzy timery.
Do generowania obwiedni użyjemy więc dwóch timerów:
- Timer0 do odmierzania czasu i podawania nowych próbek do generowania PWM,
- Timer2 do generowania 8-bitowego sygnału PWM na wyjściu OC2.
Timer2 ustawimy w ten sposób, by z maksymalną możliwą częstotliwością (preskaler równy 1), generował sygnał PWM. Dlatego też mikrokontroler będziemy taktować z zewnętrznego kwarcu
16MHz.
Timer0 ustawimy w taki sposób, by generował przerwania. Funkcja przerwania za pomocą programu odliczać będzie czas
zbliżony do 1ms (milisekundy) i po jej upływie zmieniała wypełnienie sygnału
PWM podając na niego kolejną wartość wypełnienia (ang. duty cycle). W przyszłości przerwania będziemy mogli także wykorzystać do eliminowania
drgań styków przycisków wyzwalających dźwięki.
Filtr dolnoprzepustowy
Aby sygnał
PWM zamienić na sygnał analogowy tworzący obwiednię, potrzebny będzie
filtr dolnoprzepustowy, który zrealizujemy możliwie najprościej za pomocą rezystora i kondensatora. Nasz schemat będzie więc wyglądał następująco:
Wyjaśnienia wymaga zastosowanie przyciski
S1. Podczas testów warto go dodać, by nie przeszkadzać niepotrzebnie przebiegom na pinie
MOSI w czasie programowania mikrokontrolera. Bez niego zapewne zaprogramujesz układ, ale nie należy ryzykować w szczególności podczas programowania fusebitów, by nie
zablokować mikrokontrolera.
Przykład takiego sposobu odtwarzania sygnału za pomocą PWM szczegółowo przedstawił Drzasiek w artykule:
FAST PWM - Sposób na DAC
Próba w CManiaku
Na początek do sprawdzenia algorytmu wykorzystamy
CManiak-a. Poniżej znajduje się program, który symuluje działanie
Timera0 (pętla
while) wywołującego kolejne przerwania.
Timer2 generujący sygnał
PWM wraz z
filtrem dolnoprzepustowym są symulowane za pomocą funkcji
rysuj_fragment_wykresu().
Przykład 1 (w kompilatorze)
// ***********************************************************
// Generator obwiedni
// wersja: 1.0
//
// Przykład do artykułu: http://mikrokontrolery.blogspot.com/2011/03/ATmega8-Generowanie-obwiedni-przyklad-ADSR.html
//
// Autor: Dondu
// Data: 2013.10.03
//
// Mikrokontroler: ATmega8
// Częstotliwość F_CPU: 16MHz (zewnętrzny kwarc)
// ***********************************************************
#include <stdio.h>
//definicje graniczne sygnału PWM
#define PWM_MAX 250
#define PWM_MIN 0
//definicja ilości odcinków, z których składa się obwiednia
//obwiednia ADSR składa się z 4 odcinków
#define OBWIEDNIA_ILOSC_ODCINKOW 4
//Na jedną milisekundę przypada około 8 przerwań
#define PRZERWAN_NA_MILISEKUNDE 8
//Tablice z parametrami określającymi kształt obwiedni
unsigned int obwiednia_kroki[OBWIEDNIA_ILOSC_ODCINKOW]=
{15, 4, 8, 5};
signed char obwiednia_nachylenie[OBWIEDNIA_ILOSC_ODCINKOW]=
{1, -1, 0, -2};
signed char OCR2; //symulujemy rejestr OCR2
//----------------------------------------------------------------------
//Funkcja rysująca fragment wykresu w terminalu CManiaka
//dla ułatwienia wykres jest odwrócony o 90 stopni
void rysuj_fragment_wykresu(signed char ocr2){
unsigned int f;
for(f=0; f<OCR2; f++) printf(" "); //najpierw odpowiednią ilość spacji
printf(". \r"); //a na szczycie pokaż kropkę
}
//----------------------------------------------------------------------
int main(void) {
//Zmienne lokalne statyczne dostępne tylko w tej funkcji i przechowujące
//dane pomiędzy kolejnymi przerwaniami. Więcej na ich temat:
//http://mikrokontrolery.blogspot.com/2011/02/kurs-jezyka-c-zmienne-lokalne-statyczne.html
//Parametry startowe ustawiają algorytm na końcu obwiedni,
//stąd po uruchomieniu programu algorytm rozpocznie od warunku,
//w którym sam ustawi się na początek obwiedni.
//przerwań dla odliczenia 1ms
static unsigned char licznik_przerwan = 0;
//pozostała ilość kroków w danym odcinku obwiedni
static unsigned int obwiednia_licznik_krokow = 1;
//aktualny nr generowanego odcinka obwiedni
static unsigned char obwiednia_indeks = 3;
//zmienna pomocnicza do zatrzymania programu
//i pokazania ilości wykonanych przerwań
unsigned int temp_i=0;
//Pętla nieskończona symulująca kolejne przerwania timera
while(1){
//czy już upłynęła 1 milisekunda?
if(++licznik_przerwan > (PRZERWAN_NA_MILISEKUNDE-1)){
//upłynęła 1ms czas zmienić próbkę PWM
licznik_przerwan = 0;
//czy to już ostatnia próbka w tej części ADSR?
if(--obwiednia_licznik_krokow == 0){
//tak
//Czy to już koniec obwiedni?
if(++obwiednia_indeks > (OBWIEDNIA_ILOSC_ODCINKOW-1)) {
//koniec obwiedni
OCR2 = PWM_MIN; //ustaw minimum wypełnienia PWM
obwiednia_indeks = 0; //resetuj licznik odcinków obwiedni
obwiednia_licznik_krokow = obwiednia_kroki[0];
//kończymy, gdy już narysowliśmy jedną pełną obwiednię
//potrzebna dodatkowa zmienna, ponieważ program zaczyna
//od przypadku, w którym ustawiony jest na końcu obwiedni
if(temp_i>7){
printf("ilość przerwań = %d \r", temp_i);
return 0;
}
}else{
//nowy fragment obwiedni
OCR2 = OCR2 + obwiednia_nachylenie[obwiednia_indeks];
obwiednia_licznik_krokow = obwiednia_kroki[obwiednia_indeks];
}
}else{
//dodaj do PWM kolejny krok stopnia nachylenia
OCR2 = OCR2 + obwiednia_nachylenie[obwiednia_indeks];
}
rysuj_fragment_wykresu(OCR2);
}
temp_i++;
}
}
Teraz zaglądnij do
CManiak-a, gdzie znajdziesz ten program (jako przykład nr 1). Wybierz go i skompiluj, a później zobacz wynik jego pracy w
zakładce Terminal. Twoim oczom ukaże się wykres naszej obwiedni obrócony o 90 stopni (by łatwo było napisać program wyświetlający wykres).
Jak działa program?
W zasadzie w kodzie są wszelkie komentarze istotne do zrozumienia jego działania. Tutaj dodam więc tylko parę zdań. Zacznę od tablic przechowujących parametry obwiedni:
obwiednia_kroki[]
Tablica ta przechowuje informacje o ilości kroków, które mają poszczególne fragmenty obwiedni. W naszym przypadku, będzie to ilość milisekund, które ma trwać dany fragment obwiedni:
Na rysunku są zaznaczone czasy zgodne z zadaniem konkursowym, ale w naszej tablicy w programie symulowanym w CManiak-u skróciłem je do następujących:
unsigned int obwiednia_kroki[OBWIEDNIA_ILOSC_ODCINKOW]= {15, 4, 8, 5};
by wykres wygenerowany w CManiak-u nie był zbyt duży.
obwiednia_nachylenie[]
Tablica ta zwiera współczynnik nachylenia, który dodawany jest za każdym krokiem do rejestru
OCR2, odpowiedzialnego za wypełnienie sygnału
PWM. Tablica ta zwierać może
liczby dodatnie (zbocze narastające) oraz
ujemne (zbocze opadające).
Im wartość bezwzględna współczynnika nachylenia jest większa, tym bardziej strome jest zbocze, i odwrotnie.
signed char obwiednia_nachylenie[OBWIEDNIA_ILOSC_ODCINKOW]= {1, -1, 0, -2};
Algorytm
Za każdym przerwaniem zliczane są przerwania składające się na
1ms. Gdy zostanie stwierdzony upływ
1ms, następuje dodanie kolejnego współczynnika nachylenia dla aktualnego fragmentu obwiedni. Po skończeniu generowania aktualnego odcinka, program przechodzi do następnego. Po skończeniu całej obwiedni program zatrzymuje się, bo to tylko symulator i wystarczy, że narysuje tylko jedną pełną obwiednię.
Czas na test w realnym środowisku
Skoro mamy już poprawnie działający algorytm, przeniesiemy go na nasz mikrokontroler
ATmega8.
Różnice dotyczą ustawienia timerów oraz dodaniu funkcji obsługi przerwania i przekopiowaniu do niej części kodu. Minimalnie pozmieniałem też nazwy definicji dodając przedrostki
TIMER0. Kod jest dobrze opisany więc nie powinno być problemów z jego analizą i zrozumieniem.
Program w przeciwieństwie do tego w CManiaku, nie zatrzymuje się, tylko po skończeniu generowania obwiedni, rozpoczyna generowanie następnej.
// ***********************************************************
// Generator obwiedni
// wersja: 1.0
//
// Przykład do artykułu: http://mikrokontrolery.blogspot.com/2011/03/ATmega8-Generowanie-obwiedni-przyklad-ADSR.html
// Autor: Dondu
// Data: 2013.10.03
//
// Mikrokontroler: ATmega8
// Częstotliwość F_CPU: 16MHz (zewnętrzny kwarc)
// ***********************************************************
#include <avr/io.h>
#include <stdint.h>
#include <avr/interrupt.h>
#include <math.h>
//definicje graniczne sygnału PWM
#define PWM_MAX 250
#define PWM_MIN 5
//definicja ilości odcinków, z których składa się obwiednia
//obwiednia ADSR składa się z 4 odcinków
#define OBWIEDNIA_ILOSC_ODCINKOW 4
//Timer0 odmierza odcinki czasowe potrzebne do odliczenia 1 milisekundy
//Na jedną milisekundę przypada około 8 przerwań
#define TIMER0_PRZERWAN_NA_MILISEKUNDE 8
//Definicje dot. pinu wyjściowego PWM
#define PWM_PIN (1<<PB3)
#define PWM_DDR DDRB
//Tablice z parametrami określającymi kształt obwiedni
volatile unsigned int obwiednia_kroki[OBWIEDNIA_ILOSC_ODCINKOW]=
{240, 100, 350, 70};
volatile signed char obwiednia_nachylenie[OBWIEDNIA_ILOSC_ODCINKOW]=
{1, -1, 0, -2};
int main(void)
{
PWM_DDR |= PWM_PIN; //ustaw pin PWM Timera1 jako wyjście
//Timer 2 odpowiada za generowanie sygnału PWM
TCCR2 = (1<<WGM21) | (1<<WGM20) //tryb Fast PWM
| (1<<COM21) //pin OC2 ustawiany gdy timer ma wartość 0
//i zerowany gdy osianie zadaną wartość OCR2
| (1<<CS20); //włącz Timer2 ustawiając preskaler 1
//Timer0 (jego zadania opisane wyżej)
TCCR0 = (1<<CS01); // włącz Timer0 ustawiając preskaler 8
TIMSK |= (1<<TOIE0); // włącz przerwania overflow (przepełnienie timera)
sei(); //włącz przerwania
while(1); //pętla nieskończona
}
//Procedura obsługi przerwania od przepełnienia Timera0
ISR(TIMER0_OVF_vect)
{
//Zmienne lokalne statyczne dostępne tylko w tej funkcji i przechowujące
//dane pomiędzy kolejnymi przerwaniami. Więcej na ich temat:
//http://mikrokontrolery.blogspot.com/2011/02/kurs-jezyka-c-zmienne-lokalne-statyczne.html
//Parametry startowe ustawiają algorytm na końcu obwiedni,
//stąd po uruchomieniu programu algorytm rozpocznie od warunku,
//w którym sam ustawi się na początek obwiedni.
//przerwań dla odliczenia 1ms
static unsigned char licznik_przerwan = 0;
//pozostała ilość kroków w danym odcinku obwiedni
static unsigned int obwiednia_licznik_krokow = 1;
//aktualny nr generowanego odcinka obwiedni
static unsigned char obwiednia_indeks = 3;
//--- główna część funkcji --------------------------
//czy już upłynęła 1 milisekunda?
if(++licznik_przerwan > (TIMER0_PRZERWAN_NA_MILISEKUNDE-1)){
//upłynęła 1ms czas zmienić próbkę PWM
licznik_przerwan = 0;
//czy to już ostatnia próbka w tej części obwiedni?
if(--obwiednia_licznik_krokow == 0){
//tak
//Czy to już koniec obwiedni?
if(++obwiednia_indeks > (OBWIEDNIA_ILOSC_ODCINKOW-1)) {
//koniec obwiedni
OCR2 = PWM_MIN; //ustaw minimum wypełnienia PWM
obwiednia_indeks = 0; //resetuj licznik odcinków obwiedni
obwiednia_licznik_krokow = obwiednia_kroki[0];
}else{
//nowy fragment obwiedni
OCR2 = OCR2 + obwiednia_nachylenie[obwiednia_indeks];
obwiednia_licznik_krokow = obwiednia_kroki[obwiednia_indeks];
}
}else{
//dodaj do PWM kolejny krok stopnia nachylenia
OCR2 = OCR2 + obwiednia_nachylenie[obwiednia_indeks];
}
}
}
Do pobrania
W załączniku projekt
AVR Studio 4.18:
ADSR-ver1.zip kopia:
ADSR-ver1.zip
W załączniku znajdziesz także
plik HEX skompilowany dla mikrokontrolera
ATmega8 z kwarcem
16MHz.
Rezultat
Rezultat jaki zaobserwujesz na ekranie oscyloskopu w wyniku działania powyższego programu (oczywiście po naciśnięciu przycisku
S1) będzie następujący:
Po prawej stronie widać już początek kolejnej obwiedni. I to właśnie chcieliśmy osiągnąć w zadaniu nr 1 :-)
Zadanie 2
Konkursowe zadanie nr 2 jest na tyle proste do wykonania, że nie będę go przedstawiał - wystarczy że sam zmodyfikujesz powyższy program. Zamiast tego ogłaszam:
NOWY KONKURS
Zadanie nr 3
Jak już wspomniałem w
artykule konkursowym, dążymy do wykonania prostego syntezatora dźwięków. Powinniśmy więc mieć możliwość płynnej regulacji parametrów
ADSR. Zadanie jest więc następujące:
Na bazie powyższego programu, należy utworzyć funkcję(-e), które umożliwią obliczenie współczynnika nachylenia (dla uproszczenia) tylko części
R obwiedni w zależności od stanu napięcia na wejściu
ADC do którego podłączymy
dzielnik rezystorowy w postaci
potencjometru.
EDIT 2013.10.09: Czas trwania części R ma być regulowany od 0 do 5 sekund.
Będziesz więc musiał tak zmodyfikować powyższy program, by przeliczał odpowiednio parametry w ostatnich komórkach tablic
obwiednia_kroki[] oraz
obwiednia_nachylenie[].
Zauważ, że tablica współczynników nachyleń jest typu char (liczby całkowite). Nam zależy na tym, by regulacja potencjometrem była w miarę płynna. Oznacza to, że należy wprowadzić ułamkowe wartości współczynnika nachylenia. Jednakże:
Nie dopuszczamy możliwości stosowania liczb zmiennoprzecinkowych.
Dlatego też Twoim zadaniem będzie także modyfikacja sposobu reprezentacji liczb w powyższym programie. Oczywiście rozwiązanie tego problemu zależy tylko od Ciebie :-)
Po dodaniu
potencjometru oraz
kondensatora C6 na pinie AREF (z powodu użycia przetwornika ADC), nasz schemat będzie więc wyglądał na przykład tak:
Reasumując
Rozwiązanie ma doprowadzić do sytuacji, w której za pomocą
potencjometru będzie można regulować nachylenie
odcinka R obwiedni, co zaobserwujemy na oscyloskopie.
Termin nadsyłania rozwiązań
Termin upływa w
piątek 11 października 2013r. o godz. 23:59:59
Tak jak poprzednie zadania, także i to należy wysyłać na adres:
UWAGA!
- Gdyby adres był nieczytelny to podpowiem, że składa się z liczby mnogiej słowa mikrokontroler, kropki i mojego nicku, a domena to: gmail.com.
- Po dotarciu maila na wyżej podane konto, automatycznie otrzymasz mailem informację o tym.
Nagroda
Nagrodą będzie wybrana przez zwycięzcę książka wydawnictwa Helion w formie eBook. Innymi słowy zwycięzca sam wybierze nagrodę :-)
41