近来在鼓捣STM32,玩了两个星期,感觉都玩的差不多了,于是准备做个U盘。
首先晒自制的STM32开发板(其实上面就一个LDO稳压器提供3.3V)
它曾经只素一个LQFP转接板...
掺了USB
背面有点纠结啊,因为素漆包线飞线出来的
整个U盘长这样/w\
属性页(25Q16有2MB,没全用)
整个板子上只集成了STM32 USB接口和稳压器,其他的都需要外接
窝从来不用开发板,因为觉得从硬件开始搭才能真正学习单片机
这次采用的是STM32F103R8T6,软件方面采用ST公司官方的USB例子(
STM32_USB-FS-Device_Lib_V3.0.1.zip
822.59KB
ZIP
239次下载
)打磨而来
首先把修改好的文件给放出来:
usbdisk.rar
2.77MB
RAR
303次下载
在Project\Mass_Storage\RVMDK中打开MassStorageSimpleBuffer.uvprojx,然后编译下载到STM32,再把STM32插入USB口,就能看到两个未格式化的分区,格式化之后可以储存数据了
注意:每次下载程序后都会清除内置Flash中的数据!本作品仅供情怀,实际请谨慎使用,不要保存重要数据!
下面简单说一下怎么做到的
1、USB的D-和D+分别接到PA11(USBDM)和PA12(USBDP)上,PA12(USBDP)即D+通过跳线使上拉1.5K电阻到VCC(3.3V),SPI Flash(25Q16)的MISO、MOSI、CLK分别连接到STM32的SPI1,详见STM32 datasheet, 选片CS连接到PC12,25Q16的写保护等引脚接3.3V
一下过程采用原版的USB例子,修改过的可以直接下载
2、在编译那个菜单里面的组合框中选择STM3210E-EVAL
选择菜单 Project->Options for Target 'STM3210E-EVAL'
在Output选项卡里把Create HEX File选上
在C/C++选项卡里的Preprocessor Symbols里的Define: USE_STM3210E_EVAL清除
3、usb_pwr.c
把PowerOn和PowerOff()面里的
USB_Cable_Config(ENABLE);
和
USB_Cable_Config(DISABLE);
这两句删掉,因为没有准备用这个控制电路(通过跳线把USBDP通过1.5K电阻接通到3.3V)
这时候把程序直接编译然后下载到STM32,然后把它插到电脑的USB上就能识别到两个可移动磁盘了
4、usb_desc.c
修改这两个常数为自己想要的 格式看了就懂了
MASS_StringVendor 公司名
MASS_StringProduct 产品名
usb_desc.h
MASS_SIZ_STRING_VENDOR和MASS_SIZ_STRING_PRODUCT素描述MASS_StringVendor和MASS_StringProduct的长度,包括0和开头两个东西的长度,修改字符串之后不要忘了修改MASS_SIZ_STRING_VENDOR和MASS_SIZ_STRING_PRODUCT
5、scsi_data.c
找到Standard_Inquiry_Data和Standard_Inquiry_Data2,里面有3个字符串可以修改
注意他们的长度不要修改,长度就那么长,多了少了不行
6、memory.c
uint32_t Data_Buffer[BULK_MAX_PACKET_SIZE * 16]; /* 4096 bytes*/
改下这个,因为这个决定了数据缓存有多大的区域,需要大于等于一个扇区的大小
这里内部flash一个扇区1kb,25Q16一个扇区4Kb,所以设定为4kb缓存,就素4096byte = 64byte * 16, 64byte素一个usb包传输的大小,不用管他
7、usb_scsi.c
增加一个函数在最上面:
uint8_t* Get_Custom_Inquiry_Data(uint8_t lun){
return lun == 0 ? Standard_Inquiry_Data : Standard_Inquiry_Data2;
}
在SCSI_Inquiry_Cmd(uint8_t lun)中把if(lun==0)啥的那段改成
Inquiry_Data = Get_Custom_Inquiry_Data(lun);
在SCSI_Format_Cmd中把NAND_Format();这行注解掉
其实窝还不太明白SCSI_Format_Cmd中到底需不需要对Flash或者NAND进行相关操作,窝认为这个命令只是一个查询功能,真正格式化靠的是上位机系统软件,所以只要SCSI的Write10操作没问题就能格式化成功
8、mass_mal.c
这个才是本作的重点!
里面有4个函数:
MAL_Init,MAL_Write,MAL_Read,MAL_GetStatus
分别负责初始化,写扇区,读扇区和读取状态和扇区信息
每个函数都有个lun参数,用来决定是那个分区要被读取
这里的lun只可能是0和1,因为咱们只有两个分区,一个内部flash一个SPI的flash的
这些函数都有返回值的,操作成功返回MAL_OK,失败返回MAL_FAIL
1、首先在MAL_Init里面修改Flash们的初始化方式,对于SPI Flash是Flash25_Init(位于flash25.h),对于内部Flash则为FLASH_Unlock(位于STM32固件库内)
2、然后系统会调用MAL_GetStatus读取两个分区的信息
Mass_Block_Count 总块数
Mass_Block_Size 一个扇区的大小,对于25Q16是4kb,对于内部Flash来说是1kb
Mass_Memory_Size 整个Flash的大小,不能大于Flash能承受的大小哦
3、MAL_Read和MAL_Write
这两个函数起着核心的作用,负责直接与Flash的驱动接轨。
u32 Memory_Offset 代表内存地址偏移,就是读写开始的位置
u32 *Writebuff 和 u32 *Readbuff 是指向读写缓冲区的指针,指针指向的区域是u32类型的,也就是1个word(4个字节)
u16 Transfer_Length 需要传输数据的字节数,注意不是word的数量哦,是byte的个数!
9、flash25.c(自己加的,原版木有,参考修改过的)
掌管着SPI Flash 25Q16的操作,包括初始化,读写还有擦出功能
一般SPI Flash的驱动读写都是用的1字节为基本单位的缓冲区,而这里用的是word为单位的,所以窝修改之后的程序里面用的25Q16驱动和一般的驱动不太一样,是自己写的捏
重点是批量读取Word函数
void Flash25_Read(u32 address, u32 *Readbuff, u16 length){
u16 i;
u8 buffer[4];
SPI_CS_LOW();
SPI_WriteByte(FLASH25_READ);
SPI_WriteByte((address & 0x00FF0000) >> 16);
SPI_WriteByte((address & 0x0000FF00) >> 8);
SPI_WriteByte((address & 0x000000FF));
for(i=0;i<length;i+=4){
buffer[0] = SPI_ReadByte();
buffer[1] = SPI_ReadByte();
buffer[2] = SPI_ReadByte();
buffer[3] = SPI_ReadByte();
Readbuff[i>>2] = (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3];
}
SPI_CS_HIGH();
}
和写入一个Word长度的数据:
void Flash25_WriteWord(u32 address, u32 data){
FLASH25_WriteEnable();
SPI_CS_LOW();
SPI_WriteByte(FLASH25_WRITE);
SPI_WriteByte((address & 0x00FF0000) >> 16);
SPI_WriteByte((address & 0x0000FF00) >> 8);
SPI_WriteByte((address & 0x000000FF));
SPI_WriteByte((data & 0xFF000000) >> 24);
SPI_WriteByte((data & 0x00FF0000) >> 16);
SPI_WriteByte((data & 0x0000FF00) >> 8);
SPI_WriteByte((data & 0x000000FF));
SPI_CS_HIGH();
FLASH25_WaitForWriteEnd();
}
还有用于擦除一个扇区的
void Flash25_SectorErase(u32 address){
FLASH25_WriteEnable();
SPI_CS_LOW();
SPI_WriteByte(FLASH25_SE);
SPI_WriteByte((address & 0xFF0000) >> 16);
SPI_WriteByte((address& 0xFF00) >> 8);
SPI_WriteByte(address & 0xFF);
SPI_CS_HIGH();
FLASH25_WaitForWriteEnd();
}
这三个是最重要的函数,其他还有些函数,他们都被调用过的
Flash25_ReadID这个函数用于检验25Q16和STM32的通信,实际没用
常见问题:
1、为什么在代码中找不到任何文件系统相关?
因为文件系统是PC上的操作系统来搞定的,操作系统可能是Windows,Linux,Mac甚至其他奇怪的东西啊示波器都有可能,文件系统可能是FAT32,exFAT,NTFS,EXT4等等,作为USB适配器的STM32不需要知道这些,只需在规定的时候读写扇区即可,实际编程中在MAL_Read和MAL_Write中实现这些功能
2、为什么格式化失败?
一般来说格式化的失败是读取和写入的失败,如果遇到不能格式化的问题先检查相关Flash的初始化以及读取写入擦除是否正常工作,窝在调试好25Q16的读写之后很快就成功了
3、HardFault_Handler是怎么回事
有的时候程序会卡住,然后debug下发现是程序进到HardFault_Handler里了。HardFault_Handler的原因多半是内存溢出,最有可能的就是memory.c里面的uint32_t Data_Buffer这行,可能是某个Flash的扇区大小超过了缓冲区的大小导致的,把这个缓冲的大小改大就解决问题了。
深度打磨:
如何修改有几个盘符呢?在usb_prop.c中有这么一行uint32_t Max_Lun = 1;,Max_Lun的值等于盘符数量-1,然后在usb_scsi.c,mass_mal.c等文件中把Mass_Block_Count[2],Mass_Block_Size[2],Mass_Memory_Size[2]的长度都改成盘符的数量,最后在mass_mal.c中添加相关处理过程即可。这个窝还没有实验过,不过理论上应该不难。
参考资料:
XXXXXXXXXXXXXXXXXXX/Blog/Detail_RD.Blog_whisperer_XXXXXXXXml?WebShieldDRSessionVerify=5ZhhM3tT565unfPL0iqJXXXXXXXXXXXXXXXXXXXXXXXXXX/blog/static/127412291201272312850693/XXXXXXXXXXXXXXXXXXXXXXX/s/blog_XXXXXXXXXXXXXXXXXXXml
200字以内,仅用于支线交流,主线讨论请采用回复功能。