使用STM32F103C8T6的FALSH与USB功能,模拟U盘,并介绍一下如何操作拷贝到flash的文件。
准备工作
本人使用的windows10,cubemx5.2.1,单片机为STM32F103C8T6,调试器为stlink,以及MDK5.28
PC端识别stm32模拟U盘
- 使用cubemx生成MASS storage工程
需要注意的是MSC_MEDIA_PACKET由512改为2048
并且
minimum heap size / minimum stack size需要增大修改底层驱动
usbd_storage_if.c,这一步在其他博客中都可以找到,(我这里也是应用别人的程序,由于找不到来源了,在这说声抱歉)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40/* USER CODE BEGIN INCLUDE */
#define FLASH_START_ADDR 0x08004000 //
#define FLASH_PAGE_NBR 60 // 60K
/* USER CODE END INCLUDE */
int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size)
{
/* USER CODE BEGIN 3 */
*block_num = FLASH_PAGE_NBR;
*block_size = FLASH_PAGE_SIZE;
return (USBD_OK);
/* USER CODE END 3 */
}
int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
/* USER CODE BEGIN 6 */
memcpy(buf,(uint8_t *)(FLASH_START_ADDR + blk_addr*FLASH_PAGE_SIZE),blk_len*FLASH_PAGE_SIZE);
return USBD_OK;
/* USER CODE END 6 */
}
int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
/* USER CODE BEGIN 7 */
uint16_t i;
HAL_FLASH_Unlock();
FLASH_EraseInitTypeDef f;
f.TypeErase = FLASH_TYPEERASE_PAGES;
f.PageAddress = FLASH_START_ADDR + blk_addr*FLASH_PAGE_SIZE ;
f.NbPages = blk_len;
uint32_t PageError = 0;
HAL_FLASHEx_Erase(&f, &PageError);
for(i=0;i<blk_len*FLASH_PAGE_SIZE;i+=4)
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,FLASH_START_ADDR + blk_addr*FLASH_PAGE_SIZE + i , *(uint32_t *)(&buf[i]));
HAL_FLASH_Lock();
return USBD_OK;
/* USER CODE END 7 */
}
- 编译下载到单片机中,使用USB链接电脑,会提示格式化U盘,因为初始的flash没有分区表之内的信息,全都是0xFF。
- 注意点:之前
MSC_MEDIA_PACKET设置的过小,没能格式化成功
在STM32flash中寻找文件内容
了解FAT文件系统
在电脑格式化U盘的时候,可以试试其他文件系统,我当时没有格式化成功,所以接下来的内容建立在FAT文件系统基础上,FAT百度百科- 磁盘结构
- 引导扇区(Boot Sector):位于第一个扇区,在软盘上就是0柱面(磁道)0磁头1扇区。
- 文件分配表(FAT):紧接着引导扇区的是两个完全相同的FAT表,每个FAT表占用9个(FAT2是FAT1 的 copy)
- 根目录区: FAT表之后是根目录区,根目录区长度不固定
- 数据区:根目录后面就是数据区

引导扇区格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24名称 开始字节 长度 内容 参考值
BS_jmpBOOT 0 3 一个短跳转指令 jmp Label_07c00H
nop
BS_OEMName 3 8 厂商名 'QingFeng'
BPB_BytesPerSec 11 2 每扇区字节数(Bytes/Sector) 0x200
BPB_SecPerClus 13 1 每簇扇区数(Sector/Cluster) 0x1
BPB_ResvdSecCnt 14 2 Boot记录占用多少扇区 ox1
BPB_NumFATs 16 1 共有多少FAT表 0x2
BPB_RootEntCnt 17 2 根目录区文件最大数 0xE0
BPB_TotSec16 19 2 扇区总数 0xB40[2*80*18]
BPB_Media 21 1 介质描述符 0xF0
BPB_FATSz16 22 2 每个FAT表所占扇区数 0x9
BPB_SecPerTrk 24 2 每磁道扇区数(Sector/track) 0x12
BPB_NumHeads 26 2 磁头数(面数) 0x2
BPB_HiddSec 28 4 隐藏扇区数 0
BPB_TotSec32 32 4 如果BPB_TotSec16=0,则由这里给出扇区数 0
BS_DrvNum 36 1 INT 13H的驱动器号 0
BS_Reserved1 37 1 保留,未使用 0
BS_BootSig 38 1 扩展引导标记(29h) 0x29
BS_VolID 39 4 卷序列号 0
BS_VolLab 43 11 卷标 'QingFeng'
BS_FileSysType 54 8 文件系统类型 'FAT12'
引导代码及其他内容 62 448 引导代码及其他数据 引导代码(剩余空间用0填充)
结束标志0xAA55 510 2 第510字节为0x55,第511字节为0xAA 0xAA55FAT项
当文件大小大于一个扇区时,FAT项是非常有用的,它指向文件接下来放置的位置。
FAT项是12位的,所以需要自行转换一下,在根目录区找到文件初始位置,也就是初始簇,找到对应的FAT项,大于等于0xFF8,代表文件没有下一个簇,等于0xFF7,说明坏簇,其余数值代表的是文件内容的下一个簇
根目录区
- 根据文档以及资料,第19个扇区是根目录区,但是我在flash中观察是第3个扇区,(莫名其妙),根目录区的大小由
BPB_RootEntCnt决定,Rootsize = BPB_RootEntCnt * 32 - 根目录区每个条目为32字节,主要是文件名称,格式,起始簇号以及大小

- 根据文档以及资料,第19个扇区是根目录区,但是我在flash中观察是第3个扇区,(莫名其妙),根目录区的大小由
数据区
数据区是从簇2开始,所以根目录中读到的文件开始簇号需要减2,得到的才是数据起始地址,
data_add = data_area_add + (DIR_fst_clus -2 ) * BPB_SecPerClus *BPB_BytesPerSec
- 磁盘结构