11 USB主机

更新时间:2023-12-31 10:27:01 阅读量: 教育文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

第十一章 USB主机开发

第五章至第十章介绍了常用USB设备开发,本章介绍使用Stellaris的USB处理器进行USB主机开发。USB主机开发相对于USB设备开发较简单。USB主机只有一个,只是驱动不一样而已,USB主机只需识别USB设备,并能进行数据传输、控制就行。

11.1 USB主机开发介绍

Luminary Micro Stellaris 的USB处理器具有主机控制功能,支持全速与低速。使用官方提供的USB HOST库可以轻松开发USB主机。本章主要使用USB库编程为例讲解,涵盖整个USB主机开发。

Stellaris所提供的USB处理器中,LM3S3XXX与LM3S5XXX的A0版本有一个bug,如图11-1所示,为PB0与PB1硬件连接图,在A0版本中,USB处理器工作在主机和设备模式下时,PB0与PB1不能当GPIO使用,因为在此版本中,PB0与PB1为主机与设备提供电平信号。当USB处理器工作在主机模式下时,PB0应该连接到低电平;工作在设备模式下时,PB0应该连接到高电平。同时,PB0引脚与电压信号之间应该连接一个电阻,其典型值为10Ω。PB1必须连接到5V(4.75V-5.25V)。如果USB处理器不是A0版本,PB0与PB1可以用做GPIO功能。

图11-1 PB0与PB1硬件连接图

如图11-2所示,为LM3S3xxx与LM3S5xxx系列的USB功能引脚连接图,USB处理器工作在主机模式下时,USB功能引脚连接与设备模式下一样。第一:USB0RBIAS,连接方式固定,为USB模拟电路内部必须要的 9.1 kΩ 电阻(1% 精度),普通贴片满足其需求;第二:USB0DP和USB0DM,USB0的双向差分数据管脚,连接方式固定,分别连接到USB规范中的D+和D-中,在使用时,请特别注意D+和D-的连接方式。USB0EPEN,USB主机电源输出使能引脚,主机输出电源使能信号,高电平有效,用于使能外部电源。如图11-3所示,通过USB处理器的USB0EPEN引脚输出高电平使能Vbus电源。USB0PFLT,主机模式下的外部电源异常输入引脚,指示外部电源的错误状态,低电平有效。如图11-3所示,当TPS2051的Vbus电源输出电流小于1mA或者大于500mA时,从OCn引脚输出低电平,漏开输出,通过USB处理器的USB0PFLT上拉,读取OCn的电平变化,并产生中断。注意,USB0EPEN和USB0PFLT主要工作在主机模式下,为设备提供Vbus电源当然这两个引脚可以不使用,直接通过主板5V提供Vbus电源,这会缺少USB电源欠压过流保护,但电路简单。开发人员可以根据自己设计需要,自行剪裁硬件,但是USB0RBIAS、USB0DP、USB0DM必不可少,如是是A0版本处理器,工作在主机和设备模式下时,PB0与PB1不能当GPIO使用,PB1接5V;主机模式下,PB0接地;设备模式下,PB0接5V。

图11-2 USB功能引脚连接图

图11-3 Vbus控制电路

对LM3S3xxx、LM3S5xxx、LM3S9xxx系列处理器,有一部分具有USB OTG功能,与传统不具有USB OTG功能的处理器比较,PB0做为USB0ID,此信号用于检测 USB ID 信号的状态。此时 USBPHY将在内部启用一个上拉电阻,通过外部元件(USB 连接器)检测 USB 控制器的初始状态(即电缆的 A 侧设置下拉电阻,B 侧设置上拉电阻)。该类Stellaris 支持 OTG 标准的会话请求协议 (SRP) 和主机协商协议 (HNP),提供完整的 OTG 协商。回话请求协议(SRP)允许连接在USB线缆B端的B类设备向连接在A端的A类设备发出请求,通知A类设备打开VBUS电源。主机协商协议(HNP)用于在初始会话请求协议提供供电后,决定USB线缆哪端的设备作为USB Host主控制器。

当该设备连接到非 OTG 外设或设备时,控制器可以检测出线缆终端所使用的设备类型,并提供一个寄存器来指示该控制器是用作主机控制器还是用作设备控制器。之上所提供的动作都是由 USB 控制器自动处理的。基于这种自动探测机制,系统使用A类/B类连接器取代 A/B 类连接器,可支持与另外的 OTG 设备完整的 OTG 协商。另外,USB 控制器还提供对连接到非 OTG 的外设或主机控制器的支持。可将 USB 控制器设置为专用的主机或设备模式,此时,USB0VBUS 和 USB0ID 管脚可被设置作为的 GPIO 使用。但当 USB控制器用作自供电设备时,必须将 GPIO 输入管脚或模拟比较器输入管脚连接到 VBUS 管脚上,并配置为在 VBUS 掉电时会产生中断。该中断用于禁用 USB0DP 信号上的的上拉电阻。所以具有USB OTG功能的处理器工作在主机模式下时,USB0VBUS 和 USB0ID 管脚可配置为GPIO 使用,但是

使用USB OTG去实现主机功能,那么USB0VBUS 和 USB0ID不能用做GPIO,并且USB0ID必须接地。

注意:对于具有USB OTG功能的USB处理器来说,也有一个重要的bug要注意,根据官方提供的数据手册可以看出,只要处理器不工作在USB OTG模式下,USB0VBUS 和 USB0ID 管脚可配置为GPIO 使用,但在B1版本中(其它版本也有)无论USB处理器工作在什么模式,USB0ID必须归USB使用,不能用于GPIO功能,如果工作在主机模式,USB0ID连接低电平(地);工作在设备模式,USB0ID连接高电平(5V)。同时USB0VBUS必须提供5V输入。这个bug是怎么产生的呢?由于USB通过控制状态寄存器(USBGPCS)的DEVMODOTG工作不正常造成无法通过寄存器来实现切换功能,必须通过外面的USB0ID管脚来实现,相当于使用USB OTG来让USB处理器工作在主机或者设备模式下。 11.2 USB主机开发过程

USB主机开发相对简单,在使用USB库编程时更加简单,USB主机开发流程如图11-4所示,为USB主机开发的简单流程图。USB主机开发时,首先确定主机要读取设备类型,如是在USB库中没有该类设备驱动,由开发人员按照USB库中设备驱动编写方式自行编写;编程开时,首先配置USB处理器的内核;再根据情况编写、修改驱动;注册设备驱动,一个函数就能实现;通过USB库函数来运行主机。

USB主机配置 驱动编写 注册驱动 运行主机

图11-4 USB主机开发流程

USB主机配置包括了中断控制、外设资源控制等一系列与处理器运行相关的模块、资源初始化与配置。在USB库中已经包含了常用设备驱动,比如HID、MSC、Audio等等一系列驱动。在具有这些驱动的情况下,注册驱动,让USB处理器可以方便控制USB设备。最后运行主机,枚举设备、数据传输并实现其功能。

11.2.1 主机配置

在使用USB主机之前必须配置好内部相关参数,并且主频最好不要低于30Mhz,不然会出现不可预料的后果。根据外围Vbus电路,使能对应引脚,打开Vbus电源等等。

①.使用SysCtlClockSet设置USB处理器主频,最好不要低于30Mhz,在本书中都是设置为50Mhz。

SysCtlClockSet(SYSCTL_SYSDIV_4 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | SYSCTL_XTAL_8MHZ);

②.能过SysCtlPeripheralEnable使能USB模块时钟,在使用USB模块时,必须先打开USB资源时钟,由于具有USB功能的LM3S处理器目前都只有一个USB模块USB0,所以打开USB模块时钟的函数方式也固定,参数也固定:

SysCtlPeripheralEnable(SYSCTL_PERIPH_USB0);

③.使能串口,方便调试。在嵌入式系统开发时,经常使用UART调试程序,输入控制命令、输出调试信息。在此,也使用UART调试USB主机,为了减轻开发人员的负担,处理器厂商的程序员已经把UART调试常用的命令与函数编写的非常完善并免费提供源代码,调用简单。打开UART0,配置串口参数。

SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0); SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);

GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1); UARTStdioInit(0);

④.为了让USB处理器能同时连接设备和主机,可以通过FSUSB11MTCX做USB扩展器,让处理器可能同时插入USB设备与USB主机,当然在某一个时刻只工作在主机或者设备模式下,通过USB处理器的GPIO口控制FSUSB11MTCX的S1和S2,让其选择接通主机与设备,如图11-5所示,当工作在主机模式下时,将与S1和S2连接的引脚置低。同时,打开Vbus,给插入设备供电。

Host 接口

Device

图11-5 USB接口扩展

根据图11-5所示,假设与S1和S2连接的是PH2引脚,那么选通主机连接的程序如下:

#define USB_MUX_GPIO_PERIPH SYSCTL_PERIPH_GPIOH #define USB_MUX_GPIO_BASE GPIO_PORTH_BASE #define USB_MUX_GPIO_PIN GPIO_PIN_2

#define USB_MUX_SEL_DEVICE USB_MUX_GPIO_PIN #define USB_MUX_SEL_HOST 0

SysCtlPeripheralEnable(USB_MUX_GPIO_PERIPH); // 切换为主机模式,外部FSUSB11MTCX切换

GPIOPinTypeGPIOOutput(USB_MUX_GPIO_BASE, USB_MUX_GPIO_PIN);

GPIOPinWrite(USB_MUX_GPIO_BASE, USB_MUX_GPIO_PIN, USB_MUX_SEL_HOST);

根据图11-3所示, Vbus控制电路图,打开Vbus与异常检测,应该配置USB0EPEN和USB0PFLT为USB所用,在主机模式下通过USB处理器自动控制Vbus电源。

// Vbus电源控制 端口配置

GPIOPinTypeUSBDigital(GPIO_PORTH_BASE, GPIO_PIN_3 | GPIO_PIN_4); SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);

⑤.打开USB的PLL,为USB模块工作提供时钟源,当然只要使用USB功能,就必须打开USB模块的PLl,不打开PLL,USB不能正常工作。SysCtlUSBPLLEnable,使能USB控制器的PLL模块。即USB的物理层。用到USB功能时,本函数必须被调用。

// 打开USB Phy 时钟. SysCtlUSBPLLEnable();

11.2.2 注册驱动

在官方提供的USB库中已经包含了常用的USB设备库,在使用USB主机功能时,如果没有相应的驱动,就必须自己编写,编写后,能过USBHCDRegisterDrivers注册驱动,并编写回调函数。在这部分有一些常用宏定义和函数需要读者注意:

DECLARE_EVENT_DRIVER,声明USB事件驱动库的宏。通过此宏,定义一个主机用于识别设备的驱动变量。下面是此宏的源代码:

#define DECLARE_EVENT_DRIVER(VarName, pfnOpen, pfnClose, pfnEvent) \\ void IntFn(void *pvData); \\ const tUSBHostClassDriver VarName = \\ { \\ USB_CLASS_EVENTS, \\ 0, \\ 0, \\ pfnEvent \\ }

从此宏的源代码中可以看出: VarName为变量的名称;pfnOpen为开放调用这个驱动程序回调。这个值目前是保留的,应设置为0;pfnClose关闭这个驱动调用回调;这个值目前是保留的,应设置为0; pfnEvent 将用于各种USB事件调用回调。第一个参数是变量的实际名称,用来声明本宏。第二个和第三个参数是保留,并且未使用,应当设置为零。最后一个参数是回调函数,实际被指定为一个函数指针的类型:void (*pfnEvent)(void *pvData);pfnEvent 函数是被调用的以void指针类型应该指向类型为tEventInfo的结构体。事件一旦发生的就导致pfnEvent函数被调用。

tUSBHostClassDriver,这个结构定义了一个USB主机类驱动程序接口,一旦USB设备枚举就解析找到一个USB类驱动程序。此结构体定义如下:

typedef struct {

unsigned long ulInterfaceClass;

void * (*pfnOpen)(tUSBHostDevice *pDevice); void (*pfnClose)(void *pvInstance);

void (*pfnIntHandler)(void *pvInstance); }

tUSBHostClassDriver;

其中ulInterfaceClass为当前这个设备类驱动程序支持的接口类;(*pfnOpen) (tUSBHostDevice *pDevice)为函数指针,指向当这个结构体被调用时执行的函数;(*pfnClose)(void *pvInstance),指向本结构体所定义的设备断开连接时调用的函数;(*pfnIntHandler)(void *pvInstance),当端点收到设备传送的数据并产生中断时,指向中断处理回调函数。所以,DECLARE_EVENT_DRIVER的最后一个参数必须由开发人员编写,其函数格式应该为void fun(void *par)所定义的类型,同时这个函数名fun应该与DECLARE_EVENT_DRIVER的最后一个参数名字一致。

例如:编写一个USB处理键盘数据的回调函数,并注册驱动。

// 声明USB事件驱动接口

DECLARE_EVENT_DRIVER(g_sUSBEventDriver, 0, 0, USBHCDEvents); //事件处理函数

void USBHCDEvents(void *pvData) {

tEventInfo *pEventInfo; // 指向事件参数

pEventInfo = (tEventInfo *)pvData; switch(pEventInfo->ulEvent) {

// 检测键盘.

case USB_EVENT_CONNECTED:

{

UARTprintf(\ //未知设备

g_eUSBState = STATE_UNKNOWN_DEVICE; break; } // 键盘拔出

case USB_EVENT_DISCONNECTED: {

UARTprintf(\ // 键盘移除

g_eUSBState = STATE_NO_DEVICE; break; }

case USB_EVENT_POWER_FAULT: {

UARTprintf(\ //电源异常

g_eUSBState = STATE_POWER_FAULT; break; }

default: {

break; } } }

上面实例是USB主机读取USB键盘数据的回调函数,并注册驱动,可以看出,DECLARE_EVENT_DRIVER(g_sUSBEventDriver, 0, 0, USBHCDEvents)中的第2个和和第3个参数为0,最后一个参数USBHCDEvents为回调函数名称。pvData,为回调函数的入口参数,其类型为tEventInfo,用于记录事件信息。目前支持以下事件: USB_EVENT_CONNECTED,USB_EVENT_DISCONNECTED和USB_EVENT_POWER_FAULT。tEventInfo结构体定义如下:

typedef struct {

unsigned long ulEvent; unsigned long ulInstance; }

tEventInfo;

tEventInfo的ulEvent是用于记录USB事件类型,以位的状态实现事件记录,当前支持3个事件,所以在编写回调函数时只需处理三个状态。ulInstance,暂时没有使用。

在一个USB主机系统中,可能有一个、两个、甚至更多个设备需要控制,那么会出现多个设备驱动,在注册驱动时,通过一个数组存放驱动名称与回调函数:tUSBHostClassDriver

下面是HID设备驱动代码:

const tUSBHostClassDriver g_USBHIDClassDriver = {

USB_CLASS_HID, HIDDriverOpen, HIDDriverClose, 0 };

tUSBHostClassDriver可以定义回调函数,也可以定义设备驱动。对于某一种设备驱动来说,须具备两个tUSBHostClassDriver定义,一个用于定义驱动;一个用于回调,负责数据事件处理。在注册驱动时,需把两个包含在同一个数组里,完成驱动注册,下面是一个HID设备的驱动数组:

// 注册HID类驱动

static tUSBHostClassDriver const * const g_ppHostClassDrivers[] = {

&g_USBHIDClassDriver ,&g_sUSBEventDriver };

static const unsigned long g_ulNumHostClassDrivers =

sizeof(g_ppHostClassDrivers) / sizeof(tUSBHostClassDriver *);

有了设备驱动的数组后,还需使用函数USBHCDRegisterDrivers进行驱动注册,把驱动数组里的驱动全部注册到主机中,用于设备枚举。USBHCDRegisterDrivers函数原型如下:

void USBHCDRegisterDrivers(unsigned long ulIndex,

const tUSBHostClassDriver * const *ppHClassDrvs, unsigned long ulNumDrivers) {

// 保存所有驱动到g_sUSBHCD中

g_sUSBHCD.pClassDrivers = ppHClassDrvs; // 保存驱动个数

g_sUSBHCD.ulNumClassDrivers = ulNumDrivers; }

这个函数用于初始化、注册一个HCD类设备列表,该列表包含多个驱动数组。ulIndex 指定使用哪个USB控制器,在LM3S处理器中,目前只有一个USB模块,所以,第一个参数为0;ppHClassDrvs,一个所被主机类设备支持的数组,包含所有设备驱动;ulNumDrivers 所驱动的数量。本函数将设置支持的驱动类,应该在USBHCDInit()函数前调用。

例如:注册一个驱动到USB0模块。

// 注册驱动

USBHCDRegisterDrivers(0, g_ppHostClassDrivers, g_ulNumHostClassDrivers);

USBHCDPowerConfigInit,用于设置电源脚和电源故障配置,ulIndex USB控制器,ulPwrConfig应用程序所使用的电源配置,以下值中的其中一个需要被选择来设置电源故障灵敏度:

USBHCD_FAULT_LOW 一个外部电源故障被检测到引脚驱动低. USBHCD_FAULT_HIGH 一个外部电源故障被检测到引脚驱动高. 以下值中的其中一个需要被选择来设置电源故障检测: USBHCD_FAULT_VBUS_NONE 不自动检测电源故障 USBHCD_FAULT_VBUS_TRI 故障时自动设置USBnEPEN脚为三态门 USBHCD_FAULT_VBUS_DIS 没有电源故障时自动驱动USBnEPEN脚 以下值中的其中一个需要被选择来设置电源使能脚等级和来源: USBHCD_VBUS_MANUAL 电源控制完全由应用程序管理 USBHCD_VBUS_AUTO_LOW USBEPEN 自动驱动为低.

USBHCD_VBUS_AUTO_HIGH USBEPEN 自动驱动为高.

如果USBHCD_VBUS_MANUAL被使用,那么应用程序需要提供一个事件驱动来接收USB_EVENT_POWER_ENABLE 和USB_EVENT_POWER_DISABLE事件,以及使能和禁止VBUS。USBHCDPowerConfigInit源码如下:

void USBHCDPowerConfigInit(unsigned long ulIndex, unsigned long ulPwrConfig) {

ASSERT(ulIndex == 0);

g_ulPowerConfig = ulPwrConfig; }

本函数必须在HCDInit() 函数之前调用,以至于电源配置能在电源使能前设置好,保证设备供电正常。参数 ulPwrConfig为电源故障灵敏度参数,为USB处理器配置电源故障检测和电源使能脚等级和来源。

11.2.3 运行主机

驱动准备好后,可以进行USB主机运行,在这个阶段需要用到两个函数:USBHCDInit和USBHCDMain,USBHCDInit用于初始化USB主机的控制程序,USBHCDMain模拟实时操作系统,定义调用,用于检测USB通信。

USBHCDInit,用于初始化HCD。ulIndex USB控制器;pvPool指向一个缓存池; ulPoolSize缓存池的大小。本函数将初始化一个USB主机控制,开始枚举以及与设备进行通信,本函数应该在应用程序开始时调用一次,本调用将启动一个USB主机控制器,并且任何连接的设备将立即开始枚举序列。USBHCDInit源代码如下:

void USBHCDInit(unsigned long ulIndex, void *pvPool, unsigned long ulPoolSize) {

int iDriver;

// 确保ulPoolSize定义的内存空间能够存放下读取的描述符。 ASSERT(ulPoolSize >= sizeof(tConfigDescriptor)); // 确保工作在设备模式下不调用这个函数 ASSERT(g_eUSBMode != USB_MODE_DEVICE)

// 如果没有设备USB处理器工作模式,就默认为主机模式。 if(g_eUSBMode == USB_MODE_NONE) {

g_eUSBMode = USB_MODE_HOST; }

//如果工作主机模式下时,更新一下硬件,在OTG模式下不更新。 if(g_eUSBMode == USB_MODE_HOST) {

SysCtlPeripheralEnable(SYSCTL_PERIPH_USB0); SysCtlUSBPLLEnable(); }

// 通过内部函数初始化USB控制器,准备枚举。

USBHCDInitInternal(ulIndex, pvPool, ulPoolSize); // 默认情况下没有事件驱动 g_sUSBHCD.iEventDriver = -1;

// 扫描驱动列表中的事件驱动,用于枚举。

for(iDriver = 0; iDriver < g_sUSBHCD.ulNumClassDrivers; iDriver++) {

if(g_sUSBHCD.pClassDrivers[iDriver]->ulInterfaceClass == USB_CLASS_EVENTS) {

//如果有事件驱动,把事件驱动提取出来。 g_sUSBHCD.iEventDriver = iDriver; } }

// 设置毫秒定时。

g_ulTickms = SysCtlClockGet() / 3000; // 检查是否要使用DMA。

if(CLASS_IS_DUSTDEVIL && REVISION_IS_A0) {

g_bUseDMAWA = 1; } }

通过USBHCDInit可以看出,USBHCDInit主要负责初始化HCD,根据需求更新硬件,通过USBHCDInitInternal初始化USB控制器,完成通道、DMA、定时器、端点、中断、电源初始化与配置,为枚举做准备。

USBHCDMain,主机控制设备的主程序,必须在应用程序中周期性的调用,使用时钟节拍机制来处理主机控制设备接口而无需使用一个实时多任务操作系统(RTOS),所有时间边界代码(在操作系统中需要开中断和关中断的代码区)处理全部在一个中断上下文中,但是所有堵塞操作不在此函数中进行,从而允许这些堵塞操作函数完成等待而不挂起其他的中断。在使用USB主机时,需要为USBHCDMain提供基础时钟,来完成多任务操作系统的模拟,所以要循环调用此函数。USBHCDMain的源代码如下:

void USBHCDMain(void) {

unsigned long ulIntState; tUSBHDeviceState eOldState;

// 保存设备状态

eOldState = g_sUSBHCD.eDeviceState[0]; // 判断中断事件

if(g_ulUSBHIntEvents) {

// 关中断

ulIntState = IntMasterDisable();

if(g_ulUSBHIntEvents & INT_EVENT_POWER_FAULT) {

// 电源故障

if((g_sUSBHCD.iEventDriver != -1) &&

(g_sUSBHCD.pClassDrivers[g_sUSBHCD.iEventDriver]->pfnIntHandler)) {

// 发送电源故障事件

g_sUSBHCD.EventInfo.ulEvent = USB_EVENT_POWER_FAULT; //调用事件驱动

g_sUSBHCD.pClassDrivers[g_sUSBHCD.iEventDriver]->pfnIntHandler( &g_sUSBHCD.EventInfo); }

g_sUSBHCD.eDeviceState[0] = HCD_POWER_FAULT; }

else if(g_ulUSBHIntEvents & INT_EVENT_VBUS_ERR) {

//Vbus异常

g_sUSBHCD.eDeviceState[0] = HCD_VBUS_ERROR; } else {

// 检查是否有设备连接

if(g_ulUSBHIntEvents & INT_EVENT_CONNECT) {

g_sUSBHCD.eDeviceState[0] = HCD_DEV_RESET; } else

{

// 检查是否有设备断开连接

if(g_ulUSBHIntEvents & INT_EVENT_DISCONNECT) {

g_sUSBHCD.eDeviceState[0] = HCD_DEV_DISCONNECTED; } } }

//清除事件标志

g_ulUSBHIntEvents = 0; // 打开总中断 if(!ulIntState) {

IntMasterEnable(); } }

//根据设备状态,做相应处理

switch(g_sUSBHCD.eDeviceState[0]) {

//电源异常

case HCD_POWER_FAULT: {

break; }

// Vbus异常.

case HCD_VBUS_ERROR: { //关中断

IntDisable(INT_USB0);

if((eOldState != HCD_IDLE) && (eOldState != HCD_POWER_FAULT)) {

//断开连接

USBHCDDeviceDisconnected(0); }

//复位USB模块控制器

SysCtlPeripheralReset(SYSCTL_PERIPH_USB0); //延时100ms

SysCtlDelay(g_ulTickms * 100); //重新初始化HCD

USBHCDInitInternal(0, g_sUSBHCD.pvPool, g_sUSBHCD.ulPoolSize); break; }

case HCD_DEV_RESET: {

// 触发HCD复位 USBHCDReset(0); //设备连接

g_sUSBHCD.eDeviceState[0] = HCD_DEV_CONNECTED; break; }

//设备已经连接,开始枚举 case HCD_DEV_CONNECTED: {

// 在进行数据处理前,获取设备描述符

if(g_sUSBHCD.USBDevice[0].DeviceDescriptor.bLength == 0) {

// 发送设备描述符请求

if(USBHCDGetDeviceDescriptor(0, &g_sUSBHCD.USBDevice[0]) == 0) {

// 如果不能读取设备描述符,设置为未知设备。 g_sUSBHCD.eDeviceState[0] = HCD_DEV_ERROR; //发送未知连接

SendUnknownConnect(0, 1); } }

// 如果获取到设备描述符,现在设置设备地址

else if(g_sUSBHCD.USBDevice[0].ulAddress == 0) {

// 发送设置设备地址命令 USBHCDSetAddress(1); // 保存设备地址

g_sUSBHCD.USBDevice[0].ulAddress = 1; // 设备地址设置完成状态.

g_sUSBHCD.eDeviceState[0] = HCD_DEV_ADDRESSED; }

break; }

case HCD_DEV_ADDRESSED: {

// 检查配置描述符

if (g_sUSBHCD.USBDevice[0].pConfigDescriptor == 0) {

// 获取设备描述符

if(USBHCDGetConfigDescriptor(0, &g_sUSBHCD.USBDevice[0]) == 0)

{

// 如果不能读取设备描述符,设置为未知设备。 g_sUSBHCD.eDeviceState[0] = HCD_DEV_ERROR; // 发送未知连接

SendUnknownConnect(0, 1); } }

// 地址与设备描述符都已经获取,现在进行配置 else {

// 第一次进行设备配置

USBHCDSetConfig(0, (unsigned long)&g_sUSBHCD.USBDevice[0], 1); // 配置状态

g_sUSBHCD.eDeviceState[0] = HCD_DEV_CONFIGURED; // 打开驱动0

g_iUSBHActiveDriver = USBHCDOpenDriver(0, 0); }

break; }

// 获取设备请求

case HCD_DEV_REQUEST: {

g_sUSBHCD.eDeviceState[0] = HCD_DEV_CONNECTED; break; }

// 获取字符串描述符

case HCD_DEV_GETSTRINGS: {

break; }

//设备断开连接

case HCD_DEV_DISCONNECTED: {

// 断开USB0

USBHCDDeviceDisconnected(0); // 返回空闲状态

g_sUSBHCD.eDeviceState[0] = HCD_IDLE; break; }

// 连接与枚举完成,可以跳出本函数 case HCD_DEV_CONFIGURED: {

break; }

//设备故障,等待移除. case HCD_DEV_ERROR: default: {

break; } } }

从USBHCDMain源代码中可以看出,此函数主要完成主机事件处理、枚举设备等,本函数首先检测电源、Vbus异常,并发出响应;再通过g_sUSBHCD.eDeviceState[0]记录的设备状态进行事件处理,HCD_DEV_RESET后就开始获取设备描述符;如果获取成功,主机发送设备地址,让设备自动设置主机分配的地址;地址设置完成时,再通过设备的新地址与USB主机进行通信,再次获取设备描述符、字符串描述符,从源代码中可以看出,本函数并没有处理字符串描述符。USBHCDMain调用函数图如图11-6所示。为了实时处理USB

数据,在此usb库中简单的模拟了实时操作系统,并可以及时处理相关数据,USBHCDMain必须周期性调用。

图11-6 USBHCDMain函数调用图

11.3 主机开发实例

前面两节介绍了USB主机开发的基础知识,本节主要以USB主机控制鼠标、键盘、u盘、USB音频设备为例,深入详细的介绍USB主机开发过程。

11.3.1 鼠标 本书的第五章介绍了HID设备开发,主要以鼠标与键盘为例进行了深入分析与讲解,因为鼠标与键盘的广泛使用,此USB类设备开发成为USB开发的一个热点,同时为了适应嵌入式开发模式,要对HID设备类进行控制,必须开发对应HID设备的USB主机。

鼠标、键盘是日常生活中最常用的便携式设备之一,之前ps2接口的鼠标、键盘非常流行,但后来随着USB技术的发展,具有USB接口的鼠标、键盘遍地开花、发展迅速,并占有90%的市场,鼠标、键盘生产厂商也充分利用USB灵活特性,在此基础上研发出无线鼠标键盘,大量生产,并占有相当部分市场。所以USB HID设备与主机开发的重要性不言而喻,同时USB的HID设备离不开对应的USB主机支持,因此USB HID主机开发具有相当重的地位。

本节主要介绍支持USB鼠标的USB主机开发,在USB库中已经编写好USB鼠标驱动,供USB主机使用,相关函数包含在“usbhhidmouse.h”头文件中。主机包含3个功能函数,实现对鼠标的数据读取、操作:USBHMouseOpen、USBHMouseClose、USBHMouseInit。在USB库中,默认报告为4个字节。如果有必要,可以打开USBlib源码,在usbhhidmouse.c中修改“#define USBHMS_REPORT_SIZE 4”为鼠标报告的实际字节数,由Usb库做的USB鼠标默认为4个字节。

unsigned long USBHMouseOpen(tUSBCallback pfnCallback,

unsigned char *pucBuffer, unsigned long ulBufferSize); unsigned long USBHMouseClose(unsigned long ulInstance); unsigned long USBHMouseInit(unsigned long ulInstance);

unsigned long USBHMouseOpen(tUSBCallback pfnCallback,

unsigned char *pucBuffer, unsigned long ulBufferSize);

此函数用于打开一个鼠标实例。pfnCallback,回调函数,一但发生一个新的鼠标事件时会调用此回调函数;*pucBuffer,USB主机与USB鼠标数据传输的数据缓存。该缓存的小大至少是能容纳一个普通的设备描述符报告,一般定义为128即可满足需求;ulSize,USB主机与USB鼠标数据传输的数据缓存大小。本函数主要用于打开一个鼠标实例。本函数返回的值用于区别其他所有USB鼠标的“句柄”,一个常量对应一个鼠标实例,如果没有鼠标实例,返回0。

USBHMouseOpen的函数代码如下:

unsigned long USBHMouseOpen(tUSBCallback pfnCallback, unsigned char *pucBuffer,

unsigned long ulSize) {

// 外部鼠标回调函数传递给全局鼠标结构体的回调函数, g_sUSBHMouse.pfnCallback g_sUSBHMouse.pfnCallback = pfnCallback; // 打开USBHID实例,用于初始化HID设备层。

g_sUSBHMouse.ulMouseInstance = USBHHIDOpen(USBH_HID_DEV_MOUSE, USBHMouseCallback,

(unsigned long)&g_sUSBHMouse); // 传递数据缓存指针与大小

g_sUSBHMouse.pucHeap = pucBuffer; g_sUSBHMouse.ulHeapSize = ulSize; return((unsigned long)&g_sUSBHMouse); }

通过USBHMouseOpen的函数代码可看出,此函数通过USBHHIDOpen初始化USBHID驱动层,并返回鼠标实例,同时USBHMouseOpen函数完成鼠标实例g_sUSBHMouse的初始化,其中g_sUSBHMouse主要保存与鼠标实例相关参数与函数,包含鼠标实例的所有数据。

例如:打开一个鼠标实例

// USB鼠标 接口需要的内存空间

#define MOUSE_MEMORY_SIZE 128

unsigned char g_pucBuffer[MOUSE_MEMORY_SIZE]; // 声明USB驱动,并产生g_sUSBEventDriver

DECLARE_EVENT_DRIVER(g_sUSBEventDriver, 0, 0, USBHCDEvents);

// 注册g_USBHIDClassDriver HID设备类,同时也注册一个g_sUSBEventDriver事件驱动 static tUSBHostClassDriver const * const g_ppHostClassDrivers[] = {

&g_USBHIDClassDriver ,&g_sUSBEventDriver };

//驱动个数

//***************************************************************************** static const unsigned long g_ulNumHostClassDrivers =

sizeof(g_ppHostClassDrivers) / sizeof(tUSBHostClassDriver *);

//鼠标实例,可以理解为句柄

//***************************************************************************** static unsigned long g_ulMouseInstance; // 注册驱动

USBHCDRegisterDrivers(0, g_ppHostClassDrivers, g_ulNumHostClassDrivers); UARTprintf(\//打开鼠标驱动实例 g_ulMouseInstance =

USBHMouseOpen(MouseCallback, g_pucBuffer, MOUSE_MEMORY_SIZE); UARTprintf(\

MOUSE_MEMORY_SIZE,定义128个字节,USB主机与USB鼠标数据传输的数据缓存大小,该缓存的小大至少是能容纳一个普通的设备描述符报告,所以定义128个字节完全足够;g_pucBuffer[MOUSE_MEMORY_SIZE]定义缓存,用于存放设备描述符报告;DECLARE_EVENT_DRIVER(g_sUSBEventDriver, 0, 0, USBHCDEvents),声明USBHCDEvents的事件驱动;g_ppHostClassDrivers,是设备驱动与事件驱动的集合,用于驱动注册时使用;g_ulNumHostClassDrivers,记录设备驱动与事件驱动个数;g_ulMouseInstance,鼠标实例,可以理解为句柄,是一个32位的无符号数;USBHCDRegisterDrivers,注册事件驱动与设备驱动;USBHMouseOpen,打开鼠标驱动,并

返回一个驱动标号(句柄)g_ulMouseInstance。

unsigned long USBHMouseClose(unsigned long ulInstance)

本函数用于关闭一个已打开的鼠标实例。参数 ulInstance,鼠标实例的标识值,应该为应用程序调用 USBHMouseOpen()函数时返回的值。本函数返回0。

例如:关闭一个已经打开的鼠标实例。

// USB鼠标 接口需要的内存空间

#define MOUSE_MEMORY_SIZE 128

unsigned char g_pucBuffer[MOUSE_MEMORY_SIZE]; // 声明USB驱动,并产生g_sUSBEventDriver

DECLARE_EVENT_DRIVER(g_sUSBEventDriver, 0, 0, USBHCDEvents);

// 注册g_USBHIDClassDriver HID设备类,同时也注册一个g_sUSBEventDriver事件驱动 static tUSBHostClassDriver const * const g_ppHostClassDrivers[] = {

&g_USBHIDClassDriver ,&g_sUSBEventDriver };

//驱动个数

//***************************************************************************** static const unsigned long g_ulNumHostClassDrivers =

sizeof(g_ppHostClassDrivers) / sizeof(tUSBHostClassDriver *);

//鼠标实例,可以理解为句柄

//***************************************************************************** static unsigned long g_ulMouseInstance; // 注册驱动

USBHCDRegisterDrivers(0, g_ppHostClassDrivers, g_ulNumHostClassDrivers); //打开鼠标驱动实例 g_ulMouseInstance =

USBHMouseOpen(MouseCallback, g_pucBuffer, MOUSE_MEMORY_SIZE); .......

//关闭g_ulMouseInstance这个鼠标实例。 USBHMouseClose(g_ulMouseInstance);

unsigned long USBHMouseInit(unsigned long ulInstance)

本函数用于当检测到有一个鼠标实例时,初始化一个鼠标接口,然后向USB主机控制器报告此事件。 ulInstance,鼠标实例的标识值,应该为应用程序调用USBHMouseOpen()函数时返回的值,在调用函数USBHMouseOpen()后接收到 USB_EVENT_CONNECTED事件时,本函数必须调用, 并且每次检测到 USB_EVENT_CONNECTED 事件发生时都要调用一次,不管怎么样,该函数只能在回调函数之外调用。本函数如果返回非零值表明发生了一个错误事件。

USBHMouseInit的函数代码如下:

unsigned long USBHMouseInit(unsigned long ulInstance) {

tUSBHMouse *pUSBHMouse; // 获取鼠标实例的所有数据

pUSBHMouse = (tUSBHMouse *)ulInstance; // 设置一个HID设备空闲

USBHHIDSetIdle(pUSBHMouse->ulMouseInstance, 0, 0);

// 获取一个给定设备实例的报告描述符,并存放于描述符的数据缓存空间内

USBHHIDGetReportDescriptor(pUSBHMouse->ulMouseInstance, pUSBHMouse->pucHeap,

pUSBHMouse->ulHeapSize);

// 函数用于清除或设置一个设备的引导协议状态

USBHHIDSetProtocol(pUSBHMouse->ulMouseInstance, 1); return(0); }

通过USBHMouseInit的函数代码可看出,此函数调用USBHHIDSetIdle使用HID主机处于空闲状态;通过USBHHIDGetReportDescriptor获取报告描述符;USBHHIDSetProtocol,设置设备的引导协议状态。从而完成鼠标实例的初始化。

例如:初始化鼠标实例。

// 鼠标的回调函数

unsigned long MouseCallback(void *pvCBData, unsigned long ulEvent, unsigned long ulMsgParam, void *pvMsgData) {

switch(ulEvent) {

case USB_EVENT_CONNECTED: {

UARTprintf(\ eUSBState = STATE_MOUSE_INIT; break; }

case USB_EVENT_DISCONNECTED: {

UARTprintf(\ eUSBState = STATE_NO_DEVICE; break; } ......

return(0); }

while(1) {

switch(eUSBState) {

case STATE_MOUSE_INIT: { //初始化鼠标 UARTprintf(\ USBHMouseInit(g_ulMouseInstance); eUSBState = STATE_MOUSE_CONNECTED; break; }

case STATE_MOUSE_CONNECTED: { UARTprintf(\ break; }

......

} //循环调用USB主机控制单元 USBHCDMain(); }

USBHMouseInit用于初始化一个鼠标实例,必须在检测到有

USB_EVENT_CONNECTED事件时调用此函数,即使是MouseCallback内检测

USB_EVENT_CONNECTED事件,也不能直接在MouseCallback鼠标回调函数内调用,应

该通过修改标志位,在MouseCallback之外调用USBHMouseInit。例如上面代码,

MouseCallback函数检测到USB_EVENT_CONNECTED事件时,修改eUSBState标志位为 STATE_MOUSE_INIT,在主函数中,检测到eUSBState标志位为STATE_MOUSE_INIT时,调用USBHMouseInit,初始化鼠标实例,之后修改eUSBState 标志位为

STATE_MOUSE_CONNECTED,表示鼠标连接成功,可以使用。注意,每次MouseCallback检测到有USB_EVENT_CONNECTED事件发生时,都要调用USBHMouseInit对鼠标进行初始化。

通过以上三个函数,就可以完成USB鼠标的初始化与控制,那么USB鼠标数据(X,Y,按键)怎么传递给应用程序呢?

在调用USBHMouseOpen打开鼠标驱动实例时,其第一个参数MouseCallback,用于USB鼠标事件回调,所有鼠标事件与数据都通过此函数传递。此函数的函数原型如下:

unsigned long MouseCallback(void *pvCBData, unsigned long ulEvent, unsigned long ulMsgParam,void *pvMsgData)

其中,pvCBData为回调函数数据指针,此数据由内部函数给出,通常值为0;ulMsgParam 一个事件的具体参数,能过它检测状态:

USBH_EVENT_HID_MS_PRESS //按键按下 USBH_EVENT_HID_MS_REL //按键放开 USBH_EVENT_HID_MS_X //X值发生变化 USBH_EVENT_HID_MS_Y //Y值发生变化 pvMsgData,一个事件特定的数据指针。USBH_EVENT_HID_MS_PRESS事件发生时,pvMsgData提供按下按键的值;USBH_EVENT_HID_MS_REL事件发生时,提供哪些按键松开;USBH_EVENT_HID_MS_X事件发生时,提供当前X值;USBH_EVENT_HID_MS_Y事件发生时,提供当前Y值。这个回调函数用于设备类驱动和主机管理。

本小结主要讲解USB鼠标的控制、数据读取。本小节的USB鼠标主机控制的随书源码请见光盘或与作者联系。

11.3.2 键盘

本节主要介绍支持USB键盘的USB主机开发,在USB库中已经编写好USB键盘驱动,供USB主机使用,相关函数包含在“usbhhidkeyboard.h”头文件中。主机包含6个功能函数,实现对键盘的数据读取、操作:USBHKeyboardOpen、USBHKeyboardClose、USBHKeyboardInit、USBHKeyboardModifierSet、USBHKeyboardPollRateSet、USBHKeyboardUsageToChar。在USB库中,默认报告为8个字节。如果有必要,可以打开USBlib源码,在usbhhidkeyboard.c中修改“#define USBHKEYB_REPORT_SIZE 8”为键盘报告的实际字节数,由USB库做的USB键盘默认为8个字节。

unsigned long USBHKeyboardOpen(tUSBCallback pfnCallback,

unsigned char *pucBuffer, unsigned long ulBufferSize); unsigned long USBHKeyboardClose(unsigned long ulInstance); unsigned long USBHKeyboardInit(unsigned long ulInstance);

unsigned long USBHKeyboardModifierSet(unsigned long ulInstance,

unsigned long ulModifiers); unsigned long USBHKeyboardPollRateSet(unsigned long ulInstance,

unsigned long ulPollRate); unsigned long USBHKeyboardUsageToChar( unsigned long ulInstance,

const tHIDKeyboardUsageTable *pTable, unsigned char ucUsageID);

unsigned long USBHKeyboardOpen(tUSBCallback pfnCallback,

unsigned char *pucBuffer, unsigned long ulBufferSize);

此函数用于打开一个键盘实例。pfnCallback,回调函数,一但发生一个新的键盘事件时会调用此回调函数;*pucBuffer,USB主机与USB键盘数据传输的数据缓存。该缓存的小大至少是能容纳一个普通的设备描述符报告,如果没有足够的空间,则只有部分报告能够读取,一般定义为128即可满足需求;ulSize,USB主机与USB键盘数据传输的数据缓存大小。本函数主要用于打开一个键盘实例。本函数返回的值用于区别其他所有USB键盘的“句柄”,一个常量对应一个键盘实例,如果没有键盘实例,返回0。

USBHKeyboardOpen的函数代码如下:

unsigned long USBHKeyboardOpen(tUSBCallback pfnCallback, unsigned char *pucBuffer, unsigned long ulSize) {

// 外部键盘回调函数传递给全局键盘结构体的回调函数,g_sUSBHKeyboard.pfnCallback g_sUSBHKeyboard.pfnCallback = pfnCallback;

// 打开USBHID实例,用于初始化HID设备层。 g_sUSBHKeyboard.ulHIDInstance =

USBHHIDOpen(USBH_HID_DEV_KEYBOARD, USBHKeyboardCallback, (unsigned long)&g_sUSBHKeyboard);

return((unsigned long)&g_sUSBHKeyboard); }

通过USBHKeyboardOpen的函数代码可看出,此函数通过USBHHIDOpen初始化USBHID驱动层,并返回键盘实例,同时USBHKeyboardOpen函数完成键盘实例g_sUSBHKeyboard的初始化,其中g_sUSBHKeyboard主要保存与键盘实例相关参数与函数,包含键盘实例的所有数据。

例如:打开一个键盘实例

// USB键盘 接口需要的内存空间

#define KEYBOARD_MEMORY_SIZE 128 // 分配内存单元给键盘数据存储

unsigned char g_pucBuffer[KEYBOARD_MEMORY_SIZE]; // 声明USB驱动,并产生g_sUSBEventDriver

DECLARE_EVENT_DRIVER(g_sUSBEventDriver, 0, 0, USBHCDEvents);

// 注册g_USBHIDClassDriver HID设备类,同时也注册一个g_sUSBEventDriver事件驱动 static tUSBHostClassDriver const * const g_ppHostClassDrivers[] = {

&g_USBHIDClassDriver ,&g_sUSBEventDriver };

static const unsigned long g_ulNumHostClassDrivers =

sizeof(g_ppHostClassDrivers) / sizeof(tUSBHostClassDriver *); //键盘实例,可以理解为句柄

static unsigned long g_ulKeyboardInstance; // 注册驱动

USBHCDRegisterDrivers(0, g_ppHostClassDrivers, g_ulNumHostClassDrivers); // 输出信息

UARTprintf(\// 打开键盘驱动,初始化驱动

g_ulKeyboardInstance = USBHKeyboardOpen(KeyboardCallback, g_pucBuffer,

KEYBOARD_MEMORY_SIZE);

KEYBOARD_MEMORY_SIZE,定义128个字节,USB主机与USB键盘数据传输的数据缓存大小,该缓存的小大至少是能容纳一个普通的设备描述符报告,所以定义128个字节完全足够;g_pucBuffer[KEYBOARD_MEMORY_SIZE]定义缓存,用于存放设备描述符报告;DECLARE_EVENT_DRIVER(g_sUSBEventDriver, 0, 0, USBHCDEvents),声明USBHCDEvents的事件驱动;g_ppHostClassDrivers,是设备驱动与事件驱动的集合,用于驱动注册时使用;g_ulNumHostClassDrivers,记录设备驱动与事件驱动个数;

g_ulKeyboardInstance ,键盘实例,可以理解为句柄,是一个32位的无符号数;USBHCDRegisterDrivers,注册事件驱动与设备驱动;USBHKeyboardOpen,打开键盘驱动,并返回一个驱动标号(句柄)g_ulKeyboardInstance 。

unsigned long USBHKeyboardClose(unsigned long ulInstance)

本函数用于关闭一个已打开的键盘实例。参数 ulInstance,键盘实例的标识值,应该为应用程序调用 USBHKeyboardOpen()函数时返回的值。本函数返回0。

例如:关闭一个已经打开的键盘实例。

// USB键盘 接口需要的内存空间

#define KEYBOARD_MEMORY_SIZE 128 // 分配内存单元给键盘数据存储

unsigned char g_pucBuffer[KEYBOARD_MEMORY_SIZE]; // 声明USB驱动,并产生g_sUSBEventDriver

DECLARE_EVENT_DRIVER(g_sUSBEventDriver, 0, 0, USBHCDEvents);

// 注册g_USBHIDClassDriver HID设备类,同时也注册一个g_sUSBEventDriver事件驱动 static tUSBHostClassDriver const * const g_ppHostClassDrivers[] = {

&g_USBHIDClassDriver ,&g_sUSBEventDriver };

static const unsigned long g_ulNumHostClassDrivers =

sizeof(g_ppHostClassDrivers) / sizeof(tUSBHostClassDriver *); //键盘实例,可以理解为句柄

static unsigned long g_ulKeyboardInstance; // 注册驱动

USBHCDRegisterDrivers(0, g_ppHostClassDrivers, g_ulNumHostClassDrivers); // 输出信息

UARTprintf(\// 打开键盘驱动,初始化驱动

g_ulKeyboardInstance = USBHKeyboardOpen(KeyboardCallback, g_pucBuffer, KEYBOARD_MEMORY_SIZE);

.......

//关闭g_ulKeyboardInstance 这个键盘实例。 USBHKeyboardClose(g_ulKeyboardInstance );

unsigned long USBHKeyboardInit(unsigned long ulInstance)

本函数用于当检测到有一个键盘实例时,初始化一个键盘接口,然后向USB主机控制器报告此事件。 ulInstance,键盘实例的标识值,应该为应用程序调用USBHKeyboardOpen()函数时返回的值,在调用函数USBHKeyboardOpen()后接收到 USB_EVENT_CONNECTED事件时,本函数必须调用, 并且每次检测到 USB_EVENT_CONNECTED 事件发生时都要调用一次,不管怎么样,该函数只能在回调函数之外调用。本函数如果返回非零值表明发生了一个错误事件。

USBHKeyboardInit的函数代码如下:

unsigned long USBHKeyboardInit(unsigned long ulInstance) {

unsigned char ucModData;

tUSBHKeyboard *pUSBHKeyboard; // 获取键盘实例数据

pUSBHKeyboard = (tUSBHKeyboard *)ulInstance; //HID主机设为空闲

USBHHIDSetIdle(pUSBHKeyboard->ulHIDInstance, 0, 0); // 获取报告描述符

USBHHIDGetReportDescriptor(pUSBHKeyboard->ulHIDInstance,

pUSBHKeyboard->pucBuffer, USBHKEYB_REPORT_SIZE);

// 设置启动协议

USBHHIDSetProtocol(pUSBHKeyboard->ulHIDInstance, 1); ucModData = 0;

//通过报告描述符,设置键盘灯。

USBHHIDSetReport(pUSBHKeyboard->ulHIDInstance, 0, &ucModData, 1); return(0); }

通过USBHKeyboardInit的函数代码可看出,此函数调用USBHHIDSetIdle使用HID主机处于空闲状态;通过USBHHIDGetReportDescriptor获取报告描述符;USBHHIDSetProtocol,设置设备的引导协议状态。通过USBHHIDSetReport设置报告描述符,控制键盘灯。从而完成键盘实例的初始化。

例如:初始化键盘实例。

unsigned long KeyboardCallback(void *pvCBData, unsigned long ulEvent, unsigned long ulMsgParam, void *pvMsgData) {

unsigned char ucChar; switch(ulEvent) {

// 检测连接

case USB_EVENT_CONNECTED: {

UARTprintf(\ g_eUSBState = STATE_KEYBOARD_INIT; break; }

//键盘移出

case USB_EVENT_DISCONNECTED: {

UARTprintf(\ g_eUSBState = STATE_NO_DEVICE; g_ulModifiers = 0; break; }

// 按键按下

case USBH_EVENT_HID_KB_PRESS: {

// Caps Lock 状态改变

if(ulMsgParam == HID_KEYB_USAGE_CAPSLOCK) {

g_eUSBState = STATE_KEYBOARD_UPDATE; g_ulModifiers ^= HID_KEYB_CAPS_LOCK; } else {

// backspace 按键

if((unsigned char)ulMsgParam == HID_KEYB_USAGE_BACKSPACE) {

ucChar = ASCII_BACKSPACE; } else {

//获取按键ASCII码

ucChar = (unsigned char)

USBHKeyboardUsageToChar(g_ulKeyboardInstance, &g_sUSKeyboardMap,

(unsigned char)ulMsgParam); } }

break; }

case USBH_EVENT_HID_KB_MOD: {

//your code break; }

case USBH_EVENT_HID_KB_REL: {

//your code break; } }

return(0); }

// 应用程序 ....... while(1) {

switch(g_eUSBState) {

// 第一次插入键盘,用于初始化。 case STATE_KEYBOARD_INIT: {

// 键盘初始化

USBHKeyboardInit(g_ulKeyboardInstance); // 改变当前状为已经连接到键盘

g_eUSBState = STATE_KEYBOARD_CONNECTED; break; }

case STATE_KEYBOARD_UPDATE: {

// 已经有键盘数据到达

g_eUSBState = STATE_KEYBOARD_CONNECTED; //定键盘的按键状态,g_ulModifiers为功能键状态

USBHKeyboardModifierSet(g_ulKeyboardInstance, g_ulModifiers); break; }

case STATE_KEYBOARD_CONNECTED: {

//空闲状态 break; }

case STATE_UNKNOWN_DEVICE: {

//位置设备 break; }

case STATE_NO_DEVICE: {

//无设备 break; }

default: {

break; } }

//定时调用USB主控 USBHCDMain(); }

USBHKeyboardInit用于初始化一个键盘实例,必须在检测到有USB_EVENT_CONNECTED事件时调用此函数,即使是KeyboardCallback内检测USB_EVENT_CONNECTED事件,也不能直接在KeyboardCallback键盘回调函数内调用,应该通过修改标志位,在KeyboardCallback之外调用USBHKeyboardInit。例如上面代码,KeyboardCallback函数检测到USB_EVENT_CONNECTED事件时,修改g_eUSBState标志位为 STATE_KEYBOARD_INIT,在主函数中,检测到g_eUSBState标志位为STATE_KEYBOARD_INIT时,调用USBHKeyboardInit,初始化键盘实例,之后修改g_eUSBState标志位为STATE_KEYBOARD_CONNECTED,表示键盘连接成功,可以使用。注意,每次KeyboardCallback检测到有USB_EVENT_CONNECTED事件发生时,都要调用USBHKeyboardInit对键盘进行初始化。

通过以上三个函数,就可以完成USB键盘的初始化与控制,那么USB键盘数据(按键按下、松开等)怎么传递给应用程序呢?

在调用USBHKeyboardOpen打开键盘驱动实例时,其第一个参数KeyboardCallback,用于USB键盘事件回调,所有键盘事件与数据都通过此函数传递。此函数的函数原型如下:

unsigned long KeyboardCallback(void *pvCBData, unsigned long ulEvent, unsigned long ulMsgParam,void *pvMsgData)

其中,pvCBData为回调函数数据指针,此数据由内部函数给出,通常值为0;ulMsgParam 一个事件的具体参数, pvMsgData,一个事件特定的数据指针。

unsigned long USBHKeyboardModifierSet ( unsigned long ulInstance, unsigned long ulModifiers )

这个函数用来设定键盘的按键状态,ulInstance键盘的实例,通过调用USBHKeyboardOpen() 的返回值。ulModifiers键盘要修改的位屏蔽,可以使用以下的值:

HID_KEYB_NUM_LOCK HID_KEYB_CAPS_LOCK HID_KEYB_SCROLL_LOCK HID_KEYB_COMPOSE HID_KEYB_KANA

以上值支持所有键盘,如果键盘不支持,则将忽略。如果函数调用成功返回0,否则返回非0,并指示错误状态。 注意:本函数主要用于修改大小写键盘灯状态。

unsigned long USBHKeyboardPollRateSet ( unsigned long ulInstance, unsigned long ulPollRate )

这个函数用来设定键盘的查询速率,ulInstance 指向一个键盘实例;ulPollRate 键盘查询速率,以ms为单位。这个函数允许应用程序设定键盘查询速率, ulInstance 是从 USBHKeyboardOpen() 函数返回值。 ulPollRate 这个是新的查询速率,以ms为单位。这个值初始化的时为0,表示键盘指示在状态更新时改变,任何正数,都会产生一个自动重复的查询。如果调用成功返回0,否则返回非0,并指示错误状态。

unsigned long USBHKeyboardUsageToChar ( unsigned long ulInstance, const tHIDKeyboardUsageTable * pTable, unsigned char ucUsageID )

这个函数用来映射一个UsageID到一个可以打印输出的字符。 ulInstance这个键盘的实例;pTable返回可以打印输出的字符;ucUsageID要映射的UsageID。输出 pTable 字符,这个字符定义 tHIDKeyboardUsageTable 定义的结构体,可以查看文档获取tHIDKeyboardUsageTable这个结构体的更多细节问题。这个函数使用当前shift、Caps Lock的状态返回修改的数据,按键的ASCII码。

例如:获取按键ASCII码 switch(ulEvent)

{

// 检测连接

case USB_EVENT_CONNECTED: {

UARTprintf(\ g_eUSBState = STATE_KEYBOARD_INIT; break; }

//键盘移出

case USB_EVENT_DISCONNECTED: {

UARTprintf(\ g_eUSBState = STATE_NO_DEVICE; g_ulModifiers = 0; break; }

// 按键按下

case USBH_EVENT_HID_KB_PRESS: {

// Caps Lock 状态改变

if(ulMsgParam == HID_KEYB_USAGE_CAPSLOCK) {

g_eUSBState = STATE_KEYBOARD_UPDATE; g_ulModifiers ^= HID_KEYB_CAPS_LOCK; } else {

// backspace 按键

if((unsigned char)ulMsgParam == HID_KEYB_USAGE_BACKSPACE) {

ucChar = ASCII_BACKSPACE; } else {

//获取按键ASCII码

ucChar = (unsigned char)

USBHKeyboardUsageToChar(g_ulKeyboardInstance, &g_sUSKeyboardMap,

(unsigned char)ulMsgParam); } }

break; }

.......

}

本小结主要讲解USB键盘的控制、数据读取。本小节的USB键盘主机控制的随书源码请见光盘或与作者联系。

11.3.3 U盘

本节主要介绍支持U盘的USB主机开发,在USB库中已经编写好U盘驱动,供USB主机使用,相关函数包含在“usbhmsc.h”头文件中。主机包含5个功能函数,实现对U盘的数据读取、操作:USBHMSCDriveOpen、USBHMSCDriveClose、USBHMSCDriveReady、USBHMSCBlockRead、USBHMSCBlockWrite。函数原型如下:

unsigned long USBHMSCDriveOpen(unsigned long ulDrive,

tUSBHMSCCallback pfnCallback); void USBHMSCDriveClose(unsigned long ulInstance); long USBHMSCDriveReady(unsigned long ulInstance);

long USBHMSCBlockRead(unsigned long ulInstance, unsigned long ulLBA, unsigned char *pucData, unsigned long ulNumBlocks);

long USBHMSCBlockWrite(unsigned long ulInstance, unsigned long ulLBA, unsigned char *pucData,

unsigned long ulNumBlocks);

unsigned long USBHMSCDriveOpen(unsigned long ulDrive,

tUSBHMSCCallback pfnCallback); 这个函数被调用来打开一个大容量存储设备实例。在任何设备连接之前,允许驱动器连接和断开。ulDrive是一个为0的驱动器在系统中存在的索引。如果有一个系统中的USB集线器中有固定的驱动器数量,这个数字应该只大于0。应用程序必须提供pfnCallback回调函数,如设备的枚举和移动存储设备相关的事件。这个函数将返回驱动程序实例编号,如果没有驱动程序,此函数将返回零。

void USBHMSCDriveClose ( unsigned long ulInstance)

这个函数被调用时,MSC驱动器是要处于关闭或切换到USB设备模式。 long USBHMSCDriveReady ( unsigned long ulInstance)

这个函数检查驱动器是否准备好访问。ulInstance,以确定哪个设备已准备就绪。任何非零的返回表明该设备还没有准备好或者发生错误。

long USBHMSCBlockRead ( unsigned long ulInstance,

unsigned long ulLBA, unsigned char * pucData,

unsigned long ulNumBlocks)

这个函数执行MSC设备的读取。这个函数将从ulInstance指定的U盘实例中读取相关数据。参数ulLBA指定了读取设备的逻辑块地址。此功能将只按照ulNumBlocks块大小读取, 在大多数情况下,每次读取512字节的数据。 在缓冲*pucData中至少包含ulNumBlocks * 512 个字节的数据。该函数返回零表示成功,返回负数表示失败。

long USBHMSCBlockWrite ( unsigned long ulInstance,

unsigned long ulLBA, unsigned char * pucData,

unsigned long ulNumBlocks )

这个函数执行MSC设备的写入。这个函数将从ulInstance指定的U盘实例中写入相关数据。参数 ulLBA 指定了写入设备的逻辑块地址。此功能将只按照ulNumBlocks块大小写入,在大多数情况下,每次写入512字节的数据。 在缓冲*pucData中至少包含ulNumBlocks * 512 个字节的数据空间。该函数返回零表示成功,返回负数表示失败。

例如:U盘初始化。

//FAT文件系统。

static FATFS g_sFatFs;

#define HCD_MEMORY_SIZE 128

unsigned char g_pHCDPool[HCD_MEMORY_SIZE]; //USB盘实例

unsigned long g_ulMSCInstance = 0; // 声明USB事件驱动接口

DECLARE_EVENT_DRIVER(g_sUSBEventDriver, 0, 0, USBHCDEvents); // 主机驱动

static tUSBHostClassDriver const * const g_ppHostClassDrivers[] = {

&g_USBHostMSCClassDriver ,&g_sUSBEventDriver };

static const unsigned long g_ulNumHostClassDrivers =

sizeof(g_ppHostClassDrivers) / sizeof(tUSBHostClassDriver *); #define FLAGS_DEVICE_PRESENT 0x00000001 // Holds global flags for the system. int g_ulFlags = 0;

//文件夹深度及文件名长度

#define MAX_DIR_DEPTH 10

#define MAX_FILE_NAME_LEN (8 + 1 + 3 + 1) // 8.1 + 1 // 文件夹数据结构 struct {

FILINFO FileInfo[20]; unsigned long ulIndex;

unsigned long ulSelectIndex; unsigned long ulValidValues; DIR DirState;

char szPWD[MAX_DIR_DEPTH * MAX_FILE_NAME_LEN]; u8 ucAuth[20]; }

g_DirData;

//设备状态 volatile enum {

STATE_NO_DEVICE, STATE_DEVICE_ENUM, STATE_DEVICE_READY, STATE_UNKNOWN_DEVICE, STATE_POWER_FAULT }

g_eState;//DMA使用

tDMAControlTable g_sDMAControlTable[6] __attribute__ ((aligned(1024))); //初始化 FileSystem,Mount到硬盘0。 tBoolean FileInit(void) {

if(f_mount(0, &g_sFatFs) != FR_OK) {

return(false); }

return(true); }

//U盘回调函数

void MSCCallback(unsigned long ulInstance, unsigned long ulEvent, void *pvData) {

switch(ulEvent) {

case MSC_EVENT_OPEN:

{

g_eState = STATE_DEVICE_ENUM; break; }

case MSC_EVENT_CLOSE: {

g_eState = STATE_NO_DEVICE; FileInit(); break; }

default: {

break; } } }

//事件

void USBHCDEvents(void *pvData) {

tEventInfo *pEventInfo;

pEventInfo = (tEventInfo *)pvData;

switch(pEventInfo->ulEvent) {

case USB_EVENT_CONNECTED: {

//连接成功 UARTprintf(\ g_eState = STATE_UNKNOWN_DEVICE; break; }

case USB_EVENT_DISCONNECTED: {

//设备移出 UARTprintf(\ g_eState = STATE_NO_DEVICE; break; }

case USB_EVENT_POWER_FAULT: {

//电源错误

g_eState = STATE_POWER_FAULT; break; }

default: {

break; } } }

//main函数 int main(void) {

FRESULT FileResult; FIL file,file1; DIR CurrentDir; s8 getbuff[10];

BYTE ucBuff[200]=\paul!\\r\\nThis is a File System example!\\r\\nCopyright @ \\

2010\\r\\n/*-----------------------------------------------------*/\\r\\n\ char Buff[200]; BYTE ucCnt=0;

WORD ulSize = 200; WORD x; WORD i;

g_eState = STATE_NO_DEVICE;

SysCtlClockSet(SYSCTL_SYSDIV_4 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | SYSCTL_XTAL_8MHZ); UARTStdioInit(0); //DMA控制

SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA); uDMAEnable();

uDMAControlBaseSet(g_sDMAControlTable); //注册Host驱动

UARTprintf(\

USBHCDRegisterDrivers(0, g_ppHostClassDrivers, g_ulNumHostClassDrivers); // 开打实例

g_ulMSCInstance = USBHMSCDriveOpen(0, MSCCallback); // 初始化电源

USBHCDPowerConfigInit(0, USBHCD_VBUS_AUTO_HIGH); // 初始化主机控制

USBHCDInit(0, g_pHCDPool, HCD_MEMORY_SIZE);

UARTprintf(\ FileInit();

strcpy((s8 *)g_DirData.ucAuth,\ while(1) {

USBHCDMain(); switch(g_eState) {

case STATE_DEVICE_ENUM: {

UARTprintf(\ if(USBHMSCDriveReady(g_ulMSCInstance) != 0) {

SysCtlDelay(SysCtlClockGet()/30); break; }

FileResult = f_opendir(&g_DirData.DirState, \ if(FileResult == FR_OK) {

g_DirData.ulIndex = 0;

g_DirData.ulSelectIndex = 0; g_DirData.ulValidValues = 0; g_eState = STATE_DEVICE_READY; UARTprintf(\ } else if(FileResult != FR_NOT_READY) { } memset(g_DirData.szPWD,0,MAX_DIR_DEPTH * MAX_FILE_NAME_LEN); g_ulFlags = FLAGS_DEVICE_PRESENT; break; } //可以在此读写U盘

case STATE_DEVICE_READY:

{ UARTprintf(\

f_open(&file, \ f_write(&file, ucBuff,sizeof(ucBuff), &x); .......... break; } //没有设备

case STATE_NO_DEVICE: {

if(g_ulFlags == FLAGS_DEVICE_PRESENT) { UARTprintf(\ ucCnt = 0;

g_ulFlags &= ~FLAGS_DEVICE_PRESENT; }

break; }

//未知设备插入.

case STATE_UNKNOWN_DEVICE: {

if((g_ulFlags & FLAGS_DEVICE_PRESENT) == 0) { }

g_ulFlags = FLAGS_DEVICE_PRESENT; break; }

//电源故障

case STATE_POWER_FAULT: {

break; }

default: {

break; } } } }

本小节主要讲解U盘的控制、数据读取。以上列出部分源代码,关于底层文件驱动没有在本书列出,更多U盘主机控制的随书源码请见光盘或与作者联系。

11.4 小结

硬件与软件 USB的互连支持数据在USB主机与USB设备之间的流动。这一章主要讲述为了简化主机上的客户软件与设备的功能部件之间的通信而必须的主机接口。通过3个实例介绍使用USB库对LM3S系列处理器进行USB主机开发及相关流程。完成本章学习,可以开发简单的鼠标、键盘、U盘的主机接口,还可在此基础上进一步扩展,完成更复杂的USB系统。

本文来源:https://www.bwwdw.com/article/tnjx.html

Top