发现问题
前一阵在学习ARM,到淘宝上买了一块很便宜的AT91SAM7S开发板。这块板子的设计是同ATMEL官方的AT91SAM7S-EK兼容的,因此在AT91SAM7S-EK上能跑的ATMEL例程都可以直接拿来用。由于AT91SAM7S系列的一大特色就是带有USB接口,因此我便迫不及待地想试验一下USB究竟有多么神奇。
在AT91SAM7S-EK Software Package找了个最简单的USB例程usb-device-core-project,用J-link下到板子里去,一运行,Windows竟然提示检测到无法识别的USB设备,没有出现令人期待的新硬件安装向导。多次拔插依然如故。在调试串口里看到了这些信息:
初步分析
因为用的是ATMEL官方例程,我暂时还不敢怀疑程序会出错。莫非是我的板子的问题?为了搞个究竟,我打算先研究一下源程序再说。
在main.c中查找,发现不断输出错误信息的函数是static void ISR_Vbus(const Pin *pPin)。这个函数是一个中断服务程序,内容也很简单,就是检测一下引脚pinVbus上的电压,如果是1,则调用USBD_Connect(),把引脚pinPullUp置为0;否则调用USBD_Disconnect(),把引脚pinPullUp置为1。这个中断服务函数是在函数static void VBus_Configure(void)中配置的。每当pinVbus引脚上的电压发生变化时,就会引起这个PIO中断。
//-------------------------------------------------------------------
/// Handles interrupts coming from PIO controllers.
//-------------------------------------------------------------------
static void ISR_Vbus
(const Pin
*pPin
)
{
// Check current level on VBus
if(PIO_Get
(&pinVbus
)){
TRACE_INFO
("VBUS conn\n\r"
);
USBD_Connect
();//PIO_Clear(&pinPullUp);
}
else{
TRACE_INFO
("VBUS discon\n\r"
);
USBD_Disconnect
();//PIO_Set(&pinPullUp);
}
}
上图调试串口输出的结果表明, ISR_Vbus这个中断一直在不断地被调用,而且pinVBUS引脚上的电压也在不断地高低变化。为了搞清楚问题的原因,我尝试在函数ISR_Vbus的入口处下断点。当板子插入USB接口时,程序被断了下来,继续单步运行,程序输出VBUS conn。继续全速运行,Windows竟然提示发现新硬件了,板子工作正常。但是如果删除断点全速运行,板子工作就又会不正常,真是一件奇怪的事情。
初步解决
仔细思考一下,单步运行程序和全速运行程序到底有什么区别呢?代码完全一样,但是,单步运行程序的时候在两句代码间会有延迟。由此推测程序中可能需要加入延时。联想到按键去抖动的原理,即检测到电平变化后需要等待一段时间再检测一次以排除干扰,我尝试在PIO_Get(&pinVbus)前加一小段延时,程序变成这样:
//-------------------------------------------------------------------
// Software wait loop -- suspends all tasks!
//-------------------------------------------------------------------
void delay
(int wait_time
)
{
volatile unsigned int i
;
for( i
=0
; i
< wait_time
*(BOARD_MCK/10000); i
++)
{;;;}
}
//-------------------------------------------------------------------
/// Handles interrupts coming from PIO controllers.
//-------------------------------------------------------------------
static void ISR_Vbus(
const Pin *pPin)
{
// Check current level on VBus
delay(1); // Wait a moment
if(PIO_Get(&pinVbus)){
TRACE_INFO("VBUS conn\n\r");
USBD_Connect();
//PIO_Clear(&pinPullUp);
}
else{
TRACE_INFO("VBUS discon\n\r");
USBD_Disconnect();
//PIO_Set(&pinPullUp);
}
}
再次下载运行,一切正常。
深入分析
最后再来看一下板子上pinVbus(即PA13,图中VBUS_DET)和pinPullUp(即PA16,图中USB_DP_PUP)引脚究竟是怎样连接的,彻底搞清其中原理。原理图如下所示,很简单。当板子插到USB接口中后,VBUS处会从电阻处分压到高电平,此时会触发pinVbus的PIO中断。系统检测到中断后,调用ISR_Vbus函数,将USB_DP_PUP置为低电平。此时N型MOS管Q1的Vgs>导通电压,管子导通,进而导致P型MOS管Q2的栅极接低电平,Q2也导通。这样D+数据线就通过1.5K电阻接到VCC,建立了USB连接。若设备从USB总线中移除(VBUS=0)或者将USB_DP_UP置为高电平,都会使Q1、Q2截止,断开了1.5K上拉电阻。
从电路中可以看到,pinVbus是直接连接到VBUS的,若VBUS的电平发生变化,就会产生PIO中断。在刚刚插入USB接口的那一瞬间,VBUS电平不稳定,会不断触发中断(这和按键的抖动有些类似),由此引发USB_DP_PUP引脚上的电压也不断改变。后来VBUS虽然稳定了,但是USB_DP_PUP引脚上不断改变的电压已经通过某种耦合(这可能与PCB的设计相关)使VBUS_DET处的电压产生震荡,再次引发中断,在中断处理程序中又会改变USB_DP_PUP引脚上的电压,从而使电路产生了自激震荡,无法正常工作。
经验总结
也许在你用的板子上没有出现类似的问题,但是我的这次经历表明,在中断程序中不加延时直接检测VBUS上的电压是有风险的。你可能会遇到USB有时工作正常,有时工作不正常的现象,这时就需要看看是否有上面的问题。特别是我们一般都比较信赖官方的例程,往往会不加修改的直接借用,因此那个有风险的ISR_Vbus函数可能正在被许多系统使用着。
另外,如果在调试过程中单步运行程序工作正常,而全速运行则工作不正常,这很可能是说明程序的时序上出现了问题。尝试加入一些延时(特别在检测一些易产生电平抖动的器件的电压时),也许就能解决问题。这可以算得上是一个调试的技巧。这是我从这次经历中总结出的另一个经验。
多谢sjdai指出文中不严谨之处!现已改正!若仍有问题还请大家多多指教!