技術者見習いのメモ書き

電子工作や神社めぐりなどの趣味の話を中心に書いていきます。 Twitter:@hakura_riku

USB機能付き格安マイコンCH552TのUSBシリアル通信プログラム解説

はじめに

前回の記事ではUSBディスクリプタの解説を主におこないました. 今回の記事ではCH552TのUSBシリアルプログラムの解説をおこなっていきます.

USB通信の構造

アプリケーションレベルのUSB通信では仮想の通信線を「パイプ」と呼び, 必要に応じてパイプの数を設定します. パイプを使った通信ではホスト(パソコンなど)からデータを転送することを「OUT」, デバイス(マイコンなど)からデータを転送することを「IN」と呼びます. アプリケーションレベルでの通信構造を図に示します.

f:id:hakura03:20190430152826p:plain
アプリケーションレベルの通信構造

(参考文献[3] より引用)

USBデバイスを接続した直後はデフォルトパイプ(エンドポイント0)でコントロール転送のみが可能な状態となっています. コントロール転送を用いてコンフィギュレーション(ディスクリプタを用いた初期設定)を行い, デバイスの使用方法を設定します. 設定後はエンドポイントが追加され, パイプが構築されます[3].

USBSerialプログラムについて

USBSerialのプログラムはUSB_Serial.cに書かれています.

github.com

分かりづらい部分

はじめに, このプログラムを最初に見たときに私が分からなかった部分を解説します.

以下に示すプログラムはKeil C51でメモリの絶対アドレスを指定するためのコードです. ここではエンドポイントという通信用のFIFOバッファのメモリを確保しています. SDCCで同じプログラムをコンパイルする場合はメモリ指定の書き方が異なるので注意してください.

UINT8X   Ep0Buffer[THIS_ENDP0_SIZE] _at_ 0x0000;
UINT8X  Ep2Buffer[2*MAX_PACKET_SIZE] _at_ 0x0008;
UINT8X  Ep1Buffer[MAX_PACKET_SIZE] _at_ 0x00a0;

以下に示すプログラムは「UsbSetupBuf」の定義です. CH554.Hで定義している型「PUSB_SETUP_REQ」を用いて, 先ほど定義したメモリ「Ep0Buffer」のデータをキャストしています. このプログラムでは「PUSB_SETUP_REQ」で指定されている名称が出てくるのでプログラムを見る際には「PUSB_SETUP_REQ」を確認しておくといいです.

#define UsbSetupBuf     ((PUSB_SETUP_REQ)Ep0Buffer)

数値の送信方法

紹介したシリアル通信プログラムではASCIIコードでの通信を前提としています. つまり, ASCIIコードに則った数字は送受信できますが, 純粋な数値を送受信することはできません. 数値を送信するためには以下に示す変換プログラムを用いる必要があります. このプログラムは数値を「i_data」に入れると, ASCIIの文字コードに変換して「str_t」に入れてくれるプログラムです. このプログラムはUSB_Serial.c内に書かれているのでそれを使用してください.

void u16str(UINT16 i_data, char *str_t)
{
    int n,i;
    if(str_t)
    {
        str_t[0] = '\0';
        n = 0;
        
        do
        {
            i = n;
            do
            {
                str_t[i+1] = str_t[i];
            } while (i--);
            str_t[0] = 0x30+(i_data % 10);
            i_data /= 10;
            n++;
        } while (i_data);
    }
}

USBの割り込み処理

CH552TをパソコンのUSBポートに接続すると, 始めに「case UIS_TOKEN_SETUP | 0:」(エンドポイント0を使ったコントロール転送)に入りデバイスの設定をおこないます. 「case UIS_TOKEN_SETUP | 0:」内では「UsbSetupBuf」に入った値に応じた初期設定をおこないます. この設定を完了すると他のエンドポイントが使用可能になります.

「case UIS_TOKEN_OUT | 2: 」,「case UIS_TOKEN_IN | 2: 」はエンドポイント2で実際の通信処理をおこなうための部分となっています.

void USBInterrupt( void ) interrupt INT_NO_USB using 1                       
{   
    UINT8 len; 
    if(UIF_TRANSFER)                                                           
   {
    switch (USB_INT_ST & (MASK_UIS_TOKEN | MASK_UIS_ENDP))
    {
          case UIS_TOKEN_OUT | 2:                                                              
                        LEN = USB_RX_LEN; 
                       RecieveData();
                      //EcoSendData(RecBuf);             
                        break;
           case UIS_TOKEN_IN | 2:                                                 
                      UEP2_T_LEN = 0;                                                                            
                     UEP2_CTRL = UEP2_CTRL & ~ MASK_UEP_R_RES | UEP_R_RES_ACK;                           
                        UEP2_CTRL = UEP2_CTRL & ~ MASK_UEP_T_RES | UEP_T_RES_NAK;            
                        break;
            case UIS_TOKEN_SETUP | 0:                                                 
            len = USB_RX_LEN;
            if(len == (sizeof(USB_SETUP_REQ)))
            {   
       SetReqtp = UsbSetupBuf->bRequestType;
               SetupLen = UsbSetupBuf->wLengthL;
                            
               len = 0;                                                                                                                        
               SetupReq = UsbSetupBuf->bRequest;
               if(SetReqtp == 0xc0)
                           {
                                  Ep0Buffer[0] = DataBuf[num];
                                  Ep0Buffer[1] = DataBuf[num+1];
                                  len = 2;
                                  if(num<24)
                                  { 
                                    num += 2;
                                    }
                                    else
                                    {
                                        num = 24;
                                    }
                           }
                         else if(SetReqtp == 0x40)
                           {
                                len = 9;                                                        
                           }
                           else
                           { 
                                switch(SetupReq)                                                
                                {
                                     case USB_GET_DESCRIPTOR:
                                                switch(UsbSetupBuf->wValueH)
                                                {
                                                       case 1:                                              
                                                                 pDescr = DevDesc;                                
                                                                 len = sizeof(DevDesc);                                    
                                                       break;    
                                                       case 2:                                                              
                                                                 pDescr = CfgDesc;                                
                                                                 len = sizeof(CfgDesc);
                                                       break;   
                                                       default:
                                                                 len = 0xff;                                      
                                                       break;
                                                 }
                                             if ( SetupLen > len ) SetupLen = len;                  
                                             len = SetupLen >= 8 ? 8 : SetupLen;                    
                                             memcpy(Ep0Buffer,pDescr,len);                          
                                             SetupLen -= len;
                                             pDescr += len;
                                               break;                        
                                    case USB_SET_ADDRESS:
                                               SetupLen = UsbSetupBuf->wValueL;                       
                                               break;
                                    case USB_GET_CONFIGURATION:
                                             Ep0Buffer[0] = UsbConfig;
                                             if ( SetupLen >= 1 ) len = 1;
                                             break;
                                    case USB_SET_CONFIGURATION:
                                             UsbConfig = UsbSetupBuf->wValueL;
                                             break;
                                    default:
                                               len = 0xff;                                            
                                               break;    
                                   }
                            }
                      }
                        else
                        {
                                len = 0xff;                                                     
                        }

                          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 <= 8)                                                   
                          {
                                  UEP0_T_LEN = len;
                                  UEP0_CTRL = bUEP_R_TOG | bUEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;  
                          }
                          else
                          {
                                  UEP0_T_LEN = 0;                                                       
                                  UEP0_CTRL = bUEP_R_TOG | bUEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;                       
                          }
                        break;
                 case UIS_TOKEN_IN | 0:                                                         //endpoint0 IN
                          switch(SetupReq)
                          {
                               case USB_GET_DESCRIPTOR:
                                      len = SetupLen >= 8 ? 8 : SetupLen;                               
                                            memcpy( Ep0Buffer, pDescr, len );                                 
                                            SetupLen -= len;
                                            pDescr += len;
                                            UEP0_T_LEN = len;
                                            UEP0_CTRL ^= bUEP_T_TOG;                                          
                                      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;                                                
                                      UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
                                      break;
                          }
                          break;
                 case UIS_TOKEN_OUT | 0:                                                 // endpoint0 OUT
                            len = USB_RX_LEN;
                            UEP0_T_LEN = 0;                                                    
                            UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_ACK;                                                          
                          break;
                    default:
                          break;
                }
                UIF_TRANSFER = 0;                                         
    }
    if(UIF_BUS_RST)                                             
    {
            USB_DEV_AD = 0x00;
            UIF_SUSPEND = 0;
            UIF_TRANSFER = 0;
            UIF_BUS_RST = 0;                                     
    }
      if (UIF_SUSPEND) 
        {                                                      
            UIF_SUSPEND = 0;
            if ( USB_MIS_ST & bUMS_SUSPEND ) 
            {                                                                          
                while ( XBUS_AUX & bUART0_TX );                                          
                SAFE_MOD = 0x55;
                SAFE_MOD = 0xAA;
                WAKE_CTRL = bWAK_BY_USB | bWAK_RXD0_LO;                                  
                PCON |= PD;                                                              
                SAFE_MOD = 0x55;
                SAFE_MOD = 0xAA;
                WAKE_CTRL = 0x00;
            }
    } 
      else 
      {                                                                            
          USB_INT_FG = 0x00;                                                         
      }      
}

おわりに

シリアル通信プログラムについての解説をおこないました. 次回はUSBマウスのプログラムについて紹介します.

参考文献

[1]べーた, ‘‘Cerevo TechBlog [21日目]激安中華USBマイコンは使えるのか, ” https://tech-blog.cerevo.com/archives/6068/.

[2]江苏沁恒股份有限公司, ‘‘8 位增强型USB 单片机CH552、CH551,” https://datasheet.lcsc.com/szlcsc/Jiangsu-Qin-Heng-CH552T_C111367.pdf.

[3]USB通信プログラミングテクニック, ‘‘USBの基本アーキテクチャ, ” http://www.picfun.com/usb02.html.