目录
Crazyflie Radio 2 power setting location?
其上是有一个功率放大器的。
今天核心任务是梳理清楚Crazyradio
的构造过程和发送packet
的核心机理。
class Crazyradio: public ITransport, public USBDevice
{
public:
enum Datarate
{
Datarate_250KPS = 0,
Datarate_1MPS = 1,
Datarate_2MPS = 2,
};
enum Power
{
Power_M18DBM = 0,
Power_M12DBM = 1,
Power_M6DBM = 2,
Power_0DBM = 3,
};
}
从上述Crazyradio
定义中可以看出,该类有两个基类ITransport
和USBDevice
,两个枚举变量Datarate
和Power
ITransport
:- 一个抽象基类,其中有统一的
数据传输规范
和日志管理 Crazyradio
类需要实现其中的两个纯虚接口
来完成数据的收发操作。
- 一个抽象基类,其中有统一的
USBDevice
:- USB 设备的基类,
Crazyradio
作为一个 USB 设备,继承该类可以复用一些 USB 设备的通用功能 - 主要是打开USB设备以及向USB设备发送数据
- USB 设备的基类,
文档CrazySwarm代码梳理3-Crazyradio基类中对上述两个类进行了深扒。本文档主要研究Crazyradio
。
class Crazyradio: public ITransport, public USBDevice
{
private:
uint8_t m_channel;
uint64_t m_address;
Datarate m_datarate;
bool m_ackEnable;
}
每个Crazyradio
类主要维护信道m_channel
,通信地址m_address
,传输速率m_datarate
,使能ackm_ackEnable
,这几个私有变量。
class Crazyradio: public ITransport, public USBDevice
{
public:
Crazyradio(uint32_t devid);
virtual ~Crazyradio();
static uint32_t numDevices();
float version() const {return m_version;}
/*设置Crazyradio的接口*/
void setChannel(uint8_t channel){sendVendorSetup(SET_RADIO_CHANNEL, channel, 0, NULL, 0);}
uint8_t getChannel() const {return m_channel;}
void setAddress(uint64_t address);
uint64_t getAddress() const {return m_address;}
void setDatarate(Datarate datarate){sendVendorSetup(SET_DATA_RATE, datarate, 0, NULL, 0);}
Datarate getDatarate() const {return m_datarate;}
void setPower(Power power);
void setArc(uint8_t arc);
void setArdTime(uint8_t us);
void setArdBytes(uint8_t nbytes);
void setAckEnable(bool enable);
bool getAckEnable() const {return m_ackEnable;}
void setContCarrier(bool active);
/*通过crazyradio的发送数据包接口*/
virtual void sendPacket(
const uint8_t* data,
uint32_t length,
ITransport::Ack& result);
virtual void sendPacketNoAck(
const uint8_t* data,
uint32_t length);
virtual void send2PacketsNoAck(
const uint8_t* data,
uint32_t totalLength);
}
上述Crazyradio
的几个设置参数的接口都是调用了USBDevice
基类中的sendVendorSetup
,配置Crazyradio
的参数。
核心的发送数据包的接口如下:
void Crazyradio::sendPacket(
const uint8_t* data,
uint32_t length,
Ack& result)
{
result.ack = false;
result.size = 0;
int status;
int transferred;
if (!m_handle)
throw std::runtime_error("No valid device handle!");
/*使能m_enableLogging的话,会往日志文件里打包一份发送的数据*/。
if (m_enableLogging)
logPacket(data, length);
//使用libusb_bulk_transfer接口发送数据
status = libusb_bulk_transfer(
m_handle,
/* endpoint*/ (0x01 | LIBUSB_ENDPOINT_OUT),
(uint8_t*)data, //将data发送出去
length,
&transferred,
/*timeout*/ 100);
if (status != LIBUSB_SUCCESS)
throw std::runtime_error(libusb_error_name(status));
if (length != (uint32_t)transferred) {
std::stringstream sstr;
sstr << "Did transfer " << transferred << " but " << length << " was requested!";
throw std::runtime_error(sstr.str());
}
// 读取发送的结果?
status = libusb_bulk_transfer(
m_handle,
/* endpoint*/ (0x81 | LIBUSB_ENDPOINT_IN),
(unsigned char*)&result, //将result接收回来
sizeof(result) - 1,
&transferred,
/*timeout*/ 10);
// 如果读取超时了就会直接返回
if (status == LIBUSB_ERROR_TIMEOUT)
return;
if (status != LIBUSB_SUCCESS)
throw std::runtime_error(libusb_error_name(status));
result.size = transferred - 1;
if (m_enableLogging)
logAck(result); // log的位置太奇怪了
}
从上述代码中可以看出libusb_bulk_transfer
是sendPacket
的数据发送端的最底层发送接口,其是有发送状态检测和超时保护的,如果发送失败了就报错,如果没有报错说明就没有发送失败。读取结果也是同样的过程,所以发送过程阻塞似乎并不是一个合理的解释。
但是其发完数据之后,立马就开始接收,且只有10s
除了其中定了的发送数据
和读取结果
外,m_enableLogging
可以看到并没有被代码使能,其来源于ITransport
初始化时的false
,所以使用Crazyradio
进行收发的所有数据包都是没有被log
记录下来的,反过来如说,如果使能了m_enableLogging
,就可以看到文件中生成了transport.log
,看到所有原始数据
的收发过程。
void Crazyradio::sendPacketNoAck(
const uint8_t* data,
uint32_t length)
{
int status;
int transferred;
if (!m_handle)
throw std::runtime_error("No valid device handle!");
if (m_enableLogging)
logPacket(data, length);
// Send data
status = libusb_bulk_transfer(
m_handle,
/* endpoint*/ (0x01 | LIBUSB_ENDPOINT_OUT), // 数据传输的端点,从主机到设备
(uint8_t*)data,
length,
&transferred,
/*timeout*/ 100);
if (status != LIBUSB_SUCCESS)
throw std::runtime_error(libusb_error_name(status));
if (length != (uint32_t)transferred) {
std::stringstream sstr;
sstr << "Did transfer " << transferred << " but " << length << " was requested!";
throw std::runtime_error(sstr.str());
}
}
只是少了ACK
返回值,少了一个读取飞机端数据的过程。
void Crazyradio::send2PacketsNoAck(
const uint8_t* data,
uint32_t totalLength)
{
int status;
int transferred;
if (!m_handle)
throw std::runtime_error("No valid device handle!");
if (m_enableLogging)
logPacket(data, totalLength);
// Send data
status = libusb_bulk_transfer(
m_handle,
/* endpoint*/ (0x01 | LIBUSB_ENDPOINT_OUT),
(uint8_t*)data,
totalLength,
&transferred,
/*timeout*/ 100);
if (status != LIBUSB_SUCCESS)
throw std::runtime_error(libusb_error_name(status));
if (totalLength != (uint32_t)transferred) {
std::stringstream sstr;
sstr << "Did transfer " << transferred << " but " << totalLength << " was requested!";
throw std::runtime_error(sstr.str());
}
}
怎么通过一个Crazyradio给多个Crazyfile通信呢?
在每个Crazyflie
中都维护两个通信类
class Crazyflie
{
private:
Crazyradio* m_radio; // 这个指针被通往Crazyradio
ITransport* m_transport; // 这个指针被通往CrazyUSB
int m_devId;
uint8_t m_channel;
uint64_t m_address;
Crazyradio::Datarate m_datarate;
}
那么在该Crazyflie
类中,整个Crazyradio
是怎么建立起来的呢?每一个CF
都维护了一个Crazyradio
指针m_radio
,但是每一个Crazyflie
对象又是如何初始化该Crazyradio
的呢?
Crazyradio* g_crazyradios[MAX_RADIOS]; // 这就是互斥锁保护的全局资源
std::mutex g_radioMutex[MAX_RADIOS];
Crazyflie::Crazyflie(
const std::string& link_uri,
Logger& logger,
std::function<void(const char*)> consoleCb)
{
int datarate, channel;
char datarateType;
bool success = false;
/*从link_uri中取出devID, channel, datarate, m_address*/
success = std::sscanf(link_uri.c_str(), "radio://%d/%d/%d%c/%" SCNx64,
&m_devId, &channel, &datarate,
&datarateType, &m_address) == 5;
if (!success) {
success = std::sscanf(link_uri.c_str(), "radio://%d/%d/%d%c",
&m_devId, &channel, &datarate,
&datarateType) == 4;
m_address = 0xE7E7E7E7E7;
}
/*根据devId创建Crazyradion对象*/
{
// 利用一个新的作用域,是因为uniqui_lock对象,保护了一个全局资源
std::unique_lock<std::mutex> mlock(g_radioMutex[m_devId]);
if (!g_crazyradios[m_devId]) {
g_crazyradios[m_devId] = new Crazyradio(m_devId);
g_crazyradios[m_devId]->enableLogging(LOG_COMMUNICATION);//并未使能
// g_crazyradios[m_devId]->setAckEnable(false);
g_crazyradios[m_devId]->setAckEnable(true);
g_crazyradios[m_devId]->setArc(0);
}
// 此处加锁是防止其他Crazyflie对象在这里影响通信链路。但实际上chooser.py运行的时候就跑了?
}
m_radio = g_crazyradios[m_devId]; // 将这个全局资源的指针赋能给m_radio
}
从上述信息可以看到,所有的CF
都是公用的一个全局资源Crazyradio*
的 g_crazyradios
,所以当你调用了m_radio
的通信接口的时候,如果仅有一个radio
的话本质上都会调用同一个g_crazyradios
。
只不过在创建该对象的时候,需要有一些通信设置,在这个通信设置的过程中,需要使用互斥锁进行全局资源的保护。
void Crazyflie::sendPacketInternal(
const uint8_t* data,
uint32_t length,
ITransport::Ack& ack,
bool useSafeLink)
{
static uint32_t numPackets = 0;
static uint32_t numAcks = 0;
numPackets++;
if (m_radio) {
std::unique_lock<std::mutex> mlock(g_radioMutex[m_devId]);
if (m_radio->getAddress() != m_address)
m_radio->setAddress(m_address);
if (m_radio->getChannel() != m_channel)
m_radio->setChannel(m_channel);
if (m_radio->getDatarate() != m_datarate)
m_radio->setDatarate(m_datarate);
if (!m_radio->getAckEnable())
m_radio->setAckEnable(true);
if (useSafeLink) {
std::vector<uint8_t> dataCopy(data, data + length);
dataCopy[0] &= 0xF3; //0x11110011B
dataCopy[0] |= m_curr_up << 3 | m_curr_down << 2;
m_radio->sendPacket(dataCopy.data(), length, ack);
if (ack.ack && ack.size > 0 && (ack.data[0] & 0x04) == (m_curr_down << 2))
m_curr_down = 1 - m_curr_down;
if (ack.ack)
m_curr_up = 1 - m_curr_up;
} else
m_radio->sendPacket(data, length, ack);
}
从上可知,在Crazyflie
的最核心的通信接口 sendPacketInternal
中在使用m_radio
发送数据包sendPacket
的时候,都对m_radio
的资源进行互斥锁保护。非常多线程的时候是否会进行保护并不确定。
但是假设有20个Crazyflie
对象都同时调用了sendPacketInternal
,其最后的数据出口都会被该unique_lock
阻塞在门口,依次调用该接口进行数据发送,但是如果硬件允许的话,这些数据接口都应该是可以被依次发出的,尤其控制频率不是很高的情况下,个人觉得远远没有到其通信频率的极限,所以关于这个数据链,不止有通信频带频点一说,实际上应该还有通信带宽的说法,至于怎么确定通信带宽和通信硬件的极限还是需要进行学习。