PÄ senare tid har mina kollegor i branschen oberoende av varandra börjat frÄga mig: hur fÄr man en SDR-mottagare att fÄnga alla Bluetooth-kanaler samtidigt? Bandbredden tillÄter det, det finns SDR med en utgÄngsbredband pÄ 80 MHz eller mer. Det gÄr naturligtvis att göra detta pÄ en FPGA, men utvecklingstiden skulle bli ganska lÄng. Jag har lÀnge vetat att det Àr ganska enkelt att göra detta pÄ en GPU, men att det skulle vara sÄ hÀr!
Bluetooth-standarden definierar det fysiska lagret i tvÄ versioner: Classic och Low Energy. Specifikationen finns . Dokumentet Àr fruktansvÀrt stort, att lÀsa det i sin helhet Àr farligt för hjÀrnan. Lyckligtvis har stora företag som tillverkar mÀtutrustning resurser för att skapa överskÄdliga dokument om Àmnet. och , till exempel. Jag har överhuvudtaget inte en chans i konkurrensen med dem nÀr det gÀller kvaliteten pÄ materialpresentationen. Jag ber de intresserade att studera via lÀnkarna.
Allt jag behöver veta om det fysiska lagret för att skapa ett flerkanalsfilter Àr nÀtsteg och moduleringstakt. Dessa uppgifter sammanstÀlls i en tabell i ett av de angivna dokumenten:

SÄledes behöver vi dela upp 80 MHz-bandbredden i 79 filter med ett justeringssteg pÄ 1 MHz och samtidigt i 40 filter med ett justeringssteg pÄ 2 MHz. AvstÀmningarna frÄn filterutgÄngarna mÄste vara 1 MHz och 2 MHz, respektive.
DÀrför behöver vi tvÄ filterkombinationer.
Till att börja med ska vi vÀlja parametrar för dessa filter baserat pÄ signalbanden för Bluetooth Classic och Bluetooth Low Energy. Vi behöver deras impulsrespons för att berÀkna belastningen pÄ filternas berÀkningsenheter. Det bör genast noteras att vi har valt lÀngder pÄ impulsresponsen utifrÄn kraven för den "snabba" filtreringsalgoritmen. Substansen förÀndras inte av detta. Antalet koefficienter för impulsresponsen fÄr inte vara för stort, sÄ att filtret kan implementeras pÄ rimlig berÀkningshÄrdvara.
För filter med ett justeringssteg pÄ 1 MHz vÀljer vi passbandet för lÄgpassfiltret (halva passbandet för bandpassfiltret) till 500 kHz, och lÀngden pÄ impulsresponsen justerar vi till 480 avledningar. För filter med ett justeringssteg pÄ 2 MHz vÀljer vi dessa parametrar till 1 MHz och 240 avledningar, respektive. VÀlj fönstertypen till Kaiser. Vi berÀknar impulsresponsen i filterDesigner och laddar ner dem i C-header-format:
SkÀrmdumpar frÄn filterDesigner




Man kan lösa problemet direkt genom att konstruera en motsvarande DDC-array baserad pÄ antalet filter (). Denna metod Àr bra för FPGA:er, dÀr det Àr möjligt att spara resurser genom att minska bitdjupet pÄ de första berÀkningsstegen. Dessutom Àr FPGA det mest energieffektiva sÀttet att implementera det. Men arbetsinsatsen för denna metod Àr den högsta.
Vid utförandet av filterkombinationer pÄ dagens populÀra GPU:er öppnas möjligheten att implementera en mer sofistikerad algoritm: polyfaserad filterkombination baserad pÄ FFT, som finns tillgÀnglig pÄ CUDA frÄn biblioteket. I utlÀndsk litteratur kallas algoritmen Polyphase eller WOLA (Weight, Overlap and Add) FFT Filterbank. Min ovilja att rita hindrar mig frÄn att ge en visuell förklaring sjÀlv. Det finns mÄnga material pÄ nÀtet om Àmnet, sÀrskilt en tydlig grafik som Àr gjord pÄ sida 11 (ett stort tack till de respekterade författarna), hÀr Àr den:

Jag ska försöka förklara bearbetningsschemat med mina egna ord. De som Àr kÀnsliga ombeds att inte lÀsa.
Jag ska försöka förklara bearbetningsschemat med mina egna ord inom ramen för mina metodiska möjligheter. FFT Àr konvolutionen av den ingÄende signalen med hela spektrumet av komplexa ortogonala harmoniker som faller inom intervallet av impulsresponsen. Impulsresponsen för filtret, som signalen multipliceras med innan den gÄr in i FFT, moduleras av detta spektrum av harmoniker. Med andra ord, den övergripande impulsresponsen för de resulterande filtret i kombinationen bringas utanför parentesen. DÀrefter filtreras spektrumet av harmoniker ned till ett visst antal gÄnger, pÄ grund av att bandbredden för filtret utvidgas jÀmfört med filtret med rektangulÀrt fönster. PÄ bilden ser vi en nedskÀrning med fyra. Med andra ord, efter att bandbredden har utvidgats med ett Kaiser-fönster (med en samtidig ökning av dÀmpningen i stopbandet) behöver vi inte alla filter utan endast en fjÀrdedel av dem. De övriga Àr överflödiga, deras frekvensegenskaper överlappar. FrÄn fyra pÄ varandra följande punkter i FFT vÀljer vi bara den första, vars berÀkning Àr summan av de fyra
ingÄngspunkterna, tagna över en tid som Àr en fjÀrdedel av varaktigheten av den ursprungliga FFT.
Vi vÀljer den hÄrdvara som finns tillgÀnglig. Det Àr kortet frÄn företaget "Instrument Systems" FMC126P. Jag har redan skrivit om det i en tidigare artikel. . I slot FMC on the board is inserted a submodule from the same company with an AD9371 transceiver with a bandwidth of 100 MHz. The entire stream from the transceiver can be continuously transferred to the computer for processing.
Letâs choose a video card with a GTX 1050 GPU. (Just kidding, it chose us: it was all that was at hand, snatched from the calculator for antenna calculations, but it was all the more surprising to see a working comb.) Let's move on to the software part.
Unfortunately, due to licensing issues, we cannot publish the full code. We can only show the GPU kernels. However, the rest of the code is not particularly interesting.
Here is the kernel that performs the multiplication of the signal by the window and adding, along with the wrapper for its call:
__global__ void cuComplexMultiplyWindowKernel(const cuComplex *data, const float *window, size_t windowSize, cuComplex *result) {
__shared__ cuComplex multiplicationResult[480];
multiplicationResult[threadIdx.x] = cuComplexMultiplyFloat(data[threadIdx.x + windowSize / 4 * blockIdx.x], window[threadIdx.x]);
__syncthreads();
cuComplex sum;
sum.x = sum.y = 0;
if (threadIdx.x < windowSize / 4) {
for(int i = 0; i < 4; i++) {
sum = cuComplexAdd(sum, multiplicationResult[threadIdx.x + i * windowSize / 4]);
}
result[threadIdx.x + windowSize / 4 * blockIdx.x] = sum;
}
}
cudaError_t cuComplexMultiplyWindow(const cuComplex *data, const float *window, size_t windowSize, cuComplex *result, size_t dataSize, cudaStream_t stream) {
size_t windowStep = windowSize / 4;
cuComplexMultiplyWindowKernel<<>>(data, window, windowSize, result);
return cudaGetLastError();
}
The signal processing code that calls this kernel precisely follows the algorithm scheme shown in the figure above, so I see no reason to present it here.
The combs were verified on the output spectrum of the channels in real-time. A signal from a 2450 MHz signal generator was input to the AD9371, and the filter selectivity corresponded to the calculated value.

Plans include adapting the software for the XRTX board and implementing packet searching if anyone finds it necessary or if there is free time.
All the software work was done by , glory to him!
KĂ€lla: habr.com
