65.9K
CodeProject 正在变化。 阅读更多。
Home

在 Arduino Due 上获取 USB 网络摄像头视频流 - 第四部分:USB 模块初始化和通用中断

starIconstarIconstarIconstarIconstarIcon

5.00/5 (8投票s)

2015年4月8日

CPOL

8分钟阅读

viewsIcon

31169

downloadIcon

427

USB 模块初始化和通用中断

引言

第3部分在这里

在本部分中,我将简要介绍USB 2.0,陈述本项目将简化USB主机实现的假设。我将进行Arduino的USB模块初始化,并开始编写USB中断处理程序。 

简述USB 2.0

USB总线非常流行,因为它易于使用,并且有大量的设备支持。但是,这种对终端用户易用的背后是开发人员的高复杂性。尽管Atmel的SAM3X8E负责低级事务,但为了对SAM3X8E的USB模块进行编程以实现我们感兴趣的功能,我们仍然需要理解USB基础知识。

  • 总线上可以有一个主机,可选的集线器和多于一个的设备连接。我的简化:我不会使用任何集线器,我的USB设备(摄像头)将直接连接到Arduino Due板。
  • 总线可以工作在3种不同的速度:低速、全速和高速。我的简化:速度始终是高速(HS)。
  • 设备可以有一个或多个功能。例如,摄像头可以有视频和音频功能。我的简化:我将只处理视频功能。
  • 设备通过描述符报告自身信息。我将读取它们以了解我能对特定设备做什么。这些描述符的格式在USB规范、UVC规范以及设备制造商的规范中定义。我的简化:我不会读取厂商(设备制造商)定义的描述符。
  • 要操作一个设备,它必须连接到总线,读取其描述符,并根据这些描述符中的信息进行配置。必须分配设备地址。
  • 主机与设备的端点通信。任何USB设备总有一个默认的零端点。每个端点都有其唯一的编号。还有一个术语“管道”(pipe),它表示设备地址和端点编号的逻辑组合。主机建立管道。
  • 有不同类型的端点:控制、批量、中断和同步。设备设置通过控制端点进行,视频流通过同步端点传输。
  • 控制端点可以是IN和OUT。其他端点必须是IN或OUT。
  • 端点被分组到接口中。例如,视频摄像头可以有两个接口:一个用于控制,一个用于流式传输。
  • 主机通过事务与端点通信。一个事务将包括一个令牌(token)、可选的数据和可选的握手(取决于端点类型)。我将在文章后面编码时详细解释特定的事务。所有序列都在第8.5节“事务包序列”[2, p.209]中规定,并由状态机进行精妙的图示。

Arduino Due的USB模块

USB分为几个层:机械层、电气层和协议层。机械层在第6章[2, p.85]中定义,电缆和Arduino的连接器负责处理。电气层涉及USB总线上的电平、位填充、同步模式等。SAM3X8E将负责这些。因此,我们需要处理的是协议层,SAM3X8E完全符合第9章“协议层”[2, p.195],因此强烈建议阅读。

SAM3X8E的USB模块实际上比USB本身更进一步,它是OTG(On-The-Go)功能,这意味着它可以同时作为主机和设备。OTG是对原始标准的一项新修订。它用于连接打印机到手机或照相机,或者将USB闪存驱动器连接到手机等情况。过去,手机一直是设备。现在,如果连接了其他允许的设备,它也可以作为主机。

我将强制SAM3X8E仅作为主机运行,不使用OTG。

SAM3X8E USB模块称为UOTGHS(USB On-The-Go High Speed),在[1, p 1069]中有描述。请也阅读。

在继续下一节之前,我将在我们的项目中创建一些额外的文件。

  • USB_Specification.h - 这里将包含所有标准的结构体和常量。
  • HCD.h 和 HCD.c - 这里将包含所有处理SAM3X8E UOTGHS硬件(主机)的函数。
  • USBD.h 和 USBD.c - 这里将包含所有读取描述符、设置配置等的函数(驱动程序)。

USB模块初始化

打开HCD.h文件并添加以下声明。

void HCD_Init(void);

然后我将在HCD.c文件中实现它。

根据UOTGHS的依赖性[1, p.1073],UOTGID(用于确定A或B插头的连接)和UOTGVBOF(USB总线电源线)引脚的控制必须从PIO控制器转移到UOTGHS模块。它们都属于外设A。由于将强制UOTGHS处于主机模式,因此插头识别引脚将不再有意义,所以我不会为其编写任何代码。另外,不需要PIO中断,因为UOTGHS有自己的中断。

//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

接下来需要做的是启用USB时钟生成。这与第一部分中为PLLA时钟生成84MHz的操作非常相似,但在这里我们需要生成480MHz,并且时钟名称是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

一旦时钟被启用,就将其信号供给UOTGHS模块。

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

接下来是强制UOTGHS模块进入主机模式。

//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

注意这里使用了UOTGHS_CTRL(控制)寄存器[1, p.1108]。它控制适用于整个UOTGHS模块的各种通用设置。

接下来是指定哪个电平(低或高)会使USB总线电源开启或关闭。如果我们查看Arduino Due的原理图,UOTGVBOF通过一个双极晶体管和一个运算放大器控制开关(FET晶体管)。低电平开启总线电源,高电平关闭。

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

接下来是启用OTG Pad,如果未启用,UOTGHS模块将无法正确检测总线电源。

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

现在我可以启用USB了。

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

尽管我启用了USB,但它不会工作,因为默认的时钟状态由于节电原因被冻结了。必须解冻时钟。

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

下一段代码花了我一些时间才插入在这里,我仍然不明白为什么它有效。

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

尽管我在VBus电平变化处理程序(参见下面的UOTGHS_Handler)中执行了此ACK操作,但没有这行代码,它就无法确定设备连接。找不到任何解释或文档,我只在Atmel的示例中看到过,但没有注释。

接下来的操作禁止在发生电源错误时关闭总线电源。当我插入USB设备时,我会遇到总线电源错误,我不想在那个不重要的事件期间断电。

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

之后,所有必要的中断都可以被指定。在启用UOTGHS主中断之前,它们将不起作用。

//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;

现在可以给总线供电,并启用所有中断。

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

请注意,我不是直接分配NVIC_ISER(中断设置使能寄存器)寄存器,而是使用NVIC_EnableIRQ函数。这个函数是CMSIS(Cortex微控制器软件接口标准)的一部分,每个Cortex微控制器供应商都会提供CMSIS标准函数,它们有相同的名称,但实现可能不同。

整个初始化函数如下所示。

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
}

此时,UOTGHS已经启动并运行,准备生成中断,因此必须捕获并服务这些中断。

USB模块中断

让我们在startup_sam3xa.c文件中查找中断名称。它是UOTGHS_Handler。让我们在HCD.c文件中,紧跟在HCD_Init函数下方创建一个。

void UOTGHS_Handler()
{
    
}

在HCD_Init中,我启用了许多中断,但只有一个入口点,即UOTGHS_Handler。这意味着如果发生任何这些中断,UOTGHS_Handler将被调用,我必须通过检查特定位来确定究竟发生了哪个中断。目前,我将检查六个不同的中断:电源电平变化、电源错误、设备连接/断开、SOF和设备复位。首先是电源电平变化和电源错误。

//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;
}

请注意,必须进行确认(Ack)以清除中断标志。Ack只是一个位清除操作,否则中断将挂起并反复触发。这种行为与AVR微控制器不同,在AVR微控制器中,中断标志在中断处理程序被调用后由硬件自动清除。另外,请注意UART中的PrintStr函数(参见第2部分),并且不要忘记在HCD.h文件的顶部添加#include "UART.h"。

目前,我只是确认中断并打印相应的消息,这样我们就可以在RS-232监视器中看到发生了什么。

下一个是连接和断开中断。

//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规范向设备发送复位序列。连接处理程序请求UOTGHS模块发送它。

下一个中断处理程序将向我们发出复位已完成的信号。

//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;
}

在UOTGHS_Handler的最底部,我将添加以下代码,以便在我忘记处理某个中断时可以看到。

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

整个处理程序代码。

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
}

注意SOF中断处理程序。SOF表示Start-Of-Frame(帧开始),它是一种特殊的电平变化序列(令牌),主机每125微秒为总线上的高速设备生成一次,以保持总线活动。SAM3X为我们处理这个,并且需要被处理,因为所有请求和传输都发生在SOF之间。

此时,我们可以开始读取设备描述符(执行枚举过程)。但首先,让我们构建代码,上传并观察RS-232监视器程序中的输出。尝试连接然后断开USB设备(任何设备,不一定是摄像头)几次。

注意:在主文件中添加#include HCD.h,并在所有其他初始化调用之后添加HCD_Init();调用。

如您所见,程序现在可以确定设备的连接和断开。连接后,它会复位设备,这意味着已准备好开始枚举过程。另外,请注意几次VBus错误,它们在我将USB插头插入USB插座时发生,可能与电源电平稳定有关。由于它们发生在“设备已连接”消息之前,我现在不担心它们。

结论

在这里,我展示了如何初始化SAM3X8E微控制器的USB(UOTGHS)模块。还创建了一个通用的中断入口点,它捕获所有UOTGHS中断。将来,我将在处理管道时多次回到这个中断处理程序,并添加更多中断。在下一篇文章中,我将初始化控制管道(管道0),并从我的摄像头获取第一个描述符。

源代码在此处

第5部分在此处

© . All rights reserved.