User Tools

Site Tools


zh:usb_express:ya-vusb

一个基于固件的软USB设备

  • 这个项目已经上传到Github,并且
  • 你也可以从本站的下载页上取得源码包和参考资料。

我们能在一个很简单的没有任何USB接口的MCU上实现一个USB设备吗?在这个项目中我会给你一个肯定的答案。事实上多年以前就有一个名为PowerSwitch的项目发布在互联网上。其作者用了一个AVR芯片制作了一个USB设备。这几天我创建了一个类似的项目,在另一种MCU上实现了另一种USB设备。我选择的MCU是Microchip公司设计制造的PIC24/dsPIC33芯片,并在其上实现了一个自定义的HID设备。项目包括了电路原理图和完整源代码。


背景

大约在2006年,我在互联网上发现了一个神奇的项目:PowerSwitch。这是一个通过USB接口控制的八通道电子开关,但它的主控MCU(ATMEL 90S2313)本身并没有USB硬件接口,设计者使用两个GPIO口线连接USB HOST的D+/D-线,通过固件捕获D+/D-上的信号并进行解码,最终实现了一个USB低速设备。

AT90S2313芯片的高速度是这个项目成功的关键,使用12MHz的石英晶体驱动时它具有12MIPS的高性能,每8个指令周期刚好对应了USB低速通信比特流(1.5Mbps)中的一个比特。所以固件可以准确地采样每一个比特,并且在采样间隔期间对比特流进行处理。由于12MIPS仍然不够快,所以固件只做了NRZI解码和BIT UNSTUFF,没有进行CRC校验。在发送数据时,固件会事先计算出CRC16校验码并附加在数据之后,且在数据和CRC码准备好之前一直向主机返回NAK。

非常精巧的设计,ATMEL公司有一篇应用笔记(AVR309)记录了这一技术的要点。不过这个项目的核心代码使用了AVR汇编语言,而我当时并不熟悉AT90系列芯片,所以我没有对此项目进行深入研究。到目前为止PowerSwitch演变为一个独立项目V-USB,移植到ATMEGA系列芯片上并派生出一些实用产品,比如USBAsp,但从未在非ATMEL的芯片上实现过。1)

几年前我曾经设计过一些基于USB接口的产品,由于我使用的芯片都有USB硬件接口,所以我对USB的底层协议不是特别关注。这一次我准备仿照“V-USB”重新实现一个基于固件的软USB设备,目的是更深入地学习USB底层协议。我选择了Microchip公司的PIC24F/dsPIC33系列芯片,使用汇编语言和C语言完成编码,如果你不是Microchip系列MCU产品的专家,这个项目可能会带给你相当大的困难。又或者说,带给你一个深入研究Microchip系列产品的绝好途径。


电路原理图

我选择了dsPIC33FJ12MC201这个芯片,该芯片具有20引脚的SSOP小封装,提供最大40MIPS的高性能。我使用8MHz石英晶体X1并通过PLL为CPU核提供30MHz(FPLLOUT=30MHz)主时钟,这样CPU具有15MIPS的运行速度,每10个机器周期对应了1.5Mbps比特流中的1个比特。我使用了GPIO A中的RA0/RA1口线连接USB D+/D-,由于RA0/RA1可直接触发“Change Notification”中断,因此不必额外再使用INT0口线。注意USB D+/D-必须连接到同一GPIO的两根口线上,将D+连接到RAx上而将D-连接到RBx上是不允许的。USB D-上的1.5K上拉电阻R7并未直连到3.3V,而是由GPIO B中的RB4控制。RB15口线上连接了一个LED,主机端的测试程序将通过USB口发送一些数据控制这个LED的状态。MCLR/PGC1/PGD1用做ICSP接口J2,可以使用诸如PICKit3之类的编程器烧写固件代码。RB7口线连接到ICSP接口J2的第6脚上,用于在必要时通过UART发送一些调试信息。注意ICSP接口J2的第6脚应该是对应PICKit3的LVP信号,我没用过PICKit3,不知道将这个引脚连接到RB7上是否会影响PICKit3。我用了一个NPN晶体管T1产生3.3V电源,在我的实验板上则是一颗AMS1117-33,你也可以尝试在VBUS之后串接一个红色LED代替晶体管T1,将+5V电压降至3.2V。芯片第6脚到GND之间可以直接焊一个锡桥代替R4,不必真正焊一个0欧电阻。我增加R4目的是允许把dsPIC33FJ12MC201更换成PIC24F16KA101,这只需要断开第6脚到GND的连接并去掉第14脚上的10μ电容C3,同时把石英晶体换成30MHz即可。

源代码中没有直接定义熔丝位,所以在烧写代码时需手工设置“FCKSM1/POSCMD1/FWDTEN/JTAGEN”为“编程”状态,也就是0,其它位都设为“未编程”状态即可。若使用PIC24F16KA101芯片外接30MHz晶体,则需设置“FNOSC2/FNOSC0/POSCMD0/FWDTEN”。

left:dsPIC33 right:PIC24F


软件编程环境

这个项目在WINDOWS平台上开发并测试。我使用了Microchip的XC16编译套件,版本号1.25。除此外我没有使用集成开发环境(MPLAB IDE)和调试器(如ICD 4等),也没有购买任何商用许可证。没有商用许可证意味着其GCC编译器仅能支持-O1级别的优化,这基本够用了。你可以点击下面的图标下载XC16编译器,更高版本的应该也能正常使用。

我直接使用一个批处理文件(usb.bat)对源码进行编译:

xc16-gcc -mcpu=33FJ12MC201 -O1 main.c hid.c usb.c sie.s dbg.s -o main.elf -T p33FJ12MC201.gld -Wl,--defsym,__has_user_init=1,-Map=main.map
xc16-bin2hex main.elf
xc16-objdump -D main.elf >main.txt
pause

我给连接器传了一个参数“-Wl,--defsym,__has_user_init=1”,这样在源码文件中sie.s的“__user_init”函数就会被C启动代码调用,在XC16编译器安装目录中找一下名为“libpic30.zip”的库源码包,其中的“crt0_standard.s”文件引用了“__has_user_init”这个符号。

运行于主机端的测试例程是基于Visual Studio 2008开发的。我有一个自制的固件代码烧写器代替PICKit3,有关这个烧写器的详情我会另外撰文。


源代码的结构

这个项目包括5个源码文件,其中两个是汇编语言写的(sie.s/dbg.s)。dbg.s是用于调试的,通过UART输出一些信息以及通过LED显示一些状态,这个源码完全可以省略掉。其余三个是C语言源码,usb.c用于处理部分USB标准请求,如“取描述符”等。hid.c用于处理HID协议的“Set/Get Report”请求。main.c则包括程序的初始化代码和主任务循环。需要注意的是C语言的入口点“main函数”定义在sie.s之中,main.c中只有setup函数和loop函数,这很象arduino的源码。

sie.s是这个项目的核心代码,它包括了“Change Notification”中断服务程序,USB底层的所有处理都在这个中断服务程序中进行。它还包括了存储通信数据的缓冲区和几个用于记录状态的全局变量,以及一组供usb.c调用的API函数,这个API层可以避免usb.c直接访问sie.s定义的变量。


源代码详解

我不打算把源代码的完整说明都放上来,因为文字太长了。你可以从本站的下载页上取得源码包,包中有PDF文件记录了本项目源码的所有关键信息。

1)
最近在Github上发现一个基于STM32F030同类项目
zh/usb_express/ya-vusb.txt · Last modified: 2020/09/19 08:13 (external edit)