USB是英文Universal Serial BUS(通用串行总线)的缩写,是一个外部总线标准,用于规范电脑与外部设备的连接和通讯。是应用在PC领域的接口技术。USB接口支持设备的即插即用和热插拔功能。是在1994年底由英特尔、康柏、IBM、Microsoft等多家公司联合提出的。发展到现在已经有USB1.0/1.1/2.0/3.0/3.1等多个版本。目前用的最多的就是USB2.0和USB3.0,USB3.1目前已经开始普及。STM32F103自带的USB符合USB2.0规范。标准USB共四根线组成,除VCC和GND外,另外为D+,D-,这两根数据线采用的是差分电压的方式进行数据传输的。在USB主机上,D-和D+都是接了15K的电阻到低的,所以在没有设备接入的时候,D+、D-均是低电平。而在USB设备中,如果是高速设备,则会在D+上接一个1.5K的电阻到VCC,而如果是低速设备,则会在D-上接一个1.5K的电阻到VCC。这样当设备接入主机的时候,主机就可以判断是否有设备接入,并能判断设备是高速设备还是低速设备。
STM32F1自带一个USB从机控制器,符合USB规范通信,PC主机和微控制器之间的数据传输是通过共享一个专用的数据缓冲区来完成的,该数据缓冲区能被USB外设直接访问,这块专用数据缓冲区的大小由所使用的端点数目和每个端点最大的数据分组大小所决定,每个端点最大可使用512字节缓冲区,最多可用于16个单向或8个双向端点。
USB模块同PC主机通信,根据USB规范实现令牌分组的检测,数据发送/接收的处理,和握手分组的处理。整个传输的格式由硬件完成,其中包括CRC的生成和校验。每个端点都有一个缓冲区描述块,描述该端点使用的缓冲区地址、大小和需要传输的字节数。当USB模块识别出一个有效的功能/端点的令牌分组时,(如果需要传输数据并且端点已配置)随之发生相关的数据传输。USB模块通过一个内部的16位寄存器实现端口与专用缓冲区的数据交换。在所有的数据传输完成后,如果需要,则根据传输的方向,发送或接收适当的握手分组。在数据传输结束时,USB模块将触发与端点相关的中断,通过读状态寄存器和/或者利用不同的中断来处理。USB设备架构如图所示。
USB的中断映射单元:将可能产生中断的USB事件映射到三个不同的NVIC请求线上:
(1)USB低优先级中断(通道20):可由所有USB事件触发(正确传输,USB复位等)。在处理中断前应首先确定中断源。
(2)USB高优先级中断(通道19):仅由同步和双缓冲批量传输的正确传输事件触发,为保证最大的传输速率。
(3)USB唤醒中断(通道42):由USB挂起模式的唤醒事件触发。
注:USB和CAN共用一个专用的512字节的SRAM存储器用于数据的发送和接收,因此不能同时使用USB和CAN(共享的SRAM被USB和CAN模块互斥地访问)。USB和CAN可以同时用于一个应用中但不能在同一个时间使用。
如果需要正常的使用STM32F1系列的USB模块,就需要编写USB驱动程序,这部分程序非常复杂,需要了解整个USB通信的详细过程,针对这个问题,ST公司提供了一个官方的USB驱动库,用户可以通过直接移植官方驱动库来实现USB读写控制。
我们现在直接利用官方的USB驱动源码来通过计算机进行SD卡和Flash的读写,这里我们需要对官方源码进行一些修改,用于实现这个效果。
USB Mass Storage类支持两个传输协议:
(1)Bulk-Only传输(BOT)
(2)Control/Bulk/Interrupt传输(CBI)
MassStorage类规范定义了两个类规定的请求:Get_Max_LUN和MassStorageReset,所有的MassStorage类设备都必须支持这两个请求。
(1)Get_Max_LUN(bmRequestType=10100001 bandb Request=11111110b)用来确认设备支持的逻辑单元数。MaxLUN的值必须是0~15。注意:LUN是从0开始的。主机不能向不存在的LUN发送CBW,本章我们定义MaxLUN的值为1,即代表2个逻辑单元。
(2)MassStorageReset(bmRequestType=00100001 bandb Request=11111111b)用来复位MassStorage设备及其相关接口。
支持BOT传输的MassStorage设备接口描述符要求如下:
(1)接口类代码bInterfaceClass=08h,表示为MassStorage设备。
(2)接口类子代码bInterfaceSubClass=06h,表示设备支持SCSIPrimaryCommand-2(SPC-2)。
(3)协议代码bInterfaceProtocol有3种:0x00、0x01、0x50,前两种需要使用中断传输,最后一种仅使用批量传输(BOT)。
(4)支持BOT的设备必须支持最少3个endpoint:Control,Bulk-In和Bulk-Out。USB2.0的规范定义了控制端点0。Bulk-In端点用来从设备向主机传送数据(本章用端点1实现)。Bulk-Out端点用来从主机向设备传送数据(本章用端点2实现)。
ST官方的例程是通过USB来读写SD卡(SDIO方式)和Nand Falsh,支持2个逻辑单元,我们在官方例程的基础上,只需要修改SD驱动部分代码,并将对Nand Falsh的操作修改为对SPI Falsh的操作。只要这两步完成了,剩下的就比较简单了,对底层磁盘的读写,都是在mass_mal.c文件实现的,所以我们只需要修改该函数的MAL_Init、MAL_Write、MAL_Read和MAL_GetStatus等4个函数,与我们的SD卡和SPI Falsh对应起来即可。
(1)需要添加的文件如下表所示。
文件名 | 目录 | 功能 |
---|---|---|
usb_core.c | ....\\USB\\CORE | 用于处理USB2.0协议 |
usb_init.c | 用于USB控制器的初始化 | |
usb_int.c | 负责USB的中断处理 | |
usb_mem.c | 负责处理PMA数据,即STM32内部用于USB/CAN的专用数据缓冲区 | |
usb_regs.c | 负责USB控制寄存器的底层操作 | |
usb_sil.c | 为USB端点提供特殊简化的读写访问函数 | |
usb_desc.c | ...\\USB\\CONFIG | 用于虚拟通信端口描述符的处理 |
usb_endp.c | 用于非控制传输,处理正确传输中断回调函数 | |
usb_istr.c | 用于处理USB中断 | |
usb_prop.c | 用于处理所有虚拟通信端口相关事件,包括初始化,复位等 | |
usb_pwr.c | 用于管理USB的电源状态 | |
usb_scsi.c | 与SCSI命令相关的所有处理 | |
scsi_data.c | 定义了SCSI数据 | |
memory.c | 定义USB通信的存储区读写函数 | |
mass_mal.c | 定义了USB通信的读写操作底层函数接口 | |
usb_bot.c | 定义了BOT传输协议 |
(2)usb_prop.c文件修改
原文件
修改后文件
(3)memory.h文件修改
(4)mass_mal.h文件修改
(5)memory.c文件修改
(6)usb_bot.c文件修改
(7)mass_mal.c文件重写
#include "platform_config.h"
#include "mass_mal.h"
#include "sdio_sdcard.h"
#include "w25q128.h"
long long Mass_Memory_Size[ MAX_LUN+1 ] ;
u32 Mass_Block_Size[ MAX_LUN+1 ] ;
u32 Mass_Block_Count[ MAX_LUN+1 ] ;
uint16_t MAL_Init( uint8_t lun )
{
u16 Status=MAL_OK ;
switch( lun )
{
case 0: break;
case 1: break;
default:return MAL_FAIL ;
}
return Status ;
}
uint16_t MAL_Write(uint8_t lun, uint64_t Memory_Offset, uint32_t *Writebuff, uint16_t Transfer_Length)
{
u8 STA ;
switch( lun )
{
//磁盘0为 SPI FLASH盘
case 0:
STA = 0 ;
W25QXX_Write( ( u8* )Writebuff, Memory_Offset, Transfer_Length ) ;
break ;
//磁盘1为SD卡
case 1:
STA = SD_WriteDisk( ( u8* )Writebuff, Memory_Offset>>9, Transfer_Length>>9 ) ;
break ;
default:
return MAL_FAIL ;
}
if( STA!=0 )
return MAL_FAIL ;
return MAL_OK ;
}
uint16_t MAL_Read( uint8_t lun, uint64_t Memory_Offset, uint32_t *Readbuff, uint16_t Transfer_Length )
{
u8 STA ;
switch( lun )
{
//磁盘0为 SPI FLASH盘
case 0 :
STA = 0 ;
W25QXX_Read( ( u8* )Readbuff, Memory_Offset, Transfer_Length ) ;
break;
//磁盘1为SD卡
case 1 :
STA = SD_ReadDisk( ( u8* )Readbuff, Memory_Offset>>9, Transfer_Length>>9 ) ;
break;
default:return MAL_FAIL ;
}
if( STA!=0 )
return MAL_FAIL ;
return MAL_OK ;
}
uint16_t MAL_GetStatus( uint8_t lun )
{
switch( lun )
{
case 0:return MAL_OK;
case 1:return MAL_OK;
default:return MAL_FAIL;
}
}
(8)hw_config.h文件重写
#ifndef HW_CONFIG_H
#define HW_CONFIG_H
#include "platform_config.h"
#include "usb_type.h"
typedef enum
{
DISABLE = 0,
ENABLE = 1
}FunctionalState;
#define BULK_MAX_PACKET_SIZE 0x00000040 //包大小,最大64字节
void Led_RW_ON( void ) ; //LED开启
void Led_RW_OFF( void ) ; //LED关闭
void Set_USBClock( void ) ; //USB时钟配置函数
void Enter_LowPowerMode( void ) ; //USB进入低功耗模式
void Leave_LowPowerMode( void ) ; //USB退出低功耗模式
void USB_Interrupts_Config( void ) ; //USB中断配置
void USB_Port_Set( u8 enable ) ; //USB使能
void Get_SerialNum( void ) ; //获取STM32的唯一ID
#endif
(9)hw_config.c文件重写
#include "usb_lib.h"
#include "mass_mal.h"
#include "usb_desc.h"
#include "usb_pwr.h"
#include "usb_lib.h"
#include "usb_istr.h"
void USB_NotConfigured_LED()
{
}
void USB_Cable_Config( FunctionalState NewState )
{
}
void Led_RW_OFF()
{
}
void Led_RW_ON()
{
}
void USBWakeUp_IRQHandler()
{
EXTI->PR |= 1<<18 ; //清除USB唤醒中断挂起位
}
void USB_LP_CAN1_RX0_IRQHandler()
{
USB_Istr() ;
}
void Set_USBClock()
{
RCC->CFGR &= ~( 1<<22 ) ; //USBclk=PLLclk/1.5=48Mhz
RCC->APB1ENR |= 1<<23 ; //USB时钟使能
}
void Enter_LowPowerMode()
{
bDeviceState = SUSPENDED ;
}
void Leave_LowPowerMode()
{
DEVICE_INFO *pInfo = &Device_Info ;
if( pInfo->Current_Configuration!=0 )
bDeviceState=CONFIGURED ;
else
bDeviceState = ATTACHED ;
}
void USB_Interrupts_Config()
{
EXTI->IMR |= 1<<18 ; //开启线18上的中断
EXTI->RTSR |= 1<<18 ; //line 18上事件上升降沿触发
NVIC_Init( 1, 0, USB_LP_CAN1_RX0_IRQn, 2 ) ; //组2优先级次之
NVIC_Init( 0, 0, USBWakeUp_IRQn, 2 ) ; //组2优先级最高
}
void USB_Port_Set( u8 enable )
{
RCC->APB2ENR |= 1<<2 ; //使能PORTA时钟
if( enable )
_SetCNTR( _GetCNTR()&0xFFFFFFFD ) ; //退出断电模式
else
{
_SetCNTR( _GetCNTR()|0x02 ) ; //断电模式
GPIOA->CRH &= 0xFFF00FFF ;
GPIOA->CRH |= 0x00033000 ;
PAout( 12 ) = 0 ;
}
}
void IntToUnicode( u32 value, u8 *pbuf, u8 len )
{
u8 idx ;
for( idx=0; idx
}
void Get_SerialNum()
{
u32 Device_Serial0, Device_Serial1, Device_Serial2 ;
Device_Serial0 = *( u32* )0x1FFFF7E8 ;
Device_Serial1 = *( u32* )0x1FFFF7EC ;
Device_Serial2 = *( u32* )0x1FFFF7F0 ;
Device_Serial0 += Device_Serial2 ;
if( Device_Serial0!=0 )
{
IntToUnicode( Device_Serial0, &MASS_StringSerial[ 2 ] , 8 ) ;
IntToUnicode( Device_Serial1, &MASS_StringSerial[ 18 ], 4 ) ;
}
}
(10)platform_config.h文件重写
#ifndef PLATFORM_CONFIG_H
#define PLATFORM_CONFIG_H
#include "sys.h"
#define USE_STM3210E_EVAL //当前使用的版本
#endif
(11)usb_pwr.c文件修改(改写Suspend函数)
void Suspend(void)
{
uint32_t i=0 ;
uint16_t wCNTR ;
__IO uint32_t savePWR_CR=0 ;
wCNTR = _GetCNTR() ;
for( i=0; i<8; i++ )
EP[ i ] = _GetENDPOINT( i ) ;
wCNTR |= CNTR_RESETM ;
_SetCNTR( wCNTR ) ;
wCNTR |= CNTR_FRES ;
_SetCNTR( wCNTR ) ;
wCNTR &= ~CNTR_FRES ;
_SetCNTR( wCNTR ) ;
while( ( _GetISTR()&ISTR_RESET )==0 ) ;
_SetISTR( ( uint16_t )CLR_RESET ) ;
for( i=0; i<8; i++ )
_SetENDPOINT( i, EP[ i ] ) ;
wCNTR |= CNTR_FSUSP ;
_SetCNTR( wCNTR ) ;
wCNTR = _GetCNTR() ;
wCNTR |= CNTR_LPMODE ;
_SetCNTR( wCNTR ) ;
Enter_LowPowerMode() ;
}
(12)在sys.h文件中添加几个重要的数据类型
typedef volatile uint8_t vu8;
#define __IO volatile
(13)stm32f103x.h文件中添加一个数据类型
typedef long long uint64_t ;
通过以上步骤,USB固件库的移植就完成了,然后只需要调用固件库的函数即可完成USB通信。
#include "sys.h"
#include "delay.h"
#include "usart1.h"
#include "lcd.h"
#include "sdio_sdcard.h"
#include "w25q128.h"
#include "malloc.h"
#include "mass_mal.h"
#include "usb_lib.h"
#include "hw_config.h"
#include "usb_pwr.h"
#include "memory.h"
#include "usb_bot.h"
extern u8 Max_Lun ; //支持的磁盘个数
int main()
{
u8 USB_STA, Divece_STA, tct=0, offline_cnt=0;
STM32_Clock_Init( 9 ) ; //系统时钟设置
SysTick_Init( 72 ) ; //延时初始化
USART1_Init( 72, 115200 ) ; //串口初始化为115200
LCD_Init() ; //初始化LCD
W25QXX_Init() ; //初始化W25Q128
my_mem_init( SRAMIN ) ; //初始化内部内存池
//初始化SD卡
if( SD_Init() )
Max_Lun = 0 ; //SD卡错误,则仅只有一个磁盘
//SD 卡正常
else
{
Mass_Memory_Size[ 1 ] = SDCardInfo.CardCapacity ; //得到SD卡容量(字节),当SD卡容量超过4G的时候,需要用到两个u32来表示
Mass_Block_Size[ 1 ] = 512 ; //因为我们在Init里面设置了SD卡的操作字节为512个,所以这里一定是512个字节
Mass_Block_Count[ 1 ] = Mass_Memory_Size[ 1 ]/Mass_Block_Size[ 1 ] ;
}
Mass_Memory_Size[ 0 ] = 1024*1024*12 ; //前12M字节
Mass_Block_Size[ 0 ] = 512 ; //设置SPI FLASH的操作扇区大小为512
Mass_Block_Count[ 0 ] = Mass_Memory_Size[ 0 ]/Mass_Block_Size[ 0 ] ;
delay_ms( 1800 ) ;
USB_Port_Set( 0 ) ; //USB先断开
delay_ms( 700 ) ;
USB_Port_Set( 1 ) ; //USB再次连接
LCD_ShowString( 30, 10, "USB Connecting..." ) ; //提示USB开始连接
Data_Buffer = mymalloc( SRAMIN, BULK_MAX_PACKET_SIZE*2*4 ) ; //为USB数据缓存区申请内存
Bulk_Data_Buff = mymalloc( SRAMIN, BULK_MAX_PACKET_SIZE ) ; //申请内存
//USB配置
USB_Interrupts_Config() ;
Set_USBClock() ;
USB_Init() ;
delay_ms( 1800 ) ;
while(1)
{
delay_ms( 1 ) ;
//状态改变了
if( USB_STA!=USB_STATUS_REG )
{
LCD_ShowString( 30, 30, " " ) ; //清除显示
//正在写
if( USB_STATUS_REG&0x01 )
LCD_ShowString( 30, 30, "USB Writing..." ) ; //提示USB正在写入数据
//正在读
if( USB_STATUS_REG&0x02 )
LCD_ShowString( 30, 30, "USB Reading..." ) ; //提示USB正在读出数据
if( USB_STATUS_REG&0x04 )
LCD_ShowString( 30, 50, "USB Write Err " ) ; //提示写入错误
else
LCD_ShowString( 30, 50, " " ) ; //清除显示
if( USB_STATUS_REG&0x08 )
LCD_ShowString( 30, 80, "USB Read Err " ) ; //提示读出错误
else
LCD_ShowString( 30, 80, " " ) ; //清除显示
USB_STA = USB_STATUS_REG ; //记录最后的状态
}
//获取USB连接状态
if( Divece_STA!=bDeviceState )
{
if( bDeviceState==CONFIGURED )
LCD_ShowString( 30, 10, "USB Connected " ) ; //提示USB连接已经建立
else
LCD_ShowString( 30, 10, "USB DisConnected " ) ; //提示USB被拔出了
Divece_STA = bDeviceState ;
}
tct ++ ;
if( tct==200 )
{
tct = 0 ;
if( USB_STATUS_REG&0x10 )
{
offline_cnt = 0 ; //USB连接了,则清除offline计数器
bDeviceState = CONFIGURED ;
}
//没有得到轮询
else
{
offline_cnt ++ ;
if( offline_cnt>10 )
bDeviceState = UNCONNECTED ; //2s内没收到在线标记,代表USB被拔出了
}
USB_STATUS_REG = 0 ;
}
}
}
全部0条评论
快来发表一下你的评论吧 !