应用笔记3637

为任何系统增加USB


摘要:MAX3420E利用通用的微控制器,简化了USB外设的设计。本文在介绍MAX3420E的基础上,重点讨论器件的SPI接口。说明器件如何与硬件SPI口或位仿真的通用I/O口进行连接,并给出了利用MAXQ2000微控制器进行USB设计的C程序范例。

引言

有关通用串行总线(USB)的文章通常从USB是个人电脑的一个新连接标准开始讲起。谢天谢地现在不再需要如此做,因此本引言可以简短地写为:如果你有一个嵌入式系统并且想连到PC,主流的连接通道是USB。

本文介绍了一款Maxim Integrated最新推出的芯片,MAX3420E,它可以很容易地把USB加入到任何系统中。本文主要着重于SPI接口,提供了实现通用SPI的C例程。最后给出了一个简单的USB HID (人机接口设备)—基于Windows的应急按钮程序。

为任何系统增加USB

微控制器(µC)的选择通常基于它所集成的外设。许多处理器集成了USB功能,但是大多数处理器,特别是一些低价位的处理器不含USB。有时,您可能选用了一个I/O和外设都很完美的微控制器,但它却缺少USB。您是否希望只是添加USB功能而继续使用当前的微控制器呢?

利用Maxim的芯片MAX3420E,可以为任何处理器添加USB功能。MAX3420E集成了USB全速收发器、智能USB串行接口引擎(SIE)和一个可工作到26MHz时钟的从SPI接口。MAX3420E的使用如同一个具有单个控制端点、两个双重缓冲的64字节数据端点和一个64字节的中断端点的全速USB外设。

总线供电

图1. USB总线供电
图1. USB总线供电

图1是一个普通的USB外设结构。USB的VBUS电源线为3.3V稳压器提供5V输入,该稳压器给微控制器和MAX3420E供电,无需外部电源。SPI接口可以是3、4或5线。表1列出了5线接口。

表1. 使用3到5线的SPI接口
Signal MAX3420E Direction Description
MOSI In SPI master out, slave in
MISO Out SPI master in, slave out
SCLK In Serial clock
SS# In Slave select
INT Out Interrupt (level or pulse)

如果应用中不需要中断(MAX3420E的中断条件可以通过读取寄存器直接检测到),可以去掉INT引脚,得到一个4线接口。如果SPI主机具有双向数据接口(MOSI/MISO在同一个双向引脚上),则可以省掉另外一条接口线,这样,没有中断、支持双向通信的SPI接口只需要3个引脚。

如果微控制器没有SPI接口怎么办呢? 没问题,可以很容易地设计一个直接触发GPIO的、固件形式的SPI主控器。USB的一个非常强的特性是自控流量能力,它自动配合SPI侧的任何速度(它利用在USB侧插入NAK握手来提示“忙,重试”)。很多USB外设,特别是与人接口的,即使与最低速的SPI接口都能应答自如。

如果图1中的微控制器非常小,只有10脚以下怎么办呢? 难道就不能把这些珍贵的I/O口用来连USB芯片了? 不,这就是为什么MAX3420E提供了四个通用输出口和四个通用输入口的原因,它们可以替代被用掉的处理器上的I/O口,事实上你的系统在接上MAX3420E后还得到了更多的口线。

大规模集成芯片

图2. 只连接大规模集成芯片的少许引脚
图2. 只连接大规模集成芯片的少许引脚

MAX3420E不仅仅只限用于小系统。图2说明了如何给一个ASIC、FPGA、DSP和其它大芯片增加USB功能。这样做的一个明显原因是大芯片没有内建的USB或内部的USB不是正好符合你的所需。另一个原因是随着工艺尺寸的缩小,这些大芯片不能兼容USB所需的3.3V “高”压,此时使用外部带低压SPI接口的USB芯片就是一个很好的方案。MAX3420E内带电平转换器,VL脚设定接口电平的范围,从1.7V到3.6V。

隔离USB

图3. 隔离USB
图3. 隔离USB

图3所示,由于SPI接口信号是单向的,还很容易进行光隔。同时还可以设定较低的速率来支持廉价的光耦。

SPI接口

SPI是一个简单的串行接口,它使用两根数据线,一根串行时钟和一个片选信号。SPI主控把SS#拉低来开始传输,然后驱动串行时钟SCLK来把数据同步输入和输出从设备。SPI主控通过把SS#拉回到高电平,以终止传输。

SPI接口有四种时钟模式,反映了两个信号CPOL (时钟极性)和CPHA (时钟相位)的两个状态。这两信号以(CPOL, CPHA)的形式来表示。可以证明一个接口利用正沿SCLK且在第一个正沿时钟到来以前MOSI数据已经准备就绪可以工作在(0,0)和(1,1)模式而无需任何改变。这一属性使MAX3420E无需额外的模式引脚设置,就可以工作在(0,0)或(1,1)模式。

图4图5显示了利用SPI模式在微控制器(MAXQ2000,随后介绍)和MAX3420E之间的数据传输。图4所示为(0,0)模式,图5所示为(1,1)模式。两者的区别是SCLK信号的无效电平不同,(0,0)是低无效,(1,1)是高无效。

图4. 工作在(0,0)模式的SPI接口
图4. 工作在(0,0)模式的SPI接口

图5. 工作在(1,1)模式的SPI接口
图5. 工作在(1,1)模式的SPI接口

MAX3420E每次传输接收的第一个字节是命令字节。命令字节包含寄存器号和方向位,第二个字节和后续字节包含数据。在图4和图5中,移入命令字时,来自MAX3420E (MISO引脚)的8位数据是USB状态位。此特性只在使用分离MISO和MOSI脚的接口中有效。

SPI代码

编写MAX3420E通用C代码的窍门是把原始的最基本的SPI操作封装到独立的模块中,然后针对不同的SPI接口的只需客户化这一模块。最基本的模块只须做三件事:
  1. 初始化SPI口
  2. 读一个字节
  3. 写一个字节
本文中的应用例子使用硬件SPI单元。对没有此单元的SPI主控器,我们先看看一些位仿真SPI接口的通用C代码。

位仿真SPI

Init SPI
初始化SPI口的函数会随处理器的不同而有很多变化。比较好的做法是先指定接口使用的I/O脚,设置它们的方向,然后设定SS = 1和SCLK = 0的初始条件(我们假定用SPI主控器的(0,0)模式)。

读寄存器,写寄存器
rreg是读取MAX3420E寄存器的C函数,这个宏把功能从不同微控制器的不同I/O结构中独立出来,使用宏使代码易读且与处理器无关。wreg是写MAX3420E寄存器的例程。

更换处理器时,只需对宏进行修改即可使用这些例程。例如:下面是用于不带硬件SPI单元的微控制器的宏。
#define SCLK_HI OUTA = PINSA | 0x02;
#define SCLK_LO OUTA = PINSA & 0xFD;
#define SS_HI   OUTA = PINSA | 0x04;
#define SS_LO   OUTA = PINSA & 0xFB;
#define MOSI(v) OUTA = (PINSA & 0x7F) | (v & 0x80);
#define MISO inval |= PINSA & 0x01;

BYTE rreg(BYTE r)     // Read a register, return its value.
{
int j;
BYTE bv,inval;
inval = 0;
SS_LO
bv = r<<3;                                        // Left-shift the reg number, WRITE=0
for (j=0; j<8; j++)          // send the register number and direction bit
        {
        MOSI(bv)                             // put out a bit
        bv <<= 1;                      // shift one bit left
        SCLK_HI
        SCLK_LO
        }
for (j=0; j<7; j++)          // get 7 bits and shift left into 'inval'
        {
        SCLK_HI
        MISO
        inval <<= 1;                      // shift in one bit
        SCLK_LO
        }
SCLK_HI                            // one more bit, but don't shift 'inval' this time
MISO
SCLK_LO
SS_HI
return inval;                     // return the byte we read in
}

void wreg(BYTE r,BYTE v) // register, value
{
int j;
BYTE bv;
SS_LO
bv = (r<<3)+2;                    // Left-shift the reg number, set the WRITE direction bit
for (j=0; j<8; j++)  // send the register number and direction bit
        {
        MOSI(bv)                                // put out a bit
        bv <<= 1;                         // shift one bit left
        SCLK_HI
        SCLK_LO
        }
for (j=0; j<8; j++)  // send the register data
        {
        MOSI(v)                                 // put out a bit
        v <<= 1;                          // shift one bit left
        SCLK_HI
        SCLK_LO
        }
SS_HI
}

硬件SPI

这一部分讨论上面提到的MAXQ2000微控制器,简单地说,MAXQ2000是低功耗、16位、高性能RISC处理器家族中的第一个。“Q”代表安静,表示这一结构能够与敏感的模拟电路很好地协调工作。MAXQ2000内建了一个SPI口,它与MAX3420E配合特别合适,下面的例子使用MAXQ2000开发板和MAX3420E搭建了一个简单、有趣的Windows小产品。

MAXQ2000硬件SPI单元提供了SCLK、MOSI和MISO,但是没有SS#。由于SS#的操作方式会变化(比如寻址一个字节与突发的字节串),最好用通用I/O脚做SS#。

MAXQ I/O单元

图6. MAXQ I/O单元
图6. MAXQ I/O单元

图6所示是一个基本的MAXQ I/O单元。I/O口以格式'port.bit'表示,'p'代表端口,'b'代表位。作为例子,我们主要讨论I/O口5,第3位(用P53)表示。

每一个I/O单元有一个触发器,本例中用一个称为PO5.3的位来写。'O'代表输出。你一直可以写这个触发器,它的输出有没有与引脚相连由方向位决定。配置输出脚时,实际应用时先写触发器再连到引脚比较好,这样它可以避免引脚上出现毛刺。

P53脚的方向由称作PD5.3的位来设定。'D'代表方向,D信号充当引脚驱动的输出使能:1 = 驱动,0 = 浮空。引脚的状态一直可以通过称作PI5.3的位读取,'I'代表输入,无论引脚是如何驱动的,被内部触发器(PD5.3 = 1)还是被外部的信号(PD5.3 = 0),PI位表示引脚状态。

这种结构的一个好处是如果引脚被配制成输入(PD5.3 = 0),触发器的输出没有被用作输出,那么它可以作为上拉电阻的开关重新利用。如果D = 0,0信号被重新定义,表示“连接一个上拉电阻”,如图6中的点状线所示。

许多I/O脚有中断功能,如图6下面的框图所示,中断模块有三个信号:
  • 一个中断标志位,中断请求有效时被置位,由CPU来复位。
  • 一个边沿选择位,决定是正信号跳变还是负信号跳变引起中断请求。
  • 对每一个能引起中断的引脚有一个中断使能位。
我们的应用例子把MAX3420E的中断输出配置成正边沿触发中断,在MAXQ2000这边,程序直接测试USB中断的中断触发器,而不是使用MAXQ2000的中断系统。程序除了检测按键的状态和响应USB请求外什么都不干,因此只需一个查询循环。

初始化SPI
MAXQ2000的I/O引脚由通用I/O和像SPI单元这样的特殊功能硬件共享。使用特殊功能硬件时,先配置硬件块,然后把它连到I/O脚上。程序清单中的SPI_Init()过程设置了引脚方向,配置了SPI接口,最后使能它。
void SPI_Init(void)
{
// MAXQ2000 SPI port
  CKCN = 0x00;              // system clock divisor is 1
  SS_HI                     // SS# high  
  PD5 |= 0x070;             // Set SPI output pins (SS, SCLK, DOUT) as output.
  PD5 &= ~0x080;            // Set SPI input pin (DIN) as input.
  SPICK = 0x00;             // fastest SPI clock--div by 2 
  SPICF = 0x00;             // mode(0,0), 8 bit data
  SPICN_bit.MSTM = 1;       // Set Q2000 as the master.
  SPICN_bit.SPIEN = 1;      // Enable SPI
// MAX3420E INT pin is tied to MAXQ2000 P60; make it an input
  PD6 &= ~0x01;             // PD6.0=0 (turn off output)
}
读寄存器,写寄存器
以下函数利用了MAXQ2000硬件SPI单元的优点,因此比起那些位仿真代码尺寸小而且快。
// Read a MAX3420E register, return its value.
BYTE rreg(BYTE reg)
{
BYTE dum;
  SS_LO
  SPIB = reg<<3;          // reg number w. dir=0 (IN)
  while(SPICN_bit.STBY);  // loop if data still being sent
  dum = SPIB;             // read and toss the input byte
  SPIB=0x00;              // data is don't care, we're clocking in MISO bits
  while(SPICN_bit.STBY);  // loop if data still being sent
  SS_HI
  return(SPIB);
}

// Write a MAX3420E register.
void wreg(BYTE reg, BYTE dat)
{
  SS_LO                   // Set SS# low
  SPIB = (reg<<3)+2;      // send reg. number w. DIR bit (b1) set to WRITE
  while(SPICN_bit.STBY);  // loop if data still being sent
  SPIB = dat;             // send the data
  while(SPICN_bit.STBY);  // loop if data still being sent
  SS_HI                   // set SS# high
}

例子:基于Windows的应急按钮

这个USB小产品是一个USB HID (人机接口设备),具有一个'应急'按钮。当你按下按键,所有的活动窗口被最小化,你看到的仅剩桌面,再按一下它,所有的应用窗口又重新弹回来。

USB键盘很有意思,如果插入几个键盘,它们将同时有效。所以我们的小应急按钮可以和你的正常键盘一起工作。

如果PC在待机,这个应急按钮担当了一个新角色—它可以充当PC的远程唤醒按键。不过这取决于你的PC支持不支持USB唤醒,有些可以,有些不可以。这个按钮可以帮你判断你的PC可不可以。

此例程在带有一个USB子卡(包含MAX3420E)的MAXQ2000开发板上运行。

USB详细说明

这个应用代码包含了USB做枚举类型琐碎工作的样板代码,此设备的属性已经用Panic_Button_Enum_Data.h中的特性阵列完全描述。

这个应用使用了两个端点,强制的CONTROL端点0,和EP3-IN,单缓冲64字节端点。虽然MAX3420E内含两个双重缓冲的64字节端点(EP1-OUT和EP2-OUT),在这个应用中并不需要双重缓冲的吞吐优势。

一个普遍存在HID错误概念是HID设备仅仅工作在低速下,本例展示了即使是像键盘这样的东西也可以从全速运行中得到好处,通过发送12MHz的包来而不是1.5MHz包,它可以使用更低的总线带宽。

图7. 应急按钮的流程图
图7. 应急按钮的流程图

中断端点有查询间隔,它决定了USB主设备隔多久向IN端点要数据。每隔一段时间我们可以预计到主控制器发了一个IN请求给我们的设备端点3。图7显示了处理这些请求的一个简单的状态机。只要设备被例举了,处理器重复地执行这一过程。为了简单起见,该应用程序查询中断脚是否有效,当然,如果你还有其他事要微控制器处理,你会用中断来激活Do_IN3函数。

状态机使用了两个全局变量:statebutton。C宏定义了三个状态:IDLE、RELEASE和WAIT。状态变量初始化为IDLE。如果连在MAX3420E的GPIN0上的按键按下,变量button是高,否则为低。main()中的无穷循环增加一个按键检查定时器,当定时器到时它会读一下MAX3420E中的GPIO寄存器来决定按键状态。此方法省掉了不必要的SPI流量。

当按键处于弹起状态时,状态图转到左边的两个分支,不做任何事。如果按键在IDLE状态被按下,就发一个清除桌面上活动窗口的键码。键码次序是08 (Windows键) 00 (保留)和07 (字母d)。下一个状态转到RELEASE,这样就完成了。

只要MAX3420E把数据包送到USB,它就产生另一个EP3-IN中断请求来表示EP3-IN FIFO可以再一次装载数据。然后再次进入图7函数,此时状态state = RELEASE ,因此函数发送序列00 00 00来表示“按键弹起”,下一个状态进入WAIT,意思是“等待按键被释放”。

现在函数要做的所有工作是利用WAIT状态分支程序来检测按键释放。如果按键一直按着,程序不做任何事,当按键一被释放,状态图就进到右边的两个分支,重新初始化state 变量为IDLE,使函数等候下一个按键按下。

占大部分运行时间的代码只有少数几行,图7给出了流程图:
void Do_IN3(void)
{
  switch(state)
  {
  case IDLE:
    if (button)
      {
                        wreg(rEP3INFIFO,0x08);  // "Windows" prefix key
                        wreg(rEP3INFIFO,0);
                        wreg(rEP3INFIFO,0x07);  // "D" key
                wreg(rEP3INBC,3);                       // arm it 
                state = RELEASE;        // next state sends the "keys up" code
      }
      break;    // else do nothing (and the SIE will NAK)
//
  case RELEASE:
    {
                wreg(rEP3INFIFO,0x00);          // key up
                wreg(rEP3INFIFO,0x00);
                wreg(rEP3INFIFO,0x00);          // key up
        wreg(rEP3INBC,3);                               // arm it
        state = WAIT;                   // next state waits for the PB to be unpressed
    }
    break;     
  case WAIT:
    if (!button)
      state = IDLE;
    break;
  default:  state = IDLE;
  } // end switch
} 

代码关键

需要对代码中的一些细节加以说明。

时间敏感的USB事件

MAX3420E通过在USB总线上送'K'状态10ms时间发了一个远程唤醒信号,为了避免用SPI主控器来做设定时间这种杂活,MAX3420E用自己内部来定时这个信号(事实上,所有的USB时间敏感事件),然后在时间到时给SPI主控器发一个中断。SPI主控器对这些事件不必用上它自己的定时器-它只需启动操作,然后等待完成中断。

ACKSTAT位

函数rregASwregAS的功能与rregwreg只有一点不同,它们在SPI命令字中设了ACK STATUS位。SPI主控器(我们的例子中是MAXQ2000)用这一位表示MAX3420E已经完成了当前的CONTROL请求服务,因此用应答它的状态情况的方式来终止CONTROL传输。ACKSTAT还扮演了一个内部寄存器位的角色,而且由于它含在SPI的命令字中,对它的操作能快速执行且只需少量代码。

readbytes(), writebytes()函数

readbytes()writebytes()函数使用了MAX3420E的数据突发能力。与每次字节寻址发两个字节(一个命令字节和一个数据字节)不同,这些函数先拉低SS#,送命令字,然后送入/送出一串字节,最后把SS#来拉高结束SPI传输。

哪里找到产品ID

图8. 产品ID在这里显示
图8. 产品ID在这里显示

产品ID码(在Panic_Button_Enum_Data.h中)是第一次插入应急按钮时出现的一小段信息。在确定应急按钮为HID的枚举过程中弹出来,并把它和Windows的内部驱动联系起来。

除了插入任何USB设备时都会听到一小声“叭哒”外,后续的每个附件都不发声。任何时候如果你想检查设备状态,请打开图8所示屏幕。你可以右击“我的电脑”,选择“属性”,“硬件”页,“设备管理器”按钮,展开“人机接口设备”项,右击“USB人机接口设备”,选择“属性”。

USB兼容性

查看代码后,你可能认为这对一个单键的USB设备来说要做很多工作,因为对任何USB设备都需要写一段固定代码。幸运的是我们对USB进行了仔细的归纳,枚举代码可作为任何USB设备的一个模板(拷贝-粘贴即可)。

像所有认真的开发者一样,我们希望自己的设计能够通过USB-IF认证,以避免任何知识产权问题。这个应用已经通过了USB命令验证(USBCV版本1.2.1.0)和USB-IF网站为开发者提供的HID测试。下面的图9给出了该应急按钮的记录信息。

图9. 应急按钮USB和HID测试记录和状况报告
图9. 应急按钮USB和HID测试记录和状况报告

结论

如果需要设计一个USB外设,可选择MAX3420E。该器件提供小尺寸封装,并可提供许多免费程序。MAX3420E能够为设计增加I/O口线,在支持SPI的系统中能很好地工作。由于SPI很容易通过位仿真实现,因此可以使用任何微控制器。如果需要更高性能,可以使用高达26MHz的SPI时钟。

类似文章发表于Circuit Cellar 2005年7月刊。

附录. 代码列表


下载代码列表


相关型号
MAX3420E USB外设控制器,SPI接口 免费样品  
MAXQ2000 低功耗LCD微控制器  


下一步
EE-Mail 订阅EE-Mail,接收关于您感兴趣的新文档的自动通知。

© Mar 31, 2006, Maxim Integrated Products, Inc.
The content on this webpage is protected by copyright laws of the United States and of foreign countries. For requests to copy this content, contact us.

APP 3637: Mar 31, 2006
应用笔记3637, AN3637, AN 3637, APP3637, Appnote3637, Appnote 3637