新闻中心

EEPW首页>模拟技术>设计应用> 为任何系统增加USB

为任何系统增加USB

——
作者: 时间:2007-01-26 来源:《Maxim公司》 收藏


图1. usb供电

图1是一个普通的usb外设结构。usb的vbus电源线为3.3v稳压器提供5v输入,该稳压器给微控制器和max3420e供电(无需墙上适配器)。spi接口可以是3、4或5线。表1列出了5线接口。

表1. 使用3到5线的spi接口

如果应用中不需要中断(max3420e的中断条件可以通过读取寄存器直接检测到)s,可以去掉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. 只连接大规模集成芯片的少许引脚

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

隔离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)是高无效。{{分页}}

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


spi代码
编写max3420e通用c代码的窍门是把原始的最基本的spi操作封装到独立的模块中,然后针对不同的spi接口的只需客户化这一模块。最基本的模块只须做三件事:
初始化spi口
读一个字节
写一个字节
本文中的应用例子使用硬件spi单元。对没有此单元的spi主控器,我们先看看一些位仿真spi接口的通用c代码。
位仿真spi
init spi
初始化spi口的函数会随处理器的不同而有很多变化。比较好的做法是先指定接口使用的i/o脚,设置它们的方向,然后设定ss=1和sclk=0的初始条件(我们假定用spi主控器的(00)模式)。

读寄存器,写寄存器
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 dont 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单元提供了sck、mosi和miso,但是没有ss#。由于ss#的操作方式会变化(比如寻址一个字节与突发的字节串),最好用通用i/o脚做ss#。

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 dont care, were 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. 应急按钮的流程图

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

状态机使用了两个全局变量:state和button。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位
函数rregas和wregas的功能与rreg和wreg只有一点不同,它们在spi命令字中设了ack status位。spi主控器(我们的例子中是maxq2000)用这一位表示max3420e已经完成了当前的control请求服务,因此用应答它的状态情况的方式来终止control传输。ackstat还扮演了一个内部寄存器位的角色,而且由于它含在spi的命令字中,对它的操作能快速执行且只需少量代码。

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

哪里找到产品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. usb和hid测试记录和状况报告

结论

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



关键词:

评论


相关推荐

技术专区

关闭