当前位置:首页 > 嵌入式 > 嵌入式分享
[导读]在嵌入式Linux系统中,USB接口因其即插即用和高带宽特性,成为连接数据采集设备(如ADC模块、传感器阵列)的首选。然而,市面上的通用USB设备未必满足特定采集需求,此时就需要自行开发Linux内核驱动,将自定义USB设备映射为字符设备或输入子系统节点。本文以一款虚构的“8通道12位ADC采集棒”为例,展示从USB设备探测到数据读取的完整驱动开发流程。


在嵌入式Linux系统中,USB接口因其即插即用和高带宽特性,成为连接数据采集设备(如ADC模块、传感器阵列)的首选。然而,市面上的通用USB设备未必满足特定采集需求,此时就需要自行开发Linux内核驱动,将自定义USB设备映射为字符设备或输入子系统节点。本文以一款虚构的“8通道12位ADC采集棒”为例,展示从USB设备探测到数据读取的完整驱动开发流程。

USB设备驱动骨架

Linux内核中,USB驱动通过usb_driver结构体注册。核心函数是probe(设备插入时调用)和disconnect(拔出时调用)。驱动需要匹配设备的VID/PID或类代码。

static const struct usb_device_id adc_table[] = {

   { USB_DEVICE(0x1234, 0x5678) },  // 自定义VID/PID

   {} /* 终止 */

};

MODULE_DEVICE_TABLE(usb, adc_table);


static int adc_probe(struct usb_interface *interface,

                    const struct usb_device_id *id) {

   struct usb_device *dev = interface_to_usbdev(interface);

   struct usb_endpoint_descriptor *ep;

   // 查找批量输入端点

   ep = usb_find_bulk_in_endpoint(interface->cur_altsetting);

   if (!ep) return -ENODEV;

   // 保存端点地址、分配urb等

   return 0;

}


static void adc_disconnect(struct usb_interface *interface) {

   // 释放urb、注销设备

}


static struct usb_driver adc_driver = {

   .name = "adc_collector",

   .id_table = adc_table,

   .probe = adc_probe,

   .disconnect = adc_disconnect,

};

module_usb_driver(adc_driver);

数据采集:URB的生命周期

USB请求块(URB)是内核与USB设备交换数据的核心对象。对于批量传输的数据采集设备,通常采用“提交URB → 等待完成 → 处理数据 → 重新提交”的循环模式。

struct adc_dev {

   struct usb_device *udev;

   struct urb *urb;

   unsigned char *buffer;

   size_t buffer_size;

};


static void adc_urb_complete(struct urb *urb) {

   struct adc_dev *dev = urb->context;

   if (urb->status == 0) {

       // 成功接收数据,将buffer传递给用户空间

       // 例如通过wake_up_interruptible唤醒读取进程

       // 然后将数据拷贝到字符设备缓冲区

       process_adc_data(dev, urb->transfer_buffer, urb->actual_length);

   }

   // 重新提交URB以持续采集

   usb_submit_urb(urb, GFP_ATOMIC);

}


static int adc_start_collection(struct adc_dev *dev) {

   dev->urb = usb_alloc_urb(0, GFP_KERNEL);

   dev->buffer = kmalloc(MAX_PACKET_SIZE, GFP_KERNEL);

   usb_fill_bulk_urb(dev->urb, dev->udev,

                     usb_rcvbulkpipe(dev->udev, EP_IN),

                     dev->buffer, MAX_PACKET_SIZE,

                     adc_urb_complete, dev);

   return usb_submit_urb(dev->urb, GFP_KERNEL);

}

用户空间接口:字符设备

为了让应用程序读取采集数据,驱动需要注册一个字符设备,实现read、release等文件操作。

static int adc_open(struct inode *inode, struct file *file) {

   struct adc_dev *dev = container_of(inode->i_cdev, struct adc_dev, cdev);

   file->private_data = dev;

   return 0;

}


static ssize_t adc_read(struct file *file, char __user *user_buf,

                       size_t count, loff_t *ppos) {

   struct adc_dev *dev = file->private_data;

   // 等待URB完成,从环形缓冲区拷贝数据

   wait_event_interruptible(dev->read_wait, dev->data_ready);

   copy_to_user(user_buf, dev->ring_buffer, min(count, dev->data_len));

   dev->data_ready = 0;

   return min(count, dev->data_len);

}


static const struct file_operations adc_fops = {

   .owner = THIS_MODULE,

   .open = adc_open,

   .read = adc_read,

   .release = adc_release,

};

在probe函数中调用cdev_init和cdev_add,并在disconnect中移除。

编译与测试

将驱动源码放入内核树drivers/usb/misc,修改Kconfig和Makefile。编译后加载模块:

insmod adc_collector.ko

lsusb -d 1234:5678  # 确认设备被识别

cat /dev/adc0 | hexdump  # 读取原始数据

使用usbmon或usbhid-dump调试USB事务:

modprobe usbmon

cat /sys/kernel/debug/usb/usbmon/0u | grep 1234:5678

写在最后

开发自定义USB数据采集设备驱动的核心流程可概括为:匹配设备 → 找到端点 → 分配URB → 提交批量传输 → 在完成回调中处理数据 → 通过字符设备交付用户空间。掌握了这套模式,你就可以将任意USB接口的传感器、采集卡无缝集成到嵌入式Linux系统中。当你的项目需要一个“即插即用”的高速数据通道时,内核USB驱动就是那把打开硬件之门的钥匙。



本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭