int main(void)
{
#ifdef DEBUG
debug();
#endif
RCC_Configuration();
NVIC_Configuration();
GPIO_Configuration();
LcdShow_Init();
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;//外设地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)ADC_RCVTab;//内存地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//dma传输方向单向
DMA_InitStructure.DMA_BufferSize = 160;//设置DMA在传输时缓冲区的长度 word
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//设置DMA的外设递增模式,一个外设
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//设置DMA的内存递增模式,
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//外设数据字长
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//内存数据字长
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//设置DMA的传输模式:连续不断的循环模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High;//设置DMA的优先级别
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//设置DMA的2个memory中的变量互相访问
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//独立工作模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE;//扫描方式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//连续转换
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//外部触发禁止
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 8;//用于转换的通道数
ADC_Init(ADC1, &ADC_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_8 , 1, ADC_SampleTime_239Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_9 , 2, ADC_SampleTime_239Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 3, ADC_SampleTime_239Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 4, ADC_SampleTime_239Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_12, 5, ADC_SampleTime_239Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_13, 6, ADC_SampleTime_239Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_14, 7, ADC_SampleTime_239Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_15, 8, ADC_SampleTime_239Cycles5);
ADC_DMACmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(1)
{
vu16 value1 = 0;
vu16 value2 = 0;
vu16 value3 = 0;
vu16 value4 = 0;
vu16 value5 = 0;
vu16 value6 = 0;
vu16 value7 = 0;
vu16 value8 = 0;
value1 = average(ADC_RCVTab,0);
value2 = average(ADC_RCVTab,1);
value3 = average(ADC_RCVTab,2);
value4 = average(ADC_RCVTab,3);
value5 = average(ADC_RCVTab,4);
value6 = average(ADC_RCVTab,5);
value7 = average(ADC_RCVTab,6);
value8 = average(ADC_RCVTab,7);
u8 num1 = value3 % 10;
u8 num2 = (value3 / 10) % 10;
u8 num3= (value3 / 100) % 10;
u8 num4 = value3 / 1000;
if (num1 > 9)
display[3] = num1 + (65 - 10);
else
display[3] = num1 + (48-0);
if (num2 > 9)
display[2] = num2 +(65 - 10);
else
display[2] = num2 + (48 - 0);
if (num3>9)
display[1]=num3+(65-10);
else
display[1]=num3+(48-0);
if (num4>9)
display[0]=num4+(65-10);
else
display[0]=num4+(48-0);
write_string(display);
delay();
}
}
u16 average(vu16 ADCDataTab[], u16 nChannel) { u16 averagevalue=0, maxvalue=0, minvalue=0xFFFF, i; for (i=0;i<20;i++) { averagevalue+=*(ADCDataTab+nChannel+i*8); if(*(ADCDataTab+nChannel+i*8)>maxvalue) maxvalue=*(ADCDataTab+nChannel+i*8); if(*(ADCDataTab+nChannel+i*8)
方波的图形好像不是很漂亮会有上升沿老是有尖刺,还需要作软件做处理。没截出来,
最后能出数据离不开很多前辈的经验。有些地方是借鉴了他们的东西。现在先列出来。
第一个是 21IC 上面 alien2006 原帖地址。他也是做了一个简易示波器,但是用的是以太网传输。采集部分我很多借鉴了他的方案。
第二个是电脑圈圈,他对 USB 的理解令我钦佩。我能搞出驱动,他提供的源码包非常重要,有些代码也是直接在他的基础上修改的。
2 整体方案
先说下一次完整的采集,比如外面进来的波形是正弦波,波形电压有正负, STM32 单片机的 AD 只能采集 0~3.6V 的电压,所以要对信号进行处理。也就是需要一个模拟前端电路,把电压抬上去。接着就是采集了, STM32 的 AD 可以用外设进行触发,这里用定时器进行触发。每过一个单位时间 AD 开启一次,采集一个点,这样采集的频率只要调整这个单位时间也就是定时器就可以控制了。采集了一桢数据,比如 200 个点。 DMA 中断被触发,开启 USB ,把数据发送到上位机,然后显示出来。
整个过程大体就是这样了,还有一个很重要的环节补充下。熟悉示波器的人都知道示波器有个触发概念。像刚才这样显示的话,比如前一帧数据是波峰开始显示,后一帧是波谷开始。这样显示出来的波形就是乱的,于是为了解决这个问题,就需要做触发,也就是保证每次采集的起始电位相同。我们的采用的是用外中断的形式,外面波形数据先不采集,先让它通过一个比较器,比如比较器的基准电压是 1V ,也就是每次都和 1V 比较,低于 1V 输出低电平,高于 1V 输出高电平。当数据电压大于 1V 的时候,比较器输出高电平,高电平接到单片机外中断口,这样外中断就被触发。然后开始采集,这样就能保证每帧数据的起始点都相同。
这个方案是大体方案,后来做了下修改。就是让 AD 一直在采集。外中断触发了后开启的是 DMA , AD 一直开着,只是控制 DMA 什么时候去取。从哪里开始取,取多少个点。下位机部分不再详说,贴下主要的程序,大侠们随便看看,多多指教。
定时器设置代码如下:
void TIM2_Configuration(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_DeInit(TIM2); //TIM2初始化
TIM_TimeBaseStructure.TIM_Period = 18; //设置了下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler = 0; //设置了用来作为 TIMx 时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置了时钟分割
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //选择了计数器模式向上计数
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据 TIM_TimeBaseInitStruct 中指定的参数初始化 TIMx
// TIM_PrescalerConfig(TIM2, 1, TIM_PSCReloadMode_Update); //设置 TIMx 重载次数 预分频值在更新事件装入
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;
TIM_OCInitStructure.TIM_Channel = TIM_Channel_2;
TIM_OCInitStructure.TIM_Pulse = 9;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //TIM输出比较极性低
TIM_OCInit(TIM2, &TIM_OCInitStructure);
TIM_ARRPreloadConfig(TIM2, ENABLE); //使能或者失能 TIMx 在 ARR 上的自动装载寄存器
TIM_Cmd(TIM2, ENABLE); //使能 TIMx 外设
}
AD以及 DMA 设置代码:
void ADC_Configuration(void)
{
DMA_DeInit(DMA_Channel1); //复位 DMA_Channel1
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; //外围设备地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC_Data[0]; //memory 地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //外围设备做为源
DMA_InitStructure.DMA_BufferSize = 1024; //数据单元尺寸
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外围地址是否自动增长 disable 不增长
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //memory 是否自动增长
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //外围设备寄存器尺寸 16 位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //memory尺寸 16 位
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //DMA 循环模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High; //DMA 通道优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //memory- to-memory转换
DMA_Init(DMA_Channel1, &DMA_InitStructure); //初始化 DMA 通道 1
DMA_ITConfig(DMA_Channel1, DMA_IT_TC, ENABLE); //使能 DMA 传输完成中断
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC 工作模式 ADC1 ADC2 单独工作
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //多通道扫描模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //是否启用连续转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_CC2; //触发方式
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; //ADC规则转换通道数量
ADC_Init(ADC1, &ADC_InitStructure);
//配置转换规则
ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_1Cycles5);
ADC_DMACmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1); //复位 ADC1 校准
while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位完成
ADC_StartCalibration(ADC1); //开始 ADC1 校准转换
while(ADC_GetCalibrationStatus(ADC1)); //等待转换完成
ADC_ExternalTrigConvCmd( ADC1, ENABLE); //使能或者失能 ADCx 的经外部触发启动转换功能
}
DMA中断服务程序:
void DMAChannel1_IRQHandler(void)
{
DMA_Cmd(DMA_Channel1, DISABLE); //关闭 DMA 通道 1
for(count=64;count<1024;count++) //AD数据转换处理
{
AD_Data[count]=(ADC_Data[count]*330)/4096;
}
flag_usb = 1; //usb标志位置 1 ,使能 usb 发送
DMA_ClearITPendingBit(DMA_IT_TC1);
}
下面就开始说我做的部分, USB 部分。一共有 3 个地方,
(1 ) USB 固件程序的开发
(2 ) Windows XP 下 USB 设备驱动的开发
(3 )对应的 PC 上的应用程序开发。
下面来详细论述。
3 USB固件程序的开发
这部分主要设计 USB 的协议。一共花了两个星期时间,是我们做的最开心的两个星期。因为是两个人一起来研究,导师崔也经常跑过来讨论。最后测出来的传输速度是 700-800K ,依据 USB2.0 全速理论最大速度 12Mbps ,除以八大概是 1M 多,基本把速度都开发出来了。期间参考了 ouravr 里面的一个叫极速狂飙的帖子,也成功开启了双缓冲。
固件程序主要是根据万利板子提供的 4 个 USB 程序中的那个 USB 转串程序修改的,主要是修改两个文件。第一个是 usb_desc.c ,这个文件里面配置了全部的描述符。这些描述符在 USB2.0 协议里面都有严格的定义,跟着配置就好了。
const u8 Virtual_Com_Port_DeviceDescriptor[] =
{
0x12,
USB_DEVICE_DESCRIPTOR_TYPE,
0x00,
0x02,
0xFF,
0xFF,
0x00,
0x40,
0x44,
0x44,
0x33,
0x33,
0x00,
0x02,
1,
2,
3,
0x01
};
const u8 Virtual_Com_Port_ConfigDescriptor[] =
{
0x09,
USB_CONFIGURATION_DESCRIPTOR_TYPE,
VIRTUAL_COM_PORT_SIZ_CONFIG_DESC,
0x00,
0x01,
0x01,
0x00,
0xC0,
0x00,
0x09,
USB_INTERFACE_DESCRIPTOR_TYPE,
0x00,
0x00,
0x02,
0xFF,
0xFF,
0x00,
0x00,
0x07,
USB_ENDPOINT_DESCRIPTOR_TYPE,
0x03,
0x02,
0x02,
0x00,
0x00,
0x07,
USB_ENDPOINT_DESCRIPTOR_TYPE,
0x81,
0x02,
0x40,
0x00,
0x00
};
const u8 Virtual_Com_Port_StringLangID[VIRTUAL_COM_PORT_SIZ_STRING_LANGID] =
{
VIRTUAL_COM_PORT_SIZ_STRING_LANGID,
USB_STRING_DESCRIPTOR_TYPE,
0x09,
0x04
};
const u8 Virtual_Com_Port_StringVendor[VIRTUAL_COM_PORT_SIZ_STRING_VENDOR] =
{
VIRTUAL_COM_PORT_SIZ_STRING_VENDOR,
USB_STRING_DESCRIPTOR_TYPE,
'S', 0, 'T', 0, 'M', 0, 'i', 0, 'c', 0, 'r', 0, 'o', 0, 'e', 0,
'l', 0, 'e', 0, 'c', 0, 't', 0, 'r', 0, 'o', 0, 'n', 0, 'i', 0,
'c', 0, 's', 0
};
const u8 Virtual_Com_Port_StringProduct[VIRTUAL_COM_PORT_SIZ_STRING_PRODUCT] =
{
VIRTUAL_COM_PORT_SIZ_STRING_PRODUCT,
USB_STRING_DESCRIPTOR_TYPE,
'N', 0, 'u', 0, 'm', 0, ' ', 0, 's', 0, 'e', 0, 'n', 0, 'd', 0,
' ', 0, 't', 0, 'e', 0, 's', 0, 't', 0, ' ', 0
};
const u8 Virtual_Com_Port_StringSerial[VIRTUAL_COM_PORT_SIZ_STRING_SERIAL] =
{
VIRTUAL_COM_PORT_SIZ_STRING_SERIAL,
USB_STRING_DESCRIPTOR_TYPE,
'D', 0, 'e', 0, 'm', 0, 'o', 0, ' ', 0, '1', 0, '.', 0, '0', 0,
'0', 0, '0', 0
}
有些字符没有修改还是原来DEMO 的,大家将就着看看。
还有就是Usb_prop.c 里面这个函数
void Virtual_Com_Port_Reset(void) //USBIP复位过程,当宏单元收到 RESET 信号时调用,
{ //用户程序在此过程中设置端点
pInformation->Current_Configuration = 0;
pInformation->Current_Interface = 0;
SetBTABLE(BTABLE_ADDRESS);
SetEPType(ENDP0, EP_CONTROL);
SetEPTxStatus(ENDP0, EP_TX_STALL);
SetEPRxAddr(ENDP0, ENDP0_RXADDR);
SetEPTxAddr(ENDP0, ENDP0_TXADDR);
Clear_Status_Out(ENDP0);
SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);
SetEPRxValid(ENDP0);
SetEPType(ENDP1, EP_BULK); //设置端点 1 传输模式批量传输
SetEPTxAddr(ENDP1, ENDP1_TXADDR); //缓冲区基地址 (IN 发 )
SetEPTxCount(ENDP1, 64); //配置 Tx 缓冲计数器
SetEPTxStatus(ENDP1, EP_TX_VALID); //设置端点发送有效
SetEPRxStatus(ENDP1, EP_RX_DIS); //设置端点接收关闭
SetEPType(ENDP3, EP_BULK); //设置端点 3 传输模式批量传输
SetEPRxAddr(ENDP3, ENDP3_RXADDR); //缓冲区基地址 (OUT 收 )
SetEPRxCount(ENDP3, 2); //配置 Rx 缓冲计数器
SetEPRxStatus(ENDP3, EP_RX_VALID); //设置端点接收有效
SetEPTxStatus(ENDP3, EP_TX_DIS); //端点关闭发送
SetDeviceAddress(0);
}要配置下。就是你开启的那些端点了。
4 Windows XP下 USB 设备驱动的开发
这部分是最麻烦的,至少对我来说是这样的,好像已经到了计算机专业范畴,而我对这些非常不熟悉。学习期间第一个方案用的是 LABVIEW ,它附带一个工具叫 VISA ,可以直接配置成 USB 驱动,然后再在 LABVIEW 里面直接调用,很方便。在研究 LABVIEW 的时候发现里面有很多 DEMO ,而且对中文的支持非常好,很多文档都是官方直接出中文文档。(好像半导体大额们对中国这块市场都越来越重视了,前几天看到 AVR 大部分的文档也都是直接官方翻译的中文文档。)学习了软件里面附带的入门教程,大概也能编一些简单的程序了,调试 USB 的时候发现里面有几个 USB 的 demo ,配合 VISA 生成的驱动居然直接接收到了下面传上来的数据,这对我们来说无异于是一次莫大的鼓励。后来导师推荐我试试另一种方法,就是用 Driverstudio+VC 来开发驱动和编写界面。为了学习一下 USB 的驱动开发,于是在第三个星期的时候开始 windows 下驱动的开发。老实说吃了很多苦头,到现在虽然功能基本实现了,但是对于这一块我还是只知道皮毛。 DS 里面的那些类和函数也没怎么搞透,驱动能开发出来主要还是借鉴了 DS 附带的 example 里面的 USB BULK 程序。
下面大概讲下我遇到过的问题给后来人一点借鉴:
(1 )是安装顺序的问题,因为 DriverStudio 要嵌入到 VC 中,以后的代码要用 DDK 来编写所以三个软件要很好的兼容才可以。
第一步:安装Microsoft Visual C++6.0 ;
第二步:安装Microsoft Windows XP DDK ;
第三步:安装DriverStudio 3.2 驱动程序开发工具包。
VC6.0最好安装英文版的,可以减少不知名的错误。安装完成后 DS3.2 会嵌入到 VC 中,在上面多一个标题:
DriverStudio选项卡下面第三项 DDK Build setting 要设置成 C:\WINDDK\2600 (如果 DDK 安装在 C 盘),然后要编译 DriverStudio 安装目录下 DriverStudio\DriverWorks\source\VdwLibs.dsw ,以得到 vdw_wdm.lib 这个库文件。编译的时候会出现错误,因为用 VC 打开 vdwlibs.dsw 工程文件后,有两个工程,要先将 VdwLibs 工程设为当前 Active Project ,然后在工具栏上单击右键选择 “ 组建 ” ,在弹出的编译工具栏中配置一下编译平台的设置:选择 Win32 WDM Checked 平台(因为我们用的 XP ),然后编译就应该可以了。
(2 )是利用向导生成驱动程序框架
DS有个 DriverWizard 可以生成驱动程序的大体框架。
第一步:选择开发环境VC6.0 ,命名。
第二步:选择WDM Driver 不用修改。
第三步:选择WDM Function Driver
第四步:选择USB 并设置好 PID 和 VID 要与固件里面的设置相同
第五步:设置端点,以及缓冲区。也要和固件里面相对应。
接下午几步可以根据自己来设置,也可以默认。完成后会生成一个工程目录,里面包含了两个工程,一个是驱动,是我们需要的。另一个是测试程序,好像没有用。当你编译驱动的时候,会提示有错误,这是因为 DS 里面的一个 BUG ,选择 project-setting ,左边两个工程选驱动那个,点右边 LINK 选项卡,删除 Object/library module 项的 ntstrsafe.lib 。再编译就能通过了。
(3 )接下去就是对刚才生成的框架进行研究,添加一些代码以使驱动完整。
首先来了解下生成的框架。
DriverEntry()该例程是当系统检测到与驱动程序支持的设备时被调用的。
AddDevice() 该例程为系统添加一个设备。
Unload() 该例程为系统卸载设备。
以上几个例程都存在于 XXXXDriver.cpp 是设备的基本操作可以直接使用生成的代码,不用修改。
在另一个文件 XXXXDevice.cpp 中包含了具体应用函数,比如 read 和 write ,但是这个文件也存在这一个 BUG ,在开头部分有 m_Pipe1_in.Initialize(m_Lower, 0x81, 64); 和 m_Pipe2_out.Initialize(m_Lower, 3, 64); 本次定义了两个端点,一个是叫 m_Pipe1_in , 0x81 表示这个管道的属性是入( in ),缓冲区是 64. 下面一个是 m_Pipe2_out 表示是出( out ),缓冲区是 64. 向导生成的代码里面漏掉了 “0x” 这两个字符。
关键部分代码的添加
上位机应用程序,主要是通过两种方式来控制驱动,一个是 DeviceIoControl ()函数,另一个是 ReadFile ()函数和 WriteFile ()函数。本次使用的是后者。只要在 Read(KIrp I) ,和 Write(KIrp I) 里面添加相应的代码就可以。这方面可以参考 DS3.2 附带的 example 里面的那个 usbbulk 例程。
首先简述一下完成一次驱动调用所要做的具体工作。应用程序想对 USB 设备进行 I/O 操作,它需调用 Windows API 函数比如 readfile (), I/O 管理器将此请求构造成一个合适的 I/O 请求包( IRP )并把它传递给 USB 设备驱动程序。 USB 设备驱动程序接收到这个 IRP 后,根据 IPR 中包含的具体操作代码构造相应 USB 请求块( URB ),并把它放到一个新的 IRP 中,然后传递给 USB 底层驱动程序(中间层或总线驱动程序)。 USB 底层驱动程序根据 IRP 中所含的 URB 执行相应的操作,并把操作的结果返回给 USB 设备驱动程序。 USB 设备驱动程序接收到此返回的 IRP 后,将操作结果通过 IRP 返还给 I/O 管理器,最后 I/O 管理器将此 IRP 操作结果传回给应用程序,至此应用程序对设备的一次 I/O 操作完成。当上位机调用的 ReadFile ()的时候, USB 设备驱动程序要根据 IPR 中包含的具体操作代码构造相应 USB 请求块( URB ),这个 URB 的生成就在这里实现,比如例程里面的
PURB pUrb = m_Pipe1_in.BuildBulkTransfer(
Mem, // Where is data coming from?
dwTotalSize, // How much data to read?
TRUE, // direction (TRUE = IN)
NULL // Link to next URB
);
主要是通过这个函数来实现。函数实现了之后会有不同的返回值,然后打印出不同的信息。这些信息可以在 DS 调试工具 Monitor 中看到。
5 驱动开发的过程大概就是这样了。下面是应用程序的开发。主要两步,先打开设备,然后读写数据。
(1 )打开设备
Windows下面有很多针对驱动调用的 API 函数,要调用一个 USB 设备首先就要打开这个设备,其对应的 API 函数为 CreateFile ,在本次驱动中 DS 自动生成了一个 OpenByInterface.c 文件,在该文件里面对这个 CreateFile 函数进行了封装,其参数如下
HANDLE OpenByInterface(
GUID* pClassGuid, // points to the GUID that identifies the interface class
DWORD instance, // specifies which instance of the enumerated devices to open
PDWORD pError // address of variable to receive error status
)
所以在主程序中只要调用OpenByInterface 函数就可以了。本次中具体实现的代码如下:
if(g_hUsbDevice==INVALID_HANDLE_VALUE)
{
g_hUsbDevice=OpenByInterface(
&g_UsbGuid, // points to the GUID that identifies the interface class
0, // specifies which instance of the enumerated devices to open
&Error // address of variable to receive error status
);
if(g_hUsbDevice==INVALID_HANDLE_VALUE)
{
MessageBox("打开设备失败 !",NULL,MB_OK | MB_ICONHAND);
}
else
{
MessageBox("打开设备成功 !",NULL,MB_OK | MB_ICONASTERISK);
}
}
其中GUID* pClassGuid 是对应的设备的 GUID ,具体定义如下:
GUID g_UsbGuid=GUID_DEVINTERFACE_USB;//打开设备的 GUID
其中GUID_DEVINTERFACE_USB 是在 DS 生成的 Intrface.h 文件中定义的,
#define GUID_DEVINTERFACE_USB \
{ 0x6C8CFFA6, 0xCAB8, 0x45B1, { 0xAA, 0xEE, 0x1E, 0xF4, 0xDF, 0x79, 0xF8, 0xDF } }
这个ID 保证了每个 USB 驱动的唯一性,调用紊乱的情况的出现。 OpenByInterface 这个函数有一个返回值,通过的改变可以确认设备是否打开成功,然后通过 MessageBox 函数跳出不同的反馈信息。
(2 )读写数据
Windows中读取数据的 API 函数有两种,一种是 DeviceIoControl ,一种是 ReadFile 、 WriteFile 函数。前者一个函数可以读也可以写,后者把读写分开来,这次使用的是后者。
ReadFile的具体实现如下:
ReadFile(
g_hUsbDevice, //我们的设备 HANDLE hFile
DataBuffer,//输入缓冲,无 lpBuffer
64, //输出字节数 nNumberOfBytesToRead,
&BytesReturned, //实际读取到的字节数 lpNumberOfBytesRead,
NULL
)
g_hUsbDevice是对应我们的设备, DataBuffer 是我们定义的数组,读取到的数据就存在这个数组中, 64 是一次读取的字节数, BytesReturned 是实际读取到的字节数,最后一个可以默认为 NULL 。如果成功读取到了数据,数组 DataBuffer 中的数据就会更新,然后可以做任意处理。
写数据基本和读取数据类似。