黑金动力社区:http://www.heijin.org
这一节,我们来讲讲黑金开发板USB部分的内容。黑金开发板上使用的USB芯片是南京沁恒公司的CH376,它支持USB 设备(DEVICE)方式和USB(HOST) 主机方式,并且内置了USB 通讯协议的基本固件,内置了处理Mass-Storage海量存储设备的专用通讯协议的固件,内置了SD 卡的通讯接口固件,内置了FAT16和FAT32 以及FAT12 文件系统的管理固件,支持常用的USB 存储设备(包括U 盘/USB 硬盘/USB 闪存盘/USB 读卡器)和SD 卡(包括标准容量SD 卡和高容量HC-SD 卡以及协议兼容的MMC 卡和TF 卡)。
由于芯片内部集成了USB通讯协议的基本固件,因此,免去了我们自己编写USB通讯协议的麻烦了。不仅如此,它还集成了文件系统的管理固件,那么,我们不就可以直接读取U盘中的内容了?事实就是这样的,真的方便了很多哦。下面我们就来看看这款芯片到底有多好用吧。
首先,我们看看这部分电路,如下图所示,我们采用的是8位总线模式,电路结构非常简单,与FPGA相连的一共有12根线,其中8根数据线,1根中断线,3根控制线。
下面,我们就在软核中添加USB部分的模块,其实都是通过PIO模块控制的。添加后如下图所示,其中USB_DB为8位输出PIO;USB_WR,USB_RD,USB_A0都是1位输出PIO。
而USB_nINT为输入PIO,而且电平中断,如下图设置,
都设置好以后,自动分配地址,中断,接下来就可以编译了。
编译好以后,回到Quartus界面,整理好以后,如下图所示,要记得USB_DB数据线是双向的,因此,一定要用分配bidir双向引脚,而不要用output。
下面是中断引脚部分,由于我们是低电平中断,而NIOS电平中断只支持高电平中断,所以我们需要加一个非门,如下图示所示
都设置好以后,我们就可以分配引脚了,TCL脚本有关USB部分的代码如下
#------------------------USB---------------------------------------#
set_location_assignment PIN_117 -to USB_DB[0]
set_location_assignment PIN_118 -to USB_DB[1]
set_location_assignment PIN_127 -to USB_DB[2]
set_location_assignment PIN_128 -to USB_DB[3]
set_location_assignment PIN_133 -to USB_DB[4]
set_location_assignment PIN_134 -to USB_DB[5]
set_location_assignment PIN_135 -to USB_DB[6]
set_location_assignment PIN_137 -to USB_DB[7]
set_location_assignment PIN_113 -to USB_A0
set_location_assignment PIN_115 -to USB_WR
set_location_assignment PIN_116 -to USB_nINT
set_location_assignment PIN_114 -to USB_RD
分配好引脚以后,大家就可以编译了。
USB分主机模式和设备模式,这两种模式硬件部分是相同的,只是在软件编程方面有些不同。这一节,我们来讲设备模式,也就是开发板通过USB接口与主机(电脑)相连,实现开发板与电脑的数据通信。
我们首先打开NIOS II 9.0 IDE软件,还是老过程,首先编译一遍,Ctril+b。编译成功以后,我们在system.h中会看到USB部分的代码,如下表所示
#define USB_DB_NAME "/dev/USB_DB"
#define USB_DB_TYPE "altera_avalon_pio"
#define USB_DB_BASE 0x00001840
......
#define USB_NINT_NAME "/dev/USB_nINT"
#define USB_NINT_TYPE "altera_avalon_pio"
#define USB_NINT_BASE 0x00001850
......
#define USB_WR_NAME "/dev/USB_WR"
#define USB_WR_TYPE "altera_avalon_pio"
#define USB_WR_BASE 0x00001860
......
#define USB_RD_NAME "/dev/USB_RD"
#define USB_RD_TYPE "altera_avalon_pio"
#define USB_RD_BASE 0x00001870
......
#define USB_A0_NAME "/dev/USB_A0"
#define USB_A0_TYPE "altera_avalon_pio"
#define USB_A0_BASE 0x00001880
......
下面,我们来添加USB部分的代码。
首先,我们建立一个在inc下面建立一个usb.h文件,内容如下
#ifndef __usb_h__
#define __usb_h__
//-----------------Include files-------------------------//
#include "system.h"
//----------------- CH375 DEFINE-------------------------//
//下面部分是USB寄存器地址,这部分定义可以看CH376的芯片手册
#define USB_HOST 0X06
#define USB_DEVICE 0x02
#define USB_DISABLE 0X00
#define RESET_ALL 0X05
#define CHECK_EXIST 0X06
#define SET_USB_ID 0X12
#define SET_USB_MODE 0X15
#define GET_STATUS 0X22
#define UNLOCK_USB 0X23
#define RD_USB_DATA 0X28
#define WR_USB_DATA5 0X2A
#define WR_USB_DATA7 0X2B
#define GET_IC_VER 0X01
#define ENTER_SLEEP 0X03
#define CHK_SUSPEND 0X0B
#define RD_USB_DATA0 0X27
#define RET_SUCCESS 0X51
#define RET_ABORT 0X5B
#define INT_EP2_OUT 0x02
#define INT_EP2_IN 0x0a
//host
#define DISK_READ 0X54
#define DISK_RD_GO 0X55
#define DISK_READY 0X59
#define DISK_INIT 0X51
//status
#define USB_INT_CONNECT 0x15
#define USB_INT_DISCONNECT 0X16
#define USB_INT_SUCCESS 0X14
#define USB_INT_DISK_READ 0X1D
//-----------------bus define----------------------------//
/*下面是USB的接口部分定义,这次我没有像以往那样定义结构体,是为了让大家感受一下各种形式的编程。大家要注意PIO_USB_DB_DIR的定义,通过以前的讲解,不知道大家是否理解,它是USB数据线的方向控制寄存器的定义,知道为什么要+4么,大家自己考虑吧,不明白就看看附录中的有关PIO问题解析部分内容吧*/
#define PIO_USB_DB *(volatile unsigned long int *)USB_DB_BASE
#define PIO_USB_WR *(volatile unsigned long int *)USB_WR_BASE
#define PIO_USB_RD *(volatile unsigned long int *)USB_RD_BASE
#define PIO_USB_A0 *(volatile unsigned long int *)USB_A0_BASE
#define PIO_USB_INT *(volatile unsigned long int *)USB_INT_BASE
#define PIO_USB_DB_DIR *(volatile unsigned long int *)(USB_DB_BASE+4)
#define VID 0X0FFE
#define PID 0X1000
typedef struct{
char receive_buffer[200];
int send_ok_flag;
int receive_ok_flag;
}USB_T;
//-----------------Extern function------------------------//
extern USB_T usb;
extern int initialize_usb(void);
extern int set_usb_mode(unsigned char);
extern int send_string_to_usb(char *str,int str_len);
extern void write_command_to_usb(unsigned char command);
extern void write_data_to_usb(unsigned char data);
#endif //__usb_h__
接下来,我们看看CH376的时序图,如下图所示
我们就根据上面的时序图编写驱动部分,在driver中建立一个usb.c文件,内容如下表所示
/*
* ==================================================================
* Filename: usb.c
* Description:
* Version: 1.0.0
* Created: 2010.4.16
* Revision: none
* Compiler: Nios II 9.0 IDE
* Author: 马瑞 (AVIC)
* Email: avic633@gmail.com
* =================================================================
*/
//-----------------Include files-------------------------//
#include "../inc/usb.h"
#include "altera_avalon_pio_regs.h"
#include "sys/alt_irq.h"
#include <unistd.h>
#include <stdio.h>
//-----------------Function Prototype--------------------//
void write_command_to_usb(unsigned char command);
void write_data_to_usb(unsigned char data);
unsigned char read_data_from_usb(void);
void delay(void);
//-----------------Variable------------------------------//
USB_T usb;
//-----------------Function------------------------------//
/*
* === FUNCTION ===================================================
* Name: irq_usb
* Description: 中断函数
* =================================================================
*/
void irq_usb(void)
{
unsigned int i;
unsigned char interrupt_status,data_len;
// static int times=0;
write_command_to_usb(GET_STATUS);
interrupt_status=read_data_from_usb();
switch(interrupt_status){
//Device
case INT_EP2_OUT:
write_command_to_usb(RD_USB_DATA);
data_len=read_data_from_usb();
for(i=0;i<data_len;i++)
usb.receive_buffer=read_data_from_usb();
usb.receive_buffer='\0';
usb.receive_ok_flag=1;
break;
case INT_EP2_IN:
write_command_to_usb(UNLOCK_USB);
usb.send_ok_flag=1;
break;
default :break;
}
IOWR_ALTERA_AVALON_PIO_EDGE_CAP(USB_NINT_BASE,0x00);
}
/*
* === FUNCTION ===================================================
* Name: send_string_to_usb
* Description: 发送字符串
* =================================================================
*/
int send_string_to_usb(char *str,int str_len)
{
int i;
write_command_to_usb(WR_USB_DATA7);
write_data_to_usb(str_len);
for(i=0;i<str_len;i++)write_data_to_usb(str);
return 0;
}
/*
* === FUNCTION ===================================================
* Name: initialize_usb
* Description: 初始化USB
* =================================================================
*/
int initialize_usb(void)
{
PIO_USB_RD=1;
PIO_USB_WR=1;
PIO_USB_A0=1;
usb.receive_ok_flag=0;
// enable the io interrupt
IOWR_ALTERA_AVALON_PIO_IRQ_MASK(USB_NINT_BASE,1);
IOWR_ALTERA_AVALON_PIO_EDGE_CAP(USB_NINT_BASE,0);
alt_irq_register(USB_NINT_IRQ,NULL,irq_usb);
set_usb_mode(USB_DEVICE);
return 0;
}
/*
* === FUNCTION ===================================================
* Name: usb_usb_mode
* Description: 设置USB模式
* =================================================================
*/
int set_usb_mode(unsigned char type)
{
write_command_to_usb(SET_USB_MODE);
write_data_to_usb(type);
read_data_from_usb();
if((read_data_from_usb())==0x51)return 0;
else return -1;
}
/*
* === FUNCTION ===================================================
* Name: write_command_to_usb
* Description: 写命令
* =================================================================
*/
void write_command_to_usb(unsigned char command)
{
//A0
PIO_USB_A0=1;
//DB DIR output
PIO_USB_DB_DIR=0xff;
PIO_USB_DB=command;
PIO_USB_WR=0;
PIO_USB_WR=1;
}
/*
* === FUNCTION ===================================================
* Name: delay
* Description: 延时
* =================================================================
*/
void delay(void)
{
int i;
for(i=0;i<1000;i++);
}
/*
* === FUNCTION ===================================================
* Name: write_data_to_usb
* Description: 写数据
* =================================================================
*/
void write_data_to_usb(unsigned char data)
{
//A0
PIO_USB_A0=0;
//DB DIR output
PIO_USB_DB_DIR=0xff;
PIO_USB_DB=data;
usleep(20);
PIO_USB_WR=0;
delay();
usleep(20);
PIO_USB_WR=1
}
/*
* === FUNCTION ===================================================
* Name: read_data_from_usb
* Description: 读数据
* =================================================================
*/
unsigned char read_data_from_usb(void)
{
unsigned char data=0;
//A0
PIO_USB_A0=0;
//DB DIR output
PIO_USB_DB_DIR=0;
PIO_USB_RD=0;
delay();
data=PIO_USB_DB;
PIO_USB_RD=1;
return data;
}
编写好驱动以后,我们需要编写主函数测试代码
#include <stdio.h>
#include <unistd.h>
#include "../inc/usb.h"
int main()
{
unsigned char tmp[] = "Hello USB!\n";
initialize_usb();
while(1){
if(usb.receive_ok_flag){
printf("%s\n",usb.receive_buffer);
usb.receive_ok_flag = 0;
}
send_string_to_usb(tmp,sizeof(tmp));
usleep(100000);
}
return 0;
}
程序都写完了,但工作还没有结束,如果要想调试,我们首先还需要在你的电脑上安装CH376的驱动。
首先,去南京沁恒的网站下载驱动,下载地址是:http://www.wch.cn/download/list.asp?id=66,CH376的驱动跟CH372,CH375是一样的。
双击CH372DRV.EXE,开始安装驱动,如下图所示,点击INSTALL,直接安装就可以了。
为了调试,我们还需要上位机的软件来配合,就像串口调试精灵的一个东西。这部分工作属于上位机部分的内容了。我在这里简单介绍一下吧。
南京沁恒网站提供了上位机需要的静态库函数和头文件,下载地址是:http://www.wch.cn/download/list.asp?id=28,我们可以利用他们构建自己的上位机。我使用的是NI公司的Labwindows/CVI 8.1,当然大家也可以使用VC等软件开发。
我感觉这个软件还是蛮好用的,大家可以研究一下。写好的上位机面板如下图所示,
我们可以利用它进行简单的发送和接收,软件还不够完善。下面简单介绍一下使用方法,首先需要将FPGA运行起来,然后点击上位机的打开按钮。如果是接收的话,点击Reveive,每点一次接收一次。如果发送的话,将你发送的数据写到上面的输入框中,点击Send,每点一次发送一次。如下图示
好了,到这里,有关USB的设备模式的内容就讲完了。下一节,我们将讲解有关USB主模式的内容,也就是如何读取U盘等相关内容。谢谢大家!
用户229870 2010-7-26 14:15