您尚未登录。

楼主 #1 2020-01-26 23:07:55

metro
会员
注册时间: 2019-03-09
已发帖子: 442
积分: 486

CH558T USB功能实现+性能测试

由于众所周知的原因,这几天宅在家里闲来无事(毕业论文:?那我呢),刚好这次回家带了个CH558T开发板(主要是体积小方便带),于是乎研究了一下USB功能。
刚好一直以来对USB协议挺感兴趣,所以这次没有参考官方demo,而是照着datasheet自己敲了个USB协议栈,调试了两天终于能用了,来坑网分享一下源代码核相关的调试经验。
目前实现的功能是对多种不同的传输方式组合进行速度测试,在保证性能的同时发现一些datasheet没有提及的问题,需要配合上位机进行测试。
当然,鼓捣这玩意儿还是有目的的,不过现在先卖个关子(否则到时咕掉了就不好意思了)。目前看来,CH552的资源和性能可能稍微有些不够用,CH558挺好的,虽然调试确实有点头疼 tongue

离线

楼主 #2 2020-01-26 23:23:16

metro
会员
注册时间: 2019-03-09
已发帖子: 442
积分: 486

Re: CH558T USB功能实现+性能测试

先晒一个调试组合,逻辑分析仪+CH558T最小系统板,随身携带不碍事儿~
_20200126231014.jpg
有一说一,对于USB协议的分析真的是需要专门的工具(特别是不带驱动的裸机开发),全速设备尚且可以拿普通的逻辑分析仪凑合一下,高速以上的设备还是需要更专业的协议分析仪,可以少走很多弯路(很多底层的错误是没办法在软件层面发现的)。
另外,CH55x芯片虽然性价比高,但是调试一直是个老大难问题,不仅没有专用的调试接口(基本靠GPIO,串口偶尔能用,ISD51和USB八字不合就不要想了),而且低端的CH551/2/4还有烧写次数的限制(200次,所以这也就是我买CH558的原因,能折腾),烧写还非常麻烦(我是基本靠USB,必须按着按键上电才行,通电时重启都不好使)。为此,我DIY了一根带开关的USB信号线(某宝有这玩意,但是快递已经基本停业了),勉强还能用吧。后面考虑一下用单片机做个简单的烧录器,通过按键时间长短决定是否进入bootloader。

离线

楼主 #3 2020-01-26 23:33:21

metro
会员
注册时间: 2019-03-09
已发帖子: 442
积分: 486

Re: CH558T USB功能实现+性能测试

下面分享一下代码。由于实现了比较完整的协议栈(虽然没有Class,不过Request基本都实现了),所以代码体积略大,不过编译后也不到2K。
代码经过官方工具USB3CV的验证(Test All Pass),所以大概率不会有很大问题,就当献丑了。
源程序是在Keil编译的,后面有时间再尝试一下SDCC。头文件CH558.H需要自备。

#include "../CH558.H"
#include <string.h>

#define bMaxPacketSize0 64
#define wMaxPacketSize 64
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#define HI(x) ((x) >> 8)
#define LO(x) ((x) & 0xFF)
#define WBVAL(x) LO(x), HI(x)
#define UIS_EP_IN(x) (UIS_TOKEN_IN | (x))
#define UIS_EP_OUT(x) (UIS_TOKEN_OUT | (x))
#define EP_IN(x) (USB_ENDP_DIR_MASK | (x))
#define EP_OUT(x) (x)

UINT8C DeviceDescriptor[] = {
    18,              // bLength
    1,               // bDescriptorType, DEVICE
    WBVAL(0x0200),   // bcdUSB
    0x00,            // bDeviceClass
    0x00,            // bDeviceSubClass
    0x00,            // bDeviceProtocol
    bMaxPacketSize0, // bMaxPacketSize0
    WBVAL(0x04B4),   // idVendor
    WBVAL(0x1003),   // idProduct
    WBVAL(0x0000),   // bcdDevice
    1,               // iManufacturer
    2,               // iProduct
    0,               // iSerialNumber
    1                // bNumConfigurations
};

UINT8C ConfigurationDescriptor[] = {
    9, // bLength
    2, // bDescriptorType, CONFIGURATION
    WBVAL(74), // wTotalLength
    1, // bNumInterfaces
    1, // bConfigurationValue
    0, // iConfiguration
    0x80, // bmAttributes
    250, // bMaxPower
    // Interface 0
    9, // bLength
    4, // bDescriptorType, INTERFACE
    0, // bInterfaceNumber
    0, // bAlternateSetting
    8, // bNumEndpoints
    0xFF, // bInterfaceClass
    0x00, // bInterfaceSubClass
    0x00, // bInterfaceProtocol
    0, // iInterface
    // Endpoint 1 IN
    7, // bLength
    5, // bDescriptorType, ENDPOINT
    0x81, // bEndpointAddress
    0x02, // bmAttributes
    WBVAL(wMaxPacketSize), // wMaxPacketSize
    0, // bInterval
    // Endpoint 1 OUT
    7, // bLength
    5, // bDescriptorType, ENDPOINT
    0x01, // bEndpointAddress
    0x02, // bmAttributes
    WBVAL(wMaxPacketSize), // wMaxPacketSize
    0, // bInterval
    // Endpoint 2 IN
    7, // bLength
    5, // bDescriptorType, ENDPOINT
    0x82, // bEndpointAddress
    0x02, // bmAttributes
    WBVAL(wMaxPacketSize), // wMaxPacketSize
    0, // bInterval
    // Endpoint 2 OUT
    7, // bLength
    5, // bDescriptorType, ENDPOINT
    0x02, // bEndpointAddress
    0x02, // bmAttributes
    WBVAL(wMaxPacketSize), // wMaxPacketSize
    0, // bInterval
    // Endpoint 3 IN
    7, // bLength
    5, // bDescriptorType, ENDPOINT
    0x83, // bEndpointAddress
    0x02, // bmAttributes
    WBVAL(wMaxPacketSize), // wMaxPacketSize
    0, // bInterval
    // Endpoint 3 OUT
    7, // bLength
    5, // bDescriptorType, ENDPOINT
    0x03, // bEndpointAddress
    0x02, // bmAttributes
    WBVAL(wMaxPacketSize), // wMaxPacketSize
    0, // bInterval
    // Endpoint 4 IN
    7, // bLength
    5, // bDescriptorType, ENDPOINT
    0x84, // bEndpointAddress
    0x02, // bmAttributes
    WBVAL(wMaxPacketSize), // wMaxPacketSize
    0, // bInterval
    // Endpoint 4 OUT
    7, // bLength
    5, // bDescriptorType, ENDPOINT
    0x04, // bEndpointAddress
    0x02, // bmAttributes
    WBVAL(wMaxPacketSize), // wMaxPacketSize
    0 // bInterval
};

UINT8C LanguageString[] = {
    4, // bLength
    3, // bDescriptorType, STRING
    WBVAL(0x0409)
};

UINT8C ManufacturerString[] = {
    9, // bLength
    3, // bDescriptorType, STRING
    'C', 'y', 'p', 'r', 'e', 's', 's'
};

UINT8C ProductString[] = {
    11, // bLength
    3, // bDescriptorType, STRING
    'C', 'Y', '-', 'S', 't', 'r', 'e', 'a', 'm'
};

PUINT8C StringDescriptors[] = {
    LanguageString,
    ManufacturerString,
    ProductString
};

#define STR_DESC_NUM (sizeof(StringDescriptors) / sizeof(PUINT8C))

static UINT8XV EP0Buf[64] _at_ 0x0;
static UINT8XV EP4Buf[2][64] _at_ 0x40;
static UINT8XV EP1Buf[4][64] _at_ 0x100;
static UINT8XV EP2Buf[4][64] _at_ 0x200;
static UINT8XV EP3Buf[2][64] _at_ 0x300;
static UINT8XV unused[64] _at_ 0x400;

struct {
    UINT8 req;
    UINT16 len;
    PUINT8C desc;
} data setup;

#define SetupPacket ((const USB_SETUP_REQ xdata *)EP0Buf)

#define GET_CONFIGURED() (USB_DEV_AD & bUDA_GP_BIT)
#define SET_CONFIGURED() (USB_DEV_AD |= bUDA_GP_BIT)
#define CLR_CONFIGURED() (USB_DEV_AD &= ~bUDA_GP_BIT)

void Clock_Initialize(void)
{
    SAFE_MOD = 0x55;
    SAFE_MOD = 0xAA;
    PLL_CFG = 7 << 5 | 28 << 0;
    CLOCK_CFG = bOSC_EN_INT | 6 << 0;
    SLEEP_CTRL = bSLP_OFF_USB | bSLP_OFF_ADC | bSLP_OFF_UART1 | bSLP_OFF_SPI0 | bSLP_OFF_TMR3 | bSLP_OFF_LED;
    ++SAFE_MOD;
}

void Port_Initialize(void)
{
    PORT_CFG &= ~bP2_OC;
    TNOW = 0;
    P2_PU &= ~bTNOW;
    P2_DIR |= bTNOW;
}

void USBD_Initialize(void)
{
    TNOW = 1;
    
    SAFE_MOD = 0x55;
    SAFE_MOD = 0xAA;
    SLEEP_CTRL &= ~bSLP_OFF_USB;
    WAKE_CTRL |= bWAK_BY_USB;
    ++SAFE_MOD;
    
    USB_CTRL = bUC_RESET_SIE | bUC_CLR_ALL;
    USB_CTRL = bUC_DEV_PU_EN | bUC_INT_BUSY | bUC_DMA_EN;
    UDEV_CTRL = bUD_DP_PD_DIS | bUD_DM_PD_DIS | bUD_PORT_EN;

    UEP0_DMA = (UINT16)EP0Buf;
    UEP1_DMA = (UINT16)EP1Buf;
    UEP2_DMA = (UINT16)EP2Buf;
    UEP3_DMA = (UINT16)EP3Buf;
    // UEP4_DMA = UEP0_DMA + 64

    UEP4_1_MOD = bUEP1_RX_EN | bUEP1_TX_EN | bUEP1_BUF_MOD | bUEP4_RX_EN | bUEP4_TX_EN;
    UEP2_3_MOD = bUEP3_RX_EN | bUEP3_TX_EN | bUEP2_RX_EN | bUEP2_TX_EN | bUEP2_BUF_MOD;

    USB_INT_EN = bUIE_TRANSFER | bUIE_BUS_RST;
    USB_INT_FG = 0xFF;
    IE_USB = 1;
    
    TNOW = 0;
}

void USBD_ISR(void) interrupt INT_NO_USB using 1
{
    TNOW = 1;

    if (UIF_TRANSFER) {
        if (U_TOG_OK)
            switch (USB_INT_ST & (MASK_UIS_TOKEN | MASK_UIS_ENDP)) {
            case UIS_EP_IN(1):
                break;

            case UIS_EP_OUT(1):
                break;

            case UIS_EP_IN(2):
                memcpy((PUINT8XV)USB_DMA, unused, wMaxPacketSize);
                break;

            case UIS_EP_OUT(2):
                memcpy(unused, (PUINT8XV)USB_DMA, USB_RX_LEN);
                break;
            
            case UIS_EP_IN(3):
                break;

            case UIS_EP_OUT(3):
                break;

            case UIS_EP_IN(4):
                memcpy((PUINT8XV)USB_DMA, unused, wMaxPacketSize);
                UEP4_CTRL ^= bUEP_T_TOG;
                break;

            case UIS_EP_OUT(4):
                memcpy(unused, (PUINT8XV)USB_DMA, USB_RX_LEN);
                UEP4_CTRL ^= bUEP_R_TOG;
                break;

            case UIS_EP_IN(0):
                if ((setup.req & 0x80) == USB_REQ_TYP_IN) {
                    if ((setup.req & 0x7F) == USB_GET_DESCRIPTOR) {
                        UEP0_T_LEN = (UINT8)MIN(setup.len, bMaxPacketSize0);
                        memcpy(EP0Buf, setup.desc, UEP0_T_LEN);
                        setup.desc += UEP0_T_LEN;
                        setup.len -= UEP0_T_LEN;
                        UEP0_CTRL ^= bUEP_T_TOG;
                    }
                } else {
                    // Status Stage finished
                    if ((setup.req & 0x7F) == USB_SET_ADDRESS)
                        USB_DEV_AD = *(UINT8D *)&setup.len;
                    setup.req = 0xFF;
                    UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
                }
                break;

            case UIS_EP_OUT(0):
                if ((setup.req & 0x80) == USB_REQ_TYP_OUT) {
                    // Not possible unless SET_DESCRIPTOR
                    UEP0_CTRL ^= bUEP_R_TOG;
                } else {
                    // Status Stage finished
                    setup.req = 0xFF;
                    UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
                }
                break;

            case UIS_TOKEN_SETUP:
                // Avoid STALL because of slow Setup Packet handling
                UEP0_CTRL = UEP0_CTRL & ~MASK_UEP_T_RES | UEP_T_RES_NAK;
                setup.req = SetupPacket->bRequestType & 0x80 | SetupPacket->bRequest;
                switch (SetupPacket->bRequest) {
                case USB_GET_STATUS:
                    if ((SetupPacket->bRequestType & USB_REQ_RECIP_MASK) == USB_REQ_RECIP_DEVICE ||
                        (SetupPacket->bRequestType & USB_REQ_RECIP_MASK) == USB_REQ_RECIP_INTERF && SetupPacket->wIndexL == 0) {
                        EP0Buf[0] = 0x0;
                    } else if ((SetupPacket->bRequestType & USB_REQ_RECIP_MASK) == USB_REQ_RECIP_ENDP) {
                        if (!GET_CONFIGURED()) {
                            if (SetupPacket->wIndexL == 0)
                                EP0Buf[0] = 0x0;
                            else
                                goto REQUEST_ERROR;
                        } else {
                            switch (SetupPacket->wIndexL) {
                            case EP_IN(0):
                            case EP_OUT(0):
                                EP0Buf[0] = 0x0;
                                break;

                            case EP_IN(1):
                                EP0Buf[0] = !!((UEP1_CTRL & MASK_UEP_T_RES) == UEP_T_RES_STALL);
                                break;

                            case EP_OUT(1):
                                EP0Buf[0] = !!((UEP1_CTRL & MASK_UEP_R_RES) == UEP_R_RES_STALL);
                                break;

                            case EP_IN(2):
                                EP0Buf[0] = !!((UEP2_CTRL & MASK_UEP_T_RES) == UEP_T_RES_STALL);
                                break;

                            case EP_OUT(2):
                                EP0Buf[0] = !!((UEP2_CTRL & MASK_UEP_R_RES) == UEP_R_RES_STALL);
                                break;

                            case EP_IN(3):
                                EP0Buf[0] = !!((UEP3_CTRL & MASK_UEP_T_RES) == UEP_T_RES_STALL);
                                break;

                            case EP_OUT(3):
                                EP0Buf[0] = !!((UEP3_CTRL & MASK_UEP_R_RES) == UEP_R_RES_STALL);
                                break;

                            case EP_IN(4):
                                EP0Buf[0] = !!((UEP4_CTRL & MASK_UEP_T_RES) == UEP_T_RES_STALL);
                                break;

                            case EP_OUT(4):
                                EP0Buf[0] = !!((UEP4_CTRL & MASK_UEP_R_RES) == UEP_R_RES_STALL);
                                break;

                            default:
                                goto REQUEST_ERROR;
                            }
                        }
                    } else {
                        goto REQUEST_ERROR;
                    }

                    EP0Buf[1] = 0x0;
                    UEP0_T_LEN = 2;
                    UEP0_CTRL = bUEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;
                    break;

                case USB_CLEAR_FEATURE:
                    switch (SetupPacket->wValueL) {
                    case 0: // ENDPOINT_HALT
                        if (!GET_CONFIGURED())
                            goto REQUEST_ERROR;
                        
                        switch (SetupPacket->wIndexL) {
                        case EP_IN(1):
                            UEP1_CTRL = UEP1_CTRL & ~(MASK_UEP_T_RES | bUEP_T_TOG) | UEP_T_RES_ACK;
                            break;

                        case EP_OUT(1):
                            UEP1_CTRL = UEP1_CTRL & ~(MASK_UEP_R_RES | bUEP_R_TOG) | UEP_R_RES_ACK;
                            break;

                        case EP_IN(2):
                            UEP2_CTRL = UEP2_CTRL & ~(MASK_UEP_T_RES | bUEP_T_TOG) | UEP_T_RES_ACK;
                            break;

                        case EP_OUT(2):
                            UEP2_CTRL = UEP2_CTRL & ~(MASK_UEP_R_RES | bUEP_R_TOG) | UEP_R_RES_ACK;
                            break;

                        case EP_IN(3):
                            UEP3_CTRL = UEP3_CTRL & ~(MASK_UEP_T_RES | bUEP_T_TOG) | UEP_T_RES_ACK;
                            break;

                        case EP_OUT(3):
                            UEP3_CTRL = UEP3_CTRL & ~(MASK_UEP_R_RES | bUEP_R_TOG) | UEP_R_RES_ACK;
                            break;

                        case EP_IN(4):
                            UEP4_CTRL = UEP4_CTRL & ~(MASK_UEP_T_RES | bUEP_T_TOG) | UEP_T_RES_ACK;
                            break;

                        case EP_OUT(4):
                            UEP4_CTRL = UEP4_CTRL & ~(MASK_UEP_R_RES | bUEP_R_TOG) | UEP_R_RES_ACK;
                            break;

                        default:
                            goto REQUEST_ERROR;
                        }

                        UEP0_T_LEN = 0;
                        UEP0_CTRL = bUEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;
                        break;

                    case 1: // DEVICE_REMOTE_WAKEUP
                    case 2: // TEST_MODE
                    default:
                        goto REQUEST_ERROR;
                    }
                    break;

                case USB_SET_FEATURE:
                    switch (SetupPacket->wValueL) {
                    case 0: // ENDPOINT_HALT
                        if (!GET_CONFIGURED())
                            goto REQUEST_ERROR;
                        
                        switch (SetupPacket->wIndexL) {
                        case EP_IN(1):
                            UEP1_CTRL = UEP1_CTRL & ~MASK_UEP_T_RES | UEP_T_RES_STALL;
                            break;

                        case EP_OUT(1):
                            UEP1_CTRL = UEP1_CTRL & ~MASK_UEP_R_RES | UEP_R_RES_STALL;
                            break;

                        case EP_IN(2):
                            UEP2_CTRL = UEP2_CTRL & ~MASK_UEP_T_RES | UEP_T_RES_STALL;
                            break;

                        case EP_OUT(2):
                            UEP2_CTRL = UEP2_CTRL & ~MASK_UEP_R_RES | UEP_R_RES_STALL;
                            break;

                        case EP_IN(3):
                            UEP3_CTRL = UEP3_CTRL & ~MASK_UEP_T_RES | UEP_T_RES_STALL;
                            break;

                        case EP_OUT(3):
                            UEP3_CTRL = UEP3_CTRL & ~MASK_UEP_R_RES | UEP_R_RES_STALL;
                            break;

                        case EP_IN(4):
                            UEP4_CTRL = UEP4_CTRL & ~MASK_UEP_T_RES | UEP_T_RES_STALL;
                            break;

                        case EP_OUT(4):
                            UEP4_CTRL = UEP4_CTRL & ~MASK_UEP_R_RES | UEP_R_RES_STALL;
                            break;

                        default:
                            goto REQUEST_ERROR;
                        }

                        UEP0_T_LEN = 0;
                        UEP0_CTRL = bUEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;
                        break;

                    case 1: // DEVICE_REMOTE_WAKEUP
                    case 2: // TEST_MODE
                    default:
                        goto REQUEST_ERROR;
                    }
                    break;

                case USB_SET_ADDRESS:
                    // USB Address should be changed after handshake packet, so temporarily kept in len
                    *(UINT8D *)&setup.len = SetupPacket->wValueL;
                    UEP0_T_LEN = 0;
                    UEP0_CTRL = bUEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;
                    break;

                case USB_GET_DESCRIPTOR:
                    setup.len = (UINT16)SetupPacket->wLengthL << 0 | (UINT16)SetupPacket->wLengthH << 8;
                    
                    switch (SetupPacket->wValueH) {
                    case USB_DESCR_TYP_DEVICE:
                        if (SetupPacket->wValueL > 0)
                            goto REQUEST_ERROR;
                        setup.desc = DeviceDescriptor;
                        if (setup.desc[0] < setup.len)
                            setup.len = setup.desc[0];
                        break;

                    case USB_DESCR_TYP_CONFIG:
                        if (SetupPacket->wValueL > 0)
                            goto REQUEST_ERROR;
                        setup.desc = ConfigurationDescriptor;
                        // Assuming wTotalLength < 256
                        if (setup.desc[2] < setup.len)
                            setup.len = setup.desc[2];
                        break;

                    case USB_DESCR_TYP_STRING:
                        if (SetupPacket->wValueL >= STR_DESC_NUM)
                            goto REQUEST_ERROR;
                        setup.desc = StringDescriptors[SetupPacket->wValueL];
                        if (setup.desc[0] < setup.len)
                            setup.len = setup.desc[0];
                        break;
                    
                    case USB_DESCR_TYP_INTERF:
                    case USB_DESCR_TYP_ENDP:
                    case USB_DESCR_TYP_QUALIF:
                    case USB_DESCR_TYP_SPEED:
                    default:
                        goto REQUEST_ERROR;
                    }

                    UEP0_T_LEN = (UINT8)MIN(setup.len, bMaxPacketSize0);
                    memcpy(EP0Buf, setup.desc, UEP0_T_LEN);
                    setup.desc += UEP0_T_LEN;
                    setup.len -= UEP0_T_LEN;
                    UEP0_CTRL = bUEP_R_TOG | bUEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;
                    break;

                case USB_GET_CONFIGURATION:
                    EP0Buf[0] = !!GET_CONFIGURED();
                    UEP0_T_LEN = 1;
                    UEP0_CTRL = bUEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;
                    break;

                case USB_SET_CONFIGURATION:
                    switch (SetupPacket->wValueL) {
                    case 0:
                        CLR_CONFIGURED();
                        UEP1_CTRL = bUEP_AUTO_TOG | UEP_R_RES_STALL | UEP_T_RES_STALL;
                        UEP2_CTRL = bUEP_AUTO_TOG | UEP_R_RES_STALL | UEP_T_RES_STALL;
                        UEP3_CTRL = bUEP_AUTO_TOG | UEP_R_RES_STALL | UEP_T_RES_STALL;
                        UEP4_CTRL = UEP_R_RES_STALL | UEP_T_RES_STALL;
                        break;

                    case 1:
                        SET_CONFIGURED();
                        UEP1_T_LEN = wMaxPacketSize;
                        UEP1_CTRL = bUEP_AUTO_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;
                        UEP2_T_LEN = wMaxPacketSize;
                        UEP2_CTRL = bUEP_AUTO_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;
                        UEP3_T_LEN = wMaxPacketSize;
                        UEP3_CTRL = bUEP_AUTO_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;
                        UEP4_T_LEN = wMaxPacketSize;
                        UEP4_CTRL = UEP_R_RES_ACK | UEP_T_RES_ACK;
                        break;

                    default:
                        goto REQUEST_ERROR;
                    }

                    UEP0_T_LEN = 0;
                    UEP0_CTRL = bUEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;
                    break;

                case USB_GET_INTERFACE:
                    if (!GET_CONFIGURED() || SetupPacket->wIndexL != 0)
                        goto REQUEST_ERROR;
                    EP0Buf[0] = 0;
                    UEP0_T_LEN = 1;
                    UEP0_CTRL = bUEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;
                    break;

                case USB_SET_DESCRIPTOR:
                    // Unsupported, STALL at Status Stage
                    UEP0_CTRL = bUEP_R_TOG | UEP_R_RES_ACK | UEP_T_RES_STALL;
                    break;

                case USB_SET_INTERFACE: // STALL to use default alt settings
                case USB_SYNCH_FRAME:
                default:
                REQUEST_ERROR:
                    UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_STALL;
                }
            }
    }

    if (UIF_BUS_RST) {
        UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
        UEP1_CTRL = bUEP_AUTO_TOG | UEP_R_RES_STALL | UEP_T_RES_STALL;
        UEP2_CTRL = bUEP_AUTO_TOG | UEP_R_RES_STALL | UEP_T_RES_STALL;
        UEP3_CTRL = bUEP_AUTO_TOG | UEP_R_RES_STALL | UEP_T_RES_STALL;
        UEP4_CTRL = UEP_R_RES_STALL | UEP_T_RES_STALL;

        USB_DEV_AD = 0x00;
    }

    USB_INT_FG = 0xFF;
    TNOW = 0;
}

void main(void)
{
    Clock_Initialize();
    Port_Initialize();

    USBD_Initialize();

    EA = 1;

    for (;;) {
        if (USB_MIS_ST & bUMS_SUSPEND)
            PCON |= PD;
    }
}

离线

楼主 #4 2020-01-26 23:39:56

metro
会员
注册时间: 2019-03-09
已发帖子: 442
积分: 486

Re: CH558T USB功能实现+性能测试

大概解释一下程序的功能吧。
如上文所述,这是个USB性能测试程序。匹配的上位机程序实际上是来自于Cypress FX2(也就是大名鼎鼎的CY7C68013A的所属系列),名为CyStreamer。该程序有配套的CY7C68013A固件,如果买了相关开发板的可以直接烧录并测试。
本身测试程序的功能比较简单,就是扫描除了EP0外的可用Endpoints(包括Altenate Settings),并且通过模拟传输的方式测试速度(包括IN和OUT);另外,还通过P2.5(TNOW)输出当前USB的状态,在调用USBD_Initialize和USBD_ISR时将会置高以表示处于活动状态。该程序并没有包括回环测试(loopback),我个人认为loopback更适合用CDC类作为虚拟串口的方式实现,而这在官方demo中已经实现了。
当然,为了使用Cypress的程序,这里需要把VID和PID改一改,这样才能用上官方的驱动,从而可被CyUSB.dll调用。其它描述符也尽可能照着官方固件的改了。

写这个程序的目的呢,除了实现USB协议之外,其实也有探究CH55x的USB实现的想法。所以,这里设置了4套端点(IN和OUT都有),分别对应以下几种情况:

  • EP1 IN/OUT:双缓冲,ISR中无复制

  • EP2 IN/OUT:双缓冲,ISR中有复制(使用memcpy,下同)

  • EP3 IN/OUT:单缓冲,ISR中无复制

  • EP4 IN/OUT:单缓冲,ISR中有复制

首先说说双缓冲机制。很明显,这是为了提升传输速度用的:毕竟这种级别的单片机主频不太高且执行周期长,移动速度未必比USB FS本身的读写快多少,如果没有缓冲的话,复制就要占据大部分时间,从而在复制完成前没办法准备下一阶段的传输,这通常意味着下一次传输通常只能NAK。对于没有PING机制的全速设备而言,Bulk OUT尤其是问题(毕竟需要传输一整个Packet后才能答复NAK)。双缓冲机制可以通过允许提前准备下一阶段的传输,从而比较有效地解决这个问题。
那么,在单缓冲的情况下,要实现数据的搬移,就不得不和ISR扯上关系了——要么直接在ISR中复制数据,要么直接在ISR中置为NAK,等到正常执行的阶段准备好下一阶段的传输之后再置为ACK,而后者肯定比前者更慢。因此,EP1和EP4应该是最为常用的两种情况,而EP2和EP3则是作为对照组存在。
当然,如果ISR的处理速度够快(通常中断都是在本次传输结束后开始的,因此对应的是本次传输结束到下次传输开始,当然理论上下次传输的Token Phase时也是来得及操作的),其实还有一种方案:自己维护缓冲区(通常是环形缓冲),端点缓冲只使用单缓冲,并且在ISR中切换当前缓冲。下面的测试将会看到,即使是CH558这种级别的单片机,也是完全来得及的。

最近编辑记录 metro (2020-01-27 00:18:38)

离线

楼主 #5 2020-01-27 00:18:45

metro
会员
注册时间: 2019-03-09
已发帖子: 442
积分: 486

Re: CH558T USB功能实现+性能测试

下文将会按顺序说一下调试CH558T时踩到的坑。

首先,时钟和休眠之类的倒还好(除了一开始忘了SAFE_MOD这回事儿懵逼了半天)。为了支持USB Suspend,需要在WAKE_CTRL开启USB唤醒,不过可以不用在中断里面开启Suspend,在我的代码里面是直接在loop部分判断bUMS_SUSPEND了,这样的好处是保证在当前不活动的情况下再进入休眠模式,不会影响当前的工作。
USB SIE的重置是一个小坑。原本我是在USB接收到Reset中断时对SIE进行重置(USB_CTRL = bUC_RESET_SIE | bUC_CLR_ALL,当然还有相关的初始化),但是发现这样处理之后SIE就会进入无响应状态,可能是因为重置需要一定时间。将重置改成只在Initialize阶段进行就解决了。

离线

楼主 #6 2020-01-27 11:02:23

metro
会员
注册时间: 2019-03-09
已发帖子: 442
积分: 486

Re: CH558T USB功能实现+性能测试

昨天太晚了,起床继续。

接下来说一说一个很坑的点:UIS_TRANSFER和U_IS_NAK。
在datasheet中,只提到了UIS_TRANSFER表示传输完成,而U_IS_NAK表示接收到NAK。但是,众所周知,Host并不会发送NAK,所以这里应该是用设备发送NAK更为准确?另外,这个传输完成到底是什么意思?NAK和STALL算传输完成吗?无响应呢?
带着这些问题,我测试了一下各种搭配,结果令人震惊(夸张了哈):

  • 先说U_IS_NAK,很明显是在发送NAK的时候会置1,这倒没啥好惊讶的。当然,要实现这个功能,就应该事先置bUIE_DEV_NAK为1。

  • 比较令人困惑的是UIS_TRANSFER。在bUIE_DEV_NAK为0时,只有接收/发送ACK的时候置1,NAK和STALL没有反应(没有测试无响应,猜测和STALL一致);但是当bUIE_DEV_NAK为1时,如果接收/发送NAK,UIS_TRANSFER也会置1(STALL仍然没反应)。这不得不说是比较有趣的行为。

  • 另外,在bUIE_DEV_NAK为1,且对应端点的bUEP_AUTO_TOG为1时,如果使用设备的接收功能(对应Bulk OUT)会出现奇怪的bug,表现是U_IS_NAK没有正确设置,且自动切换TOG的功能错误(反应就是U_TOG_OK的值错误),如下图所示。但是,只要上述两个flag的任意一个为0(不使能NAK中断,或是手动切换TOG),则功能又恢复正常。而且,这个bug并不影响Bulk IN,或是没有发送NAK的情形。这一点也是令人困惑。

_20200127110737.png
鉴于以上原因,在使用CH558的USB功能时,我建议:

  1. 不要使用U_IS_NAK,也就是置bUIE_DEV_NAK为0。一般来说,对于NAK我们也不需要进行特殊的操作,而是让它重新传输一次就行了。

  2. 在不打开bUIE_DEV_NAK的时候,自动切换TOG还是好用的,当然手动切换也并不麻烦,只需要异或一下TOG就好了。

  3. 由于STALL信号不触发中断,因此对于Control Transfer而言,不能在Token为OUT(Host到Device方向)的时候设置Device为回复STALL,否则无法正确接收到下一个Setup包。正确的选择是,在需要表明Request Error的情况下,对于Control Read应当在Data Stage阶段回复STALL,而对于Control Write则只能在Status Stage阶段回复STALL。也就是说,只能令UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_STALL,而不能同时使用UEP_R_RES_STALL。

  4. 另外,在UEP0_CTRL的STALL设置优先级高于bUC_INT_BUSY发出的NAK,也就是说即使bUC_INT_BUSY为1,在ISR中仍然会优先发送STALL信号。由于STALL信号仅需发送一个周期(下一个周期应当恢复到Setup包),而Setup包本身的处理逻辑又十分复杂,在发送STALL表明Request Error之后(典型例子是全速设备的Device_Qualifier),如果接收到Setup包(对于SETUP Token正确回复ACK)并且按原有路径在ISR中处理Setup包,则会因为当前Setup包的处理时间过长,从而对于当前的Request错误地回复STALL(也就是说在ISR中已经经历了Setup和Data两个Stage),造成逻辑错误。对此,解决方案是在接收到Setup包时第一步先将UEP0_CTRL的STALL去掉(见case UIS_TOKEN_SETUP下一行),之后正常处理便是(bUC_INT_BUSY在此时会正确地回复NAK)。

_20200127113747.png

最近编辑记录 metro (2020-01-27 11:38:14)

离线

楼主 #7 2020-01-27 12:01:12

metro
会员
注册时间: 2019-03-09
已发帖子: 442
积分: 486

Re: CH558T USB功能实现+性能测试

最后说说CH558的USB缓冲机制。
经过测试发现,CH558实现的USB缓冲机制是最怂的方案:不管是否开了双缓冲,也不管发送/接收是否做了并行处理(也就是一边CPU复制一边USB传输),只要设置了回复NAK(不管是通过bUC_INT_BUSY还是手动修改UEPn_CTRL),且在数据传输阶段(即Data Phase)时当前中断仍处于活跃状态(可能更准确的说法是UIS_TRANSFER为1?不是很清楚内部机制),那么当前的Transaction就会自动回复NAK表示暂缓传输。
换句话说,所有基于ISR内复制的方案,单端点的最高速度都只能达到理论值的一半左右(一半时间用于回复NAK,当然不影响多个端点同时传输的情形)。(当然关掉bUC_INT_BUSY手动切换,在ISR中保证数据传输时不会出现冲突也是可行的,不过这样一来逻辑就复杂了,不如移到外面。)
好消息是我们总能保证数据是正确的,理论上不会出现新数据覆盖旧数据的情形。
不过,上面的结论从另一个方面证实了,只要ISR中的处理速度够快(大概100个周期还是够的?),把数据搬运的过程移到ISR之外,那么还是可以达到最高速度的。这也在某种程度上说明了双缓冲的优势是有局限性的,既然可以通过ISR中快速切换缓冲区的方式获得更大的缓冲区,那为什么还要用自带的双缓冲呢?因此,除非想好了确实有必要使用双缓冲,否则以CH558的硬件设计来看并没有很大必要。(猜测多缓冲可能更适合可以提前submit的架构,类似CY7C68013A?)
_20200127120204.png
(只要处理速度够快,NAK就追不上我,单缓冲也一样)

最近编辑记录 metro (2020-01-27 13:04:07)

离线

#8 2020-01-27 13:46:51

cityf
会员
注册时间: 2017-11-03
已发帖子: 195
积分: 195

Re: CH558T USB功能实现+性能测试

关注,祝愿热衷分享的楼主新年快乐!

离线

楼主 #9 2020-01-27 17:30:53

metro
会员
注册时间: 2019-03-09
已发帖子: 442
积分: 486

Re: CH558T USB功能实现+性能测试

感觉还是需要补充说明一下测试结果,其实尚未已经有答案了。
各个端点的测试结果如下(IN/OUT均一致):

  • EP1:~1000 KB/s

  • EP2:~500 KB/s

  • EP3:~1000 KB/s

  • EP4:~500 KB/s

所以,在此次测试中,对速度产生影响的主要因素还是在于搬移数据导致的ISR执行时间过长,从而每两个数据包会有一个返回NAK。单双缓冲则影响不大。
大家也可以自己用电脑测试一下结果。

离线

#10 2020-01-29 12:07:31

伍零壹
会员
注册时间: 2019-12-16
已发帖子: 157
积分: 40

Re: CH558T USB功能实现+性能测试

USB1.1? USB2.0 淘宝那种逻辑分析仪好像很多都分析不了

离线

#11 2020-01-29 12:19:55

smartcar
会员
注册时间: 2018-02-19
已发帖子: 735
积分: 735

Re: CH558T USB功能实现+性能测试

伍零壹 说:

USB1.1? USB2.0 淘宝那种逻辑分析仪好像很多都分析不了

淘宝山上的 Saleae16 可以分析 USB 1.1(USB 2.0 FullSpeed)

就是那种传输速率为12Mbps的协议.

QQ图片20200129121949.png

离线

楼主 #12 2020-01-29 15:05:47

metro
会员
注册时间: 2019-03-09
已发帖子: 442
积分: 486

Re: CH558T USB功能实现+性能测试

伍零壹 说:

USB1.1? USB2.0 淘宝那种逻辑分析仪好像很多都分析不了

USB 2.0是包括高速(High-speed 480 Mb/s)、全速(Full-speed 12 Mb/s)和低速(Low-speed 1.5 Mb/s)三种传输速度的,而USB 1.1只涵盖了后两种,也就是说后两种设备可以选择1.1和2.0两种标准予以实现。
要分析高速USB 2.0的话当然就不能这么搞了,首先要有足够的采样率,其次还要正确分析高速传输状态下的差分信号(±400 mV),没有相应PHY的话通常搞不来的。

最近编辑记录 metro (2020-01-29 15:10:06)

离线

#13 2020-01-29 22:04:39

ljbfly
会员
注册时间: 2017-12-07
已发帖子: 37
积分: 27

Re: CH558T USB功能实现+性能测试

厉害!!!在学习Ch552的例子

离线

#14 2020-08-09 11:46:40

echo
会员
注册时间: 2020-04-16
已发帖子: 348
积分: 353.5

Re: CH558T USB功能实现+性能测试

好贴帮顶,有专门的HS分析仪,利用USB2.0 PHY和FPGA来抓取数据,价格就不太美好了。

离线

#15 2020-12-18 19:08:28

sy373466062
会员
注册时间: 2018-11-12
已发帖子: 130
积分: 116

Re: CH558T USB功能实现+性能测试

Excellent analyzed on CH558

离线

页脚

工信部备案:粤ICP备20025096号 Powered by FluxBB

感谢为中文互联网持续输出优质内容的各位老铁们。 QQ: 516333132, 微信(wechat): whycan_cn (哇酷网/挖坑网/填坑网) service@whycan.cn