Merge branch 'master' of github.com:felis/USB_Host_Shield_2.0

This commit is contained in:
Andrew J. Kroll 2018-01-13 22:37:46 -05:00
commit 8318e5eeba
4 changed files with 155 additions and 74 deletions

View file

@ -225,11 +225,11 @@ uint8_t BTD::Init(uint8_t parent __attribute__((unused)), uint8_t port __attribu
// First interface in the configuration must have Bluetooth assigned Class/Subclass/Protocol // First interface in the configuration must have Bluetooth assigned Class/Subclass/Protocol
// And 3 endpoints - interrupt-IN, bulk-IN, bulk-OUT, not necessarily in this order // And 3 endpoints - interrupt-IN, bulk-IN, bulk-OUT, not necessarily in this order
for(uint8_t i = 0; i < num_of_conf; i++) { for(uint8_t i = 0; i < num_of_conf; i++) {
if(VID == IOGEAR_GBU521_VID && PID == IOGEAR_GBU521_PID) { if((VID == IOGEAR_GBU521_VID && PID == IOGEAR_GBU521_PID) || (VID == BELKIN_F8T065BF_VID && PID == BELKIN_F8T065BF_PID)) {
ConfigDescParser<USB_CLASS_VENDOR_SPECIFIC, WI_SUBCLASS_RF, WI_PROTOCOL_BT, CP_MASK_COMPARE_ALL> confDescrParser(this); // Needed for the IOGEAR GBU521 ConfigDescParser<USB_CLASS_VENDOR_SPECIFIC, WI_SUBCLASS_RF, WI_PROTOCOL_BT, CP_MASK_COMPARE_ALL> confDescrParser(this); // Workaround issue with some dongles
rcode = pUsb->getConfDescr(bAddress, 0, i, &confDescrParser); rcode = pUsb->getConfDescr(bAddress, 0, i, &confDescrParser);
} else { } else {
ConfigDescParser<USB_CLASS_WIRELESS_CTRL, WI_SUBCLASS_RF, WI_PROTOCOL_BT, CP_MASK_COMPARE_ALL> confDescrParser(this); ConfigDescParser<USB_CLASS_WIRELESS_CTRL, WI_SUBCLASS_RF, WI_PROTOCOL_BT, CP_MASK_COMPARE_ALL> confDescrParser(this); // Set class id according to the specification
rcode = pUsb->getConfDescr(bAddress, 0, i, &confDescrParser); rcode = pUsb->getConfDescr(bAddress, 0, i, &confDescrParser);
} }
if(rcode) // Check error code if(rcode) // Check error code

7
BTD.h
View file

@ -27,8 +27,11 @@
#define PS3NAVIGATION_PID 0x042F // Navigation controller #define PS3NAVIGATION_PID 0x042F // Navigation controller
#define PS3MOVE_PID 0x03D5 // Motion controller #define PS3MOVE_PID 0x03D5 // Motion controller
#define IOGEAR_GBU521_VID 0x0A5C // The IOGEAR GBU521 dongle does not presents itself correctly, so we have to check for it manually // These dongles do not present themselves correctly, so we have to check for them manually
#define IOGEAR_GBU521_VID 0x0A5C
#define IOGEAR_GBU521_PID 0x21E8 #define IOGEAR_GBU521_PID 0x21E8
#define BELKIN_F8T065BF_VID 0x050D
#define BELKIN_F8T065BF_PID 0x065A
/* Bluetooth dongle data taken from descriptors */ /* Bluetooth dongle data taken from descriptors */
#define BULK_MAXPKTSIZE 64 // Max size for ACL data #define BULK_MAXPKTSIZE 64 // Max size for ACL data
@ -264,7 +267,7 @@ public:
* @return Returns true if the device's VID and PID matches this driver. * @return Returns true if the device's VID and PID matches this driver.
*/ */
virtual bool VIDPIDOK(uint16_t vid, uint16_t pid) { virtual bool VIDPIDOK(uint16_t vid, uint16_t pid) {
if(vid == IOGEAR_GBU521_VID && pid == IOGEAR_GBU521_PID) if((vid == IOGEAR_GBU521_VID && pid == IOGEAR_GBU521_PID) || (vid == BELKIN_F8T065BF_VID && pid == BELKIN_F8T065BF_PID))
return true; return true;
if(my_bdaddr[0] != 0x00 || my_bdaddr[1] != 0x00 || my_bdaddr[2] != 0x00 || my_bdaddr[3] != 0x00 || my_bdaddr[4] != 0x00 || my_bdaddr[5] != 0x00) { // Check if Bluetooth address is set if(my_bdaddr[0] != 0x00 || my_bdaddr[1] != 0x00 || my_bdaddr[2] != 0x00 || my_bdaddr[3] != 0x00 || my_bdaddr[4] != 0x00 || my_bdaddr[5] != 0x00) { // Check if Bluetooth address is set
if(vid == PS3_VID && (pid == PS3_PID || pid == PS3NAVIGATION_PID || pid == PS3MOVE_PID)) if(vid == PS3_VID && (pid == PS3_PID || pid == PS3NAVIGATION_PID || pid == PS3MOVE_PID))

View file

@ -27,8 +27,11 @@
XBOXONE::XBOXONE(USB *p) : XBOXONE::XBOXONE(USB *p) :
pUsb(p), // pointer to USB class instance - mandatory pUsb(p), // pointer to USB class instance - mandatory
bAddress(0), // device address - mandatory bAddress(0), // device address - mandatory
bNumEP(1), // If config descriptor needs to be parsed
qNextPollTime(0), // Reset NextPollTime
pollInterval(0),
bPollEnable(false) { // don't start polling before dongle is connected bPollEnable(false) { // don't start polling before dongle is connected
for(uint8_t i = 0; i < XBOX_MAX_ENDPOINTS; i++) { for(uint8_t i = 0; i < XBOX_ONE_MAX_ENDPOINTS; i++) {
epInfo[i].epAddr = 0; epInfo[i].epAddr = 0;
epInfo[i].maxPktSize = (i) ? 0 : 8; epInfo[i].maxPktSize = (i) ? 0 : 8;
epInfo[i].bmSndToggle = 0; epInfo[i].bmSndToggle = 0;
@ -46,8 +49,8 @@ uint8_t XBOXONE::Init(uint8_t parent, uint8_t port, bool lowspeed) {
uint8_t rcode; uint8_t rcode;
UsbDevice *p = NULL; UsbDevice *p = NULL;
EpInfo *oldep_ptr = NULL; EpInfo *oldep_ptr = NULL;
uint16_t PID; uint16_t PID, VID;
uint16_t VID; uint8_t num_of_conf; // Number of configurations
// get memory address of USB device address pool // get memory address of USB device address pool
AddressPool &addrPool = pUsb->GetAddressPool(); AddressPool &addrPool = pUsb->GetAddressPool();
@ -142,31 +145,30 @@ uint8_t XBOXONE::Init(uint8_t parent, uint8_t port, bool lowspeed) {
if(rcode) if(rcode)
goto FailSetDevTblEntry; goto FailSetDevTblEntry;
/* The application will work in reduced host mode, so we can save program and data num_of_conf = udd->bNumConfigurations; // Number of configurations
memory space. After verifying the VID we will use known values for the
configuration values for device, interface, endpoints and HID for the XBOXONE Controllers */
/* Initialize data structures for endpoints of device */ USBTRACE2("NC:", num_of_conf);
epInfo[ XBOX_OUTPUT_PIPE ].epAddr = 0x01; // XBOX one output endpoint
epInfo[ XBOX_OUTPUT_PIPE ].epAttribs = USB_TRANSFER_TYPE_INTERRUPT;
epInfo[ XBOX_OUTPUT_PIPE ].bmNakPower = USB_NAK_NOWAIT; // Only poll once for interrupt endpoints
epInfo[ XBOX_OUTPUT_PIPE ].maxPktSize = EP_MAXPKTSIZE;
epInfo[ XBOX_OUTPUT_PIPE ].bmSndToggle = 0;
epInfo[ XBOX_OUTPUT_PIPE ].bmRcvToggle = 0;
epInfo[ XBOX_INPUT_PIPE ].epAddr = 0x01; // XBOX one input endpoint
epInfo[ XBOX_INPUT_PIPE ].epAttribs = USB_TRANSFER_TYPE_INTERRUPT;
epInfo[ XBOX_INPUT_PIPE ].bmNakPower = USB_NAK_NOWAIT; // Only poll once for interrupt endpoints
epInfo[ XBOX_INPUT_PIPE ].maxPktSize = EP_MAXPKTSIZE;
epInfo[ XBOX_INPUT_PIPE ].bmSndToggle = 0;
epInfo[ XBOX_INPUT_PIPE ].bmRcvToggle = 0;
rcode = pUsb->setEpInfoEntry(bAddress, 3, epInfo); // Check if attached device is a Xbox One controller and fill endpoint data structure
for(uint8_t i = 0; i < num_of_conf; i++) {
ConfigDescParser<0, 0, 0, 0> confDescrParser(this); // Allow all devices, as we have already verified that it is a Xbox One controller from the VID and PID
rcode = pUsb->getConfDescr(bAddress, 0, i, &confDescrParser);
if(rcode) // Check error code
goto FailGetConfDescr;
if(bNumEP >= XBOX_ONE_MAX_ENDPOINTS) // All endpoints extracted
break;
}
if(bNumEP < XBOX_ONE_MAX_ENDPOINTS)
goto FailUnknownDevice;
rcode = pUsb->setEpInfoEntry(bAddress, bNumEP, epInfo);
if(rcode) if(rcode)
goto FailSetDevTblEntry; goto FailSetDevTblEntry;
delay(200); // Give time for address change delay(200); // Give time for address change
rcode = pUsb->setConf(bAddress, epInfo[ XBOX_CONTROL_PIPE ].epAddr, 1); rcode = pUsb->setConf(bAddress, epInfo[ XBOX_ONE_CONTROL_PIPE ].epAddr, bConfNum);
if(rcode) if(rcode)
goto FailSetConfDescr; goto FailSetConfDescr;
@ -176,7 +178,8 @@ uint8_t XBOXONE::Init(uint8_t parent, uint8_t port, bool lowspeed) {
delay(200); // let things settle delay(200); // let things settle
// initialize the controller for input // Initialize the controller for input
uint8_t writeBuf[5];
writeBuf[0] = 0x05; writeBuf[0] = 0x05;
writeBuf[1] = 0x20; writeBuf[1] = 0x20;
writeBuf[2] = 0x00; writeBuf[2] = 0x00;
@ -204,6 +207,12 @@ FailSetDevTblEntry:
goto Fail; goto Fail;
#endif #endif
FailGetConfDescr:
#ifdef DEBUG_USB_HOST
NotifyFailGetConfDescr();
goto Fail;
#endif
FailSetConfDescr: FailSetConfDescr:
#ifdef DEBUG_USB_HOST #ifdef DEBUG_USB_HOST
NotifyFailSetConfDescr(); NotifyFailSetConfDescr();
@ -225,11 +234,53 @@ Fail:
return rcode; return rcode;
} }
/* Extracts endpoint information from config descriptor */
void XBOXONE::EndpointXtract(uint8_t conf, uint8_t iface, uint8_t alt, uint8_t proto, const USB_ENDPOINT_DESCRIPTOR *pep) {
bConfNum = conf;
uint8_t index;
if((pep->bmAttributes & bmUSB_TRANSFER_TYPE) == USB_TRANSFER_TYPE_INTERRUPT) { // Interrupt endpoint
index = (pep->bEndpointAddress & 0x80) == 0x80 ? XBOX_ONE_INPUT_PIPE : XBOX_ONE_OUTPUT_PIPE; // Set the endpoint index
} else
return;
// Fill the rest of endpoint data structure
epInfo[index].epAddr = (pep->bEndpointAddress & 0x0F);
epInfo[index].maxPktSize = (uint8_t)pep->wMaxPacketSize;
#ifdef EXTRADEBUG
PrintEndpointDescriptor(pep);
#endif
if(pollInterval < pep->bInterval) // Set the polling interval as the largest polling interval obtained from endpoints
pollInterval = pep->bInterval;
bNumEP++;
}
void XBOXONE::PrintEndpointDescriptor(const USB_ENDPOINT_DESCRIPTOR* ep_ptr) {
#ifdef EXTRADEBUG
Notify(PSTR("\r\nEndpoint descriptor:"), 0x80);
Notify(PSTR("\r\nLength:\t\t"), 0x80);
D_PrintHex<uint8_t > (ep_ptr->bLength, 0x80);
Notify(PSTR("\r\nType:\t\t"), 0x80);
D_PrintHex<uint8_t > (ep_ptr->bDescriptorType, 0x80);
Notify(PSTR("\r\nAddress:\t"), 0x80);
D_PrintHex<uint8_t > (ep_ptr->bEndpointAddress, 0x80);
Notify(PSTR("\r\nAttributes:\t"), 0x80);
D_PrintHex<uint8_t > (ep_ptr->bmAttributes, 0x80);
Notify(PSTR("\r\nMaxPktSize:\t"), 0x80);
D_PrintHex<uint16_t > (ep_ptr->wMaxPacketSize, 0x80);
Notify(PSTR("\r\nPoll Intrv:\t"), 0x80);
D_PrintHex<uint8_t > (ep_ptr->bInterval, 0x80);
#endif
}
/* Performs a cleanup after failed Init() attempt */ /* Performs a cleanup after failed Init() attempt */
uint8_t XBOXONE::Release() { uint8_t XBOXONE::Release() {
XboxOneConnected = false; XboxOneConnected = false;
pUsb->GetAddressPool().FreeAddress(bAddress); pUsb->GetAddressPool().FreeAddress(bAddress);
bAddress = 0; bAddress = 0; // Clear device address
bNumEP = 1; // Must have to be reset to 1
qNextPollTime = 0; // Reset next poll time
pollInterval = 0;
bPollEnable = false; bPollEnable = false;
#ifdef DEBUG_USB_HOST #ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nXbox One Controller Disconnected\r\n"), 0x80); Notify(PSTR("\r\nXbox One Controller Disconnected\r\n"), 0x80);
@ -238,28 +289,36 @@ uint8_t XBOXONE::Release() {
} }
uint8_t XBOXONE::Poll() { uint8_t XBOXONE::Poll() {
uint8_t rcode = 0;
if(!bPollEnable) if(!bPollEnable)
return 0; return 0;
uint16_t BUFFER_SIZE = EP_MAXPKTSIZE;
uint8_t rcode = pUsb->inTransfer(bAddress, epInfo[ XBOX_INPUT_PIPE ].epAddr, &BUFFER_SIZE, readBuf); if((int32_t)((uint32_t)millis() - qNextPollTime) >= 0L) { // Do not poll if shorter than polling interval
if (!rcode) { qNextPollTime = (uint32_t)millis() + pollInterval; // Set new poll time
readReport(); uint16_t length = (uint16_t)epInfo[ XBOX_ONE_INPUT_PIPE ].maxPktSize; // Read the maximum packet size from the endpoint
#ifdef PRINTREPORT uint8_t rcode = pUsb->inTransfer(bAddress, epInfo[ XBOX_ONE_INPUT_PIPE ].epAddr, &length, readBuf, pollInterval);
printReport(); // Uncomment "#define PRINTREPORT" to print the report send by the Xbox ONE Controller if(!rcode) {
readReport();
#ifdef PRINTREPORT // Uncomment "#define PRINTREPORT" to print the report send by the Xbox ONE Controller
for(uint8_t i = 0; i < length; i++) {
D_PrintHex<uint8_t > (readBuf[i], 0x80);
Notify(PSTR(" "), 0x80);
}
Notify(PSTR("\r\n"), 0x80);
#endif #endif
} }
#ifdef DEBUG_USB_HOST #ifdef DEBUG_USB_HOST
else if (rcode != 0x04) { // not a matter of no update to send else if(rcode != hrNAK) { // Not a matter of no update to send
Notify(PSTR("\r\nXbox One Poll Failed, error code: "), 0x80); Notify(PSTR("\r\nXbox One Poll Failed, error code: "), 0x80);
NotifyFail(rcode); NotifyFail(rcode);
} }
#endif #endif
return rcode; }
return rcode;
} }
void XBOXONE::readReport() { void XBOXONE::readReport() {
if(readBuf == NULL)
return;
if(readBuf[0] == 0x07) { if(readBuf[0] == 0x07) {
// The XBOX button has a separate message // The XBOX button has a separate message
if(readBuf[4] == 1) if(readBuf[4] == 1)
@ -302,25 +361,13 @@ void XBOXONE::readReport() {
// Handle click detection for triggers // Handle click detection for triggers
if(triggerValue[0] != 0 && triggerValueOld[0] == 0) if(triggerValue[0] != 0 && triggerValueOld[0] == 0)
L2Clicked = true; L2Clicked = true;
triggerValueOld[0] = triggerValue[0]; triggerValueOld[0] = triggerValue[0];
if(triggerValue[1] != 0 && triggerValueOld[1] == 0) if(triggerValue[1] != 0 && triggerValueOld[1] == 0)
R2Clicked = true; R2Clicked = true;
triggerValueOld[1] = triggerValue[1]; triggerValueOld[1] = triggerValue[1];
} }
void XBOXONE::printReport() { //Uncomment "#define PRINTREPORT" to print the report send by the Xbox ONE Controller
#ifdef PRINTREPORT
if(readBuf == NULL)
return;
for(uint8_t i = 0; i < XBOX_REPORT_BUFFER_SIZE; i++) {
D_PrintHex<uint8_t > (readBuf[i], 0x80);
Notify(PSTR(" "), 0x80);
}
Notify(PSTR("\r\n"), 0x80);
#endif
}
uint16_t XBOXONE::getButtonPress(ButtonEnum b) { uint16_t XBOXONE::getButtonPress(ButtonEnum b) {
if(b == L2) // These are analog buttons if(b == L2) // These are analog buttons
return triggerValue[0]; return triggerValue[0];
@ -345,7 +392,7 @@ bool XBOXONE::getButtonClick(ButtonEnum b) {
} }
uint16_t button = pgm_read_word(&XBOX_BUTTONS[(uint8_t)b]); uint16_t button = pgm_read_word(&XBOX_BUTTONS[(uint8_t)b]);
bool click = (ButtonClickState & button); bool click = (ButtonClickState & button);
ButtonClickState &= ~button; // clear "click" event ButtonClickState &= ~button; // Clear "click" event
return click; return click;
} }
@ -355,7 +402,7 @@ int16_t XBOXONE::getAnalogHat(AnalogHatEnum a) {
/* Xbox Controller commands */ /* Xbox Controller commands */
uint8_t XBOXONE::XboxCommand(uint8_t* data, uint16_t nbytes) { uint8_t XBOXONE::XboxCommand(uint8_t* data, uint16_t nbytes) {
uint8_t rcode = pUsb->outTransfer(bAddress, epInfo[ XBOX_OUTPUT_PIPE ].epAddr, nbytes, data); uint8_t rcode = pUsb->outTransfer(bAddress, epInfo[ XBOX_ONE_OUTPUT_PIPE ].epAddr, nbytes, data);
#ifdef DEBUG_USB_HOST #ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nXboxCommand, Return: "), 0x80); Notify(PSTR("\r\nXboxCommand, Return: "), 0x80);
D_PrintHex<uint8_t > (rcode, 0x80); D_PrintHex<uint8_t > (rcode, 0x80);
@ -364,7 +411,8 @@ uint8_t XBOXONE::XboxCommand(uint8_t* data, uint16_t nbytes) {
} }
void XBOXONE::onInit() { void XBOXONE::onInit() {
// a short buzz to show the controller is active // A short buzz to show the controller is active
uint8_t writeBuf[11];
writeBuf[0] = 0x09; writeBuf[0] = 0x09;
writeBuf[1] = 0x08; writeBuf[1] = 0x08;
writeBuf[2] = 0x00; writeBuf[2] = 0x00;

View file

@ -26,15 +26,17 @@
#include "Usb.h" #include "Usb.h"
#include "xboxEnums.h" #include "xboxEnums.h"
/* Data Xbox ONE taken from descriptors */ /* Xbox One data taken from descriptors */
#define EP_MAXPKTSIZE 32 // max size for data via USB #define XBOX_ONE_EP_MAXPKTSIZE 64 // Max size for data via USB
/* Names we give to the 3 XboxONE pipes */ /* Names we give to the 3 XboxONE pipes */
#define XBOX_CONTROL_PIPE 0 #define XBOX_ONE_CONTROL_PIPE 0
#define XBOX_OUTPUT_PIPE 1 #define XBOX_ONE_OUTPUT_PIPE 1
#define XBOX_INPUT_PIPE 2 #define XBOX_ONE_INPUT_PIPE 2
// PID and VID of the different devices - see: https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c #define XBOX_ONE_MAX_ENDPOINTS 3
// PID and VID of the different versions of the controller - see: https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c
// Official controllers // Official controllers
#define XBOX_VID1 0x045E // Microsoft Corporation #define XBOX_VID1 0x045E // Microsoft Corporation
@ -59,12 +61,8 @@
#define XBOX_ONE_PID11 0x542A // Xbox ONE spectra #define XBOX_ONE_PID11 0x542A // Xbox ONE spectra
#define XBOX_ONE_PID12 0x543A // PowerA Xbox One wired controller #define XBOX_ONE_PID12 0x543A // PowerA Xbox One wired controller
#define XBOX_REPORT_BUFFER_SIZE 14 // Size of the input report buffer
#define XBOX_MAX_ENDPOINTS 3
/** This class implements support for a Xbox ONE controller connected via USB. */ /** This class implements support for a Xbox ONE controller connected via USB. */
class XBOXONE : public USBDeviceConfig { class XBOXONE : public USBDeviceConfig, public UsbConfigXtracter {
public: public:
/** /**
* Constructor for the XBOXONE class. * Constructor for the XBOXONE class.
@ -108,6 +106,14 @@ public:
return bPollEnable; return bPollEnable;
}; };
/**
* Read the poll interval taken from the endpoint descriptors.
* @return The poll interval in ms.
*/
uint8_t readPollInterval() {
return pollInterval;
};
/** /**
* Used by the USB core to check what this driver support. * Used by the USB core to check what this driver support.
* @param vid The device's VID. * @param vid The device's VID.
@ -161,7 +167,32 @@ protected:
/** Device address. */ /** Device address. */
uint8_t bAddress; uint8_t bAddress;
/** Endpoint info structure. */ /** Endpoint info structure. */
EpInfo epInfo[XBOX_MAX_ENDPOINTS]; EpInfo epInfo[XBOX_ONE_MAX_ENDPOINTS];
/** Configuration number. */
uint8_t bConfNum;
/** Total number of endpoints in the configuration. */
uint8_t bNumEP;
/** Next poll time based on poll interval taken from the USB descriptor. */
uint32_t qNextPollTime;
/** @name UsbConfigXtracter implementation */
/**
* UsbConfigXtracter implementation, used to extract endpoint information.
* @param conf Configuration value.
* @param iface Interface number.
* @param alt Alternate setting.
* @param proto Interface Protocol.
* @param ep Endpoint Descriptor.
*/
void EndpointXtract(uint8_t conf, uint8_t iface, uint8_t alt, uint8_t proto, const USB_ENDPOINT_DESCRIPTOR *ep);
/**@}*/
/**
* Used to print the USB Endpoint Descriptor.
* @param ep_ptr Pointer to USB Endpoint Descriptor.
*/
void PrintEndpointDescriptor(const USB_ENDPOINT_DESCRIPTOR* ep_ptr);
private: private:
/** /**
@ -171,6 +202,7 @@ private:
void onInit(); void onInit();
void (*pFuncOnInit)(void); // Pointer to function called in onInit() void (*pFuncOnInit)(void); // Pointer to function called in onInit()
uint8_t pollInterval;
bool bPollEnable; bool bPollEnable;
/* Variables to store the buttons */ /* Variables to store the buttons */
@ -184,11 +216,9 @@ private:
bool L2Clicked; // These buttons are analog, so we use we use these bools to check if they where clicked or not bool L2Clicked; // These buttons are analog, so we use we use these bools to check if they where clicked or not
bool R2Clicked; bool R2Clicked;
uint8_t readBuf[EP_MAXPKTSIZE]; // General purpose buffer for input data uint8_t readBuf[XBOX_ONE_EP_MAXPKTSIZE]; // General purpose buffer for input data
uint8_t writeBuf[12]; // General purpose buffer for output data
void readReport(); // read incoming data void readReport(); // Used to read the incoming data
void printReport(); // print incoming date - Uncomment for debugging
/* Private commands */ /* Private commands */
uint8_t XboxCommand(uint8_t* data, uint16_t nbytes); uint8_t XboxCommand(uint8_t* data, uint16_t nbytes);