ch55xでUSBのプログラムをするには


ch552を使ってUSBを扱うことができるようになったが,ch55xduinoを使って他の用途への適用を容易にできるようにしたいと考えていた. そこで,WCHの公式とch554_sdcc_usb_blinkyとch55xduinoにおけるUSBのプログラムの違いについて比較してみた.

まず,最も違うのがdescriptorの書き方である. WCHでは,直接数値の羅列として指定しているので,修正するためには知識が必要である. blinkyでは,独自に構造体を定義して,それを使っている. ch55xduinoでは,LUFAのdescriptorで定義された構造体などを使っている. WCHが作ったCH554.Hにも,descriptor用の構造体が定義されており,上記のいずれの場合にもこれを取り込んでいるのだが,その構造体はあまり利用されていない. 16bitの変数のbyteの順番の問題があるかも知れないが,この構造体をうまく活用できると良いのでは無いかと思われる. また,string descriptorについても,それぞれ苦労して書いているが,それらを統合したこんな書き方はどうかなと思う.

__code uint16_t ManufacturerString[] = {
  ( sizeof(ManufacturerString) | (USB_DESCR_TYP_STRING << 8)),
  'w', 'c', 'h', '.', 'c', 'n' };

byte数やunicodeの0を自然な形でうまく処理できていると思う. これをマクロの形にすると,もっとスッキリするかも知れない. 一番難しいのが,構造が複雑なconfiguration descriptorであるが,vendor spacificな場合には,それほど複雑な構造にする必要は無いように思う. interfaceの数とそれに含まれるEnd pointの数を固定すると,比較的簡単な構造体が定義できる.

次に違うのが,割り込みの処理の中の分岐の仕方である. WCHとblinkyでは,switch caseを使って,長々と書いていて,多重にネストされているので,どの処理がどこに対応するのか,非常に分かりにくい. 一方,ch55xduinoでは,in,out,sof,setupの処理をそれぞれのend pointに分けてサブルーチンを呼ぶようにしていて,少しスッキリしている. しかし,end point 0のsetupの処理が必然的に非常に長くなって,読みづらい. ここの処理を,standardとclassとvendorに分けて,後ろの二つを別のサブルーチンにすると良いのでは無いかと思う.

これらの方針の元に,vendor specificな機器用のプログラムを書きやすくするルーチンを作ってみた. USBの基本的な処理のためのルーチンは,USBhandler.hに書いてあり,descriptorやendpointの設定と処理をメインのinoファイルに書くようにした.

USBhandler.hの内容は,以下の通りである.

#ifndef __USB_HANDLER_H__
#define __USB_HANDLER_H__

void EP0_OUT_Callback();
void EP1_OUT_Callback();
void EP2_OUT_Callback();
void EP3_OUT_Callback();
void EP4_OUT_Callback();
void EP0_SOF_Callback();
void EP1_SOF_Callback();
void EP2_SOF_Callback();
void EP3_SOF_Callback();
void EP4_SOF_Callback();
void EP0_IN_Callback();
void EP1_IN_Callback();
void EP2_IN_Callback();
void EP3_IN_Callback();
void EP4_IN_Callback();
void EP0_SETUP_Callback();
void EP1_SETUP_Callback();
void EP2_SETUP_Callback();
void EP3_SETUP_Callback();
void EP4_SETUP_Callback();
uint8_t EP0_DATA_Callback();
uint8_t EP0_VENDOR_Callback();
uint8_t EP0_CLASS_Callback();
void USB_RESET_Callback();

#define UsbSetupBuf ((PUSB_SETUP_REQ)Ep0Buffer)
__data uint16_t SetupLen;
__data uint8_t SetupReq;
volatile __xdata uint8_t UsbConfig;
volatile __xdata uint8_t UsbInterface;
__code uint8_t *__data pDescr;

void EP0_SETUP_Callback() {
  __data uint8_t len = USB_RX_LEN;
  if (len == (sizeof(USB_SETUP_REQ))) {
    SetupLen = ((uint16_t)UsbSetupBuf->wLengthH << 8) | (UsbSetupBuf->wLengthL);
    len = 0; // Default is success and upload 0 length
    SetupReq = UsbSetupBuf->bRequest;
    if ((UsbSetupBuf->bRequestType & USB_REQ_TYP_MASK) != USB_REQ_TYP_STANDARD)
    {
      switch ((UsbSetupBuf->bRequestType & USB_REQ_TYP_MASK)) {
      case USB_REQ_TYP_VENDOR: len = EP0_VENDOR_Callback(); break;
      case USB_REQ_TYP_CLASS:  len = EP0_CLASS_Callback(); break;
      default: len = 0xFF; break;
      }
    } else // Standard request
    {
      switch (SetupReq) // Request ccfType
      {
      case USB_GET_DESCRIPTOR:
        switch (UsbSetupBuf->wValueH) {
        case 1:
          pDescr = (__code uint8_t *) DeviceDescriptor;
          len = pDescr[0];
          break;
        case 2:
          pDescr = (__code uint8_t *) ConfigurationDescriptor;
          len = sizeof( ConfigurationDescriptor );
          break;
        case 3: // String Descriptor
          switch (UsbSetupBuf->wValueL) {
#ifdef USB_STRING_0
	  case 0:
            pDescr = (__code uint8_t *) USB_STRING_0 ;
	    len = pDescr[0];
	    break;
#endif
#ifdef USB_STRING_1
	  case 1:
	    pDescr = (__code uint8_t *) USB_STRING_1 ;
	    len = pDescr[0];
	    break;
#endif
#ifdef USB_STRING_2
	  case 2:
            pDescr = (__code uint8_t *) USB_STRING_2 ;
            len = pDescr[0];
	    break;
#endif
#ifdef USB_STRING_3
	  case 3:
            pDescr = (__code uint8_t *) USB_STRING_3 ;
            len = pDescr[0];
	    break;
#endif
#ifdef USB_STRING_4
	  case 4:
            pDescr = (__code uint8_t *) USB_STRING_4 ;
            len = pDescr[0];
	    break;
#endif
#ifdef USB_STRING_5
	  case 5:
            pDescr = (__code uint8_t *) USB_STRING_5 ;
            len = pDescr[0];
	    break;
#endif
#ifdef USB_STRING_6
	  case 6:
            pDescr = (__code uint8_t *) USB_STRING_6 ;
            len = pDescr[0];
	    break;
#endif
	  default: len = 0xff; break;
          }
          break;
        default: len = 0xff; break;
        }
        if (len != 0xff) {
          if (SetupLen > len) SetupLen = len; // Limit length
          len = (SetupLen >= DEFAULT_ENDP0_SIZE) ? DEFAULT_ENDP0_SIZE : SetupLen;
          for (__data uint8_t i = 0; i < len; i++) Ep0Buffer[i] = pDescr[i];
          SetupLen -= len;
          pDescr += len;
        }
        break;
      case USB_SET_ADDRESS:
        SetupLen = UsbSetupBuf->wValueL; // Save the assigned address
        break;
      case USB_GET_CONFIGURATION:
        Ep0Buffer[0] = UsbConfig;
        if (SetupLen >= 1) len = 1;
        break;
      case USB_SET_CONFIGURATION:
        UsbConfig = UsbSetupBuf->wValueL;
        break;
      case USB_GET_INTERFACE:
        Ep0Buffer[0] = UsbInterface;
        if (SetupLen >= 1) len = 1;
        break;
      case USB_SET_INTERFACE:
        UsbInterface = UsbSetupBuf->wValueL;
        break;
      case USB_CLEAR_FEATURE: // Clear Feature
        if ((UsbSetupBuf->bRequestType & 0x1F) ==
            USB_REQ_RECIP_DEVICE) // Clear the device featuee.
        {
          if ((((uint16_t)UsbSetupBuf->wValueH << 8) | UsbSetupBuf->wValueL) == 0x01)
          {
            if (ConfigurationDescriptor.cfg_descr.bmAttributes & 0x20) {
              // wake up
            } else {
              len = 0xFF; // Failed
            }
          } else {
            len = 0xFF; // Failed
          }
        } else if ((UsbSetupBuf->bRequestType & USB_REQ_RECIP_MASK) ==
                   USB_REQ_RECIP_ENDP) // endpoint
        {
          switch (UsbSetupBuf->wIndexL) {
          case 0x84:
            UEP4_CTRL = UEP4_CTRL & ~(bUEP_T_TOG | MASK_UEP_T_RES) | UEP_T_RES_NAK;
            break;
          case 0x04:
            UEP4_CTRL = UEP4_CTRL & ~(bUEP_R_TOG | MASK_UEP_R_RES) | UEP_R_RES_ACK;
            break;
          case 0x83:
            UEP3_CTRL = UEP3_CTRL & ~(bUEP_T_TOG | MASK_UEP_T_RES) | UEP_T_RES_NAK;
            break;
          case 0x03:
            UEP3_CTRL = UEP3_CTRL & ~(bUEP_R_TOG | MASK_UEP_R_RES) | UEP_R_RES_ACK;
            break;
          case 0x82:
            UEP2_CTRL = UEP2_CTRL & ~(bUEP_T_TOG | MASK_UEP_T_RES) | UEP_T_RES_NAK;
            break;
          case 0x02:
            UEP2_CTRL = UEP2_CTRL & ~(bUEP_R_TOG | MASK_UEP_R_RES) | UEP_R_RES_ACK;
            break;
          case 0x81:
            UEP1_CTRL = UEP1_CTRL & ~(bUEP_T_TOG | MASK_UEP_T_RES) | UEP_T_RES_NAK;
            break;
          case 0x01:
            UEP1_CTRL = UEP1_CTRL & ~(bUEP_R_TOG | MASK_UEP_R_RES) | UEP_R_RES_ACK;
            break;
          default: len = 0xFF; break;
          }
        } else {
          len = 0xFF; // Unsupported for non-endpoint
        }
        break;
      case USB_SET_FEATURE: // Set Feature
        if ((UsbSetupBuf->bRequestType & 0x1F) ==
            USB_REQ_RECIP_DEVICE) // Set  the device feature.
        {
          if ((((uint16_t)UsbSetupBuf->wValueH << 8) | UsbSetupBuf->wValueL) ==
              0x01) {
            if (ConfigurationDescriptor.cfg_descr.bmAttributes & 0x20) {
              // suspend

              // while ( XBUS_AUX & bUART0_TX );    //Wait till uart0 sending
              // complete SAFE_MOD = 0x55; SAFE_MOD = 0xAA; WAKE_CTRL =
              // bWAK_BY_USB | bWAK_RXD0_LO | bWAK_RXD1_LO; //wake up by USB or
              // RXD0/1 signal PCON |= PD; //sleep SAFE_MOD = 0x55; SAFE_MOD =
              // 0xAA; WAKE_CTRL = 0x00;
            } else {
              len = 0xFF; // Failed
            }
          } else {
            len = 0xFF; // Failed
          }
        } else if ((UsbSetupBuf->bRequestType & 0x1F) ==
                   USB_REQ_RECIP_ENDP) // endpoint
        {
          if ((((uint16_t)UsbSetupBuf->wValueH << 8) | UsbSetupBuf->wValueL) ==
              0x00) {
            switch (((uint16_t)UsbSetupBuf->wIndexH << 8) |
                    UsbSetupBuf->wIndexL) {
            case 0x84:
              UEP4_CTRL = UEP4_CTRL & (~bUEP_T_TOG) | UEP_T_RES_STALL;
              break;
            case 0x04:
              UEP4_CTRL = UEP4_CTRL & (~bUEP_R_TOG) | UEP_R_RES_STALL;
              break;
            case 0x83:
              UEP3_CTRL = UEP3_CTRL & (~bUEP_T_TOG) | UEP_T_RES_STALL;
              break;
            case 0x03:
              UEP3_CTRL = UEP3_CTRL & (~bUEP_R_TOG) | UEP_R_RES_STALL;
              break;
            case 0x82:
              UEP2_CTRL = UEP2_CTRL & (~bUEP_T_TOG) | UEP_T_RES_STALL;
              break;
            case 0x02:
              UEP2_CTRL = UEP2_CTRL & (~bUEP_R_TOG) | UEP_R_RES_STALL;
              break;
            case 0x81:
              UEP1_CTRL = UEP1_CTRL & (~bUEP_T_TOG) | UEP_T_RES_STALL;
              break;
            case 0x01:
              UEP1_CTRL = UEP1_CTRL & (~bUEP_R_TOG) | UEP_R_RES_STALL;
            default: len = 0xFF; break;
            }
          } else {
            len = 0xFF; // Failed
          }
        } else {
          len = 0xFF; // Failed
        }
        break;
      case USB_GET_STATUS:
        Ep0Buffer[0] = 0x00;
        Ep0Buffer[1] = 0x00;
        len = (SetupLen >= 2) ? 2 : SetupLen;
        break;
      default: len = 0xff; break;
      }
    }
  } else {
    len = 0xff; // Wrong packet length
  }
  if (len == 0xff) {
    SetupReq = 0xFF;
    UEP0_CTRL = bUEP_R_TOG | bUEP_T_TOG | UEP_R_RES_STALL | UEP_T_RES_STALL; // STALL
  } else if (len <= DEFAULT_ENDP0_SIZE) // Tx data to host or send 0-length packet
  {
    UEP0_T_LEN = len;
    UEP0_CTRL = bUEP_R_TOG | bUEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK; // Expect DATA1
  } else {
    UEP0_T_LEN = 0; // Tx data to host or send 0-length packet
    UEP0_CTRL = bUEP_R_TOG | bUEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK; // Expect DATA1
  }
}

void EP0_IN_Callback() {
  switch (SetupReq) {
  case USB_GET_DESCRIPTOR: {
    __data uint8_t len = (SetupLen >= DEFAULT_ENDP0_SIZE) ? DEFAULT_ENDP0_SIZE : SetupLen;
    for (__data uint8_t i = 0; i < len; i++) Ep0Buffer[i] = pDescr[i];
    SetupLen -= len;
    pDescr += len;
    UEP0_T_LEN = len;
    UEP0_CTRL ^= bUEP_T_TOG; // Switch between DATA0 and DATA1
  } break;
  case USB_SET_ADDRESS:
    USB_DEV_AD = USB_DEV_AD & bUDA_GP_BIT | SetupLen;
    UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
    break;
  default:
    UEP0_T_LEN = 0; // End of transaction
    UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
    break;
  }
}

void EP0_OUT_Callback() {
  if (UEP0_T_LEN = EP0_DATA_Callback()) {
      UEP0_CTRL |= UEP_R_RES_ACK | UEP_T_RES_ACK; // send packet
  } else {
    UEP0_T_LEN = 0;
    UEP0_CTRL |= UEP_R_RES_ACK | UEP_T_RES_NAK; // Respond Nak
  }
}

#pragma save
#pragma nooverlay
void USBInterrupt(void) { // inline not really working in multiple files in SDCC
  if (UIF_TRANSFER) {
    // Dispatch to service functions
    __data uint8_t callIndex = USB_INT_ST & MASK_UIS_ENDP;
    switch (USB_INT_ST & MASK_UIS_TOKEN) {
    case UIS_TOKEN_OUT: { // SDCC will take IRAM if array of function pointer is
                          // used.
      switch (callIndex) {
      case 0: EP0_OUT_Callback(); break;
      case 1: EP1_OUT_Callback(); break;
      case 2: EP2_OUT_Callback(); break;
      case 3: EP3_OUT_Callback(); break;
      case 4: EP4_OUT_Callback(); break;
      default: break;
      }
    } break;
    case UIS_TOKEN_SOF: { // SDCC will take IRAM if array of function pointer is
                          // used.
      switch (callIndex) {
      case 0: EP0_SOF_Callback(); break;
      case 1: EP1_SOF_Callback(); break;
      case 2: EP2_SOF_Callback(); break;
      case 3: EP3_SOF_Callback(); break;
      case 4: EP4_SOF_Callback(); break;
      default: break;
      }
    } break;
    case UIS_TOKEN_IN: { // SDCC will take IRAM if array of function pointer is
                         // used.
      switch (callIndex) {
      case 0: EP0_IN_Callback(); break;
      case 1: EP1_IN_Callback(); break;
      case 2: EP2_IN_Callback(); break;
      case 3: EP3_IN_Callback(); break;
      case 4: EP4_IN_Callback(); break;
      default: break;
      }
    } break;
    case UIS_TOKEN_SETUP: { // SDCC will take IRAM if array of function pointer
                            // is used.
      switch (callIndex) {
      case 0: EP0_SETUP_Callback(); break;
      case 1: EP1_SETUP_Callback(); break;
      case 2: EP2_SETUP_Callback(); break;
      case 3: EP3_SETUP_Callback(); break;
      case 4: EP4_SETUP_Callback(); break;
      default: break;
      }
    } break;
    }

    UIF_TRANSFER = 0; // Clear interrupt flag
  }

  // Device mode USB bus reset
  if (UIF_BUS_RST) {
    UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
    UEP1_CTRL = bUEP_AUTO_TOG |
                UEP_T_RES_NAK; // Endpoint 1 automatically flips the sync flag,
                               // and IN transaction returns NAK
    UEP2_CTRL = bUEP_AUTO_TOG | UEP_T_RES_NAK |
                UEP_R_RES_ACK; // Endpoint 2 automatically flips the sync flag,
                               // IN transaction returns NAK, OUT returns ACK
    // UEP4_CTRL = UEP_T_RES_NAK | UEP_R_RES_ACK;  //bUEP_AUTO_TOG only work for
    // endpoint 1,2,3

    USB_DEV_AD = 0x00;
    UIF_SUSPEND = 0;
    UIF_TRANSFER = 0;
    UIF_BUS_RST = 0; // Clear interrupt flag

    UsbConfig = 0;

    USB_RESET_Callback();
  }

  // USB bus suspend / wake up
  if (UIF_SUSPEND) {
    UIF_SUSPEND = 0;
    if (USB_MIS_ST & bUMS_SUSPEND) { // Suspend

      // while ( XBUS_AUX & bUART0_TX );                    // Wait for Tx
      // SAFE_MOD = 0x55;
      // SAFE_MOD = 0xAA;
      // WAKE_CTRL = bWAK_BY_USB | bWAK_RXD0_LO;    // Wake up by USB or RxD0
      // PCON |= PD; // Chip sleep SAFE_MOD = 0x55; SAFE_MOD = 0xAA; WAKE_CTRL =
      // 0x00;

    } else {             // Unexpected interrupt, not supposed to happen !
      USB_INT_FG = 0xFF; // Clear interrupt flag
    }
  }
}
#pragma restore

void USBDeviceCfg() {
  USB_CTRL = 0x00;            // Clear USB control register
  USB_CTRL &= ~bUC_HOST_MODE; // This bit is the device selection mode
  USB_CTRL |= bUC_DEV_PU_EN | bUC_INT_BUSY |
              bUC_DMA_EN; // USB device and internal pull-up enable,
                          // automatically return to NAK before interrupt flag
                          // is cleared during interrupt
  USB_DEV_AD = 0x00;      // Device address initialization
  //     USB_CTRL |= bUC_LOW_SPEED;
  //     UDEV_CTRL |= bUD_LOW_SPEED; //Run for 1.5M
  USB_CTRL &= ~bUC_LOW_SPEED;
  UDEV_CTRL &= ~bUD_LOW_SPEED; // Select full speed 12M mode, default mode
#if defined(CH551) || defined(CH552) || defined(CH549)
  UDEV_CTRL = bUD_PD_DIS; // Disable DP/DM pull-down resistor
#endif
#if defined(CH559)
  UDEV_CTRL = bUD_DP_PD_DIS; // Disable DP/DM pull-down resistor
#endif
  UDEV_CTRL |= bUD_PORT_EN; // Enable physical port
}

void USBDeviceIntCfg() {
  USB_INT_EN |= bUIE_SUSPEND;  // Enable device hang interrupt
  USB_INT_EN |= bUIE_TRANSFER; // Enable USB transfer completion interrupt
  USB_INT_EN |= bUIE_BUS_RST;  // Enable device mode USB bus reset interrupt
  USB_INT_FG |= 0x1F;          // Clear interrupt flag
  IE_USB = 1;                  // Enable USB interrupt
  EA = 1;                      // Enable global interrupts
}

#endif

inoファイルの例は,以下の通りである.

#define DEFAULT_ENDP0_SIZE 16
#define MAX_PACKET_SIZE 64

#include <Arduino.h>

#if (DEFAULT_ENDP0_SIZE+2*MAX_PACKET_SIZE) > USER_USB_RAM
#error "This example needs more USB ram. Increase this setting in menu."
#endif

__xdata __at (0x0) uint8_t Ep0Buffer[DEFAULT_ENDP0_SIZE]; //Endpoint 0 OUT&IN
__xdata __at (DEFAULT_ENDP0_SIZE) uint8_t Ep1Buffer[MAX_PACKET_SIZE]; // OUT
__xdata __at (DEFAULT_ENDP0_SIZE+MAX_PACKET_SIZE) uint8_t Ep1inBuffer[MAX_PACKET_SIZE]; // IN
#define NUMBER_OF_EP 2
#define NUMBER_OF_INTERF 1

__code USB_DEV_DESCR DeviceDescriptor =
{    
  sizeof(USB_DEV_DESCR), USB_DESCR_TYP_DEVICE,
  0x00, 0x02,             // USB Spec Release Number in BCD format (L,H)
  USB_DEV_CLASS_VEN_SPEC, // Class Code
  0, 0,                   // Subclass code, Protocol code
  DEFAULT_ENDP0_SIZE,     // Max packet size for EP0
  0x48, 0x43,             // Vendor ID (L,H)
  0x37, 0x55,             // Product ID (L,H)
  0x00, 0x00,             // Device release number in BCD format (L,H)
  1, 2, 3,                // string index for Manufacturer, Product, Device Serial Number
  1,                      // Number of possible configurations
};
#define USB_STRING_0 LanguageString
__code uint16_t LanguageString[] = {
  ( sizeof(LanguageString) | (USB_DESCR_TYP_STRING << 8)),
  0x0409 };
#define USB_STRING_1 ManufacturerString
__code uint16_t ManufacturerString[] = {
  ( sizeof(ManufacturerString) | (USB_DESCR_TYP_STRING << 8)),
  'w', 'c', 'h', '.', 'c', 'n' };
#define USB_STRING_2 ProductString
__code uint16_t ProductString[] = {
  ( sizeof(ProductString) | (USB_DESCR_TYP_STRING << 8)),
  'v','e','n','d','o','r' };
#define USB_STRING_3 SerialString
__code uint16_t SerialString[] = {
  ( sizeof(SerialString) | (USB_DESCR_TYP_STRING << 8)),
    '1', '2', '3', '4', '5' };

typedef struct _USB_INF_DESCR_LONG {
 USB_ITF_DESCR itf_descr;
 USB_ENDP_DESCR endp_descr[NUMBER_OF_EP];
} USB_ITF_DESCR_LONG;
__code struct {
 USB_CFG_DESCR cfg_descr;
 USB_ITF_DESCR_LONG itf_descr[NUMBER_OF_INTERF];
} ConfigurationDescriptor =
{
 {sizeof(USB_CFG_DESCR), USB_DESCR_TYP_CONFIG, sizeof(ConfigurationDescriptor),0,
  NUMBER_OF_INTERF, // Number of interfaces in this cfg
  1,   // Index value of this configuration
  4,   // Configuration string index
  0x80,// Attributes
  100, // Max power consumption (2X mA)
 }, // confuguration descriptor
 {
  {
   {sizeof(USB_ITF_DESCR), USB_DESCR_TYP_INTERF,
    0,            // Interface Number
    0,            // Alternate Setting Number
    NUMBER_OF_EP, // Number of endpoints in this intf
    USB_DEV_CLASS_VEN_SPEC,
    0,0,   // Subclass code, Protocol code
    0,     // Interface string index
   }, // interface descriptor
   {
    {sizeof(USB_ENDP_DESCR), USB_DESCR_TYP_ENDP,
     1+USB_ENDP_DIR_MASK*0,// Endpoint Address (*0:OUT, *1:IN)
     USB_ENDP_TYPE_BULK,   // Attributes (CTRL/ISOCH/BULK/INTER)
     MAX_PACKET_SIZE,0,    // Max Packet Size (L,H)
     0,    //Interval
    }, // endpoint descriptor
    {sizeof(USB_ENDP_DESCR), USB_DESCR_TYP_ENDP,
     1+USB_ENDP_DIR_MASK*1,// Endpoint Address (*0:OUT, *1:IN)
     USB_ENDP_TYPE_BULK, // Attributes (CTRL/ISOCH/BULK/INTER)
     MAX_PACKET_SIZE,0,  // Max Packet Size (L,H)
     0,    //Interval
    }, // endpoint descriptor
   }, // endpoint descriptors (NUMBER_OF_EP)
  }, // interface descriptor with endpoint descriptors
 }, // interface descriptors (NUMBER_OF_INTERF)
};
#define USB_STRING_4 InterfaceString
__code uint16_t InterfaceString[] = {
  ( sizeof(InterfaceString) | (USB_DESCR_TYP_STRING << 8)),
    'V','e','n','d','e','r' };

void USBDeviceEndPointCfg() {
#if defined(CH559)
  // CH559 use different endianness for these registers
  UEP0_DMA_H = ((uint16_t)Ep0Buffer >> 8); // Endpoint 0/4 data transfer address
  UEP0_DMA_L = ((uint16_t)Ep0Buffer >> 0); // Endpoint 0/4 data transfer address
  UEP1_DMA_H = ((uint16_t)Ep1Buffer >> 8); // Endpoint 1 data transfer address
  UEP1_DMA_L = ((uint16_t)Ep1Buffer >> 0); // Endpoint 1 data transfer address
//  UEP2_DMA_H = ((uint16_t)Ep2Buffer >> 8); // Endpoint 2 data transfer address
//  UEP2_DMA_L = ((uint16_t)Ep2Buffer >> 0); // Endpoint 2 data transfer address
//  UEP3_DMA_H = ((uint16_t)Ep3Buffer >> 8); // Endpoint 3 data transfer address
//  UEP3_DMA_L = ((uint16_t)Ep3Buffer >> 0); // Endpoint 3 data transfer address
#else
  UEP0_DMA = (uint16_t)Ep0Buffer; // Endpoint 0/4 data transfer address
  UEP1_DMA = (uint16_t)Ep1Buffer; // Endpoint 1 data transfer address
//  UEP2_DMA = (uint16_t)Ep2Buffer; // Endpoint 2 data transfer address
//  UEP3_DMA = (uint16_t)Ep3Buffer; // Endpoint 3 data transfer address
#endif
// AUTO_TOG:automatically flips the sync flag, R:OUT, T:IN
  UEP0_CTRL = UEP_T_RES_NAK | UEP_R_RES_ACK;
  UEP1_CTRL = bUEP_AUTO_TOG | UEP_T_RES_NAK | UEP_R_RES_ACK;
  UEP2_CTRL = bUEP_AUTO_TOG | UEP_T_RES_NAK | UEP_R_RES_ACK;
  UEP3_CTRL = bUEP_AUTO_TOG | UEP_T_RES_NAK | UEP_R_RES_ACK;
  UEP4_CTRL = bUEP_AUTO_TOG | UEP_T_RES_NAK | UEP_R_RES_ACK;
// bUEPn_RX_EN:OUT, bUEPn_TX_EN:IN, bUEPn_BUF_MOD:double buffer(n:1-3)
  UEP2_3_MOD = bUEP2_RX_EN | bUEP2_TX_EN; // | bUEP3_RX_EN | bUEP3_TX_EN;
  UEP4_1_MOD = bUEP1_RX_EN | bUEP1_TX_EN; // | bUEP4_RX_EN | bUEP4_TX_EN;
//Pre-use send length must be cleared
  UEP0_T_LEN = UEP1_T_LEN = UEP2_T_LEN = UEP3_T_LEN = UEP4_T_LEN = 0;
}

#include "USBhandler.h"

// Out
//void EP1_OUT_Callback(){}
void EP2_OUT_Callback(){}
void EP3_OUT_Callback(){}
void EP4_OUT_Callback(){}
// SOF
void EP0_SOF_Callback(){}
void EP1_SOF_Callback(){}
void EP2_SOF_Callback(){}
void EP3_SOF_Callback(){}
void EP4_SOF_Callback(){}
// IN
//void EP1_IN_Callback(){}
void EP2_IN_Callback(){}
void EP3_IN_Callback(){}
void EP4_IN_Callback(){}
// SETUP
void EP1_SETUP_Callback(){}
void EP2_SETUP_Callback(){}
void EP3_SETUP_Callback(){}
void EP4_SETUP_Callback(){}
// EP0 custom
//uint8_t EP0_VENDOR_Callback(){return 0xff;}
uint8_t EP0_CLASS_Callback(){return 0xff;}
//uint8_t EP0_DATA_Callback(){return 0;}
// RESET
//void USB_RESET_Callback(){}

volatile __xdata uint8_t writing = false;
volatile __xdata uint8_t reading = false;
volatile __xdata uint8_t ready = false;
volatile __xdata uint16_t len=0;
volatile __xdata uint16_t sum=0;

void USB_RESET_Callback(){
  writing=false; reading = false; ready = false;
  len=0; sum=0;
}
uint8_t EP0_VENDOR_Callback(){
  uint16_t value;
  value=UsbSetupBuf->wValueH; value=(value<<8) | UsbSetupBuf->wValueL;
  switch(SetupReq){
  case 0 : reading=true; return 0; break;
  case 1 : *Ep0Buffer=writing; return 1; break;
  case 2 : *Ep0Buffer=reading; return 1; break;
  case 3 : *Ep0Buffer=ready; return 1; break;
  case 4 : Ep0Buffer[0]=len & 0xff; Ep0Buffer[1]=len>>8; return 2; break;
  case 5 : Ep0Buffer[0]=sum & 0xff; Ep0Buffer[1]=sum>>8; return 2; break;
  case 6 : return len; break;
  default: return 0xFF; // Command not supported
  }
  return 0;
}
uint8_t EP0_DATA_Callback(){
  len = USB_RX_LEN;
  switch(SetupReq){
  case 1 : *Ep0Buffer+=1; break;
  case 2 : *Ep0Buffer+=2; break;
  default: *Ep0Buffer+=0xff;
  }
}
void EP1_OUT_Callback(){
  len=0;
  if ( U_TOG_OK ) { // Out-of-sync packets will be discarded
//  UEP1_CTRL ^= bUEP_R_TOG; // Automatically flipped
    len = USB_RX_LEN;
    UEP1_CTRL = (UEP1_CTRL & ~MASK_UEP_R_RES) | UEP_R_RES_NAK; // NAK after receive
    writing=true;
  }
  ready=false;
}
void EP1_IN_Callback() {
  UEP1_T_LEN = 0;
  UEP1_CTRL = (UEP1_CTRL & ~MASK_UEP_T_RES) | UEP_T_RES_NAK; // NAK after send
  ready=false;
}

void setup() {
  delay(5);
  USBDeviceCfg();         // Device mode configuration
  USBDeviceEndPointCfg(); // Endpoint configuration
  USBDeviceIntCfg();      // Interrupt configuration
}

void loop() {
  uint16_t pos=0;
  if(writing && !ready){
    for(pos=0;pos<len;pos++) Ep1inBuffer[pos]=Ep1Buffer[pos];
    sum+=len;
    if(len<MAX_PACKET_SIZE) writing=false; // end of data
    else ready=true; // ready for OUT
    UEP1_CTRL = (UEP1_CTRL & ~MASK_UEP_R_RES) | UEP_R_RES_ACK; // receive next data
  }
  if(reading && !ready){
    for(pos=0;pos<MAX_PACKET_SIZE;pos++){
      if(pos==sum){ reading=false; break; }
    }
    sum-=pos;
    UEP1_T_LEN = len = pos;
    UEP1_CTRL  = (UEP1_CTRL & ~MASK_UEP_T_RES) | UEP_T_RES_ACK; // transmit
    ready=true; // data ready for IN
  }
}

control transferでSetupReq=1でreadingの値を0以外に設定すると,送信済のdataが送り返されてくるという,単純なプログラムである. ino中では,descriptorの定義を必要に応じて変更して,callbackの定義を追加することによって,様々な処理を行わせることができる.

今度,ch55xを使ってUSBデバイスを作成する際には,使ってみようと思う. ch55xduinoを使ってvendor specificなUSBデバイスの作成をしたいと思っている人の役に立つと良いな.