曾经对USB开发有过兴趣,也买了开发板和配套书籍,研究了半天,发现自己对用DIY一个USB鼠标或者一个USB键盘根本不感兴趣,书上的也尽是接口啊/协议什么,目的还是教你做个鼠标器..而我只想做一个能通过USB接口实现简单通讯功能的小玩意儿~~~
其实早就对AVRUSB(现在叫V-USB)这套东西有兴趣了,它使用AVR单片机模拟USB接口协议,所需要的元器件很简单,不需要专用的USB接口芯片,也不需要弄懂很复杂的协议,相关的程序/小制作网上也多的是,只是对USB接口的电压表/温度计/红外分析仪这类制作没心情,想搞一个独特的,有一定实用性的,又能跟网络搭上关系的东西,这不又在"爱折腾"看到这个网站访客计数器,于是DIY开始了...
规划:
1.首先需要一个上位机软件去取得网站访客计数.
爱折腾上的这个是通过web程序生成一个包含计数值的文本文件,然后又上位机读取后发给下位机显示,这样就要求这个网站计数器必须跟网站在一起,也就是说你能控制这个访客计数程序提供你想要的数据格式,而我想实现的读取现在使用很广泛的cnzz的访客计数器,这就要求上位机必须能实现模拟登录,并解析web页面,取出所需的数字,经过合理的数据转换后发给下位机显示,能显示今日/昨日/每日/累计的PV/访客/IP共12个项目,其中目前累计PV已经达到了4千万,这就要求下位机至少有8个数码管.
2.需要一个开发V-USB设备上位机的接口库,因为V-USB并未为C#提供一个标准开发库,所以要找到这么一个支持C#开发的接口,所幸找到了,这就是LIBUSBDOTNET,而且是开源的,当然这个库提供的例程是针对通用USB设备的,还好网上也有这种用AVRUSB的上位机下位机例程.
3.下位机:
(1)USB接口:MCU采用M8,电压5V,通过3.6V稳压管实现跟USB连接(USB接口电平是3.0~3.6V)
(2)数据显示器:规划是显示9位数字,即最多能显示9亿点击量,有相应的显示项目的指示,有按键实现显示项目的切换,网上可供选择的模块有两种(这只是我个人的想法,想一次就购买全所有的元件,这样只能在一家店铺选择了),一种可以显示16位数字,没有LED指示灯,有键盘,数码管个头比较小,不能显示小数点,另一种是能显示8位数,8个指示灯,8个按钮,能显示小数点,考虑到今后的几年计数值不太可能达到亿次,所以8位即可,加上有LED指示灯,减少了清晰指示当前显示项目的编程压力,所以最后选择了后一个模块.
实战:
1.上位机编程:
因为不用delphi好多年,目前用的最多的客户端编程软件还是vs2008(C#),因此搜索了网络,找到了模拟登录的方法,并且研究后确认cnzz可以通过GET的方法提交数据,返回的数据也是有规律的,可以被分类提取,整个过程用到了"模拟提交"/"正则匹配"等技术,经过编程验证,数据读取成功!而且就算没有下位机,这个程序也是一个很好的网站访客技术软件,能实现定时刷新功能.
2.下位机硬件:
因为V-USB要求AVR单片机的晶振频率为12M,而且必须有精度保证,所以内置的RC振荡器是不能用了,不需外置晶振电路,至于USB硬件接口,参照相关的标准及电路即可,3.6V的稳压管平时不太用,一般手头不会有存货的,所幸我早有打造v-usb的计划,早已经买了一批放那儿了.按电路搭建起来,检查电源是否短路,然后插入电脑USB接口,顺利被电脑发现,尽管还不能被识别.
3.下位机程序:
网上下到的avr驱动包大部分用的是AVR-GCC,此前一直用的是CVAVR,所以专门下载了winavr和avrstudio的最新版本,经过几天折腾,好歹能顺利编译出基本的程序了,只是对GCC的MAKEFILE还是不甚了了,不过要研究也是以后的事情,现在最主要的还是先把眼前的事情完成.
程序分三大部分:一部分是V-USB驱动程序,这个按照通常的方法修改必要的参数,比如PID,VID,USB接入的端口及引脚等等,程序本身是不能动的,可以自定义的仅仅是usbFunctionSetup,usbFunctionRead,usbFunctionWrite这三个函数,一般来说usbFunctionSetup就可以传送少量的数据,这在小制作中是够用了,如果需要大量数据传输则要用到usbFunctionRead,usbFunctionWrite了.这部分要完成数据的接收/判别/存储功能.
第二部分是对接收的数据按事先规定好的协议进行处理,处理成可供显示的格式.
第三部分是数据的显示,及按键输入的控制,因为买显示模块的时候店家已经提供相应的库,应用到程序里就可以了.
4.上下位机的通讯:
这是最重要也是最困难的.
首先要先定下通讯协议和数据格式.刚开始的时候打算用LIBUSBDOTNET中的 OpenEndpointWriter 类向下位机传送批量数据,就是从网络接收到的12个访问计数项,后来发现网上根本没有跟V-USB有关的 OpenEndpointWriter 类的用法例程,下位机倒是有用usbFunctionRead,usbFunctionWrite来处理批量数据的,不过人家用的上位机是delphi或者BCB的,根本没有C#的,所以这个方案最终放弃,转而使用 UsbSetupPacket 和 ControlTransfer 传输数据,下位机则使用usbFunctionSetup接收数据.
因为usbFunctionSetup只能传送8个字节,data[0]专门用于主机向设备发送命令的,比如要向设备发送数据或者要求设备向主机发送数据等等,这是USB协议规定好的,不能乱用,data[1]一般用做存放自定义的命令字节,比如要发送的数据类型/命令类型,这是可以任意规定的,剩下的data[2..7]则是可以任意用来放置要传送的数据,直到了这个规定,接下来就要设计一个数据格式/协议了:data[1]放置12个计数项目的编号,从0到11,data[2..7]放置计数数值,这个数值该如何放置才能更简单更容易处理呢?可以用的方法有两个,一个是直接将一个整型数(四字节)拆分成4个字节放入data[2..3],data[4..5],下位机接收后再还原出来,这样能表示的数字非常巨大,完全满足下传数据的范围,但是这样的话下位机的数据处理要求比较难,而V-USB要求除驱动外的程序运行时间必须小于20ms,否则可能造成数据丢失,另外一个方案是将8位数字拆成8个BCD码,放入4个字节,传到下位机后,下位机只要按顺序将这8个数分别写入8个数码管即可,根本无需任何的数据转换,这样仅仅是上位机多了点程序处理环节,而下位机则节约了大量的运行时间!因此最终方案选择了后一个.但是后来还是出问题了,出问题的就是这个UsbSetupPacket这个类的参数(跟下位机的usbFunctionSetup对应)的data[2..3],data[4..5],data[6..7]的数据类型是有符号16位整型,也就是说,其数据范围是+-32768,转换成二进制数就是0111 1111 1111 1111高字节最大的数字范围只有127,而两位BCD码转成10二进制最大是9*256+9=2313,远大于能存储的数值,最终采取了如下的数据结构:
按8位的显示数字算,每4位用两个字节表示,比如9999 9999这个数就分成两个9999来表示,这两个字节又分高低字节,每个字节表示2位数,也就是十进制的99,因为这个高字节最大是127,不能用BCD码表示,所以仍按二进制来表达,最大是99,正好在127之内,这两个字节传到下位机后再转成BCD码送显示器显示,这样就成功的解决了数据格式的表示问题,而且最大的数字仍要大于8位数(因为有6个字节可用,8位数只用掉了4个字节).
安排好上位机的数据格式问题,下位机只要按协议还原出来就可以了.
5.显示/按键模块的程序
刚测试店家提供的模块的时候,怎么都不能让模块点亮,怀疑是模块有问题,后来让店家传了一个他自己做的一个示例hex文件,写入M8后,模块能正常点亮,说明模块本身没问题,问题出在编译器上,挣扎了一个晚上,终于发现是makefile写的有问题.
6.系统总成
最终上位机传送12项计数值,最小刷新时间是10秒,并同时每隔一秒传送一个有别于12个项目编号的值,如果下位机在若干秒内没有接收到这个数值,则显示某些符号,以告知用户,USB通讯已中断,显示模块的8个数码管用于显示8位计数值,如果数字少于8位,前导的0自动消隐,LED指示灯有8个,前4个分别只是今日/昨日/每日/累计,后3个用于只是PV/访客/IP,用3个按键操作项目的显示,头两个向前向后选择显示项目,后一个按键切换到自动轮换显示.上位机方面,原本是用管理员帐号实现模拟登录的,后来考虑到安全问题,改用查看密码登录,这样只要填写计数器的ID和查看密码就可登录了,所幸最终的数据页面跟管理员的是一样的,程序不用大的改动.
7.遇到的问题:
(1)USB识别问题,在家里的电脑上都能成功的识别出硬件插入的,但是拿到单位的电脑上却发现3台电脑8个USB端口只有一个能识别出硬件插入,并成功安装驱动,也问过其他人,但是没有答案,后来拿万用表测data-的引脚电压,发现只有3.1v,而相同电路相同参数的USBASP有3.4,照理说3.0~3.6之间都能应该能识别的,可实际上就是不行,最后还是更换了这两个稳压管,更换后的电压是3.3v,果然能识别出来了.
(2)然后是V-USB的PC驱动问题,因为这类USB设备(包括USBASP)都是用的免费的PID/VID,就是说都是相同的,这样不同的设备插入后都会被识别为同一个硬件,后来用了LIBUSBDOTNET自动的inf向导工具,生成了自己的驱动安装文件,这样插入硬件后就能显示出不同的硬件名称了.
(3)LIBUSBDOTNET的动态库问题,USBASP和我的计数器显示器都显示为liabusb_win32类型的设备,用到的硬件驱动都是libusb0,奇怪的是插入任意一个硬件,用这个库做的程序都不能正常的识别出来,不管是官方的还是我自己做的,尽管在设备管理器上显示都正常的设备.但是USBASP的客户端软件avr_fighter却能正常识别出usbasp,但是它用的是BCB编程的,没有用到libusbdotnet的库,估计是两个硬件的驱动程序都相同的问题(即libusb0不能同时支持两个USB设备),后来是修改了我自己的PID/VID,并且将驱动安装到自己定义的一个目录上(默认是windows\system32)但是还是没有解决根本问题,设备插入后程序还是不能打开它,有时要两个设备同时插入,程序才能识别出来,重启后又不能识别了,到目前为止这个问题还是不能很好的解决,不过在只有一个V-USB设备的电脑上还是很正常的.
又:写完上面的文字后,又去libusbdotnet官网上逛了一下,都是E文,基本看不懂,凑合用google网页翻译看了一下,发现确也有人提出同时插入两个相同v-usb设备时,不能被枚举的问题,又有人提到他用修改libusb0.sys,libusb0.dll为自己的名字后,再安装驱动的事情,给了我一点点灵感,那么改哪个的驱动呢?是usbasp的还是我的网页计数显示器?因为计数器可能要拿到外面去用的,而usbasp是自己用的设备,在自己的电脑上才有可能同时插入两个设备,因此先彻底删除了原来的USBASP驱动程序,然后修改.inf文件,将所有涉及到libusb0.sys,libusb0.dll,及安装的服务名都改成自定义的名称,当然也要改一下驱动文件的名称,然后再安装,果然现在我的上位机能完全正常的识别出我的小设备了,当然了,两个设备还是不能同时插入,不过这个已经不是大问题了,毕竟usbasp只是个写入设备,写入完成后即可拔出的.
附靓照一张,至于要不要给它按一个漂亮点的外壳,看心情而定~~~