无法在 Mac OS X 上使用 C + libusb 声明 USB 接口

Posted

技术标签:

【中文标题】无法在 Mac OS X 上使用 C + libusb 声明 USB 接口【英文标题】:Unable to claim USB interface with C + libusb on Mac OS X 【发布时间】:2013-12-13 17:41:33 【问题描述】:

我有一个使用 PIC32 微控制器构建的复合 USB + CDC 设备,我正在尝试连接到该设备并将一些数据从我的 Mac 发送到 CDC 数据接口端点。

我知道电路可以 100% 工作,因为设备注册为 HID 操纵杆,并且我可以使用 Zoc 终端在 /dev/tty.usbmodemfa132 上连接到设备。我可以使用Zoc 发送命令,然后通过闪烁电路上的一些 LED 来查看我的 MCU 对这些命令的响应。

我在 Mac OS X Mavericks 上运行它,但几周前在 Mountain Lion 上放弃了一个类似的例子,遇到了同样的问题。

我的代码如下:

// Includes -----------------------------------------------------------------------------------------------------------
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <libusb-1.0/libusb.h>
#include <unistd.h>

// Defines ------------------------------------------------------------------------------------------------------------
#define VID 0x04d8
#define PID 0x005e
#define CDC_DATA_INTERFACE_ID 2

// Function Declarations ----------------------------------------------------------------------------------------------
void print_device(libusb_device *device);
void send(libusb_context *usb_context, uint16_t vid, uint16_t pid);

// Function Definitions -----------------------------------------------------------------------------------------------

/**
 * main
 */
int main(int argc, char **argv)

    libusb_device **usb_devices = NULL;
    libusb_context *usb_context = NULL;
    ssize_t device_count = 0;
    bool debug_enabled = false;
    int c;

    // Collect command line attributes
    while ( (c = getopt(argc, argv, "d")) != -1) 
        switch (c) 
            case 'd':
                debug_enabled = true;
                break;
        
    

    // Initialize USB context
    int result = libusb_init(&usb_context);
    if(result < 0) 
        printf("Unable to initialise libusb!");
        return EXIT_FAILURE;
    

    // Turn debug mode on/off
    if(debug_enabled) 
        libusb_set_debug(usb_context, 3);
    

    // Get USB device list
    device_count = libusb_get_device_list(usb_context, &usb_devices);
    if(device_count < 0) 
        puts("Unable to retrieve USB device list!");
    

    // Iterate and print devices
    puts("VID    PID     Manufacturer Name\n------ ------ -------------------");
    for (int i = 0; i < device_count; i++) 
        print_device(usb_devices[i]);
    

    // Attempt to send data
    send(usb_context, VID, PID);

    // Cleanup and exit
    libusb_free_device_list(usb_devices, 1);
    libusb_exit(usb_context);
    return EXIT_SUCCESS;


/**
 * print_device
 */
void print_device(libusb_device *device)

    struct libusb_device_descriptor device_descriptor;
    struct libusb_device_handle *device_handle = NULL;

    // Get USB device descriptor
    int result = libusb_get_device_descriptor(device, &device_descriptor);
    if (result < 0) 
        printf("Failed to get device descriptor!");
    

    // Only print our devices
    if(VID == device_descriptor.idVendor && PID == device_descriptor.idProduct) 
        // Print VID & PID
        printf("0x%04x 0x%04x", device_descriptor.idVendor, device_descriptor.idProduct);
     else 
        return;
    

    // Attempt to open the device
    int open_result = libusb_open(device, &device_handle);
    if (open_result < 0) 
        libusb_close(device_handle);
        return;
    

    // Print the device manufacturer string
    char manufacturer[256] = " ";
    if (device_descriptor.iManufacturer) 
        libusb_get_string_descriptor_ascii(device_handle, device_descriptor.iManufacturer,
            (unsigned char *)manufacturer, sizeof(manufacturer));
        printf(" %s", manufacturer);
    

    puts("");

    libusb_close(device_handle);


/**
 * send
 */
void send(libusb_context *usb_context, uint16_t vid, uint16_t pid)

    libusb_device_handle *device_handle;
    device_handle = libusb_open_device_with_vid_pid(usb_context, vid, pid);

    if (device_handle == NULL) 
        puts("Unable to open device by VID & PID!");
        return;
    
    puts("Device successfully opened");

    unsigned char *data = (unsigned char *)"test";

    if (libusb_kernel_driver_active(device_handle, CDC_DATA_INTERFACE_ID)) 
        puts("Kernel driver active");
        if (libusb_detach_kernel_driver(device_handle, CDC_DATA_INTERFACE_ID)) 
            puts("Kernel driver detached");
        
     else 
        puts("Kernel driver doesn't appear to be active");
    

    int result = libusb_claim_interface(device_handle, CDC_DATA_INTERFACE_ID);
    if (result < 0) 
        puts("Unable to claim interface!");
        libusb_close(device_handle);
        return;
    
    puts("Interface claimed");

    int written = 0;
    result = libusb_bulk_transfer(device_handle, (3 | LIBUSB_ENDPOINT_OUT), data, 4, &written, 0);
    if (result == 0 && written == 4) 
        puts("Send success");
     else 
        puts("Send failed!");
    

    result = libusb_release_interface(device_handle, CDC_DATA_INTERFACE_ID);
    if (result != 0) 
        puts("Unable to release interface!");
    

    libusb_close(device_handle);

我收到以下错误输出:

libusb: 0.828223 error [darwin_open] USBDeviceOpen: another process has device opened for exclusive access
libusb: 0.828241 info [darwin_open] device open for access
Device successfully opened
Kernel driver doesn't appear to be active
libusb: 0.828641 error [darwin_claim_interface] USBInterfaceOpen: another process has device opened for exclusive access
Unable to claim interface!
libusb: 0.828766 info [event_thread_main] thread exiting

有没有办法让我从其他进程中释放 USB 设备,释放它以便我可以认领它?

是否有其他方法可以连接到 /dev/tty.usbmodemfa132 以向 USB 设备上的 CDC 接口发送和接收数据?

也许是 libusb 的替代品?

【问题讨论】:

尝试printf("Unable to claim interface: %s\n", libusb_error_name(result)); 以获取有关错误的更多信息。 我想我听说过一些关于 OSX 在其他类型的复合设备上也存在这种问题的消息——例如,也有 CDC 串行通道的程序员 找到不同的数据发送方式会更容易吗?像 zoc 这样的终端如何连接到 /dev/tty.usbmodema123?我敢打赌它不使用 libusb。 Mac 的唯一解决方案似乎是使用 C 中的 termios 库直接连接到 /dev/tty... 【参考方案1】:

没错。虽然 libusb 在 Linux 中似乎是无所不能的,但您不能使用它连接到 Mac OS X 上的 USB CDC 接口,因为 AppleUSBCDCACM 驱动程序已经声明了该接口。

您应该做的是使用人们连接到串行端口的标准方式。这会更容易,因为您不必担心端点和批量传输等。这是我为我们的一款基于 CDC 的产品编写的跨平台 C 代码示例,该产品连接到 COM 端口以读取和写入一些数据 (source)。它使用标准函数openreadwrite

// Uses POSIX functions to send and receive data from a Maestro.
// NOTE: You must change the 'const char * device' line below.

#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

#ifdef _WIN32
#define O_NOCTTY 0
#else
#include <termios.h>
#endif

// Gets the position of a Maestro channel.
// See the "Serial Servo Commands" section of the user's guide.
int maestroGetPosition(int fd, unsigned char channel)

  unsigned char command[] = 0x90, channel;
  if(write(fd, command, sizeof(command)) == -1)
  
    perror("error writing");
    return -1;
  

  unsigned char response[2];
  if(read(fd,response,2) != 2)
  
    perror("error reading");
    return -1;
  

  return response[0] + 256*response[1];


// Sets the target of a Maestro channel.
// See the "Serial Servo Commands" section of the user's guide.
// The units of 'target' are quarter-microseconds.
int maestroSetTarget(int fd, unsigned char channel, unsigned short target)

  unsigned char command[] = 0x84, channel, target & 0x7F, target >> 7 & 0x7F;
  if (write(fd, command, sizeof(command)) == -1)
  
    perror("error writing");
    return -1;
  
  return 0;


int main()

  // Open the Maestro's virtual COM port.
  const char * device = "\\\\.\\USBSER000";  // Windows, "\\\\.\\COM6" also works
  //const char * device = "/dev/ttyACM0";  // Linux
  //const char * device = "/dev/cu.usbmodem00034567"; // Mac OS X
  int fd = open(device, O_RDWR | O_NOCTTY);
  if (fd == -1)
  
    perror(device);
    return 1;
  

#ifndef _WIN32
  struct termios options;
  tcgetattr(fd, &options);
  options.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
  options.c_oflag &= ~(ONLCR | OCRNL);
  tcsetattr(fd, TCSANOW, &options);
#endif

  int position = maestroGetPosition(fd, 0);
  printf("Current position is %d.\n", position); 

  int target = (position < 6000) ? 7000 : 5000;
  printf("Setting target to %d (%d us).\n", target, target/4);
  maestroSetTarget(fd, 0, target);

  close(fd);
  return 0;

【讨论】:

哇,这比我想要的要多得多。谢谢大卫!我开始尝试查看是否可以使用 kextunload 来杀死 Mac 上的 USB CDC 驱动程序,但我认为这种方式使解决方案过于复杂。 我接受你的回答,即使我没有使用你的代码。这很有帮助,让我走上了正确的道路。我从en.wikibooks.org/wiki/Serial_Programming/Serial_Linux#termios 抓取了一个 C sn-p 并稍作修改。我的版本车见gist.github.com/josefvanniekerk/7702279 因为接受的答案更像是解决方法而不是对问题的回答,最终是否可以使用 libusb 来实现? 您可能会考虑的另一个选项是libserialport。我不知道自从我写完答案后是否有任何变化。 不幸的是,我需要测试 lisusb,因为它将被使用并且可以在 linux 上运行【参考方案2】:

如果你想使用苹果FTDI串口驱动也能识别的USB设备,可以先卸载驱动:

sudo kextunload -b com.apple.driver.AppleUSBFTDI

之后就可以通过libusb正常使用了。

对于被识别为串行设备的其他设备,您可能需要卸载一些其他驱动程序。

【讨论】:

【参考方案3】:

问题似乎是由于使用相同库的不同驱动程序之间存在冲突,在我的情况下,它们与以前的三星设备安装有关。我是这样解决的:

kextstat | grep -v apple

获得这样的回报:

  70    0 0x57574000 0x3000     0x2000     com.devguru.driver.SamsungComposite (1.2.4) <33 4 3>
  72    0 0x57831000 0x7000     0x6000     com.devguru.driver.SamsungACMData (1.2.4) <71 33 5 4 3>
  94    0 0x57674000 0x3000     0x2000     com.devguru.driver.SamsungACMControl (1.2.4) <33 4 3>

然后:

$ sudo kextunload -b com.devguru.driver.SamsungComposite
$ sudo kextunload -b com.devguru.driver.SamsungACMData
$ sudo kextunload -b com.devguru.driver.SamsungACMControl

完成。享受

【讨论】:

以上是关于无法在 Mac OS X 上使用 C + libusb 声明 USB 接口的主要内容,如果未能解决你的问题,请参考以下文章

无法在Mac OS X上找到正在侦听端口8001的进程

无法在 mac Os X 上使用 npm 安装 juggernaut - 版本 10.7.4

如何在 Mac OS X 终端上使用 make 编译 C 程序

无法在 Mac OS X 上启动 mysql 得到 mysql.sock 连接错误

Ruby cairo gem 无法在 Mac OS X Yosemite 上安装

在 Mac OS X 上清除缓冲区缓存