KerberosSDR代码笔记(4) 接收机程序(滤波器、直流抑制、循环缓存)


接收机程序分为2部分,一部分在hydra_receiver.py中,另一部分在_receiver/C文件夹下,由几个c语言写的程序构成。

第二篇笔记中讲过:

_receiver文件夹下还有个C文件夹,另外有一个hydra_receiver.py。C文件夹里的程序比较底层,涉及直接的硬件交互。hydra_receiver.py实现了一个面向对象的接收机,它可以被算法所调用,它可以控制底层的C程序,另外它可以做一些数据预处理工作,包含滤波、去dc offset、相位差修正等功能。

_receiver/C里的c程序比较多。rtl_daq.c是最直接与rtlsdr交互的,它调用了rtlsdr的api,然后通过stdout传输数据给python代码,rtl_rec.h实现了一个封装了rtlsdr硬件的结构体,4个接收机各自可以有不同的增益和中心频点、采样率、缓存等,它的作用是把这些变量打包在一个结构上,这样方便区分。rtl_daq.c就是使用这个结构体来与rtlsdr硬件api进行交互的。sync.c和sync.h利用延迟计算的结果,对stdin中收到的iq数据进行延迟操作并输出到stdout。gate.c相当于一个门,它读取stdin的数据,并有选择性地输出到stdout中。sim.c的作用是当没有实际硬件时,它可以代替rtl_daq.c来仿真,把模拟出来的iq数据提供给python代码。
 

先来看一下hydra_receiver.py

https://github.com/rtlsdrblog/kerberossdr/blob/22f588b60280f2b8d3b6e1c29959abea51c84c03/_receiver/hydra_receiver.py

 

# KerberosSDR Receiver

# Copyright (C) 2018-2019  Carl Laufer, Tamás Pető
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see .
#

# -*- coding: utf-8 -*-

import numpy as np
import sys
import time
from struct import pack, unpack
from scipy import signal

class ReceiverRTLSDR():
    """
                    RTL SDR based receiver controller module
        Description:
         ------------
           Implements the functions to handle multiple RTL-SDR receivers 
        Main functions:
           这些函数是用来处理多个rtlsdr接收机的
         ------------------
        Notes:
         ------------
         
 
        Features:
         ------------
        Project: Hydra
        Authors: Tamás Pető
        License: No license
        Changelog :
            - Ver 1.0000 : Initial version (2018 04 23)       
            - Ver 2.0000 : (2018 05 22) 
    """

    # GUI Signal definitions
   
    def __init__(self):
            #print("[ INFO ] Python rec: Starting Python RTL-SDR receiver")
            
            # Receiver control parameters 这是控制3个c程序的fifo           
            self.gc_fifo_name = "_receiver/C/gate_control_fifo"
            self.sync_fifo_name = "_receiver/C/sync_control_fifo"
            self.rec_control_fifo_name = "_receiver/C/rec_control_fifo"
            #struct.pack用来处理c语言结构
            self.gate_trigger_byte = pack('B',1)          
            self.gate_close_byte = pack('B',2)
            self.sync_close_byte = pack('B',2)
            self.rec_control_close_byte = pack('B',2) 
            #这几个命令用整数表示,pack函数把python中的整数转为c语言的无符号字符类型
            self.sync_delay_byte = 'd'.encode('ascii')
            self.reconfig_tuner_byte = 'r'.encode('ascii')
            self.noise_source_on_byte = 'n'.encode('ascii')
            self.noise_source_off_byte = 'f'.encode('ascii')
            #这几个命令用字符表示,要在python里把它们从unicode转为ascii来给c语言使用
            self.gc_fifo_descriptor = open(self.gc_fifo_name, 'w+b', buffering=0)
            self.sync_fifo_descriptor = open(self.sync_fifo_name, 'w+b', buffering=0)
            self.rec_control_fifo_descriptor = open(self.rec_control_fifo_name, 'w+b', buffering=0)
            #这3个描述符用来在python中操作这3个fifo
            self.receiver_gain = 0 # Gain in dB x 10 
            self.receiver_gain_2 = 0 # Gain in dB x 10 
            self.receiver_gain_3 = 0 # Gain in dB x 10 
            self.receiver_gain_4 = 0 # Gain in dB x 10 
            #接收机增益
            # Data acquisition parameters
            self.channel_number = 4 #接收机有4个
            self.block_size = 0; #128 * 1024 #256*1024 #接收块大小
                        
            self.overdrive_detect_flag = False

            # IQ preprocessing parameters  #IQ预处理参数
            self.en_dc_compensation = False  #直流抑制
            self.fs = 1.024 * 10**6  # Sampling frequency #采样率
            self.iq_corrections = np.array([1,1,1,1], dtype=np.complex64)  # Used for phase and amplitude correction     
            #iq_corrections数组,4个通道各对应一个,默认值是1,这个修正值是复数
            self.fir_size = 0  #抽头大小
            self.fir_bw = 1  # Normalized to sampling frequency 
            self.fir_filter_coeffs = np.empty(0) #fir滤波器的系数
            self.decimation_ratio = 1   #降采样倍数            
            
            
    def set_sample_offsets(self, sample_offsets):
        #设置采样时间差
        #print("[ INFO ] Python rec: Setting sample offset")
        delays = [0] + (sample_offsets.tolist()) #获取各通道的采样时间差
        self.sync_fifo_descriptor.write(self.sync_delay_byte)#告知c语言要发送延迟了
        self.sync_fifo_descriptor.write(pack("i"*4,*delays)) #发送4个延迟 
        # *操作符代表接收任意数量的参数并转为元组

    def reconfigure_tuner(self, center_freq, sample_rate, gain):
       #设置接收机硬件参数
       #print("[ INFO ] Python rec: Setting receiver center frequency to:",center_freq)
       #print("[ INFO ] Python rec: Setting receiver sample rate to:",sample_rate)
       #print("[ INFO ] Python rec: Setting receiver gain to:",gain)
       self.rec_control_fifo_descriptor.write(self.reconfig_tuner_byte) 
       #告知c语言要重新设置接收机硬件了   
       self.rec_control_fifo_descriptor.write(pack("I", int(center_freq)))
       #设置接收机中心频率
       self.rec_control_fifo_descriptor.write(pack("I", int(sample_rate)))
       #设置接收机采样率
       self.rec_control_fifo_descriptor.write(pack("i", int(gain[0])))
       self.rec_control_fifo_descriptor.write(pack("i", int(gain[1])))
       self.rec_control_fifo_descriptor.write(pack("i", int(gain[2])))
       self.rec_control_fifo_descriptor.write(pack("i", int(gain[3])))
       #设置接收机增益
    
    def switch_noise_source(self, state):
        #更改噪声源状态
        if state:
            #print("[ INFO ] Python rec: Turning on noise source")
            self.rec_control_fifo_descriptor.write(self.noise_source_on_byte)
            #告知c语言打开噪声源
        else:
            #print("[ INFO ] Python rec: Turning off noise source")
            self.rec_control_fifo_descriptor.write(self.noise_source_off_byte)
            #告知c语言关闭噪声源

    def set_fir_coeffs(self, fir_size, bw):
        """
            Set FIR filter coefficients #设置FIR滤波器系数
            
            TODO: Implement FIR in C and send coeffs there
        """
        
        # Data preprocessing parameters
        #数据预处理参数
        if fir_size >0 :
            #抽头必须大于0
            cut_off = bw/(self.fs / self.decimation_ratio) 
            #计算正规化的截止频率 滤波器带宽/(采样率/降采样倍数)
            self.fir_filter_coeffs = signal.firwin(fir_size, cut_off, window="hann") 
            #按指定的抽头、截止频率和窗函数得到滤波器系数
        self.fir_size = fir_size
        
    def download_iq_samples(self):
            self.iq_samples = np.zeros((self.channel_number, self.block_size//2), dtype=np.complex64)
            #新建iq_samples变量,行数是通道数,列数是块大小的一半
            #为啥列数是块大小的一半?因为块大小是对应原始数据来说的,原始数据按照iqiqiq这样存储
            #现在iq_samples存储的是complex类型了,一个位置就可以包含iq,所以长度要除以2
            self.gc_fifo_descriptor.write(self.gate_trigger_byte)
            #告知c语言gate,打开门
            #print("[ INFO ] Python rec: Trigger writen")
            # -*- coding: utf-8 -*-
            #time.sleep(0.5)
            read_size = self.block_size * self.channel_number
            #一次读取大小是块大小*通道数量

            #byte_data=[]
            #format_string = "B"*read_size
            #while True:
            byte_array_read = sys.stdin.buffer.read(read_size)
            #从stdin的缓存中读取指定大小的数据存入byte_array_read
            """                
                if not byte_array_read or len(byte_data) >= read_size:
                    print("EOF")
                    break
            """
            overdrive_margin = 0.95
            self.overdrive_detect_flag = False

            byte_data_np = np.frombuffer(byte_array_read, dtype='uint8', count=read_size)
            #把读取到的数据转为numpy的uint8数据类型,并存入byte_data_np

            self.iq_samples.real = byte_data_np[0:self.channel_number*self.block_size:2].reshape(self.channel_number, self.block_size//2)
            self.iq_samples.imag = byte_data_np[1:self.channel_number*self.block_size:2].reshape(self.channel_number ,self.block_size//2)
            #在byte_data_np中的存储方式还是原始数据,具体说是iqiqiqiq这样交替存储
            #另外由于接收机有4个通道,这4个通道的数据都在byte_data_np里,它们的存储顺序是
            #一开头全部是第1个通道的iqiqiqiq,存完了紧接着就是第2个通道的iqiqiq
            #再然后是第3和第4通道的数据,数量是正正好好不多不少的
            #每个通道长度完全按照block_size指定
            #byte_data_np[0:self.channel_number*self.block_size:2]
            #从0开始,步进是2,这样正好读取的是i,把q跳过了
            #结束位置是数组的最后,block_size是一个通道包含iq的长度,乘以channel_number
            #就是4个通道包含iq的总长度了,所以是一只读到最后,把4个通道的i值全部读出来了
            #同理byte_data_np[1:self.channel_number*self.block_size:2]
            #这个读的就是4个通道的q值
            #读完了之后还做了reshape,reshape函数会根据它的2个参数重新组成2维数组
            #2维数组尺寸是 行数self.channel_number 列数self.block_size//2
            #本来长度是channel_number*block_size,由于步进2一个跳一个
            #现在长度是channel_number*block_size//2了
            #然后reshape函数每隔block_size//2就把这个数组分隔开来
            #把隔开的这4段(channel_number)重新作为4行数据
            #总之最后得到的self.iq_samples.real中存储了4行,block_size//2列的i数据
            #第1行就是第1个通道的i数据,第2行是第2隔通道的i数据,一共4行以此类推
            #self.iq_samples.imag也类似,只不过存储的都是对应的q数据


     #       for m in range(self.channel_number):    
      #          real = byte_data_np[m*self.block_size:(m+1)*self.block_size:2]
       #         imag = byte_data_np[m*self.block_size+1:(m+1)*self.block_size:2]
                #real = np.array(byte_data[::2], dtype=np.uint8)
                #imag = np.array(byte_data[1::2], dtype=np.uint8)
        #        self.iq_samples[m,:].real, self.iq_samples[m,:].imag = real, imag
                # Check overdrive
                #if (np.greater(self.iq_samples[m, :].real,int(127+128*overdrive_margin)).any()) or  (np.less(self.iq_samples[m, :].real, int(127-128*overdrive_margin)).any()):                      
                #      self.overdrive_detect_flag = True
                      #print("[ WARNING ] Overdrive at ch: %d"%m)   
                      #print("real max: ",np.max(self.iq_samples[m, :].real))
                      #print("real min: ",np.min(self.iq_samples[m, :].real))
                #if (np.greater(self.iq_samples[m, :].imag, int(127+128*overdrive_margin)).any()) or (np.less(self.iq_samples[m, :].imag, int(127-128*overdrive_margin)).any()):                      
                #      self.overdrive_detect_flag = True
                      #print("[ WARNING ] Overdrive at ch: %d"%m)                
                      #print("imag max: ",np.max(self.iq_samples[m, :].real))
                      #print("imag min: ",np.min(self.iq_samples[m, :].real))


            
            self.iq_samples /= (255 / 2)
            self.iq_samples -= (1 + 1j) 
            #这上面的2个命令的目的是转换数据的范围
            #byte_data_np里的数据格式是uint8
            #最小值是00000000对应0,最大值是11111111对应255,0~255
            #我们要把它转换为-1~1(暂时不考虑虚部,只考虑实部)
            #要做的操作是所有数据都除以(255/2)即127.5然后再减去1
            #这样0就变为-1,255变为1
            #由于iq_samples还包含虚部,它也要做类似操作除以(255/2)然后减去1j
                      
            
            #np.save("hydra_raw.npy",self.iq_samples)
            self.iq_preprocessing()
            #获取完数据后就要做iq与处理
            #print("[ DONE] IQ sample read ready")
            
            
            #return iq_samples    
       
    def iq_preprocessing(self):
        #iq预处理
        # Decimation
        #降采样
        if self.decimation_ratio > 1:
           iq_samples_dec = np.zeros((self.channel_number, round(self.block_size//2/self.decimation_ratio)), dtype=np.complex64)
           #新建二维数组用来存放降采样后的数据,行数还是channel_number
           #列数由block_size//2缩小decimation_ratio倍
           for m in range(self.channel_number):
               #每行(每个接收机通道)单独处理,这是第m行的数据
               iq_samples_dec[m, :] = self.iq_samples[m, 0::self.decimation_ratio]
               #从原来的二维数组中取出一行,然后列是从头到尾,但是步进是decimation_ratio
               #这样采样点就减少了decimation_ratio倍
               #然后再把这些行组成新的二维数组并重新存入iq_samples数组
           self.iq_samples = iq_samples_dec

        # FIR filtering
        if self.fir_size > 0:
            for m in range(self.channel_number):
                self.iq_samples[m, :] = np.convolve(self.fir_filter_coeffs, self.iq_samples[m, :], mode="same")
                #fir滤波器的原理就是把滤波器系数和采样点做卷积
                #得到的结果就是经过滤波后的采样数据了,同样也是每一行单独处理

        # Remove DC content (Force on for now)
        if self.en_dc_compensation or True:
            for m in np.arange(0, self.channel_number):
               self.iq_samples[m,:]-= np.average( self.iq_samples[m,:])
               #如果直流抑制打开了就要取采样点的平均值,然后所有采样点再减去这个平均值
               #直流分量在频域上是0Hz的一个波峰,在时域上相当于信号围绕的横轴偏离了x坐标轴
               #如果取得了信号在时域上的平均值就知道偏离了多少,然后把每个采样点减去这个值
               #信号就会围绕x坐标轴变化了,相当于频域0Hz的波峰没有了
               #不过信号不是完美的正弦波,因此平均值不一定就正好是要减去的偏移量
           
        # IQ correction
        for m in np.arange(0, self.channel_number):
            self.iq_samples[m, :] *= self.iq_corrections[m]
            #每个通道即使接收完全相同的信号,采样点也会有相位差
            #相当于在复平面上,每个通道的采样点都相对基准通道的对应采样点做了旋转
            #我们把这个相位差求到以后得到了对应的修正量存入了iq_correction里
            #然后我们乘以这个对应的修正量就可以把采样点旋转回来(乘以e^(j*phi)就可以旋转)
            #这样如果是相同信号的话所有采样点就可以重合了
            #如果是真实信号,它们之间的相位差也是通道本身产生的
            #由于各个通道相对基准通道的相位差都不一样,因此每行都各自有自己的iq_correction[m]
    
        
    def close(self):
        self.gc_fifo_descriptor.write(self.gate_close_byte)
        self.sync_fifo_descriptor.write(self.sync_close_byte)
        self.rec_control_fifo_descriptor.write(self.rec_control_close_byte)
        #向3个fifo发送关闭命令对应的字节
        time.sleep(1)
        #等待对应的c程序关闭完成
        self.gc_fifo_descriptor.close()
        self.sync_fifo_descriptor.close()
        #把fifo也关闭掉
        print("[ INFO ] Python rec: FIFOs are closed")

iq预处理的fir滤波器有必要单独讲一下

参考这2篇文章:

http://bigsec.net/b52/scipydoc/filters.html#firwin

https://baike.baidu.com/item/FIR滤波器/1983543?fr=aladdin

FIR滤波器就是一组抽头或者叫滤波器系数,把它们和原始信号进行对应相乘再相加,得到滤波器后的采样点,这个操作正好就是卷积的公式。

接下来看c程序,首先看一下makefile,大致了解哪几个代码编译出一个可执行文件。

# Makefile for compiling Hydra DSP code
# Version : 1.0
# Date: August 2018
# Author: Tamás Pető
CC=gcc
# For Raspberry Pi 3
#CFLAGS=-Wall -O3 -mcpu=cortex-a53 -mfloat-abi=hard -mfpu=neon-fp-armv8 -mneon-for-64bits -mtune=cortex-a53

# For Tinkerboard
#CFLAGS=-Wall -O2 -march=armv7-a -mtune=cortex-a17 -mfpu=neon -mfloat-abi=hard

# For Generic x86
CFLAGS=-Wall -march=native

RM= rm -f

#一共会编译出4个可执行文件
all: rtl_daq sync gate sim

#rtl_daq.c和rtl_rec.h这两个文件编译出rtl_daq 直接与rtlsdr硬件交互
rtl_daq: rtl_daq.c rtl_rec.h
	$(CC) $(CFLAGS) rtl_daq.c -lpthread -lrtlsdr -o rtl_daq

#sync.c和sync.h这两个文件编译出sync 用来实现采样时间同步
sync: sync.c sync.h
	$(CC) $(CFLAGS) sync.c -lpthread -o sync

#gate.c编译出gate 用来控制输出
gate: gate.c
	$(CC) $(CFLAGS) gate.c -lpthread -o gate

#sim.c编译出sim 它可以代替rtl_daq 在没有硬件情况下提供仿真iq数据给python
sim: sim.c
	$(CC) $(CFLAGS) sim.c -o sim

clean:
	$(RM) rtl_daq sync gate sim

由于我们要看接收机内部各个程序如何配合,所以要先来看一下启动脚本run.sh。

这个启动脚本的命令顺序很重要,它决定了几个程序互相之间的数据走向。

rtl_daq->sync->gate->hydra_main_windows.py

先从设备获取数据->同步(挑选合适的数组起始位置)->门决定是把数据丢掉还是输出给python->最终把需要的数据给python程序

#!/bin/bash

BUFF_SIZE=256 #Must be a power of 2. Normal values are 128, 256. 512 is possible on a fast PC. #这里设置一次处理的数据量的大小
IPADDR="0.0.0.0" #本机ip地址
IPPORT="8081"  #网页端口

### Uncomment the following section to automatically get the IP address from interface wlan0 ###
### Don't forget to comment out "IPADDR="0.0.0.0" ###

# IPADDR=$(ip addr show wlan0 | grep "inet\b" | awk '{print $2}' | cut -d/ -f1)
# while [ "$IPADDR" == "" ] || [ "$IPADDR" == "169.254.*" ]
# do
# sleep 1
# echo "waiting for network"
# IPADDR=$(ip addr show wlan0 | grep "inet\b" | awk '{print $2}' | cut -d/ -f1)
# echo $IPADDR
# done

### End of Section ###

# Useful to set this on low power ARM devices
#sudo cpufreq-set -g performance

# Set for RPI3 with heatsink/fan
#sudo cpufreq-set -d 1.4GHz
# Set for Tinkerboard with heatsink/fan
#sudo cpufreq-set -d 1.8GHz

# Clear memory  #释放内存
sudo sh -c "echo 0 > /sys/module/usbcore/parameters/usbfs_memory_mb"
echo '3' | sudo dd of=/proc/sys/vm/drop_caches status=none

echo "Starting KerberosSDR"

# Kill old instances #先把以前开的程序全部关闭
sudo kill $(ps aux | grep 'rtl' | awk '{print $2}') 2>/dev/null || true
sudo pkill rtl_daq
sudo pkill sim
sudo pkill sync
sudo pkill gate
sudo pkill python3

# Enable on the Pi 3 to prevent the internet from hogging the USB bandwidth
#sudo wondershaper wlan0 3000 3000
#sudo wondershaper eth0 3000 3000

sleep 1

# Create RAMDISK for jpg files #新建ramdisk来存放频繁更改的jpg文件
sudo mount -osize=30m tmpfs /ram -t tmpfs

# Remake Controller FIFOs #重新新建几个用于控制c程序的fifo
rm -f _receiver/C/gate_control_fifo
mkfifo _receiver/C/gate_control_fifo

rm -f _receiver/C/sync_control_fifo
mkfifo _receiver/C/sync_control_fifo

rm -f _receiver/C/rec_control_fifo
mkfifo _receiver/C/rec_control_fifo

# Start programs at realtime priority levels
curr_user=$(whoami)
sudo chrt -r 50 ionice -c 1 -n 0 ./_receiver/C/rtl_daq $BUFF_SIZE 2>/dev/null 1| sudo chrt -r 50 ./_receiver/C/sync $BUFF_SIZE 2>/dev/null 1| sudo chrt -r 50 ./_receiver/C/gate $BUFF_SIZE 2>/dev/null 1|sudo nice -n -20 sudo -u $curr_user python3 -O _GUI/hydra_main_window.py $BUFF_SIZE $IPADDR &>/dev/null&
#这里的启动顺序很有讲究,它不但可以开启所有需要的程序,而且决定了标准输出1是由谁输出给谁

# Comment the above and uncomment the below to show all errors to the log files
#sudo chrt -r 50 ionice -c 1 -n 0 ./_receiver/C/rtl_daq $BUFF_SIZE 2>log_rtl_daq 1| sudo chrt -r 50 ./_receiver/C/sync $BUFF_SIZE 2>log_sync 1| sudo chrt -r 50 ./_receiver/C/gate $BUFF_SIZE 2>log_gate 1|sudo nice -n -20 sudo -u $curr_user python3 -O _GUI/hydra_main_window.py $BUFF_SIZE $IPADDR &>log_python&
#这一句命令和上面命令功能类似,区别是错误是否保存,前面那句直接把错误2输出给/dev/null黑洞
#而这里的命令把错误输出给文件

# Start PHP webserver which serves the updating images
echo "Server running at $IPADDR:$IPPORT"
sudo php -S $IPADDR:$IPPORT -t _webDisplay >&- 2>&-
#开启php服务器

另外每个程序还都有一个$BUFF_SIZE,这个变量是sh脚本开头指定的256,这样所有的程序都有一个统一的数据长度要处理。

接下来看一下rtl_daq.c,首先看看它配套的结构体rtl_rec.h

#include 
#include  //引用了rtl-sdr的api
#include 
struct rtl_rec_struct {
    int dev_ind, gain; //dev_ind表示的是第几个rtl设备,相当于rtl=后的那个数字,gain是增益
    rtlsdr_dev_t *dev; //这是设备句柄
    uint8_t *buffer;   //对应的数据缓存
    unsigned long buff_ind; //缓存编号
    pthread_t async_read_thread;   //这个rtl设备对应的异步读取的线程     
    uint32_t center_freq, sample_rate;//这个rtl设备的中心频率和采样率
};

现在来看rtl_daq.c 

/* KerberosSDR Python GUI
 *
 * Copyright (C) 2018-2019  Carl Laufer, Tamás Pető
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 *
 *
 *
 * Project : KerberosSDR
 * Object  : Coherent multichannel receiver for the RTL chipset based software defined radios
 * Date    : 2018 09 10 - 2019 02 07
 * State   : Production
 * Version : 0.1
 * Author  : Tamás Pető
 * Modifcations : Carl Laufer
 *
 *
 *
 */
/* Compile like this:
gcc -std=c99 rtl_rec.h rtl_daq.c -lpthread -lrtlsdr -o rtl_daq
*/

#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 
#include 
#include 

#include "rtl-sdr.h"
// TODO: Remove unnecessary includes
#include "rtl_rec.h"

#define NUM_CH 4  // Number of receiver channels //这是接收机通道数量,默认是4个
#define NUM_BUFF 1 // Number of buffers //缓存数量
//#define BUFF_LEN (16*16384) //(16 * 16384)
#define SAMPLE_RATE 2000000 //默认采样率
#define CENTER_FREQ 107200000 //默认中心频率
#define GAIN 5 //默认增益

#define CFN "_receiver/C/rec_control_fifo" /* Receiver control FIFO name */
//这是python用来控制rtl_daq的fifo,现在在这片代码里简称为CFN
#define NOTUSED(V) ((void) V)
#define ASYNC_BUF_NUMBER     30

#define DEFAULT_RATE         2000000
#define DEFAULT_FREQ         107200000

int BUFF_LEN = 0;

struct rtl_rec_struct* rtl_receivers; //设备结构体
pthread_mutex_t buff_ind_mutex; //互斥锁
pthread_mutex_t fifo_mutex;
pthread_cond_t buff_ind_cond; //条件变量
pthread_t fifo_read_thread; //和python交互的线程

int reconfig_trigger=0, exit_flag=0; 
//reconfig_trigger用来判断是否要重新设置接收机 exit_flag是判断是否要退出程序
int noise_source_state = 0; //噪声源状态
int last_noise_source_state = 0; //上一个周期的噪声源状态

unsigned int read_buff_ind = 0; //这个变量实际没有用过

bool writeOrder[4]; //用来判断哪个通道已经读取完毕了

unsigned int writeCount = 0; //这个变量实际没有用过


void * fifo_read_tf(void* arg)
{
    /*          FIFO read thread function
    *
    */
    //这是与python交互的线程调用的函数

    uint8_t signal;//这是用来存储从python程序获得的变量
    int gain_read = GAIN; //存储4个通道的增益的变量
    int gain_read_2 = GAIN;
    int gain_read_3 = GAIN;
    int gain_read_4 = GAIN;

    int gain_read_array[4]; //存储4个通道增益的数组

    uint32_t center_freq_read = CENTER_FREQ, sample_rate_read = SAMPLE_RATE;
    //存储中心频率和采样率的变量
    FILE * fd = fopen(CFN, "r"); // FIFO descriptor//这是rtl_daq的控制fifo的文件句柄 
    (void)arg; 
    
    fprintf(stderr,"FIFO read thread is running\n");    
    if(fd!=0) //如果fifo文件句柄没问题就算fifo打开成功
        fprintf(stderr,"FIFO opened\n");
    else
        fprintf(stderr,"FIFO open error\n");
    while(!exit_flag){
        //这里是线程调用的循环
        fread(&signal, sizeof(signal), 1, fd); //先读出fifo里传过来的命令
        //pthread_mutex_lock(&buff_ind_mutex);    
    


        if( (uint8_t) signal == 2)
        {            
            //fprintf(stderr,"Signal2: FIFO read thread exiting \n");
            exit_flag = 1;           
            //这是终止程序的命令,获取到这个命令就把退出程序的变量设为1,所有循环都会停止
        }
        else
        {            
            reconfig_trigger=1;
            //rtl_daq只有2种控制命令,一种是退出,一种是设置接收机
            //所以如果不是退出程序就是要设置接收机,要把设置接收机变量设为1
        }


        if( (char) signal == 'r')
        {
            //这个命令对应于设置接收机的中心频率,采样率和增益
            //fprintf(stderr,"Signal 'r': Reconfiguring tuner \n");
            fread(¢er_freq_read, sizeof(uint32_t), 1, fd);
            fread(&sample_rate_read, sizeof(uint32_t), 1, fd);
            fread(&gain_read, sizeof(int), 1, fd);
            //一旦获取这个命令,就把后面紧接获取到的数据存入中心频率,采样率和增益对应的变量

            fread(&gain_read_2, sizeof(int), 1, fd);
            fread(&gain_read_3, sizeof(int), 1, fd);
            fread(&gain_read_4, sizeof(int), 1, fd);
            //增益有好几个,都按顺序和它们的长度读取并存储在本地变量中

            gain_read_array[0] = gain_read;
            gain_read_array[1] = gain_read_2;
            gain_read_array[2] = gain_read_3;
            gain_read_array[3] = gain_read_4;
            //再把增益从变量里存入一个增益数组
            
            //fprintf(stderr,"[ INFO ] Center freq: %u MHz\n", ((unsigned int) center_freq_read/1000000));
            //fprintf(stderr,"[ INFO ] Sample rate: %u MSps\n", ((unsigned int) sample_rate_read/1000000));
            //fprintf(stderr,"[ INFO ] Gain: %d dB\n",(gain_read/10));
            
            for(int i=0; idev_ind] = true; 
        //一旦进入这个回调函数,就说明这个接收机硬件读取完毕了
        //所以把这个设备号对应的writeOrder设置为高,对于这个设备已经可以往外写数据了
    	memcpy(rtl_rec->buffer, buf, len);
        //要把buf里的数据往这个设备对应结构体的缓存(rtl_rec->buffer)里写
        //根据读取到的数据长度len决定memcpy的长度
        
        //fprintf(stderr, "Read_buff_ind:%d, rtl_recbuff_ind:%d\n",rtl_rec->buff_ind, rtl_rec->buff_ind);
        pthread_cond_signal(&buff_ind_cond);
        //回调函数处理完成了,可以发出信号,程序其它部分可以做下一步操作
        //pthread_mutex_unlock(&buff_ind_mutex);
}



void *read_thread_entry(void *arg)
{
    //这是异步读取的线程对应的函数,4个接收机有4个这种线程在运行
    //fprintf(stderr, "[ DEBUG ] Pointer value %p\n", arg);
    struct rtl_rec_struct *rtl_rec = (struct rtl_rec_struct *) arg;// Set the thread's own receiver structure   
    //设置这个线程对应的接收机结构体
    //fprintf(stderr, "[ INFO ] Initializing RTL-SDR device, index:%d\n", rtl_rec->dev_ind);   
   
    rtlsdr_dev_t *dev = NULL;
    
    dev = rtl_rec->dev; //从接收机结构体中获得对应rtlsdr的句柄

    if (rtlsdr_set_dithering(dev, 0) !=0) // Only in keenerd's driver
    {
        fprintf(stderr, "[ ERROR ] Failed to disable dithering: %s\n", strerror(errno));
    }
    //dither是用较少的位数存储更多位的信息,方法是在最后几位加入噪音
    //现在把这个功能禁用了

    if (rtlsdr_set_tuner_gain_mode(dev, 1) !=0)
    {
        fprintf(stderr, "[ ERROR ] Failed to disbale AGC: %s\n", strerror(errno));
    }
    //手动指定增益,禁用AGC

    if (rtlsdr_reset_buffer(dev) !=0)
    {
        fprintf(stderr, "[ ERROR ] Failed to reset receiver buffer: %s\n", strerror(errno));
    }
    //重置接收机缓冲区
    
    while(!exit_flag)
    {
        //现在进入异步读取线程的循环
        //一旦进入循环就要设置所有硬件参数,这些参数都来自于rtl_rec结构体
        //在下面几行中这些参数会被传输给rtlsdr的api
        //CHECK1(rtlsdr_set_testmode(dev, 1)); // Set this to enable test mode
        if (rtlsdr_set_center_freq(dev, rtl_rec->center_freq) !=0)
        {
            fprintf(stderr, "[ ERROR ] Failed to set center frequency: %s\n", strerror(errno));
        }
        //设置中心频率
        
        if (rtlsdr_set_tuner_gain(dev, rtl_rec->gain) !=0)
        {
            fprintf(stderr, "[ ERROR ] Failed to set gain value: %s\n", strerror(errno));
        }   
        //设置调谐机增益

        if (rtlsdr_set_sample_rate(dev, rtl_rec->sample_rate) !=0)
        {
            fprintf(stderr, "[ ERROR ] Failed to set sample rate: %s\n", strerror(errno));
        }
        //设置采样率

        //fprintf(stderr, "[ DONE ] Device is initialized %d\n", rtl_rec->dev_ind);
        rtlsdr_read_async(dev, rtlsdrCallback, rtl_rec, ASYNC_BUF_NUMBER, BUFF_LEN);       
        //这是告知rtlsdr开始做异步读取了,把读取完毕后要调用的回调函数也给了api,
        //另外还包括要读取的设备的结构体dev,要读取的长度BUFF_LEN(根据程序启动时的参数指定)
        //ASYNC_BUF_NUMBER是固定的30,这是用户自己任意设置的
        //另外第3个参数是我们自己在rtl_rec.h写的结构体直接可以传给rtlsdr_read_async
        //因为这个上下文是用户可以自定义的
        //但是第1个参数dev必须是驱动定义的

        //这个rtlsdr_read_async调用了就会阻塞,直到被cancel为止
        //然后rtlsdrCallback就不停回调
        //所以如果要更改接收机硬件参数,必须要先调用rtlsdr_cancel_async
        //从这一点上也可以明白要重新设置硬件参数要先取消异步读取
    }
    
    
    fprintf(stderr, "[ INFO ] Device:%d handler thread exited\n", rtl_rec->dev_ind);

return NULL;
}

int main( int argc, char** argv )
{
    static char buf[262144 * 4 * 30];

    setvbuf(stdout, buf, _IOFBF, sizeof(buf));
    //设置缓冲模式
    fprintf(stderr, "[ INFO ] Starting multichannel coherent RTL-SDR receiver\n");


    writeOrder[0] = false;
    writeOrder[1] = false;
    writeOrder[2] = false;
    writeOrder[3] = false;
    //这是4个接收机对应的写入完毕的标志


    BUFF_LEN = (atoi(argv[1])/16) * 16384;
    //argv[1]是run.sh传进来的值,是256,16384/16=1024,所以BUFF_LEN是256*1024
    //这与其它几个程序都是统一的

    // Allocation
    rtl_receivers = malloc(sizeof(struct rtl_rec_struct)*NUM_CH);
    //这是在按照接收机的结构体大小和数量,创建接收机结构体的内存空间
    for(int i=0; idev_ind = i;        
        //然后每一个结构体的dev_ind可以指定为我们所需要的0,1,2,3了
        //对应于gnuradio里的rtl=0,rtl=1...
    }
   
    // Initialization
    for(int i=0; ibuff_ind=0;     //这个变量后来没有用过    
        rtl_rec->gain = GAIN;    //设置默认增益
        rtl_rec->center_freq = CENTER_FREQ; //设置默认中心频率
        rtl_rec->sample_rate = SAMPLE_RATE; //设置默认采样率
        rtl_rec->buffer = malloc(NUM_BUFF * BUFF_LEN * sizeof(uint8_t));
        //结构体对应缓存申请空间
        //这个空间就是BUFF_LEN对应的空间,NUM_BUFF在这个c程序里是1,就是说没有双倍缓冲
	    //rtlsdr_set_gpio(rtl_rec->dev, 0, 0);
      
        if(! rtl_rec->buffer)
        {
            fprintf(stderr, "[ ERROR ] Data buffer allocation failed. Exiting..\n");   
            exit(1);
            //如果缓冲创建失败就退出
        }
           
    }
    pthread_mutex_init(&buff_ind_mutex, NULL);
    //初始化互斥锁
    //这个互斥锁实际在程序里没怎么用上,因为只有一块区域加了锁,别的区域没有,等于没锁
    //如果两块区域加锁,进入一块加锁区域后,在解锁前,另一块加锁区域就无法进入
    //但是现在只有一块解锁区域,在解锁前,其它区域都可以随意进入,所以锁不锁住都没影响
    //只是为配合条件变量所以要加锁
    pthread_cond_init(&buff_ind_cond, NULL); 
    //初始化条件变量

    // we're going to test the "data_ready, exit_flag and reconfig_trigger" so we need the mutex for safety
    pthread_mutex_lock(&buff_ind_mutex);
    //这是互斥锁加锁区域的开头,只是测试使用和配合条件变量使用的
   
    for(int i=0; idev_ind) !=0)
    	{
       		//fprintf(stderr, "[ ERROR ] Failed to open RTL-SDR device: %s\n", strerror(errno));
    	}
    	rtl_rec->dev = dev;
        //获得句柄后再存入结构体对应位置
    }

/*
    for(int i=0; idev) !=0)
    	{
       		//fprintf(stderr, "[ ERROR ] Failed to open RTL-SDR device: %s\n", strerror(errno));
    	}
    }
*/

    int rc;
    pthread_attr_t attr;
    struct sched_param param;
    rc = pthread_attr_init (&attr);
    rc = pthread_attr_getschedparam(&attr, ¶m);
    //rc = pthread_attr_setschedpolicy(&attr, SCHED_RR);

    
    // Spawn reader threads
    for(int i=0; idev, 1, 0);
                  //如果噪声源状态是1,就应该打开噪声源,rtlsdr的gpio就要设置为1
	          else if (noise_source_state == 0)
	              rtlsdr_set_gpio(rtl_rec->dev, 0, 0);
                  //如果噪声源状态是0,gpio就设置为0
          }

          last_noise_source_state = noise_source_state;
          //设置完了要保存当前状态,用来与下一次的噪声源状态比较

          for(int i=0; i < NUM_CH; i++)
          {
              //数据已经准备好了,在设备结构体的缓存里了,就要准备把缓存往stdout输出了
              rtl_rec = &rtl_receivers[i];
              //按设备标号的顺序获取结构体
              fwrite(rtl_rec->buffer, BUFF_LEN, 1, stdout);
              //把结构体中的缓存,按照规定的长度BUFF_LEN输出到stdout中
              //stdout会把这4个接收机的数据传给下一个程序sync.c的stdin
              //fflush(stdout);
          }
          writeOrder[0] = false;
          writeOrder[1] = false;
          writeOrder[2] = false;
          writeOrder[3] = false;
          fflush(stdout);
          //传输完成后,重置4个标志位,等待下一次读取完成,清空stdout

          /* We need to reconfigure the tuner, so the async read must be stopped*/
          if(reconfig_trigger==1)
          {
            //如果python要求更改接收机硬件参数就必须先停止掉异步读取
            //这时由于已经确保上一次数据准备完毕并且发走了,停止读取不会影响数据完整性
            for(int i=0; idev) != 0)
    {
        fprintf(stderr, "[ ERROR ]  Async read stop failed: %s\n", strerror(errno));
        exit(1);
    }
    //退出时要取消4个rtl设备的异步读取
    fprintf(stderr, "[ INFO ] Async read stopped at device:%d\n",i);        
    pthread_join(rtl_rec->async_read_thread, NULL);
    //然后等待4个结构体中异步读取线程退出

    free(rtl_rec->buffer);
    //释放4个接收机结构体的缓存
    /* This is does not work currently, TODO: Close the devices properly
    if(rtlsdr_close(rtl_rec->dev) != 0)
    {
        fprintf(stderr, "[ ERROR ]  Device close failed: %s\n", strerror(errno));
        exit(1);
    }
    fprintf(stderr, "[ INFO ] Device closed with id:%d\n",i);
    */
  }
  pthread_mutex_unlock(&buff_ind_mutex);
  //取消线程锁,没实际用处
  pthread_join(fifo_read_thread, NULL);
  //等待fifo控制线程结束
  fprintf(stderr, "[ INFO ] All the resources are free now\n");
  //TODO: Free up buffers and join pthreads
  return 0;
}

关于互斥锁和信号量

参考:

https://baike.baidu.com/item/pthread_cond_signal/564029?fr=aladdin

https://blog.csdn.net/Mr_sunp/article/details/76889346

下一步就可以看sync了,分别是sync.h和sync.c

首先来看看sync.h

#include 
struct sync_buffer_struct { // Each channel has a circular buffer struct
	uint32_t delay;          //延迟
	uint8_t *circ_buffer;  // Circular buffer	//环形缓存
};
//这是同步缓存结构体,每个接收机通道都各有一个,包含一个环形缓存和这个通道的延迟

接下来看sync.c

/* KerberosSDR Sync
 *
 * Copyright (C) 2018-2019  Carl Laufer, Tamás Pető
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 *
 */

#include 
#include 
#include 
#include 
#include 
#include "sync.h"

//2097152 - Buff size for real work
//#define BUFFER_SIZE 128 * 1024 //128*1024 // Sample 
#define BUFFER_NO 2  // Buffer number //缓存数量,需要2段是因为要实现循环缓存
#define CHANNEL_NO 4 // Channel number //接收机通道数量,4个
#define CFN "_receiver/C/sync_control_fifo" // Name of the gate control fifo - Control FIFO name //这是控制sync程序的fifo 在这个文件内用CFN作为它的简称

int BUFFER_SIZE = 0; 缓存大小,在run.sh里会传入

/*
 *  Signal definitions: 
 * 
 * 
 *      
 * 
 */

static sem_t trigger_sem; //信号量
static int trigger; //用来触发、计数的变量
static int exit_flag = 0; //退出程序的标志位
static int delays[CHANNEL_NO]; //各通道的延迟
pthread_t fifo_read_thread;    //sync fifo的读取线程
void * fifo_read_tf(void* arg) 
{
/*          FIFO read thread function
 * 
 */ //这是python程序与这个sync程序交互使用的线程函数,它们使用sync fifo来传命令
    fprintf(stderr,"FIFO read thread is running\n");    
    (void)arg; 
    FILE * fd = fopen(CFN, "r"); // FIFO descriptor    //打开这个fifo文件
    if(fd!=0)
        fprintf(stderr,"FIFO opened\n");
    else
        fprintf(stderr,"FIFO open error\n");
    uint8_t signal; //这里的信号其实就是python程序发过来的命令
    while(1){
        fread(&signal, sizeof(signal), 1, fd);
        if( (uint8_t) signal == 1)
        {   //这里从来没有用到过,因为python程序不会发这个命令
            fprintf(stderr,"Signal1: trigger\n");
            trigger++;
            sem_wait(&trigger_sem);          
        }            
        else if( (uint8_t) signal == 2)
        {   //退出命令,收到这个命令,程序会退出
            fprintf(stderr,"Signal2: FIFO read thread exiting \n");
            exit_flag = 1;
            break;
        }
        else if( (char) signal == 'd')
        {   //这是python要给sync发送延迟大小的命令,收到这个命令后,紧接着4个就是4个通道的延迟
            //fprintf(stderr,"Signal 'd': Updating delay values \n");
            fread(delays, sizeof(*delays), 4, fd);
            //这一行把接下来读到的4个int值都存到delays数组里
            for(int m=0; m < CHANNEL_NO; m++)     //对每个通道单独做处理
                if(abs(delays[m]) < BUFFER_SIZE) //如果这个通道的延迟的绝对值小于128
                {
                    //fprintf(stderr,"[ INFO ] Channel %d, delay value %d\n",m,delays[m]);
                    delays[m] *=2; // I and Q !
                    //把通道数量*2,这可能是因为python信号处理代码中的延迟是按照复数算的
                    //但是这里的数组存的是实数iq值,iqiqiq,因此所有长度要翻倍
                    //那么在翻倍以前就要与默认的缓存长度256的一半比较
                    //后面的程序里会看到BUFFER_SIZE是外部传入的默认长度256的一半
                    //所以这个if分支里的意义就是如果延迟绝对值小于一个缓存长度就要继续使用它
                }
                else
                {
                    //这里else分支的意义是翻倍后的延迟绝对值大于一个一个缓存长度
                    //如果这样那么双缓存(2倍长度的缓存)就不足以实现循环缓存了
                    //所以这种延迟就要丢掉,设置为0
                    //fprintf(stderr,"[ ERROR ] Delay value over buffer size: %d, Setting to zero\n",delays[m]);
                    delays[m] = 0;
                    //fprintf(stderr,"[ WARNING ]Channel %d, delay value %d\n",m,delays[m]);
                }
            trigger++; // Set trigger for updating delay values
            //这里trigger变量从0变为了1,用来通知程序其它部分delays数组获取完毕了
            sem_wait(&trigger_sem); 
            //等待信号量,在其它部分发送释放信号之前,等在这里         

        }
    }
    fclose(fd);
    return NULL;
}


int main(int argc, char** argv)
{    

    static char buf[262144 * 4 * 30];
    //这个buf长度在rtl_daq,sync,gate里都一样
    //262144=256*1024很好理解,跟一个通道的iqiqiq缓存长度一样,4代表4个通道都连续存储在buf里
    //30不太知道为啥可能跟rtl_daq里的ASYNC_BUF_NUMBER有关系

    setvbuf(stdout, buf, _IOFBF, sizeof(buf));

    BUFFER_SIZE = (atoi(argv[1])/2) * 1024;
    //这里BUFFER_SIZE比rtl_daq和sync里的小,把输入值256除以2才乘以1024
    //另两个c程序里都直接是256*1024,我猜测这里是因为要跟复数运算出的delay结果比较
    //但是在这个程序的其它部分,使用BUFFER_SIZE的时候都乘上了2,那相当于其它部分不影响,抵消/2了

    int read_size; // Stores the read bytes from stdin 从stdin里读到的数据长度
    int first_read = 0; 
    //这个变量用来判断是不是刚打开程序后最开始读的4个通道的数据
    //它会从0变为1,再变为2,再变为3,直到开头4个通道数据读完m变为3以后它变为了4
    //再然后程序再读取4个通道数据后它就不会再增加了一直是4
    int write_index = BUFFER_NO-1; // Buffer index 0..BUFFER_NO-1//
    //这个变量有时是0,有时候是1
    //这是由于我们的循环缓存要输出延迟后的数据,相当于要在数组里跳过一些值再做输出
    //要保证输出的数据长度跟延迟大小无关,另外跳过的数据也要想办法也按一定次序输出掉
    //因此使用的方法实际上是用两倍于标准缓存的长度实现的
    //每个通道的循环缓存按照标准缓存长度分为2段
    //这2段的处理方式是不一样的
    //程序运行的时候,有时候4个通道的有用数据都在后半段那就先输出后半段,再返回输出前半段
    //另一些时候有用数据都在前半段,那缓存够长就直接输出了
    //要知道是在前半段还是后半段就用write_index是0还是1来区分
    //4个通道一次处理完成前write_index不会从0变为1,也不会从1变为0
    //处理完4个通道数据后才会变
    
    uint8_t *read_pointer; //这个指针用来把循环缓存里的数据写给stdout用的
    
    /* Initializing signal thread */ 
    sem_init(&trigger_sem, 0, 0);  // Semaphore is unlocked
    //初始化信号量
    trigger = 0;
    //初始化trigger变量,开头这个变量是0,获得delays后才变为1
   
    
    pthread_create(&fifo_read_thread, NULL, fifo_read_tf, NULL);
    //创建用来与python程序交互的fifo读取线程
   
    // Initializing delay buffers
    struct sync_buffer_struct* sync_buffers; //创建sync缓存结构体
    sync_buffers = malloc(CHANNEL_NO*sizeof(*sync_buffers));
    //这里申请了4倍的内存空间,也就是说4个通道的sync缓存结构体现在都创建好了并且是连续的内存空间
    for(int m=0;mcirc_buffer = malloc(BUFFER_NO*BUFFER_SIZE*2*sizeof(uint8_t)); // *2-> I and Q    
        //初始化每个sync缓存里的循环缓存
        //sync_buffers + m * sizeof(*sync_buffers)代表了指向第m通道的sync结构体的指针
        //这个结构体内还有一个指针指向它对应的circ_buffer,这个指针本身已经分配内存了
        //但是现在它指向的区域还没有分配内存,因此要给它再malloc一次
        //由于是循环缓存,缓存长度是正常的长度乘以BUFFER_NO,这里BUFFER_NO用了2
        //剩余的BUFFER_SIZE*2*sizeof(uint8_t)就是其它几个程序中正常的一条缓存的长度
        //它是(256/2)*2*sizeof(uint8_t)
        (sync_buffers + m * sizeof(*sync_buffers))->delay = BUFFER_SIZE;//BUFFER_SIZE/2;  
        //这里为什么要把每个通道里的默认delay设置为128还不清楚?这个值后期一直会有影响
        //fprintf(stderr,"ch: %d, Buff pointer: %p\n",m,(sync_buffers + m * sizeof(*sync_buffers))->circ_buffer);
    }
    
    fprintf(stderr,"Start delay sync test\n");
    while(!exit_flag)
    {      
        //主程序循环
        if(feof(stdin)) //如果读到结束符了,要退出主循环
            break;    
        
        write_index = (write_index+1)% BUFFER_NO;   
        //这个write_index是在主循环中的,4个通道读完了以后更新一次
        //在0,1,0,1之间切换,它决定了是连续输出还是分段输出
        //write_index初始化时是1,首次进入下面for循环时,更新为0         
        for(int m=0; mcirc_buffer +(BUFFER_SIZE*2*write_index)*sizeof(uint8_t), sizeof(uint8_t), (BUFFER_SIZE*2), stdin);           
            //首次从stdin中往各通道的循环缓存中存入数据
            //由于循环缓存是两倍正常长度(block_size),方便起见,我们用左右来区分
            //第一次进for循环write_index是0,那就是往4个接收机通道的循环缓存的左半部分存入  
            //4个通道处理完以后,第二次运行主循环,这时write_index变为1
            //stdin上读到的4个接收机通道数据会存在它们对应的循环缓存的右半部分
            //第三次运行主循环,又会更新左半部分
            if(read_size == 0)
            {   //如果读不到东西说明出错了,要退出程序
                //fprintf(stderr,"Read error at channel: %d, wr_index: %d\n",m, write_index);
                exit_flag = 1;
                break;
            }
                
            /* Write from delay pointer*/ 
            int delay = (sync_buffers + m * sizeof(*sync_buffers))->delay; 
            //从目前正在处理的通道的sync缓存中读出它的延迟,用于向stdout输出               
            if(first_read == CHANNEL_NO)
            {
                //first_read最开始是0,然后主循环第一次运行
                //4个接收机在这里的first_read会从0递增到3,而CHANNEL_NO=4所以都进不了这里
                //下次主循环运行,4个接收机的第一个通道在处理时first_read变为4
                //才会进入这个分支,进入这个分支后first_read不会再增加一直是4了
                //后续每次运行都会进这个分支
                //所以这个变量叫作first_read,它的作用是判断当前是否是程序初次打开后
                //4个通道的首次读取,因为首次读取只读完了循环缓存的左半部分,没有有半部分
                //这样输出会有问题
                
                //接下来的write_index用来判断的实际意义是
                //从左半部分缓存开始读取
                //还是先从右半部分缓存开始读取,读完了再读左半部分
                //首次write_index=0的时候虽然能进for循环,但是进不了这里
                //因为first_read把首次for循环的4次给排除掉了
                //然后做了一次主循环,这时write_index已经变为1了才进入这里
                //与此同时整个左半部分右半部分缓存已经都写完了,左边老数据右边新数据
                //这时就该从左往右读数据
                //4次for循环读完,再后面一次经过主循环来到这里,就是左边新右边老了
                //这时要从右边开始读取,读完了再从左边读,而此时write_index正好也是0
                //下面这两段程序做的正好实现了要做的操作
                if(write_index == 1)
                {
                    //在这里对应左边老右边新的状态           
                    read_pointer = (sync_buffers + m * sizeof(*sync_buffers))->circ_buffer + sizeof(uint8_t) * delay;
                    //实际的操作是先从左边缓存经过delay大小的延迟,得到读取的指针
                    fwrite(read_pointer , sizeof(uint8_t), BUFFER_SIZE*2, stdout);
                    //然后从这个指针开始输出长度为BUFFER_SIZE*2的数据
                    //也就是其它几个程序处理的标准block_size=256
                    fflush(stdout);
                }
                else // Write index must be 0
                {
                    //在这里对应左边新右边老的状态
                    //这种情况下分两段输出
                    // Write first chunk
                    read_pointer = (sync_buffers + m * sizeof(*sync_buffers))->circ_buffer + sizeof(uint8_t) * (delay+BUFFER_SIZE*2);
                    //在右半边经过delay延迟,所以circ_buffer指针先要加BUFFER_SIZE*2=256
                    //这样先来到右半部分的缓存,然后再加上delay个位置的延迟,这样延迟就完成了
                    fwrite(read_pointer , sizeof(uint8_t), (BUFFER_SIZE*2-delay), stdout);            
                    //这样就可以从read_pointer开始读取并往stdout输出数据了
                    //由于现在只有右半边的循环缓存,比较短,加上经过了delay个延迟
                    //那么整个缓存只剩下BUFFER_SIZE*2-delay了(就是右半边缓存减去delay)
                    //只能输出这个长度的数据
                    //剩下的数据要从左半边开始输出
                 
                    //Write second chunk
                    read_pointer = (sync_buffers + m * sizeof(*sync_buffers))->circ_buffer;
                    //左半边从时间上来讲是紧接右半边最后一个数据的,所以直接从头输出就行
                    fwrite(read_pointer , sizeof(uint8_t), delay, stdout);
                    //输出的长度只需补上之前那一段少掉的部分就行,相比标准长度少了延迟的那一段
                    //所以要输出的就是delay长的数据
                    fflush(stdout);
                }      
                //注意这里说的左半边右半边,说的是整个循环缓存,它的长度是2*256=512,
                //半边也有256长,相当于一个完整的block_size
                //如果delay比较大,那么从左往右输出和从右输出完再从左输出是否会有重复输出情况?             
            }
            else
                //这里对应的是没有进入上一个分支的情况,代表所有4个通道还只有左半部分有数据
                //这样就自增first_read,直到它达到4之后,再也不会进入这个分支了
                first_read ++; 
                
            /* Log */                
            //fprintf(stderr,"Channel: %d, wr_index: %d\n",m,write_index);
            //fprintf(stderr,"Delay %d\n", delay);
                

        }

        /* Trigger receiver, updating delay values*/
        if(trigger == 1)
        {          
            //一旦trigger=1说明python程序通过控制fifo发来新的delay了
            for(int k=0; kdelay += delays[k];
                //这时要把delays数组里4个量更新到4个接收机的sync缓存里的delay里
                delays[k] = 0;
                //更新完了可以把delays数组清空掉,等待下一次读取
            }
            trigger --;
            //一旦收到的4个delay都保存完了,就没必要再次设置sync数组,这里没必要再进来
            //trigger就可以变为0了,直到下一次收到trigger==1才再设置
            sem_post(&trigger_sem);
            //这个信号量表示4个delay设置完毕,可以从控制fifo接收新的delay了
            //如果上次收到的4个delay没接受完毕,这个信号不会发出去,那么控制fifo是阻塞的
            //不会再收到新的delay,防止混起来
        }


        if(exit_flag)  //如果退出的标志位为1就结束主循环
            break;    
        
    }     
    
    /* Free up buffers */ 
    for(int m=0;mcirc_buffer);   
        //如果结束主循环,就要把4个sync缓存里指向的4个循环缓存里的空间释放出来  
    }
   
    fprintf(stderr,"[ EXIT ] Sync block exited\n");
    return 0;
    
}

信号量可以参考:

https://www.cnblogs.com/zhengAloha/p/8665719.html

接下来讲gate.c,这个程序比较简单用来决定什么时候输出数据给python的receiver程序的。它也是唯一一个会发数据给python的c程序,其它几个c程序只是接收python的命令,不发数据给python程序。

这个程序顾名思义就是一个门,根据python程序的要求,打开它的时候,数据就送入hydra_receiver.py,如果关闭它,数据就扔掉。

/* KerberosSDR Gate
 *
 * Copyright (C) 2018-2019  Carl Laufer, Tamás Pető
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 *
 */

#include 
#include 
#include 
#include 
#include 


//#define BUFFER_SIZE 1024 * 128 * 4//1024*256*4
#define CFN "_receiver/C/gate_control_fifo" // Name of the gate control fifo - Control FIFO name
//这是gate对应的控制fifo,在这里简称为CFN

//#define BUFFER_SIZE 256 * 1024 * 4

int BUFFER_SIZE = 0;
//BUFFER_SIZE,程序启动的时候会传入

static sem_t trigger_sem;
//信号量

static volatile int trigger=0, exit_flag=0; 
//两个标志位,trigger用来控制开门,exit_flag用来控制是否退出程序
pthread_t fifo_read_thread; //控制fifo的读取线程   
void * fifo_read_tf(void* arg) 
{
/*          FIFO read thread function
 * 
 */
    //FIFO的读取线程对应的函数
    fprintf(stderr,"FIFO read thread is running\n");    
    FILE * fd = fopen(CFN, "r"); // FIFO descriptor //打开gate对应的控制FIFO的文件
    if(fd!=0) //判断文件是否打开失败
        fprintf(stderr,"FIFO opened\n");
    else
        fprintf(stderr,"FIFO open error\n");
    uint8_t trigger_read;//存储读取到的命令的变量
    while(1){
        fread(&trigger_read, sizeof(trigger_read), 1, fd);
        //从控制fifo的文件里读取命令,这里第一个参数是trigger_read的地址
        //相当于指向它的指针,那么读到的内容赋值到trigger_read变量上了
        if( (uint8_t) trigger_read == 1)
        {
            //如果读到的命令是1,表示要开门
            //fprintf(stderr,"Trigger received\n");
            trigger++;
            //如果要开门trigger变量就从0变为1
            sem_wait(&trigger_sem);    
            //然后阻塞在这里,在收到信号量之前不会再去检测控制fifo的命令      
        }            
        else if( (uint8_t) trigger_read == 2)
        {
            //如果读到的命令是2,表示要退出
            fprintf(stderr,"[ EXIT ] FIFO read thread exiting \n");
            exit_flag = 1; //把退出的标志位设为1
            break;  //退出当前线程的循环
        }
    }
    fclose(fd); //退出线程函数前关闭文件句柄
    return NULL;
}


int main(int argc, char** argv)
{

    static char buf[262144 * 4 * 30];
    //这个缓存在所有c程序里一样大
    setvbuf(stdout, buf, _IOFBF, sizeof(buf));


    BUFFER_SIZE = atoi(argv[1]) * 1024 * 4;
    //BUFFER_SIZE是256*1024*4,是标准长度*4,因为把4个接收机的数据都放在一起了
    int read_size; //实际读取到的文件长度
    uint8_t * buffer;    //读取和输出用的缓存

    sem_init(&trigger_sem, 0, 0);  // Semaphore is unlocked //初始化信号量   
    pthread_create(&fifo_read_thread, NULL, fifo_read_tf, NULL);
    //创建读取控制fifo的线程
    
    // Allocate sample buffer
   // uint8_t buffer[BUFFER_SIZE];
    buffer= malloc(BUFFER_SIZE*sizeof(uint8_t));//按照设定的长度创建内存空间

    fprintf(stderr,"Start gate control\n");    
    while(1)
    {        
        if(feof(stdin))
            break;
        
        read_size = fread(buffer,sizeof(*buffer), BUFFER_SIZE, stdin);
        //不管门的状态如何,都不停从stdin也就是sync的输出中读取

        //read_size = read(STDIN_FILENO, buffer, BUFFER_SIZE);        
        if(read_size>0)
        { //如果读取的数据没问题
            if(trigger == 1)
            { 
                //如果门打开
                fwrite(buffer, sizeof(*buffer), read_size, stdout);
                //把buffer里的数据输出给stdout,也就是输出给hydra_receiver.py
                //这个开门操作是这个python代码中的download_iq_samples函数控制的
                //这个download_iq_samples函数被调用一下就会开一次门
                fflush(stdout);
                trigger --;
                //输出完毕要把门关掉,直到下次开门的命令才会再输出
                sem_post(&trigger_sem);
                //这次操作完了就可以发出信号,让fifo处理线程继续接收命令了
            }            
            else
            {
                //如果没开门,就不输出,不停读取新数据,相当于把老的采样扔掉
                //fprintf(stderr,"No trigger, dropping %d samples..\n", read_size);
            }
            
            if(exit_flag) //如果退出标志位为1,就退出主循环
              break;  
            
        }
        
    }    
    pthread_join(fifo_read_thread, NULL); //等待控制fifo解析线程结束
    sem_destroy(&trigger_sem); //销毁信号量
    fprintf(stderr,"[ EXIT ] Gate control exited\n");
    return 0;
    
}

rtl_daq.c里还调用了几个rtlsdr的函数,这里找到了kerberossdr修改的rtlsdr驱动,可以粗略看一下

rtl-sdr.h

https://github.com/rtlsdrblog/rtl-sdr-kerberos/blob/01d5a884ea297d8ee9a4eec2dcf8dd6e40bc51ba/include/rtl-sdr.h

其它几个设置rtlsdr硬件参数的函数都比较简单,最终都在调用libusb_control_transfer函数

这里主要摘录了比较复杂的读取数据有关的函数

/*!
 * Read samples from the device asynchronously. This function will block until
 * it is being canceled using rtlsdr_cancel_async()
 * //异步读取采样点,这个函数会阻塞,直到rtlsdr_cancel_async被调用为止
 * \param dev the device handle given by rtlsdr_open() //rtlsdr_dev_t 硬件句柄
 * \param cb callback function to return received samples //回调函数
 * \param ctx user specific context to pass via the callback function //传给回调的参数
 * \param buf_num optional buffer count, buf_num * buf_len = overall buffer size
 *		  set to 0 for default buffer count (15) //这是我们设置的30
 * \param buf_len optional buffer length, must be multiple of 512,
 *		  should be a multiple of 16384 (URB size), set to 0
 *		  for default buffer length (16 * 32 * 512) //这里是256*1024
 * \return 0 on success
 */
RTLSDR_API int rtlsdr_read_async(rtlsdr_dev_t *dev,
				 rtlsdr_read_async_cb_t cb,
				 void *ctx,
				 uint32_t buf_num,
				 uint32_t buf_len);

/*!  //这是取消异步读取用的函数
 * Cancel all pending asynchronous operations on the device.
 *
 * \param dev the device handle given by rtlsdr_open()
 * \return 0 on success
 */
RTLSDR_API int rtlsdr_cancel_async(rtlsdr_dev_t *dev);

接下来看librtlsdr.c

 https://github.com/rtlsdrblog/rtl-sdr-kerberos/blob/6b446a860e434d09d190c9e5c898646538710c6e/src/librtlsdr.c

这里只放了几个关键的函数

static void LIBUSB_CALL _libusb_callback(struct libusb_transfer *xfer)
{ //这是libusb的标准回调函数
	rtlsdr_dev_t *dev = (rtlsdr_dev_t *)xfer->user_data; //从参数里获得设备句柄

	if (LIBUSB_TRANSFER_COMPLETED == xfer->status) { //如果传输完成
		if (dev->cb) //就可以调用用户自定义的回调了,可以看到顺序和rtlsdrCallback一样
			dev->cb(xfer->buffer, xfer->actual_length, dev->cb_ctx);
            //其中xfer->buffer是xfer自带的缓存,不是rtl_daq.c创建的

		libusb_submit_transfer(xfer); /* resubmit transfer */ //再次要求传输
		dev->xfer_errors = 0;
	} else if (LIBUSB_TRANSFER_CANCELLED != xfer->status) {
#ifndef _WIN32
		if (LIBUSB_TRANSFER_ERROR == xfer->status)
			dev->xfer_errors++;

		if (dev->xfer_errors >= dev->xfer_buf_num ||
		    LIBUSB_TRANSFER_NO_DEVICE == xfer->status) {
#endif
			dev->dev_lost = 1;
			rtlsdr_cancel_async(dev);
			fprintf(stderr, "cb transfer status: %d, "
				"canceling...\n", xfer->status);
#ifndef _WIN32
		}
#endif
	}
}



static int _rtlsdr_alloc_async_buffers(rtlsdr_dev_t *dev)
{
	unsigned int i;

	if (!dev)
		return -1;

	if (!dev->xfer) { //要创建30个libusb_transfer
		dev->xfer = malloc(dev->xfer_buf_num *
				   sizeof(struct libusb_transfer *));

		for(i = 0; i < dev->xfer_buf_num; ++i)
			dev->xfer[i] = libusb_alloc_transfer(0);//初始化这些transfer
	}

	if (dev->xfer_buf)
		return -2;
                   
	dev->xfer_buf = malloc(dev->xfer_buf_num * sizeof(unsigned char *));
    //创建30个指向buf的指针
	memset(dev->xfer_buf, 0, dev->xfer_buf_num * sizeof(unsigned char *));
    //这些指针的初始化

/*#if defined (__linux__) && LIBUSB_API_VERSION >= 0x01000105
	fprintf(stderr, "Allocating %d zero-copy buffers\n", dev->xfer_buf_num);
	dev->use_zerocopy = 1;
	for (i = 0; i < dev->xfer_buf_num; ++i) {
		dev->xfer_buf[i] = libusb_dev_mem_alloc(dev->devh, dev->xfer_buf_len);
        //这些缓存都要创建内存空间,尝试创建zero-copy内存空间
		if (!dev->xfer_buf[i]) { //如果创建缓存失败,就要回到用户空间创建缓存
			fprintf(stderr, "Failed to allocate zero-copy "
					"buffer for transfer %d\nFalling "
					"back to buffers in userspace\n", i);
			dev->use_zerocopy = 0;   
			break;
		}
	}
	// zero-copy buffer allocation failed (partially or completely)
	// we need to free the buffers again if already allocated
	if (!dev->use_zerocopy) {
		for (i = 0; i < dev->xfer_buf_num; ++i) {
			if (dev->xfer_buf[i]) //一部分缓存创建失败,要把别的成功的缓存释放掉
				libusb_dev_mem_free(dev->devh,
						    dev->xfer_buf[i],
						    dev->xfer_buf_len);
		}
	}
#endif*/

	/* no zero-copy available, allocate buffers in userspace */
	if (!dev->use_zerocopy) {
		for (i = 0; i < dev->xfer_buf_num; ++i) {
			dev->xfer_buf[i] = malloc(dev->xfer_buf_len);
            //在用户空间创建缓存
			if (!dev->xfer_buf[i])
				return -ENOMEM;
		}
	}

	return 0;
}

static int _rtlsdr_free_async_buffers(rtlsdr_dev_t *dev)
{
	unsigned int i;

	if (!dev)
		return -1;

	if (dev->xfer) {
		for(i = 0; i < dev->xfer_buf_num; ++i) {
			if (dev->xfer[i]) {
				libusb_free_transfer(dev->xfer[i]); 把dev->xfer都释放掉
			}
		}

		free(dev->xfer);
		dev->xfer = NULL;
	}

	if (dev->xfer_buf) {
		for (i = 0; i < dev->xfer_buf_num; ++i) {
			if (dev->xfer_buf[i]) {
				if (dev->use_zerocopy) {//如果使用zerocopy,就用libusb来释放xfer_buf
/*#if defined (__linux__) && LIBUSB_API_VERSION >= 0x01000105
					libusb_dev_mem_free(dev->devh,
							    dev->xfer_buf[i],
							    dev->xfer_buf_len);
#endif*/
				} else {//否则在用户空间释放xfer_buf
					free(dev->xfer_buf[i]); 
				}
			}
		}

		free(dev->xfer_buf); //把指向dev->xfer_buf的指针也释放
		dev->xfer_buf = NULL;
	}

	return 0;
}

int rtlsdr_read_async(rtlsdr_dev_t *dev, rtlsdr_read_async_cb_t cb, void *ctx,
			  uint32_t buf_num, uint32_t buf_len)
{
	unsigned int i;
	int r = 0;
	struct timeval tv = { 1, 0 };
	struct timeval zerotv = { 0, 0 };
	enum rtlsdr_async_status next_status = RTLSDR_INACTIVE;

	if (!dev)
		return -1;

	if (RTLSDR_INACTIVE != dev->async_status)
		return -2;

	dev->async_status = RTLSDR_RUNNING;
	dev->async_cancel = 0;

	dev->cb = cb; //用户自定义的回调函数传到这里来
	dev->cb_ctx = ctx; //这是用户自定义的要传给回调函数的参数

	if (buf_num > 0)
		dev->xfer_buf_num = buf_num; //30
	else
		dev->xfer_buf_num = DEFAULT_BUF_NUMBER; 

	if (buf_len > 0 && buf_len % 512 == 0) /* len must be multiple of 512 */
		dev->xfer_buf_len = buf_len; //256*1024
	else
		dev->xfer_buf_len = DEFAULT_BUF_LENGTH;

	_rtlsdr_alloc_async_buffers(dev); //创建用于异步读取的缓存

	for(i = 0; i < dev->xfer_buf_num; ++i) {
        //设置libusb,用来做传输
		libusb_fill_bulk_transfer(dev->xfer[i],
					  dev->devh,
					  0x81,
					  dev->xfer_buf[i],
					  dev->xfer_buf_len,
					  _libusb_callback, //这是给libusb的回调函数
					  (void *)dev,
					  BULK_TIMEOUT);
        //设置完了要发送
		r = libusb_submit_transfer(dev->xfer[i]);
		if (r < 0) {
			fprintf(stderr, "Failed to submit transfer %i\n"
					"Please increase your allowed " 
					"usbfs buffer size with the "
					"following command:\n"
					"echo 0 > /sys/module/usbcore"
					"/parameters/usbfs_memory_mb\n", i);
			dev->async_status = RTLSDR_CANCELING;
			break;
		}
	}

	while (RTLSDR_INACTIVE != dev->async_status) {
		r = libusb_handle_events_timeout_completed(dev->ctx, &tv,
							   &dev->async_cancel);
		if (r < 0) {
			/*fprintf(stderr, "handle_events returned: %d\n", r);*/
			if (r == LIBUSB_ERROR_INTERRUPTED) /* stray signal */
				continue;
			break;
		}

		if (RTLSDR_CANCELING == dev->async_status) { //这里是打算退出异步读取了
			next_status = RTLSDR_INACTIVE; //那么下一个状态就是非活动状态了

			if (!dev->xfer)
				break;  

			for(i = 0; i < dev->xfer_buf_num; ++i) {
				if (!dev->xfer[i])
					continue;

				if (LIBUSB_TRANSFER_CANCELLED !=
						dev->xfer[i]->status) { //如果libusb还没有取消,就调用取消函数
					r = libusb_cancel_transfer(dev->xfer[i]); 
					/* handle events after canceling
					 * to allow transfer status to
					 * propagate */ //还要把没完成的任务做完?
					libusb_handle_events_timeout_completed(dev->ctx,
									       &zerotv, NULL);
					if (r < 0)
						continue;

					next_status = RTLSDR_CANCELING;
                    //还需要等待,下一个状态还是cancelling
				}
			}

			if (dev->dev_lost || RTLSDR_INACTIVE == next_status) {
				/* handle any events that still need to
				 * be handled before exiting after we
				 * just cancelled all transfers */
				libusb_handle_events_timeout_completed(dev->ctx,
								       &zerotv, NULL);
				break;
			}
		}
	}

	_rtlsdr_free_async_buffers(dev); //释放缓存

	dev->async_status = next_status;

	return r;
}

int rtlsdr_cancel_async(rtlsdr_dev_t *dev)
{
	if (!dev)
		return -1;

	/* if streaming, try to cancel gracefully */ //如果还在工作,就改状态为cancelling
	if (RTLSDR_RUNNING == dev->async_status) {
		dev->async_status = RTLSDR_CANCELING;
		dev->async_cancel = 1;
		return 0;
	}

	/* if called while in pending state, change the state forcefully */
#if 0
	if (RTLSDR_INACTIVE != dev->async_status) {
		dev->async_status = RTLSDR_INACTIVE;
		return 0;
	}
#endif
	return -2;
}

我本来以为异步读取rtlsdr_read_async函数调用一次,rtlsdrCallback就会返回一次,并且rtlsdr_read_async也会返回,但是根据rtl-sdr.h的描述并不会,而是一直阻塞着,直到调用rtlsdr_cancel_async函数为止,要实际调试看一下,但是现在每个接收机硬件的rtlsdr_read_async都有单独的读取线程,即使阻塞了也不影响程序其它部分的运行。

前几天发现了一个奇怪的现象:

https://www.bilibili.com/video/av85636140

如果开同步显示,但是不点开噪声源,这时候采样时间延迟是随机的,开了噪声源后才变成一个非零常数。

我今天又用ubuntu电脑直接接kerberossdr试过了,没有这个问题,一打开同步显示,采样时间延迟立马就是非零常数了,可能是树莓派或者网页的bug。

不过我发现采样时间同步和相位同步做完后把噪声源关掉但是不关同步显示,采样时间延迟保持0,但是相位差会从0变为一个比较小的偏差,可能因为虽然硬件上校准好了,但是没有噪声源,4个接收机还是收到了一些微弱的不同的信号,造成了有一些相位差,应该是正常现象,收到的信号本身就不同了,有相位差没问题的。

你可能感兴趣的:(KerberosSDR)