给USB HID设备发送数据

image

《圈圈教你玩USB》 第五章 提到可以自己设计一个HID设备,于是,进行实验,刷新他的 Firmware 运行他的测试程序,在 Win7 3和64 下都不需要安装驱动程序,直接使用应用程序即可。

usb0device

《圈圈教你玩USB》 第五章 带的 Firmware 程序和Windows下的应用程序:

用户自定义HID设备上位机应用程序及源代码包

用户自定义HID设备下位机固件源代码包

自己动手直接编写一个 VS2008 程序来对这个设备发送数据(主要是我一直没法再 VS2008下成功编译他的Application)

//http://blog.csdn.net/xuxinhua/article/details/6329182
#include "stdafx.h"
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <setupapi.h>
extern "C" {
void __stdcall
HidD_GetHidGuid (
   OUT   LPGUID   HidGuid
   );
typedef struct _HIDD_ATTRIBUTES {
    ULONG   Size; // = sizeof (struct _HIDD_ATTRIBUTES)

    //
    // Vendor ids of this hid device
    //
    USHORT  VendorID;
    USHORT  ProductID;
    USHORT  VersionNumber;

    //
    // Additional fields will be added to the end of this structure.
    //
} HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES;

BOOLEAN __stdcall
HidD_GetAttributes (
    IN  HANDLE              HidDeviceObject,
    OUT PHIDD_ATTRIBUTES    Attributes
    );
}

#pragma comment( lib, "hid.lib" )
#pragma comment( lib, "setupapi.lib" )

int main(array<System::String ^> ^args)
{
	 GUID HidGuid;
	 BOOL Result;

	//获取HID设备的接口类GUDI
		HidD_GetHidGuid(&HidGuid);
	//输出一下看看GUID
		printf("HID GUID: {%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}\n"
		   , HidGuid.Data1, HidGuid.Data2, HidGuid.Data3
		   , HidGuid.Data4[0], HidGuid.Data4[1], HidGuid.Data4[2], HidGuid.Data4[3], HidGuid.Data4[4]
		   , HidGuid.Data4[5], HidGuid.Data4[6], HidGuid.Data4[7] );
	
	//根据获得的GUID枚举HID设备
	HDEVINFO hDevInfo = SetupDiGetClassDevs( &HidGuid, NULL, 0, DIGCF_PRESENT|DIGCF_DEVICEINTERFACE );
    if( INVALID_HANDLE_VALUE != hDevInfo )
    {
        SP_DEVICE_INTERFACE_DATA strtInterfaceData = { sizeof(SP_DEVICE_INTERFACE_DATA) };
        for( DWORD index=0; SetupDiEnumDeviceInterfaces(hDevInfo,NULL,&HidGuid,index,&strtInterfaceData); ++index )
        {
            char buf[1000];
            SP_DEVICE_INTERFACE_DETAIL_DATA& strtDetailData = (SP_DEVICE_INTERFACE_DETAIL_DATA&)buf[0];
            strtDetailData.cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
            if( SetupDiGetDeviceInterfaceDetail(hDevInfo,&strtInterfaceData,&strtDetailData,_countof(buf),NULL,NULL) )
            {
                printf("[%d] path: %ls\n", index, strtDetailData.DevicePath);
				
				//这里打开的有可能是USB键盘鼠标这样比较特别的设备(只能查询)
                HANDLE hUsb = CreateFile( strtDetailData.DevicePath, 
							NULL, FILE_SHARE_WRITE, 
							NULL, OPEN_EXISTING, 
							FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, 
							NULL );
	
                // 查询设备标识
                HIDD_ATTRIBUTES strtAttrib = { sizeof(HIDD_ATTRIBUTES) };
				Result=HidD_GetAttributes(hUsb,&strtAttrib);
                
				//所以这里要关闭一下,后面找到我们自己的设备确定可以写入再打开一次
			    CloseHandle( hUsb );
                
				if(TRUE==Result)
                    {
						if ((0x8888==strtAttrib.VendorID) && 
						   (0x6==strtAttrib.ProductID))	//找到我们自己的设备
						     {
								printf("VendorID : %hX\n", strtAttrib.VendorID );
								printf("ProductID: %hX\n", strtAttrib.ProductID );
								printf("VerNumber: %hX\n", strtAttrib.VersionNumber );
	
								//确定是我们自己的设备,再打开一次,注意我们这里使用的是同步发送
								hUsb = CreateFile( strtDetailData.DevicePath, 
												   GENERIC_WRITE, FILE_SHARE_WRITE, 
												   NULL, OPEN_EXISTING, 
												   FILE_ATTRIBUTE_NORMAL, NULL );
		
								//发送报告的缓冲区,1字节报告ID+8字节报告数据。
								UCHAR WriteReportBuffer[9];
								DWORD lpNumberOfBytesWritten;
								UINT LastError;
									
								WriteReportBuffer[0]=0x00;
								WriteReportBuffer[1]=0x75;
									
								//调用WriteFile函数发送数据
								Result=WriteFile(hUsb,
												WriteReportBuffer,
											    9,
											    &lpNumberOfBytesWritten,
										       	NULL);
								//如果函数返回失败,则可能是真的失败,也可能是IO挂起
								if(Result==FALSE)
										{
											//获取最后错误代码
												LastError=GetLastError();
											//看是否是真的IO挂		
												if((LastError==ERROR_IO_PENDING)||(LastError==ERROR_SUCCESS))
													{ return TRUE;	}
												//否则,是函数调用时发生错误,显示错误代码
												else	
													{
														printf("Sending error:%d \n",LastError);
														//如果最后错误为1,说明该设备不支持该函数。
														if(LastError==1)
														{printf("This device doesn't support WriteFile function \n");}
													}


										}

                				CloseHandle( hUsb );
								}//if ((0x8888==strtAttrib.VendorID) && 
					}		//if(TRUE==Result)
            }	// if( SetupDiGetDeviceInterfaceDetail(hDevInfo,&strtInterfaceData,&strtDetailData,_countof(buf),NULL,NULL) )

        }	//for( DWORD index=0;
        
		if( GetLastError() != ERROR_NO_MORE_ITEMS )
			{printf("No more items!\n"); }

        SetupDiDestroyDeviceInfoList( hDevInfo );
    }	//if( INVALID_HANDLE_VALUE != hDevInfo )

	system("PAUSE");
    return 0;
}

 

运行结果:

usb0

程序下载

HidSend

OpenCV在捕获的摄像头上写字

这个实验和之前的画圆的类似,先捕捉图像,然后再上面输出文字。

#include "stdafx.h"
#include "highgui.h"
#include "cv.h"
#include "stdio.h"
#include <ctype.h>
#include <time.h>

int main(int argc,char ** argv)
{
	time_t	t;
	char showMsg[256];

	//the font variable    
    CvFont font; 

       CvCapture *capture = 0;

	   //从摄像头读取
       capture = cvCaptureFromCAM(0);

 
       if(!capture)
       {
              fprintf(stderr,"Could not initialize capturing...\n");
              return -1;
       }
 
       cvNamedWindow("Laplacian",1);
 
       //循环捕捉,直到用户按键跳出循环体
       for(;;)
       {
              IplImage * frame =0;
             
              //抓起一祯
              frame = cvQueryFrame(capture);
 
              if(!frame)
                     break;

	time(&t);
	strftime(showMsg, sizeof(showMsg), "%H%M%S", localtime(&t) ); //格式化输出.
   
    // 初始化字体   
    cvInitFont(&font,CV_FONT_HERSHEY_SIMPLEX, 4.5,1,0,2);//初始化字体,准备写到图片上的   
    // cvPoint 为起笔的x,y坐标   
    cvPutText(frame,showMsg,cvPoint(300,100),&font,CV_RGB(255,0,0));//在图片中输出字符  

              cvShowImage("Laplacian",frame);
 
              if(cvWaitKey(10)>0)
                     break;
       }
       cvReleaseCapture(&capture);
       cvDestroyWindow("Laplacian");
       return 0;
}

 

campaint

参考:

1.http://blog.csdn.net/jiadelin/article/details/2916329 strftime()函数用法
2.http://blog.csdn.net/jinshengtao/article/details/20549221 利用OpenCV给图像添加标注
3.http://blog.csdn.net/ycc892009/article/details/6516528 opencv-在图像上显示字符(不包括中文)

OpenCV 的cvCircle函数

写了一个小程序,获得摄像头输出之后直接在上面画图。

cvCircle(CvArr* img, CvPoint center, int radius, CvScalar color, int thickness=1, int lineType=8, int shift=0)

img为图像指针,单通道多通道都行,不需要特殊要求
center为画圆的圆心坐标
radius为圆的半径
color为设定圆的颜色,比如用CV_RGB(255, 0,0)设置为红色

thickness为设置圆线条的粗细,值越大则线条越粗,为负数则是填充效果 【参考1】

#include "stdafx.h"
#include "highgui.h"
#include "cv.h"
#include "stdio.h"
#include <ctype.h>
 
int main(int argc,char ** argv)
{
		int	r=0;

       CvCapture *capture = 0;

	   //从摄像头读取
       capture = cvCaptureFromCAM(0);

 
       if(!capture)
       {
              fprintf(stderr,"Could not initialize capturing...\n");
              return -1;
       }
 
       cvNamedWindow("Laplacian",1);
 
       //循环捕捉,直到用户按键跳出循环体
       for(;;)
       {
              IplImage * frame =0;
             
              //抓起一祯
              frame = cvQueryFrame(capture);
 
              if(!frame)
                     break;

			  r>20?r=0:r++;


			  //画一个圈  
			  cvCircle(frame,cvPoint(frame->width / 2,frame->height /2 ), 45+abs(10-r), CV_RGB(150, 100,100), 4, 8, 0);

              cvShowImage("Laplacian",frame);
 
              if(cvWaitKey(10)>0)
                     break;
       }
       cvReleaseCapture(&capture);
       cvDestroyWindow("Laplacian");
       return 0;
}

 

campaint

参考:
1.http://blog.163.com/yuhua_kui/blog/static/9679964420141104385570/ OpenCV cvCircle函数
2.http://wiki.opencv.org.cn/index.php/Cxcore%E7%BB%98%E5%9B%BE%E5%87%BD%E6%95%B0 Cxcore绘图函数

如何在XP上部署OpenCV编写的程序

本文使用环境:

1. XP 下,VS2008
2. 只适合你用Release编译出来的结果,不适合Debug版本(因为我测试Debug版本在XP上始终无法成功,所以删除了所有相关的 *d.dll)
3. 只是简单的测试之类的(如果你想更专业,可以只分发用到的DLL;或者重新编译OpenCV生成 Lib,通过静态编译将内容添加到你的EXE中)

步骤:

1.按照Debug编译,然后在工作机上测试OK
2.将生成的 Release 目录下的全部内容拷贝到dll的目录下 OpenCVXPPackage
3.如果目标机之前没有安装过 VS2008 的支持包,需要先安装一次vcredist2008sp1
4.之后在目标机上运行生成的 EXE 即可

注意:
如果出现 “应用程序正常初始化(0xc0000135)失败。请单击‘确定’,终止应用程序”,请安装 Microsoft .NET Framework 2.0

vcredist2008sp1:
vcredist2008sp1

OpenCVXPPackage(其中包含了一个编译好的测试应用程序):
OpenCVXPPackage

应用程序运行结果:
Capture

Step to UEFI (12) —- EADK 中的 Time 函数

这次的工作是基于 EADK 的,编译借用EADK的环境这样做有编译速度快,测试方便的优点,具体配置请参考 ”http://www.lab-z.com/how-to-use-eadk/“

关于 time_t 的定义可以在 \EadkPkg_A2\StdLib\Include\time.h 中看到。测试了一下,sizeof(time_t) == 4。

time()函数取得的结果是当前系统的时间,具体的定义是:返回从GMT1970年1月1日 0:0:0 开始经过的秒数表示的当前时间和日期。

下面可以看到 time() 有两种用法。

代码如下,很简单

/** @file
    A simple, basic, application showing how the Hello application could be
    built using the "Standard C Libraries" from StdLib.

    Copyright (c) 2010 - 2011, Intel Corporation. All rights reserved.<BR>
    This program and the accompanying materials
    are licensed and made available under the terms and conditions of the BSD License
    which accompanies this distribution. The full text of the license may be found at
    http://opensource.org/licenses/bsd-license.

    THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
    WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
//#include  <Uefi.h>
//#include  <Library/UefiLib.h>
//#include  <Library/ShellCEntryLib.h>

#include  <stdio.h>
#include  <time.h>

/***
  Demonstrates basic workings of the main() function by displaying a
  welcoming message.

  Note that the UEFI command line is composed of 16-bit UCS2 wide characters.
  The easiest way to access the command line parameters is to cast Argv as:
      wchar_t **wArgv = (wchar_t **)Argv;

  @param[in]  Argc    Number of argument tokens pointed to by Argv.
  @param[in]  Argv    Array of Argc pointers to command line tokens.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{

  time_t t;

  time(&t);

  printf("%ld\n",t);

  t=time(NULL);

  printf("%s\n",ctime(&t));

  return 0;
}

 

运行结果

time

下载

main

参考:

1.http://ganquan.info/standard-c/function/time Standard C 语言标准函数库速查 (Cheat Sheet)

2.书籍 《C函数实用手册》 个人感觉这本书不错,蛮实用,实例详尽。缺点是缺少一些新的C函数,可能是因为这本书目标是TC,并且是2003年的书籍。当初是为了用 Turbo C 所以才买的.

Arduino制作一个电子骰子(3)

上面几篇文章介绍的都是虚拟的电子骰子,这篇文章将会介绍用Arduino来玩实体的骰子。相比前面几篇材料更复杂,牵涉到的配备更多。当然,所有的技术问题都能找到合理的解释,暂时无法回答的问题是“这个东西能干什么?”

首先介绍使用到的材料:

1. 电动骰子机(toabao购买,电池驱动,上面有个按钮,按动时自动旋转骰子)
2. 继电器 (我们用它来控制上面骰子机的开关)
3. 电池夹(用来给骰子机供电)
4. Arduino UNO (最基础的型号足够用的,如果你有其他的型号肯定也没问题)
5. 摄像头 (我选用的是 罗技的 U-VQN42,这是很老的型号,以至于目前没有Win7的驱动)
6. 电脑 (我们用摄像头采集图像,然后使用Intel 的OpenCV来进行图像的识别处理)

可以看出这个实验涉及到的内容远远比之前的复杂,设计这个实验大约花费了2周左右,实做将近两天。

从流程上来说,功能过程如下:

Drawing1

硬件部分的介绍:
介绍Arduino的连接:

Untitled Sketch_bb

虽然骰子机内部是要安装2节电池的,但是我发现我用一节五号充电电池也能让它工作的很好,考虑到外壳强度的问题,也就直接选择一节电池了。Arduino在这里就是发挥骰子机开关的功能。

此外的建议是:连接继电器的常断开端口,这样,当断电时骰子机不会转动。

image003

骰子机原来的是亚克力外壳,因为反光的问题,在测试时结果不理想,所以重新用纸做了一个。这里特别感谢严小姐,她用灵巧的双手帮我完成了这个工作。可以看到,目前整个摄像头可以伸进去,视界中只有地盘和白色的区域,这对于识别是非常有利的。还建议使用一个外部固定光源来进行补光之类的设定。

image004

软件部分:

1. Arduino程序。

#define LED_PIN 13

void setup()
{
Serial.begin(9600);
pinMode(LED_PIN, OUTPUT);
//Default
digitalWrite(LED_PIN, HIGH);
}

void loop()
{
char c;

while (Serial.available() > 0)
{
c=Serial.read();
if (‘b’==c)
{
digitalWrite(LED_PIN, LOW);
}
if (‘s’==c)
{
digitalWrite(LED_PIN, HIGH);
}
}
}

实验中 Arduino 的程序非常简单,测试时可以使用 Putty 这样的工具打开对应串口,直接发送命令测试。

2. PC端程序。

因为要使用OpenCV,所以选择了VS2008,本打算使用Delphi,无奈他的OpenCV不成熟。

这个程序可能分成两部分,一部分是串口通讯,只需要发送部分【参考1】,另外一部分是OpenCV的识别。算法上来自【参考2】,他给出了一种非常简单的算法:首先对图像进行Canny边缘提取,然后使用FloodFill对于包络范围进行填充,同时会返回填充区域的面积,利用面积就可以判断识别出来的是否为骰子的点(例如:1点的时候面积可能在200左右,2点的话,每个点可能在50左右),对于处在合理面积中的识别区域就判断为点数。

#include “stdafx.h”
#include <opencv2/opencv.hpp>
#include <opencv/cv.h>
#include <opencv/cxcore.h>
#include “opencv2/highgui/highgui.hpp”
#include

using namespace cv;

int p1=49, p2=258; //Paramters for Canny
Mat framepic; //framepic 存放摄像头抓取图像
Mat framegray; // framepic 灰度化后的图像
Mat canny; //canny 存放摄像头处理后的图像
VideoCapture cap(0);
int count[7]={0,0,0,0,0,0,0};
int counter=0;
int retry=0;

void on_trackbar(int, void*) {
Canny(framegray, canny, p1, p2);
imshow(“canny”, canny);
}

void delayshow(int dSecond){
time_t sTime=time(NULL);

while (difftime(time(NULL),sTime)<dSecond)
{
cap>>framepic;
cvtColor(framepic,framegray, CV_BGR2GRAY);
GaussianBlur(framegray, framegray,Size(7,7),1.5,1.5);
Canny(framegray, canny, p1, p2);
imshow(“canny”, framegray);
imshow(“Preview”,canny);
int c=waitKey(33);
if(c==27) break;
}
}

int main(int argc, const char** argv)
{
//==============================================
//code for COM port communication Begin
//==============================================
DCB dcb;
HANDLE hCom;
BOOL fSuccess;
TCHAR SPort[]=L”\\\\.\\COM7″;

char txcharstart[]=”b”; //b char for Start shaking dice
char txcharstop []=”s”; //s char for Stop shaking dice

DWORD iBytesWritten;

//Prepare COM part communication
hCom = CreateFile(SPort,
GENERIC_WRITE,
0, // must be opened with exclusive-access
NULL, // no security attributes
OPEN_EXISTING, // must use OPEN_EXISTING
0, // not overlapped I/O
NULL // hTemplate must be NULL for comm devices
);

if (hCom == INVALID_HANDLE_VALUE)
{
// Handle the error.
printf (“[Error] CreateFile failed with error %d.\n”, GetLastError());
return (1);
}

SetupComm(hCom, 32, 32);

// Build on the current configuration, and skip setting the size
// of the input and output buffers with SetupComm.

fSuccess = GetCommState(hCom, &dcb);

if (!fSuccess)
{
// Handle the error.
printf (“[Error] GetCommState failed with error %d.\n”, GetLastError());
return (2);
}

// Fill in DCB: 9600 bps, 8 data bits, no parity, and 1 stop bit.

dcb.BaudRate = CBR_9600; // set the baud rate
dcb.ByteSize = 8; // data size, xmit, and rcv
dcb.Parity = NOPARITY; // no parity bit
dcb.StopBits = ONESTOPBIT; // one stop bit

fSuccess = SetCommState(hCom, &dcb);

if (!fSuccess)
{
// Handle the error.
printf (“[Errror] SetCommState failed with error %d.\n”, GetLastError());
return (3);
}
//==============================================
//code for COM port communication End
//==============================================

srand(time(NULL));

int dTime;
if(!cap.isOpened())
return -1;

cap>>framepic;
cvtColor(framepic,framegray, CV_BGR2GRAY);
Canny(framegray, canny, p1, p2);
imshow(“canny”, framegray);
imshow(“Preview”, framegray);

//You may have to find the P1 and P2 value for your environment
createTrackbar(“p1″,”canny”,&p1,1000,on_trackbar);
createTrackbar(“p2″,”canny”,&p2,1000,on_trackbar);

//Loop shaking dice and count
for(;;)
{
//Shake start
printf (“[Message] Send start command \n”);
if (FALSE==WriteFile(hCom, &txcharstart, strlen(txcharstart), &iBytesWritten,NULL)) {
printf(“[Error]: %d”, GetLastError());
retry=0;
while ((TRUE==WriteFile(hCom, &txcharstart, strlen(txcharstart), &iBytesWritten,NULL)) ||
(retry>5))
{
printf(“[Error]: %d”, GetLastError());
retry++;
}
}

dTime=rand()%2+1;

//Shake the dice for a random seconds
printf(“Shake delay %d \n”,dTime);
delayshow(dTime);

//Shake stop
printf (“[Message] Send stop command \n”);
if (FALSE==WriteFile(hCom, &txcharstop, strlen(txcharstop), &iBytesWritten,NULL)) {
printf(“[Error]: %d”, GetLastError());
retry=0;
while ((TRUE==WriteFile(hCom, &txcharstop, strlen(txcharstop), &iBytesWritten,NULL)) ||
(retry>5))
{
printf(“[Error]: %d”, GetLastError());
retry++;
}
}

//It delay 5 seconds for dice stopping
delayshow(5);

//==============================================
//code dice point recognize start
//==============================================
cap>>framepic;
cvtColor(framepic,framegray, CV_BGR2GRAY);
GaussianBlur(framegray, framegray,Size(7,7),1.5,1.5);
Canny(framegray, canny, p1, p2);
imshow(“canny”, framegray);
imshow(“Preview”,canny);

int num = 0;
for(int y=0;y<canny.size().height;y++)
{
uchar *row = canny.ptr(y);
for(int x=0;x<canny.size().width;x++)
{
if(row[x] <= 128)
{
int area = floodFill(canny, Point(x,y), CV_RGB(0,0,160));
printf(“filling %d, %d gray, area is %d\n”, x, y, area);
if(area>37 && area < 300) num++;
}
}
}
printf(“number is %d\n”, num);
//==============================================
//code dice point recognize end
//==============================================
counter++;
count[num]++;
printf(“=====================================================\n”);
printf(“Total=%4d| One | Two | Three | Four | Five | Six |\n”,counter);
printf(” | %4d|%4d | %4d | %4d | %4d | %4d|\n”,count[1],count[2],count[3],count[4],count[5],count[6]);
printf(“=====================================================\n”);
printf(“Show result delay 3s\n”);
delayshow(3);
int c=waitKey(33);
if(c==27) break;
}

system(“PAUSE”);
CloseHandle(hCom);

return 0;
}

运行结果就是下面这个样子。Console 显示统计结果,旁边一个显示灰度化后的摄像头内容,另外一个显示边缘提取后的结果。

image005

我只做了一个多小时,收集的结果不多,看起来结果并不平均,不知道如果做个一万次之类的会是什么结果。

运行时的视频(刚开始放在Tudou的时候,审核没有通过,估计是因为名称中有“骰子”。后来申诉了一下)

http://www.tudou.com/programs/view/TtqRy-KRr-8/?resourceId=414535982_06_02_99

其实更好的方案是记录下每次的运行结果,因为我发现这个东西每次之间似乎有相关性。比如说这次出现六点,那么下次出现六点的可能性似乎大于 1/6。猜测和骰子机的摇骰子方式有相关性。

最后的最后,吐槽一下电脑的问题:第一个没想到是前面说过的罗技摄像头居然不支持Windows 7,如果你也想做同样的实验需要购买摄像头不妨考虑一下买个二手的微软摄像头,只是外观差一些,效果和支持应该比罗技的好多了;第二个是梅捷 SY-D2700-U3M 的主板,BIOS中居然没有设计风扇温控功能【参考4 提到的Watchdog就是为他设计的】。最近天气热了,硬盘丢失的问题更是频频出现,鲁大师检测CPU温度50左右,主板温度在60左右就会出现这个现象。当我连接一个风扇到上面的时候,5000RPM让人难以忍受。下一个实验就是如何做一个能够调节转速的风扇了。

参考:
参考1:VS2008串口发送参考代码
http://www.lab-z.com/%E4%B8%87%E4%B8%87%E6%B2%A1%E6%83%B3%E5%88%B0%EF%BC%8C%E4%B8%B2%E5%8F%A3%E7%9A%84%E6%95%85%E4%BA%8B/
参考2:超简单的opencv骰子点数识别,效果居然还不错 http://chen-xiao.com/?p=250
参考3:OpenCV的imshow无法正常显示视频http://haibuo1981.iteye.com/blog/1401764
非常感谢作者解决了困惑我一下午的问题,如果你用 cap >> img 这样之后,想再通过显示imShow到屏幕上(特别是在一个循环中),必须使用waitKey(xx)给程序足够的时间来更新画面。
参考4:使用 Arduino 打造一个硬件的Watchdog
http://www.lab-z.com/%E4%BD%BF%E7%94%A8-arduino-%E6%89%93%E9%80%A0%E4%B8%80%E4%B8%AA%E7%A1%AC%E4%BB%B6%E7%9A%84watchdog/

万万没想到,串口的故事

周末在家进行实验,打算写个VS2008串口发送数据的小程序。结果万万没想到麻烦一大堆。

目标:用VS2008发送串口数据。

为了实现这个目标,需要有能发送和接受串口数据的设备,我有一只USB转串口线,又找到了一个USB转串口的小板子。

20140705984

下面的这个小卡在之前的《使用 Arduino 打造一个硬件的Watchdog》出场过。

20140705985

问题是这两个哥们连不上啊~当然对于每个都分别测试过 loop back.唯独连在一起的时候工作不正常。

20140705981

串口公头定义如下

c2cec3fdfc039245937957298794a4c27c1ed21b0ff405fb

4 数据终端准备好(DTR)
5 信号地线(SG)
6 数据准备好(DSR)
7 请求发送(RTS)
8 清除发送(CTS)
9 振铃指示(RI)

插上之后发现两个都是 ch341A:

dv1

他们之间无法正常通信(当然 TXD TXD GND 已经连接好),乱码,严重的乱码,发送 01 接收到的是 7F,尝试使用Putty和WCH自己的工具都是不行的,波特率之类的设定经过一遍一遍的检查,确定都已经设置为相同了。真是万万没想到啊!

本打算放弃了,忽然想起来前一段买了一个 USB转IIC的小卡,可以当作串口用,也是CH340系列的芯片。再找出我珍藏多年的25米串口线,连在一起。

20140705986

测试他和我那个USB转串口线,通过Putty可以通讯,一切正常。

dv2

终于开始调试程序,发现程序运行时出现错误,GetLastError = 2 意思是“ERROR_FILE_NOT_FOUND 2 (0x2) The system cannot find the file specified.” 【参考2】,但是实验 COM6是可以的。搜索资料,找到【参考3】,原来是我用的下面这样的形式,只给COM1-9来用…….

hComm = CreateFile( “com6”, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );

要想打开COM10及其以后的必须使用\\\\.\\COM27这样的形式。

CreateFile(
“\\\\.\\COM27”, // address of name of the communications device
fdwAccess, // access (read-write) mode
0, // share mode
NULL, // address of security descriptor
OPEN_EXISTING, // how to create
0, // file attributes
NULL // handle of file with attributes to copy
);

万万没想到啊,还有这样的限制。继续下面的调试。

继续测试发现我的程序这边发送,那边可以收到了,但是只能收到前面几个字节。我的程序端显示发送的字符串数量是正确的,但是接收到的字数却是少了一截。

xx

实验发现如果升高波特率比如使用 9600反倒没问题。于是怀疑这个问题和超时相关,查看COMMTIMEOUTS相关的资料。

// COMMTIMEOUTS对象

COMMTIMEOUTS comTimeOut;
// 接收时,两字符间最大的时延
comTimeOut.ReadIntervalTimeout = 0;
// 读取每字节的超时
comTimeOut.ReadTotalTimeoutMultiplier = 0;
// 读串口数据的固定超时
// 总超时= ReadTotalTimeoutMultiplier * 字节数+ ReadTotalTimeoutConstant
comTimeOut.ReadTotalTimeoutConstant = 0;
// 写每字节的超时
comTimeOut.WriteTotalTimeoutMultiplier = 0;
// 写串口数据的固定超时
comTimeOut.WriteTotalTimeoutConstant = 0;
// 将超时参数写入设备控制
SetCommTimeouts(hCom,&comTimeOut);

上面写为0表示会无限等待下去。但是仍然不好用。

调试又发现如果在WriteFile的时候断点调试,是可以收到的。但是我在他前面加入一个 sheep之后仍然现象相同。

最终,万万没想到,真相只有一个:我在最后设定了关闭串口 CloseHandle(hCom) ,但是如果这个时候没有完成发送就被截断(按道理,应该是同步操作,但是不知道为什么,这里不会等待完全发送)。对于我程序来说解决方法就是把关闭串口放在 pause 的后面………

// ComPortSend.cpp : main project file.
//http://forums.codeguru.com/showthread.php?442634-Serial-port-program-in-c-language
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <conio.h>


int main(int argc, char *argv[])
{
   DCB dcb;
   HANDLE hCom;
   BOOL fSuccess;
   TCHAR SPort[]=L"\\\\.\\COM27";
   char txchar[]="This is a test from www.lab-z.com!";
   DWORD iBytesWritten;

   hCom = CreateFile(SPort,
                    GENERIC_WRITE,
                    0,    // must be opened with exclusive-access
                    NULL, // no security attributes
                    OPEN_EXISTING, // must use OPEN_EXISTING
                    0,    // not overlapped I/O
                    NULL  // hTemplate must be NULL for comm devices
                    );

   if (hCom == INVALID_HANDLE_VALUE) 
   {
       // Handle the error.
       printf ("CreateFile failed with error %d.\n", GetLastError());
       return (1);
   }

   SetupComm(hCom, 32, 32);//输入输出缓冲区大小都是1024个字节


   // Build on the current configuration, and skip setting the size
   // of the input and output buffers with SetupComm.

   fSuccess = GetCommState(hCom, &dcb);

   if (!fSuccess) 
   {
      // Handle the error.
      printf ("GetCommState failed with error %d.\n", GetLastError());
      return (2);
   }

   // Fill in DCB: 300 bps, 8 data bits, no parity, and 1 stop bit.

   dcb.BaudRate = CBR_300;     // set the baud rate
   dcb.ByteSize = 8;             // data size, xmit, and rcv
   dcb.Parity = NOPARITY;        // no parity bit
   dcb.StopBits = ONESTOPBIT;    // one stop bit

   fSuccess = SetCommState(hCom, &dcb);

   if (!fSuccess) 
   {
      // Handle the error.
      printf ("SetCommState failed with error %d.\n", GetLastError());
      return (3);
   }

   WriteFile(hCom, &txchar, strlen(txchar), &iBytesWritten,NULL);
   printf ("Serial port %s successfully reconfigured. \n", "COM27");

   system("PAUSE");

   CloseHandle(hCom);

   return (0);
}

 

终于我的Putty在另外一端能够接收到完整的数据了。

代码下载 ComPortSend

后记:从我目前的认知来看,USB转串口质量最好的是 FT232 ,然后是 PL2xx(严重的问题是它没有 Win8的驱动) ,CH34x系列只能排在最后。如果成本压力不是很大,有机会应该优先考虑Ft232。

参考:
1. http://www.elecfans.com/yuanqijian/jiekou/200801247510.html
2. http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx System Error Codes (0-499)
3. http://bbs.csdn.net/topics/80130516 用CreateFile打开串口设备,出现问题?
4. http://www.lab-z.com/%E4%BD%BF%E7%94%A8-arduino-%E6%89%93%E9%80%A0%E4%B8%80%E4%B8%AA%E7%A1%AC%E4%BB%B6%E7%9A%84watchdog/ 使用 Arduino 打造一个硬件的Watchdog

指定过程变量i用寄存器的问题

Pmason_rose 问我“VC中强制一个过程中变量i使用ebx咋写”?

VC2008下面先写一个简单的程序

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])
{
int i;

for (i=0;i<10;i++)
{
printf("%d: www.lab-z.com\n",i);
}
getchar();
return 0;
}

确定上面的程序可以正常编译通过之后,可以设置 Assembler Output 中,让VC2008直接输出机器码和汇编代码:

forcereg

 

在 Debug目录下会出现 loop.cod,内容如下,可以看出使用内存变量 i 做的循环

	ff		 lea	 edi, DWORD PTR [ebp-204]
  00012	b9 33 00 00 00	 mov	 ecx, 51			; 00000033H
  00017	b8 cc cc cc cc	 mov	 eax, -858993460		; ccccccccH
  0001c	f3 ab		 rep stosd

; 9    : 	int i;
; 10   : 
; 11   : 	for (i=0;i<10;i++)

  0001e	c7 45 f8 00 00
	00 00		 mov	 DWORD PTR _i$[ebp], 0
  00025	eb 09		 jmp	 SHORT $LN3@wmain
$LN2@wmain:
  00027	8b 45 f8	 mov	 eax, DWORD PTR _i$[ebp]
  0002a	83 c0 01	 add	 eax, 1
  0002d	89 45 f8	 mov	 DWORD PTR _i$[ebp], eax
$LN3@wmain:
  00030	83 7d f8 0a	 cmp	 DWORD PTR _i$[ebp], 10	; 0000000aH
  00034	7d 1d		 jge	 SHORT $LN1@wmain

; 12   : 	{
; 13   : 		printf("%d: www.lab-z.com\n",i);

  00036	8b f4		 mov	 esi, esp
  00038	8b 45 f8	 mov	 eax, DWORD PTR _i$[ebp]
  0003b	50		 push	 eax
  0003c	68 00 00 00 00	 push	 OFFSET ??_C@_0BD@OPIHGHME@?$CFd?3?5www?4lab?9z?4com?6?$AA@
  00041	ff 15 00 00 00
	00		 call	 DWORD PTR __imp__printf
  00047	83 c4 08	 add	 esp, 8
  0004a	3b f4		 cmp	 esi, esp
  0004c	e8 00 00 00 00	 call	 __RTC_CheckEsp

; 14   : 	}

  00051	eb d4		 jmp	 SHORT $LN2@wmain
$LN1@wmain:

首先,使用寄存器变量试试,用register 关键字修饰 Int i; 修改程序

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])
{
	register int i;

	for (i=0;i<10;i++)
	{
		printf("%d: www.lab-z.com\n",i);
	}
	getchar();
	return 0;
}

查看结果

; 9    : 	register int i;
; 10   : 
; 11   : 	for (i=0;i<10;i++)

  0001e	c7 45 f8 00 00
	00 00		 mov	 DWORD PTR _i$[ebp], 0
  00025	eb 09		 jmp	 SHORT $LN3@wmain
$LN2@wmain:
  00027	8b 45 f8	 mov	 eax, DWORD PTR _i$[ebp]
  0002a	83 c0 01	 add	 eax, 1
  0002d	89 45 f8	 mov	 DWORD PTR _i$[ebp], eax
$LN3@wmain:
  00030	83 7d f8 0a	 cmp	 DWORD PTR _i$[ebp], 10	; 0000000aH
  00034	7d 1d		 jge	 SHORT $LN1@wmain

; 12   : 	{
; 13   : 		printf("%d: www.lab-z.com\n",i);

  00036	8b f4		 mov	 esi, esp
  00038	8b 45 f8	 mov	 eax, DWORD PTR _i$[ebp]
  0003b	50		 push	 eax
  0003c	68 00 00 00 00	 push	 OFFSET ??_C@_0BD@OPIHGHME@?$CFd?3?5www?4lab?9z?4com?6?$AA@
  00041	ff 15 00 00 00
	00		 call	 DWORD PTR __imp__printf
  00047	83 c4 08	 add	 esp, 8
  0004a	3b f4		 cmp	 esi, esp
  0004c	e8 00 00 00 00	 call	 __RTC_CheckEsp

; 14   : 	}

  00051	eb d4		 jmp	 SHORT $LN2@wmain

恩就是说编译器根本没有鸟我…..查看资料:

“声明为register类型的变量提示计算机这个变量应该存储于机器的硬件寄存器而不是内存中。通常,寄存器变量比存于内存中的变量访问起来效率更高。但是,编译器并不一定要理睬register关键字,如果有太多的变量被声明为register类型,那么编译器只选取前几个,将其实际存储于寄存器中,其余的就按普通变量处理。如果一个编译器自己具有一套寄存器优化方案的话,它也可能忽略register关键字,其依据是由编译器决定哪些变量春处于寄…”【参考1】

还有其他办法,就是直接插入汇编语言。修改程序如下:

// loop.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])
{
	int i;

__asm        
{   
	mov	ebx,0
next:
    push ebx
	mov i,ebx
} 
		printf("%d: www.lab-z.com\n",i);
__asm        
{	
	pop	ebx
    inc ebx
    cmp ebx,10
	jNz	next
} 

	getchar();
	return 0;
}

运行结果和之前的程序完全一样,对应的汇编和机器码

; 9    : 	int i;
; 10   : 
; 11   : __asm        
; 12   : {   
; 13   : 	mov	ebx,0

  0001e	bb 00 00 00 00	 mov	 ebx, 0
$next$5245:

; 14   : next:
; 15   :     push ebx

  00023	53		 push	 ebx

; 16   : 	mov i,ebx

  00024	89 5d f8	 mov	 DWORD PTR _i$[ebp], ebx

; 17   : } 
; 18   : 		printf("%d: www.lab-z.com\n",i);

  00027	8b f4		 mov	 esi, esp
  00029	8b 45 f8	 mov	 eax, DWORD PTR _i$[ebp]
  0002c	50		 push	 eax
  0002d	68 00 00 00 00	 push	 OFFSET ??_C@_0BD@OPIHGHME@?$CFd?3?5www?4lab?9z?4com?6?$AA@
  00032	ff 15 00 00 00
	00		 call	 DWORD PTR __imp__printf
  00038	83 c4 08	 add	 esp, 8
  0003b	3b f4		 cmp	 esi, esp
  0003d	e8 00 00 00 00	 call	 __RTC_CheckEsp

; 19   : __asm        
; 20   : {	
; 21   : 	pop	ebx

  00042	5b		 pop	 ebx

; 22   :     inc ebx

  00043	43		 inc	 ebx

; 23   :     cmp ebx,10

  00044	83 fb 0a	 cmp	 ebx, 10			; 0000000aH

; 24   : 	jNz	next

  00047	75 da		 jne	 SHORT $next$5245

; 25   : }

因此,直接嵌入汇编是一个很好的解决方法。需要注意的是堆栈的平衡以及在复杂的数据结构和操作的情况下一定要保证不能干扰参数传递。

参考1:http://bbs.csdn.net/topics/300225947