Bygget fungerar för övrigt fint som en röstförvrängare, kan "byta plats på bas och diskant".
Musik kan man få och låta hur illa som helst om frekvensfelet att tillräckligt, något 10-tal Hz räcker.
En bild på bygget:
Den här gången använde jag mig av Microchips FIR-filter skrivna i assembler
som kompilerades med "C30" från Microchip.
Det gick åt ett antal timmar innan allt fungerade som det skulle.
dsPIC skiljer sig en del från de vanliga (PIC12 -16 och 18-serien ) jag har hållit på med tidigare.
Ska försöka förklara lite hur den fungerar.
Insignalen samplas med 32kHz / 12-bitar, det är en timer som ger interrupt med rätt tidsintervall ( 31,25us ).
Efter det delas och sedan filtreras signalen i två stycken allpassfilter med 2 x 128 "Taps".
Nu har vi två signaler, kallar dem för I och Q.
Allpassfiltret ( eller Hilbert filter ) ger en fasförskjutning mellan I och Q på 90 grader.
Så här anropas filtret i C:
Kod: Markera allt
FIR(BLOCK_LENGTH, &FilterOut[0], &A_D_value[0], &Hilbert_Allpass_Filter_I_channel);
Out_I=FilterOut[0];
FIR(BLOCK_LENGTH, &FilterOut[0], &A_D_value[0], &Hilbert_Allpass_Filter_Q_channel);
Out_Q=FilterOut[0];
Själva filtret är en samling hexadecimala tal, ett av "filtren" eller snarare filterkoefficienterna ser ut så här:
Kod: Markera allt
; ..............................................................................
; File Hilbert_Allpass.s
; ..............................................................................
.equ Hilbert_Allpass_I_channel_NumTaps, 128
; ..............................................................................
; Allocate and initialize filter taps
;
.section Hilbert_AllpassFilter_I_channel_psvconst, code ;<-Syntax supported in MPLAB C30
;v1.30 and later
.align 256
Hilbert_allpass_I_channel_Taps:
.hword 0xffc7, 0xfe4f, 0xfb1e, 0xf714, 0xf4a6, 0xf76c, 0x027a, 0x160c
.hword 0x2de2, 0x4193, 0x4764, 0x38be, 0x1634, 0xe8bf, 0xbef0, 0xa6fb
.hword 0xa83b, 0xbf99, 0xe0e8, 0xfcc8, 0x082b, 0x0184, 0xf0dd, 0xe2d9
.hword 0xe172, 0xee97, 0x0396, 0x1580, 0x1bc4, 0x150c, 0x0794, 0xfcfb
.hword 0xfc44, 0x05c2, 0x1356, 0x1c98, 0x1c34, 0x130c, 0x0753, 0x005a
.hword 0x01e9, 0x0a0d, 0x12b2, 0x15c2, 0x110d, 0x077a, 0xfee2, 0xfc20
.hword 0xffff, 0x06ef, 0x0b9c, 0x0a7d, 0x040c, 0xfc3e, 0xf7bc, 0xf8c4
.hword 0xfddc, 0x02f9, 0x0457, 0x00eb, 0xfaef, 0xf638, 0xf595, 0xf901
.hword 0xfdcf, 0x009a, 0xff91, 0xfb94, 0xf774, 0xf5f7, 0xf7fa, 0xfc00
.hword 0xff51, 0xffe1, 0xfd9e, 0xfa60, 0xf88c, 0xf96e, 0xfc71, 0xff9b
.hword 0x00ff, 0xffff, 0xfda3, 0xfbc9, 0xfbd4, 0xfdcc, 0x0068, 0x020c
.hword 0x01da, 0x003f, 0xfe8f, 0xfe0d, 0xff17, 0x00f8, 0x0274, 0x02ac
.hword 0x01a7, 0x003d, 0xff77, 0xffd6, 0x010b, 0x023c, 0x02a3, 0x020f
.hword 0x00f9, 0x0022, 0x000b, 0x00a7, 0x0171, 0x01d3, 0x018d, 0x00d5
.hword 0x002b, 0xfffa, 0x0052, 0x00e5, 0x0146, 0x0130, 0x00b1, 0x0016
.hword 0xffb4, 0xffb2, 0xfffc, 0x0053, 0x0078, 0x004b, 0xfff3, 0xffdb
; ..............................................................................
; Allocate delay line in (uninitialized) Y data space
.section .ydata, data, ymemory ;<-Syntax supported in MPLAB C30
;v1.30 and later
.align 512
Hilbert_I_Delay:
.space Hilbert_Allpass_I_channel_NumTaps*2
; ..............................................................................
; Allocate and intialize filter structure
.section .data
.global _Hilbert_Allpass_Filter_I_channel
_Hilbert_Allpass_Filter_I_channel:
.hword Hilbert_Allpass_I_channel_NumTaps
.hword psvoffset(Hilbert_allpass_I_channel_Taps)
.hword psvoffset(Hilbert_allpass_I_channel_Taps) + Hilbert_Allpass_I_channel_NumTaps*2 - 1
.hword psvpage(Hilbert_allpass_I_channel_Taps)
.hword Hilbert_I_Delay
.hword Hilbert_I_Delay + Hilbert_Allpass_I_channel_NumTaps*2 - 1
.hword Hilbert_I_Delay
som skriver ut koefficienterna i en textfil. Sen är det bara klippa & klistra in i MPLAB.
Sedan finns det en mjukvarublandare som multiplicerar I och Q-signalerna med 8kHz ( 1/4-del av 32kHz )
Blandare nummer ett använder sekvensen 1, 0 ,-1, 0 att multiplicera I med.
Blandare nummer två använder sekvensen 0, 1 , 0,-1 att multiplicera Q med.
Då får vi en automatisk fasförskjutning på 90 grader ( jämför sin / cos ).
"Lokaloscillatorn" på 8kHz ser i stort sett ut så här:
Kod: Markera allt
signed int I_Osc[Phases] = {1, 0,-1, 0}; // In-phase and Quadrature oscillator
signed int Q_Osc[Phases] = {0, 1, 0,-1};
if (p==4) // Local oscillator control
{
p=0;
}
p=p+1;
En mjukvaruversion av två stycken Mini-circuits "SBL-1"
Kod: Markera allt
Out_to_AD = ((Out_I * I_Osc[p]) - (Out_Q * Q_Osc[p])); // Software Mixer, sideband select.
Sound = (Out_to_AD + 2048 );
Sen skickas data via SPI till en 12-bit D/A MCP4821 som ger ut den färdiga signalen.
Kod: Markera allt
SPI1BUF = ( Sound | 0x1000 ); // Send 16-bit data to D/A converter
while ( SPI1STATbits.SPITBF == 1 )
{
}
Dummy_read = SPI1BUF;
Klockoscillator är på 6,144MHz * 16x ger 98,304MHz / 4 ger en effektiv klockfrekvens på 24,576MHz.
dsPIC:en jobbar inte för fullt än, bilden visar hur lång tid filtret, lokaloscillator och blandning tar.
5us / div, periodtid 31,25us . Då signalen är noll så är dsPIC "ledig" och kan hinna med annat.
Inte illa med tanke på att den kör igenom 2 x 128 "taps" stora FIR-filter på den tiden bl.a.
Strömförbrukning ca 90mA @ 5V.
Utsignalen övre sidbandet "USB", frekvensområdet (ca) 0 - 4,5kHz uppblandat med 8kHz oscillator till 8-12,5kHz.
Undre sidbandet "LSB" syns knappt ( frekvensområdet 3,5 - 8kHz )
För radiobruk så kan det räcka med ett billigt keramiskt filter ( på t.ex. 455kHz ) som rensar bort onödigt skräp
blir i varje fall betydligt lättare än med den traditionella "filter-metoden" som kräver ett bra men dyrt kristallfilter.
En blandare tillkommer förstås som t.ex. NE612, har inte kommit så långt än....