Getting video stream from USB web-camera on Arduino Due - Part 4: USB module ...

:

Introduction

Part 3 is here.

In this part I'll make quick introduction in USB 2.0, I'll state this project assumptions that simplifies USB host implementation. I'll do Arduino's USB module initialization and start writing USB interrupt handler. 

Briefly about USB 2.0

USB bus is very popular because it is easy to use and there are plenty supported devices out there. But this ease of use for end users comes with high complexity for developers. Although Atmel's SAM3X8E takes care of low level things, we still need to understand USB basics in order to program SAM3X8E's USB module to perform functionality we are interested in:

  • There can be one host, optional hubs and one or more devices connected to the bus. My simplification: I will not use any hubs, my USB device (camera) will be directly connected to Arduino Due board.
  • Bus can operate on 3 different speeds: low, full and high. My simplification: speed is always high (HS).
  • Devices can have one or more functions. For example, a camera can have video and audio functions.  My simplification: I will deal with video functions only.
  • A device reports about itself with descriptors. I'm going to read them to understand what I can do with a particular device. Formats of those descriptors are defined in USB specification, in UVC specification and by device manufacturers. My simplification: I will not read vendor (device manufacturer) defined descriptors.
  • To operate a device, it must be connected to a bus, its descriptors read and it must be configured according to information in those descriptors. Device address must be assigned.
  • Host communicates with device's endpoints. There is always a default endpoint zero in any USB device. Each endpoint has its unique number. There is also term "pipe" which means a logical combination of device address and endpoint number. Host establishes pipes.
  • There are different types of endpoints: control, bulk, interrupt and isochronous. Device setup is performed via control endpoint, video-stream comes via isochronous endpoints.
  • Control endpoint can be IN and OUT. Other endpoints must be either IN or OUT.
  • Endpoints are grouped into interfaces. For example, video camera can have 2 interfaces: one for control and one for streaming.
  • Host communicates with a endpoint using transactions. A transaction will consist of a token, optional data and optional handshake (depending on endpoint type). I will explain in details about particular transaction when I code it later in the articles. All sequences are specified in paragraph 8.5 "Transaction Packet Sequences" [2, p.209], illustrated nicely by help of state machines.

Arduino Due's USB module

USB is split in several layers: mechanical, electrical and protocol layer. Mechanical is defined in Chapter 6 [2, p.85], cables and Arduino's connectors take care about it. Electrical is about electric levels on USB bus, bit-stuffing, sync pattern, etc. SAM3X8E will take care about it. So what we need to handle is protocol layer, SAM3X8E fully complies with Chapter 9 "Protocol layer" [2, p.195] thus it's highly recommended to read it.

SAM3X8E USB module is actually a bit more than just USB, it is OTG (On-The-Go) which means that it can be a host and a device in the same time. OTG is a new amendment to original standard. It is created for such cases as connecting printer to phone or to photo-camera, or connecting USB-memory stick to phone. In the past phone was always a device. Now it can also be a host if some other permitted device is connected to it. 

I will force SAM3X8E to be a host only, no OTG.

SAM3X8E USB module is called UOTGHS (USB On-The-Go High Speed), it is described in [1, p 1069]. Please read it too.

Before I move to next section let me create additional files in our project:

  • USB_Specification.h - here will go all standard structures and constants.
  • HCD.h and HCD.c - here will go all functions that deal with SAM3X8E UOTGHS hardware (host)
  • USBD.h and USBD.c - here will go all functions that read descriptors, set configuration, etc. (driver)

USB module initialization

Open HCD.h and add following declaration:

void HCD_Init(void);

Then I'll implement it in HCD.c file.

According to UOTGHS dependencies [1, p.1073] control of pins UOTGID (used to determine connection A or B-plug) and UOTGVBOF (USB bus power line) must be transferred from PIO controller to UOTGHS module. They both are peripheral A. As HOST mode will be forced upon UOTGHS, plug determination pin will make no sense thus I will not write any code for it. Also, no PIO interrupts are needed as UOTGHS has its own interrupts:

//Sets up VBOF pin - takes it out of PIO control
PIOB->PIO_IDR |= PIO_PB10A_UOTGVBOF;            //Disables interrupt
PIOB->PIO_ABSR &= ~PIO_PB10A_UOTGVBOF;          //Peripheral A selected
PIOB->PIO_PDR |= PIO_PB10A_UOTGVBOF;            //Moves pin control from PIO controller to Peripheral

Next thing to do is to enable USB clock generation. It is very similar to what is done in part 1 for PLLA clock to generate 84MHz, but here we need to generate 480MHz and clock name is UPLL:

//Enabling USB clock
PMC->CKGR_UCKR = CKGR_UCKR_UPLLCOUNT(3) | CKGR_UCKR_UPLLEN;   //UPLL enabling
while (0 == (PMC->PMC_SR & PMC_SR_LOCKU));                    //wait until clock is ready

Once clock is enabled, supply its signal into UOTGHS module:

//Enabling UOTGHS peripheral clock
PMC->PMC_PCER1 |= (1u << (ID_UOTGHS - 32));                   //PCER1 is used because ID > 32

Next is to force HOST mode upon UOTGHS module:

//Clearing bit UIDE which means: The USB mode (device or host) is selected from the UIMOD bit.
UOTGHS->UOTGHS_CTRL &= ~UOTGHS_CTRL_UIDE;    
UOTGHS->UOTGHS_CTRL &= ~UOTGHS_CTRL_UIMOD;                    //Force HOST mode

Note the use of UOTGHS_CTRL (control) register [1, p.1108]. It controls various general settings that apply to the whole UOTGHS module.

Next is to specify which level (low or high) makes USB bus power ON or OFF. If we look at Arduino Due schematic UOTGVBOF controls switch (FET-transistor) via one bipolar transistor and one operational amplifier. Low level makes bus power ON, high - OFF:

UOTGHS->UOTGHS_CTRL &= ~UOTGHS_CTRL_VBUSPO;                    //Active level is low

Next is to enable OTG Pad, without it enabled UOTGHS module won't sense bus power correctly:

UOTGHS->UOTGHS_CTRL |= UOTGHS_CTRL_OTGPADE;                    //Enable OTG pad

Now I can enable USB:

UOTGHS->UOTGHS_CTRL |= UOTGHS_CTRL_USBE;                       //Enable USB

Althogh I enabled USB it won't work as default clock status is frozen due to power saving reasons. Clock has to be unfreezed:

//Unfreeze USB clock
UOTGHS->UOTGHS_CTRL &= ~UOTGHS_CTRL_FRZCLK;                    //Unfreezes clock
while(0 == (UOTGHS->UOTGHS_SR & UOTGHS_SR_CLKUSABLE))          //Wait until clock is ready

Next piece of code took me awhile to insert it here and I still have no explanation why it works:

UOTGHS->UOTGHS_SCR = UOTGHS_SCR_VBUSTIC;                       //Clears pending VBus transition interrupt

Although I do this Ack operation in VBus level change handler (see UOTGHS_Handler below), without this line it does not determine device connection. No explanation or documentation can be found, I saw it only in Atmel examples with no given comments.

Next operation prohibits powering bus off if power error happens. I will have bus power errors when plugging USB device in and I don't want to lose power during that unimportant event:

UOTGHS->UOTGHS_CTRL |= UOTGHS_CTRL_VBUSHWC;                    //No hardware control over UOTGHVBOF pin

After that all necessary interrupts can be specified. They won't work until UOTGHS master interrupt is enabled:

//Enable Vbus change and Vbus error interrupts
UOTGHS->UOTGHS_CTRL |= UOTGHS_CTRL_VBUSTE | UOTGHS_CTRL_VBERRE;
    
//Enable main control interrupts: Connection, disconnection, SOF and reset
UOTGHS->UOTGHS_HSTIER = UOTGHS_HSTICR_DCONNIC | UOTGHS_HSTICR_HSOFIC | UOTGHS_HSTICR_RSTIC;

Bus can now be powered up and all interrupts enabled:

//VBus activation
UOTGHS->UOTGHS_SFR = UOTGHS_SR_VBUSRQ;
    
NVIC_EnableIRQ((IRQn_Type) ID_UOTGHS);                         //Enables UOTGHS master interrupt

Note that I don't assign NVIC_ISER (interrupt set enable register) register directly but rather use NVIC_EnableIRQ function. This function is a part of CMSIS (Cortex Microcontroller Software Interface Standard), every cortex microcontroller vendor supplies standard functions as part of CMSIS, they have the same names but implementation may differ.

The whole initialization function looks like this:

void HCD_Init()
{
    //Sets up VBOF pin - takes it out of PIO control
    PIOB->PIO_IDR |= PIO_PB10A_UOTGVBOF;      //Disables interrupt
    PIOB->PIO_ABSR &= ~PIO_PB10A_UOTGVBOF;    //Peripheral A selected (p.1073 of SAM3X manual)
    PIOB->PIO_PDR |= PIO_PB10A_UOTGVBOF;      //Moves pin control from PIO controller to Peripheral
    
    //Enabling USB clock
    PMC->CKGR_UCKR = CKGR_UCKR_UPLLCOUNT(3) | CKGR_UCKR_UPLLEN; //Enables UPLL
    while (0 == (PMC->PMC_SR & PMC_SR_LOCKU));            //Waits until clock is ready
    
    //Enabling UOTGHS peripheral clock
    PMC->PMC_PCER1 |= (1u << (ID_UOTGHS - 32));           //PCER1 is used because ID > 32
    
    //Clearing bit UIDE which means: The USB mode (device or host) is selected from the UIMOD bit.
    UOTGHS->UOTGHS_CTRL &= ~UOTGHS_CTRL_UIDE;    
    UOTGHS->UOTGHS_CTRL &= ~UOTGHS_CTRL_UIMOD;            //Forces HOST mode
    
    UOTGHS->UOTGHS_CTRL &= ~UOTGHS_CTRL_VBUSPO;           //Active level is low    
    UOTGHS->UOTGHS_CTRL |= UOTGHS_CTRL_OTGPADE;           //Enable OTG pad
    UOTGHS->UOTGHS_CTRL |= UOTGHS_CTRL_USBE;              //Enables USB
    
    //Unfreeze USB clock
    UOTGHS->UOTGHS_CTRL &= ~UOTGHS_CTRL_FRZCLK;           //Unfreezes clock
    while(0 == (UOTGHS->UOTGHS_SR & UOTGHS_SR_CLKUSABLE)) //Waits until clock is ready    
    
    UOTGHS->UOTGHS_SCR = UOTGHS_SCR_VBUSTIC;              //Clears pending VBus transition interrupt
    
    UOTGHS->UOTGHS_CTRL |= UOTGHS_CTRL_VBUSHWC;           //No hardware control over UOTGHVBOF pin
    
    //Enable Vbus change and Vbus error interrupts
    UOTGHS->UOTGHS_CTRL |= UOTGHS_CTRL_VBUSTE | UOTGHS_CTRL_VBERRE;
    
    //Enable main control interrupts: Connection, disconnection, SOF and reset
    UOTGHS->UOTGHS_HSTIER = UOTGHS_HSTICR_DCONNIC | UOTGHS_HSTICR_DDISCIC 
                           | UOTGHS_HSTICR_HSOFIC | UOTGHS_HSTICR_RSTIC;    
    
    UOTGHS->UOTGHS_SFR = UOTGHS_SR_VBUSRQ;                //VBus activation
    
    NVIC_EnableIRQ((IRQn_Type) ID_UOTGHS);                //Enables UOTGHS master interrupt
}

At this point UOTGHS is up and running and ready to generate interrupts thus they must be caught and served.

USB module interrupt

Let's look into startup_sam3xa.c file for interrupt name. It is UOTGHS_Handler. Lets create one in HCD.c file right under HCD_Init function:

void UOTGHS_Handler()
{
    
}

In HCD_Init I enabled many interrupts but there is only one entry point which is UOTGHS_Handler. That means if any of those interrupts happen, UOTGHS_Handler will be called and I must determine which exactly interrupt occurred by checking certain bits. For now I'll check six different interrupts: power level change, power error, device connection/disconnection, SOFs and device reset. First is power level and power error:

//Manage Vbus error
if (0 != (UOTGHS->UOTGHS_SR & UOTGHS_SR_VBERRI))
{
    UOTGHS->UOTGHS_SCR = UOTGHS_SCR_VBERRIC;                //Ack VBus error interrupt
    PrintStr("VBus error.\r\n");
    return;
}
    
//Manage Vbus state change
if (0 != (UOTGHS->UOTGHS_SR & UOTGHS_SR_VBUSTI))
{
    UOTGHS->UOTGHS_SCR = UOTGHS_SCR_VBUSTIC;                //Ack VBus transition
    PrintStr("VBus level changed.\r\n");
    return;
}

Note that acknowledge (Ack) must be performed to clear interrupt flag. Ack is just a bit-clearing operation, otherwise an interrupt will be pending and firing up again and again. This behaviour is different from AVR microcontrollers where interrupt flag is cleared automatically by hardware once interrupt handler is called. Also note PrintStr function from UART (see part 2) and don't forget to add #include "UART.h" on top of HCD.h file.

For now I just acknowledge interrupts and print appropriate messages so we can see what's going on in RS-232 monitor.

Next connection and disconnection interrupts:

//Manage connection event
if ( 0 != (UOTGHS->UOTGHS_HSTISR & UOTGHS_HSTISR_DCONNI))
{
    UOTGHS->UOTGHS_HSTICR = UOTGHS_HSTICR_DCONNIC;             //Ack connection
    PrintStr("Device connected.\r\n");
      
    UOTGHS->UOTGHS_HSTCTRL |= UOTGHS_HSTCTRL_RESET;            //Resets port
    return;
}
    
//Manage disconnection event
if (0 != (UOTGHS->UOTGHS_HSTISR & UOTGHS_HSTISR_DDISCI))
{
    UOTGHS->UOTGHS_HSTICR = UOTGHS_HSTICR_DDISCIC;             //Ack disconnection
    PrintStr("Device disconnected.\r\n\r\n\r\n");
    return;
}

Once connection has happened, a reset sequence must be sent to device in accordance with USB specification. Connection handler asks UOTGHS module to send it.

Next interrupt handler will signal us that reset has been done:

//USB bus reset detection
if (0 != (UOTGHS->UOTGHS_HSTISR & UOTGHS_HSTISR_RSTI))
{
    UOTGHS->UOTGHS_HSTICR = UOTGHS_HSTICR_RSTIC;              //Ack reset
    PrintStr("Reset performed.\r\n");
    return;
}

To very bottom of UOTGHS_Handler I'll put following code to see if I forgot to handle some interrupt:

PrintStr("Unmanaged Interrupt.\n\r");                         //If I forgot to handle something

The whole handler code:

void UOTGHS_Handler()
{
    //Manage Vbus error
    if (0 != (UOTGHS->UOTGHS_SR & UOTGHS_SR_VBERRI))
    {
        UOTGHS->UOTGHS_SCR = UOTGHS_SCR_VBERRIC;                //Ack VBus error interrupt
        PrintStr("VBus error.\r\n");
        return;
    }
    
    //Manage Vbus state change
    if (0 != (UOTGHS->UOTGHS_SR & UOTGHS_SR_VBUSTI))
    {
        UOTGHS->UOTGHS_SCR = UOTGHS_SCR_VBUSTIC;                //Ack VBus transition
        PrintStr("VBus level changed.\r\n");
        return;
    }
    
    //Manage SOF interrupt
    if (0 != (UOTGHS->UOTGHS_HSTISR & UOTGHS_HSTISR_HSOFI))
    {
        UOTGHS->UOTGHS_HSTICR = UOTGHS_HSTICR_HSOFIC;            //Ack SOF
        return;
    }
    
    //Manage connection event
    if ( 0 != (UOTGHS->UOTGHS_HSTISR & UOTGHS_HSTISR_DCONNI))
    {
        UOTGHS->UOTGHS_HSTICR = UOTGHS_HSTICR_DCONNIC;            //Ack connection
        PrintStr("Device connected.\r\n");
        
        UOTGHS->UOTGHS_HSTCTRL |= UOTGHS_HSTCTRL_RESET;            //Resets port
        return;
    }
    
    //Manage disconnection event
    if (0 != (UOTGHS->UOTGHS_HSTISR & UOTGHS_HSTISR_DDISCI))
    {
        UOTGHS->UOTGHS_HSTICR = UOTGHS_HSTICR_DDISCIC;            //Ack disconnection
        PrintStr("Device disconnected.\r\n\r\n\r\n");
        return;
    }
    
    //USB bus reset detection
    if (0 != (UOTGHS->UOTGHS_HSTISR & UOTGHS_HSTISR_RSTI))
    {
        UOTGHS->UOTGHS_HSTICR = UOTGHS_HSTICR_RSTIC;            //Ack reset
        PrintStr("Reset performed.\r\n");
        return;
    }
    
    PrintStr("Unmanaged Interrupt.\n\r");                        //If I forgot to handle something
}

Note SOF interrupt handler. SOF means Start-Of-Frame, it's a special level change sequence (token) that HOST generates every 125us for High Speed devices on the bus to keep it active. SAM3X does this for us and it needs to be handled because all requests and transfers happens in between SOFs.

At this moment we can start reading device descriptors (perform enumeration process). But first let's build the code, upload it and observe the output in RS-232 monitor program. Try to connect and then disconnect a USB device (any device, not necessary a camera) several times:

Note: add #include HCD.h to main file and add HCD_Init(); call after all other init calls.

As you can see the program can now determine device connection and disconnection. After connection it resets device which means readiness to start enumeration process. Also note several VBus errors, they happen in the moment when I'm moving USB-plug into USB-receptacle and probably related to power level settling. As it happens before "Device connected" message I don't worry about it for now.

Conclusion

Here I shown how to initialize USB (UOTGHS) module of SAM3X8E microcontroller. General interrupt entry point is also created which catches all UOTGHS interrupts. I will return to this interrupt handler several times in the future articles as more interrupts are added when I deal with pipes. In the next article I'll initialize control pipe (pipe 0) and get first descriptors from my camera.

Source code is here.

Part 5 is here.