工作环境(蓝色粗体字为特别注意内容)
1,系统环境:Win7 Ultimate sp1、libusb-win32 version 1.2.6.0、VC98
2、参考文献:https://www.cnblogs.com/lknlfy/p/3189949.html
手头有个老设备需要通过并口控制,无奈,笔记本电脑没有并口,只能买一个USB转并口的转接线,某宝花了28元买了一根号称支持Winxp、Win7、Win8、Win10的usb转DB25打印机线。
快递一到就迫不及待的接上电脑,驱动倒是自动成功安装,于是,打开设备管理,左看右看愣是没有看到LPT接口!通常,如果看到LPT1接口的话,在VC中可以使用如下方式打开
hPort = CreateFile("\\\\.\\LPT1",GENERIC_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hPort == INVALID_HANDLE_VALUE)
{
printf("Cannot open LPT%d!\n", port_id);
return 0;
}
虽然没看到LPT接口,但是仔细检查,发现Universal Serial Bus controllers(usb控制器)下面多了一个“usb printer support”,估计这就是这根转接线了。于是点击windows开始-Devices and printers(设备和打印机)
发现一个未指定的设备:
单击任一打印机,选择Property(属性)-Ports(端口),发现一个有意思的端口“USB001”
也就是说,如果该端口上面连接着打印机,那么 新建一个打印机设备,并且指定该端口,就能够正常使用该打印机来进行打印工作。但是,回到前面的初衷,我们是要用DB25打印机端口来跟一款老设备进行通信,而不是用来打印!既然出现了“USB001”于是灵光一闪,想到咱们在VC中能不能像下面这样写呢?
hPort = CreateFile("\\\\.\\USB001",GENERIC_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
很遗憾,打开失败,按照我上一篇博客将USB001的打印机映射到LPT1端口,在CreateFile也是打开失败。查找资料,发现有关Windows操作USB设备的方法,典型的方法有两种:1、使用Window API来操作USB端口,2、使用第三方USB库libusb-win32来操作USB端口。这里我们选择后者,因为第一种方法要安装Windows SDK,还得装一堆东西,而我手头只有一个VC98的cl编译器(囧~~~~~),纯命令行编译。下面来看看如何使用libusb-win32来操作这个USB转DB25转接线。
Step1.当然是下载libusb-win32了,libusb-win32传送门。
下载下来之后,解压,应该有以下目录
├─bin
│ ├─amd64
│ ├─ia64
│ └─x86
├─examples
├─include
└─lib
├─bcc
├─dynamic
├─gcc
├─msvc
├─msvc_i64
└─msvc_x64
这里先介绍介绍几个重要的文件夹:
(1)bin 目录主要是inf-wizard.exe这个程序,这个程序是libusb的驱动程序,需要先进行安装驱动。
(2)examples目录主要是libusb-win32的一些例程
(3)include目录存放libusb-win32的头文件
(4)lib目录主要是存放已经编译好的现成的库
首先,双击bin目录下面的驱动,选择对应usb转db25对应的端口,
记住下面这两个值,后面用到!
一路next,最后点击“Install Now”进行驱动安装
驱动安装完成之后,打开设备管理器,你会看到如下的libusb设备:
好,至此,libusb-win32的驱动安装完毕,下面开始在vc环境下面使用libusb控制这个usb设备。
Step2:修改examples下面的bulk.c文件,将Step1中的Vendor ID和Product ID写入代码中,下面代码为改动后的代码,红色为改动的地方。
#include "../include/lusb0_usb.h"
#include <stdio.h>
#include<windows.h>
// Enables this example to work with a device running the
// libusb-win32 PIC Benchmark Firmware.
#define BENCHMARK_DEVICE
//////////////////////////////////////////////////////////////////////////////
// TEST SETUP (User configurable)
// Issues a Set configuration request
#define TEST_SET_CONFIGURATION
// Issues a claim interface request
#define TEST_CLAIM_INTERFACE
// Use the libusb-win32 async transfer functions. see
// transfer_bulk_async() below.
//#define TEST_ASYNC
// Attempts one bulk read.
#define TEST_BULK_READ
// Attempts one bulk write.
#define TEST_BULK_WRITE
//////////////////////////////////////////////////////////////////////////////
// DEVICE SETUP (User configurable)
// Device vendor and product id.
#define MY_VID 0x1234
#define MY_PID 0x1234
// Device configuration and interface id.
#define MY_CONFIG 1
#define MY_INTF 0
// Device endpoint(s)
#define EP_IN 0x81
#define EP_OUT 0x01
// Device of bytes to transfer.
#define BUF_SIZE 4
//////////////////////////////////////////////////////////////////////////////
usb_dev_handle *open_dev(void);
static int transfer_bulk_async(usb_dev_handle *dev,
int ep,
char *bytes,
int size,
int timeout);
usb_dev_handle *open_dev(void)
{
struct usb_bus *bus;
struct usb_device *dev;
for (bus = usb_get_busses(); bus; bus = bus->next)
{
for (dev = bus->devices; dev; dev = dev->next)
{
if (dev->descriptor.idVendor == MY_VID
&& dev->descriptor.idProduct == MY_PID)
{
return usb_open(dev);
}
}
}
return NULL;
}
int main(void)
{
usb_dev_handle *dev = NULL; /* the device handle */
char tmp[BUF_SIZE]={0x01,0x01,0x01,0x01};
int ret;
int sendTime = 100;
void* async_read_context = NULL;
void* async_write_context = NULL;
usb_init(); /* initialize the library */
usb_find_busses(); /* find all busses */
usb_find_devices(); /* find all connected devices */
if (!(dev = open_dev()))
{
printf("error opening device: \n%s\n", usb_strerror());
return 0;
}
else
{
printf("success: device %04X:%04X opened\n", MY_VID, MY_PID);
}
#ifdef TEST_SET_CONFIGURATION
if (usb_set_configuration(dev, MY_CONFIG) < 0)
{
printf("error setting config #%d: %s\n", MY_CONFIG, usb_strerror());
usb_close(dev);
return 0;
}
else
{
printf("success: set configuration #%d\n", MY_CONFIG);
}
#endif
#ifdef TEST_CLAIM_INTERFACE
if (usb_claim_interface(dev, 0) < 0)
{
printf("error claiming interface #%d:\n%s\n", MY_INTF, usb_strerror());
usb_close(dev);
return 0;
}
else
{
printf("success: claim_interface #%d\n", MY_INTF);
}
#endif
#ifdef TEST_BULK_WRITE
#ifdef BENCHMARK_DEVICE
ret = usb_control_msg(dev, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN,
14, /* set/get test */
2, /* test type */
MY_INTF, /* interface id */
tmp, 1, 1000);
#endif
#ifdef TEST_ASYNC
// Running an async write test
ret = transfer_bulk_async(dev, EP_OUT, tmp, sizeof(tmp), 5000);
#else
// Running a sync write test
//每隔1秒发送一次数据
while(sendTime>0){
ret = usb_bulk_write(dev, EP_OUT, tmp, sizeof(tmp), 5000);
sendTime--;
Sleep(1000);
}
#endif
if (ret < 0)
{
printf("error writing:\n%s\n", usb_strerror());
}
else
{
printf("success: bulk write %d bytes\n", ret);
}
#endif
#ifdef TEST_BULK_READ
#ifdef BENCHMARK_DEVICE
ret = usb_control_msg(dev, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN,
14, /* set/get test */
1, /* test type */
MY_INTF, /* interface id */
tmp, 1, 1000);
#endif
#ifdef TEST_ASYNC
// Running an async read test
ret = transfer_bulk_async(dev, EP_IN, tmp, sizeof(tmp), 5000);
#else
// Running a sync read test
ret = usb_bulk_read(dev, EP_IN, tmp, sizeof(tmp), 5000);
#endif
if (ret < 0)
{
printf("error reading:\n%s\n", usb_strerror());
}
else
{
printf("success: bulk read %d bytes\n", ret);
}
#endif
#ifdef TEST_CLAIM_INTERFACE
usb_release_interface(dev, 0);
#endif
if (dev)
{
usb_close(dev);
}
printf("Done.\n");
return 0;
}
/*
* Read/Write using async transfer functions.
*
* NOTE: This function waits for the transfer to complete essentially making
* it a sync transfer function so it only serves as an example of how one might
* implement async transfers into thier own code.
*/
static int transfer_bulk_async(usb_dev_handle *dev,
int ep,
char *bytes,
int size,
int timeout)
{
// Each async transfer requires it's own context. A transfer
// context can be re-used. When no longer needed they must be
// freed with usb_free_async().
//
void* async_context = NULL;
int ret;
// Setup the async transfer. This only needs to be done once
// for multiple submit/reaps. (more below)
//
ret = usb_bulk_setup_async(dev, &async_context, ep);
if (ret < 0)
{
printf("error usb_bulk_setup_async:\n%s\n", usb_strerror());
goto Done;
}
// Submit this transfer. This function returns immediately and the
// transfer is on it's way to the device.
//
ret = usb_submit_async(async_context, bytes, size);
if (ret < 0)
{
printf("error usb_submit_async:\n%s\n", usb_strerror());
usb_free_async(&async_context);
goto Done;
}
// Wait for the transfer to complete. If it doesn't complete in the
// specified time it is cancelled. see also usb_reap_async_nocancel().
//
ret = usb_reap_async(async_context, timeout);
// Free the context.
usb_free_async(&async_context);
Done:
return ret;
}
vc98的cl编译器编译脚本如下:
cl main.c ..\lib\msvc\libusb.lib
编译成功:
IDE环境自行配置inc和lib路径,暂且不在本文的讨论范围。编译运行之后该程序就会循环发送数据到usb转db25并口线,直到达到设定的次数。关于DB25打印机并口引脚定义,请看下图:
针脚 功能 针脚 功能
1 选通 (STROBE低电平) 10 确认 (ACKNLG低电平)
2 数据位0 (DATAO) 11 忙 (BUSY)
3 数据位1 (DATA1) 12 缺纸 (PE)
4 数据位2 (DATA2) 13 选择 (SLCT)
5 数据位3 (DATA3) 14 自动换行 (AUTOFEED低电平)
6 数据位4 (DATA4) 15 错误 (ERROR低电平)
7 数据位5 (DATA5) 16 初始化(INIT低电平)
8 数据位6 (DATA6) 17 选择输入 (SLCTIN低电平)
9 数据位7 (DATA7) 18-25 地线路(GND)
D0-D7为 数据线, S0-S7为状态线, 但是S0,S1,S2是看不见的(从图中你也可以看出), 状态线是用来读取数据的, 但S0却不同, 它是超时标志位, 其他的状态线从第10-11-12-13-15针是用来发送数据的(可以看出是5位). 那么我们怎么能得到这些数据端口呢? 很简单: 每一个并口都有一个地址. 在Windows2000中, 你可以在打印机端口(LPT1)的属性中看到他们. 比如:我的是0378-037F, 如果是10进制, 那么就是888. 同样你也可以看到你的COM端口的地址. 让我以打印机为例解释一下这些针位的意义:
S0: 在EPP(增强的串口)模式下, 如果超时的话, 这位置1.
S1: 没用(估计是装饰).
S2: 大多数情况下没有使用.
S3: 如果打印机发生了错误则置0. 它通常被叫做nError或者nFault.
S4: 如果数据达到, 则置1.我们通常叫做Select.
S5: 如果没有打印纸了则置1.通常叫做PaperEnd或者PaperEmpty或者PError.
S6: 如果打印机得到了一个字节的数据则此位置0, 通常叫做nAck或者nAcknowledge.
S7: 如果打印机处于繁忙的状态则此位置0, 通常叫做Busy.
控制线:
这些线通常用来输出,但有时也可以用于输入. 他们占用C0-C7(如图), 但是在接口上C4, C5, C6, C7是不可见, 他们占用的端口地址是0x37A.
C0: 这一针脚是保留的. 他发送命令去读取端口上的数据(D0-D7). 当计算机启动的时候,这一位被置1,通常叫做nStrobe.
C1: 保留.通过他发送命令给打印机, 可以得到下一条打印线.(LF)
C2: 重置打印机并且清空数据缓冲区.(nInitialize)
C3: 保留.置高电平打开数据输入.启动计算机时置0.(nSelectIn)
地线:
从G0-G7的针是接地用的, 他们一般是用来完善电路的.
综上所述,在 我的应用程序里使用数据线而不是具有保留位的控制线或者状态线作为数据传输。原因显而易见:我们可以发送任何数据到数据线上,比如00000000,这样 8根针就没有任何电压(0伏特);当然也可以发送11111111(255),这样每根针都有+5伏特的电压。但是如果我们使用控制线,他有C0,C1和 C3是保留,当我们发送0000000的时候,他却只能是0100,所以这样数据就不正确了。
信号 |
数据位 |
针脚 |
方向 |
-Strobe |
?C0 |
1 |
Output |
+Data Bit 0 |
D0 |
2 |
Output |
+Data Bit 1 |
D1 |
3 |
Output |
+Data Bit 2 |
D2 |
4 |
Output |
+Data Bit 3 |
D3 |
5 |
Output |
+Data Bit 4 |
D4 |
6 |
Output |
+Data Bit 5 |
D5 |
7 |
Output |
+Data Bit 6 |
D6 |
8 |
Output |
+Data Bit 7 |
D7 |
9 |
Output |
-Acknowledge |
S6 |
10 |
Input |
+Busy |
?S7 |
11 |
Input |
+Paper End |
S5 |
12 |
Input |
+Select In |
S4 |
13 |
Input |
-Auto Feed |
?C1 |
14 |
Output |
-Error |
S3 |
15 |
Input |
-Initialize |
C2 |
16 |
Output |
-Select |
?C3 |
17 |
Output |
Ground |
- |
18-25 |
Ground |
Step3.按照引脚定义,我们将引脚接上示波器,观察①脚波形:
说明有数据,且只有①脚有数据(0x01,8位数据,只有第一位是高电平),并且周期是1s,符合我们的要求,实验验证成功!