【KrakenSDR】MATLAB接口

KrakenSDRClient.m

bash 复制代码
classdef KrakenSDRClient < handle
    properties
        ip_addr = '127.0.0.1'
        data_port = 5000
        ctrl_port = 5001
        num_channels = 5
        freq_hz
        gain
        debug = false
        timeout = 5.0

        valid_gains = [ ...
            0, 0.9, 1.4, 2.7, 3.7, 7.7, 8.7, 12.5, 14.4, 15.7, ...
            16.6, 19.7, 20.7, 22.9, 25.4, 28.0, 29.7, 32.8, 33.8, ...
            36.4, 37.2, 38.6, 40.2, 42.1, 43.4, 43.9, 44.5, 48.0, 49.6];

        connected = false

        data_client
        ctrl_client

        iq_header
    end

    properties (Constant)
        FRAME_TYPE_DATA  = 0
        FRAME_TYPE_DUMMY = 1
        FRAME_TYPE_RAMP  = 2
        FRAME_TYPE_CAL   = 3
        FRAME_TYPE_TRIGW = 4
        SYNC_WORD = uint32(hex2dec('2BF7B95A'))
        HEADER_SIZE = 1024
        RESERVED_BYTES = 192
    end

    methods
        function obj = KrakenSDRClient(ip_addr, data_port, ctrl_port, num_channels, freq_mhz, gain, debug_flag, timeout)
            if nargin >= 1 && ~isempty(ip_addr), obj.ip_addr = ip_addr; end
            if nargin >= 2 && ~isempty(data_port), obj.data_port = data_port; end
            if nargin >= 3 && ~isempty(ctrl_port), obj.ctrl_port = ctrl_port; end
            if nargin >= 4 && ~isempty(num_channels), obj.num_channels = num_channels; end
            if nargin >= 5 && ~isempty(freq_mhz)
                obj.freq_hz = uint64(freq_mhz * 1e6);
            else
                obj.freq_hz = uint64(416.588 * 1e6);
            end
            if nargin >= 6 && ~isempty(gain)
                obj.gain = obj.normalize_gain(gain);
            else
                obj.gain = obj.normalize_gain(10.0);
            end
            if nargin >= 7 && ~isempty(debug_flag), obj.debug = logical(debug_flag); end
            if nargin >= 8 && ~isempty(timeout), obj.timeout = timeout; end

            obj.iq_header = struct();
        end

        function gain_list = normalize_gain(obj, gain_in)
            if isempty(gain_in)
                gain_list = 10.0 * ones(1, obj.num_channels);
            elseif isnumeric(gain_in) && isscalar(gain_in)
                gain_list = double(gain_in) * ones(1, obj.num_channels);
            elseif isnumeric(gain_in) && isvector(gain_in)
                if numel(gain_in) ~= obj.num_channels
                    error('gain list length must be %d', obj.num_channels);
                end
                gain_list = double(gain_in(:)).';
            else
                error('gain must be a scalar or a numeric vector');
            end
        end

        function connect(obj)
            if obj.connected
                return;
            end

            obj.data_client = tcpclient(obj.ip_addr, obj.data_port, 'Timeout', obj.timeout);
            write(obj.data_client, uint8('streaming'), 'uint8');

            obj.ctrl_client = tcpclient(obj.ip_addr, obj.ctrl_port, 'Timeout', obj.timeout);

            obj.connected = true;

            obj.send_control_command([uint8('INIT'), zeros(1,124,'uint8')]);
            obj.set_center_freq(obj.freq_hz);
            obj.set_if_gain(obj.gain);
        end

        function close(obj)
            if obj.connected
                try
                    write(obj.data_client, uint8('q'), 'uint8');
                catch
                end

                try
                    write(obj.ctrl_client, [uint8('EXIT'), zeros(1,124,'uint8')], 'uint8');
                catch
                end
            end

            obj.data_client = [];
            obj.ctrl_client = [];
            obj.connected = false;
        end

        function delete(obj)
            obj.close();
        end

        function send_control_command(obj, msg_bytes)
            if ~obj.connected
                error('KrakenSDR is not connected');
            end

            write(obj.ctrl_client, uint8(msg_bytes), 'uint8');
            reply = obj.recv_exact(obj.ctrl_client, 128);

            status = char(reply(1:4));
            if ~strcmp(status, 'FNSD')
                error('Control command failed, reply=%s', status);
            end
        end

        function set_center_freq(obj, freq_hz)
            if ~obj.connected
                error('KrakenSDR is not connected');
            end

            obj.freq_hz = uint64(freq_hz);

            freq_bytes = typecast(uint64(obj.freq_hz), 'uint8');   % little-endian
            cmd = [uint8('FREQ'), freq_bytes, zeros(1,116,'uint8')];

            obj.send_control_command(cmd);
        end

        function set_if_gain(obj, gain_in)
            if ~obj.connected
                error('KrakenSDR is not connected');
            end

            gain_list = obj.normalize_gain(gain_in);
            obj.gain = gain_list;

            clipped = zeros(1, obj.num_channels, 'uint32');
            for k = 1:obj.num_channels
                [~, idx] = min(abs(obj.valid_gains - gain_list(k)));
                closest = obj.valid_gains(idx);
                clipped(k) = uint32(round(closest * 10));
            end

            gain_bytes = typecast(clipped, 'uint8');  % little-endian
            cmd = [uint8('GAIN'), gain_bytes, zeros(1, 128 - (obj.num_channels + 1)*4, 'uint8')];

            obj.send_control_command(cmd);
        end

        function bytes = recv_exact(~, client, nbytes)
            bytes = zeros(1, nbytes, 'uint8');
            received = 0;

            while received < nbytes
                available = client.NumBytesAvailable;
                if available <= 0
                    pause(0.001);
                    continue;
                end

                to_read = min(nbytes - received, available);
                chunk = read(client, to_read, 'uint8');
                bytes(received + 1 : received + numel(chunk)) = chunk;
                received = received + numel(chunk);
            end
        end

        function header = decode_header(obj, header_bytes)
            if numel(header_bytes) ~= obj.HEADER_SIZE
                error('Invalid header size');
            end

            p = 1;

            header.sync_word         = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
            header.frame_type        = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;

            hw = char(header_bytes(p:p+15));
            header.hardware_id       = deblank(char(hw(hw~=0))); p = p + 16;

            header.unit_id           = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
            header.active_ant_chs    = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
            header.ioo_type          = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;

            header.rf_center_freq    = typecast(uint8(header_bytes(p:p+7)), 'uint64'); p = p + 8;
            header.adc_sampling_freq = typecast(uint8(header_bytes(p:p+7)), 'uint64'); p = p + 8;
            header.sampling_freq     = typecast(uint8(header_bytes(p:p+7)), 'uint64'); p = p + 8;

            header.cpi_length        = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
            header.time_stamp        = typecast(uint8(header_bytes(p:p+7)), 'uint64'); p = p + 8;
            header.daq_block_index   = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
            header.cpi_index         = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
            header.ext_integration_cntr = typecast(uint8(header_bytes(p:p+7)), 'uint64'); p = p + 8;

            header.data_type         = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
            header.sample_bit_depth  = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
            header.adc_overdrive_flags = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;

            header.if_gains = zeros(1, 32, 'uint32');
            for k = 1:32
                header.if_gains(k) = typecast(uint8(header_bytes(p:p+3)), 'uint32');
                p = p + 4;
            end

            header.delay_sync_flag   = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
            header.iq_sync_flag      = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
            header.sync_state        = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
            header.noise_source_state= typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;

            % 跳过 reserved 区
            p = p + obj.RESERVED_BYTES * 4;

            header.header_version    = typecast(uint8(header_bytes(p:p+3)), 'uint32');
        end

        function dump_header(~, header)
            fprintf('--- IQ Header Info ---\n');
            fprintf('Sync word: %u | Version: %u\n', header.sync_word, header.header_version);
            fprintf('Frame type: %u | Unit ID: %u\n', header.frame_type, header.unit_id);
            fprintf('RF Freq: %.2f MHz | IQ Freq: %.2f MHz\n', ...
                double(header.rf_center_freq)/1e6, double(header.sampling_freq)/1e6);
            fprintf('Channels: %u | CPI Length: %u\n', header.active_ant_chs, header.cpi_length);
            fprintf('Sample bit depth: %u\n', header.sample_bit_depth);
            fprintf('----------------------\n');
        end

        function [iq, header] = receive_iq_frame(obj)
            if ~obj.connected
                error('KrakenSDR is not connected');
            end

            header_bytes = obj.recv_exact(obj.data_client, obj.HEADER_SIZE);
            header = obj.decode_header(header_bytes);
            obj.iq_header = header;

            if obj.debug
                obj.dump_header(header);
            end

            payload_size = double(header.cpi_length) * double(header.active_ant_chs) * 2 * double(header.sample_bit_depth) / 8;

            if payload_size <= 0
                iq = [];
                return;
            end

            payload = obj.recv_exact(obj.data_client, payload_size);

            % Python 版本按 complex64 解释
            % 即每个复数样本 = float32 I + float32 Q
            float_data = typecast(uint8(payload), 'single');

            if mod(numel(float_data), 2) ~= 0
                error('Payload cannot be parsed into complex float32 pairs');
            end

            I = float_data(1:2:end);
            Q = float_data(2:2:end);
            iq_complex = complex(I, Q);

            iq = reshape(iq_complex, double(header.cpi_length), double(header.active_ant_chs)).';
        end

        function [iq, header] = get_iq_once(obj)
            if ~obj.connected
                obj.connect();
            end

            write(obj.data_client, uint8('IQDownload'), 'uint8');
            [iq, header] = obj.receive_iq_frame();
        end

        function [iq, header] = get_data_frame(obj, max_retry)
            if nargin < 2
                max_retry = 20;
            end

            if ~obj.connected
                obj.connect();
            end

            for k = 1:max_retry
                write(obj.data_client, uint8('IQDownload'), 'uint8');
                [iq, header] = obj.receive_iq_frame();

                if header.frame_type == obj.FRAME_TYPE_DATA
                    return;
                end
            end

            error('Failed to get DATA frame within max_retry');
        end
    end
end

test_kraken.m

bash 复制代码
clear; clc;

client = KrakenSDRClient( ...
    '127.0.0.1', ...   % IP
    5000, ...          % data_port
    5001, ...          % ctrl_port
    5, ...             % num_channels
    416.588, ...       % freq_mhz
    12.5, ...          % gain:只写一个数字,5通道自动相同
    true, ...          % debug
    5.0);              % timeout

try
    client.connect();

    [iq, header] = client.get_data_frame();

    fprintf('IQ size: %d x %d\n', size(iq,1), size(iq,2));
    fprintf('Current gain: ');
    disp(client.gain);

    fprintf('Header center freq: %.0f Hz\n', double(header.rf_center_freq));
    fprintf('Header sampling freq: %.0f Hz\n', double(header.sampling_freq));

    disp('CH0 first 10 samples:');
    disp(iq(1,1:10));

    % 运行过程中动态设置所有通道相同 gain
    % client.set_if_gain(20.7);

catch ME
    fprintf(2, 'Error: %s\n', ME.message);
end

client.close();
相关推荐
似水এ᭄往昔2 小时前
【Linux】--进程概念
linux·运维·服务器
IDIOT___IDIOT2 小时前
Linux 使用 `cp` 命令导致挂载点被覆盖问题记录
linux·运维·服务器
@土豆3 小时前
bond主备模式配置步骤
网络
feifeigo1233 小时前
近场声全息(NAH)数据与MATLAB实现
开发语言·matlab
RisunJan3 小时前
Linux命令-mount(用于挂载Linux系统外的文件)
linux·运维·服务器
fie88893 小时前
基于MATLAB的非线性模型预测控制(NMPC)在CSRT系统中的应用
开发语言·matlab
国冶机电安装3 小时前
其他弱电系统安装:从方案设计到落地施工的完整指南
大数据·运维·网络
m0_738120723 小时前
我的创作纪念日0328
java·网络·windows·python·web安全·php
脆皮炸鸡7553 小时前
Linux开发工具~~~版本控制器Git以及调试工具GDB
linux·服务器·开发语言·经验分享·git·学习方法