TTT4265 Elektronisk systemdesign og -analyse II Analyseøving 8 - løsningsforslag Innlevering tirsdag 3. November 8:00 Oppgave 1. Periodisitet, tidsskift, og tidsreversering (4p) Oppgave 2. Tidsskift (1p)
Oppgave 3. Systemegenskaper (6p)
Oppgave 4. Energi og effekt til periodisk signal (2p) Oppgave 5. Beskrivelse av FIR-filter (3p)
Oppgave 6. Kombinering av enkeltsystemer (3p) Oppgave 7. Kausalitet og BIBO-stabilitet (2p)
Oppgave 8. Implementasjon av FIR-filter på Arduino (16p) a) Med b=0.7, blir enhetspulsresponsen som følger: 1.2 1 0.8 h(n) 0.6 0.4 0.2 Alle verdier når n er utenfor intervallet [0,9] er lik null. b) 0-2 0 2 4 6 8 10 n For et FIR-filter blir filterkoeffisientene lik enhetspulsresponsen, altså b k = h(k) (se side 52 i "Signalbehandling og kommunikasjon" av Bojana Gajic). Uttrykket for systemresponsen blir da y n = + h k x(n k),-. der 9 er satt inn som øvre grense fordi det er den høyeste verdien der h(k) 0. Videre er inngangssignalet vårt i perioden som starter med n=0, lik 1, 0 n 19 x n = 0, 20 n 39 Vi kan nå bruke disse likningene til å plotte en periode av y(n). Når vi velger å plotte i perioden 0 n 39, vil vi også i noen tilfeller få med x(n) fra området -9 n 0 i summen vår. Vi forstår at disse verdiene må være null, siden x(n) er periodisk. Plottet blir: 3.5 3 2.5 2 y(n) 1.5 1 0.5 0 0 5 10 15 20 25 30 35 40 n
c) Forslag til kode: #include <Wire.h> #include <Adafruit_MCP4725.h> #include <TimerOne.h> //Pinout: //Filter input - A0. //DAC SDA - A4. //DAC SDL - A5. int val; //To hold the current unfiltered value, i.e. x(n) int valfilt; //To hold the current filtered value, i.e. y(n) volatile boolean sampletime; //Flag to denote that a new conversion should take place. const int ORDER = 9; //Order of FIR filter. const int DIVIDE = 3277; //The output is divided by this number to normalize it. byte bufpoint = 0; //Current position in the circular buffer. //The coefficients. const int COEFF[ORDER+1] = {4096,2867,2007,1404,983,688,481,337,236,165; //Circular buffer used by the filter routine to hold current and previous x(n) values. //long, to make the multiplications happen in 32 bit. int inbuf[order+1] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0; //Instantiate DAC. Adafruit_MCP4725 dac; void setup() { //Set up timer to trigger the function samplinginterrupt each ms. Timer1.initialize(1000); Timer1.attachInterrupt(samplingInterrupt); //Set up DAC at correct I2C address. dac.begin(0x62); void loop() { //Check if it's time for a new conversion/filter run. if(sampletime) { //Read x(n) from the ADC. val = analogread(a0); //Run FIR filter. The current x(n) is input, the current y(n) is output. valfilt = firfilt(val); //Write current y(n) to the DAC. dac.setvoltage(valfilt,false); //Reset flag. sampletime = false; //ISR (interrupt service routine) triggered by timer 1 each ms. void samplinginterrupt(void) { //Set the flag true each ms to denote that it's time to convert/filter. sampletime = true; //FIR filter routine. Takes the current x(n) as argument, and output the current y(n). int firfilt(int currin) { //Variable to hold the current output. long currout = 0; //Put the current x(n) in the correct buffer position. inbuf[bufpoint] = currin; //Do filtering: Loop through all the coefficients, and multiply with the correct x //values in the buffer. //Accumulate the result in the variable currout. for (int i=0; i< ORDER; i++) { currout += (long)coeff[i]*(long)inbuf[bufpoint];
//Wrap the buffer pointer around when it is needed. if(bufpoint >= ORDER) bufpoint = 0; else bufpoint++; //Do the last multiplication/accumulation without updating the buffer pointer. //Better to just do this outside the loop, than to implement extra checks inside //the loop (higher comp time). currout += (long)coeff[order]*(long)inbuf[bufpoint]; //Normalize and return the output. return currout/divide; Bilde av inngang og utgang på oscilloskopet (1V/div): Beregninger: Signalfrekvens: f 7 1000 Hz f 789 = = = 25 Hz samples/periode 40 Skalering av utgang etter filtering: y(n) = FIRutgang O.PQ8RPR8SPOTPQ8R,UVWW878VXR7,YSVZ8X9 W8SRVZWUZ7RVZ,X8X9 T\ = FIRutgang = `abcr9yx9 T ]\ ^.T 3277
Kommentarer: I koden over er bufferet inbuf, som tar vare på tidligere inngangsverdier implementert som et såkalt sirkulært buffer. Dette betyr at når en inngangsverdi først er satt inn i bufferet (altså arrayet), så flyttes den aldri før den til slutt overskrives. I stedet brukes bare en pointer inbuf til å holde styr på hvor vi er i bufferet, og denne flippes alltid rundt til null når vi har nådd enden av arrayet. Bufferet blir derfor som en slags ring. Grunnen til at vi gjør det slik som dette er at vi slipper å flytte på data i minnet når de først er lagret, og vi sparer da masse tid. I denne oppgaven, som bare har ti koeffisienter, har vi ganske god tid. Vi er derfor ikke avhengig av å bruke et sirkulært buffer, og hvilken som helst metode som tar vare på nok foregående verdier vil nok føre frem. Vi kan for eksempel tenke at vi bruker et array som en kø, der det fremmerste elementet er det nyeste. Vi må da hver gang forskyve alle elementene med en posisjon for å gi plass til en ny verdi (den eldste dyttes da ut). Det kan være enklere å se for seg hvordan dette virker, men forskyvningsoperasjonen tar en del prosesseringstid, og kan for bli kritisk i større FIR-filtere. I vår oppgave vil det nok imidlertid fungere greit. Biblioteket TimerOne brukes til å styre en timer, som kaller opp funksjonen samplinginterrupt hvert millisekund. Dette gjøres i form av at timeren genererer et såkalt avbrudd (interrupt), som setter hovedprogrammet (loop()) på pause kaller opp en spesiell funksjon som kalles en ISR (interrupt service routine). I vårt tilfelle er det samplinginterrupt som blir ISR-en. Dette fungerer bra, men det som er tingen er at når vi kjører ISR-en, kan ikke systemet sette i gang andre ISR-er før vi returnerer til hovedprogrammet. Dette lager trøbbel hvis vi prøver å skrive til DAC-en vår inne i ISR-en, siden DAC-biblioteket selv benytter seg av interrupts/isr-er for å kommunisere med DAC-en. I koden er dette løst ved å kun bruke ISR-en til å sette et flagg som sier at nå er det på tide å lese, filtrere og skrive, og så gjøre de faktiske operasjonene i loop() i stedet. Flaggvariabelen sampletime må deklareres volatile. Enkelt forklart, så gjør dette kompilatoren oppmerksom på at sampletime kan endres fra et sted utenom hovedprogrammet, og derfor må den lastes inn fra minnet på nytt hver gang den skal leses. Både buffer-arrayet og arrayet som holder koeffisientene er int. Når selve multiplikasjonene utføres, må imidlertid verdiene castes til long, slik at det er en 32-bits multiplikasjon som blir gjort. Hvis ikke vil svare fra multiplikasjonen være int, og dette vil føre til overflyt.