文章目录
- 前言
- 一、配置以太网(针对X310)
- 二、Clion+Cmake入门(新建项目开始)
-
- 1.理清项目结构
- 2.项目文件初始设置
-
- 新建clion项目
- 建立目录结构(空文件夹)
- [复制 Find 模块](#复制 Find 模块)
- [写 CMakeLists.txt(AI辅助)](#写 CMakeLists.txt(AI辅助))
- [创建占位 main 文件](#创建占位 main 文件)
- 编译验证
- 3.编辑file_comm部分
- 4.编辑realtime_comm部分
- 核心代码
- 过程中遇到的问题
前言
由于这是实验室已经配好的电脑,所以没有硬件驱动的安装过程,不好意思啦
⚠️ 注意:单台USRP设备自收发不要用MATLAB
在Windows + MATLAB环境下,用单台USRP做同步收发时,while循环会因为TX/RX阻塞互相等待而卡死,无法通过MATLAB内部机制解决。
切换到**Linux + C++(pthread)**后,将发送和接收分别放在独立线程中,彻底解决了这个问题。如果你在做单台设备的雷达感知或loopback实验,直接用C++ + UHD库,不要在MATLAB上浪费时间。
一、配置以太网(针对X310)
1.连接设备,设置以太网静态IP
设备连接到电脑上的网口上,实验室用的万兆网口
打开电脑设置------网络------以太网(enp1s0f1 是本机连接USRP的网卡接口名)
手动配置IP为192.168.40.3,设置完重新开关 一下以太网


2.Linux环境初始化
在运行 uhd_usrp_probe 或任何UHD程序之前,先在终端执行以下命令:
bash
# 增大网络缓冲区,防止USRP数据流丢包(over/underflow)
sudo sysctl -w net.core.rmem_max=24912805
sudo sysctl -w net.core.wmem_max=24912805
# 开启巨帧(Jumbo Frame),降低网络传输开销,适配USRP 1GigE/10GigE接口
sudo ifconfig enp1s0f1 mtu 9000
enp1s0f1 是本机连接USRP的网卡接口名,根据实际情况替换(可用 ip link 查看)。
3.检测连接
初始化后在终端执行,看到下面这样的输出即为连接成功
bash
uhd_usrp_probe

二、Clion+Cmake入门(新建项目开始)
由于之前从未接触过c++项目,所以这部分会比较琐碎,供我以后操作使用
原始项目是GitHub的通信项目(两台N210收发)https://github.com/bmorgan5/fun_ofdm
1.理清项目结构
接手的项目往往如山一样堆积,所以从0开始理项目结构很重要
项目主要包括两个内容:
1.file_comm:发射已经写好的dat文件内容,接收后保存为dat文件,不做任何处理,交由matlab处理
2.realtime_comm:完整的发射---接收---解调---译码流程,但很简略,没有任何复杂处理,朴实无华的64点fft
x310_comm/
├── CMakeLists.txt
├── cmake/
│ └── Modules/
│ ├── FindUHD.cmake
│ └── FindFFTW3.cmake
├── common/
│ └── CMakeLists.txt
├── file_comm/
│ ├── CMakeLists.txt
│ └── file_comm_main.cpp
├── realtime_comm/
│ ├── CMakeLists.txt
│ └── realtime_comm_main.cpp
├── data/
│ ├── input/
│ └── output/
└── build/
| 文件夹 | 职责 |
|---|---|
| cmake/Modules/ | Find 模块,从旧项目直接复制 |
| common/ | 两个 target 共用的代码(暂为空) |
| file_comm/ | 文件读写通信的全部源码 |
| realtime_comm/ | 实时 USRP 通信的全部源码 |
| data/ | 运行时数据文件,不参与编译 |
| build/ | cmake 构建产物,不入 git |
2.项目文件初始设置
新建clion项目
在 CLion 中新建 C++ 项目,命名为 x310_comm。CLion 会自动生成一个顶层 main.cpp 和 CMakeLists.txt,把自动生成的 main.cpp 直接删掉。

建立目录结构(空文件夹)
在项目根目录下手动创建以下文件夹:
cmake/Modules/
common/
file_comm/
realtime_comm/
data/input/
data/output/
build/

复制 Find 模块
从旧项目的 cmake/Modules/ 目录,把以下两个文件直接复制到新项目的 cmake/Modules/:
FindUHD.cmake
FindFFTW3.cmake
写 CMakeLists.txt(AI辅助)
顶层 CMakeLists.txt
common/CMakeLists.txt
file_comm/CMakeLists.txt
realtime_comm/CMakeLists.txt
创建占位 main 文件
在 file_comm/ 和 realtime_comm/ 各新建对应的 .cpp 文件,内容先只写:
cpp
int main() { return 0; }

编译验证
Cmake流程:编辑CMakeList.txt------终端cmake------在Clion中点文件重新加载Cmake项目
进入空的build文件夹---右键在终端打开,执行以下命令
bash
cmake ..
make
看到以下输出说明骨架配置成功:

3.编辑file_comm部分
(1)函数文件
file_comm_main.cpp函数和CMakeLists.txt如附录所示,不多赘述

(2)数据文件
需要把matlab生成的发送帧文件(tx_waveform.dat)放在data文件夹中
然后就可以直接运行得到接收端data了!保存为rx_result.dat,便于拿回matlab处理

运行是在右上角选择配置运行的,不是matlab那样直接运行文件

4.编辑realtime_comm部分
(1)函数文件
把原项目的函数代码粘贴进realtime_comm文件夹,realtime_comm_main.cpp函数和CMakeLists.txt如附录所示,不多赘述

(2)运行结果

核心代码
file_comm部分
file_comm_main.cpp
cpp
#include <iostream>
#include <vector>
#include <complex>
#include <thread>
#include <atomic>
#include <chrono>
#include <fstream>
#include <uhd/usrp/multi_usrp.hpp>
#include <uhd/stream.hpp>
using namespace std;
static atomic<bool> stop_signal_called(false);
// ======== 发送线程函数 ========
void trans(
vector<complex<double>> buff,
uhd::tx_streamer::sptr tx_stream,
uhd::tx_metadata_t md,
size_t total_num_samps,
double timeout,
vector<uhd::time_spec_t> frame_start_times)
{
int tx_num = (int)frame_start_times.size();
for(int i = 0; i < tx_num; i++)
{
if(stop_signal_called) break;
md.has_time_spec = true;
md.time_spec = frame_start_times[i];
md.start_of_burst = false;
md.end_of_burst = false;
size_t num_acc_samps = 0;
while(num_acc_samps < total_num_samps)
{
size_t samps_to_send = min(total_num_samps - num_acc_samps, buff.size());
size_t num_tx_samps = tx_stream->send(&buff.front(), samps_to_send, md, timeout);
md.has_time_spec = false;
num_acc_samps += num_tx_samps;
}
md.end_of_burst = true;
tx_stream->send("", 0, md);
cout << "[DBG] 第 " << i << " 帧发送完,样本数: " << total_num_samps << endl;
}
}
// ======== 接收函数 ========
void recv(
uhd::usrp::multi_usrp::sptr usrp,
size_t rx_total_num_samps,
vector<uhd::time_spec_t> frame_start_times,
ofstream& rx_log)
{
uhd::stream_args_t stream_args("fc64", "");
stream_args.channels = {0};
uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args);
uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE);
stream_cmd.num_samps = rx_total_num_samps;
stream_cmd.stream_now = false;
uhd::rx_metadata_t md;
vector<complex<double>> buff(rx_stream->get_max_num_samps());
vector<void*> buffs;
buffs.push_back(&buff.front());
int num = (int)frame_start_times.size();
for(int i = 0; i < num; i++)
{
if(stop_signal_called) break;
stream_cmd.time_spec = frame_start_times[i];
rx_stream->issue_stream_cmd(stream_cmd);
size_t num_acc_samps = 0;
vector<complex<double>> all_samples;
while(!stop_signal_called && num_acc_samps < rx_total_num_samps)
{
size_t num_rx_samps = rx_stream->recv(buffs, buff.size(), md, 1.0, true);
if(stop_signal_called) break;
if(md.error_code == uhd::rx_metadata_t::ERROR_CODE_TIMEOUT)
{
cout << "Timeout while streaming\n";
break;
}
if(md.error_code == uhd::rx_metadata_t::ERROR_CODE_OVERFLOW)
continue;
if(md.error_code == uhd::rx_metadata_t::ERROR_CODE_LATE_COMMAND)
{
cerr << "[WARN] Late command, skipping frame " << i << "\n";
all_samples.clear();
num_acc_samps = rx_total_num_samps;
break;
}
if(md.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE)
{
cerr << "Receiver error: " << md.strerror() << "\n";
break;
}
all_samples.insert(all_samples.end(),
buff.begin(), buff.begin() + num_rx_samps);
num_acc_samps += num_rx_samps;
}
if(all_samples.empty()) continue;
cout << "[DBG] 收到样本数: " << all_samples.size() << endl;
// 写帧长(4字节),再写IQ数据(float32 interleaved)
uint32_t frame_len = (uint32_t)all_samples.size();
rx_log.write((char*)&frame_len, sizeof(uint32_t));
for(auto& c : all_samples)
{
float re = (float)c.real();
float im = (float)c.imag();
rx_log.write((char*)&re, sizeof(float));
rx_log.write((char*)&im, sizeof(float));
}
cout << "[DBG] 已写入帧,样本数: " << frame_len << endl;
}
// 结束RX流
uhd::stream_cmd_t stop_cmd(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS);
stop_cmd.stream_now = true;
rx_stream->issue_stream_cmd(stop_cmd);
vector<complex<double>> drain_buff(rx_stream->get_max_num_samps());
vector<void*> drain_buffs(1, &drain_buff.front());
uhd::rx_metadata_t drain_md;
while(true)
{
size_t nsamps = rx_stream->recv(drain_buffs, drain_buff.size(), drain_md, 0.1, false);
if(nsamps == 0) break;
}
this_thread::sleep_for(chrono::milliseconds(50));
}
// ======== main ========
int main()
{
// ---------- 参数 ----------
double freq = 3.51e9;
double rate = 100e6;
double tx_gain = 20.0;
double rx_gain = 20.0;
string device_addr = "192.168.40.2";
double seconds_in_future = 0.05;
const int frames_per_batch = 10;
const double frame_period = 0.5;
int run_nums = 200; // 总帧数
// ---------- 初始化USRP ----------
cout << "初始化USRP..." << endl;
auto usrp = uhd::usrp::multi_usrp::make(
uhd::device_addr_t("type=x300,master_clock_rate=200e6,addr=" + device_addr)
);
usrp->set_tx_subdev_spec(uhd::usrp::subdev_spec_t("B:0"));
usrp->set_rx_subdev_spec(uhd::usrp::subdev_spec_t("A:0"));
usrp->set_tx_rate(rate);
usrp->set_rx_rate(rate);
usrp->set_tx_freq(uhd::tune_request_t(freq));
usrp->set_rx_freq(uhd::tune_request_t(freq));
usrp->set_tx_gain(tx_gain);
usrp->set_rx_gain(rx_gain);
cout << "TX率: " << usrp->get_tx_rate()/1e6 << " Msps" << endl;
cout << "RX率: " << usrp->get_rx_rate()/1e6 << " Msps" << endl;
this_thread::sleep_for(chrono::milliseconds(200));
// ---------- 读取MATLAB生成的发射波形 ----------
ifstream wf(string(PROJECT_DATA_DIR) + "/tx_waveform.dat", ios::binary);
if(!wf) { cerr << "无法打开tx_waveform.dat" << endl; return 1; }
wf.seekg(0, ios::end);
size_t file_bytes = wf.tellg();
wf.seekg(0, ios::beg);
size_t num_samples = file_bytes / (2 * sizeof(float));
vector<float> raw(num_samples * 2);
wf.read(reinterpret_cast<char*>(raw.data()), file_bytes);
wf.close();
vector<complex<double>> tx_samples(num_samples);
for(size_t i = 0; i < num_samples; i++)
tx_samples[i] = complex<double>(raw[2*i], raw[2*i+1]);
cout << "读取波形: " << num_samples << " 个样本" << endl;
size_t tx_total_num_samps = tx_samples.size();
size_t rx_total_num_samps = (size_t)(tx_samples.size() * 1.5);
// ---------- 构造TX streamer ----------
uhd::stream_args_t tx_args("fc64", "");
tx_args.channels = {0};
uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(tx_args);
// ---------- 打开接收数据文件(全程只开一次)----------
ofstream rx_log(string(PROJECT_DATA_DIR) + "/rx_result.dat", ios::binary);
if(!rx_log) { cerr << "无法创建rx_all.dat" << endl; return 1; }
// ---------- 主循环 ----------
cout << "开始收发..." << endl;
int batch_idx = 0;
int total_frames = 0;
while(!stop_signal_called && total_frames < run_nums)
{
usrp->set_time_now(uhd::time_spec_t(0.0));
vector<uhd::time_spec_t> frame_start_times(frames_per_batch);
for(int i = 0; i < frames_per_batch; i++)
frame_start_times[i] = uhd::time_spec_t(
seconds_in_future + frame_period * static_cast<double>(i));
uhd::tx_metadata_t tx_md;
thread tx_thread([&](){
trans(tx_samples, tx_stream, tx_md,
tx_total_num_samps, 0.5, frame_start_times);
});
recv(usrp, rx_total_num_samps, frame_start_times, rx_log);
tx_thread.join();
batch_idx++;
total_frames += frames_per_batch;
cout << "完成第 " << batch_idx << " 批,共 " << total_frames << " 帧" << endl;
}
rx_log.close();
cout << "结束,数据已保存至 rx_all.dat" << endl;
return 0;
}
file_comm中的CMakeLists.txt
cpp
add_executable(file_comm file_comm_main.cpp)
target_compile_definitions(file_comm PRIVATE
PROJECT_DATA_DIR="${CMAKE_SOURCE_DIR}/data"
)
target_include_directories(file_comm PRIVATE
${CMAKE_SOURCE_DIR}/common
)
target_link_libraries(file_comm PRIVATE
${UHD_LIBRARIES}
Boost::filesystem Boost::system Boost::thread
${FFTW3_LIBRARIES}
Threads::Threads
)
realtime_comm部分
realtime_comm_main.cpp
cpp
#include <iostream>
#include <vector>
#include <complex>
#include <thread>
#include <atomic>
#include <chrono>
#include <cstring>
#include <uhd/usrp/multi_usrp.hpp>
#include <uhd/stream.hpp>
#include "receiver_chain.h"
#include "frame_builder.h"
using namespace std;
using namespace fun;
static atomic<bool> stop_signal_called(false);
// ======== 回调函数 ========
void callback(vector<vector<unsigned char>> payloads)
{
for(auto& p : payloads)
cout << "Received: " << string(p.begin(), p.end()) << endl;
}
// ======== 发送线程函数(完全照搬感知代码trans) ========
void trans(
vector<complex<double>> buff,
uhd::tx_streamer::sptr tx_stream,
uhd::tx_metadata_t md,
size_t total_num_samps,
double timeout,
vector<uhd::time_spec_t> frame_start_times)
{
int tx_num = (int)frame_start_times.size();
for(int i = 0; i < tx_num; i++)
{
if(stop_signal_called) break;
md.has_time_spec = true;
md.time_spec = frame_start_times[i];
md.start_of_burst = false;
md.end_of_burst = false;
size_t num_acc_samps = 0;
while(num_acc_samps < total_num_samps)
{
size_t samps_to_send = min(total_num_samps - num_acc_samps, buff.size());
size_t num_tx_samps = tx_stream->send(&buff.front(), samps_to_send, md, timeout);
md.has_time_spec = false;
num_acc_samps += num_tx_samps;
}
md.end_of_burst = true;
tx_stream->send("", 0, md);
// 每帧发送完后
cout << "[DBG] 第 " << i << " 帧发送完,样本数: " << total_num_samps << endl;
}
}
// ======== 接收函数(完全照搬感知代码recv,去掉感知计算部分) ========
void recv(
uhd::usrp::multi_usrp::sptr usrp,
size_t rx_total_num_samps,
vector<uhd::time_spec_t> frame_start_times,
receiver_chain& rec_chain,
void(*cb)(vector<vector<unsigned char>>))
{
uhd::stream_args_t stream_args("fc64", "");
stream_args.channels = {0};
uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args);
uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE);
stream_cmd.num_samps = rx_total_num_samps;
stream_cmd.stream_now = false;
uhd::rx_metadata_t md;
vector<complex<double>> buff(rx_stream->get_max_num_samps());
vector<void*> buffs;
buffs.push_back(&buff.front());
int num = (int)frame_start_times.size();
for(int i = 0; i < num; i++)
{
if(stop_signal_called) break;
stream_cmd.time_spec = frame_start_times[i];
rx_stream->issue_stream_cmd(stream_cmd);
size_t num_acc_samps = 0;
vector<complex<double>> all_samples;
while(!stop_signal_called && num_acc_samps < rx_total_num_samps)
{
size_t num_rx_samps = rx_stream->recv(buffs, buff.size(), md, 1.0, true);
if(stop_signal_called) break;
if(md.error_code == uhd::rx_metadata_t::ERROR_CODE_TIMEOUT)
{
cout << "Timeout while streaming\n";
break;
}
if(md.error_code == uhd::rx_metadata_t::ERROR_CODE_OVERFLOW)
continue;
if(md.error_code == uhd::rx_metadata_t::ERROR_CODE_LATE_COMMAND)
{
cerr << "[WARN] Late command, skipping frame " << i << "\n";
all_samples.clear();
num_acc_samps = rx_total_num_samps;
break;
}
if(md.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE)
{
cerr << "Receiver error: " << md.strerror() << "\n";
break;
}
all_samples.insert(all_samples.end(),
buff.begin(), buff.begin() + num_rx_samps);
num_acc_samps += num_rx_samps;
}
if(all_samples.empty()) continue;
// 加这行,看收到了多少样本
cout << "[DBG] 收到样本数: " << all_samples.size() << endl;
auto packets = rec_chain.process_samples(all_samples);
// 加这行,看解出了多少包
cout << "[DBG] 解出包数: " << packets.size() << endl;
cb(packets);
// 送入接收链路处理
//auto packets = rec_chain.process_samples(all_samples);
//cb(packets);
}
// 优雅结束RX流
uhd::stream_cmd_t stop_cmd(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS);
stop_cmd.stream_now = true;
rx_stream->issue_stream_cmd(stop_cmd);
vector<complex<double>> drain_buff(rx_stream->get_max_num_samps());
vector<void*> drain_buffs(1, &drain_buff.front());
uhd::rx_metadata_t drain_md;
while(true)
{
size_t nsamps = rx_stream->recv(drain_buffs, drain_buff.size(), drain_md, 0.1, false);
if(nsamps == 0) break;
}
this_thread::sleep_for(chrono::milliseconds(50));
}
// ======== main ========
int main()
{
// ---------- 参数 ----------
double freq = 3.51e9;
double rate = 50e6;
double tx_gain = 20.0;
double rx_gain = 20.0;
string device_addr = "192.168.40.2";
double seconds_in_future = 0.05; // 每帧提前量
const int frames_per_batch = 10; // 每批帧数
const double frame_period = 0.5; // 帧间隔(s)
// ---------- 初始化USRP ----------
cout << "初始化USRP..." << endl;
auto usrp = uhd::usrp::multi_usrp::make(
uhd::device_addr_t("type=x300,master_clock_rate=200e6,addr=" + device_addr)
);
usrp->set_tx_subdev_spec(uhd::usrp::subdev_spec_t("B:0"));
usrp->set_rx_subdev_spec(uhd::usrp::subdev_spec_t("A:0"));
usrp->set_tx_rate(rate);
usrp->set_rx_rate(rate);
usrp->set_tx_freq(uhd::tune_request_t(freq));
usrp->set_rx_freq(uhd::tune_request_t(freq));
usrp->set_tx_gain(tx_gain);
usrp->set_rx_gain(rx_gain);
cout << "TX率: " << usrp->get_tx_rate()/1e6 << " Msps" << endl;
cout << "RX率: " << usrp->get_rx_rate()/1e6 << " Msps" << endl;
this_thread::sleep_for(chrono::milliseconds(200));
// ---------- 构造TX streamer(只创建一次,不会和RX同时存在) ----------
uhd::stream_args_t tx_args("fc64", "");
tx_args.channels = {0};
uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(tx_args);
// ---------- 构造发送帧 ----------
frame_builder fb;
string s = "Hello World";
vector<unsigned char> data(s.begin(), s.end());
auto tx_samples = fb.build_frame(data, RATE_1_2_BPSK);
size_t tx_total_num_samps = tx_samples.size();
size_t rx_total_num_samps = (size_t)(tx_samples.size() * 1.5); // 多收一点保证帧完整
receiver_chain rec_chain;
// ---------- 主循环(完全照搬感知代码批次结构) ----------
cout << "开始收发..." << endl;
int batch_idx = 0;
int total_frames = 0;
int run_nums = 200; // 总帧数
while(!stop_signal_called && total_frames < run_nums)
{
// 重置硬件时钟(与感知代码完全一致)
usrp->set_time_now(uhd::time_spec_t(0.0));
// 构造这一批的帧时间表
vector<uhd::time_spec_t> frame_start_times(frames_per_batch);
for(int i = 0; i < frames_per_batch; i++)
frame_start_times[i] = uhd::time_spec_t(
seconds_in_future + frame_period * static_cast<double>(i));
// 启动发射线程
uhd::tx_metadata_t tx_md;
thread tx_thread([&](){
trans(tx_samples, tx_stream, tx_md,
tx_total_num_samps, 0.5, frame_start_times);
});
// 主线程做接收(RX streamer在recv内部创建和销毁)
recv(usrp, rx_total_num_samps, frame_start_times,
rec_chain, callback);
tx_thread.join();
batch_idx++;
total_frames += frames_per_batch;
cout << "完成第 " << batch_idx << " 批,共 " << total_frames << " 帧" << endl;
}
cout << "结束" << endl;
return 0;
}
realtime_comm中的CMakeLists.txt
cpp
add_executable(realtime_comm
realtime_comm_main.cpp
channel_est.cpp
fft.cpp
fft_symbols.cpp
frame_builder.cpp
frame_decoder.cpp
frame_detector.cpp
interleaver.cpp
modulator.cpp
parity.cpp
phase_tracker.cpp
ppdu.cpp
puncturer.cpp
receiver_chain.cpp
symbol_mapper.cpp
timing_sync.cpp
viterbi.cpp
)
target_include_directories(realtime_comm PRIVATE
${CMAKE_SOURCE_DIR}/common
)
target_link_libraries(realtime_comm PRIVATE
${UHD_LIBRARIES}
Boost::filesystem Boost::system Boost::thread
${FFTW3_LIBRARIES}
Threads::Threads
)
target_include_directories(realtime_comm PRIVATE
${CMAKE_SOURCE_DIR}/common
)
target_link_libraries(realtime_comm PRIVATE
${UHD_LIBRARIES}
Boost::filesystem Boost::system Boost::thread
${FFTW3_LIBRARIES}
Threads::Threads
)