这一节,我们来讲讲黑金开发板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: [email protected] * ================================================================= */ //-----------------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[i]=read_data_from_usb(); usb.receive_buffer[i]='\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[i]); 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盘等相关内容。谢谢大家!