从图(一)中得知,在 USB 中断处理函数(HAL_HCD_IRQHandler)中,在判断为 Host channel 中断时,调用了 HCD_HC_IN_IRQHandler()函数来处理 IN 指令。而在 HCD_HC_IN_IRQHandler()函数中判断如果是 NAK 引起的中断,且该端点为控制传输或者是块传输的话,就重新使能这个通道。一旦重新使能该通道,主机硬件又自动发送 IN 令牌来企图从设备获取数据,直到设备准备好数据后,不再回复 NAK 握手,而是回复主机要获取的数据,然后主机硬件回复 ACK 来结束本次 transfer 。
结论 :
通过以上的分析可以得知,该问题是由于库函数中对于 NAK 是自动重新发送 IN 令牌的,这样做的目的是确保数据的及时响应。根据客户的需求,我们可以使用定时器延时的方法,延长重新使能通道的时间。
处理 :
使用定时器来延迟重新使能该通道的时间,进而延迟重新发送 IN 令牌的时间。基于 U 盘读写的例程。
一.在主函数中增加初始化
tiM3 的代码:
//Tomas Li add
TimHandle.Instance = TIM3;
TimHandle.Init.Prescaler = 5;
TimHandle.Init.CounterMode = TIM_COUNTERMODE_UP;
TimHandle.Init.Period = 3000;
TimHandle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_Base_Init(&TimHandle);
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
HAL_TIM_ConfigClockSource(&TimHandle, &sClockSourceConfig);
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&TimHandle, &sMasterConfig);
//Tomas Li add
二.在 HAL_TIM_Base_MspInit()函数中增加中断初始化
//Tomas Li add
if(htim->Instance==TIM3)
{
/* Peripheral clock enable */
__TIM3_CLK_ENABLE();
/* Peripheral interrupt init*/
/* Sets the priority grouping field */
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_0);
HAL_NVIC_SetPriority(TIM3_IRQn, 5, 1);
HAL_NVIC_EnableIRQ(TIM3_IRQn);
}
//Tomas Li add
三.在 main.h 文件中增加 TIM3 的宏定义代码:
//Tomas Li add
/* Definition for TIMx clock resources */
#define TIMx TIM3
#define TIMx_CLK_ENABLE __TIM3_CLK_ENABLE
/* Definition for TIMx's NVIC */
#define TIMx_IRQn TIM3_IRQn
#define TIMx_IRQHandler TIM3_IRQHandler
//Tomas Li add
四.在 HCD_HC_IN_IRQHandler()函数中增加判断块传输(Bulk)的语句并开启定时器
else if (hhcd->hc[chnum].ep_type == EP_TYPE_CTRL)
{
/* re-activate the channel */
USBx_HC(chnum)->HCCHAR &= ~USB_OTG_HCCHAR_CHDIS;
USBx_HC(chnum)->HCCHAR |= USB_OTG_HCCHAR_CHENA;
}//tomas li add
else if((hhcd->hc[chnum].ep_type == EP_TYPE_BULK)){
chnum_dup=chnum; //Mark this value for TIM3 interrupt to used.
HAL_TIM_Base_Start_IT(&TimHandle);
//tomas li add
}
五.在 TIM3 的回调函数 HAL_TIM_PeriodElapsedCallback()中重新使能通道并关闭定时器
/* Add by Tomas */
USB_OTG_GlobalTypeDef *USBx = USB_OTG_FS;
/* re-activate the channel */
USBx_HC(chnum_dup)->HCCHAR |= USB_OTG_HCCHAR_CHENA; //enable channal
USBx_HC(chnum_dup)->HCCHAR &= ~USB_OTG_HCCHAR_CHDIS; //disable channal
HAL_TIM_Base_Stop_IT(htim);
/* Add by Tomas */
结果 :
实际在 U 盘的读写测试,可以正常的读写。使用 USB 分析仪,查看 IN 令牌的发送时间在不定期的变化(结果如图二所示),这是因为在重启通道之后,立即就会有一次 IN 的传输(这个时间是硬件决定的)。而下一次的 IN 令牌,会在我们设置的时间后再发出.