STM32 USB NAND FLASH 模拟U盘

本文介绍如何使用STM32开发板的NAND Flash来模拟U盘,并详细讲解了涉及的USB驱动及文件系统的实现过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们现在用开发板的NAND Flash模拟一个U盘,在我们之前的CustomHID工程上修改。
首先要到官方工程中拷贝下面几个文件:usb_bot.c,usb_bot.h,usb_scsi.c,usb_scsi.h,mass_mal.c,mass_mal.h,memory.c,memory.h,把这些文件加入到我们的USB_User组中。我们还要到网上下载一个圈圈写的fsmc_nand.c和fsmc_nand.h,因为我试了下官方的fsmc_nand和nand_if两个文件键入工程后,NAND无法初始化成功,读写就更不行了,检查过硬件引脚接线,都没有问题,程序嘛,说起来惭愧,没有时间深究,也是为了省事,所以就到网上下了大家广泛使用的圈圈写的fsmc_nand.c文件了,而且还有详细注解。还要注意要添加外设库文件:stm32f10x_fsmc.c文件,否则会出现很多未定义的。截图看看:
STM32 USB NAND FLASH 模拟U盘 - ziye334 - ziye334的博客
由于很多文件是有官方拷贝过来的,编译的时候肯定会又出现很多问题,相信大家有能力慢慢解决的,不过这里有个重要的步骤,大家一定要做:打开project->Options forTarget “.....”->C/C++的Define域要添加几个宏定义,跟USB相关的就是要增加USE_STM3210E_EVAL这个宏定义,这样的话我们就相当于选了一款评估板,usb的代码就会根据这个宏定义选择相应的代码编译了。
 STM32 USB NAND FLASH 模拟U盘 - ziye334 - ziye334的博客
  接下去就讲讲代码一直部分了。首先是usb_desc.c这个文件,mass stroage的描述符跟CustomHID或鼠标键盘的不一样,它没有HID鸟叔服和报告描述符了,多了一个接口字符串描述符,端点不再是中断传输,而是批量传输了。配置描述符没有什么变化,大家修改下VID和PID吧;接口描述符的bNumEndpoints(该接口所使用的端点数)域为2个端点,bInterfaceClass(该接口所使用的类)域改为0x08表示MASS STORAGE 类,bInterfaceSubClass(该接口所用的子类)域改为0x06,nInterfaceProtocol (该接口使用的协议)与改为0x50,加一个接口的字符串索引iInterface域为4;输入端点描述符设置端点1为输入端点,负责向USB主机发送数据,该端点的 bmAttributes端点的属性改为为批量传输0x02,支持最大的数据包长度为0x40,即64字节,不需要轮询时间;端点2作为输出端点,接收USB发过来的数据,跟端点1一样设置批量传输,最大数据包长度为64字节,不需要轮询你时间。其他的描述符可以不用变。最后还要描写一个接口字符串描述符,大姐可以自定义写。代码贴出如下:

/* USB标准设备描述符*/ const uint8_t MASS_DeviceDescriptor[MASS_SIZ_DEVICE_DESC] = { 0x12, /*bLength:长度,设备描述符的长度为18字节*/ USB_DEVICE_DESCRIPTOR_TYPE, /*bDescriptorType:类型,设备描述符的编号是0x01*/ 0x00, /*bcdUSB:所使用的USB版本为2.0*/ 0x02, 0x00, /*bDeviceClass:设备所使用的类代码*/ 0x00, /*bDeviceSubClass:设备所使用的子类代码*/ 0x00, /*bDeviceProtocol:设备所使用的协议*/ 0x40, /*bMaxPacketSize:最大包长度为64字节*/ 0x34, /*idVendor:厂商ID为0x1234*/ 0x22, 0x11, /*idProduct:产品ID为0x1010*/ 0x11, 0x00, /*bcdDevice:设备的版本号为2.00*/ 0x02, 1, /*iManufacturer:厂商字符串的索引*/ 2, /*iProduct:产品字符串的索引*/ 3, /*iSerialNumber:设备的序列号字符串索引*/ 0x01 /*bNumConfiguration:设备有1种配置*/ }; /* CustomHID设备描述符 */ /* USB配置描述符集合(配置、接口、端点、类、厂商)(Configuration, Interface, Endpoint, Class, Vendor */ const uint8_t MASS_ConfigDescriptor[MASS_SIZ_CONFIG_DESC] = { 0x09, /*bLength:长度,设备字符串的长度为9字节*/ USB_CONFIGURATION_DESCRIPTOR_TYPE, /*bDescriptorType:类型,配置描述符的类型编号为0x2*/ MASS_SIZ_CONFIG_DESC, /*wTotalLength:配置描述符的总长度为41字节*/ 0x00, 0x01, /*bNumInterfaces:配置所支持的接口数量1个*/ 0x01, /*bConfigurationValue:该配置的值*/ 0x00, /*iConfiguration:该配置的字符串的索引值,该值为0表示没有字符串*/ 0xC0, /* bmAttributes:设备的一些特性,0xc0表示自供电,不支持远程唤醒 D7:保留必须为1,D6:是否自供电,D5:是否支持远程唤醒,D4~D0:保留设置为0*/ 0x32, /*从总线上获得的最大电流为100mA */ // 0x96, /*MaxPower:设备需要从总线上获取多少电流,单位为2mA,0x96表示300mA*/ /************** 接口描述符****************/ 0x09, /*bLength:长度,接口描述符的长度为9字节 */ USB_INTERFACE_DESCRIPTOR_TYPE,/* bDescriptorType:接口描述符的类型为0x4 */ 0x00, /*bInterfaceNumber:该接口的编号*/ 0x00, /*bAlternateSetting:该接口的备用编号 */ 0x02, /*bNumEndpoints:该接口所使用的端点数*/ 0x08, /*bInterfaceClass该接口所使用的类为MASS STORAGE*/ 0x06, /*bInterfaceSubClass:该接口所用的子类 1=BOOT, 0=no boot */ 0x50, /*nInterfaceProtocol :该接口使用的协议0=none, 1=keyboard, 2=mouse */ 4, /*iInterface: 该接口字符串的索引 */ /********************输入端点描述符******************/ 0x07, /* bLength: 端点描述符的长度为7字节*/ USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: 端点描述符的类型为0x05*/ 0x81, /* bEndpointAddress: 该端点(输入)的地址,D7:0(OUT),1(IN),D6~D4:保留,D3~D0:端点号*/ 0x02, /* bmAttributes: 端点的属性为为批量传输. D0~D1表示传输类型:0(控制传输),1(等时传输),2(批量传输),3(中断传输) 非等时传输端点:D2~D7:保留为0 等时传输端点: D2~D3表示同步的类型:0(无同步),1(异步),2(适配),3(同步) D4~D5表示用途:0(数据端点),1(反馈端点),2(暗含反馈的数据端点),3(保留),D6~D7:保留,*/ 0x40, /* wMaxPacketSize: 该端点支持的最大包长度为64字节*/ 0x00, 0x00, /* bInterval: 轮询间隔(2 ms) */ /********************输出端点描述符******************/ 0x07, /* 端点描述符的长度为7字节 */ USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: 端点描述符的类型为0x05*/ 0x02, /* bEndpointAddress: 该端点(输出)的地址,D7:0(OUT),1(IN),D6~D4:保留,D3~D0:端点号*/ 0x02, /* bmAttributes: 端点的属性为为批量传输端点 */ 0x40, /* wMaxPacketSize: 该端点支持的最大包长度为64字节 */ 0x00, 0x00, /* bInterval: 轮询间隔(2 ms) */ }; /* 语言ID描述符 */ const uint8_t MASS_StringLangID[MASS_SIZ_STRING_LANGID] = { MASS_SIZ_STRING_LANGID, /*bLength:本描述符的长度为4字节*/ USB_STRING_DESCRIPTOR_TYPE, /*bDescriptorType:字符串描述符的类型为0x03*/ 0x09, /*bString:语言ID为0x0409,表示美式英语*/ 0x04 }; /* LangID = 0x0409: U.S. English*/ /*厂商字符串描述符*/ const uint8_t MASS_StringVendor[MASS_SIZ_STRING_VENDOR] = { MASS_SIZ_STRING_VENDOR, /*bLength:厂商字符串描述符的长度*/ USB_STRING_DESCRIPTOR_TYPE, /*bDescriptorType:字符串描述符的类型为0x03*/ 'B' , 0, 'y', 0, ':' , 0, 'z' , 0, 'i', 0, 'y', 0,'e', 0,'3', 0, '3', 0, '4', 0 /*自定义*/ }; /*产品的字符串描述符*/ const uint8_t MASS_StringProduct[MASS_SIZ_STRING_PRODUCT] = { MASS_SIZ_STRING_PRODUCT, /* bLength:产品的字符串描述符*/ USB_STRING_DESCRIPTOR_TYPE, /* bDescriptorType:字符串描述符的类型为0x03*/ 'z', 0, 'i', 0, 'y', 0, 'e', 0, '3', 0, '3', 0 ,'4',0/*自定义*/ }; /*产品序列号的字符串描述符*/ uint8_t MASS_StringSerial[MASS_SIZ_STRING_SERIAL] = { MASS_SIZ_STRING_SERIAL, /* bLength:产品序列号*/ USB_STRING_DESCRIPTOR_TYPE, /* bDescriptorType:字符串描述符的类型为0x03*/ '1', 0, '2', 0, '3', 0,'4', 0,'5', 0, '6', 0, '7', 0 /*自定义*/ }; /*接口字符串描述符*/ const uint8_t MASS_StringInterface[MASS_SIZ_STRING_INTERFACE] = { MASS_SIZ_STRING_INTERFACE, 0x03, /* Interface 0: "ST Mass" */ 'S', 0, 'T', 0, ' ', 0, 'M', 0, 'a', 0, 's', 0, 's', 0 };

接下去讲讲hw_config.c的文件的修改。该文件首先要添加几个关于内存读写的LED灯的相关函数,主要有四个:Led_RW_OFF(),Led_RW_ON();USB_Configured_LED(),USB_NotConfigured_LED(),代码如下:

void Led_RW_ON(void) { LED3_ON(); }

void Led_RW_OFF(void) { LED3_OFF(); } /void USB_Configured_LED(void) { LED1_ON(); } void USB_NotConfigured_LED(void) { LED1_OFF(); }

我们用开发板上的LED1和LED2,LED1连表示正在读写内存,LED2亮表示USB配置成功。我们还要编写一个MAL_Init()函数,用来初始化媒体接口层函数,如下:

void MAL_Config(void) { RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC, ENABLE); //一定要使能FSMC的时钟,否则电脑读不出U盘的容量的信息 MAL_Init(0); }

我们当前只是使用一个逻辑单元,即我们要在电脑上产生1个盘,所以MAL_Init(0),但是这里注意一定要使能下FSMC的时钟,否则电脑会读不出U盘的信息,还跳出需要格式化串口,点击格式化去无法格式化,现象如下:
STM32 USB NAND FLASH 模拟U盘 - ziye334 - ziye334的博客
 MAL_Config()我在我工程的BSP.c的BSP_Init()中调用。这样的话hw_config.c文件就修改完毕了。
接下去就是usb_prop.c文件了,这个文件修改的地方还不少。首先我们的在该文件的最上方定义一个变量uint32_t Max_Lun=0;表示最大逻辑单元数,0表示1个逻辑盘,1则表示2个逻辑盘。接着修改复位函数MASS_Reset()不详细讲了,看代码:

extern unsigned char Bot_State; extern Bulk_Only_CBW CBW;

void MASS_Reset(void) { /* Set CustomHID_DEVICE as not configured */ pInformation->Current_Configuration = 0; //设置当前的配置为0,表示没有配置过 pInformation->Current_Interface = 0; //默认的接口 /* Current Feature initialization */ pInformation->Current_Feature = MASS_ConfigDescriptor[7];//当前的属性,bmAttributes:设备的一些特性,0xc0表示自供电,不支持远程唤醒 #ifdef STM32F10X_CL /* EP0 is already configured in DFU_Init() by USB_SIL_Init() function */ /* Init EP1 IN snd EP1 OUT as Interrupt endpoint */ OTG_DEV_EP_Init(EP1_IN, OTG_DEV_EP_TYPE_INT, EP1_SIZE); OTG_DEV_EP_Init(EP1_OUT, OTG_DEV_EP_TYPE_INT, EP1_SIZE); #else SetBTABLE(BTABLE_ADDRESS); /* Initialize Endpoint 0 */ SetEPType(ENDP0, EP_CONTROL); //设置端点1为控制端点 SetEPTxStatus(ENDP0, EP_TX_NAK); //设置端点0发送延时 SetEPRxAddr(ENDP0, ENDP0_RXADDR); //设置端点0的接收缓冲区地址 SetEPTxAddr(ENDP0, ENDP0_TXADDR); //设置端点0的发送缓冲区地址 Clear_Status_Out(ENDP0); //清除端点0的状态 SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);//设置端点0的接收的计数 SetEPRxValid(ENDP0); //使能接收状态 /* Initialize Endpoint 1 */ SetEPType(ENDP1, EP_BULK); //设置端点1为中断控制端点 SetEPTxAddr(ENDP1, ENDP1_TXADDR); //设置端点1的接收缓冲地址 SetEPTxStatus(ENDP1, EP_TX_NAK); SetEPRxStatus(ENDP1, EP_RX_DIS); /* Initialize Endpoint 2 */ SetEPType(ENDP2, EP_BULK); //设置端点2为中断控制端点 SetEPRxAddr(ENDP2, ENDP2_RXADDR); //设置端点2的接收缓冲地址 SetEPRxCount(ENDP2, Device_Property.MaxPacketSize); //设置端点2的接收计数 SetEPRxStatus(ENDP2, EP_RX_VALID); SetEPTxStatus(ENDP2, EP_TX_DIS); bDeviceState = ATTACHED; //设置设备状态为 ATTACHED状态 /* Set this device to response on default address */ SetDeviceAddress(0); //设置设备为默认地址 #endif /* STM32F10X_CL */ bDeviceState = ATTACHED; CBW.dSignature = BOT_CBW_SIGNATURE; Bot_State = BOT_IDLE; USB_NotConfigured_LED(); }

处理带数据阶段的建立函数MASS_Data_Setup也要修改成如下:

RESULT MASS_Data_Setup(uint8_t RequestNo) { uint8_t *(*CopyRoutine)(uint16_t); CopyRoutine = NULL; if ((Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT)) && (RequestNo == GET_MAX_LUN) && (pInformation->USBwValue == 0) && (pInformation->USBwIndex == 0) && (pInformation->USBwLength == 0x01)) { CopyRoutine = Get_Max_Lun; } else { return USB_UNSUPPORT; } if (CopyRoutine == NULL) { return USB_UNSUPPORT; } pInformation->Ctrl_Info.CopyData = CopyRoutine; pInformation->Ctrl_Info.Usb_wOffset = 0; (*CopyRoutine)(0); return USB_SUCCESS; }

没有带数据阶段的建立函数当然也要修改:

RESULT MASS_NoData_Setup(uint8_t RequestNo)
{
if ((Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT))
&& (RequestNo == MASS_STORAGE_RESET) && (pInformation->USBwValue == 0)
&& (pInformation->USBwIndex == 0) && (pInformation->USBwLength == 0x00))
{
/* Initialize Endpoint 1 */
ClearDTOG_TX(ENDP1);

/* Initialize Endpoint 2 */
ClearDTOG_RX(ENDP2);

/*intialise the CBW signature to enable the clear feature*/
CBW.dSignature = BOT_CBW_SIGNATURE;
Bot_State = BOT_IDLE;

return USB_SUCCESS;
}
return USB_UNSUPPORT;
}

还要添加一个获取最大逻辑单元数的函数:Get_Max_Lun()

uint8_t *Get_Max_Lun(uint16_t Length) { if (Length == 0) { pInformation->Ctrl_Info.Usb_wLength = LUN_DATA_LENGTH; return 0; } else { return((uint8_t*)(&Max_Lun)); } }

该文件主要有如下几个函数,其他的一些函数,我们则可以删去:

void MASS_init(void); void MASS_Reset(void); void Mass_Storage_SetConfiguration(void); void Mass_Storage_ClearFeature(void); void Mass_Storage_SetDeviceAddress (void); void MASS_Status_In (void); void MASS_Status_Out (void); RESULT MASS_Data_Setup(uint8_t); RESULT MASS_NoData_Setup(uint8_t); RESULT MASS_Get_Interface_Setting(uint8_t Interface, uint8_t AlternateSetting); uint8_t *MASS_GetDeviceDescriptor(uint16_t ); uint8_t *MASS_GetConfigDescriptor(uint16_t); uint8_t *MASS_GetStringDescriptor(uint16_t); uint8_t *Get_Max_Lun(uint16_t Length);

这里usb_prop.c就修改完了。

memory.c,scsi_data.c、usb_scsi.c就使用官方的程序不需要修改,当然如果出现编译错误,适当修改吧。

最后要修改的是mass_mal.c文件,这个文件有四个函数,分别是MAL_Init()、MAL_Write()、MAL_Read()、MAL_GetStatus()。官方代码里,这个文件有还多#if #else #endif 语句,我们不需要想官方那样写那么多兼容的程序,我们目标很简单,就是NAND,所以我们简化官方程序如下:

uint32_t Mass_Memory_Size[2]; uint32_t Mass_Block_Size[2]; uint32_t Mass_Block_Count[2]; __IO uint32_t Status = 0;

uint16_t MAL_Init(uint8_t lun) { u16 status = MAL_OK; switch (lun) { case 0: FlashInit(); break; default: return MAL_FAIL; } return status; }

uint16_t MAL_Write(uint8_t lun, uint32_t Memory_Offset, uint32_t *Writebuff, uint16_t Transfer_Length) { switch (lun) { case 0: FlashWriteOneSector(Memory_Offset,(u8*)Writebuff, Transfer_Length); break; default: return MAL_FAIL; } return MAL_OK; }

uint16_t MAL_Read(uint8_t lun, uint32_t Memory_Offset, uint32_t *Readbuff, uint16_t Transfer_Length) { switch (lun) { case 0: FlashReadOneSector(Memory_Offset, (u8*)Readbuff, Transfer_Length); break; default: return MAL_FAIL; } return MAL_OK; }

uint16_t MAL_GetStatus (uint8_t lun) { if (lun == 0) { Mass_Block_Count[0] = FLASH_MAX_SECTOR_ADDR/FLASH_SECTOR_SIZE; //NAND_ZONE_SIZE * NAND_BLOCK_SIZE * NAND_MAX_ZONE ; Mass_Block_Size[0] = FLASH_SECTOR_SIZE; //NAND_PAGE_SIZE; Mass_Memory_Size[0] = (Mass_Block_Count[0] * Mass_Block_Size[0]); LED2_ON(); return MAL_OK; } LED2_OFF(); return MAL_FAIL; }

工程总体上修改差不多,一些细节的问题就靠自己解决了。程序修改成功后会出现什么现象呢?看图:

STM32 USB NAND FLASH 模拟U盘 - ziye334 - ziye334的博客
我们可以在我的电脑里的可移动存储设备里多了一个”可移动磁盘(H)“,这就是我用开发板的128M的NAND Flash模拟出来的,我在该盘里添加文件写速度只有130KB/秒左右,读的速度到是很快,有1,5MB/秒:
STM32 USB NAND FLASH 模拟U盘 - ziye334 - ziye334的博客STM32 USB NAND FLASH 模拟U盘 - ziye334 - ziye334的博客
  这个模拟出来的U盘还存在一个问题,就是无法格式化,可能是没有文件系统的原因,也许是程序上存在Bug,总之,慢慢解决吧!!!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值