一、引言
在软件无线电领域中,USRP(Universal Software Radio Peripheral)设备是常用的硬件平台,usrp_source
作为从 USRP 设备获取数据的重要模块,其消息处理机制对于系统的稳定运行和有效控制起着关键作用。本文将结合相关代码,深入剖析 usrp_source
的消息处理机制。
二、代码结构概述
所提供的代码是 usrp_source
实现的一部分,属于 GNU Radio 框架中与 UHD(Universal Hardware Driver)相关的内容。代码定义了 usrp_source
类及其实现类 usrp_source_impl
,涵盖了设备参数设置、消息端口管理、命令处理等多个与消息处理相关的功能模块。
三、usrp_source消息处理关键部分
(一)消息端口管理
- 消息端口注册
在usrp_source_impl
的构造函数中,通过以下代码注册了一个输出消息端口:
message_port_register_out(ASYNC_MSGS_PORT_KEY);
这表明 usrp_source
模块能够通过 ASYNC_MSGS_PORT_KEY
这个端口向外发送异步消息。异步消息的发送可以用于通知上层应用程序一些重要的事件,例如设备状态的变化或者错误情况的发生。
(二)命令处理机制
- 命令处理函数注册
构造函数中还包含了命令处理函数的注册代码:
register_msg_cmd_handler(cmd_tag_key(),
[this](const pmt::pmt_t& tag, const int, const pmt::pmt_t&) {
this->_cmd_handler_tag(tag);
});
这里使用 register_msg_cmd_handler
函数注册了一个处理特定命令的函数。当接收到与 cmd_tag_key()
相关的命令消息时,会调用 lambda 表达式中定义的函数,最终执行 _cmd_handler_tag
函数。
- 命令处理函数
_cmd_handler_tag
void usrp_source_impl::_cmd_handler_tag(const pmt::pmt_t& tag) { _tag_now = true; }
_cmd_handler_tag
函数的作用是将 _tag_now
标记设置为 true
。当数据接收正常且 _tag_now
为 true 时,会创建与数据相关的时间戳、采样率、中心频率等元信息标签,并将其添加到输出数据的相应通道上。这是因为当模块接收到异步消息时,设备参数或状态可能发生了改变,所以需要更新这些元信息以保证数据的准确性和完整性。然而,令人费解的是,usrp_source
仅仅更新了 _tag_now
,却并未读取消息的任何具体内容,也没有依据usrp_source
设置的举动。但更新_tag_now
又表明 USRP 的设备参数或状态发生了改变,进而需要更新数据流标签
这其实是因为usrp_source的消息处理机制实际上继承自usrp_block的消息处理机制。
四、usrp_block的消息处理机制
(一)消息端口与命令注册
- 消息端口注册
在usrp_block_impl
的构造函数中,通过如下代码注册了一个输入消息端口:
message_port_register_in(pmt::mp("command"));
这使得usrp_block
能够接收来自外部的命令消息,为设备的参数设置和状态控制提供了入口。
- 命令处理函数注册
构造函数中还大量使用宏定义来注册各种命令处理函数:
#define REGISTER_CMD_HANDLER(key, _handler) \
register_msg_cmd_handler( \
key, [this](const pmt::pmt_t& var, int chan, const pmt::pmt_t& msg) { \
this->_handler(var, chan, msg); \
})
// Register default command handlers:
REGISTER_CMD_HANDLER(cmd_freq_key(), _cmd_handler_freq);
REGISTER_CMD_HANDLER(cmd_gain_key(), _cmd_handler_gain);
REGISTER_CMD_HANDLER(cmd_power_key(), _cmd_handler_power);
// 其他命令处理函数注册...
通过register_msg_cmd_handler
函数,将不同的命令关键字(如cmd_freq_key
、cmd_gain_key
等)与对应的处理函数(如_cmd_handler_freq
、_cmd_handler_gain
等)关联起来。当接收到包含这些命令关键字的消息时,相应的处理函数将被调用。
(二)命令消息处理流程
- 消息格式转换与校验
在msg_handler_command
函数中,首先对接收到的消息进行处理。如果消息是元组格式(这是一种旧的消息格式),会将其转换为字典格式,以保证后续处理的一致性:
if (pmt::is_tuple(msg)) {
if (pmt::length(msg) != 2 && pmt::length(msg) != 3) {
d_logger->alert("Error while unpacking command PMT: {}",
pmt::write_string(msg));
return;
}
pmt::pmt_t new_msg = pmt::make_dict();
new_msg = pmt::dict_add(new_msg, pmt::tuple_ref(msg, 0), pmt::tuple_ref(msg, 1));
if (pmt::length(msg) == 3) {
new_msg = pmt::dict_add(new_msg, cmd_chan_key(), pmt::tuple_ref(msg, 2));
}
d_debug_logger->warn("Using legacy message format (tuples): {}",
pmt::write_string(msg));
return msg_handler_command(new_msg);
}
接着,确保消息是字典格式,如果不是则记录错误并返回:
if (!(pmt::is_dict(msg)) && pmt::is_pair(msg)) {
d_logger->debug(
"Command message is pair, converting to dict: '{}': car({}), cdr({})",
pmt::write_string(msg),
pmt::car(msg),
pmt::cdr(msg));
msg = pmt::dict_add(pmt::make_dict(), pmt::car(msg), pmt::cdr(msg));
}
if (!pmt::is_dict(msg)) {
d_logger->error("Command message is neither dict nor pair: {}",
pmt::write_string(msg));
return;
}
- 消息内容处理
- 时间戳处理:如果消息中包含时间戳相关的关键字
cmd_time_key
,则根据消息中的时间信息设置设备的命令时间:
if (pmt::dict_has_key(msg, cmd_time_key())) {
size_t mboard_index = pmt::to_long(pmt::dict_ref(
msg,
cmd_mboard_key(),
pmt::from_long(::uhd::usrp::multi_usrp::ALL_MBOARDS) // Default to all mboards
));
pmt::pmt_t timespec_p = pmt::dict_ref(msg, cmd_time_key(), pmt::PMT_NIL);
if (timespec_p == pmt::PMT_NIL) {
clear_command_time(mboard_index);
} else {
::uhd::time_spec_t timespec(
time_t(pmt::to_uint64(pmt::car(timespec_p))), // Full secs
pmt::to_double(pmt::cdr(timespec_p)) // Frac secs
);
d_debug_logger->debug("Setting command time on mboard {}", mboard_index);
set_command_time(timespec, mboard_index);
}
}
- 通道与方向处理:读取消息中的通道号
chan
和方向信息,方向信息会影响设备参数设置的目标方向(如接收或发送),并且方向信息的存在可能会强制设备进行频率调谐:
int chan = int(pmt::to_long(pmt::dict_ref(msg,
cmd_chan_key(),
pmt::from_long(-1) // Default to all chans
)));
_force_tune = pmt::dict_has_key(msg, cmd_direction_key());
- 命令值处理:遍历消息中的所有键值对,根据命令关键字调用相应的处理函数进行处理:
pmt::pmt_t msg_items = pmt::dict_items(msg);
for (size_t i = 0; i < pmt::length(msg_items); i++) {
try {
dispatch_msg_cmd_handler(pmt::car(pmt::nth(i, msg_items)),
pmt::cdr(pmt::nth(i, msg_items)),
chan,
msg);
} catch (pmt::wrong_type& e) {
d_logger->alert("Invalid command value for key {}: {}",
pmt::write_string(pmt::car(pmt::nth(i, msg_items))),
pmt::write_string(pmt::cdr(pmt::nth(i, msg_items))));
break;
}
}
在dispatch_msg_cmd_handler
函数中,根据之前注册的命令处理函数映射表_msg_cmd_handlers
,调用对应的处理函数:
void usrp_block_impl::dispatch_msg_cmd_handler(const pmt::pmt_t& cmd,
const pmt::pmt_t& val,
int chan,
pmt::pmt_t& msg)
{
if (_msg_cmd_handlers.has_key(cmd)) {
_msg_cmd_handlers[cmd](val, chan, msg);
}
}
例如,_cmd_handler_freq
函数用于处理频率相关的命令消息,它会根据消息中的频率值和其他相关信息(如本振偏移)更新设备的调谐请求:
void usrp_block_impl::_cmd_handler_freq(const pmt::pmt_t& freq_,
int chan,
const pmt::pmt_t& msg)
{
const pmt::pmt_t direction = get_cmd_or_default_direction(msg);
double freq = pmt::to_double(freq_);
::uhd::tune_request_t new_tune_request(freq);
if (pmt::dict_has_key(msg, cmd_lo_offset_key())) {
double lo_offset =
pmt::to_double(pmt::dict_ref(msg, cmd_lo_offset_key(), pmt::PMT_NIL));
new_tune_request = ::uhd::tune_request_t(freq, lo_offset);
}
_update_curr_tune_req(new_tune_request, chan, direction);
}
_update_curr_tune_req
函数会根据通道号和方向,更新当前的调谐请求,并标记相应通道需要重新调谐:
void usrp_block_impl::_update_curr_tune_req(::uhd::tune_request_t& tune_req,
int chan,
pmt::pmt_t direction)
{
if (chan == -1) {
for (size_t i = 0; i < _nchan; i++) {
_update_curr_tune_req(tune_req, int(i), direction);
}
return;
}
if (pmt::eqv(direction, direction_rx())) {
if (tune_req.target_freq != _curr_rx_tune_req[chan].target_freq ||
// 其他条件判断
_force_tune) {
_curr_rx_tune_req[chan] = tune_req;
_rx_chans_to_tune[chan] = true;
}
} else {
// 类似的发送方向处理
}
}
- 频率调谐检查:在处理完所有命令消息后,检查是否需要对所有通道进行频率调谐,并执行相应的调谐操作:
_set_center_freq_from_internals_allchans();
_force_tune = false;
_set_center_freq_from_internals_allchans
函数会遍历所有通道,根据之前标记的需要调谐的通道,执行实际的频率设置操作:
void usrp_block_impl::_set_center_freq_from_internals_allchans()
{
for (size_t chan = 0; chan < _rx_chans_to_tune.size(); chan++) {
if (_rx_chans_to_tune[chan]) {
_set_center_freq_from_internals(chan, direction_rx());
_rx_chans_to_tune[chan] = false;
}
}
for (size_t chan = 0; chan < _tx_chans_to_tune.size(); chan++) {
if (_tx_chans_to_tune[chan]) {
_set_center_freq_from_internals(chan, direction_tx());
_tx_chans_to_tune[chan] = false;
}
}
}
四、总结
usrp_source
的消息处理机制通过消息端口的注册与管理、命令处理函数的设置以及错误消息的处理等多个环节协同工作。此外,还需要注意usrp_source
通过继承usrp_block
的消息处理机制,在接收到消息后,虽然表面上只是更新了_tag_now
,但实际上是依赖usrp_block
内部复杂的命令处理流程来修改设备参数。usrp_block
负责接收、解析和处理各种命令消息,包括设备的频率、增益、功率、天线等参数的设置,以及时间同步、GPIO控制等操作。这种分层的消息处理机制,使得usrp_source
能够专注于数据获取功能,而将消息处理的重任交给usrp_block
,提高了代码的模块化和可维护性。