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