Semestrální práce z ICZ

Generátor signálů na DSP

vypracoval: Martin Řehák




Zadání:

1) Navrhněte a implementujte generátor, který bude generovat sinusový, trojúhelníkový a obdélníkový signál proměnné frekvence a amplitudy. Viz podklady v příloze.

2) Napište program v Matlabu a ověřte princip generování signálů s různým kmitočtem a amplitudou.

3) Napište program v assembleru procesoru 'C54x nebo C a odlaďte v simulátoru, případně na vývojové desce v reálném čase.

4) Vypracujte písemnou zprávu, ve které uvedete princip, postup řešení a dosažené výsledky.



Teoretický rozbor:

Symetrický obdélníkový signál (square wave) je tvořen dvěma konstantními úrovněmi kladné a záporné polarity, jenž se střídají každou půlperiodu. Ideální obdélníkový signál má nekonečné spektrum a tak ho na žádné reálné soustavě s omezenou šířkou pásma nelze přesně vygenerovat, reálný signál se obdélníku více či méně přibližuje. K jeho vygenerování stačí vypočítat počet vzorků na jednu půlperiodu T/2:

T/2 = fs/(2f)

kde fs je vzorkovací frekvence a f je požadovaná frekvence. Např. pro fs = 48kHz a f = 500Hz vychází T = 96 a tedy T/2 = 48 vzorků na půlperiodu.

Symetrický trojúhelníkový signál (triangle wave) se skládá z lineárního náběhu a doběhu, jenž se střídají každou půlperiodu. Vzhledem k ostrým zlomům na vrcholech je spektrum taktéž nekonečné, i když vyšší harmonické nesou mnohem méně energie než tomu bylo u obdélníku. K jeho vygenerování stačí vypočítat počet vzorků na jednu půlperiodu T/2 (stejně jako u obdélníku) a přírůstek amplitudy na jeden vzorek:

du/dt = ±MAX/(T/2)

kde MAX je maximální zobrazitelné neznaménkové číslo pro daný systém (16bit -> 65536).

Asymetrický pilovitý signál (sawtooth wave) je variantou trojúhelníkového signálu s lineárním náběhem (2x pomalejším) a skokovým doběhem.

Jednotkový (diracův) impuls je tvořen jedním jednotkovým vzorkem následovaný nulami.

Bílý šum (white noise) je tvořen náhodnými vzorky s rovnoměrným rozložením, které jsou vzájemně nekorelované. V ideálním případě by v něm měli být obsažené všechny frekvence a spektrum by se mělo blížit konstantě.

Sinusový signál (sine wave) je daný předpisem A*sin(2*Pi*f). Lze ho generovat několika způsoby. Jednou z možností je použití předpočítané tabulky sinu - stačí jedna čtvrtperioda, přesnost závisí na velikosti tabulky, tedy náročné na paměť, ale nenáročné na výkon. Další možnost je využití Taylorova rozvoje pro funkci sinus:

sin(x)=x-x^3/3!+x^5/5!-x^7/7!...

Tímto způsobem bývá sinus implementován v matematické knihovně pro C.

Zajímavou možností je využití speciálního IIR filtru na mezi stability:
IIR filter schema

s přenosovou funkcí:
H(z)=z*sin(O)/[z^2-2z*cos(O)+1]

kde :
O=2*Pi*f/fs


a s odpovídající diferenční rovnicí:

y[n]=x[n-1]*sin(O)+2cos(O)*y[n-1]-y[n-2]

kde :
x[n]=d[n]

Vstupní signál je interně generovaná posloupnost jednotkového impulsu a nul. Po každém vložení hodnoty do vstupu filtru je vygenerována jedna hodnota amplitudy sinu. Výstupní sinusový signál začíná nulou a pokračuje bez přechodového děje.



Implementace v Matlabu:

První verze programu využívá funkci filter. Nejprve se spočítají potřebné koeficienty, pak se vygeneruje vektor vstupního signálu (jednotkový impuls) a aplikuje se funkce filter. Na výstupu je požadovaná sinusovka.

singen1.m

clc;
clear;

fs=48000;                       %vzorkovaci frekvence
f=500;                          %pozadovana frekvence
Q=2*pi*f/fs;                    %theta

B(1)=0;                         %B koeficienty
B(2)=sin(Q);
A(1)=1;                         %A koeficienty
A(2)=-2*cos(Q);
A(3)=1;

N=1000;                         %delka posloupnosti
x=zeros(1,N);
x(1)=1;                         %dirac

y=filter(B,A,x);                %generovani vystupni posloupnosti
subplot(2,1,1);
axis([0 1000 -1.2 1.2]);
hold;
plot(y);

%B(1)=ceil(B(1)*32768)/32768;   %kvnatizace koeficientu na 16b.
%B(2)=ceil(B(2)*32768)/32768;
%A(1)=ceil(A(1)*32768)/32768;
%A(2)=ceil(A(2)*32768)/32768;
%A(3)=ceil(A(3)*32768)/32768;

B(1)=ceil(B(1)*256)/256;        %kvnatizace koeficientu na 8b.
B(2)=ceil(B(2)*256)/256;
A(1)=ceil(A(1)*256)/256;
A(2)=ceil(A(2)*256)/256;
A(3)=ceil(A(3)*256)/256;

y2=filter(B,A,x);               %generovani vystupni posloupnosti
subplot(2,1,2);
axis([0 1000 -1.2 1.2]);
hold on;
plot(y2);

Při testování odolnosti vůči kvantizaci koeficientů se ukázalo, že i nepatrná změna koeficientu (4-5 desetinné místo) vyvolá značné změny frekvence a amplitudy - zřejmě důsledek mnohačetného násobení ve funkci filter.

změna výstupního signálu při kvantování na 16 bitů:
sine-quantized 16b
(nepatrně vyšší frekvence a menší amplituda)

zobrazení pólů na jednotkové kružnici:
poles of sine-quantized 16b

zvětšený detail-pól nesedí na kružnici:
poles of sine-quantized 16b-zoomed

změna výstupního signálu při kvantování na 8 bitů:
sine-quantized 8b


Druhá verze programu pak vychází přímo z diferenčních rovnic, vliv kvantování se zde projevuje výrazně méně.

singen3.m

clc;
clear;

fs=48000;                       %vzorkovaci frekvence
f=500;                          %pozadovana frekvence
Q=2*pi*f/fs;
B=2*cos(Q);
A=sin(Q);

A=floor(A*32000)/32000;         %ukazka vlivu kvantizace koeficientu
B=floor(B*32000)/32000;

N=1000;

y4=zeros(1,N);                  %vystupni pole 1x1000 
y1=A;
y2=0;
y3=0;

for n=1:N
  y2=y1;
  y1=y3;
  y3=y1*B-y2;
  y4(n)=-y3;
end
    
axis([0 1000 -1.2 1.2]);
hold;
plot(y4);

výstupní signál při kvantování na 16 bitů:
sine-quantized 16b



Implementace na hardware:

Výše zmíněné algoritmy jsem implementoval na vývojové desce s DSP procesorem TMS320VC5416 firmy Texas Instruments. Deska je vybavená USB řadičem, 64kW RAM, 256kW FlashROM, 16bit stereo kodekem PCM3002 s vzorkovací frekvencí do 48kHz (s klasickými analog. I/O mic-in, line-in, line-out, spkr-out), 3-mi expanzními porty, 4-mi spínači DIP a 4-mi LED pro libovolné použití.

TMS320VC5416 DSK
TMS320VC5416 DSK photo

Spolu s deskou je dodáváno firemní vývojové prostředí Code Composer Studio (CCS), které umožňuje psát programy v C a Assembleru. IDE CCS v sobě zahrnuje editor zdrojového kódu, manažer projektu, kompilátor, linker, debugger/monitor, systém nápovědy a další. Na DSP procesoru běží kernel, který komunikuje za běhu s CCS, jenž umožňuje nahrávat a spouštět uživatelské programy, ovlivňovat/ monitorovat běh programu, obsah pamětí, přístupy k I/O portům... Kernel umí jednoduchý multitasking, kdy lze 'součastně' spustit až 16 uživatelských procesů s různě nastavenými prioritami.

Code Composer Studio
CCS-screenshot

Program jsem napsal v jazyku C (v podstatě ANSI C se specifickými rozšířeními pro ovládání I/O). Vycházel jsem přitom z tutoriálu tone.c (generátor sinusu pomocí tabulky), z původního kódu ale nakonec nic nezbylo. Jádrem programu je funkce my_generator(), která běží jako jediný uživatelský proces s nastavenou prioritou 8. Na začátku se provede inicializace kodeku, LED a DIP. Pak program vstoupí do hlavní smyčky do - while ve které se aktualizuje frekvence a amplituda, dále se vyhodnotí stav DIP spínačů a podle toho se rozhodne který signál se má generovat. Je-li na DIP platná kombinace, switch - case vybere odpovídající blok, který pak vygeneruje jednu periodu daného signálu (jinak se na výstup posílá 0) Každý case blok pro generování daného průběhu obsahuje jednak výpočet potřebných parametrů jako např. výše zmiňované T/2 nebo du/dt a vnitřní smyčku, která generuje vlastní vzorky jedné periody a posílá je na výstup kodeku. Na kodek je třeba zapisovat vzorek 2x, neboť jednou se zapisuje do pravého a podruhé do levého kanálu. Jak jsem zjistil, výstup line-out je invertovaný, zatímco speaker-out je neinvertovaný. Pro větší flexibilitu jsem umožnil provést inverzi nastavením globální proměnné. V případě generování sinusového průběhu jsem pro výpočet koeficientů filtru využil funkcí cos() a sin() z math.h. Abych procesor zbytečně nepřetěžoval jejich počítáním, je výpočet proveden pouze při prvním spuštění nebo při změně frekvence. Bílý šum je generovaný pomocí funkce rand(), která vrací číslo 0-(MAX_INT-1). Změna požadované amplitudy se provádí nastavením parametru kodeku (všechny bloky generují signál s maximální dynamikou MIN_INT-MAX_INT).



Návod k použití:

Otevření, a překlad programu v CCS a načtení a spouštění na desce:
1) K vývojové desce připojíme napájení a USB kabelem propojíme s PC.
2) Spustíme Code Composer Studio.
3) V levém okně klepneme pravým tlačítkem na záložku Projects a zvolíme Open Project...
4) Vyhledáme soubor našeho projektu s příponou .pjt.
5) Nyní můžeme procházet jednotlivé části projektu pomocí rozbalovacích tlačítek [+].
6) V záložce Source se nachází zdroják v C, jeho poklepáním se otevře v editoru.
7) V menu vybereme Project/Rebuild All, výpis o překladu se zobrazí v dolním okně.
8) V menu vybereme File/Load Program... a vybereme přeložený .out soubor.
9) V menu vybereme Debug/Run čímž se program na desce spustí.
10) Pro zastavení programu lze dát v menu Debug/Halt.

Vlastní ovládání běžícího programu:
Běh programu lze řídit jednak stavem spínačů DIP a jednak pomocí globálních proměnných z CSS. Průchod hlavní smyčkou je signalizován rozsvícením LED1. Pomocí DIP spínačů se navolí generovaný průběh signálů takto:
žádný DIP sepnut - obdélníkový průběh
sepnut DIP1 - posloupnost diracových impulsů
sepnut DIP2 - pilovitý průběh
sepnut DIP3 - trojúhelníkový průběh
sepnut DIP4 - sinusový průběh
sepnut DIP1 a DIP5 - bílý šum

Globální C-proměnné lze za běhu programu modifikovat v menu Edit/Variable..., zadáním jejího identifikátoru a hodnoty. Po zadání identifikátoru a stisku klávesy [TAB] se zobrazí aktuální hodnota proměnné. Zde je přehled konfiguračních globálních proměnných a jejich platných hodnot:
f - požadovaná frekvence generátoru [0-fs/2Hz]
g - zisk (amplituda výstupního signálu) [0-255]
m - typ generovaného průběhu - jen pro čtení!
r - run flag, hodnotou 0 se ukončí běh hlavní smyčky
invert - inverze výstupního signálu [-1,+1]



Výpis programu:

tone.c

/****************************************************************************/
/*                        Function Generator 0.2                            */
/****************************************************************************/
/* Created: 1. 1. 2005 [TI-C54X]                                            */
/* Last modified: 7. 1. 2005 [TI-C54X]                                      */
/* Copyright (C) 2005 by Martin Rehak                                       */
/* Contact: rehakm2@feld.cvut.cz                                            */
/****************************************************************************/

// hlavicky viz TIROOT\C5400\DSK5416\INCLUDE
#include "tonecfg.h"                   // automaticky generovano z konfigurace
#include "dsk5416.h"                   // specificke I/O registry CPLD
#include "dsk5416_led.h"               // fce na ovladani LED
#include "dsk5416_dip.h"               // fce na cteni DIP prepinace
#include "dsk5416_pcm3002.h"           // fce na ovladani kodeku
#include <stdlib.h>                    // fce rand()
#include <math.h>                      // mat. fce (sinus,..)

// ovladani generatoru se deje pres globalni promene - MENU/EDIT/Variable...
long fs=48000;                         // vzorkovaci frekvence kodeku (max. 48000Hz, nemenit)
float f=500.0;                         // frekvence generatoru 0-fs/2 Hz
int g=255;                             // zisk (amplituda vystupniho signalu) 0-255
int m=0;                               // typ generovaneho prubehu
int r=1;                               // flag pro ukonceni programu
int invert=-1;                         // invertovat vystup +1/-1 (line-out je invert., speaker out neni invert.)

#define PI ((double)3.1415927)

DSK5416_PCM3002_Config setup = {       // defaultni nastaveni kodeku
  0x01FF,                              // Set-Up Reg 0 - Left channel DAC attenuation (1ff=0dB)
  0x01FF,                              // Set-Up Reg 1 - Right channel DAC attenuation
  0x0,                                 // Set-Up Reg 2 - Various ctl e.g. power-down modes
  0x0                                  // Set-Up Reg 3 - Codec data format control
};

void my_generator(void)                // uzivatelska uloha spustena jadrem
{
  DSK5416_PCM3002_CodecHandle hcodec;  // handler kodeku
  long t0,i,dudt;
  float f_old=0;
  double a,b,w,y1,y2,y3;

  hcodec=DSK5416_PCM3002_openCodec(0, &setup); // spust kodek
  DSK5416_PCM3002_setFreq(hcodec,fs);  // nastav vzorkovaci frekvenci kodeku
  DSK5416_LED_on(0);                   // signalizace behu generatoru

  do
    {
    t0=(int)(fs/f+0.5);                // aktualizace poctu vzorku na periodu
    DSK5416_PCM3002_outGain(hcodec,g); // aktualizace nastaveni vystupni amplitudy
    m=!DSK5416_DIP_get(0)+2*!DSK5416_DIP_get(1)+4*!DSK5416_DIP_get(2)+8*!DSK5416_DIP_get(3);
    switch (m)                         // aktualizuj mode podle stavu DIP
      {
      case 0 :                         // mod 0 - obdelnik (zadny DIP)
        for (i=0; i<(t0/2); i++)       // negativni pulperioda
          {
          while (!DSK5416_PCM3002_write16(hcodec,-32767*invert)); // pravy kanal
          while (!DSK5416_PCM3002_write16(hcodec,-32767*invert)); // levy kanal
          }
        for (i=0; i<(t0/2); i++)       // pozitivni pulperioda
          {
          while (!DSK5416_PCM3002_write16(hcodec,32767*invert)); // pravy kanal
          while (!DSK5416_PCM3002_write16(hcodec,32767*invert)); // levy kanal
          }
        break;
      case 1 :                         // mod 1 - dirac (DIP 1)
        while (!DSK5416_PCM3002_write16(hcodec,32767*invert)); // pravy kanal
        while (!DSK5416_PCM3002_write16(hcodec,32767*invert)); // levy kanal
        for (i=0; i<(t0-1); i++)       // negativni pulperioda
          {
          while (!DSK5416_PCM3002_write16(hcodec,0)); // pravy
          while (!DSK5416_PCM3002_write16(hcodec,0)); // levy kanal
          }
        break;
      case 2 :                         // mod 2 - pila (DIP 2)
        dudt=65535/t0;
        for (i=0; i<(t0-1); i++)       // linearni nabeh
          {
          while (!DSK5416_PCM3002_write16(hcodec,(i*dudt-32767)*invert)); // pravy kanal
          while (!DSK5416_PCM3002_write16(hcodec,(i*dudt-32767)*invert)); // levy kanal
          }                                                        // skokovy sebeh
        while (!DSK5416_PCM3002_write16(hcodec,-32767*invert)); // pravy kanal
        while (!DSK5416_PCM3002_write16(hcodec,-32767*invert)); // skokovy sebeh
        break;
      case 4 :                         // mod 4 - trojuhelnik (DIP 3)
        dudt=65535/(t0/2);
        for (i=0; i<(t0/2); i++)       // linearni nabeh
          {
          while (!DSK5416_PCM3002_write16(hcodec,(i*dudt-32767)*invert)); // pravy kanal
          while (!DSK5416_PCM3002_write16(hcodec,(i*dudt-32767)*invert)); // levy kanal
          }
        for (i=0; i<(t0/2); i++)       // linearni sebeh
          {
          while (!DSK5416_PCM3002_write16(hcodec,(32767-i*dudt)*invert)); // pravy kanal
          while (!DSK5416_PCM3002_write16(hcodec,(32767-i*dudt)*invert)); // levy kanal
          }
        break;
      case 8 :                         // mod 8 - sinus (DIP 4)
        if (f!=f_old)                  // pokud se frekvence zmenila
          {                            // prepocitej koeficienty IIR filtru
              w=2*PI*f/fs;             // omega
              a=sin(w);                // koeficient A IIR sin. filtru (sin se pocita jen obcas-mala zatez)
              b=2*cos(w);              // koeficient B IIR sin. filtru
              y1=a;
              y2=0;
              y3=0;
              }
        f_old=f;                       // aktualizuj f_old
        for (i=0; i
          while (!DSK5416_PCM3002_write16(hcodec,(rand()-16384)*2)); // pravy kanal
          while (!DSK5416_PCM3002_write16(hcodec,(rand()-16384)*2)); // levy kanal
          }
        break;
      default :                        // pro vsechny ostatni kombinace vysli 0 signal
        while (!DSK5416_PCM3002_write16(hcodec,0)); // pravy kanal
        while (!DSK5416_PCM3002_write16(hcodec,0)); // levy kanal
        break;
      }
    } while (r!=0);

  DSK5416_LED_off(0);
  DSK5416_PCM3002_closeCodec(hcodec);  // uzavri kodek
}

void main()
{
  DSK5416_init();                      // inicializace knihovny pro ovladani I/O desky
  DSK5416_DIP_init();                  // inicializace DIP prepinacu
  DSK5416_LED_init();                  // inicializace LEDek
}



Naměřené hodnoty a závěr:

Generovaný signál jsem doma monitoroval na osciloskopu Grundig MO 52 a čítačem/multimetem Metex M-3860M. Pro získání průběhů do této dokumentace jsem připojil line-out výstup na line-in vstup zvukové karty a průběhy nasamploval (vzorkování 44100Hz, 16bit). Abych měl s čím porovnávat, potřeboval jsem ještě zachytit digitální signál před vstupem do kodeku. To jsem provedl v CCS pomocí sondy (probe), kdy jsem si nejprve prvních vygenerovaných 7000 vzorků uložil do pole a to jsem pak po skončení běhu programu přečetl z paměti desky do souboru. Zde je upravený zdrojový kód, který jsem pro zachycení používal tone.bak. Výstupní soubor jsem po úpravě převedl programem h2d.exe do dekadické-textové podoby se kterou lze pracovat v Matlabu. Digitální a analogový signál mají tedy různé vzorkovací frekvence a tak časová osa neodpovídá, nicméně čítačem jsem ověřil, že odchylka frekvence je minimální.

Obdélníkový signál 500Hz (digitální)
waveform

Obdélníkový signál 500Hz (analogový)
waveform
Zde vidíme jednak pokles temen impulsů vlivem nenulové dolní mezní frekvence a také zpomalení a zaoblení hran vlivem konečné horní mezní frekvence.

Trojúhelníkový signál 500Hz (digitální)
waveform

Trojúhelníkový signál 500Hz (analogový)
waveform
Zde vidíme, že zkreslení je vcelku minimální, protože trojúhelník nemá v harmonických tolik energie jako obdélník a tudíž se jejich úbytek projeví méně.

Pilovitý signál 500Hz (digitální)
waveform

Pilovitý signál 500Hz (analogový)
waveform
Vlivem konečné doby přeběhu se prodloužil strmý seběh a objevily se zákmity.

Diracův impuls s opakováním 500Hz (digitální)
waveform

Diracův impuls s opakováním 500Hz (analogový)
waveform
Pokud bychom mohli předpokládat, že systém D/A převodníku a výstupního zesilovače je lineární, získali bychom vlastně impulsovou odezvu rekonstrukčního filtru.

Bílý šum (digitální)
waveform

Bílý šum (analogový)
waveform
Zde toho vidět moc není, proto si ukážeme spektrum.

Spektrum bílého šumu (digitální)
spectrum

Spektrum bílého šumu (analogový)
spectrum
Vidíme že spektrum digitálního signálu je celkem rovné, zatímco u analogového signálu k oběma stranám ubývá. Dává nám vlastně představu o frekvenční charakteristice výstupních obvodů. Ještě před fs/2 je patrný prudký pokles. Spektrum analogového signálu je mnohem hladší protože jsem ho nechal počítat z celého průběhu asi 5s signálu. U digitálního signálu jsem neměl k dispozici víc jak 7000 vzorků.

Sinusový signál 500Hz (digitální)
waveform

Sinusový signál 500Hz (analogový)
waveform
Sinusový signál vypadá tak jak má, ještě se podíváme na spektrum.

Spektrum sinusového signálu 500Hz (digitální)
waveform

Spektrum sinusového signálu (analogový)
waveform
V obou případech je vidět hlavní peak na frekvenci 500Hz. Na analogovém signálu najdeme patrné vyšší harmonické na 1000Hz, 2000Hz, 3500Hz...

Na závěr lze říci, že s pomocí DSP se dá celkem jednoduše generovat téměř libovolný signál, jehož kvalita je určena hlavně D/A převodníkem a výstupními analogovými obvody. Protože mě možnosti DSP desky zaujaly, pokusil jsem se vygenerovat i složitější hudební signál, viz můj druhý program Tracker.