广州周立功单片机发展有限公司 Tel,(020) 38730916 38730917 38730976 38730977 Fax,38730925
USB驱动程序简要说明及应用例子
USB 总线主要用于 USB 设备与主机之间的数据通信,特别为 USB 设备 与主机之间大量数据的传输提供了高速、可靠的传输协议。例如:在嵌入式系统中,可以利用 USB 设备与微控制器构成 USB 设备 。 USB 设备 与 PC 机 USB 主控器 相连就可以实现嵌入式系统与
PC 机之间的通信了,也就可以实现诸如 U 盘、移动硬盘,USB 接口打印机等功能。
本驱动程序利用 Philips 公司生产的 USB 芯片 PDIUSBD12 与微控制器构成 USB 设备,
在 μC/OS-II 操作系统上实现该 USB 设备与 PC 机通信的 USB 协议,为用 户提供与 PC 机通信的 API 函数。
下面先简单介绍 USB 芯片 PDIUSBD12。
1.1 PDIUSBD12 简介
PDIUSBD12 是一款性价比很高的 USB 器件,完 全符合 USB1.1 版的规范 。 具有以下特性,
null 高性能 USB 接口器件,集成了 SIE,FIFO 存储器、收发器以及电压调整器;
null 可与任何外部微控制器/微处理实现高速并行接口( 2M 字节/秒);
null 完全自治的直接内存存取( DMA)操作;
null 集成 320 字节多结构 FIFO 存储器;
null 在批量模式和同步模式下均可实现 1M 字节/秒的数据传输速率;
null 在挂起时可控制 LazyClock 输出;
null 采用 GoodLink 技术的连接指示器,在通讯时使 LED 闪烁;
null 可编程的时钟频率输出;
null 符合 ACPI,OnNOW 和 USB 电源管理的要求;
null 内部上电复位和低电压复位电路;
null 有 SO28 和 TSSOP28 封装;
null 工业级操作温度:- 40℃~ +85℃;
null 高于 8kV 的在片静电防护电路,减少了额外元件的费用;
null 具有高错误恢复率( >99%)的全扫描设计确保了高品质;
null 双电源操作,3.3±0.3V 或扩展的 5V 电源,范围为 3.6~5.5V;
PDIUSBD12 管脚排列及内部功能框图如图 1 所示,表 1 为 PDIUSBD12 的管脚说明。
1
2
3
4
5
6
7
8
9
10
11
12 17
18
19
20
21
22
23
24
25
26
27
28DATA<0>
DATA<1>
DATA<2>
DATA<3>
GND
DATA<4>
DATA<5>
DATA<6>
DATA<7>
ALE
CS_N
A0
V
OUT3.3
D+
D–
V
DD
XTAL2
XTAL1
RESET_N
GL_N
EOT_N
DMACK_N
DMREQSUSPEND
13 16 WR_NCLKOUT
14 15 RD_NINT_N
控制模拟收发 器上 游端口
PLL锁相环位 时钟恢复
PHILIPS
串行 接口引 擎电 压调整器
SoftConnect
内部RAM
存储 器管 理单元并行和DMA
接口
图 1 PDIUSBD12 管脚排列及内部功能框图
- 1 -
广州周立功单片机发展有限公司 Tel,(020) 38730916 38730917 38730976 38730977 Fax,38730925
如图 1 所示,PDIUSBD12 芯片内部集成了串行接口引擎 SIE,存储器管理单元 ( MMU)
和集成 RAM、模拟收发器以及电压调整器。可与任何外部微处理器实现高速数据传输的并行接口( 2MB/s) 。 PDIUSBD12 芯片提 供 3 个端点,其中主端点(端点 2) 的双缓冲配置增加了数据吞吐量并轻松实现实时数据传输。
PDIUSBD12 采用 GoodLink技术的连接 指示器,在通讯时使 LED闪烁,为通信状态提供了用户友好的指示,方便 用户调试。 SoftConnect
TM
技术允许系统微控制器可控制 USB总线重新连接和重新初始化而不需要拔出电缆。
PDIUSBD12 的详细介绍请见北航出版的<< PDIUSBD12 USB 固件编程与驱动开发>>或到 http://www.zlgmcu.com 网站下载 PDIUSBD12 使用手册。
表 1 PDIUSBD12 管脚描述
管脚 符 号 类型 描 述
1 DATA<0> IO2 双向数据位 0
2 DATA<1> IO2 双向数据位 1
3 DATA<2> IO2 双向数据位 2
4 DATA<3> IO2 双向数据位 3
5 GND P 地
6 DATA<4> IO2 双向数据位 4
7 DATA<5> IO2 双向数据位 5
8 DATA<6> IO2 双向数据位 6
9 DATA<7> IO2 双向数据位 7
10 ALE I
地址锁存使能。 在多路地址 /数据总线中,下降沿关闭地址信息锁存。
将其固定为低电平用于单地址 /数据总线配置。
11 CS_N I 片选(低有效)
12 SUSPEND I,OD4 器件挂起状态,高电平表示器件处于挂起状态
13 CLKOUT O2 可编程时钟输出(可控分频)
14 INT_N OD4 中断(低有效)
15 RD_N I 读选通(低有效)
16 WR_N I 写选通(低有效)
17 DMREQ O4 DMA 请求
18 DMACK_N I DMA 应答(低有效)
19 EOT_N I
DMA 传输结束 (低有效) 。 EOT_N 仅当 DMACK_N 和 RD_N 或
WR_N 一起激活时才有效。
20 RESET_N I 复位 (低有效且不同步) 。 片内上电复位电路,该管脚可固定接 V
CC

21 GL_N OD8 GoodLink LED 指示器(低有效)
22 XTAL1 I 晶振连接端 1( 6MHz)
23 XTAL2 O
晶振连接端 2( 6MHz) 。如果采用外部时钟信号取代晶振,可连接
XTAL1,XTAL2 应当悬空。
24 V
CC
P
电源电压( 4.0V~5.5V),要使器件工作在 3.3V,对 V
CC
和 V
OUT3.3
脚都提供 3.3V。
25 D- A USB D-数据线
26 D+ A USB D+数据线
- 2 -
广州周立功单片机发展有限公司 Tel,(020) 38730916 38730917 38730976 38730977 Fax,38730925
(接上表)
27 V
OUT3.3
P
3.3V调整 输出 。要使 器件 工 作 在 3.3V,对 V
CC
和 V
OUT3.3
脚都提供
3.3V。
28 A0 I
地址位。 A0=1 选择命令指令,A0=0 选择数据。该位在多路地址 /
数据总线配置时可忽略,应将其接高电平。
注,O2,2mA 驱动输出 OD4,4mA 驱动开漏输出
OD8,8mA 驱动开漏输出 IO2,4mA 输出
在没有使用到 DMA 方式的时候,DMACK_N 和 EOT_N 要求接上拉电阻。
明白了 PDIUSBD12 所提 供的资源与硬件接口,便可以设计 PDIUSBD12 与 LPC2200 的硬件电路了。
1.2 PDIUSBD12 与 LPC2200 的硬件电路设计
这里以 LPC2200 微控制器 与 PDIUSBD12 构成 USB 设备,PDIUSBD12 连接 到 LPC2200
的硬件原理图如图 2 所 示,由该图 可见 PDIUSBD12 各引脚 (图 2 中的网 络标号) 与 LPC2200
的连接关系,如表 2 所示。
表 2 PDIUSBD12 与 LPC2200 连接关系
PDIUSBD12 功 能 LPC2200
D0 ~ D7 PDIUSBD12 数据总线 D0 ~ D7
AD0 PDIUSBD12 地址总线 A0
CS_USB PDIUSBD12 片选线 nCS2
RD PDIUSBD12 读使能 (低电平有效 ) nOE
WR PDIUSBD12 写使能 (低电平有效 ) nEW
INT_USB PDIUSBD12 中断输出信号 P0.16_EINT0
RST_USB PDIUSBD12 复位输入信号 P0.10_RTS1
SUSP PDIUSBD12 挂起输入信号 P0.13_DTR1
由以上关系,可知 PDIUSBD12 使用 LPC2200 外 部存储控制的 Bank2 部 分,其地址如下,
数据地址 0x82000000(偶数地址)
命令地址 0x82000001(奇数地址)
RST_USB,SUSP 为 LPC2200 输出引脚,PDIUSBD12 中断信号为 中断输入信号,且为外部中断 0。
如图 2 所示 C1,C2 和 X1 为 PDIUSBD12 提供工作 所 需的时钟 输 入;发光 二 极 管
GoodLink 在 正常通信时闪烁; PDIUSBD12 的 AD0 连接到 LPC2200 的 A0,当 LPC2200 在
A0 引脚输出 1 时,表示输出到 PDIUSBD12 数据总 线 D0 ~ D7 上的数据为命令字,当 A0
引脚输出 0 时,表示输出到 PDIUSBD12 数据总线 D0 ~ D7 的数据为数据字。
- 3 -
广州周立功单片机发展有限公司 Tel,(020) 38730916 38730917 38730976 38730977 Fax,38730925
D12XTAL2
D12XTAL1
CS_USB
USBDP
/GOODLNK
USBDM
RST_USB
D12DM
D12DP
AD0
INT_USB
X1
6MHz
VBUS
1
D-
2
D+
3
GND
4
SHIELD
5
CZ1
US B
R7
1K
R3
18R+/-1%
R2
18R+/-1%
C5
C1
68pF
C2
22pF
R5
1M
R4
1M
C3
470pF
C6
0.1uF
+
C4
105
+
C7
4.7u16V
R1
10K
FB1
FB2
VCC33
VCC33
VCC33
RD
WR
DATA0
1
DATA1
2
DATA2
3
DATA3
4
GND
5
DATA4
6
DATA5
7
DATA6
8
DATA7
9
ALE
10
CS_N
11
SUSPEND
12
CLKOUT
13
INT_N
14
RD_N
15
WR_N
16
DMREQ
17
DMACK_N
18
EOT_N
19
RESET_N
20
GL_N
21
XTAL1
22
XTAL2
23
VCC
24
D-
25
D+
26
VOUT3.3V
27
A0
28
U1
PDIUSBD12(TSSOP-28)
L1
GOODLINK
VBUS
USB-
USB+
SUSP
R6 1M
VCC33
C8
104
R8
1M
D0
D1
D2
D3
D4
D5
D6
D7
GND
R9 10K
CBG201209U151B
CBG201209U151B
104
R10 10K
VCC33
INT_USB
SUSP
R11 10K
图 2 PDIUSBD12 与 LPC2200 硬件连接原理图
1.3 PDIUSBD12 驱动软件构架
为了使驱动软件可移植性强、易维护,采用分层的方法编写 PDIUSBD12 的驱动程 序。
综合考虑 USB 协议,PDIUSBD12 硬件接线,μC/OS-II 的结构 来组织驱动软件的构架,如表
3 所示为 USB 驱动程序软件分层结构表。
表 3 USB 驱动程序软件包分层结构表
文件名 简要说明 相关性
D12HAL.c PDIUSBD12 与 MCU 之间的硬件接口 与硬件相关
D12CI.c PDIUSBD12 命令接口 与硬件无关
Chap_9.c USB 协议层 与 μC/OS-II 相关
Descriptor.c USB 协议层 与 μC/OS-II 相关
D12Driver.c USB 应用层 与 μC/OS-II 相关
其中 Chap_9.c,Descriptor.c 与 D12Driver.c 与 μC/OS-II 相关,以 上各层关系可用图 3 用来表示。
实时操作系统
USB应用层 D12Driver.c
USB协议层 Chap_9.c,Descriptor.c
USB接口控制驱动 D12CI.c
USB设备控制驱动 D12HAL.c
图 3 USB 驱动分层结构图
1.4 USB 驱动程序软件包的使用
本驱动软 件 包包括的 文 件见表 4。 表中的所 有 文件保存 于 本文档目 录 下 的 D12 For
uCos-II\USBDriver\D12 目录中,目前本 USB 驱动程序软件包的版本号为 1.0。
- 4 -
广州周立功单片机发展有限公司 Tel,(020) 38730916 38730917 38730976 38730977 Fax,38730925
表 4 USB 驱动程序主要文件列表
文件名 描述及作用
D12Config.h D12 驱动程序的配置,包括所有与硬件相关的配置
D12HAL.c USB 设备控制驱动,与具体使用的 MCU 及硬件相关
D12HAL.h USB 设备控制驱动 D12HAL.c 的头文件
D12CI.c USB 接口控制驱动,与具体使用的 USB 芯片相关
D12CI.h USB 接口控制驱动 D12CI.c 的头文件
Descriptor.c USB 协议层,PDIUSBD12 所有描述符取值
Descriptor.h USB 描述符结构体的定义,以及 PDIUSBD12 用到的一些宏定义取值
Chap_9.c USB 协议层,完成所有与控制传输相关的工作
Chap_9.h USB 协议层 Chap_9.c 的头文件,包含控制传输用到的结构体的定义
D12Driver.c USB 应用层,包含提供给用户使用的 API 函数
D12Driver.h USB 应用层 D12Driver.c 的头文件
D12 For uCos-II\USBDriver\目录的 USBDriver.mcp工程提供了使用该驱动程序的一个例子。该例子建立时使用的工程模板为,ARM Executable Image for UCOSII(for lpc2200)。
如果你的硬件配置与软件包的默认配置不同。 那么只须修改软件包中的 D12Config.h 文件,该文件主要是配置 PDIUSBD12 与 LPC2200 的引脚。 D12Config.h 文件 相关配置的修改的方法请见,嵌入式系统软件开发实例,中的,USB 驱动程序开发》,这一章详细地说明了该软件包的开发方法及该文件的配置方法。
除此以外,还有几点需要注意,
( 1)每个 C 文件的开始都包括了头文件( #include "config.h"),该头文 件是工程模板
ARM Executable Image for UCOSII(for lpc2200)的配置文件,软件包中默认已加上。
( 2) 在工程模板的 IRQ.s 文件的 END 语句之前加上下面的语句 (例子中默认已加上),
Usb_Handler HANDLER Usb_Exception
( 3)由于 PDIUSBD12 收发大量数据时中断比较频繁,因此必须修改工程模板中允许中断嵌套的层数,因此 Startup.s 文件的第二行应改为(例子中已进行修改),
IRQ_STACK_LEGTH EQU 9*25 ;每层嵌套需要 9 个字堆栈,允许 25 层嵌套
( 4)本例子 使用了 3 个事件,控制传输处理使用了 1 个信号量,例子应用程序中使用了 2 个信号量。因此 os_cfg.h 文件中操作系统最大事件数的定义应至少为 3。
#define OS_MAX_EVENTS 3
( 5)本例子不使用统计任务,因此把统计任务关闭( os_cfg.h 文件中),
#define OS_TASK_STAT_EN 0
( 6) PDIUSBD12使用 了外部存储空间的 Bank2部分,所以 必须配置 LPC2200的 BCFG2
寄存器,在模板的 target.c 文件的 TargetInit()函数添加以下代码,
BCFG2 = 0;
BCFG2 |= (0x04 << 5)|(0x01 << 10)|(0x04 << 11); //配置 BCFG2 控制寄存器
( 7)工程模 板配置外部 RAM 和外部 FLASH ROM 总线的访 问速度是适应于大部分型号的 RAM 与 FLASH ROM 的。 对于 EasyARM2200 开发板 上的 RAM 与 FLASH ROM 的型号,必须重新配置控制总线有关参数的 BCFG0 和 BCFG1 寄存器,这样程序的运行速度才能跟上 PDIUSBD12 的运 行速度。这些值的配置的具体方法见 LPC2200 数据手册以及你所使用的 RAM 与 FLASH ROM 的数据手册。程序的运行速度主要与 CPU 主频与外部 RAM、
FLASH 速度 相关。 这方面在模板的 target.c 的 TargetResetInit()函 数中修改。 如程序清单 1 所
- 5 -
广州周立功单片机发展有限公司 Tel,(020) 38730916 38730917 38730976 38730977 Fax,38730925
示。
程序清单 1 配置 TargetResetInit ()函数
#ifdef __DEBUG //开发板上 JP6 接为,Bank0-RAM,Bank1-Flash,JP7 接为,OUTSIDE
MEMMAP = 0x3; //remap
BCFG0 = 0x10000400; //外部 RAM
BCFG1 = 0x10000460; //外部 Flash
#endif
#ifdef __OUT_CHIP //开发板上 JP6 接为,Bank0-Flash,Bank1-RAM,JP7 接为,OUTSIDE
MEMMAP = 0x3; //remap
BCFG0 = 0x10000460; //外部 FLASH
BCFG1 = 0x10000400; //外部 RAM
#endif
#ifdef __IN_CHIP //开发板上 JP6 接为,Bank0-RAM,Bank1-Flash,JP7 接为,INSIDE
MEMMAP = 0x1; //remap
BCFG0 = 0x10000400; //外部 RAM
BCFG1 = 0x10000460; //外部 Flash
#endif
USB 驱动程序软件包提供给用户 6 个 API 函数。这 6 个函 数都在 D12Driver.c 文件中 。
下面说明这 6 个函数的作用以及入口参数,如表 5 到表 10 所示。
表 5 Init_D12()函数
函数名称 Init_D12
函数原型 INT8U Init_D12(void)
功能描述 设置 D12 与硬件的连接,初始化 D12,复位 D12,初始化相关变量
函数参数 无
函数返回值 0,初始化成功; 1,初始化失败
特殊说明
和注意点
该函数执行时间比较长,且执行时关闭了中断,所以只允许在 μC/OS-II 开始运行时调
用。
表 6 TaskSetup()函数
函数名称 TaskSetup
函数原型 void TaskSetup(void *pdata)
功能描述 控制传输处理任务
函数参数 void *pdata,任务入口参数
函数返回值 无
特殊说明
和注意点
在 μC/OS-II 开 始运行时要创建该任务,该任务的堆栈为,OS_STK TaskSetupStk[128];
该任务的优先级必须高于用户任务,以便 USB 能够及时 完成枚举。建议设置该任务优先级设为 0。
- 6 -
广州周立功单片机发展有限公司 Tel,(020) 38730916 38730917 38730976 38730977 Fax,38730925
表 7 ReadPort1()函数
函数名称 ReadPort1
函数原型 INT8U ReadPort1(INT32U len,INT8U *recbuff,INT16U timeout)
功能描述 从端点 1 读出 len 个字节
函数参数
INT32U len,要读出的字节数(取值范围为 0x00000001 ~ 0xFFFFFFFF)
INT8U *recbuff,接收数据指针(长度为 len)
INT16U timeout,超时时间,必须大于等于 0
函数返回值 0,读出成功; > 0,读出失败(错误码,见表 11)
特殊说明
和注意点
如果用户需 要 无限等待,那 么请在该函 数 返回超时错 误 时再次调用 该 函数,直至 函 数 返回 0(读出成功)为止。
表 8 ReadPort2()函数
函数名称 ReadPort2
函数原型 INT8U ReadPort2(INT32U len,INT8U *recbuff,INT16U timeout)
功能描述 从端点 2 读出 len 个字节
函数参数
INT32U len,要读出的字节数(取值范围为 0x00000001 ~ 0xFFFFFFFF)
INT8U *recbuff,接收数据指针(长度为 len)
INT16U timeout,超时时间,必须大于等于 0
函数返回值 0,读出成功; > 0,读出失败(错误码,见表 11)
特殊说明
和注意点
如果用户需 要 无限等待,那 么请在该函 数 返回超时错 误 时再次调用 该 函数,直至 函 数 返回 0(读出成功)为止。
表 9 WritePort1()函数
函数名称 WritePort1
函数原型 INT8U WritePort1(INT32U len,INT8U *sendbuff,INT16U timeout)
功能描述 用端点 1 发送 len 个字节
函数参数
INT32U len,发送的字节数(取值范围为 0x00000001 ~ 0xFFFFFFFF)
INT8U sendbuff,发送缓冲区指针
INT16U timeout,超时等待时间,必须大于等于 0
函数返回值 0,发送成功; > 0,发送失败(错误码,见表 11)
特殊说明
和注意点
如果用户需 要 无限等待,那 么请在该函 数 返回超时错 误 时再次调用 该 函数,直至 函 数 返回 0(发送成功)为止。
表 10 WritePort2()函数
函数名称 WritePort2
函数原型 INT8U WritePort2(INT32U len,INT8U *sendbuff,INT16U timeout)
功能描述 用端点 2 发送 len 个字节
函数参数
INT32U len,发送的字节数(取值范围为 0x00000001 ~ 0xFFFFFFFF)
INT8U sendbuff,发送缓冲区指针
INT16U timeout,超时等待时间,必须大于等于 0
- 7 -
广州周立功单片机发展有限公司 Tel,(020) 38730916 38730917 38730976 38730977 Fax,38730925
(接上表)
函数返回值 0,发送成功; > 0,发送失败(错误码,见表 11)
特殊说明
和注意点
如果用户需 要 无限等待,那 么请在该函 数 返回超时错 误 时再次调用 该 函数,直至 函 数 返回 0(发送成功)为止。
表 7 至表 10 中提到的函数返回值所代表的错误码的定义如表 11 所示,这些错误码的定义在 D12Driver.h 文件中。
表 11 读写端点 API 函数返回值
错误码宏定义 宏定义值 含义
USB_NO_ERR 0x00 函数执行成功
USB_ERR_NO_CONFIG 0x01 USB 总线未配置完成错误
USB_ERR_BUFF_INVALID 0x02 接收或发送缓冲区错误
USB_ERR_WR_TIMEOUT 0x03 接收或发送超时错误
调用读写端点函数,WritePort1(),WritePort2(),ReadPort1(),ReadPort2()必须遵守以下规则,
读写端点的任务必须保证有机会能调用到以上函数,才能成功收发数据。
这是因为以上函数给出了接收数据的缓冲区或发送数据的缓冲区。只有给出了缓冲区,
从主机发来 的数据才有,地方(接 收缓冲区),让它存放。 否则本驱动 程序将收到 的数据都
“扔掉” 。同 理,只有这 些函数给出 了需要发送 的数据在什 么“地方( 发送缓冲区),,驱动程序才能有目的地把数据发送出去。
1.5 软件包使用例子
下面举一个例子来说明怎样使用表 5 至表 10 的 API 函数,例子演示了两个任务之间在争着从端点 2 发送 1024 个字节。编写 USB 应用程序需要编写两个程序,
( 1) PDIUSBD12 设备端程序。
( 2) USB 主机( PC 机)端程序。
1,PDIUSBD12 设备端程序分析
设备端的程序运行由 μC/OS-II 操作系 统控制,它被动地等待来自 PC 机的命令,操作系统的启动任务为 TaskStart(),它初始化 PDIUSBD12 以及创建 各个任务以后,其运作过程如图 4 所示。图中以及下面提到的 RW_NUMS 宏定义的值为 1024。
其中优先级为 0 的任务 为 TaskSetup(),它负责完成 USB 的控制传输处理 (枚举和标准设备请求) 。经 过 PDIUSBD12 的初始化 以及枚举成功之后,μC/OS-II 操作系 统的运作过程为,
( 1)任务 1( TaskRec1)和任务 2( TaskRec2)分别在等待信号量 TaskRec1_Sem 和
TaskRec2_Sem。而起动任务( TaskStart)一直在等待主机命令。
( 2) TaskStart 如果收到命令字 0x01,就发送信号量给 TaskRec1,使其就 绪。 如果收到其它命令字,则发信号量给 TaskRec2,使其就绪。
( 3)任务 1 收到信号量以后,发一个字节 ack 给主机,作为应答。主机收到应答后,
发 1024 个字 节给该任务,该任务用 ReaPort2()接收这 1024 个字 节。 然后延时 1 个时钟 节拍,
最后该任务将收到的 1024 个字节发送给 PC 机( WritePort2()) 。
( 4)任 务 2 收到信号量以后,它的动作也与任务 1 一样,只是任务 2 接收到 1024 字节后,没有延时就立即发送 1024 个字节给 PC 机。
- 8 -
广州周立功单片机发展有限公司 Tel,(020) 38730916 38730917 38730976 38730977 Fax,38730925
//初始化 PDIUSBD12
//创建各个任 务
for (;;){
err = ReadPort1(1,&Cmd,200); //等待主机 命令
if (err == OS_NO_ERR){
if (Cmd == 0x01)
OSSemPost(TaskRec1_Sem); //发送信号量 给任务 1
else
OSSemPost(TaskRec2_Sem); //发送信号量 给任务 2
}
for (;;){
OSSemPend(TaskRec1_Sem,0,&err);
WritePort1(1,&ack,200); //应答 USB主机
ReadPort2(RW_NUMS,Buff,200); //接收数据
OSTimeDly(1);
Buff[0] = OSPrioCur; //标 识任务优先级
WritePort2(RW_NUMS,Buff,200); //发送 数据
}
for (;;){
OSSemPend(TaskRec2_Sem,0,&err);
WritePort1(1,&ack,200); //应答 USB主机
ReadPort2(RW_NUMS,Buff,200); //接收数据
Buff[0] = OSPrioCur; //标识任 务优先级
WritePort2(RW_NUMS,Buff,200); //发送数据
}
起动任务 TaskStart(优先级 3 )
任务 1 TaskRec1(优 先级1 ) 任务 2 TaskRec2(优先 级2 )
图 4 例子运行示意图
任务 1 和任 务 2 发送 1024 个字节给 PC 机时,第一 个字节为对应任务的优先级 ( Buff[0]
= OSPrioCur) 。这样,主机就知道收到的数据来自哪一个任务了。
任务 1 收到信号量以后,还要给主机一个应答,而不是立即就接收来自主机的 1024 个字节,这是因为任务 1 收 到信号量不一定就能很快取得运行的机会 (可能它还在等待其它信号量,而且任 务切换还要 花一定的时 间,因此切换 时间具有不 确定性),而 采用应答机 制就确保了 ReadPort2(RW_NUMS,Buff,200)一 定有机会运行了,该函数 给出了接收数据的,地方 (接收缓冲区 Buff),,有了地方存放数据,才能收取数据。
任务 1 为什 么要延时 1 个时钟周期 才发送数据 呢?这是为 了显示两个 任务争着发 送
1024 个字节 的结果而特别使用的。因为 OSTimeDly(1)的延时 是“不可靠”的,也就是说有可能存在着抖动。 这一点在北京航空航天大学出版社出版的,嵌入式实时操作系统 μC/OS-II》
(第 2 版)的第 2.32 小节中详细分析了这一点。
由于 ARM 的 执行速度很快,要使 OSTimeDly(1)抖 动的可能性较大,就需要把 μC/OS-II
每秒发生的中断次数减少,即改变 os_cfg.h 中宏定义 OS_TICKS_PER_SEC 的值,
#define OS_TICKS_PER_SEC 100
那么怎样才看得出两个任务争着发送数据的结果呢? 这就 需要 USB 主机端程序的配合了。
2,PC 机端程序分析
PC 机端的编程必须与设备端紧密结合,在本例子中,PC 机端程序的流程如图 5 所示 。
图 5 的虚线 表示 USB 主机 ( PC 机) 与 PDIUSBD12 之间的发 送与接收关系,箭头指向接收的一方。图中,PC 机为主机,主动发起了命令,PDIUSBD12 设备对命令做出响应。
图 5 的 1 和 2,PC 机使任务 1 就绪,并且收到了任务 1 的应答。
图 5 的 3 和 4,PC 机使任务 2 就绪,并且收到了任务 2 的应答。
图 5 的 5 和 6,PC 机向设备端发送了两次 1024 个 字节,第 1 次被任务 1 接收到,第 2
- 9 -
广州周立功单片机发展有限公司 Tel,(020) 38730916 38730917 38730976 38730977 Fax,38730925
次被任务 2 接收到。
发 送命令 0x01
发 送命令 0x02
任务1Tas kRec1就绪任务2 TaskRec2就绪接收 任务 1 的应答 任 务 2发 应答字 0x01
接收 任务 2 的应答 任 务 2发 应答字 0x02
发送1024 个字节 任务1接收 1024个字节发送1024 个字节 任务2接收 1024个字节任务1或任务2发 送1024个字 节接收1024 个字节任务1或任务2发 送1024个字 节接收1024 个字节
USB主机 PDIUSBD12设备端
1
2
3
4
5
6
7
8
图 5 USB 主机程序与 PDIUSBD12 设备端程序的关系
图 5 的 7 和 8,由于任务 1 收完 1024 个字节以后,延时了 1 个时钟周期,导致任务 1
或任务 2 谁 先占用发送端点具有不确定性。 这时,我们就可以看出两个任务争用端点的效果了。
为了方便用户使用 D12 PACK,我们 提供了 PC 机端的 PDIUSBD12 的驱动 程序,驱动程序在,D12 驱动”目录 下,请根据你的 PC 机使用的操作系统选择安装不同的驱动程序。
对于端点 1 和端点 2 的 读写,我们提供了 EasyUSB.dll 动态库,该动态库在,EasyUSB”
目录下,动 态库的使用 方法请见,EasyUSB”目录下的,EasyUSB 动 态库使用指 南
_EasyUSB_.pdf”文件。
PDIUSBD12 设备端驱动程序提供的 API 函数与 PC 机端 EasyUSB.dll 动态 库提供的函数的对应关系如表 12 所示。表中每一行的两个函数是发送与接收的关系。
表 12 设备端与主机端函数对应表
PDIUSBD12 设备端驱动程序 API 函数 PC 机主机端 EasyUSB.dll
INT8U ReadPort1(INT32U len,INT8U *recbuff,
INT16U timeout)
int WriteData(int pipenum,unsigned char *sendbuffer,
int len,int waittime=-1);
pipenum 取值为 1
INT8U ReadPort2(INT32U len,INT8U *recbuff,
INT16U timeout)
int WriteData(int pipenum,unsigned char *sendbuffer,
int len,int waittime=-1);
pipenum 取值为 3
INT8U WritePort1(INT32U len,INT8U *sendbuff,
INT16U timeout)
int ReadData(int pipenum,unsigned char *recbuffer,
int len,int waittime=-1);
pipenum 取值为 0
INT8U WritePort2(INT32U len,INT8U *sendbuff,
INT16U timeout)
int ReadData(int pipenum,unsigned char *recbuffer,
int len,int waittime=-1);
pipenum 取值为 2
注意,EasyUSB.dll 各 函 数 的参数 len 不 得超过 1024,PDIUSBD12 设 备端 各 函数的 参数 len 无 此限制 。
- 10 -
广州周立功单片机发展有限公司 Tel,(020) 38730916 38730917 38730976 38730977 Fax,38730925
3.PDIUSBD12 设备端源码分析
下面说明设备端程序编写的详细过程,
第一步 包 含驱动程序相关头文件及定义应用程序使用的堆栈及事件。 将 PDIUSBD12
驱动程序软件包加入到我们提供的工程模板中,然后请按照第 1.4 节( USB 驱动程序 软件 包的使用)修改相关内容。最后建立 main.c 文件,main.c 文件的开始部分见程序清单 2。
程序清单 2 包含相关头文件、定义堆栈及事件
#include "config.h" //包含 LPC2200 模板的配置头文件
#include "D12Driver.h" //包含 PDIUSBD12 驱动程序的头文件
OS_STK TaskStartStk[128]; //操作系统起动任务堆栈
OS_STK TaskRec1Stk[400]; //任务 1 堆栈 (400 个字 )
OS_STK TaskRec2Stk[400]; //任务 2 堆栈 (400 个字 )
OS_EVENT *TaskRec1_Sem; //任务 1 就绪信号量
OS_EVENT *TaskRec2_Sem; //任务 2 就绪信号量
第二步 配 置 PDIUSBD12 的中断优先 级和设置 PDIUSBD12 的 中 服务程序地址。 如程序清单 3 所示。
程序清单 3 配置 PDIUSBD12 的中断
void Init_D12Int(void)
{
VICVectCntl1 = (0x20 | 0x0E); //EINT0 通道分配到 IRQ slot 1
VICVectAddr1 = (INT32U)Usb_Handler; //设置 EINT0 向量地址
VICIntEnable = 1 << 14; //允许 EINT0
}
第三步 建 立 int main(void)函数。 主函数初始化 μC/OS-II 操作系统并启动该操作系统。该函数如程序清单 4 所示。
程序清单 4 int main(void)函数
int main (void)
{
OSInit(); //初始化 μC/OS-II
OSTaskCreate(TaskStart,(void *)0,&TaskStartStk[127],3); //创建起动任务
OSStart(); //开始多任务
}
第四步 初 始化目标板及 PDIUSBD12,运行起动任务。 这些工作由起动任务 TaskStart()
完成。如程序清单 5 所示。
程序清单 5 起动任务 TaskStart()
void TaskStart (void *pdata)
{
- 11 -
广州周立功单片机发展有限公司 Tel,(020) 38730916 38730917 38730976 38730977 Fax,38730925
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr;
#endif
INT8U Cmd,err;
pdata = pdata; /* Prevent compiler warning */
TargetInit(); //LPC2200 开发板初始化
err = Init_D12(); //PDIUSBD12 初始化
Init_D12Int(); //配置 PDIUSBD12 的中断
TaskRec1_Sem = OSSemCreate(0); //创建两个信号量,信号量初值为 0
TaskRec2_Sem = OSSemCreate(0);
if (err == 0) //如果初始化 PDIUSBD12 成功,才创建控制传输处理任务
OSTaskCreate(TaskSetup,(void *)0,&TaskSetupStk[127],0);
OSTaskCreate(TaskRec1,(void *)0,&TaskRec1Stk[399],1); //创建任务 1 和任务 2
OSTaskCreate(TaskRec2,(void *)0,&TaskRec2Stk[399],2);
for (;;){
err = ReadPort1(1,&Cmd,200); //等待来自 PC 机的命令 (1)
if (er = OS_NO_ER){ //接收正确
if (Cmd == 0x01)
OSemPost(TaskRec1_Sem); //使任务 1 就绪
else
OSemPost(TaskRec2_Sem); //使任务 2 就绪
}
}
}
请注意程序清单 5( 1) 等待来自 PC 机的命令的超时等待时间为 200 个 时钟周期,虽然只是接收一个字节,但是,超时时间必须足够长,防止该任务频繁返回超时错误。 导致数 据接收错误。
第五步 任 务接收数据及发送数据。 完成了前面的三步,下面就是用户应用程序的任务了。 这两个任务从 PC 机接收到 1024 个字节后,就争着向 PC 机发送数据。 这两个任务的程序框架在图 4 已说明得很 清楚了。 但请注意任务接收与发送数据时超时时间的定义,超时时间太短容易导致收发失败,超时时间过长又容易导致错误发生后恢复时间过长,请用户根据实际需要来衡量。
程序清单 6 任务 1 以及任务 2
#define RW_NUMS 1024 //任务 1 和任务 2 收发数据字节数
void TaskRec1(void *pdata)
{
- 12 -
广州周立功单片机发展有限公司 Tel,(020) 38730916 38730917 38730976 38730977 Fax,38730925
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr;
#endif
INT8U Buff[RW_NUMS]; //接收及发送缓冲区
INT8U ack = 0x01; //应答主机数值
INT8U err; //函数返回值
pdata = pdata; /* Prevent compiler warning */
for (;;){
OSSemPend(TaskRec1_Sem,0,&err); //等待 TaskStart 的命令
err = WritePort1(1,&ack,200); //应答 USB 主机
if (err == USB_NO_ERR){ //应答正确
err = ReadPort2(RW_NUMS,Buff,200); //接收数据
OSTimeDly(1); /延时一个时钟周期
if (err == USB_NO_ERR){ //接收正确
Buf[0] = OSPrioCur; /标识该任务
err = WritePort2(RW_NUMS,Buff,200); //发送数据
}
}
}
}
void TaskRec2(void *pdata)
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr;
#endif
INT8U Buff[RW_NUMS]; //接收及发送缓冲区
INT8U ack = 0x02; //应答主机数值
INT8U er; /函数返回值
pdata = pdata; /* Prevent compiler warning */
for (;;){
OSSemPend(TaskRec2_Sem,0,&err); //等待 TaskStart 的命令
err = WritePort1(1,&ack,200); //应答 USB 主机
if (err == USB_NO_ERR){ //应答正确
err = ReadPort2(RW_NUMS,Buff,200); //接收数据
if (err == OS_NO_ERR){ //接收正确
Buff[0] = 0x02; //标识该任务
err = WritePort2(RW_NUMS,Buff,200); //发送数据
}
}
}
}
- 13 -
广州周立功单片机发展有限公司 Tel,(020) 38730916 38730917 38730976 38730977 Fax,38730925
第六步 编译程序并运行。 运行程序的方法见本目录下的 readme.txt 文件。
4.PC 机端源码分析
PC 机端我们提供了一个用 VC++6.0 写的一个例子,该例子在,PCTest” 目录下。 当然也可以用 VB,CB,Dephi 等高级语言 引用动态库 EasyUSB.dll 来实现。下面是在 VC++6.0
下实现该例子的过程。
第一步:新建一个 VC++6.0 工程。
第二步,将,EasyUSB” 目录下的所有文件拷贝到你的工程目录下。 将 EasyUSB.lib 和
EasyUSB.h 加入到你的工程中,在用到 EasyUSB.dll 函数的文件包含 EasyUSB.h 头文件。
第三步:使用 EasyUSB.dll 提供的函 数编写程序。程序清单 7 是图 5 中 PC 机端发送 与接收数据的片断,用到的各变量为,
cmd1 初值为 1,字节型;
cmd2 初值为 2,字节型;
SendBuff1 为发送缓冲区 1,字节型,长度 1024 字节,负责将数据发送给任务 1;
SendBuff2 为发送缓冲区 2,字节型,长度 1024 字节,负责将数据发送给任务 2;
RecBuff1 为 接收缓冲区 1,字节型,长度 1024 字 节,负责接收来自任务 1 或任务 2 的数据。
RecBuff2 为 接收缓冲区 2,字节型,长度 1024 字 节,负责接收来自任务 1 或任务 2 的数据。
下面一共 8 句收发数据语句,请对照图 5 及 PCTest.dsw 工程中 的 PCTestDlg.cpp 文件进行分析。其中宏定义 RW_NUMS 的值为 1024。
程序清单 7 PC 机数据收发
ret = WriteData(1,&cmd1,1,1000); //发送命令字 1,使设备端任务 1就绪
if (ret != 1){
MessageBox("发送命令字 1失败 "); return;
}
ret = ReadData(0,&cmd1,1,1000); //接收设备端任务 1的响应
if ((ret != 1)||(cmd1 != 1)){
MessageBox("任务 1无响应或响应错误 "); return;
}
ret = WriteData(1,&cmd2,1,1000); //发送命令字 2,使设备端任务 2就绪
if (ret != 1){
MessageBox("发送命令字 1失败 "); return;
}
ret = ReadData(0,&cmd2,1,1000); //接收设备端任务 2的响应
if ((ret != 1)||(cmd2 != 2)){
MessageBox("任务 2无响应或响应错误 "); return;
}
ret = WriteData(3,SendBuff1,RW_NUMS,1000); //发送 1024个字节给任务 1
if (ret != RW_NUMS){
MessageBox("发送数据给任务 1错误 "); return;
- 14 -
广州周立功单片机发展有限公司 Tel,(020) 38730916 38730917 38730976 38730977 Fax,38730925
}
ret = WriteData(3,SendBuff2,RW_NUMS,1000); //发送 1024个字节给任务 2
if (ret != RW_NUMS){
MessageBox("发送数据给任务 2错误 "); return;
}
ret = ReadData(2,RecBuff1,RW_NUMS,1000); //接收任务 1或任务 2的 1024个字节
if (ret != RW_NUMS){
MessageBox("接收任务 1或 2的数据错误 "); return;
}
ret = ReadData(2,RecBuff2,RW_NUMS,1000); //接收任务 1或任务 2的 1024个字节
if (ret != RW_NUMS){
MessageBox("接收任务 1或 2的数据错误 "); return;
}
请注意 EasyUSB.dll 中 ReadData()与 WriteData()函数的 len 参数的最小取值为 1( 不能为 0),最大取值为 1024。
还有一点必须提醒:由于 PC 机有时 候比较繁忙,可能导致 EasyUSB.dll 中的读写函数没有机会执行(写入或读出数据)而出现超时错误,这时可以再试读或试写一两次。
5.例子运行结果
运行例子的步骤为,
( 1)将 D12 PACK 安装到 EasyARM2200 开发板 的 PACK 接 口,并用 USB 线将 PC 机机与 D12 PACK 连接起来。
( 2)用 ADS1.2 运行,D12Driver”目 录中的 USBDriver.mcp 工程,有关怎样运行例子程序的说明请见本目录的 readme.txt 文件。
( 3) 重新编译 USBDriver.mcp 工程并将程序下载到 EasyARM2200 开发板,运行该程序。
( 4) 运行程 序后,PC 机提示发现 USB 新硬件,提示安装新硬件的 USB 驱动程序,请根据你的操作系统安装相应的 USB 驱动程序,驱动程序在,D12 驱动,目录下。安装 D12
驱动程序成功后,D12 PACK 上的绿灯应该点亮,即枚举成功。
( 5)运行,PCTest”目 录下的 PCTest.exe 文件。 在“发送数据”框填写要发送的两个字节 (每次发送 1024 个相 同的字节,共发两次),然后 按,发送” 按键。 这时,从 PDIUSBD12
设备端接收到 2048 个字 节,如图 6 所示 。,接收数据” 框中的第一个框为首先接收到的 1024
个字节,第二个框为随后接收到的 1024 个字节。
接收到的 1024 个字节的 第 1 个字节 说明发送这批数据( 1024 个字节)的 任务优先级 。
( 6)继续按,发送”按键多几次,你会发现收到同一批数据的顺序不同了。如图 7 所示。但是,可以看到,尽管收到同一批数据的顺序不同,但数据内容并不发生变化。
这就可以说明,任务 1 与任 务 2 争着 往端点 2 发 送数据,但它们都保持着数据的完整性。
这样就实现了我们驱动程序的设计目标。
- 15 -
广州周立功单片机发展有限公司 Tel,(020) 38730916 38730917 38730976 38730977 Fax,38730925
图 6 从 PDIUSBD12 接收到 1024 个字节
图 7 按多次按发送按键的结果
运行于 μC/OS-II 上的 PDIUSBD12 驱动程序软件包的说明就介绍到此,用户可以在此驱动程序的基础上实现其它功能,
( 1) 在本驱 动的协议层增加其它请求如厂商请求,大容量类请求。 这样,USB 驱动程序的功能就更加强大了;
( 2)通过该 USB 驱动程 序来控制 EasyARM2200 开发板上的一些外设,实现在 PC 机对设备的控制,如控制数码管的显示等等。
总之,在本驱动程序的基础上,请用户发挥自己的想像力,动手编写程序实现更多的实用功能。
- 16 -