`
首先谈谈这件作品的创作灵感,今年国赛有一道题是在一块覆铜板上画线,然后显示在 12864液晶上,其实要实现这个不难,难就难在精度上,国赛对这个要求很高。我找了一块普通的敷铜板测了一下任意两点之间的电阻,结果令人大失所望,不管怎么测,电阻都接近于零而且变化不大,然后上网查资料,有没有那种电阻率比较大的板子,也没有找到。当时还找了那个无穷平面的电阻网络分析,大家知道铜板表面的电阻并不是线性分布的,反正公式很复杂的样子,没看懂,于是去问电路老师,在我的预料之内,老师也不知道,就这样放弃了。刚好在网上找到了那种电阻屏,于是就索性改用电阻屏了,但是如果仅仅是在12864上显示,就太没意思了,刚好学长开会的时候提到了用VB编上位机,于是就开始学VB,刚开始是想做一个画图板,但后来觉得那个用处不大,要是能写字就好了。好啦,废话不多说了,开始进入正题。 先说说 VB编程,核心部分当然是那个手写识别了,也是查了一些资料,很复杂的样子,单凭一人之力想做一个手写输入法很困难,网上七找八找,看看有没有源代码之类的,但事实很快就证明了我是在做白日梦,那些应该属于技术机密了,一般找不到。但值得庆幸的是,找到了一个手写控件,名字叫ActivateHandWrite,如果你不知道什么是控件的话,那下面的就不用看了,建议先学学VB的基础知识。好啦,最难的部分就这样轻松的解决了。编上位机主要是用到串口 通信那块,涉及到一个控件叫做 MsComm,在对VB有一定的了解下,建议看看那本《Visual Basic与RS232串行通信控制》,这本书讲得非常好,不看后悔死你,当时就是因为数据接收类型问题,以及怎么处理接收过来的数据,纠结了很久,最后在这本书上找到了解决办法。这里提供一种比较好的数据接收方法,1.把InputMode设为comInputModeBynary, Rthreshold设为1,这样每接收到一个字符就会触发一次Oncomm事件,下面以一段程序为例进行讲解:
Case comEvReceive '接收到了RThreshold个字符。持续产生该事件,直到使用了Input属性删除了接收缓冲区中的数据 With MSComm1 Dim ab(6) As Byte '字节数据类型数组,用来存储接收到的一组字节数据 Dim av As Variant '用来从接收缓冲区读取数据 av = .Input '读取一个接收字节 ab(1) = av(0) '转换保存到字节数据类型数组 If ab(1) = &HF0 Then '判断是否为数据开始标志 .RThreshold = 0 '关闭OnComm事件接收,此时并不触发OnComm事件(类似于单片机的串口中断) Do DoEvents Loop Until .InBufferCount >= 5 '循环等待MSComm1接收缓冲区>=5个字节
av = .Input '读取第二个数据字节, ab(2) = av(0) '转换保存到字节数据类型数组 av = .Input ab(3) = av(0) av = .Input ab(4) = av(0) av = .Input ab(5) = av(0) av = .Input ab(6) = av(0) '该字符为模式选择 .RThreshold = 1 '打开MSComm1事件接收
End If End With End Select 这里以接收5个字节为例,将其存放到数组av里面,每次存一个后就将其赋值给数组ab,前面的F0为起始字符,注意ab(6)一定要是Byte类型的数组,接下来只要对接收到的坐标进行相应处理就OK了,具体后面再详解。 接下来再谈谈如何把手写控件识别出来的字输入到我们想要输入的文本框里面呢?我想这是这个上位机最难的部分了,需要调用一些 API函数,其实本人对API也是一知半解。Windows系统是一个消息传递机制,它就像一个黑匣子一样,操作系统是直接对硬件进行操作的,我们不知道硬件到底发生了什么变化,只知道这一变化由系统返回了一个什么样的消息,我们再对这个消息进行处理。如果仅仅是将得到的字符显示在本窗体里,那样就没意思了,所以我们需要先找到获得有输入焦点的窗口的句柄,然后利用PostMessage函数将消息传送过去。(注意SendMessage和PostMessage的区别),花了好几天时间,几乎找遍了各大论坛,就是关于跨线程调用的问题,自己也是似懂非懂,下面列出需要用到的API函数:Rem 以下6个API函数的作用是将字符传送到光标所在控件 Private Declare Function GetCurrentThreadId Lib "kernel32" () As Long Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As Long, lpdwProcessId As Long) As Long Private Declare Function GetForegroundWindow Lib "user32" () As Long Private Declare Function AttachThreadInput Lib "user32" (ByVal idAttach As Long, ByVal idAttachTo As Long, ByVal fAttach As Long) As Long Private Declare Function GetFocus Lib "user32" () As Long Private Declare Function PostMessage& Lib "user32" Alias "PostMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any)
如果对API没有一定的了解,看了也是白搭。下面无偿奉献源码:
Private Sub Command_Click(Index As Integer) Dim Text1 As String Text1 = Command(Index).Caption
Dim data() As Byte Dim hwd As Long Dim hwnd1 As Long '定义获得焦点窗口的句柄 Dim idself As Long, id As Long
hwd = GetForegroundWindow '获得前台窗口 If hwd <> Form1.hwnd Then idself = GetCurrentThreadId '获得当前进程的ID id = GetWindowThreadProcessId(hwd, 0) '获得指定句柄窗体的ID AttachThreadInput id, idself, True '附加输入线程 hwnd1 = GetFocus '得到当前键盘光标所在的窗口的句柄
'不知道是中文还是英文或是标点,用下面的方法判断并发送消息 Dim str As String, i As Integer Dim kkcc As Variant str = Text1 data = StrConv(str, vbFromUnicode)
For i = 1 To Len(str) kkcc = Asc(Mid(str, i, 1)) If kkcc < 0 Then data = StrConv(Mid(str, i, 1), vbFromUnicode) PostMessage hwnd1, wm_char, data(0), 0& PostMessage hwnd1, wm_char, data(1), 0& Else PostMessage hwnd1, wm_char, kkcc, ByVal 0 End If Next
' 取消附加的输入线程 AttachThreadInput id, idself, False End If Call Clear End Sub
注意这段程序里还有一个很重要的问题,如果不知道识别的字符是汉字还是英文怎么办呢?它们占的字节是不一样的。说到这里,硬骨头基本上已经啃下来来了。 还有一些很重要的问题需要注意,输入法是需要始终保持前端显示的,并且不能拥有输入焦点,否则的话你试试看,根本无法输入,只要一点击输入法,文本框就失去焦点了,窗体变成灰色。
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long Rem 转移输入焦点的声明 Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long Rem 窗口置顶的声明(使窗口始终保持在最前端显示,非常必要) Private Declare Function SetWindowPos Lib "user32" (ByVal hwnd As Long, ByVal hWndInsertAfter As Long, ByVal X As Long, ByVal Y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long 路漫漫其修远兮,吾将上下而求索,好好努力吧!看不懂不要紧,慢慢来。 SendMessage hwnd1, WM_NCACTIVATE, WA_ACTIVE, 0 注意这里的SendMessage不是传递字符消息,而是让有输入焦点的窗体不失去焦点,具体细节自己查资料。
对坐标进行采集后怎样才能将其显示出来呢?最初的设想是在一个PictureBox控件里将采集的坐标画出来,字的模样是显示出来了,但手写控件不识别,只有用鼠标写才能识别,于是不得不用鼠标API函数来模拟了。 Rem 控制鼠标移动的声明 Private Declare Sub mouse_event Lib "user32" (ByVal dwFlags As Long, ByVal dx As Long, ByVal dy As Long, ByVal cButtons As Long, ByVal dwExtraInfo As Long)
通过对采集的坐标进行滤波,基本上能将电阻屏变成一个触摸板,可以上下左右移动鼠标,但要手写的话,需要将鼠标左键按下才能写,流程如下: 模拟左键按下、移动鼠标、释放左键,但实际上要让单片机获取一个鼠标左键按下(相当于手写笔按下)的消息很难,最后采用的是下面这个函数: Rem 将鼠标移动到屏幕指定位置 Private Declare Function SetCursorPos Lib "user32" (ByVal X As Long, ByVal Y As Long) As Long 有了这个函数,就不需要模拟鼠标左键按下了 单片机用的是外部中断,只要电阻屏有触摸,就触发外部中断,顺便提一下,用的是ADS7843电阻屏专用的AD转换芯片,可以很容易实现电阻屏电极的切换和坐标的采集,无触摸时,让单片机发送一个特定的字符,作为鼠标左键释放的消息。这里还要注意系统默认的坐标原点是左上角,用下面的API函数将坐标原点转换到窗体的左上角,这样处理坐标就方便多了: Rem 屏幕坐标转客户坐标 Private Declare Function ScreenToClient Lib "user32" (ByVal hwnd As Long, lpPoint As POINTAPI) As Long Private Type POINTAPI X As Long Y As Long End Type 基本上,讲到这里,难点都解决了。下面谈谈硬件部分,其实硬件再简单不过了,用的是STC89C52单片机,一个Max232芯片,一块ADS7843, 一块 7寸的电阻屏,程序部分除了ADS7843驱动外,都没有什么技术含量,这里不再啰嗦。下面展示一下作品图片。
作者:李小龙 QQ: 1358774810 2013年11月5日
`
|