天天看點

Linux驅動開發: 編寫USB接口光譜儀驅動

一、環境介紹

主控端: ubuntu 18.04 (64位)

開發闆: Exynos4412(Cortex-A9) ----友善之臂Tiny4412

完整驅動源碼+配套上位機下載下傳位址:  

https://download.csdn.net/download/xiaolong1126626497/19036980

二、功能介紹

使用的光譜儀裝置是USB接口的,廠家隻提供了windows下的驅動和配套軟體,不支援Linux使用,也沒有完善的資料,現在裝置想在Linux系統下使用,要支援PClinux和嵌入式Linux裝置,隻能自己重新開發一個驅動再編寫一個配套的上位機(上位機是采用QT寫的)。

思路:  因為這個光譜儀是日本裝置,廠家不好聯系,沒拿到詳細資料,隻有一份簡單的指令手冊,但是提供了windows下可以使用的軟體。所有就在windows系統下使用USB抓包軟體,分析光譜儀裝置與windows下軟體間的通信資料包,對比指令手冊,得到完整的通訊流程,然後再對比編寫了一個Linux系統下的驅動。

windows下的USB抓包軟體推薦:

Bus Hound
官方下載下傳位址:http://perisoft.net/bushound/
USBlyzer
官網下載下傳位址:http://www.usblyzer.com/download.htm 
 
USBTrace 
官網下載下傳位址:http://www.sysnucleus.com/usbtrace_download.html      

如果是Linux下想使用USB抓包可以用usbmon,使用方法看這裡:

https://blog.csdn.net/xiaolong1126626497/article/details/112135161

三、核心驅動代碼

3.1  頭檔案.h

#ifndef SPECTROMETER_H
#define SPECTROMETER_H
#define Get_Module_Information  0x01000000UL
#define Get_Spectrometer_Information    0x02000000UL
#define Get_Module_Property 0x03000000UL
#define Get_Data_Position_Property  0x04000000UL
#define Get_Data_Count_Property 0x05000000UL
#define Get_Data_Transmit_Property  0x06000000UL
#define Get_Data_Trigger_Offset_Property    0x07000000UL
#define Get_Exposure_Property   0x08000000UL
#define Get_Gain_Property   0x08000000UL
#define Get_Ad_Offset_Property  0x0A000000UL
#define Get_Wavelength_Property 0x0C000000UL
#define Get_Image_Size  0x0D000000UL
#define Get_Capture_Mode    0x0F000000UL
#define Set_Capture_Mode    0x10000000UL
#define Get_Data_Position   0x11000000UL
#define Set_Data_Position   0x12000000UL
#define Get_Data_Count  0x13000000UL
#define Set_Data_Count  0x14000000UL
#define Get_Data_Transmit   0x15000000UL
#define Set_Data_Transmit   0x16000000UL
#define Get_Data_TriggerOffset  0x17000000UL
#define Set_Data_TriggerOffset  0x18000000UL
#define Get_Exposure_Time   0x19000000UL
#define Set_Exposure_Time   0x1A000000UL
#define Get_Exposure_Cycle  0x1B000000UL
#define Set_Exposure_Cycle  0x1C000000UL
#define Get_Trigger_Mode    0x1D000000UL
#define Set_Trigger_Mode    0x1E000000UL
#define Get_Trigger_Polarity    0x1F000000UL
#define Set_Trigger_Polarity    0x20000000UL
#define Get_Trigger_Output  0x21000000UL
#define Set_Trigger_Output  0x22000000UL
#define Get_Gain    0x25000000UL
#define Set_ain 0x26000000UL
#define Get_Ad_Offset   0x27000000UL
#define Set_Ad_OffSet   0x28000000UL
#define Get_Calibration_Coefficient 0x29000000UL
#define Set_Calibration_Coefficient 0x3B000000UL
#define Get_Cooling_Temperature 0x2A000000UL
#define Capture_Start   0x2F000000UL
#define Fire_Trigger    0x30000000UL
#define Capture_Stop    0x31000000UL
#define Read_Eeprom_User_Area   0x33000000UL
#define Write_Eeprom_User_Area  0x34000000UL
#define Get_Status_Request  0x3D000000UL
#define Get_Defective_Pixel 0x3E000000UL
 
#pragma pack(1)
struct DEV_CMD
{
    unsigned char *buff;       //讀寫資料緩沖區
    unsigned int write_len;    //寫資料長度
    unsigned int read_len;     //讀資料長度
};
#define DEV_NAME "/dev/spectrometer_usb_drv"
 
#define IOCTL_CMD_RW 0x39654127  //讀寫指令
#endif      

3.2 驅動.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <asm/uaccess.h>
#include <linux/bcd.h>
#include <linux/uaccess.h>
#include "spectrometer_cmd_list.h"
 
/*容納所有裝置特定内容的結構 */
struct usb_spectrometer
{
    struct usb_device   *udev;          /* 此裝置的USB裝置 */
    struct usb_interface    *interface; /*該裝置的接口*/
    struct usb_anchor   submitted;      /* 萬一需要撤回送出*/
    struct urb      *bulk_in_urb;       /*用urb讀取資料*/
    unsigned char           *bulk_in_buffer;    /* 接收資料的緩沖區 */
    size_t          bulk_in_size;       /*接收緩沖區的大小 */
    size_t          bulk_in_filled;     /* 緩沖區中的位元組數 */
    size_t          bulk_in_copied;     /* 已經複制到使用者空間 */
    __u8            bulk_in_endpointAddr;   /* 端點中的批量位址 */
    __u8            bulk_out_endpointAddr;  /*批量輸出端點的位址 */
    int             errors;         /* 最後一個請求被取消 */
    bool            ongoing_read;       /* 讀正在進行*/
    bool            processed_urb;      /* 表示尚未處理 */
};
static struct usb_spectrometer *tiny4412_usb_dev=NULL;
    
/*
[   25.845000] usb 1-2.2: new high-speed USB device number 6 using s5p-ehci
[   25.950000] usb 1-2.2: New USB device found, idVendor=0661, idProduct=294b
[   25.950000] usb 1-2.2: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[   25.950000] usb 1-2.2: Product: EZ-USB
[   25.950000] usb 1-2.2: Manufacturer: Cypress
[  726.360000] usb 1-2.2: new high-speed USB device number 7 using s5p-ehci
[  726.475000] usb 1-2.2: config 1 interface 0 altsetting 0 has 7 endpoint descriptors, different from the interface descriptor's value: 5
[  726.480000] usb 1-2.2: New USB device found, idVendor=148f, idProduct=5370
[  726.480000] usb 1-2.2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[  726.480000] usb 1-2.2: Product: 802.11 n WLAN
[  726.480000] usb 1-2.2: Manufacturer: Ralink
[  726.485000] usb 1-2.2: SerialNumber: 1.0
*/
 
//定義USB的IDTAB
static const struct usb_device_id tiny4412_usb_id[] = 
{
    {USB_DEVICE(0x0661,0x294b)},
    {}
};
 
/*
MODULE_DEVICE_TABLE 有兩個功能。
一是:将裝置加入到外設隊列中,
二是告訴程式閱讀者該裝置是熱插拔裝置或是說該裝置支援熱插拔功能。
該宏定義在<linux/module.h>下
這個宏有兩個參數,第一個參數裝置名,第二個參數該裝置加入到子產品中時對應産生的裝置搜尋符号,這個宏生成了一個名為__mod_pci_device_table
局部變量,這個變量指向第二個參數
*/
 
MODULE_DEVICE_TABLE (usb,tiny4412_usb_id);
 
 
static int usb_dev_open(struct inode *inode, struct file *file)
{
    printk("open:USB光譜儀裝置.\n");
    printk("指令結構大小_drv:%d\n",sizeof(struct DEV_CMD));
    return 0;
}
 
//讀寫指令
static long usb_dev_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long argv)
{
    //轉換指針類型
    struct DEV_CMD *buff=(struct DEV_CMD *)argv;
    struct DEV_CMD dev_cmd;
    int ret=0;
    int actual_length=0;
    int err=0;
    int n;
    printk("讀寫:USB光譜儀裝置.\n");
    int read_len=0;
    //拷貝應用層的資料到本地
    if(copy_from_user(&dev_cmd,buff,sizeof(struct DEV_CMD)))
    {
        printk("copy_from_user error1...\n");
        return -2;
    }
 
    //拷貝應用層的資料到本地
    if(copy_from_user(tiny4412_usb_dev->bulk_in_buffer,dev_cmd.buff,dev_cmd.write_len))
    {
        printk("copy_from_user error2...\n");
        return -2;
    }
 
    //指令符合要求
    if(IOCTL_CMD_RW==cmd)
    {
        /*同步送出寫請求*/
        ret=usb_bulk_msg(tiny4412_usb_dev->udev,
                    usb_sndbulkpipe(tiny4412_usb_dev->udev,tiny4412_usb_dev->bulk_out_endpointAddr),
                    tiny4412_usb_dev->bulk_in_buffer,dev_cmd.write_len,&actual_length,HZ*100);
        if(ret<0)
        {
            printk("寫位元組:%d Byte,端點:%#x\n",dev_cmd.write_len,tiny4412_usb_dev->bulk_out_endpointAddr);
            for(n=0;n<dev_cmd.write_len;n++)
            {
                printk("%#x ",tiny4412_usb_dev->bulk_in_buffer[n]);
            }
            printk("\n");
            printk("同步送出寫請求錯誤.錯誤值:%d\n",ret);
            return -3;
        }
        dev_cmd.write_len=actual_length; //成功寫入的長度
        printk("write:len=%d\n",actual_length);
 
        //讀取的長度大于0.就表示需要讀取資料
        if(dev_cmd.read_len>0)
        {
            //判斷是否讀取大資料
            if(dev_cmd.buff[3]==0x2f&&dev_cmd.buff[7]==0x02)
            {
                printk("0x86端點:\n");
                for(n=0;n<2;n++)
                {
                    /*同步送出讀請求:端點0x86*/
                    err=usb_bulk_msg(tiny4412_usb_dev->udev, 
                                    usb_rcvbulkpipe(tiny4412_usb_dev->udev,tiny4412_usb_dev->bulk_in_endpointAddr),
                                    tiny4412_usb_dev->bulk_in_buffer,tiny4412_usb_dev->bulk_in_size, &actual_length,HZ*500);
                    if(err<0)
                    {
                        printk("0x86_讀取第%d次,狀态值:%d\n",n,err);
                        break;
                    }
                    memcpy(dev_cmd.buff+read_len,tiny4412_usb_dev->bulk_in_buffer,actual_length);
                    read_len+=actual_length;
                }
                printk("0x86_一共讀取%d次,一共%d位元組.\n",n,read_len);
                
                printk("0x88端點:\n");
                for(n=0;n<9;n++)
                {
                    /*同步送出讀請求:端點0x88*/
                    err=usb_bulk_msg(tiny4412_usb_dev->udev, 
                                    usb_rcvbulkpipe(tiny4412_usb_dev->udev,0x88),
                                    tiny4412_usb_dev->bulk_in_buffer,tiny4412_usb_dev->bulk_in_size, &actual_length,HZ*500);
                                    
                    if(err<0)
                    {
                        printk("0x88_讀取第%d次,狀态值:%d\n",n,err);
                        break;
                    }
                    memcpy(dev_cmd.buff+read_len,tiny4412_usb_dev->bulk_in_buffer,actual_length);
                    read_len+=actual_length;
                }
                printk("0x88_一共讀取%d次,一共%d位元組.\n",n,read_len);
                
                /*同步送出讀請求:端點0x86*/
                err=usb_bulk_msg(tiny4412_usb_dev->udev, 
                                usb_rcvbulkpipe(tiny4412_usb_dev->udev,tiny4412_usb_dev->bulk_in_endpointAddr),
                                tiny4412_usb_dev->bulk_in_buffer,tiny4412_usb_dev->bulk_in_size, &actual_length,HZ*500);
                if(err<0)
                {
                    printk("0x86_讀取最後一次,狀态值:%d\n",err);
                }
                
                printk("0x86_最後讀取%d位元組:\n",actual_length);
                memcpy(dev_cmd.buff+read_len,tiny4412_usb_dev->bulk_in_buffer,actual_length);
                read_len+=actual_length;
            }
            else
            {
                //buff清0
                memset(dev_cmd.buff,0,sizeof(dev_cmd.buff));
                /*同步送出讀請求*/
                ret = usb_bulk_msg(tiny4412_usb_dev->udev, 
                                usb_rcvbulkpipe(tiny4412_usb_dev->udev,tiny4412_usb_dev->bulk_in_endpointAddr),
                                tiny4412_usb_dev->bulk_in_buffer,tiny4412_usb_dev->bulk_in_size, &actual_length,HZ*500);
                if(ret<0)
                {
                    printk("同步送出讀請求錯誤.錯誤值:%d\n",ret);
                    return -4;
                }
                //實際讀取的長度
                memcpy(dev_cmd.buff,tiny4412_usb_dev->bulk_in_buffer,actual_length);
                read_len=actual_length;
            }
            
            dev_cmd.read_len=read_len;
            printk("read:len=%d\n",dev_cmd.read_len);
        }
    }
    
    //将資料拷貝到應用層
    err=copy_to_user(buff,&dev_cmd,sizeof(struct DEV_CMD));
    if(err)
    {
        printk("data copy-->user error!!!\n");
        return -3;
    }
    return 0;
}
 
static int usb_dev_release(struct inode *inode, struct file *file)
{
    printk("release:USB光譜儀裝置.\n");
    return 0;
}
 
static const struct file_operations tiny4412_usb_dev_fops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl=usb_dev_unlocked_ioctl,
    .open  = usb_dev_open,
    .release = usb_dev_release,
};
static struct miscdevice usb_dev_miscdev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = DEV_NAME,
    .fops = &tiny4412_usb_dev_fops,
};
 
//USB裝置資訊與驅動端比對成功的時候調用。
static int test_probe(struct usb_interface *interface,const struct usb_device_id *id)  //資源探索函數
{
    int i=0;
    size_t buffer_size;
    struct usb_device *dev_info;
    unsigned char *bcdUSB_p;
    struct usb_host_interface *host_inter;
    struct usb_endpoint_descriptor *endpoint;
    int size;
    
    tiny4412_usb_dev=kzalloc(sizeof(*tiny4412_usb_dev), GFP_KERNEL);
    tiny4412_usb_dev->udev = usb_get_dev(interface_to_usbdev(interface));
    tiny4412_usb_dev->interface = interface;
 
    printk("識别到USB光譜儀裝置,正在進行裝置初始化.\n");
 
    /*通過接口擷取裝置資訊*/
    dev_info = interface_to_usbdev(interface);
    bcdUSB_p=(unsigned char *)&dev_info->descriptor.bcdUSB;
    printk("裝置與描述表相容的USB裝置說明版本号=0x%x%x\n", bcd2bin(*bcdUSB_p),bcd2bin(*(bcdUSB_p+1)));   //從USB裝置描述符中擷取USB版本
    printk("廠商ID = %#x\n",dev_info->descriptor.idVendor); //從USB裝置描述符中擷取廠商ID
    printk("裝置ID = %#x\n",dev_info->descriptor.idProduct);//從USB裝置描述符中擷取産品ID
 
    printk("裝置類 = %#x\n",interface->cur_altsetting->desc.bInterfaceClass);   //從USB裝置擷取裝置類
    printk("裝置從類 = %#x\n",interface->cur_altsetting->desc.bInterfaceSubClass);//從USB裝置擷取裝置從類
    printk("裝置協定 = %#x\n",interface->cur_altsetting->desc.bInterfaceProtocol);//從USB裝置擷取裝置協定
    /*擷取目前接口設定*/
    host_inter=interface->cur_altsetting;
    /*擷取端點描述符*/
    for(i=0;i<host_inter->desc.bNumEndpoints;i++) 
    {
        endpoint = &host_inter->endpoint[i].desc;
        printk("端點号[%d]:%#x\n",i,endpoint->bEndpointAddress&0xFF);
        if(endpoint->bEndpointAddress&1<<7)
        {
            printk("端點[%d] 輸入端點(裝置到主機)\n",i);
        }
        else
        {
            printk("端點[%d] 輸出端點(主機到裝置)\n",i);
        }
        switch(endpoint->bmAttributes)
        {
            case 0:printk("端點[%d] 裝置支援控制傳輸.\n",i);break;
            case 1:printk("端點[%d] 裝置支援同步傳輸.\n",i);break;
            case 2:printk("端點[%d] 裝置支援批量傳輸.\n",i);break;
            case 3:printk("端點[%d] 裝置支援中斷傳輸.\n",i);break;
        }
        /*從端點描述符中擷取傳輸的資料大小 */
        size = usb_endpoint_maxp(endpoint);
        printk("端點[%d] 傳輸的資料大小:%d\n",i,size);
 
        //輸入端點
        if(!tiny4412_usb_dev->bulk_in_endpointAddr &&usb_endpoint_is_bulk_in(endpoint)) 
        {
            /* 批量輸入端點 */
            buffer_size = usb_endpoint_maxp(endpoint);
            tiny4412_usb_dev->bulk_in_size = buffer_size;
            tiny4412_usb_dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
            printk("probe:tiny4412_usb_dev->bulk_in_size=%d\n",tiny4412_usb_dev->bulk_in_size);
            printk("probe:tiny4412_usb_dev->bulk_in_endpointAddr=%#X\n",tiny4412_usb_dev->bulk_in_endpointAddr);
            tiny4412_usb_dev->bulk_in_buffer = kmalloc(buffer_size,GFP_KERNEL);
            if(!tiny4412_usb_dev->bulk_in_buffer)
            {
                printk("無法配置設定bulk_in_buffer");
                break;
            }
            tiny4412_usb_dev->bulk_in_urb = usb_alloc_urb(0,GFP_KERNEL);
            if(!tiny4412_usb_dev->bulk_in_urb)
            {
                printk("無法配置設定bulk_in_urb");
                break;
            }
        }
        
        //輸出端點
        if(!tiny4412_usb_dev->bulk_out_endpointAddr &&usb_endpoint_is_bulk_out(endpoint))
        {
            /*  批量輸出端點 */
            tiny4412_usb_dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
            printk("probe:tiny4412_usb_dev->bulk_out_endpointAddr=%#x\n",tiny4412_usb_dev->bulk_out_endpointAddr);
        }
    }
    /*向核心注冊一個雜項字元裝置*/
    if(misc_register(&usb_dev_miscdev)==0)
    {
        printk("USB光譜儀裝置節點注冊成功:/tiny4412_usb_dev/%s ,主裝置号:10,次裝置号:%d\n",
            usb_dev_miscdev.name,usb_dev_miscdev.minor);    
    }
    
    printk("版本:2021/01/12 12:10");
    return 0;
}
 
//USB斷開的時候調用
static void test_disconnect(struct usb_interface *intf)
{
    /*從核心登出一個雜項字元裝置*/
    misc_deregister(&usb_dev_miscdev);
    
    printk("USB光譜儀裝置已斷開.\n"); 
}
 
//定義USB驅動結構體 
static struct usb_driver tiny4412_usb_driver = {
    .name = "spectrometer_usb_drv",
    .id_table = tiny4412_usb_id,
    .probe = test_probe,
    .disconnect = test_disconnect
};
 
static int __init tiny4412_usb_init(void)
{
    printk("正在安裝USB光譜儀驅動.\n"); 
    //注冊USB裝置驅動
    usb_register(&tiny4412_usb_driver);
    return 0;
}
 
static void __exit tiny4412_usb_exit(void)
{
     //登出USB裝置驅動
     usb_deregister(&tiny4412_usb_driver);
     printk("USB光譜儀驅動解除安裝成功.\n"); 
}
 
module_init(tiny4412_usb_init);
module_exit(tiny4412_usb_exit);
MODULE_AUTHOR("xiaolong");
MODULE_LICENSE("GPL");      

繼續閱讀