Linux嵌入式下USB设备驱动开发:实现自定义的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驱动就是那把打开硬件之门的钥匙。





