usb_bulk_msg 说明

本文详细介绍USB设备驱动程序的编写方法,包括usb_bulk_msg和usb_control_msg接口函数的使用,urb的初始化过程,以及如何编译配置和使用驱动模块。

usb_bulk_msg接口函数的定义如下:

  int usb_bulk_msg(struct usb_device *usb_dev,unsigned int pipe,

  void *data,int len,int *actual_length,int timeout);

  其参数为:

  struct usb_device *usb_dev:指向批量消息所发送的目标USB设备指针。

  unsigned int pipe:批量消息所发送目标USB设备的特定端点,此值是调用usb_sndbulkpipe或者usb_rcvbulkpipe来创建的。

  void *data:如果是一个OUT端点,它是指向即将发送到设备的数据的指针。如果是IN端点,它是指向从设备读取的数据应该存放的位置的指针。

  int len:data参数所指缓冲区的大小。

  int *actual_length:指向保存实际传输字节数的位置的指针,至于是传输到设备还是从设备接收取决于端点的方向。

  int timeout:以Jiffies为单位的等待的超时时间,如果该值为0,该函数一直等待消息的结束。

  如果该接口函数调用成功,返回值为0,否则返回一个负的错误值。

  usb_control_msg接口函数定义如下:

  int usb_control_msg(struct usb_device *dev,unsigned int pipe,__u8    request,__u8requesttype,__u16 value,__u16 index,void *data,__u16 size,int timeout)

  除了允许驱动程序发送和接收USB控制消息之外,usb_control_msg函数的运作和usb_bulk_msg函数类似,其参数和usb_bulk_msg的参数有几个重要区别:

  struct usb_device *dev:指向控制消息所发送的目标USB设备的指针。

  unsigned int pipe:控制消息所发送的目标USB设备的特定端点,该值是调用usb_sndctrlpipe或usb_rcvctrlpipe来创建的。

  __u8 request:控制消息的USB请求值。

  __u8 requesttype:控制消息的USB请求类型值。

  __u16 value:控制消息的USB消息值。

  __u16 index:控制消息的USB消息索引值。

  void *data:如果是一个OUT端点,它是指身即将发送到设备的数据的指针。如果是一个IN端点,它是指向从设备读取的数据应该存放的位置的指针。

  __u16 size:data参数所指缓冲区的大小。

  int timeout:以Jiffies为单位的应该等待的超时时间,如果为0,该函数将一直等待消息结束。

  如果该接口函数调用成功,返回传输到设备或者从设备读取的字节数;如果不成功它返回一个负的错误值。

  这两个接口函数都不能在一个中断上下文中或者持有自旋锁的情况下调用,同样,该函数也不能被任何其它函数取消,使用时要谨慎。

  我们要给未知的USB设备写驱动程序,只需要把这个框架程序稍做修改就可以用了,前面我们已经说过要修改制造商和产品的ID号,把0xfff0这两个值改为未知USB的ID号。

  #define USB_SKEL_VENDOR_ID      0xfff0

  #define USB_SKEL_PRODUCT_ID     0xfff0

  还有就是在探测函数中把需要探测的接口端点类型写好,在这个框架程序中只探测了批量(USB_ENDPOINT_XFER_BULK)IN和OUT端点,可以在此处使用掩码(USB_ENDPOINT_XFERTYPE_MASK)让其探测其它的端点类型,驱动程序会对USB设备的每一个接口进行一次探测,当探测成功后,驱动程序就被绑定到这个接口上。再有就是urb的初始化问题,如果你只写简单的USB驱动,这块不用多加考虑,框架程序里的东西已经够用了,这里我们简单介绍三个初始化urb的辅助函数:

  usb_fill_int_urb :它的函数原型是这样的:

  void usb_fill_int_urb(struct urb *urb,struct usb_device *dev,

  unsigned int pipe,void *transfer_buff,

  int buffer_length,usb_complete_t complete,

  void *context,int interval);

  这个函数用来正确的初始化即将被发送到USB设备的中断端点的urb。

  usb_fill_bulk_urb :它的函数原型是这样的:

  void usb_fill_bulk_urb(struct urb *urb,struct usb_device *dev,

  unsigned int pipe,void *transfer_buffer,

  int buffer_length,usb_complete_t complete)

  这个函数是用来正确的初始化批量urb端点的。

  usb_fill_control_urb :它的函数原型是这样的:

  void usb_fill_control_urb(struct urb *urb,struct usb_device *dev,unsigned int pipe,unsigned char *setup_packet,void *transfer_buffer,int buffer_length,usb_complete_t complete,void *context);

  这个函数是用来正确初始化控制urb端点的。

  还有一个初始化等时urb的,它现在还没有初始化函数,所以它们在被提交到USB核心前,必须在驱动程序中手工地进行初始化,可以参考内核源代码树下的/usr/src/~/drivers/usb/media下的konicawc.c文件。

  驱动模块的编译、配置和使用

  现在我们的驱动程序已经大体写好了,然后在linux下把它编译成模块就可以把驱动模块插入到内核中运行了,编译的Makefile文件可以这样来写:

  ifneq ($(KERNELRELEASE),)

         obj-m := xxx.o

  else

         KERNELDIR ?= /lib/modules/$(shell uname -r)/build

         PWD := $(shell pwd)

  default:

         $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

  endif

  clean:

         rm -rf *.mod.* *.o *.ko .*.ko.* .tmp* .*.mod.o.* .*.o.*

  其中xxx是源文件的文件名,在linux下直接执行make就可以生成驱动模块(xxx.ko)了。生成驱动模块后使用insmod xxx.ko就可以插入到内核中运行了,用lsmod可以看到你插入到内核中的模块,也可以从系统中用命令rmmod xxx把模块卸载掉;如果把编译出来的驱动模块拷贝到/lib/modules/~/kernel/drivers/usb/下,然后depmod一下,那么你在插入USB设备的时候,系统就会自动为你加载驱动模块的;当然这个得有hotplug的支持;加载驱动模块成功后就会在/dev/下生成设备文件了,如果用命令cat/proc/bus/usb/devices,我们可以看到驱动程序已经绑定到接口上了:

  T:  Bus="03" Lev="01" Prnt="01" Port="01" Cnt="01" Dev#=  2 Spd="12"  MxCh= 0

  D:  Ver= 1.10 Cls="02"(comm.) Sub="00" Prot="00" MxPS= 8 #Cfgs=  1

  P:  Vendor="1234" ProdID="2345" Rev= 1.10

  C:* #Ifs= 1 Cfg#= 1 Atr="c0" MxPwr=  0mA

  I:  If#= 1 Alt= 0 #EPs= 2 Cls="0a"(data ) Sub="00" Prot="00" Driver="test"_usb_driver /*我们的驱动*/

  E:  Ad="01"(O) Atr="02"(Bulk) MxPS=  64 Ivl="0ms"

  E:  A

d="82"(I) Atr="02"(Bulk) MxPS=  64 Ivl="0ms"

  此框架程序生成的是skel0(可以自由修改)的设备文件,现在就可以对这个设备文件进行打开、读写、关闭等的操作了。

  面对层出不穷的新的USB设备,必须有人不断编写新的驱动程序以便让这些设备能够在linux下正常的工作,从这个意义上讲,驱动程序的编写本身就是一件非常有意义的工作,本文只是起到一个抛砖引玉的作用,帮助那些有志于写驱动程序的开发人员进一步了解USB驱动程序的设计思路,从而吸引更多的人加入到这个队伍中来。linux不仅为我们提供了一个顶级质量的操作系统,而且也为我们提供了参与到其未来开发过程的机会,我们完全可以从中得到无尽的快乐!

#include <Arduino.h> #include <usb/usb_host.h> // USB标准描述符类型 #define USB_DEVICE_DESC 1 #define USB_CONFIGURATION_DESC 2 #define USB_INTERFACE_DESC 4 #define USB_ENDPOINT_DESC 5 // USB传输类型 //#define USB_BM_ATTRIBUTES_XFER_BULK 2 #define USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK 0x80 class MyUsbHost { private: usb_host_client_handle_t clientHandle = nullptr; bool currentInterfaceIsPrinter = false; uint8_t currentInterfaceNumber = 0; bool isTransferInProgress = false; uint8_t printerDeviceAddress = 0; public: bool printerReady = false; uint8_t printerEndpointOut = 0; uint8_t printerEndpointIn = 0; uint16_t printerVid = 0; uint16_t printerPid = 0; usb_device_handle_t printerDeviceHandle = nullptr; bool printerInterfaceFound = false; bool interfaceClaimed = false; void begin() { const usb_host_config_t config = { .skip_phy_setup = false, .intr_flags = ESP_INTR_FLAG_LEVEL1 }; esp_err_t err = usb_host_install(&config); if (err != ESP_OK) { Serial.printf("usb_host_install failed: %d\n", err); return; } Serial.println("USB Host initialized"); } void task() { // 处理USB主机事件 uint32_t event_flags; usb_host_lib_handle_events(0, &event_flags); if (clientHandle == nullptr) { usb_host_client_config_t client_config = { .is_synchronous = false, .max_num_event_msg = 10, .async = { .client_event_callback = client_event_callback, .callback_arg = this, } }; Serial.println("USB Host client config set"); esp_err_t err = usb_host_client_register(&client_config, &clientHandle); if (err != ESP_OK) { Serial.printf("usb_host_client_register failed: %d\n", err); return; } Serial.println("USB Host client registered"); } if (clientHandle != nullptr) { usb_host_client_handle_events(clientHandle, 0); } } private: static void client_event_callback(const usb_host_client_event_msg_t* event_msg, void* arg) { MyUsbHost* host = (MyUsbHost*)arg; Serial.printf("USB_HOST_CLIENT_EVENT: %d\n", event_msg->event); switch (event_msg->event) { case USB_HOST_CLIENT_EVENT_NEW_DEV: Serial.printf("USB_HOST_CLIENT_EVENT_NEW_DEV: address=%d\n", event_msg->new_dev.address); host->handleNewDevice(event_msg->new_dev.address); break; case USB_HOST_CLIENT_EVENT_DEV_GONE: Serial.println("USB_HOST_CLIENT_EVENT_DEV_GONE"); host->handleDeviceGone(); break; default: Serial.printf("Unknown event: %d\n", event_msg->event); break; } } void handleNewDevice(uint8_t dev_addr) { printerDeviceAddress = dev_addr; Serial.printf("New device connected with address: %d\n", dev_addr); // 根据设备地址打开设备以获取设备句柄 esp_err_t err = usb_host_device_open(clientHandle, dev_addr, &printerDeviceHandle); if (err != ESP_OK) { Serial.printf("Failed to open device: %d (%s)\n", err, esp_err_to_name(err)); return; } if (printerDeviceHandle == nullptr) { Serial.println("Device handle is null"); return; } Serial.printf("Device opened successfully. Address: %d, Handle: %p\n", dev_addr, printerDeviceHandle); // 获取设备描述符 const usb_device_desc_t* dev_desc; err = usb_host_get_device_descriptor(printerDeviceHandle, &dev_desc); if (err != ESP_OK) { Serial.printf("Failed to get device descriptor: %d (%s)\n", err, esp_err_to_name(err)); usb_host_device_close(clientHandle, printerDeviceHandle); printerDeviceHandle = nullptr; return; } printerVid = dev_desc->idVendor; printerPid = dev_desc->idProduct; Serial.printf("Device found: VID=0x%04x, PID=0x%04x\n", printerVid, printerPid); // 获取配置描述符 const usb_config_desc_t* config_desc; err = usb_host_get_active_config_descriptor(printerDeviceHandle, &config_desc); if (err != ESP_OK) { Serial.printf("Failed to get config descriptor: %d (%s)\n", err, esp_err_to_name(err)); usb_host_device_close(clientHandle, printerDeviceHandle); printerDeviceHandle = nullptr; return; } Serial.printf("Config descriptor length: %d\n", config_desc->wTotalLength); // 解析配置描述符 parseConfigDescriptor((uint8_t*)config_desc, config_desc->wTotalLength); // 如果找到了打印机接口,尝试claim接口 if (printerInterfaceFound && printerDeviceHandle != nullptr) { Serial.printf("Attempting to claim interface %d\n", currentInterfaceNumber); err = usb_host_interface_claim(clientHandle, printerDeviceHandle, currentInterfaceNumber, 0); if (err != ESP_OK) { Serial.printf("Failed to claim interface: %d (%s)\n", err, esp_err_to_name(err)); printerReady = false; } else { Serial.println("Interface claimed successfully"); interfaceClaimed = true; printerReady = true; } } } void handleDeviceGone() { Serial.println("Device disconnected"); resetPrinterState(); } void resetPrinterState() { printerReady = false; printerEndpointOut = 0; printerEndpointIn = 0; interfaceClaimed = false; printerInterfaceFound = false; currentInterfaceNumber = 0; currentInterfaceIsPrinter = false; isTransferInProgress = false; printerVid = 0; printerPid = 0; // 只在设备句柄有效时关闭设备 if (printerDeviceHandle != nullptr) { // 先释放声明的接口 if (interfaceClaimed) { usb_host_interface_release(clientHandle, printerDeviceHandle, currentInterfaceNumber); } usb_host_device_close(clientHandle, printerDeviceHandle); printerDeviceHandle = nullptr; } } void parseConfigDescriptor(uint8_t* desc, uint16_t len) { uint8_t* p = desc; uint16_t offset = 0; Serial.printf("Parsing config descriptor, total length: %d\n", len); // 重置打印机端点信息 printerEndpointOut = 0; printerEndpointIn = 0; printerInterfaceFound = false; currentInterfaceIsPrinter = false; uint8_t printerInterfaceNumber = 0; // 保存打印机接口编号 while (offset < len) { if (offset + 2 > len) { Serial.printf("End of descriptor at offset %d\n", offset); break; } uint8_t length = p[0]; uint8_t type = p[1]; if (length == 0) { Serial.println("Invalid descriptor length: 0"); break; } if (offset + length > len) { Serial.printf("Descriptor length %d exceeds total length at offset %d\n", length, offset); break; } Serial.printf("Descriptor: type=0x%02x, length=%d\n", type, length); if (type == USB_INTERFACE_DESC) { if (length < sizeof(usb_intf_desc_t)) { Serial.printf("Interface descriptor too short: %d\n", length); } else { usb_intf_desc_t* intf = (usb_intf_desc_t*)p; currentInterfaceNumber = intf->bInterfaceNumber; // 检查是否为标准打印机类 currentInterfaceIsPrinter = (intf->bInterfaceClass == 0x07); Serial.printf("Interface descriptor found: Interface=%d, Class=0x%02x, SubClass=0x%02x, Protocol=0x%02x\n", intf->bInterfaceNumber, intf->bInterfaceClass, intf->bInterfaceSubClass, intf->bInterfaceProtocol); if (currentInterfaceIsPrinter) { printerInterfaceFound = true; printerInterfaceNumber = currentInterfaceNumber; // 保存打印机接口编号 Serial.printf("Printer interface found: Interface=%d, Class=0x%02x, SubClass=0x%02x, Protocol=0x%02x\n", intf->bInterfaceNumber, intf->bInterfaceClass, intf->bInterfaceSubClass, intf->bInterfaceProtocol); } } } else if (type == USB_ENDPOINT_DESC) { if (length < sizeof(usb_ep_desc_t)) { Serial.printf("Endpoint descriptor too short: %d\n", length); } else { usb_ep_desc_t* ep_desc = (usb_ep_desc_t*)p; Serial.printf("Endpoint descriptor found for interface %d: Address=0x%02x, Attributes=0x%02x\n", currentInterfaceNumber, ep_desc->bEndpointAddress, ep_desc->bmAttributes); if ((ep_desc->bmAttributes & 0x03) == USB_BM_ATTRIBUTES_XFER_BULK) { if (currentInterfaceIsPrinter) { if (ep_desc->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) { printerEndpointIn = ep_desc->bEndpointAddress; Serial.printf("Printer IN endpoint found: 0x%02x\n", printerEndpointIn); } else { printerEndpointOut = ep_desc->bEndpointAddress; Serial.printf("Printer OUT endpoint found: 0x%02x\n", printerEndpointOut); } } else { Serial.printf("BULK endpoint for non-printer interface: 0x%02x\n", ep_desc->bEndpointAddress); } } } } p += length; offset += length; } // 检查是否找到了打印机接口和至少一个端点 if (printerInterfaceFound && (printerEndpointOut != 0 || printerEndpointIn != 0)) { currentInterfaceNumber = printerInterfaceNumber; // 使用打印机接口编号 printerReady = true; Serial.println("Printer is ready!"); if (printerEndpointOut != 0) { Serial.printf("Printer OUT endpoint: 0x%02x\n", printerEndpointOut); } if (printerEndpointIn != 0) { Serial.printf("Printer IN endpoint: 0x%02x\n", printerEndpointIn); } } else if (printerInterfaceFound) { Serial.println("Printer interface found but no BULK endpoints detected"); } else { Serial.println("No printer interface found"); } } static void transferCallback(usb_transfer_t *transfer) { MyUsbHost* host = (MyUsbHost*)transfer->context; host->isTransferInProgress = false; if (transfer->status == USB_TRANSFER_STATUS_COMPLETED) { Serial.printf("Transfer completed, %d bytes transferred\n", transfer->actual_num_bytes); } else { Serial.printf("Transfer failed with status: %d\n", transfer->status); } host->isTransferInProgress = false; usb_host_transfer_free(transfer); } public: // 打印数据到打印机(带重试机制) bool printData(const uint8_t* data, size_t length, int maxRetries = 3) { for (int attempt = 0; attempt < maxRetries; attempt++) { if (attempt > 0) { Serial.printf("Retry attempt %d/%d\n", attempt, maxRetries); delay(100 * attempt); } // 检查设备状态 if (!printerReady || printerEndpointOut == 0) { Serial.println("Printer not ready for printing"); Serial.printf("Printer ready: %s, Device handle: %p, Endpoint OUT: 0x%02x\n", printerReady ? "YES" : "NO", printerDeviceHandle, printerEndpointOut); // 尝试重新打开设备 if (printerDeviceHandle == nullptr && printerDeviceAddress != 0) { Serial.println("Attempting to re-open device..."); esp_err_t open_err = usb_host_device_open(clientHandle, printerDeviceAddress, &printerDeviceHandle); if (open_err != ESP_OK) { Serial.printf("Failed to re-open device: %d (%s)\n", open_err, esp_err_to_name(open_err)); // 如果无法重新打开设备,重置状态并返回 if (open_err == ESP_ERR_NOT_FOUND) { resetPrinterState(); } continue; } // 重新声明接口 if (!interfaceClaimed && printerDeviceHandle != nullptr) { Serial.println("Re-claiming interface..."); esp_err_t claim_err = usb_host_interface_claim(clientHandle, printerDeviceHandle, currentInterfaceNumber, 0); if (claim_err != ESP_OK) { Serial.printf("Failed to re-claim interface: %d (%s)\n", claim_err, esp_err_to_name(claim_err)); // 如果无法声明接口,关闭设备并继续尝试 usb_host_device_close(clientHandle, printerDeviceHandle); printerDeviceHandle = nullptr; continue; } else { Serial.println("Interface re-claimed successfully"); interfaceClaimed = true; printerReady = true; } } } // 如果仍然没有有效的设备句柄,返回失败 if (printerDeviceHandle == nullptr) { Serial.println("No valid device handle available"); continue; } } if (isTransferInProgress) { Serial.println("Transfer already in progress, please wait"); continue; } // 确保USB主机栈已处理所有事件 task(); // 检查设备是否仍然连接 if (printerDeviceHandle == nullptr) { Serial.println("Device handle became null before transfer"); continue; } usb_transfer_t* transfer; esp_err_t err = usb_host_transfer_alloc(length, 0, &transfer); if (err != ESP_OK) { Serial.printf("Failed to allocate transfer: %d (%s)\n", err, esp_err_to_name(err)); continue; } // 配置传输前再次检查设备句柄 if (printerDeviceHandle == nullptr) { Serial.println("Device handle became null before transfer configuration"); usb_host_transfer_free(transfer); continue; } // 配置传输 transfer->device_handle = printerDeviceHandle; transfer->bEndpointAddress = printerEndpointOut; transfer->num_bytes = length; transfer->callback = transferCallback; transfer->context = this; transfer->timeout_ms = 5000; // 复制数据到传输缓冲区 memcpy(transfer->data_buffer, data, length); isTransferInProgress = true; // 提交传输前再次检查设备句柄 if (printerDeviceHandle == nullptr) { Serial.println("Device handle became null before transfer submission"); isTransferInProgress = false; usb_host_transfer_free(transfer); continue; } // 提交传输 err = usb_host_transfer_submit(transfer); if (err != ESP_OK) { Serial.printf("Failed to submit transfer: %d (%s)\n", err, esp_err_to_name(err)); isTransferInProgress = false; usb_host_transfer_free(transfer); // 根据错误类型采取不同措施 switch (err) { case ESP_ERR_NOT_FOUND: Serial.println("Device not found during transfer submit"); // 设备可能已断开,重置状态 resetPrinterState(); return false; case ESP_ERR_INVALID_STATE: Serial.println("Invalid state during transfer submit"); // 尝试重置接口声明状态 if (interfaceClaimed && printerDeviceHandle != nullptr) { usb_host_interface_release(clientHandle, printerDeviceHandle, currentInterfaceNumber); interfaceClaimed = false; } // 重新声明接口 if (printerDeviceHandle != nullptr) { esp_err_t claim_err = usb_host_interface_claim(clientHandle, printerDeviceHandle, currentInterfaceNumber, 0); if (claim_err == ESP_OK) { Serial.println("Interface re-claimed successfully"); interfaceClaimed = true; } else { Serial.printf("Failed to re-claim interface: %d (%s)\n", claim_err, esp_err_to_name(claim_err)); } } continue; default: Serial.println("Other error during transfer submit"); continue; } } // 等待传输完成 uint32_t startTime = millis(); while (isTransferInProgress && (millis() - startTime < 6000)) { task(); delay(10); } if (!isTransferInProgress) { return true; } else { Serial.println("Transfer timeout"); isTransferInProgress = false; } } Serial.println("All print attempts failed"); return false; } // HP打印机初始化 bool hpInitialize() { // 发送HP标准重置命令 const uint8_t initCmd[] = "x1B@"; // HP Reset Serial.println("Sending HP reset command..."); if (printData(initCmd, sizeof(initCmd)-1)) { delay(100); return true; } return false; } // 打印文本 bool printText(const char* text) { return printData((const uint8_t*)text, strlen(text)); } // 打印简单文本页 bool printSimpleTestPage() { Serial.println("Sending simple test page..."); const char* testPage = "HP Deskjet 1212 Test Page\r\n" "=========================\r\n\r\n" "This is a simple test print from ESP32.\r\n" "Printer Model: HP Deskjet 1212\r\n" "Connection: USB\r\n" "Status: Connected and Ready\r\n\r\n" "If you can read this text, the test was successful!\r\n\r\n" "Timestamp: "; if (!printData((const uint8_t*)testPage, strlen(testPage))) return false; // 添加时间戳 char timestamp[20]; sprintf(timestamp, "%lu\r\n\r\n", millis()); if (!printData((const uint8_t*)timestamp, strlen(timestamp))) return false; // 表单进纸命令 const char* formFeed = "\r\n\r\n\x0C"; return printData((const uint8_t*)formFeed, strlen(formFeed)); } }; MyUsbHost usbHost; void setup() { Serial.begin(115200); Serial.println("HP Deskjet 1212 USB Printer Test"); Serial.println("================================="); Serial.println("Waiting for printer connection..."); usbHost.begin(); } void loop() { usbHost.task(); static bool testStarted = false; static uint32_t testStartTime = 0; if (usbHost.printerReady && !testStarted) { testStarted = true; testStartTime = millis(); Serial.println("=== PRINTER READY FOR TESTING ==="); // 初始化打印机 Serial.println("Initializing printer..."); if (usbHost.hpInitialize()) { Serial.println("Printer initialized successfully"); } else { Serial.println("Failed to initialize printer"); } delay(1000); // 打印简单测试页 Serial.println("Printing simple test page..."); if (usbHost.printSimpleTestPage()) { Serial.println("Simple test page sent to printer"); } else { Serial.println("Failed to send simple test page"); } Serial.println("=== PRINT TESTING COMPLETED ==="); } delay(100); } 运行结果如下: 11:33:54.911 -> Descriptor: type=0x04, length=9 11:33:54.911 -> Interface descriptor found: Interface=2, Class=0xff, SubClass=0x04, Protocol=0x01 11:33:54.945 -> Descriptor: type=0x05, length=7 11:33:54.945 -> Endpoint descriptor found for interface 2: Address=0x0a, Attributes=0x02 11:33:54.945 -> BULK endpoint for non-printer interface: 0x0a 11:33:54.945 -> Descriptor: type=0x05, length=7 11:33:54.945 -> Endpoint descriptor found for interface 2: Address=0x8b, Attributes=0x02 11:33:54.945 -> BULK endpoint for non-printer interface: 0x8b 11:33:54.945 -> Printer is ready! 11:33:54.945 -> Printer OUT endpoint: 0x08 11:33:54.945 -> Printer IN endpoint: 0x89 11:33:54.988 -> Attempting to claim interface 0 11:33:54.988 -> Interface claimed successfully 11:33:54.988 -> === PRINTER READY FOR TESTING === 11:33:54.988 -> Initializing printer... 11:33:54.988 -> Sending HP reset command... 11:33:54.988 -> Transfer completed, 4 bytes transferred 11:33:55.108 -> Printer initialized successfully 11:33:56.121 -> Printing simple test page... 11:33:56.121 -> Sending simple test page... 11:33:56.121 -> Transfer completed, 243 bytes transferred 11:33:56.121 -> Transfer completed, 8 bytes transferred 11:33:56.167 -> Transfer completed, 5 bytes transferred 11:33:56.167 -> Simple test page sent to printer 11:33:56.167 -> === PRINT TESTING COMPLETED === 优化、简化一下
最新发布
09-02
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值