一、環境介紹
主控端: 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");