PODZESPOAY
Gadający STM32
Zastosowanie kodeka Speex
do odtwarzania komunikatów
głosowych
Konstruktorzy urządzeń elektronicznych często decydują się na
dodanie do swoich projektów funkcji odtwarzania komunikatów
głosowych. Na łamach Elektroniki Praktycznej prezentowane
były już najróżniejsze gadające zegary, termometry czy też
Przygotowanie nagrań
woltomierze. Najczęściej zadanie rejestracji, przechowywania
dzwiękowych
i odtwarzania dzwięków powierzane było specjalizowanym
Komunikaty głosowe, które mają być odtwo-
układom magnetofonów półprzewodnikowych, czyli układom
rzone przez przykładową aplikację należy na-
ISD14xx. Podyktowane to było stosunkowo prostą ich obsługą grać na komputerze PC z wykorzystaniem do-
wolnego programu do rejestracji dzwięku, np.
a przede wszystkim brakiem innej tak prostej i stosunkowo taniej
systemowego Rejestratora Dzwięku i zapisać
technologii przechowywania dzwięku. Przechowywanie dzwięków
je w formacie .wav. Ważne jest, aby częstotli-
w surowej postaci jest ogromnie nieefektywne pod względem
wość próbkowania dzwięku podczas rejestracji
wymaganej pojemności pamięci, natomiast dekompresja chociażby
była równa 8 kHz. Rozdzielczość próbkowania
najpopularniejszego formatu dzwięku MP3 wymaga bardzo dużej,
powinna wynosić 16-bitów. Następnie należy
jak na typowe układy mikrokontrolerowe, mocy obliczeniowej, albo
dokonać kompresji nagrań za pomocą programu
specjalizowanych i trudno dostępnych dekoderów sprzętowych. speexenc.exe , który znajduje się w archiwum
z przykładową aplikacją przygotowaną przez
Jeszcze kilka lat temu zastosowanie we mach mikroprocesorowych, gdzie głównym STMicroelectronics (AN2812). Program ten moż-
własnym projekcie mikroprocesora 32-bito- kryterium doboru oprogramowania jest niskie na również pobrać ze strony www.speex.org/
wego o wydajności kilkudziesięciu MIPS było zużycie pamięci programu oraz mocy oblicze- downloads. Program speexenc.exe jest aplikacją
zadaniem będącym poza zasięgiem prze- niowej procesora. pracującą w wierszu poleceń. W celu otrzyma-
ciętnego amatora. Dziś w cenie zbliżonej do nia pliku zakodowanego kodekiem Speex, który
najwyższych modeli wiodącej prym rodziny Przykład zastosowania będzie można odtworzyć za pomocą aplikacji
mikrokontrolerów 8-bitowych można nabyć W artykule zostanie przedstawiona aplikacja demonstracyjnej należy wywołać program spe-
bardzo szeroką gamę wydajnych 32-bitowych wykorzystująca kodek Speex do odtwarzania exenc.exe z następującymi parametrami :
mikrokontrolerów zbudowanych w oparciu komunikatów głosowych przechowywanych speexenc.exe -n --quality 4 input_
o rdzeń ARM, posiadających szeroką gamę w pamięci Flash mikrokontrolera STM32. Apli- file output_file
układów peryferyjnych. Niekwestionowanym kacja została przygotowana dla kompilatora Parametr input_file określa ścieżkę dostępu
hitem ostatnich miesięcy są mikrokontrolery Raisonance, ale w prosty sposób może zostać do pliku z nagranym komunikatem dzwięko-
STM32 wykorzystujące nowoczesny rdzeń Cor- dostosowana do innych kompilatorów dla mi- wym, natomiast parametr output_file określa
tex-M3. Producent tych mikrokontrolerów, fir- krokontrolerów STM32. Omawiany w artykule ścieżkę dostępu do pliku wyjściowego.
ma STMicroelectronics, przygotował aplikację program został przygotowany dla zestawu Aby możliwe było wykorzystanie tak otrzy-
(AN2812) demonstrującą możliwości kodeka ZL27ARM (www.kamami.pl) z mikrokontro- manych danych w kodzie zródłowym, należy
Speex w zakresie nagrywania i odtwarzania lerem STM32F103VBT6 posiadającym 128 kB dokonać konwersji pliku zawierającego dane
komunikatów głosowych. pamięci Flash oraz 20 kB pamięci RAM, lecz binarne do pliku tekstowego zawierającego ta-
może zostać uruchomiony praktycznie na do- blicę danych zawierającą liczbową reprezentację
Czym jest kodek Speex? wolnej platformie sprzętowej z mikrokontrole- każdego bajtu pliku binarnego. Do tego celu
Speex jest darmowym kodekiem o otwar- rem STM32. Zestaw powinien być wyposażony można wykorzystać przykładowo program Hex
tym kodzie zródłowym, zaprojektowanym w wyświetlacz LCD 2x16 znaków. Do zestawu Workshop i za pomocą opcji Export wygene-
specjalnie dla przetwarzania mowy. Strona należy dołączyć prosty filtr RC, pokazany na rować plik zródłowy języka C z tablicą zawie-
WWW projektu jest dostępna pod adresem schemacie. Wyjście filtra należy podłączyć do rającą dane z otwartego pliku. W omawianej
www.speex.org. Kodek Speex został zapro- wzmacniacza mocy sterującego głośnikiem. aplikacji, dane reprezentujące nagrane komuni-
jektowany z myślą o wykorzystaniu go w te- Możliwie jest podłączenie wyjścia PWM bez- katy znajdują się w pliku voice.h. Oprócz tego,
lefonii internetowej VoIP, jednak ze względu pośrednio z głośniczkiem znajdującym się na w pliku voice.h została zdefiniowana struktura
na stosunkowo niskie wymagania sprzętowe płytce zestawu ZL27ARM, jednakże głośność Sound_t. Definicja struktury jest przedstawiona
idealnie nadaje się do wykorzystania w syste- dzwięku będzie bardzo niska. następująca:
80 ELEKTRONIKA PRAKTYCZNA 12/2008
Zastosowanie kodeka Speex
typedef struct pozycjach tablicy, co zostanie wy-
{ korzystane w programie demon-
u8 * Pointer; stracyjnym.
u16 Length;
}Sounds_t; Dekodowanie dzwięku
Struktura ta składa się z dwóch pól: pierwsze z pamięci Flash
pole struktury jest wskaznikiem do typu u8 i bę- Ogólny algorytm odtwarza-
dzie przechowywać adres początku tablicy z za- nia komunikatów dzwiękowych
kodowanymi danymi, natomiast drugie pole przedstawiono na rys. 1. Apli-
jest typu u16 i będzie przechowywać długość kacja wykorzystuje dwa bufory
komunikatu w ramkach. Długość komunikatu do dekodowania i odtwarzania
w ramkach można obliczyć poprzez podziele- nagrania. Dekodowanie dzwię-
nie rozmiaru pliku *.spx (czyli rozmiaru tablicy) ku zapisanego w pamięci Flash
przez liczbę bajtów w ramce, czyli przez liczbę mikrokontrolera dokonywane
20. Oprócz struktury w pliku voice.h została jest w ramach funkcji PlaySo-
zdefiniowana również tablica typu Sound_t und. Funkcja ta przyjmuje dwa
przechowująca dane wszystkich zapisanych parametry: wskaznik do obsza-
w pliku komunikatów dzwiękowych. Kod inicju- ru pamięci, w którym znajdu-
jący tablicę przedstawiono niżej: je się zakodowany kodekiem
Sounds_t Sounds[3]={ Speex dzwięk oraz jego długość
{rawData1, 311}, w ramkach. Każda ramka zako-
{rawData2, 96}, dowanego nagrania składa się
{rawData3, 71}, z dwudziestu bajtów, natomiast
}; ramka zdekodowanego nagra-
W celu uzyskania danych określających kon- nia zajmuje 160 bajtów. Wynika
kretny komunikat wystarczy odwołać się do wy- z tego, iż współczynnik kompre- Rys. 1.
branej pozycji tablicy, np.: Sounds[0].Poin- sji nagrania wynosi 1:8.
ter zwróci adres komunikatu, a Sounds[0]. Kod funkcji PlaySound przedstawiono na pełnienia obydwu buforów wyjściowych zdeko-
Length zwróci jego długość. Dzięki temu moż- list. 1. dowanymi danymi. Dekodowanie każdej ramki
na w prosty sposób odtworzyć wszystkie komu- Działanie funkcji rozpoczyna się od zdekodo- nagrania przebiega w następujący sposób :
nikaty za pomocą pętli iterującej po wszystkich wania dwóch pierwszych ramek nagrania i wy- " tablica input_bytes musi zostać wypełnio-
na odczytaną z pamięci Flash ramką sygnału,
List. 1.
" następnie zawartość tablicy input_bytes
void PlaySound(const u8 *Sound, u16 Lenght)
jest kopiowana, za pomocą funkcji speex_
{
vu16 i;
bits_read_from, do struktury bits, stano-
for(i=0;i
input_bytes[i] = *(Sound + sample_index++);
wiącej wejście dekodera Speex,
" ramka znajdująca się w strukturze bits jest
speex_bits_read_from(&bits, input_bytes, ENCODED_FRAME_SIZE);
speex_decode_int(dec_state, &bits, (spx_int16_t*)OUT_Buffer[0]);
dekodowana do bufora wyjściowego OutBuf-
for(i=0;i input_bytes[i] = *(Sound + sample_index++);
Po zdekodowaniu dwóch ramek i wypełnie-
speex_bits_read_from(&bits, input_bytes, ENCODED_FRAME_SIZE);
niu obydwu buforów wyjściowych do zmiennej
speex_decode_int(dec_state, &bits, (spx_int16_t*)OUT_Buffer[1]);
Play zapisywana jest liczba 1. Zmienna Play
NB_Frames++;
sygnalizuje fakt wypełnienia buforów zdekodo-
Play = 1; wanymi danymi i zezwala na ich odtworzenie
przez procedurę obsługi przerwania od timera
while(NB_Frames < Lenght)
{
TIM2. Pozostała część nagrania jest dekodowa-
if(Start_Decoding == 1)
{ na w pętli, aż do osiągnięcia końca nagrania,
for(i=0;iczyli do momentu, aż zmienna NB_Frames
input_bytes[i] = *(Sound + sample_index++);
zrówna się ze zmienną Length. Stan zmiennej
speex_bits_read_from(&bits, input_bytes, ENCODED_FRAME_SIZE);
Start_Decoding określa, który z buforów
speex_decode_int(dec_state, &bits, (spx_int16_t*)OUT_Buffer[0]);
został opróżniony i powinien zostać wypeł-
Start_Decoding = 0;
NB_Frames++; niony nowymi danymi. Stan tej zmiennej jest
}
modyfikowany przez procedurę odtwarzania
if(Start_Decoding == 2)
dzwięku z buforów wyjściowych.
{
for(i=0;i input_bytes[i] = *(Sound + sample_index++);
Odtwarzanie dzwięku z buforów
speex_bits_read_from(&bits, input_bytes, ENCODED_FRAME_SIZE);
wyjściowych
speex_decode_int(dec_state, &bits, (spx_int16_t*)OUT_Buffer[1]);
Jako przetwornik cyfrowo-analogowy wyko-
Start_Decoding = 0;
rzystywany jest timer 1 pracujący w trybie PWM.
NB_Frames++;
} Współczynnik wypełnienia sygnału generowa-
}
nego przez timer 1 jest aktualizowany z często-
Play = 0;
sample_index = 0;
tliwością 8 kHz w ramach obsługi przerwania
NB_Frames = 0;
od timera 2. Kod handlera przerwania od timera
outBuffer = OUT_Buffer[0];
}
TIM2 przedstawiono na list. 2.
ELEKTRONIKA PRAKTYCZNA 12/2008 81
PODZESPOAY
Na początku procedury następuje załadowa-
List. 2.
void TIM2_IRQHandler(void)
nie do rejestru ARR wartości, od której licznik
{
rozpoczyna odliczanie oraz wyzerowanie flagi
TIM2->ARR = TIM2ARRValue;
TIM2->SR = TIM_INT_Update;
przerwania. Następnie sprawdzany jest stan
zmiennej Play, która określa czy dane z bufora if(Play)
{
wyjściowego mają być przekazane na wyjście.
TIM1->CCR1 = ((*outBuffer>>6)) + 0x200 ;
Jeżeli wartość zmiennej jest różna od zera, na-
if(outBuffer == &OUT_Buffer[1][159])
stąpi załadowanie do rejestru CCR1 licznika
{
outBuffer = OUT_Buffer[0];
TIM1 wartości określającej aktualny współczyn-
Start_Decoding = 2;
nik wypełnienia generowanego sygnału PWM }
else if(outBuffer == &OUT_Buffer[0][159])
oraz sprawdzenie, czy osiągnięto koniec które-
{
outBuffer = OUT_Buffer[1];
gokolwiek z buforów wyjściowych. W przypad-
Start_Decoding = 1;
ku, gdy cały aktualnie odtwarzany bufor został }
else
odczytany, następuje przełączenie odtwarzania
{
outBuffer++;
na drugi z buforów. Jeśli natomiast bufor nie
}
został w pełni odtworzony, inkrementowany
}
else
jest wskaznik outBuffer, który wskazuje na ak-
{
tualną pozycję w buforze wyjściowym. W przy- TIM1->CCR1 = 0x200 ;
}
padku, gdy zmienna Play jest wyzerowana, do
}
rejestru CCR1 timera TIM1 zapisywana jest stała
wartość 0x200, odpowiadająca w przybliżeniu
List. 3.
wartości masy analogowej na wyjściu sygna-
void Vocoder_Init(void)
łu, czyli połowie napięcia maksymalnego gene- {
/* Peripherals InitStructure define ----------------------------------------
rowanego przez układ PWM timera TIM1, gdyż
-*/
GPIO_InitTypeDef GPIO_InitStructure;
w przypadku omawianej aplikacji timery TIM1
ADC_InitTypeDef ADC_InitStructure;
i TIM2 pracują nieprzerwanie przez cały czas
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
działania aplikacji. Oryginalna aplikacja opraco-
wana przez firmę STMicroeletronics działa nieco /* TIM1 configuration ------------------------------------------------------
-*/
inaczej, gdyż timery są aktywowane tylko na
TIM_DeInit(TIM1);
TIM_OCStructInit(&TIM_OCInitStructure);
czas odtwarzania komunikatu, co objawia się
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
niestety nieprzyjemnymi trzaskami w momen- GPIO_StructInit(&GPIO_InitStructure);
cie włączania i wyłączania generatora PWM.
/* Configure PA.08 as alternate function (TIM1_OC1) */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
W przedstawianej w artykule aplikacji ten
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
problem nie występuje, kosztem niewielkiego
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
obciążenia procesora spowodowanego ciągłym
GPIO_ResetBits(GPIOA, GPIO_Pin_8);
występowaniem przerwania od timera TIM2.
/* TIM1 used for PWM genration */
TIM_TimeBaseStructure.TIM_Prescaler = 0x00; /* TIM1CLK = 72 MHz */
TIM_TimeBaseStructure.TIM_Period = 0x3FF; /* 10 bits resolution */
Inicjalizacja układów peryferyjnych
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
Ponieważ aplikacja wykorzystuje podczas pra-
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
cy dwa układy timerów konieczne jest ich wła-
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
ściwe zainicjalizowanie. Inicjalizacja wykorzy-
/* TIM1 s Channel1 in PWM1 mode */
stywanych układów peryferyjnych realizowana
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
jest za pomocą funkcji Vocoder_Init, której kod
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
przedstawiono na list. 3.
TIM_OCInitStructure.TIM_Pulse = 0x200;/* Duty cycle: 50%*/
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
Timer TIM1 jest skonfigurowany do pracy
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
w trybie PWM. Wyjście PA8 jest skonfigurowane
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset;
jako wyjście funkcji alternatywnej, czyli w tym
TIM_OC1Init(TIM1, &TIM_OCInitStructure);
przypadku wyjście PWM timera TIM1. Timer
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
TIM2 został skonfigurowany do generowania TIM_ARRPreloadConfig(TIM1, ENABLE);
przerwania z częstotliwością 8 kHz.
/* TIM2 configuration ------------------------------------------------------
-*/
TIM_DeInit(TIM2);
Konfiguracja układu przerwań
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
Ponieważ przesyłanie danych z buforów
/* TIM2 used for timing, the timing period depends on the sample rate */
TIM_TimeBaseStructure.TIM_Prescaler = 0x00; /* TIM2CLK = 72 MHz */
wyjściowych na wyjście analogowe odbywa się
TIM_TimeBaseStructure.TIM_Period = TIM2ARRValue;
w ramach obsługi przerwania od timera TIM2,
TIM_TimeBaseStructure.TIM_ClockDivision = 0x0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
konieczne jest odpowiednie skonfigurowanie
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
układu przerwań. Kod funkcji odpowiedzialnej
/* Output Compare Inactive Mode configuration: Channel1 */
za konfigurację układu przerwań przedstawiono
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Inactive;
TIM_OCInitStructure.TIM_Pulse = 0x0;
na list. 4.
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Disable);
}
Główny kod programu
Zasadnicza część aplikacji odtwarzającej ko-
munikaty z pamięci Flash mikrokontrolera jest praktyczne przedstawienie wykorzystania kodeka nych komunikatów dzwiękowych. Kod głównej
stosunkowo prosta, gdyż jej celem jest jedynie Speex do odtwarzania uprzednio przygotowa- funkcji programu przedstawiony jest na list. 5.
82 ELEKTRONIKA PRAKTYCZNA 12/2008
Zastosowanie kodeka Speex
Działanie funkcji rozpoczyna się od inicjali-
List. 4.
void InterruptConfig(void)
zacji wykorzystywanych układów peryferyjnych
{
NVIC_InitTypeDef NVIC_InitStructure;
oraz kodeka. Następnie na wyświetlaczu LCD
NVIC_DeInit();
wyświetlany jest ekran powitalny i po krótkiej
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x00);
chwili oczekiwania następuje odtworzenie ko-
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQChannel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; munikatów w pętli nieskończonej. Wykorzysta-
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
na do tego celu zostaje tablica Sounds prze-
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
chowująca adresy oraz długości poszczególnych
}
komunikatów. Oprócz odtworzenia komunikatu
na wyświetlaczu LCD jest wyświetlany numer ak-
List. 5. tualnie odtwarzanego komunikatu.
int main(void)
{
vu8 i;
Podsumowanie
vu32 j;
Przedstawiona w artykule aplikacja demon-
Demo_Init(); //
LCD_Initialize(); //
stracyjna zajmuje, łącznie z nagranymi komuni-
Speex_Init(); // Inicjalizacja
Vocoder_Init(); // katami, około 30 kB pamięci Flash mikrokontro-
Vocoder_Start(); //
lera. Do dyspozycji programisty pozostaje jeszcze
LCD_GoTo(0,0); //
spora ilość miejsca, w zależności od zastosowa-
LCD_WriteText( Speex Demo - EP ); //
nego typu mikrokontrolera, na zasadniczą apli-
LCD_GoTo(0,1); // Ekran powitalny
LCD_WriteText( www.ep.com.pl ); //
kację, wykorzystującą kodek Speex do odtwarza-
for(j = 0; j < 0x3FFFFF; j++); // Opóznienie
nia dzwięku. Trudno bowiem sobie wyobrazić, iż
while(1) // pętla nieskończona
odtwarzanie komunikatów dzwiękowych może
{
for(i = 0; i < 9; i++)
być jedyną funkcją, do której zaprzęgnięty zo-
{
LCD_GoTo(0,1); // stanie mikrokontroler z rodziny STM32, stano-
LCD_WriteText( Komunikat nr ); // Wyświetlenie napisu
wi to rzecz jasna tylko dodatek do szerokiego
LCD_WriteData(i+ 0 ); //
grona aplikacji, w których zastosowanie mogą
PlaySound(Sounds[i].Pointer, Sounds[i].Length); // Odtworzenie dzwięku
znalezć rewelacyjne mikrokontrolery STM32.
for(j = 0; j < 0xFFFFF; j++); // Opóznienie
Radosław Kwiecień, EP
}
}
radoslaw.kwiecien@ep.com.pl
}
R E K L A M A
forum.ep.com.pl
ELEKTRONIKA PRAKTYCZNA 12/2008 83
Wyszukiwarka
Podobne podstrony:
zastosowanie metod fotometrii absorpcyjnej
STM32 Butterfly RS232
Odpromienniki i ich praktyczne zastosowanie
rosliny zastosowania pojemnikienclematis main
Konwencja o zastosowaniu do wojny morskiej założeń konwencji genewskiej
Mikrokontrolery PIC w praktycznych zastosowaniach mipicp
Przekładnie planetarne w zastosowaniach przemysłowych
Metoda 5S Zastosowanie wdrazanie i narzedzia wspomagajace
Algorytm genetyczny – przykład zastosowania
Lacznosc satelitarna w zastosowaniach wojskowych
Zastosowanie i skuteczność terapii poznawczo behawioralnej w leczeniu schizofrenii
6 ZASTOSOWANIA
Zastosowanie OCT do stratygrafii
więcej podobnych podstron