基于WSL2调试CDC设备-Windows下正常Linux下不工作案例分享

原创 嵌入式Lee 2025-01-09 16:04

.前言

前面我们在https://mp.weixin.qq.com/s/s3uC-SHaVcXWAZK1sK6EZw?token=6568576&lang=zh_CN

WSL2中配置支持UVC》一文中,分享了使用usbipd绑定USBWSL中,并重新编译WSL2内核以支持UVC。基于此,我们能随意修改内核重新编译加载新的内核运行,这样我们就可以方便的在PC上进行USB相关的调试了。上一篇分享了调试UVC,这一篇再来分享一个调试CDC的案例。

案例的问题是一个CDC设备在Windows下能够工作,Linux下不能工作,下面就来分享调试过程。

.使能CDC调试信息

老规矩,第一步是找到对应的驱动的位置打开调试信息,增加自己的调试信息跟踪数据和执行流。CDC的驱动代码位于drivers/usb/class/cdc-acm.c

我们把最开头的

#undef DEBUG

#undef VERBOSE_DEBUG

改为

#define DEBUG

#define VERBOSE_DEBUG

再按照前文介绍的,重新编译内核并替换。

编译内核

sudo make -j$(nproc) KCONFIG_CONFIG=.config && sudo make modules_install -j$(nproc) && sudo make install -j$(nproc)

复制内核到windows下,复制到自己的WSL的安装目录即可。

sudo cp vmlinux /mnt/d/WSL/Ubuntu2204-221101/Ubuntu_2204.1.7.0_x64

修改windows下用户目录下的.wslconfig文件(以下去windows下操作也可以)

nano /mnt/c/Users/qinyu/.wslconfig

添加如下内容(ctrl+o是保存,ctrl+x是退出)

[wsl2]

kernel=D:\\WSL\\Ubuntu2204-221101\\Ubuntu_2204.1.7.0_x64\\vmlinux

关闭所有WSL终端和程序

wsl --shutdown

重新打开WSL终端

uname -r -a查看编译时间是否更新,如果没有可以重启下电脑试一下。

sudo dmesg -c清除信息

usbipd bind -b x-x

usbipd attach -w -b x-x 链接设备到WSL

此时WSLdmesg查看

[  447.668696] vhci_hcd vhci_hcd.0pdev(0rhport(0sockfd(3)[  447.670050] vhci_hcd vhci_hcd.0devid(196610speed(3speed_str(high-speed)[  447.672172] vhci_hcd vhci_hcd.0: Device attached[  448.020519] usb 1-1: new high-speed USB device number 2 using vhci_hcd[  448.170543] usb 1-1: SetAddress Request (2) to port 0[  448.208695] usb 1-1: New USB device found, idVendor=1993, idProduct=0101, bcdDevice= 1.00[  448.209585] usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3[  448.210382] usb 1-1: Product: xxxxSpecificDevice[  448.210935] usb 1-1: Manufacturer: xxxx Team.[  448.211560] usb 1-1: SerialNumber: 0.1[  448.213988] cdc_acm: probe of 1-1:1.0 failed with error -22qinyunti@qinyunti:~$

先找到驱动入口

acm_probe

添加打印信息看是否执行到这里

我们看到执行到了这里

如果没有打印可以尝试修改printkdebug输出等级。echo 8 /proc/sys/kernel/printk

.在关键位置添加打印信息

上面打印信息可以看到,后面打印了错误

那么我们就继续添加打印信息,跟踪到底是哪里开始出错的。


static int acm_probe(struct usb_interface *intf,             const struct usb_device_id *id){    struct usb_cdc_union_desc *union_header = NULL;    struct usb_cdc_call_mgmt_descriptor *cmgmd = NULL;    unsigned char *buffer = intf->altsetting->extra;    int buflen = intf->altsetting->extralen;    struct usb_interface *control_interface;    struct usb_interface *data_interface;    struct usb_endpoint_descriptor *epctrl = NULL;    struct usb_endpoint_descriptor *epread = NULL;    struct usb_endpoint_descriptor *epwrite = NULL;    struct usb_device *usb_dev = interface_to_usbdev(intf);    struct usb_cdc_parsed_header h;    struct acm *acm;    int minor;    int ctrlsize, readsize;    u8 *buf;    int call_intf_num = -1;    int data_intf_num = -1;    unsigned long quirks;    int num_rx_buf;    int i;    int combined_interfaces = 0;    struct device *tty_dev;    int rv = -ENOMEM;    int res;    dev_dbg(&intf->dev,"acm_probe ...\n");    /* normal quirks */    quirks = (unsigned long)id->driver_info;    if (quirks == IGNORE_DEVICE){        dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);        return -ENODEV;    }    memset(&h, 0x00sizeof(struct usb_cdc_parsed_header));    num_rx_buf = (quirks == SINGLE_RX_URB) ? 1 : ACM_NR;    /* handle quirks deadly to normal probing*/    if (quirks == NO_UNION_NORMAL) {        data_interface = usb_ifnum_to_if(usb_dev, 1);        control_interface = usb_ifnum_to_if(usb_dev, 0);        /* we would crash */        if (!data_interface || !control_interface){            dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);            return -ENODEV;        }        dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);            goto skip_normal_probe;    }    /* normal probing*/    if (!buffer) {        dev_err(&intf->dev, "Weird descriptor references\n");        return -EINVAL;    }    if (!buflen) {        if (intf->cur_altsetting->endpoint &&                intf->cur_altsetting->endpoint->extralen &&                intf->cur_altsetting->endpoint->extra) {            dev_dbg(&intf->dev,                "Seeking extra descriptors on endpoint\n");            buflen = intf->cur_altsetting->endpoint->extralen;            buffer = intf->cur_altsetting->endpoint->extra;        } else {            dev_err(&intf->dev,                "Zero length descriptor references\n");            return -EINVAL;        }    }    cdc_parse_cdc_header(&h, intf, buffer, buflen);    union_header = h.usb_cdc_union_desc;    cmgmd = h.usb_cdc_call_mgmt_descriptor;    if (cmgmd){        call_intf_num = cmgmd->bDataInterface;        dev_dbg(&intf->dev,"%s %d call_intf_num=%d\n",__FILE__,__LINE__,call_intf_num);    }    if (!union_header) {        if (intf->cur_altsetting->desc.bNumEndpoints == 3) {            dev_dbg(&intf->dev, "No union descriptor, assuming single interface\n");            combined_interfaces = 1;            control_interface = data_interface = intf;            goto look_for_collapsed_interface;        } else if (call_intf_num > 0) {            dev_dbg(&intf->dev, "No union descriptor, using call management descriptor\n");            data_intf_num = call_intf_num;            data_interface = usb_ifnum_to_if(usb_dev, data_intf_num);            control_interface = intf;        } else {            dev_dbg(&intf->dev, "No union descriptor, giving up\n");            return -ENODEV;        }    } else {        int class = -1;        data_intf_num = union_header->bSlaveInterface0;        control_interface = usb_ifnum_to_if(usb_dev, union_header->bMasterInterface0);        data_interface = usb_ifnum_to_if(usb_dev, data_intf_num);        if (control_interface){            class = control_interface->cur_altsetting->desc.bInterfaceClass;            dev_dbg(&intf->dev,"%s %d class=%d\n",__FILE__,__LINE__,class);        }        if (class != USB_CLASS_COMM && class != USB_CLASS_CDC_DATA) {            dev_dbg(&intf->dev, "Broken union descriptor, assuming single interface\n");            combined_interfaces = 1;            control_interface = data_interface = intf;            goto look_for_collapsed_interface;        }    }    if (!control_interface || !data_interface) {        dev_dbg(&intf->dev, "no interfaces\n");        return -ENODEV;    }    if (data_intf_num != call_intf_num)        dev_dbg(&intf->dev, "Separate call control interface. That is not fully supported.\n");    if (control_interface == data_interface) {        /* some broken devices designed for windows work this way */        dev_warn(&intf->dev,"Control and data interfaces are not separated!\n");        combined_interfaces = 1;        /* a popular other OS doesn't use it */        quirks |= NO_CAP_LINE;        if (data_interface->cur_altsetting->desc.bNumEndpoints != 3) {            dev_err(&intf->dev, "This needs exactly 3 endpoints\n");            return -EINVAL;        }look_for_collapsed_interface:        res = usb_find_common_endpoints(data_interface->cur_altsetting,                &epread, &epwrite, &epctrl, NULL);        if (res)            return res;        goto made_compressed_probe;    }skip_normal_probe:    /*workaround for switched interfaces */    if (data_interface->cur_altsetting->desc.bInterfaceClass != USB_CLASS_CDC_DATA) {        if (control_interface->cur_altsetting->desc.bInterfaceClass == USB_CLASS_CDC_DATA) {            dev_dbg(&intf->dev,                "Your device has switched interfaces.\n");            swap(control_interface, data_interface);        } else {            dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);            return -EINVAL;        }    }    /* Accept probe requests only for the control interface */    if (!combined_interfaces && intf != control_interface){        dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);        return -ENODEV;    }    if (data_interface->cur_altsetting->desc.bNumEndpoints < 2 ||        control_interface->cur_altsetting->desc.bNumEndpoints == 0){        dev_dbg(&intf->dev,"%s %d %d %d\n",__FILE__,__LINE__,data_interface->cur_altsetting->desc.bNumEndpoints,control_interface->cur_altsetting->desc.bNumEndpoints);        return -EINVAL;    }    epctrl = &control_interface->cur_altsetting->endpoint[0].desc;    epread = &data_interface->cur_altsetting->endpoint[0].desc;    epwrite = &data_interface->cur_altsetting->endpoint[1].desc;    /* workaround for switched endpoints */    if (!usb_endpoint_dir_in(epread)) {        /* descriptors are swapped */        dev_dbg(&intf->dev,            "The data interface has switched endpoints\n");        swap(epread, epwrite);    }made_compressed_probe:    dev_dbg(&intf->dev, "interfaces are valid\n");    acm = kzalloc(sizeof(struct acm), GFP_KERNEL);    if (!acm){        dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);        return -ENOMEM;    }    tty_port_init(&acm->port);    acm->port.ops = &acm_port_ops;    ctrlsize = usb_endpoint_maxp(epctrl);    readsize = usb_endpoint_maxp(epread) *                (quirks == SINGLE_RX_URB ? 1 : 2);    acm->combined_interfaces = combined_interfaces;    acm->writesize = usb_endpoint_maxp(epwrite) * 20;    acm->control = control_interface;    acm->data = data_interface;    usb_get_intf(acm->control); /* undone in destruct() */    minor = acm_alloc_minor(acm);    if (minor < 0) {        acm->minor = ACM_MINOR_INVALID;        dev_dbg(&intf->dev,"%s %d minor=%d\n",__FILE__,__LINE__,minor);        goto err_put_port;    }    acm->minor = minor;    acm->dev = usb_dev;    if (h.usb_cdc_acm_descriptor)        acm->ctrl_caps = h.usb_cdc_acm_descriptor->bmCapabilities;    if (quirks & NO_CAP_LINE)        acm->ctrl_caps &= ~USB_CDC_CAP_LINE;    acm->ctrlsize = ctrlsize;    acm->readsize = readsize;    acm->rx_buflimit = num_rx_buf;    INIT_DELAYED_WORK(&acm->dwork, acm_softint);    init_waitqueue_head(&acm->wioctl);    spin_lock_init(&acm->write_lock);    spin_lock_init(&acm->read_lock);    mutex_init(&acm->mutex);    if (usb_endpoint_xfer_int(epread)) {        acm->bInterval = epread->bInterval;        acm->in = usb_rcvintpipe(usb_dev, epread->bEndpointAddress);    } else {        acm->in = usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress);    }    if (usb_endpoint_xfer_int(epwrite))        acm->out = usb_sndintpipe(usb_dev, epwrite->bEndpointAddress);    else        acm->out = usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress);    init_usb_anchor(&acm->delayed);    acm->quirks = quirks;    buf = usb_alloc_coherent(usb_dev, ctrlsize, GFP_KERNEL, &acm->ctrl_dma);    if (!buf)        goto err_put_port;    acm->ctrl_buffer = buf;    if (acm_write_buffers_alloc(acm) < 0){        dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);        goto err_free_ctrl_buffer;    }    acm->ctrlurb = usb_alloc_urb(0, GFP_KERNEL);    if (!acm->ctrlurb){        dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);        goto err_free_write_buffers;    }    for (i = 0; i < num_rx_buf; i++) {        struct acm_rb *rb = &(acm->read_buffers[i]);        struct urb *urb;        rb->base = usb_alloc_coherent(acm->dev, readsize, GFP_KERNEL,                                &rb->dma);        if (!rb->base){            dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);            goto err_free_read_urbs;        }        rb->index = i;        rb->instance = acm;        urb = usb_alloc_urb(0, GFP_KERNEL);        if (!urb){            dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);            goto err_free_read_urbs;        }        urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;        urb->transfer_dma = rb->dma;        if (usb_endpoint_xfer_int(epread))            usb_fill_int_urb(urb, acm->dev, acm->in, rb->base,                     acm->readsize,                     acm_read_bulk_callback, rb,                     acm->bInterval);        else            usb_fill_bulk_urb(urb, acm->dev, acm->in, rb->base,                      acm->readsize,                      acm_read_bulk_callback, rb);        acm->read_urbs[i] = urb;        __set_bit(i, &acm->read_urbs_free);    }    for (i = 0; i < ACM_NW; i++) {        struct acm_wb *snd = &(acm->wb[i]);        snd->urb = usb_alloc_urb(0, GFP_KERNEL);        if (!snd->urb)            goto err_free_write_urbs;        if (usb_endpoint_xfer_int(epwrite))            usb_fill_int_urb(snd->urb, usb_dev, acm->out,                NULL, acm->writesize, acm_write_bulk, snd, epwrite->bInterval);        else            usb_fill_bulk_urb(snd->urb, usb_dev, acm->out,                NULL, acm->writesize, acm_write_bulk, snd);        snd->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;        if (quirks & SEND_ZERO_PACKET)            snd->urb->transfer_flags |= URB_ZERO_PACKET;        snd->instance = acm;    }    usb_set_intfdata(intf, acm);    i = device_create_file(&intf->dev, &dev_attr_bmCapabilities);    if (i < 0){        dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);        goto err_free_write_urbs;    }    if (h.usb_cdc_country_functional_desc) { /* export the country data */        struct usb_cdc_country_functional_desc * cfd =                    h.usb_cdc_country_functional_desc;        acm->country_codes = kmalloc(cfd->bLength - 4, GFP_KERNEL);        if (!acm->country_codes){            dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);            goto skip_countries;        }        acm->country_code_size = cfd->bLength - 4;        memcpy(acm->country_codes, (u8 *)&cfd->wCountyCode0,                            cfd->bLength - 4);        acm->country_rel_date = cfd->iCountryCodeRelDate;        i = device_create_file(&intf->dev, &dev_attr_wCountryCodes);        if (i < 0) {            kfree(acm->country_codes);            acm->country_codes = NULL;            acm->country_code_size = 0;            goto skip_countries;        }        i = device_create_file(&intf->dev,                        &dev_attr_iCountryCodeRelDate);        if (i < 0) {            device_remove_file(&intf->dev, &dev_attr_wCountryCodes);            kfree(acm->country_codes);            acm->country_codes = NULL;            acm->country_code_size = 0;            dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);            goto skip_countries;        }    }skip_countries:    usb_fill_int_urb(acm->ctrlurb, usb_dev,             usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress),             acm->ctrl_buffer, ctrlsize, acm_ctrl_irq, acm,             /* works around buggy devices */             epctrl->bInterval ? epctrl->bInterval : 16);    acm->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;    acm->ctrlurb->transfer_dma = acm->ctrl_dma;    acm->notification_buffer = NULL;    acm->nb_index = 0;    acm->nb_size = 0;    acm->line.dwDTERate = cpu_to_le32(9600);    acm->line.bDataBits = 8;    acm_set_line(acm, &acm->line);    if (!acm->combined_interfaces) {        rv = usb_driver_claim_interface(&acm_driver, data_interface, acm);        if (rv){            dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);            goto err_remove_files;        }    }    tty_dev = tty_port_register_device(&acm->port, acm_tty_driver, minor,            &control_interface->dev);    if (IS_ERR(tty_dev)) {        rv = PTR_ERR(tty_dev);        dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);        goto err_release_data_interface;    }    if (quirks & CLEAR_HALT_CONDITIONS) {        usb_clear_halt(usb_dev, acm->in);        usb_clear_halt(usb_dev, acm->out);    }    dev_info(&intf->dev, "ttyACM%d: USB ACM device\n", minor);    return 0;err_release_data_interface:    if (!acm->combined_interfaces) {        /* Clear driver data so that disconnect() returns early. */        usb_set_intfdata(data_interface, NULL);        usb_driver_release_interface(&acm_driver, data_interface);    }err_remove_files:    if (acm->country_codes) {        device_remove_file(&acm->control->dev,                &dev_attr_wCountryCodes);        device_remove_file(&acm->control->dev,                &dev_attr_iCountryCodeRelDate);    }    device_remove_file(&acm->control->dev, &dev_attr_bmCapabilities);err_free_write_urbs:    for (i = 0; i < ACM_NW; i++)        usb_free_urb(acm->wb[i].urb);err_free_read_urbs:    for (i = 0; i < num_rx_buf; i++)        usb_free_urb(acm->read_urbs[i]);    acm_read_buffers_free(acm);    usb_free_urb(acm->ctrlurb);err_free_write_buffers:    acm_write_buffers_free(acm);err_free_ctrl_buffer:    usb_free_coherent(usb_dev, ctrlsize, acm->ctrl_buffer, acm->ctrl_dma);err_put_port:    tty_port_put(&acm->port);    return rv;}

此时再来看打印信息


[   16.623759] vhci_hcd vhci_hcd.0pdev(0rhport(0sockfd(3)[   16.624646] vhci_hcd vhci_hcd.0devid(196610speed(3speed_str(high-speed)[   16.625829] vhci_hcd vhci_hcd.0Device attached[   16.970553] usb 1-1new high-speed USB device number 2 using vhci_hcd[   17.120556] usb 1-1SetAddress Request (2) to port 0[   17.157661] usb 1-1New USB device found, idVendor=1993, idProduct=0101, bcdDevice= 1.00[   17.158611] usb 1-1New USB device stringsMfr=1Product=2SerialNumber=3[   17.159421] usb 1-1Product: xxxxSpecificDevice[   17.159979] usb 1-1Manufacturer: xxxx Team.[   17.160426] usb 1-1SerialNumber0.1[   17.162352] cdc_acm 1-1:1.0: acm_probe ...[   17.162842] cdc_acm 1-1:1.0: drivers/usb/class/cdc-acm.c 1233 call_intf_num=1[   17.163594] cdc_acm 1-1:1.0: drivers/usb/class/cdc-acm.c 1260 class=2[   17.164254] cdc_acm 1-1:1.0: drivers/usb/class/cdc-acm.c 1320 2 0[   17.164876cdc_acm: probe of 1-1:1.0 failed with error -22qinyunti@qinyunti:~$

可以看到正是如下位置,EINVAL返回值就是22

此时data_interface->cur_altsetting->desc.bNumEndpoints0

control_interface->cur_altsetting->desc.bNumEndpoints0,也就是控制接口没有端点。

if (data_interface->cur_altsetting->desc.bNumEndpoints < 2 ||        control_interface->cur_altsetting->desc.bNumEndpoints == 0){        dev_dbg(&intf->dev,"%s %d %d %d\n",__FILE__,__LINE__,data_interface->cur_altsetting->desc.bNumEndpoints,control_interface->cur_altsetting->desc.bNumEndpoints);        return -EINVAL;    }

四. 解决方法

4.1修改内核驱动

如果可以修改内核,简单粗暴,直接修改如下

将控制接口端点相关的处理加上条件判断,有控制接口端点才处理,否则不处理

static int acm_probe(struct usb_interface *intf,             const struct usb_device_id *id){    struct usb_cdc_union_desc *union_header = NULL;    struct usb_cdc_call_mgmt_descriptor *cmgmd = NULL;    unsigned char *buffer = intf->altsetting->extra;    int buflen = intf->altsetting->extralen;    struct usb_interface *control_interface;    struct usb_interface *data_interface;    struct usb_endpoint_descriptor *epctrl = NULL;    struct usb_endpoint_descriptor *epread = NULL;    struct usb_endpoint_descriptor *epwrite = NULL;    struct usb_device *usb_dev = interface_to_usbdev(intf);    struct usb_cdc_parsed_header h;    struct acm *acm;    int minor;    int ctrlsize, readsize;    u8 *buf;    int call_intf_num = -1;    int data_intf_num = -1;    unsigned long quirks;    int num_rx_buf;    int i;    int combined_interfaces = 0;    struct device *tty_dev;    int rv = -ENOMEM;    int res;    dev_dbg(&intf->dev,"acm_probe ...\n");    /* normal quirks */    quirks = (unsigned long)id->driver_info;    if (quirks == IGNORE_DEVICE){        dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);        return -ENODEV;    }    memset(&h, 0x00sizeof(struct usb_cdc_parsed_header));    num_rx_buf = (quirks == SINGLE_RX_URB) ? 1 : ACM_NR;    /* handle quirks deadly to normal probing*/    if (quirks == NO_UNION_NORMAL) {        data_interface = usb_ifnum_to_if(usb_dev, 1);        control_interface = usb_ifnum_to_if(usb_dev, 0);        /* we would crash */        if (!data_interface || !control_interface){            dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);            return -ENODEV;        }        dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);            goto skip_normal_probe;    }    /* normal probing*/    if (!buffer) {        dev_err(&intf->dev, "Weird descriptor references\n");        return -EINVAL;    }    if (!buflen) {        if (intf->cur_altsetting->endpoint &&                intf->cur_altsetting->endpoint->extralen &&                intf->cur_altsetting->endpoint->extra) {            dev_dbg(&intf->dev,                "Seeking extra descriptors on endpoint\n");            buflen = intf->cur_altsetting->endpoint->extralen;            buffer = intf->cur_altsetting->endpoint->extra;        } else {            dev_err(&intf->dev,                "Zero length descriptor references\n");            return -EINVAL;        }    }    cdc_parse_cdc_header(&h, intf, buffer, buflen);    union_header = h.usb_cdc_union_desc;    cmgmd = h.usb_cdc_call_mgmt_descriptor;    if (cmgmd){        call_intf_num = cmgmd->bDataInterface;        dev_dbg(&intf->dev,"%s %d call_intf_num=%d\n",__FILE__,__LINE__,call_intf_num);    }    if (!union_header) {        if (intf->cur_altsetting->desc.bNumEndpoints == 3) {            dev_dbg(&intf->dev, "No union descriptor, assuming single interface\n");            combined_interfaces = 1;            control_interface = data_interface = intf;            goto look_for_collapsed_interface;        } else if (call_intf_num > 0) {            dev_dbg(&intf->dev, "No union descriptor, using call management descriptor\n");            data_intf_num = call_intf_num;            data_interface = usb_ifnum_to_if(usb_dev, data_intf_num);            control_interface = intf;        } else {            dev_dbg(&intf->dev, "No union descriptor, giving up\n");            return -ENODEV;        }    } else {        int class = -1;        data_intf_num = union_header->bSlaveInterface0;        control_interface = usb_ifnum_to_if(usb_dev, union_header->bMasterInterface0);        data_interface = usb_ifnum_to_if(usb_dev, data_intf_num);        if (control_interface){            class = control_interface->cur_altsetting->desc.bInterfaceClass;            dev_dbg(&intf->dev,"%s %d class=%d\n",__FILE__,__LINE__,class);        }        if (class != USB_CLASS_COMM && class != USB_CLASS_CDC_DATA) {            dev_dbg(&intf->dev, "Broken union descriptor, assuming single interface\n");            combined_interfaces = 1;            control_interface = data_interface = intf;            goto look_for_collapsed_interface;        }    }    if (!control_interface || !data_interface) {        dev_dbg(&intf->dev, "no interfaces\n");        return -ENODEV;    }    if (data_intf_num != call_intf_num)        dev_dbg(&intf->dev, "Separate call control interface. That is not fully supported.\n");    if (control_interface == data_interface) {        /* some broken devices designed for windows work this way */        dev_warn(&intf->dev,"Control and data interfaces are not separated!\n");        combined_interfaces = 1;        /* a popular other OS doesn't use it */        quirks |= NO_CAP_LINE;        if (data_interface->cur_altsetting->desc.bNumEndpoints != 3) {            dev_err(&intf->dev, "This needs exactly 3 endpoints\n");            return -EINVAL;        }look_for_collapsed_interface:        res = usb_find_common_endpoints(data_interface->cur_altsetting,                &epread, &epwrite, &epctrl, NULL);        if (res)            return res;        goto made_compressed_probe;    }skip_normal_probe:    /*workaround for switched interfaces */    if (data_interface->cur_altsetting->desc.bInterfaceClass != USB_CLASS_CDC_DATA) {        if (control_interface->cur_altsetting->desc.bInterfaceClass == USB_CLASS_CDC_DATA) {            dev_dbg(&intf->dev,                "Your device has switched interfaces.\n");            swap(control_interface, data_interface);        } else {            dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);            return -EINVAL;        }    }    /* Accept probe requests only for the control interface */    if (!combined_interfaces && intf != control_interface){        dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);        return -ENODEV;    }    if (data_interface->cur_altsetting->desc.bNumEndpoints < 2){        dev_dbg(&intf->dev,"%s %d %d %d\n",__FILE__,__LINE__,data_interface->cur_altsetting->desc.bNumEndpoints,control_interface->cur_altsetting->desc.bNumEndpoints);        return -EINVAL;    }    if( control_interface->cur_altsetting->desc.bNumEndpoints != 0){        epctrl = &control_interface->cur_altsetting->endpoint[0].desc;    }    epread = &data_interface->cur_altsetting->endpoint[0].desc;    epwrite = &data_interface->cur_altsetting->endpoint[1].desc;    /* workaround for switched endpoints */    if (!usb_endpoint_dir_in(epread)) {        /* descriptors are swapped */        dev_dbg(&intf->dev,            "The data interface has switched endpoints\n");        swap(epread, epwrite);    }made_compressed_probe:    dev_dbg(&intf->dev, "interfaces are valid\n");    acm = kzalloc(sizeof(struct acm), GFP_KERNEL);    if (!acm){        dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);        return -ENOMEM;    }    tty_port_init(&acm->port);    acm->port.ops = &acm_port_ops;    if(epctrl != NULL){        ctrlsize = usb_endpoint_maxp(epctrl);    }    readsize = usb_endpoint_maxp(epread) *                (quirks == SINGLE_RX_URB ? 1 : 2);    acm->combined_interfaces = combined_interfaces;    acm->writesize = usb_endpoint_maxp(epwrite) * 20;    acm->control = control_interface;    acm->data = data_interface;    usb_get_intf(acm->control); /* undone in destruct() */    minor = acm_alloc_minor(acm);    if (minor < 0) {        acm->minor = ACM_MINOR_INVALID;        dev_dbg(&intf->dev,"%s %d minor=%d\n",__FILE__,__LINE__,minor);        goto err_put_port;    }    acm->minor = minor;    acm->dev = usb_dev;    if (h.usb_cdc_acm_descriptor)        acm->ctrl_caps = h.usb_cdc_acm_descriptor->bmCapabilities;    if (quirks & NO_CAP_LINE)        acm->ctrl_caps &= ~USB_CDC_CAP_LINE;    acm->ctrlsize = ctrlsize;    acm->readsize = readsize;    acm->rx_buflimit = num_rx_buf;    INIT_DELAYED_WORK(&acm->dwork, acm_softint);    init_waitqueue_head(&acm->wioctl);    spin_lock_init(&acm->write_lock);    spin_lock_init(&acm->read_lock);    mutex_init(&acm->mutex);    if (usb_endpoint_xfer_int(epread)) {        acm->bInterval = epread->bInterval;        acm->in = usb_rcvintpipe(usb_dev, epread->bEndpointAddress);    } else {        acm->in = usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress);    }    if (usb_endpoint_xfer_int(epwrite))        acm->out = usb_sndintpipe(usb_dev, epwrite->bEndpointAddress);    else        acm->out = usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress);    init_usb_anchor(&acm->delayed);    acm->quirks = quirks;    if(epctrl != NULL){        buf = usb_alloc_coherent(usb_dev, ctrlsize, GFP_KERNEL, &acm->ctrl_dma);        if (!buf)            goto err_put_port;        acm->ctrl_buffer = buf;    }    if (acm_write_buffers_alloc(acm) < 0){        dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);        goto err_free_ctrl_buffer;    }    if(epctrl != NULL){        acm->ctrlurb = usb_alloc_urb(0, GFP_KERNEL);        if (!acm->ctrlurb){            dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);            goto err_free_write_buffers;        }    }    for (i = 0; i < num_rx_buf; i++) {        struct acm_rb *rb = &(acm->read_buffers[i]);        struct urb *urb;        rb->base = usb_alloc_coherent(acm->dev, readsize, GFP_KERNEL,                                &rb->dma);        if (!rb->base){            dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);            goto err_free_read_urbs;        }        rb->index = i;        rb->instance = acm;        urb = usb_alloc_urb(0, GFP_KERNEL);        if (!urb){            dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);            goto err_free_read_urbs;        }        urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;        urb->transfer_dma = rb->dma;        if (usb_endpoint_xfer_int(epread))            usb_fill_int_urb(urb, acm->dev, acm->in, rb->base,                     acm->readsize,                     acm_read_bulk_callback, rb,                     acm->bInterval);        else            usb_fill_bulk_urb(urb, acm->dev, acm->in, rb->base,                      acm->readsize,                      acm_read_bulk_callback, rb);        acm->read_urbs[i] = urb;        __set_bit(i, &acm->read_urbs_free);    }    for (i = 0; i < ACM_NW; i++) {        struct acm_wb *snd = &(acm->wb[i]);        snd->urb = usb_alloc_urb(0, GFP_KERNEL);        if (!snd->urb)            goto err_free_write_urbs;        if (usb_endpoint_xfer_int(epwrite))            usb_fill_int_urb(snd->urb, usb_dev, acm->out,                NULL, acm->writesize, acm_write_bulk, snd, epwrite->bInterval);        else            usb_fill_bulk_urb(snd->urb, usb_dev, acm->out,                NULL, acm->writesize, acm_write_bulk, snd);        snd->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;        if (quirks & SEND_ZERO_PACKET)            snd->urb->transfer_flags |= URB_ZERO_PACKET;        snd->instance = acm;    }    usb_set_intfdata(intf, acm);    i = device_create_file(&intf->dev, &dev_attr_bmCapabilities);    if (i < 0){        dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);        goto err_free_write_urbs;    }    if (h.usb_cdc_country_functional_desc) { /* export the country data */        struct usb_cdc_country_functional_desc * cfd =                    h.usb_cdc_country_functional_desc;        acm->country_codes = kmalloc(cfd->bLength - 4, GFP_KERNEL);        if (!acm->country_codes){            dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);            goto skip_countries;        }        acm->country_code_size = cfd->bLength - 4;        memcpy(acm->country_codes, (u8 *)&cfd->wCountyCode0,                            cfd->bLength - 4);        acm->country_rel_date = cfd->iCountryCodeRelDate;        i = device_create_file(&intf->dev, &dev_attr_wCountryCodes);        if (i < 0) {            kfree(acm->country_codes);            acm->country_codes = NULL;            acm->country_code_size = 0;            goto skip_countries;        }        i = device_create_file(&intf->dev,                        &dev_attr_iCountryCodeRelDate);        if (i < 0) {            device_remove_file(&intf->dev, &dev_attr_wCountryCodes);            kfree(acm->country_codes);            acm->country_codes = NULL;            acm->country_code_size = 0;            dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);            goto skip_countries;        }    }skip_countries:    if(epctrl != NULL){        usb_fill_int_urb(acm->ctrlurb, usb_dev,                usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress),                acm->ctrl_buffer, ctrlsize, acm_ctrl_irq, acm,                /* works around buggy devices */                epctrl->bInterval ? epctrl->bInterval : 16);        acm->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;        acm->ctrlurb->transfer_dma = acm->ctrl_dma;    }    acm->notification_buffer = NULL;    acm->nb_index = 0;    acm->nb_size = 0;    acm->line.dwDTERate = cpu_to_le32(9600);    acm->line.bDataBits = 8;    acm_set_line(acm, &acm->line);    if (!acm->combined_interfaces) {        rv = usb_driver_claim_interface(&acm_driver, data_interface, acm);        if (rv){            dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);            goto err_remove_files;        }    }    tty_dev = tty_port_register_device(&acm->port, acm_tty_driver, minor,            &control_interface->dev);    if (IS_ERR(tty_dev)) {        rv = PTR_ERR(tty_dev);        dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);        goto err_release_data_interface;    }    if (quirks & CLEAR_HALT_CONDITIONS) {        usb_clear_halt(usb_dev, acm->in);        usb_clear_halt(usb_dev, acm->out);    }    dev_info(&intf->dev, "ttyACM%d: USB ACM device\n", minor);    return 0;err_release_data_interface:    if (!acm->combined_interfaces) {        /* Clear driver data so that disconnect() returns early. */        usb_set_intfdata(data_interface, NULL);        usb_driver_release_interface(&acm_driver, data_interface);    }err_remove_files:    if (acm->country_codes) {        device_remove_file(&acm->control->dev,                &dev_attr_wCountryCodes);        device_remove_file(&acm->control->dev,                &dev_attr_iCountryCodeRelDate);    }    device_remove_file(&acm->control->dev, &dev_attr_bmCapabilities);err_free_write_urbs:    for (i = 0; i < ACM_NW; i++)        usb_free_urb(acm->wb[i].urb);err_free_read_urbs:    for (i = 0; i < num_rx_buf; i++)        usb_free_urb(acm->read_urbs[i]);    acm_read_buffers_free(acm);    usb_free_urb(acm->ctrlurb);err_free_write_buffers:    acm_write_buffers_free(acm);err_free_ctrl_buffer:    usb_free_coherent(usb_dev, ctrlsize, acm->ctrl_buffer, acm->ctrl_dma);err_put_port:    tty_port_put(&acm->port);    return rv;}

修改后看到成功枚举了tty设备。

[   16.706981] vhci_hcd vhci_hcd.0: pdev(0) rhport(0) sockfd(3)[   16.711093] vhci_hcd vhci_hcd.0: devid(196610) speed(3) speed_str(high-speed)[   16.712732] vhci_hcd vhci_hcd.0Device attached[   17.060874] usb 1-1: new high-speed USB device number 2 using vhci_hcd[   17.210837] usb 1-1SetAddress Request (2) to port 0[   17.249544] usb 1-1New USB device found, idVendor=1993, idProduct=0101, bcdDevice= 1.00[   17.250420] usb 1-1New USB device strings: Mfr=1Product=2SerialNumber=3[   17.251202] usb 1-1Product: xxxxSpecificDevice[   17.251676] usb 1-1Manufacturer: xxxx Team.[   17.252108] usb 1-1SerialNumber0.1[   17.254065] cdc_acm 1-1:1.0: acm_probe ...[   17.254541] cdc_acm 1-1:1.0: drivers/usb/class/cdc-acm.c 1233 call_intf_num=1[   17.255145] cdc_acm 1-1:1.0: drivers/usb/class/cdc-acm.c 1260 class=2[   17.255641] cdc_acm 1-1:1.0: interfaces are valid[   17.257047] cdc_acm 1-1:1.0: acm_ctrl_msg - rq 0x20, val 0x0, len 0x7, result 7[   17.257922] cdc_acm 1-1:1.0ttyACM0: USB ACM deviceqinyunti@qinyunti:~$ qinyunti@qinyunti:~ls /dev/ttyACM0/dev/ttyACM0

测试读写

此时会打印以下错误,找到错误的地方

[   49.533354hv_balloon: Max. dynamic memory size: 7618 MB[  750.950166] mini_init (145): drop_caches: 11707.885065] cdc_acm 1-1:1.0: acm_port_activate - usb_submit_urb(ctrl irq) failedqinyunti@qinyunti:~$

同样要加上判断

static int acm_port_activate(struct tty_port *port, struct tty_struct *tty){    struct acm *acm = container_of(port, struct acm, port);    int retval = -ENODEV;    int i;     mutex_lock(&acm->mutex);    if (acm->disconnected)        goto disconnected;     retval = usb_autopm_get_interface(acm->control);    if (retval)        goto error_get_interface;     /*     * FIXME: Why do we need this? Allocating 64K of physically contiguous     * memory is really nasty...     */    set_bit(TTY_NO_WRITE_SPLIT, &tty->flags);    acm->control->needs_remote_wakeup = 1;     if(acm->ctrlurb){        acm->ctrlurb->dev = acm->dev;        retval = usb_submit_urb(acm->ctrlurb, GFP_KERNEL);        if (retval) {            dev_err(&acm->control->dev,                "%s - usb_submit_urb(ctrl irq) failed\n", __func__);            goto error_submit_urb;        }    }    acm_tty_set_termios(tty, NULL);     /*     * Unthrottle device in case the TTY was closed while throttled.     */    clear_bit(ACM_THROTTLED, &acm->flags);     retval = acm_submit_read_urbs(acm, GFP_KERNEL);    if (retval)        goto error_submit_read_urbs;     usb_autopm_put_interface(acm->control);     mutex_unlock(&acm->mutex);     return 0; error_submit_read_urbs:    for (i = 0; i < acm->rx_buflimit; i++)        usb_kill_urb(acm->read_urbs[i]);    usb_kill_urb(acm->ctrlurb);error_submit_urb:    usb_autopm_put_interface(acm->control);error_get_interface:disconnected:    mutex_unlock(&acm->mutex);     return usb_translate_errors(retval);}

使用minicom测试

sudo apt-get install minicom

sudo minicom -s  配置

sudo minicom -D /dev/ttyACM0 打开

设备那边收到数据原样返回,可以看到读写正常了。


Ctrl+A,再按Z进入界面,Q退出。

4.2使用自定义驱动

如果内核无法替换,则可以参考cdc_acm.c修改,单独再实现一个驱动,加载该驱动即可。

复制修改后的cdc_acm.ccdc_acm.h

nano Makefile添加

CURDIR = $(shell pwd)KVERS = $(shell uname -r)obj-m := cdc-acm.o all:        make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules clean:        make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean

ctrl+o保存

Ctrl+x退出

make编译

qinyunti@qinyunti:~/cdc_acm$ sudo insmod cdc-acm.ko 

[sudo] password for qinyunti: 

insmod: ERROR: could not insert module cdc-acm.ko: Device or resource busy

提示设备busy是因为设备号被占用了

cat /proc/devices可以看到

166 ttyACM

我们修改

还是报错,看dmesg是已经加载了对应的驱动

[   42.783891cdc_acm: loading out-of-tree module taints kernel.[   42.815334ErrorDriver 'cdc_acm' is already registered, aborting...[   49.595978hv_balloon: Max. dynamic memory size: 7618 MB[  217.087763ErrorDriver 'cdc_acm' is already registered, aborting...qinyunti@qinyunti:~/cdc_acm$

我们给驱动改个名字

cdc_acm改为cdc_usb

此时看到出现了设备

qinyunti@qinyunti:~/cdc_acm$ ls /dev/ttyUSB0

/dev/ttyUSB0

使用minicom测试串口OK

sudo minicom -D /dev/ttyUSB0

4.3修改CDC设备驱动

如果设备可以修改,则修改设备控制接口增加一个中断端点,并响应对应的请求即可。

而对于一些已经固化的驱动比如bootrom中的程序可能就改不了了,只能改PC端的驱动了。

.总结

以上主要是分享基于WSL2如何修改内核CDC相关代码,增加调试信息去进行调试,问题本身不是很重要,重点是分享分析过程。而分享的案例本身的问题是CDC设备没有提供控制接口的中断端点,Linux下驱动必须要求有,而Windows下没有也可以,这也看出Windows下兼容性容错性上做的更好。

解决方案根据具体情况而定,这也是嵌入式开发中经常会遇到的情况,兼容性问题,各种填坑问题等,往往解决一个问题可能需要山路十八弯才能解决,然而解决一个这样的问题却是价值的体现,这也是嵌入式开发中能力价值的体现。比如上面如果设备端驱动改不了了则只能改内核驱动,内核驱动改不了则只能加载自定义的驱动。









评论 (0)
  •       知识产权保护对工程师的双向影响      正向的激励,保护了工程师的创新成果与权益,给企业带来了知识产权方面的收益,企业的创新和发明大都是工程师的劳动成果,他们的职务发明应当受到奖励和保护,是企业发展的重要源泉。专利同时也成了工程师职称评定的指标之一,专利体现了工程师的创新能力,在求职、竞聘技术岗位或参与重大项目时,专利证书能显著增强个人竞争力。专利将工程师的创意转化为受法律保护的“无形资产”,避免技术成果被他人抄袭或无偿使
    广州铁金刚 2025-03-25 11:48 181浏览
  • 汽车导航系统市场及应用环境参照调研机构GII的研究报告中的市场预测,全球汽车导航系统市场预计将于 2030年达到472亿美元的市场规模,而2024年至2030年的年复合成长率则为可观的6.7%。汽车导航系统无疑已成为智能汽车不可或缺的重要功能之一。随着人们在日常生活中对汽车导航功能的日渐依赖,一旦出现定位不准确或地图错误等问题,就可能导致车主开错路线,平白浪费更多行车时间,不仅造成行车不便,甚或可能引发交通事故的发生。有鉴于此,如果想要提供消费者完善的使用者体验,在车辆开发阶段便针对汽车导航功能
    百佳泰测试实验室 2025-03-27 14:51 187浏览
  • 家电,在人们的日常生活中扮演着不可或缺的角色,也是提升人们幸福感的重要组成部分,那你了解家电的发展史吗?#70年代结婚流行“四大件”:手表、自行车、缝纫机,收音机,合成“三转一响”。#80年代随着改革开放的深化,中国经济开始飞速发展,黑白电视机、冰箱、洗衣机这“新三件”,成为了人们对生活的新诉求。#90年代彩电、冰箱、全自动洗衣机开始大量进入普通家庭,快速全面普及,90年代末,家电产品实现了从奢侈品到必需品的转变。#00年代至今00年代,随着人们追求高品质生活的愿望,常用的电视机、洗衣机等已经远
    启英AI平台 2025-03-25 14:12 89浏览
  • 在智能语音产品的开发过程中,麦克风阵列的选型直接决定了用户体验的优劣。广州唯创电子提供的单麦克风与双麦克风解决方案,为不同场景下的语音交互需求提供了灵活选择。本文将深入解析两种方案的性能差异、适用场景及工程实现要点,为开发者提供系统化的设计决策依据。一、基础参数对比分析维度单麦克风方案双麦克风方案BOM成本¥1.2-2.5元¥4.8-6.5元信噪比(1m)58-62dB65-68dB拾音角度全向360°波束成形±30°功耗8mW@3.3V15mW@3.3V典型响应延迟120ms80ms二、技术原
    广州唯创电子 2025-03-27 09:23 154浏览
  • 六西格玛首先是作为一个量度质量水平的指标,它代表了近乎完美的质量的水平。如果你每天都吃一个苹果,有一间水果店的老板跟你说,他们所卖的苹果,质量达到六西格玛水平,换言之,他们每卖一百万个苹果,只会有3.4个是坏的。你算了一下,发现你如果要从这个店里买到一个坏苹果,需要805年。你会还会选择其他店吗?首先发明六西格玛这个词的人——比尔·史密斯(Bill Smith)他是摩托罗拉(Motorloa)的工程师,在追求这个近乎完美的质量水平的时候,发明了一套方法模型,开始时是MAIC,后来慢慢演变成DMA
    优思学院 2025-03-27 11:47 149浏览
  • WT588F02B是广州唯创电子推出的一款高性能语音芯片,广泛应用于智能家电、安防设备、玩具等领域。然而,在实际开发中,用户可能会遇到烧录失败的问题,导致项目进度受阻。本文将从下载连线、文件容量、线路长度三大核心因素出发,深入分析烧录失败的原因并提供系统化的解决方案。一、检查下载器与芯片的物理连接问题表现烧录时提示"连接超时"或"设备未响应",或烧录进度条卡顿后报错。原因解析接口错位:WT588F02B采用SPI/UART双模通信,若下载器引脚定义与芯片引脚未严格对应(如TXD/RXD交叉错误)
    广州唯创电子 2025-03-26 09:05 146浏览
  • 案例概况在丹麦哥本哈根,西门子工程师们成功完成了一项高安全设施的数据集成项目。他们利用宏集Cogent DataHub软件,将高安全设施内的设备和仪器与远程监控位置连接起来,让技术人员能够在不违反安全规定、不引入未经授权人员的情况下,远程操作所需设备。突破OPC 服务器的远程连接难题该项目最初看似是一个常规的 OPC 应用:目标是将高安全性设施中的冷水机(chiller)设备及其 OPC DA 服务器,与远程监控站的两套 SCADA 系统(作为 OPC DA 客户端)连接起来。然而,在实际实施过
    宏集科技 2025-03-27 13:20 109浏览
  • 在电子设计中,电磁兼容性(EMC)是确保设备既能抵御外部电磁干扰(EMI),又不会对自身或周围环境产生过量电磁辐射的关键。电容器、电感和磁珠作为三大核心元件,通过不同的机制协同作用,有效抑制电磁干扰。以下是其原理和应用场景的详细解析:1. 电容器:高频噪声的“吸尘器”作用原理:电容器通过“通高频、阻低频”的特性,为高频噪声提供低阻抗路径到地,形成滤波效果。例如,在电源和地之间并联电容,可吸收电源中的高频纹波和瞬态干扰。关键应用场景:电源去耦:在IC电源引脚附近放置0.1μF陶瓷电容,滤除数字电路
    时源芯微 2025-03-27 11:19 152浏览
  • 在嵌入式语音系统的开发过程中,广州唯创电子推出的WT588系列语音芯片凭借其优异的音质表现和灵活的编程特性,广泛应用于智能终端、工业控制、消费电子等领域。作为该系列芯片的关键状态指示信号,BUSY引脚的设计处理直接影响着系统交互的可靠性和功能拓展性。本文将从电路原理、应用场景、设计策略三个维度,深入解析BUSY引脚的技术特性及其工程实践要点。一、BUSY引脚工作原理与信号特性1.1 电气参数电平标准:输出3.3V TTL电平(与VDD同源)驱动能力:典型值±8mA(可直接驱动LED)响应延迟:语
    广州唯创电子 2025-03-26 09:26 201浏览
  • 在当今竞争激烈的工业环境中,效率和响应速度已成为企业制胜的关键。为了满足这一需求,我们隆重推出宏集Panorama COOX,这是Panorama Suite中首款集成的制造执行系统(MES)产品。这一创新产品将Panorama平台升级为全面的工业4.0解决方案,融合了工业SCADA和MES技术的双重优势,帮助企业实现生产效率和运营能力的全面提升。深度融合SCADA与MES,开启工业新纪元宏集Panorama COOX的诞生,源于我们对创新和卓越运营的不懈追求。通过战略性收购法国知名MES领域专
    宏集科技 2025-03-27 13:22 182浏览
  • ​2025年3月27日​,贞光科技授权代理品牌紫光同芯正式发布新一代汽车安全芯片T97-415E。作为T97-315E的迭代升级产品,该芯片以大容量存储、全球化合规认证、双SPI接口协同为核心突破,直击智能网联汽车"多场景安全并行"与"出口合规"两大行业痛点,助力车企抢占智能驾驶与全球化市场双赛道。行业趋势锚定:三大升级回应智能化浪潮1. 大容量存储:破解车联网多任务瓶颈随着​车机功能泛在化​(数字钥匙、OTA、T-BOX等安全服务集成),传统安全芯片面临存储资源挤占难题。T97-415E创新性
    贞光科技 2025-03-27 13:50 148浏览
  • 长期以来,智能家居对于大众家庭而言就像空中楼阁一般,华而不实,更有甚者,还将智能家居认定为资本家的营销游戏。商家们举着“智慧家居、智慧办公”的口号,将原本价格亲民、能用几十年的家电器具包装成为了高档商品,而消费者们最终得到的却是家居设备之间缺乏互操作性、不同品牌生态之间互不兼容的碎片化体验。这种早期的生态割裂现象致使消费者们对智能家居兴趣缺失,也造就了“智能家居无用论”的刻板印象。然而,自Matter协议发布之后,“命运的齿轮”开始转动,智能家居中的生态割裂现象与品牌生态之间的隔阂正被基于IP架
    华普微HOPERF 2025-03-27 09:46 109浏览
  • 文/陈昊编辑/cc孙聪颖‍2025 年,作为中国实施制造强国战略第一个十年计划的关键里程碑,被赋予了极为重大的意义。两会政府工作报告清晰且坚定地指出,要全力加速新质生产力的发展进程,推动传统产业全方位向高端化、智能化与绿色化转型。基于此,有代表敏锐提议,中国制造应从前沿技术的应用切入,逐步拓展至产业生态的构建,最终延伸到提升用户体验的维度,打出独树一帜、具有鲜明特色的发展牌。正是在这样至关重要的时代背景之下,于 AWE 2025(中国家电及消费电子博览会)这一备受瞩目的舞台上,高端厨房的中国方案
    华尔街科技眼 2025-03-25 16:10 82浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦