应用笔记 3936

Maxim USB库


摘要 : Maxim USB库是使用ARM7™处理器并基于MAX3421E/MAX3420E提供的软件例程。本应用笔记对系统及其软件运行进行了说明,在同一ARM® C语言代码中同时实现了USB主机和外设功能。这种方法使得USB外围设备和嵌入式主机的开发和研究,具有在USB电缆的另一端带有参考设备的优势,而所有的这些都包含在同一的C语言代码中。

本应用笔记是应用笔记3997,"建立Maxim USB库"。 可下载包含Maxim演示程序的Keil™项目。

绪论

Maxim USB库是两块电路板和C语言程序的组合。利用这个系统,可以:
  • 研究、操作、测试和修改基于ARM7微控制器(µC)与MAX3420E USB外设控制器连接的USB功能设备
  • 研究、操作、测试和修改基于ARM7微控制器(µC)与MAX3421E USB主机控制器连接的USB功能主机图1显示了主机从USB存储棒中的数据检索。
  • 使用USB电缆将主机连接至外设,并同时运行主机和外设。开发USB主机或USB外设代码时,USB电缆的另一端有一个参考设计是非常有用的。这样,USB电缆的两端,主机和外设因为都是执行相同的C语言代码,从而具有可控性和定制性。
图1. MAX3421主机/ARM从任意的USB外设重新得到枚举数据,并通过串口连接至运行有终端仿真程序的PC来报告结果。
图1. MAX3421主机/ARM从任意的USB外设重新得到枚举数据,并通过串口连接至运行有终端仿真程序的PC来报告结果。

其它资料

编程指南、数据资料和Maxim网站提供的MAX3420E和MAX3421E应用笔记:

MAX3420E
MAX3421E
Maxim应用笔记AN3937,"建立Maxim USB库"

硬件

本应用C语言代码运行在两板装置上:
图2. Maxim评估板插进Keil MCB2130板。
图2. Maxim评估板插进Keil MCB2130板。

图2展示了板级设备。蓝色电路板是Keil MCB2130,其中包含一块ARM7芯片,Philips® LPC2138。这个微控制器包含2个SPI™硬件单元,连接至两块Maxim USB控制芯片。

竖立的电路板是MAX3421EEVKIT-1。一个MAX3420E外设控制器连接至ARM的一个SPI端口,并布线连接至USB的“B”连接器(J5),在图2中用“3420 P”标记(P代表外设)。MAX3421E主机/外设控制器连接至ARM的另外一个SPI端口,并布线连接至“3421P” (J2)和“3421H” (J1)连接器(H代表主机)。应用中运行的代码将MAX3421E作为主机使用,因此评估套间电路板中部的USB连接器(J2)没有使用。

主机软件使用两个串口中的一个(MCB2130电路板上的P1)将USB说明信息发送到运行终端仿真程序的PC。终端程序,如Tera终端程序,可以仿真终端(VT100),识别由程序发送的专用“转移码”序列以实现清屏和指针复位。终端程序的串口设置为38400、N、8、1,无流量控制。

图2中的米色方盒和带状电缆是Keil ULINK JTAG装载-调试器。Keil µVision®3开发环境支持该调试单元。MCB2130附带一个评估版µVision3,具有完整功能版本的Keil工具集。但评估版在代码容量上有16kB的限制。

本应用笔记的附件包含整个Keil工程的源文件,另还有.hex格式的装载模块。如有ULINK JTAG单元,可编译该代码,并可通过JTAG端口进行加载和调试。这对于快速开发USB、修改工作主机和/或外设代码使其适合开发目的是一条极好的途径。如果没有ULINK,可使用称为Flash Magic的免费程序加载、运行hex文件,这个程序可从网站www.esacademy.com获得。参考Maxim应用笔记3937可获得如何配置和使用该程序的信息。

图3. MAX3421EEVKIT-1电路板框图。带阴影的椭圆形是主要软件模块。
图3. MAX3421EEVKIT-1电路板框图。带阴影的椭圆形是主要软件模块。

程序功能

图3显示了MAX3421EEVKIT-1电路框图和本应用笔记的软件模块。MAX3420E和MAX3421E都使用SPI总线访问其寄存器组。LPC2138含有2个硬件SPI单元:
  • 一个专用SPI端口(SPI)
  • 一个通用串行接口(SSP,同步串行端口)
可对SSP编程以实现第二个SPI端口。这些端口的编程不同。例如,SSP有8个发送、接收FIFO,而SPI只有1个读缓存器。为与MAX3420E和MAX3421E兼容,两个SPI接口都编程工作在8位数据模式下。软件模块SPI_FNs.C包含两个USB控制器都使用的寄存器访问函数。

MAX3420E属于SPI单元,所以对这块芯片来说函数有一个“P” (例如Prreg和Preg)前缀,表明外设操作。MAX3421E属于SSP单元,所以其访问函数有一个“H”前缀(例如Hwreg),表明为主机操作。

外设程序

C模块3420_HIDKB.C实现一个通过使用评估电路的连接器J5连接至PC机的USB外设。MAX3420E的INT输出引脚连接至ARM7的EINT0 (外部中断0)引脚。这个中断可在由MAX3420E实现的USB外设需要服务时随时触发。

本应用列举和实现了标准的USD HID (人性化接口设备)。符合标准Windows®平台的一大优势是设备驱动内置在Windows中,从而不需要安装专用的驱动。Windows将由本应用实现的设备识别为一个标准键盘。按任意的连接至MAX3420E GP-IN引脚的四个按钮,将使“键盘”在任意打开的接收文本窗中输入文本字符串,如记事本和写字板。


注意:本应用可在任何可接收文本应用中输入文本,如电子邮件、汇编编辑器、或Word文档。在按键之前请确认有一个安全的应用,如记事本被打开,并处于有效状态。作者根据经验可以确定:在一个打开的C源文件中输入无效文本将不能进行完全编译。


主机代码

3241_Host_EVK.C文件包含主机代码,其使MAX3421E完成一定的枚举步骤,该枚举步骤与有USB设备插入连接器J4时PC所执行的枚举步骤相似。这个模块中的main()函数就像HID键盘代码的中断服务程序。EINT0中断服务程序简单如下:
                        // EINT0 Interrupt handler--MAX3420E INT pin
void INT3420 (void) __irq
{
service_irqs();         // Do the USB thing in 3420_HIDKB_INT_EVK.C module
EXTINT      = 1;        // Clear EINT0 interrupt flag (b0)
VICVectAddr = 0;        // Dummy write to indicate end of interrupt service
}
基础程序main( )包含以下无限循环子程序:

while(1)
  {
  detect_device();
  waitframes(200);      // Some devices require this
  enumerate_device();
  wait_for_disconnect();
  }
Enumerate_device()函数完成绝大部分的工作,向连接设备发送USB请求,并通过串口报告结果。任意的USB设备插入评估板的连接器J1,这个函数将向设备发送枚举请求,并通过串口报告结果(图1)。

同步主机/外设操作

因为MAX3421EEVKIT-1电路板包含一个USB外设和一个USB主机,且它们使用ARM7上单独的SPI接口,所以软件将可以很容易的编写,使主机和外设应用同时的运行。主机应用运行main()在后台与MAX3421E对话,而HID外设代码使用MAX3420E中断激活service_irqs()函数。

如果用USB电缆连接评估板的连接器J5和J1,ARM处理器可作为USB主机与外设对话,而这个外设就是其自身。图4显示了3421_Host.C的主机代码询问由3420_HIDKB.C实现外设的报告。

图4. 通过USB询问自身的代码演示
图4. 通过USB询问自身的代码演示

主机代码开发

通读enumerate_device()函数有助于理解USB主机是如何工作的,也有助于理解如何命令MAX3421E完成枚举USB设备时的各种主机操作。而开发主机代码,如果对外设插入主机的响应过程不清楚,有时将很难诊断问题。如果在全部的控制中(3420_HIDKB.C模块)拥有参考外设,将使问题很容易发现,并可改变设备的响应以测试主机纫件。

外设代码开发

主机是用于开发USB外设代码的优秀询问工具。MAX3421E和3421_Host.C模块的结合,产生一个简单但功能强大的USB信息包生成器和分析器。通过编写C语言主机代码,可以控制外设状态;MAX3421E显示外设的响应过程。

实例

假设编写外设代码以处理USB字符串。在hidkb_enum_tables.h文件中按如下方式编写生产商字符串XYZ Widget Company:
// STRING descriptor 1--Manufacturer ID
{
18,             // bLength
0x03,           // bDescriptorType = string
'X','Y','Z',' ','W','i','d','g','e','t',' ','C','o','m','p','a','n','y'
},
编译该文件,将其装入ARM的flash存储器,并运行。主机程序将报告如下生产商字符串:

图5. 不良结果,例程没有按照要求显示生产商名字,代码有问题。
图5. 不良结果,例程没有按照要求显示生产商名字,代码有问题。

出现了什么问题? 原来,USB规定文本字符串用Unicode表示,即每个字符用两个字节表示。回顾外设的源代码,更改如下:
// STRING descriptor 1--Manufacturer ID
        {
        35                      // bLength
        0x03,                   // bDescriptorType = string
        'X',0,'Y',0,'Z',0,' ',0,'W',0,'i',0,'d',0,'g',0,'e',0,'t',0,
        ' ',0,'C',0,'o',0,'m',0,'p',0,'a',0,'n',0,'y' // Unicode!
        },
编译、运行,应该可以看到:

图6. 正常的代码例程,使用Unicode格式,可看到结果。
图6. 正常的代码例程,使用Unicode格式,可看到结果。

该结果有所改善,但并不完美。Company中的“y”到哪去了? 仔细计数该字符串有35个字符,并将其作为第一个字节输入以显示字符串长度。设计没有注意的是:长度字节也是字符串,必须包含前两个字节。代码最终的修改如下:
// STRING descriptor 1--Manufacturer ID
        {
        37               // bLength
        0x03,                   // bDescriptorType = string
        'X',0,'Y',0,'Z',0,' ',0,'W',0,'i',0,'d',0,'g',0,'e',0,'t',0,
        ' ',0,'C',0,'o',0,'m',0,'p',0,'a',0,'n',0,'y' // Unicode!
        },
这是结果应为如下所示:

图7. 最佳的代码实例。代码现在可实现设计想要实现的功能。
图7. 最佳的代码实例。代码现在可实现设计想要实现的功能。

通过测试外设代码和使用主机控制器及编码运行/分析,由于报告的结果是由MAX3420E发送至MAX3421E的实际USB报文,从而可以看到与PC机响应完全相同的结果。最好的是,这些都可在一个可控的基础上实现,因而不必担心PC机的如何响应而使代码开发出现错误。

代码说明

图8. Keil工程的结构
图8. Keil工程的结构

图8显示了三个主要模块,并展开显示了它们的文件依靠。没有展开的模块是Keli开发环境所需要的内部处理文件。

SPI_FNs.C

这个模块包含由其它两个模块调用的公用函数。

void init_PLL(void)

此函数设定LPC2138的PLL和时钟除法器。附着于LPC2138 (FOSC)的晶体为12.000MHz。根据公式CCLK = FOSC × M,设定CPU频率为48MHz,其中M = 4。LPC2138使用内部CCO (电流控制振荡器)倍频FOSC以获得更高的内部频率。FCCO = CCLK × 2 × P。FCCO必须处于156MHz和320MHz之间。对于P = 2,FCCO = 48MHz × 2 × 2 = 192MHz。


注意:Keil文件startup.s也包含设置PLL参数的启动代码。init_PLL()函数将有效的取代这些设置。


重要警告:修改这些设定会有一定的风险。错误的设定能迫使LPC2138超出其规定的限度,从而使Keil调试器停止作用。如果这种情况发生,补救方法是使用Flash Magic公用软件擦除LPC2138的flash存储器(使用第二个串口,连接至Keil电路板的P2),修改代码中的PLL设定,然后再试验。

init_IO

这个函数初始化LPC2138的I/O引脚和SPI单元(SPI和SSP):
  • SPI由CCLK/8时钟控制,允许最大值。SCLK为48MHz/8 = 6MHz。
  • SSP由CCLK/(2 × VBDIV)时钟控制。VBDIV设置为1,使MAX3421E的SCK信号为48MHz/2 = 24MHz。MAX3421E的SCK输入最高可运行至26MHz。
关于SPI接口
SPI接口使用如下信号:
  • MOSI 主机输出数据,从机输入数据
  • MISO 主机输入数据,从机输出数据
  • SCLK 串口时钟,由LPC2138提供
  • SS# 从机选择,由LPC2138驱动
LPC2138的SPI硬件控制前三个信号,但SS#使用一个GP-OUT引脚(P0.7)直接设置和清除。

关于SSP接口
虽然SSP有一个硬件SS#引脚,但使用GP-OUT引脚作SS# (P0.20)更简单。

Hwreg
Hwritebytes

这些函数使用LPC2138的SSP硬件向MAX3421E写入寄存器和FIFO数据。H前缀表示主机操作。Hwreg写入一个信号寄存器值;Hwritebites向MAX3421E的FIFO写入多个字节。

Hrreg
Hreadbytes

这些函数使用LPC2138的SSP硬件从MAX3421E中读出寄存器和FIFO数据。

SSP接口
SSP硬件需要译码,从而在发送和接收通道上需要数据FIFO。在一个SPI操作中,每8位数据移总是伴随着8位数据移。LPC2138使用如下步骤访问MAX3421E的SPI接口:
  1. 触发SS# (低)
  2. 发送由登记号和方向位构成的命令字节
  3. 发送/接收一个或多个数据字节
  4. 释放SS# (高)
SPI写操作简单明了—每个数据都会移出,输入FIFO内的数据可以被忽略。但读操作更加复杂,因为读FIFO至少包含一个坏字节,即步骤2中自动由时钟控制记入的。由于以前的多字节写操作也可以使输入数据失时效。输入FIFO不能由硬件禁止或清除。因此,读SPI数据的代码首先要手动清除FIFO,即从SSPDR中读出字节,直至表示“读FIFO不为空”的标志位被释放。

Pwreg
PwregAS
Pwritebytes

这些函数使用LPC2138的SPI硬件向MAX3420E写入寄存器和FIFO数据。第二个函数与第一个函数一样写入寄存器值,但其还设置SPI命令字节中的ACKSTAT位。这是停止USB控制转移的捷径。细节请参考应用笔记3598,"MAX3420E编程指南"

Prreg
PrregAS
Preadbytes

这些函数使用LPC2138的SPI硬件从MAX3420E中读出寄存器和FIFO数据。第二个函数与第一个函数一样读出寄存器值,但还设置SPI命令中的ACKSTAT位。

readout

readout这个函数更新连接至MAX3421E通用输出引脚GPO[6:0]的7段信息显示,函数保留GPO[7]的设置,GPO[7]控制USB “A”连接器(评估板的J1)的VBUS开关。

3421_Host.C

这个模块实现MAX3421E USB主机,该主机访问USB设备并通过MCB22310的串口报告枚举数据。作为参考,附录A给出了主机枚举由3420_HIDKB.C实现的内置设备的LeCroy (CATC)总线过程。

这个模块中有三种函数类型:
  1. 初始化函数
  2. 公用函数
  3. main()中调用的高级函数
下面的说明将按此顺序。

Reset_Host

MAX3421E包含上电复位,因此这个操作并不是必须的。尽管如此,程序开发阶段使用该字段,不需要重复上电即可初始化调试过程,是复位功能的很好方式。启动该函数使器件清零,不会带有以前的调试信息。

复位MAX3421E使片上振荡器停止工作。解除复位后,这个函数在返回之前将等待OSCOKIRQ位(振荡器正常中断请求)变为有效。

initialize_3420

这个函数在3420_HIDKB.C模块中。

initialize_ARM_Interrupts

这个函数建立ARM向量中断如下:
  1. EINT0连接至MAX3420E的INT引脚,使用优先级0 (最高)。
  2. Timer0用于功能发光体闪烁,使用优先级1。
  3. EINT2连接至MAX3421E的INT引脚,指定优先级为2,在这个程序中没有使用到该中断。当然,这个中断是为了方便才提供的。
  4. Timer1用于检测发送/停止按钮(在3420_HIDKB.C中使用),使用优先级3。

service_irqs

该函数在3420_HIDKB.C模块中。它进行HID键盘仿真。初始化ARM中断的代码仅需将该地址置为一个中断向量。

waitframes

用于USB主机测量时间的一个简单方法是计算1ms帧标志的个数。当微控制器设定寄存器位SOFKANAB = 1时,MAX3421E自动产生这些由全速模式的SOF数据包或低速模式的“保持激活”脉冲组成的帧标志,随后FRAMIRQ位每毫秒触发一次。

USB 规定要求确定的长延迟时间,例如复位一个设备后给它一个“复位恢复时间”。调用waitframes函数将是实现相对长的延迟时间的一个简便方法。

detect_device

这个函数在检测到一个USB设备插入USB-A连接器J1后返回。

wait_for_disconnect

该函数在检测到一个插入USB-A连接器的设备断开后返回。

Send_Packet

所有发送USB数据包的函数都调用这个函数。图9显示了IN事务处理的总线过程和实现它的C程序。

图9. 启动USB总线动作,由MAX3421E调用C程序函数
图9. 启动USB总线动作,由MAX3421E调用C程序函数

  1. 微控制器在FNADDR寄存器中设定函数地址。因为在微控制器重新载入FNADDR寄存器之前,装载值一直保持不变,所以每个数据包不需重复这个步骤。
  2. 微控制器通过写入HXFR寄存器开始传输数据,指明USB令牌和终端。然后等待HXFRDNIRQ (主机传输完成)触发IRQ,发送信号完成。
  3. 微控制器读HRSL (主机计算结果)寄存器来决定事务处理的结果。16个计算结果代码表示下列结果:
    A.成功(ACK)
    B.设备占用(NAK)
    C.非正常握手(STALL)
    D.设备故障,如超时、串音或总线错误
  4. 微控制器读计数寄存器字节,然后卸载RCVFIFO并将数据存入一个字节数组中。


这个函数处理NAK重试,连续重新启动数据传输直到:
  1. 收到ACK握手,或
  2. 超过常数NAK_LIMIT设定的预设限制
如果设备无反应,函数重试传输,直至受到RETRY_LIMIT限制。

CTL_Write_ND

这个函数以无数据模式执行控制-写操作(_ND = no data)。这个函数仅在主机Set_Address请求时调用。它可以实现Sent_Packet的两个调用:第一个用于SETUP数据包,第二用于IN-Handshake数据包。

IN_Transfer

这个函数尽可能多地调用Send_Packet (指定的IN),以重新获得USB的数据记录。MAX3420E和MAX3421E终端FIFOS为64字节长,如此长的字符描述,举例来说,需要多个IN请求数据包组成一个大数组(XfrData[2000])。

CTL_Read

这个函数处理3态CONTROL-Read USB事务,调用三个函数:
  1. 调用Send_Packet发送SETUP数据包
  2. 调用IN_Transfer使数据进入XfrDaTa[]中
  3. 调用Send_Packet发送OUT-Handshake数据包

enumerate_device

这个函数执行附录A所示的10个数据传输。因为它调用以前的函数来实现低级USB操作,所以通过研究这个函数可很容易理解枚举步骤。

print_error

这个函数检查4位HRSL值(图9中的第3项)以打印输出主机的传输结果。如果通过数据为0,不打印输出。这个函数返回HRSL值,因此调用这个函数可以很容易地检测错误,例如:
if(print_error(HR)) return;

3420_HIDKB.C

这个模块使用MAX3420E实现一个USB外设,符合标准HID类,可以象标准键盘输入一样自动输入文本。入口点是service_irqs函数,该函数在响应LPC2138的EINT1引脚时作为中断服务程序调用。ENT1引脚连接至MAX3420E的INT输出引脚。

Reset_Peripheral

这个函数以与Reset_Host相同的方式复位MAX3420E。

initialize_3420

这个函数完成三件事:
  1. 配置MAX3420E SPI接口以实现全双工操作(分离MOSI和MISO引脚)
  2. 配置MAX3420E INT引脚为上升沿有效
  3. 通过应用程序使能MAX3420E中断

service_irqs

这个函数直接处理总线复位和设备断开。这个函数也调用do_SETUPC处理枚举和do_IN3处理键盘输入。


注意:为精简代码,本应用不处理USB的挂起-恢复任务。实现该功能的例子可参考Maxim应用笔记3690,"MAX3420E的USB杖举程序(及其他)"。AN3690中的枚举代码作为3420_HIDKB.C的基础。

do_SETUP

这个函数通过检查8字节SETUP数据包中的各个字节处理设备枚举,然后调用函数以处理特定的请求。代码仅处理USB标准请求类,因为本应用程序中没有实现USB分类或客户请求。

do_IN3

这个函数根据HIDKB_enum_tables.h中的Message[]字符数组发出按键信息。每字符三个字节的格式定义在与HID Report Descriptor相同的文件中,如图10所示。

图10. Report Descriptor详述了一个键盘按键的3字节数据格式
图10. Report Descriptor详述了一个键盘按键的3字节数据格式

最终考虑和免责声明

插入J1的USB设备可能是十亿个左右可用设备的任意一个,因此不必说,将有各种各样的响应满足主机的枚举请求。任意一个通过兼容性测试的设备(有USB标识显示证明)都将在枚举时显示无误。本程序中的故障检测由每次主机事务处理后对HRSL值的检查完成。因为许多故障条件难以模拟,所以故障检查没有进行广泛的测试。

附录A

3240E_HIDKB.C枚举的USB总线跟踪过程

附录A
更详细的图
(PDF, 234kB)