PIC24F/PIC32MX USB HUB Driver 2013.9.3 2014.1.13

    [Japanese][English]

    1.INTRODUCTION

    The Microchip driver for PIC24 acting as an USB host driver does not support multiple USB devices, even when connected via an USB HUB.

    Connecting USB devices (e.g. memory disk, web camera, mouse, keyboard, etc) via an USB hub is very useful. This is why I made a PIC24 driver USB driver able to work with an USB Hub. It is merged in the file "usb_host.c" provided by MicroChip.

    I modified the files "usb_host.c", "usb_host.h", "usb_host_local.h" contained in the directory Microchip\USB from "microchip_solutions_v2013-02-15.zip".

    Host module "usb_host.c" includes the hub driver. You can connect many devices to PIC24 using an USB Hub, replacing the files "usb_host.c", "usb_host.h" and "usb_host_local.h".


    2.HOW TO USE

    This USB host driver, which supports USB hub, does not necessarily request a USB hub. If a single USB device is connected to the PIC USB port, the USB driver can work as a single device host.

    Both USB 1.1 "Full-speed" hub and USB 2.0 "High-speed" hub can be connected. But USB ver2.0 hub will work only as USB ver1.1 because the PIC USB module is only "Full-speed"(USB 1.1).

    The current maximum number of ports which can be connected is 4. You can change the definition in usb_config.h, for instance to support a 6-port hub:


      #define USB_HUB_NUMBER_OF_DOWN_STREAM_PORTS    6

    All of Low-speed and Highspeed USB devices can be connect to the hub port. In addition, High speed USB devices can be connected to it if it works as Full-speed. Needless to say, both a host driver and a middle driver which support each USB device are necessary.

    "usbClientDrvTable[]" in usb_config.c and each device information must be described in usbTPL[] to use a middle driver. Any hub information description is not necessary in these tables because a driver is built-in.

    Because the hub driver is built-in, a hub can be connected to another hub port. But a cascade of hubs is not supported. The enumeration process works but after that the software is programmed to keep the cascaded hub in hold state and unmanaged.

    No cascaded hub should not be a limitation for most applications. But it you want to connect more, you can try to improve the driver software!

       2014.1.13


       I updated Hub driver to support multiple HUBs. Cascade connection of HUB is possible.

       When using multiple HUBs, the number of the sum totals of the port of each hub into usb_config.h must be defined. For example, When Using three Hubs. If each Hub has four ports, the number of the sum totals of ports is tewlve. Twelve must be set into usb_config.h as follows.

        #define USB_HUB_NUMBER_OF_DOWN_STREAM_PORTS    12




    3. How usb_host.c has been modified to support a USB HUB

    The explanations below assume a basic knowledge of The C Language Programming, of the USB communication and of USB programs provided by Microchip corp. Please excuse me if there is not all the information you need.

    3-1. to support multiple devices

    First, the host driver had to be modified to support multiple devices because multiple devices are connected via the USB Hub. Furthermore, a driver for the USB Hub itself had to be developped.

    The original usb_host.c driver program only supports a single device. It is good to modify it as a little as possible to minimize efforts but also to keep it reliable.

    The external interface must fit the existing ones and this is somewhat difficult to achieve.

    I thought it was better to modify the variables which manage the connected devices into arrays (file usb_host.c). The number of elements of the array variables is "the number of hub ports + 2".

     The following is the head of modified usb_host.c.

      
      // These should all be moved into the USB_DEVICE_INFO structure.
      //static BYTE                          countConfigurations;
      //static BYTE                          numCommandTries; 
      //static BYTE                          numEnumerationTries;
      //static volatile WORD                 numTimerInterrupts;
      //static volatile USB_ENDPOINT_INFO   *pCurrentEndpoint;
      //BYTE                                *pCurrentConfigurationDescriptor    = NULL;
      //BYTE                                *pDeviceDescriptor                  = NULL;
      //static BYTE                         *pEP0Data                           = NULL;
      //static volatile WORD                 usbHostState; 
      //volatile WORD                        usbOverrideHostState;
      
      // this macro defined in usb_config.h
      #ifndef USB_HUB_NUMBER_OF_DOWN_STREAM_PORTS
          // support default Hub it has one up stream port, 4 down stream ports.
          #define USB_HUB_NUMBER_OF_DOWN_STREAM_PORTS 4
      #endif
      
      #define USB_MAX_ADDRESS              (USB_HUB_NUMBER_OF_DOWN_STREAM_PORTS + 1)
      
      static BYTE                          countConfigurations[USB_MAX_ADDRESS+1]; 
      static BYTE                          numCommandTries[USB_MAX_ADDRESS+1];
      static BYTE                          numEnumerationTries[USB_MAX_ADDRESS+1];
      static volatile WORD                 numTimerInterrupts[USB_MAX_ADDRESS+1];
      static volatile USB_ENDPOINT_INFO   *pCurrentEndpoint[USB_MAX_ADDRESS+1];
      BYTE                                *pCurrentConfigurationDescriptor[USB_MAX_ADDRESS+1];
      BYTE                                *pDeviceDescriptor[USB_MAX_ADDRESS+1];
      static volatile WORD                 usbHostState[USB_MAX_ADDRESS+1];
      static BYTE                         *pEP0Data[USB_MAX_ADDRESS+1];
      static USB_DEVICE_INFO               usbDeviceInfo[USB_MAX_ADDRESS+1];
      volatile WORD                        usbOverrideHostState[USB_MAX_ADDRESS+1];
      

    As all this data is moved into the USB_DEVICE_INFO structure, I thought that it was better that the different variables from countConfigurations to usbOverrideHostState should be put into a usbDeviceInfo[] structure. But because this would have strongly impacted the code, I only changed the existing variables to arrays.

    Like that, I needed only to insert "[deviceAddress]" at the end of each variable (replacement function of the source editor!).

    Device addresses are static and not dynamic: the device address of the hub is set to 1, the one of the hub port 1 is set to 2, the one of the hub port 2 is set to 3 and the one of the hub port n is set to "n+1".

    These addresses are used for the array index. In the source file, the index variables of the array is named "deviceAddress" as often as possible.

    When the USB hub is connected, it is managed as usbDeviceInfo[1] and the device which is connected to the Hub port 4 is managed as usbDeviceInfo[5]. Even if only port4 is connected, the device addresses of 1 and 5 are used.

    The device address is therefore the same as the array index value and the usbDeviceInfo[].deviceAddress variables. The setting is after the SET_ADDRESS request during the enumeration. When in the DETACHED state or just before the setting of the enumeration address, the value is 0.

    A device, whose array index is 1, is either one is directly connected to the PIC USB port or a USB hub.

    In the initial state, STATE_DETACHED is set in usbHostSTate[] for all devices. In USBHostTasks() process, all the process of for() loop is done for all devices.

      
        // Call Hub Function
        if(flgHubAttached)
        {
            _USB_HubTasks();
        }
      
        for(deviceAddress = 1; deviceAddress <= USB_MAX_ADDRESS; deviceAddress++)
        {
          // See if we got an interrupt to change our state.
          if (usbOverrideHostState[deviceAddress] != NO_STATE)
          {
              usbHostState[deviceAddress] = usbOverrideHostState[deviceAddress];
              usbOverrideHostState[deviceAddress] = NO_STATE;
          }
      
          //-------------------------------------------------------------------------
          // Main State Machine
      
          switch (usbHostState[deviceAddress] & STATE_MASK)
          {
              case STATE_DETACHED:
                  switch (usbHostState[deviceAddress] & SUBSTATE_MASK)
                  {
                      case SUBSTATE_INITIALIZE:
                          // We got here either from initialization or from the user
                          // unplugging the device at any point in time.
                          // Turn off the module and free up memory.
                          USBHostShutdown(deviceAddress);
                          DEBUG_PutString( "HOST: Initializing DETACHED state.\r\n" );
      

    After these modifications of the driver sources, it apparently supports multiple USB devices connection. But the initial setting of a device (after the process of an attached device detection) is different for the USB hub or a device directly connected to the PIC than compared to the devices connected to the USB Hub ports.

    In the former one, the device reset process is done in the host driver but in the later one, it is not neccesarry because the hub has already resetted the devices. In short in the later one, the reset process must be skipped in USBHostTasks().

    When the hub driver detects the connection of a device to a port, the state of the port is changed from STATE_DETACHED to STATE_ATTACHED. For this, usbHostsState[port+1] is set to STATE_ATTACHED.

    Also, in USBHostTasks(), the process from STATE_ATTACHED get worked for a device which is connected to the port.

    Because any device which deviceAddress is greater than or equal to 2 is connected to the USB Hub port, usbHostState[deviceAddress] is set to SUBSTATE_GET_DEVICE_DESCRIPTOR_SIZE; the device reset process is then skipped in the state which is next to SUBSTATE_SETTLE. After this step, the enumeration is processed as same as a normal process.

      
      case STATE_ATTACHED:
          switch (usbHostState[deviceAddress] & SUBSTATE_MASK)
          {
              case SUBSTATE_SETTLE:
                  if(deviceAddress >= 2) // HUB downstream port connect device.
                  {
                      // Initialize the USB Device information
                      usbDeviceInfo[deviceAddress].currentConfiguration      = 0;
                      usbDeviceInfo[deviceAddress].attributesOTG             = 0;
                      usbDeviceInfo[deviceAddress].flags.val                 = 0;
                      _USB_InitErrorCounters(deviceAddress);
                      // See if the device is low speed.
                      if(portDeviceLowSpeed)
                      {
                          DEBUG_PutString( "HOST: HUB Port Device Low Speed!\r\n" );
                          usbDeviceInfo[deviceAddress].flags.bfIsLowSpeed    = 1;
                          usbDeviceInfo[deviceAddress].deviceAddressAndSpeed = 0x80;
                      }
                      // Jump to GET_DESCRIPTOR_SIZE state
                      usbHostState[deviceAddress] = STATE_ATTACHED | SUBSTATE_GET_DEVICE_DESCRIPTOR_SIZE;
                      break;
                 }
      
                 // Wait 100ms for the insertion process to complete and power
                 // at the device to be stable.
                 switch (usbHostState[deviceAddress] & SUBSUBSTATE_MASK)
                 {
                     case SUBSUBSTATE_START_SETTLING_DELAY:
                         DEBUG_PutString( "HOST: Starting settling delay.\r\n" );
      

    In other places, the processing is different between the device with address 1 and the others with addresses greater than or equal to 2: there are some "if(deviceAddress >=2) ... ".

    When a device is disconnected from the hub port, the HUB driver detects it, its usbHostState[port+1] is set to STATE_DETACHED and the state is changed to DETACHED.

    On the other hand, when the whole USB hub is disconnected from the PIC USB port, the detach process must be processed for both the USB hub and all its devices (all the devices which are connected to the hub ports get unaccessible). So for any detach of the device directly connected to the PIC USB port, all devices states are set to STATE_DETACHED in the interrupt process.

      
           // -----------------------------------------------------------------------
          // Detach ISR
          if (U1IEbits.DETACHIE && U1IRbits.DETACHIF)
          {
          //  DEBUG_PutChar( ']' );
      
              U1IR                    = USB_INTERRUPT_DETACH;
              U1IEbits.DETACHIE       = 0;
      
              for(deviceAddress = 1; deviceAddress <= USB_MAX_ADDRESS; deviceAddress++)
                  usbOverrideHostState[deviceAddress] = STATE_DETACHED;
      
              #ifdef  USB_SUPPORT_OTG
                  //If HNP Related Detach Detected, Process Disconnect Event
                  USB_OTGEventHandler (0, OTG_EVENT_DISCONNECT, 0, 0 );
      
                  //If SRP Related D+ Low and SRP Is Active, Process D+ Low Event
                  USB_OTGEventHandler (0, OTG_EVENT_SRP_DPLUS_LOW, 0, 0 );
      
                  //Disable HNP, Detach Interrupt Could've Triggered From Cable Being Unplugged
                  USBOTGDisableHnp();
              #endif
          }
      


    3-2. to support Low-speed devices

    In the original host driver program, a Low-speed device like a mouse is supported when connected to the PIC USB port directly. When such a Low-speed device is connected via the USB hub, it can't communicate as is.

    The steps to communicate with a Low-speed device via Full-speed hub of USB1.1 are the following: First, a PRE token is transmitted in Full-speed to inform the hub to initiate the communication in Low-speed. Second, a SETUP token is transmitted in Low-speed. So hub can relay the Low-speed signal to the port where a Low-speed device is connected to.

    In short, the PRE token process is necessary in the communication with a Low-speed device. But in the PIC Reference manual, only three messages (SETUP/IN/OUT) are supported. A mouse and keyboard cannot be managed without a PRE token. When I read the PIC24F Reference Manual in more detail, I found the following descriptions in the explanation of the LSPD bit of the register U1EP0 bit 7.

      
      bit 7 LSPD: Low-Speed Direct Connection Enable bit (UEP0 only)(1,2)
      1 = Direct connection to a low-speed device enabled
      0 = Direct connection to a low-speed device disabled
      
      Note 1: These bits are available only for U1EP0 and only in Host mode. For all other U1EPn registers, these bits
      are always unimplemented and read as ‘0’.
      2: Clear this bit if the USB module is operating in Host mode and connected to a low-speed device through a
      hub.
      

    No description of a PRE token is in the manual. But when the device address whose bit 7 is set to 1 is written in the U1ADDR register and the bit7(LSPD bit) of U1EP0 is set to 0 along the descriptions of the manual, they can communicate. It seems that the PRE token is transmitted automatically.

    It would be nice to improve the PIC32 Reference manual, not only explaining that "0=Direct connection to a low-speed device disabled; Hub required with PRE_PID" but also that the PRE token is transmitted automatically.


    3-3.The resource assignment of USB bus

    The management of a USB bus is necessary because multiple devices request transfer of a control, an interrupt, an isochronous data and bulk data independently.

    It is necessary that the priority of a control transfer be the highest and that the bulk transfer works only when the USB bus is not busy.

    In the original program, all of the interrupt process of SOF, _USB_FinedNextToken() function and the interrupt process of transfer completions work. But it is only for one device.

    So I make the following 4 functions for multiple devices. (at the end of usb_host.c)

      void _USB_FindService(BYTE transferType);

        Check the end points for all devices which request the transfer that is designated by transfer Type. The results are set in the global array ServiceEndpoint[].


      void _USB_FrameFirstTransaction(void);

        This function is called from a SOF interrupt routine every 1 msec. It calls _USB_FindService() and checks the end points of transfer requests in order of a control transfer, an interrupt transfer, isochronous transfer and bulk transfer.

        When no end point of a transfer request is found, it just ends. When an end point of a transfer request is found, it clears the usbBusInfo.wBytesSentInFrame variable, calls _USB_FrameNextTransaction() and initiate the communication.


      void _USB_FrameNextTransaction(void)

        Retrieve an end point of a transfer request from the ServiceEndpoint[] array, call _USB_AdvanceFindNextToken() and request the communication process for the end point.

        _USB_AdvanceFindNextToken() function returns TRUE when it waits for an interrupt of a transfer completion when a USB communication has been initiated. It returns FALSE when it is not so (ex. the communication is completed). So the function just ends when TURN is returned and waits for a call from an interrupt of a transfer completion.

        When FALSE is returned, it checks the next end point. When processes of all end points of a transfer request are completed, it ends.


      BOOL _USB_AdvanceFindNextToken(BYTE deviceAddress, BYTE transferType);

        Initiate(token transfer) and complete the communication of an end point of a device which is designated in deviceAddress according to a transfer state.

        Calculate the integration process of usbBusInfo.wBytesSentInFrame which manages bytes of transfered signals. When it exceeds USB_FRAME_MAX_TRANSFER_SIZE(MAX transfer size per a frame), the next transfer request is transfered to the next USB frame.

          USB_FRAME_MAX_TRANSFER_SIZE is set to 1,350 bytes which is 90% of the MAX transfer bytes per frame to take overhead into consideration.

        An isochronous transfer is only one time per frame on the USB spec. But a bulk transfer is repeated within one frame until the size of transfered bytes reaches the MAX size of bytes to use the USB bus more efficiently.
        When a transfer is started and waits for the transfer completion interrupt, it returns TRUE. When it is not so (ex. a transfer completion), it returns FALSE.


    4. The USB Hub driver

    Because the hub is also one a USB device, I made the following two functions like the functions for other devices at the beginning of usb_host.c.

      - _USB_HubInit() : Initialization process.
      - _USB_HubTasks() : Hub process.

    4-1.Initialization of a hub

    At the end of a device enumeration process, it checks a class of the connected device. If it is a hub class device, it calls _USB_HubInit() function.

      
          case SUBSUBSTATE_INIT_CLIENT_DRIVERS:
              DEBUG_PutString( "HOST: Initializing client drivers...\r\n" );
              _USB_SetNextState(deviceAddress);
      
              // if connected first device is Hub, then Hub initialize.
              if(deviceAddress == 1 && pDeviceDescriptor[deviceAddress][4] == USB_HUB_CLASSCODE)
              {
                  if(!_USB_HubInit(deviceAddress))
                  {
                      _USB_SetErrorCode( deviceAddress, USB_HOLDING_CLIENT_INIT_ERROR );
                      _USB_SetHoldState(deviceAddress);
                  }
                  break;
              }
      
              // Initialize client driver(s) for this configuration.
              if (usbDeviceInfo[deviceAddress].flags.bfUseDeviceClientDriver)
              {
                  // We have a device that requires only one client driver.  Make sure
                  // that client driver can initialize this device.  If the client
                  // driver initialization fails, we cannot enumerate this device.
                  DEBUG_PutString( "HOST: Using device client driver.\r\n" );
      
                  temp = usbDeviceInfo[deviceAddress].deviceClientDriver;
                  if (!usbClientDrvTable[temp].Initialize(usbDeviceInfo[deviceAddress].deviceAddress,
                                                                     usbClientDrvTable[temp].flags, temp))
                  {
                      _USB_SetErrorCode( deviceAddress, USB_HOLDING_CLIENT_INIT_ERROR );
                      _USB_SetHoldState(deviceAddress);
                  }
               }
      

    In the initialization process of USB_HubInit(), the number of hub ports is gotten with reading HUB_DESCRIPTOR from a hub. After that, the PowerON process is done for all ports.

    At the end, the flag flgHubAttached is set: it indicates that a hub is connected and it ends the initialization process.


    4-2.The control of a hub

    When "flgHubAttached" is set, _USB_HubTasks() is called from the application every time when USBHostTasks() is called. _USB_HubTasks() is a state machine and it is processed depending on a HubState variable.

    Main function of _USB_HubTasks() is to watch the hub status in the interrupt transfer and to process any port whose state has changed.

    When a USB device is connected to a hub port, because the port is noticed in Hub State, it reads the status of the port, clears the change bit and processes the reset of the device connected. After that, it checks that the port is enabled and proceeds to the attaching process of the device. In case that a device is disconnected, it proceeds to the detaching process.

    In the attaching process and the detaching process, it simply sets usbHostState[port+1] of the port to STATE_ATTACHED or STATE_DETACHED.

    Because the power management of a USB bus and USB Hub is not checked, it must not exceed the PIC USB port power except when a powered USB hub is used.



    5. Sample Project (add this item 2013.11.22)

    5-1. usb_hub_demo

    This sample project connects one mouse, one camera and one memory disk to PIC32MX via HUB.


    5-2. usb_hub_hid_demo

    This sample project connects some numbers of HID devices (mouse, keyboard and joystick) to PIC32MX via HUB.
    If a little source program is changed, it is applicable to PIC24FJ.

    It can connect via HUB and can also work two or more mice and two joysticks or more.
    Of course, it is also possible to connect one mouse, one keyboard and one joystick via HUB.


    Note:1. This page translated the Japanese page and was written by Mr. Kawamura.
    He's my best friend. I can never thank him enough.
    2. Mr. Dupriez reviewed this english page. I am thankful also to him. (2013.9.25 Suwa)