SAM9G45之USB学习笔记
最近一直在学习SAM9G45的USB功能,资料大部分都是从网上找的,还有就是官方给的库,只是把自己调试过程中遇到的问题记录下来,因为没有调试完,所以会持续更新。
问题一(其实也不是问题),SAM9G45只有7个端点(包括端点0),我开始调试的时候以为不带0,哎,郁闷。
问题二(待求证),不过从我目前的调试来看,结论是对的。SAM9G45的端点不是全双工了,一个端点不能同时配置为IN和OUT,但是手册又有自相矛盾的地方。
从表38-1来看它的每个端点都支持ctrl传输模式,根据USB协议规定,这个传输模式要求端点必须是全双工的,因为ctrl的端点必须支持messsge pipe类型。
从表38-2也能看出,CTRL模式是双向的;
从表38-3的注意1来看,它说control transfer必须是1bank,从38-1看,bank1只能是端点0;从这里来看其它端点是不能支持ctrl模式的。和我的实验结论一致,也可能是我不会设置端点地址,如果哪位网友正好知道,请给我留言,谢谢。
三、Bus Hound配置问题
这个东西用的不熟悉,抓包发现只能抓取32的字节的IN指令包,以为是设备问题,最后发现是busHound设置问题,郁闷。
四、这个是目前遇到最郁闷的问题
刚开始的Demo是hid_transfer,我在这个基础上改的CCID,并且开始只是用一个设备+一个interface调试的,很顺利,很快就识别出来了。应用需求,需要两个interface,改过之后怎么也识别不了第二个设备。Bus Hound上面,也没有第二个设备的报文。郁闷,以为是配置问题,结果把配置复制带interface0上面,正常。郁闷了,所有的操作函数都是一套,通过接口传参数,来确定是操作那个设备,难道个别的函数有错误,参数没有改过来?于是把所有的函数全部对了一遍,没有错误啊?这是怎么回事。最后找到原因了,原来是windows系统的驱动问题。如果一个设备的V_ID和P_ID不修改,之前已经插入过电脑,并且正确设别并安装驱动的话,如果再次插入(即使配置付改变了)驱动不会从新安装,这个时候设备出来的还是原来的设备。
解决方法:
1、修改P_ID或者V_ID的值;
2、或者在注册表里面删掉对应的配置信息;
我修改了P_ID值,正确识别了两个设备:
《———————————————————————————————————————————————————-》
官方USB协议帧学习笔记:
先上传几张图片,回头再整理:
图1 协议栈的结构
图2 USB的状态机
图3 USB的协议栈回调函数架构
首先要知道USB的驱动状态:
USB device states
/// The device is currently suspended.
#define USBD_STATE_SUSPENDED 0
/// USB cable is plugged into the device.
#define USBD_STATE_ATTACHED 1
/// Host is providing +5V through the USB cable.
#define USBD_STATE_POWERED 2
/// Device has been reset.
#define USBD_STATE_DEFAULT 3
/// The device has been given an address on the bus.
#define USBD_STATE_ADDRESS 4
/// A valid configuration has been selected.
#define USBD_STATE_CONFIGURED 5
deviceState(状态变量)
其次USB的API返回值
USB device API return values
/// Indicates the operation was successful.
#define USBD_STATUS_SUCCESS 0
/// Endpoint/device is already busy.
#define USBD_STATUS_LOCKED 1
/// Operation has been aborted.
#define USBD_STATUS_ABORTED 2
/// Operation has been aborted because the device has been reset.
#define USBD_STATUS_RESET 3
/// Operation failed because parameter error
#define USBD_STATUS_INVALID_PARAMETER 4
/// Operation failed because HW not supported
#define USBD_STATUS_HW_NOT_SUPPORTED 5
端点的状态:
Endpoint states
/// Endpoint states: Endpoint is disabled
#define UDP_ENDPOINT_DISABLED 0
/// Endpoint states: Endpoint is halted (i.e. STALLs every request)
#define UDP_ENDPOINT_HALTED 1
/// Endpoint states: Endpoint is idle (i.e. ready for transmission)
#define UDP_ENDPOINT_IDLE 2
/// Endpoint states: Endpoint is sending data
#define UDP_ENDPOINT_SENDING 3
/// Endpoint states: Endpoint is receiving data
#define UDP_ENDPOINT_RECEIVING 4
1、CCIDDriver_Initialize()
CCIDDriver_Initialize()->USBD_Init()->USBDCallbacks_Initialized()->IRQ_ConfigureIT(AT91C_ID_UDPHS, 0, UDPD_IrqHandler);//, UDPD_IrqHandler);
把UDPD_IrqHandler()配置为USB的中断函数;
UDPD_IrqHandler()调用UDPHS_EndpointHandler()和UDPHS_DmaHandler()
其中:
UDPHS_EndpointHandler()->USBDCallbacks_RequestReceived()->CCID_RequestHandler()->CCID
处理和USBDDriver_RequestHandler()
UDPHS_EndpointHandler()和UDPHS_DmaHandler()都会调用UDPHS_EndOfTransfer()
来看下UDPHS_EndOfTransfer()
//------------------------------------------------------------------------------
/// Handles a completed transfer on the given endpoint, invoking the
/// configured callback if any.
/// \param bEndpoint Number of the endpoint for which the transfer has completed.
/// \param bStatus Status code returned by the transfer operation
//------------------------------------------------------------------------------
static void UDPHS_EndOfTransfer( unsigned char bEndpoint, char bStatus )
{
Endpoint *pEndpoint = &(endpoints[bEndpoint]);
Transfer *pTransfer = &(pEndpoint->transfer);
// Check that endpoint was sending or receiving data
if( (pEndpoint->state == UDP_ENDPOINT_RECEIVING)
|| (pEndpoint->state == UDP_ENDPOINT_IDLE)
|| (pEndpoint->state == UDP_ENDPOINT_SENDING) ) {
TRACE_DEBUG_WP("UDPHS_EndOfTransfer ");
if(pEndpoint->state == UDP_ENDPOINT_SENDING) {
pEndpoint->sendZLP = 0;
}
// Endpoint returns in Idle state
//if(pEndpoint->state == UDP_ENDPOINT_SENDING){
pEndpoint->state = UDP_ENDPOINT_IDLE;
//}
// Invoke callback is present
if (pTransfer->fCallback != 0) {
((TransferCallback) pTransfer->fCallback)
(pTransfer->pArgument,
bStatus,
pTransfer->transferred,
pTransfer->remaining + pTransfer->buffered);
}
else {
TRACE_DEBUG_WP("No callBack\n\r");
}
}
}
当接收完成或者发送完成,就会调用一个callback,这个callback是哪里来的呢?继续看
void CCID_SmartCardRequest_V1( uint8_t chCcidNum )
{
unsigned char bStatus;
if(chCcidNum >= CCID_INTERFACE_NUM){
TRACE_ERROR("CCID_SmartCardRequest chCcidNum >= CCID_INTERFACE_NUM\n\r");
return;
}
//do{
bStatus = CCID_Read_V1((void*)&ccidDriver.tCcidStruct[chCcidNum].sCcidCommand,
sizeof(S_ccid_bulk_out_header),
(TransferCallback)&CCIDCommandDispatcher_V1,
(void*)chCcidNum );//直接把值变为地址传输
//}while (bStatus != USBD_STATUS_SUCCESS);
}
这个是API层的CCID调用函数,
//------------------------------------------------------------------------------
/// Reads data from the Data OUT endpoint
/// \param pBuffer Buffer to store the received data
/// \param dLength data buffer length
/// \param fCallback Optional callback function
/// \param pArgument Optional parameter for the callback function
/// \return USBD_STATUS_LOCKED or USBD_STATUS_SUCCESS
//------------------------------------------------------------------------------
/*
unsigned char CCID_Read(void *pBuffer,
unsigned int dLength,
TransferCallback fCallback,
void *pArgument)
{
return USBD_Read(CCID_EPT_DATA_OUT, pBuffer, dLength, fCallback, pArgument);
}*/
unsigned char CCID_Read_V1( void *pBuffer,
unsigned int dLength,
TransferCallback fCallback,
void *pArgument)
{
uint8_t chEndpointOut = 0;
if(((uint8_t)pArgument) >= CCID_INTERFACE_NUM){
TRACE_ERROR("CCID_Read chCcidNum >= CCID_INTERFACE_NUM\n\r");
return USBD_STATUS_INVALID_PARAMETER;
}
switch((uint8_t)pArgument){
#if (CCID_INTERFACE_NUM>0)
case 0:
chEndpointOut = CCID_INTERFACE0_EPT_DATA_OUT;
break;
#endif
#if (CCID_INTERFACE_NUM>1)
case 1:
chEndpointOut = CCID_INTERFACE1_EPT_DATA_OUT;
break;
#endif
#if (CCID_INTERFACE_NUM>2)
case 2:
chEndpointOut = CCID_INTERFACE2_EPT_DATA_OUT;
break;
#endif
#if (CCID_INTERFACE_NUM>3)
case 3:
chEndpointOut = CCID_INTERFACE3_EPT_DATA_OUT;
break;
#endif
}
return USBD_Read(chEndpointOut, pBuffer, dLength, fCallback, pArgument);
}
继续看:
//------------------------------------------------------------------------------
/// Reads incoming data on an USB endpoint (OUT)
/// \param bEndpoint Index of endpoint
/// \param *pData Data to be readen
/// \param dLength Data length to be receive
/// \param fCallback Callback to be call after the success command
/// \param *pArgument Callback argument
/// \return USBD_STATUS_LOCKED or USBD_STATUS_SUCCESS
//------------------------------------------------------------------------------
char USBD_Read( unsigned char bEndpoint,
void *pData,
unsigned int dLength,
TransferCallback fCallback,
void *pArgument )
{
Endpoint *pEndpoint = &(endpoints[bEndpoint]);
Transfer *pTransfer = &(pEndpoint->transfer);
// Return if the endpoint is not in IDLE state
if (pEndpoint->state != UDP_ENDPOINT_IDLE) {
return USBD_STATUS_LOCKED;
}
TRACE_DEBUG_WP("Read%d(%d) ", bEndpoint, dLength);
// Endpoint enters Receiving state
pEndpoint->state = UDP_ENDPOINT_RECEIVING;
// Set the transfer descriptor
pTransfer->pData = pData;
pTransfer->remaining = dLength;
pTransfer->buffered = 0;
pTransfer->transferred = 0;
pTransfer->fCallback = fCallback;
pTransfer->pArgument = pArgument;
#ifdef DMA
// Test if endpoint type control
if(AT91C_UDPHS_EPT_TYPE_CTL_EPT == (AT91C_UDPHS_EPT_TYPE&(AT91C_BASE_UDPHS->UDPHS_EPT[bEndpoint].UDPHS_EPTCFG))) {
#endif
// Control endpoint
// Enable endpoint IT
AT91C_BASE_UDPHS->UDPHS_IEN |= (1 << SHIFT_INTERUPT << bEndpoint);
AT91C_BASE_UDPHS->UDPHS_EPT[bEndpoint].UDPHS_EPTCTLENB = AT91C_UDPHS_RX_BK_RDY;
#ifdef DMA
}
else {
TRACE_DEBUG_WP("Read%d(%d) ", bEndpoint, dLength);
// Others endpoints (not control)
if( pTransfer->remaining > DMA_MAX_FIFO_SIZE ) {
// Transfer the max
pTransfer->buffered = DMA_MAX_FIFO_SIZE;
}
else {
// Transfer the good size
pTransfer->buffered = pTransfer->remaining;
}
AT91C_BASE_UDPHS->UDPHS_DMA[bEndpoint].UDPHS_DMAADDRESS = (unsigned int)(pTransfer->pData);
// Clear unwanted interrupts
AT91C_BASE_UDPHS->UDPHS_DMA[bEndpoint].UDPHS_DMASTATUS;
// Enable DMA endpoint interrupt
AT91C_BASE_UDPHS->UDPHS_IEN |= (1 << SHIFT_DMA << bEndpoint);
TRACE_DEBUG_WP("\n\r_RR:%d ", pTransfer->remaining );
TRACE_DEBUG_WP("B:%d ", pTransfer->buffered );
TRACE_DEBUG_WP("T:%d ", pTransfer->transferred );
// DMA config
AT91C_BASE_UDPHS->UDPHS_DMA[bEndpoint].UDPHS_DMACONTROL = 0; // raz
AT91C_BASE_UDPHS->UDPHS_DMA[bEndpoint].UDPHS_DMACONTROL =
( ((pTransfer->buffered << 16) & AT91C_UDPHS_BUFF_COUNT)
| AT91C_UDPHS_END_TR_EN
| AT91C_UDPHS_END_TR_IT
| AT91C_UDPHS_END_B_EN
| AT91C_UDPHS_END_BUFFIT
| AT91C_UDPHS_CHANN_ENB );
}
#endif
return USBD_STATUS_SUCCESS;
}
到这看明白了吧,是上层的CCID_SmartCardRequest_V1()通过usb_read()设置的
看下端点的结构体:
//------------------------------------------------------------------------------
/// Describes the state of an endpoint of the UDP controller.
//------------------------------------------------------------------------------
typedef struct
{
/// Current endpoint state.
volatile unsigned char state;
/// Current reception bank (0 or 1).
unsigned char bank;
/// Maximum packet size for the endpoint.
unsigned short size;
/// Describes an ongoing transfer (if current state is either
/// <UDP_ENDPOINT_SENDING> or <UDP_ENDPOINT_RECEIVING>)
Transfer transfer;
/// Special case for send a ZLP
unsigned char sendZLP;
} Endpoint;
/// Describes an ongoing transfer on a UDP endpoint.
typedef struct
{
/// Pointer to a data buffer used for emission/reception.
char *pData;
/// Number of bytes which have been written into the UDP internal FIFO
/// buffers.
volatile int buffered;
/// Number of bytes which have been sent/received.
volatile int transferred;
/// Number of bytes which have not been buffered/transferred yet.
volatile int remaining;
/// Optional callback to invoke when the transfer completes.
volatile TransferCallback fCallback;
/// Optional argument to the callback function.
void *pArgument;
} Transfer;
至此,我们把自己每个端点的处理函数CCIDCommandDispatcher_V1()绑定到了相应的端点上。
问题:
1、到目前为止,有一点我不是很理解,为什么每次收到后,需要对相应的DMA重新初始化,相应的端点也同样做了初始化,我查看了HID、CDC历程,都是这么处理的。因此在CCIDCommandDispatcher_V1()里面我们需要调用usb_read()做相应初始化,或者利用查询方式,在应用层初始化。
<————————————————————————————2021.08.05———————————————————————————>
一、今天测试MCU的CDC功能,在其它产品上在跑了,没问题了。但是应用到我的产品上,发现老是复位。和没有问题的产品区别:
1)它是USB数据到UART1,我是USB数据到UART3;
2)它是连接的其它UART模块,我是连接的NB;
通过屏蔽代码,发现是看门狗复位。
通过屏蔽代码,NB上电不使能,没有问题。
NB上电会通过串口往外发信息,导致MCU“死机”,看门狗复位。
在分析原因,上电USB会初始化,和上位机交互,如果这个时候应用层通过USB发送数据,会导致USB出问题。解决方案是等待USB初始化完成,才允许应用层通过USB发送数据。
还没有评论,来说两句吧...