STM32F103ZET6移植FATFS:从原理图到SD卡文件读写全流程

发布时间:2026/9/11 10:53:26
STM32F103ZET6移植FATFS:从原理图到SD卡文件读写全流程 简介一份面向STM32开发者的STM32F103ZET6开发板资料包将PDF原理图与FATFS文件系统函数库版实验源码整合在一起适合正在学习单片机存储应用或准备在项目中集成文件系统的开发者。原理图完整绘制了开发板的元器件布局、信号流向和电源分配帮助理解各引脚功能及外部设备连接方式函数库版源码则提供了封装好的C语言例程覆盖SD卡、SPI Flash等常用存储介质上的文件读写操作从应用层到底层驱动均能直接参考。压缩包共268个文件约7.44MB以C语言源文件和头文件为主体另有编译生成的axf、hex、o文件、PDF原理图以及uvprojx工程文件覆盖源码编写、编译、烧录的完整流程并附有编码转换相关模块便于中文文件名处理。已有303人学习下载。借助这套资料既能通过原理图分析硬件设计又能学习STM32的GPIO、SPI接口与FATFS库的配合方式掌握文件系统挂载、打开、读取、写入、删除等核心操作对希望快速上手SD卡数据记录、固件升级等场景的开发来说是提升开发效率的实用参考。1. 拿到一个STM32F103ZET6资料包先分清原理图和源码谁是主线点开一个“开发板PDF原理图FATFS文件系统实验源码”压缩包多数人第一反应是先翻PDF对着图看一遍引脚其实真正能让系统跑起来的是那几份函数库版源码。原理图解决的是“板子上哪些引脚连了什么东西”FATFS实验源码解决的是“怎么往SD卡里写文件并在电脑上读出来”两者配合才能完成一次完整的存储类嵌入式实验。STM32F103ZET6这颗144引脚的芯片有512KB Flash和64KB SRAM跑FATFS时资源绰绰有余这也是绝大多数中容量开发板把它选为主控芯片的原因。这篇文章按照我拿到这类包后的实际工作习惯来写先讲怎么看原理图确定硬件通道再讲FATFS在函数库版工程里的移植步骤最后把编译下载和掉电验证这串流程走通。适合刚把单片机学完、开始接触文件系统的读者也适合手头有板子但资料散乱、想自己重新搭一遍的工程师。2. 从PDF原理图里拆出FATFS实验必须确认的4个硬件条件2.1 定位SD卡槽的通信接口SPI还是SDIOSTM32F103ZET6的PDF原理图拿到手先别管LED和按键直接找SD卡槽。开发板上SD卡座通常标着U2、U3或J开头的编号网上卖得最多那批板子用的是自弹式TF卡座。卡座引脚旁边会有一排标注最常见的是SPI1_SCK、SPI1_MISO、SPI1_MOSI、SPI1_CS这类丝印看到这组引脚名就可以确定卡工作在SPI模式。部分板子会引出SDIO_D0、SDIO_CK、SDIO_CMD这组信号那走的就是SDIO四线模式。这两种模式对应FATFS底层diskio.c里完全不同的初始化时序和读写函数搞错总线基本调不出来。判断SPI和SDIO还有一个更快的办法看SD卡座上有没有接上拉电阻。SPI模式的SD卡空闲时CS、SCK、MOSI都需要上拉到3.3V原理图上一般在卡座旁边排着4颗10k电阻SDIO模式则不需要那么多上拉。开发板的PDF原理图里如果只找到了SD卡座、没有丝印标注就把图放到200%看走线连接到了芯片的哪个引脚顺着网络标号找PA4、PA5、PA6、PA7这四个脚是SPI1PB12、PB13、PB14、PB15是SPI2PC10到PC12加PD2是SDIO。2.2 确认卡座供电和电平转换电路是否完整SD卡工作时供电必须干净原理图上卡座的第4脚VDD会接一个10uF钽电容或者陶瓷电容到地旁边再并联一个0.1uF去耦电容。更关键的看点是电平转换老一批开发板为了兼容5V逻辑会加74LVC4245或TXS0108这类转换芯片新板子直接用3.3V单片机和3.3V卡通信就不加。ST官方的3.3V供电SPI通信不需要电平转换直接连就行但卡座电源引脚上那种10uF电容不能省很多FATFS写文件写到一半卡死不是代码问题是电源纹波太大导致卡内部逻辑复位。电容缺失的判断依据原理图上SD卡VDD引脚如果只有一个空网络名没有旁路电容那板子在批量贴片时很可能也漏了。这种情况下写文件时用示波器量VDD引脚会看到写入瞬间电压跌落超过300mV卡直接忙状态超时FATFS的disk_status返回STA_NODISK。2.3 查询芯片数据手册确认SPI外设时钟和引脚复用功能原理图上确定了PA5、PA6、PA7连的是SPI1后还要回到STM32F103ZET6的数据手册确认这几个引脚的复用功能没有冲突。F103系列芯片PA4的默认复用功能是SPI1_NSSPA5是SPI1_SCKPA6是SPI1_MISOPA7是SPI1_MOSI原理图设计者一般不会改这个但国产GD32的板子偶尔会挪引脚所以核对一遍没坏处。方法是打开数据手册的Alternate function mapping表看引脚号对应的AFIO配置。2.4 从原理图提取LED和串口引脚作为FATFS调试指示灯文件系统实验最头疼的是不知道卡有没有挂载成功所以原理图上还要找两个调试资源一个LED、一个串口。最常见的搭配是PB5接LED、USART1的PA9和PA10接串口芯片CH340。做法是在FATFS的f_mount返回结果里加上debug输出用串口打印错误码。f_mount成功返回FR_OK就点灯失败就灭灯这种做法比插着JTAG反复断点来得直接。USB转串口芯片在原理图里通常标U1引脚标注TXD、RXD跨接到PA10、PA9看起来像是交叉连接其实TXD连MCU的RXD、RXD连MCU的TXD确认这条链路没反就行。LED阳极一般串一个1k电阻到3.3V阴极接MCU引脚这种接法是低电平点亮写代码时要注意GPIO_ResetBits才会亮很多从51转过来的读者会在这里反一下。3. 函数库版工程搭建从CubeMX到标准外设库的取舍3.1 标准外设库和HAL库的差异为什么函数库版源码还能用标题里写的是函数库版读成标准外设库Standard Peripheral Library或者SPL。STM32F103系列函数库版官方在2014年后就不再维护但大量开发板资料和书籍仍然以它为教学蓝本。函数库版的核心优势是直接把寄存器操作封装成函数结构清晰到可以从底层开始理解。FATFS文件系统恰恰是裸机程序函数库版的GPIO、SPI、中断函数全部是同步执行不需要考虑RTOS的互斥问题所以调试起来比HAL库还直接。我现在遇到F103项目仍然喜欢用函数库但会用CubeMX先生成时钟树参考再手动搭建工程这样既不违背函数库的简洁又能拿到准确的72MHz配置。如果自己新建工程用库函数版本3.5.0时钟配置按标准流程走/* stm32f10x.h 中定义 */ #define SYSCLK_FREQ_72MHz 72000000 /* system_stm32f10x.c 中由 SystemInit() 调用 */ static void SetSysClockTo72(void) { RCC-CFGR (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL)); RCC-CFGR | (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9); /* HSE 8MHz8 * 9 72MHz */ }这段代码把外部8MHz晶振经过PLL倍频到72MHzFATFS实验对时钟精度没有太高要求但SD卡的SPI模式需要正确的分频系数。代码中RCC_CFGR_PLLSRC_HSE选择外部晶振作时钟源RCC_CFGR_PLLMULL9把频率乘以9。如果板载晶振不是8M而是12M就必须改用倍频因子6否则串口波特率会偏差超过2%打印信息变成乱码。3.2 下载FATFS源码选对版本和裁剪配置FATFS官方版本目前开源社区最新到R0.152023年发布支持了exFAT和长文件名。做STM32F103实验下载R0.15即可因为使用的是静态内存配置文件读写都靠一个全局文件系统对象不用动态内存分配。下载后只用到源文件ff.c、ff.h、ffconf.h、diskio.c、diskio.h以及一个可选的ff_unicode.c用于中文长文件名。关键配置在ffconf.h里#define _USE_MKFS 1 /* 支持 f_mkfs用于格式化SD卡 */ #define _USE_LFN 2 /* 使用堆栈临时缓冲区支持长文件名 */ #define _MAX_SS 512 /* SD卡扇区大小常规卡固定512 */ #define _FS_RPATH 1 /* 支持 chdir 和相对路径 */ #define _VOLUMES 1 /* 挂载一个卷 */“_USE_LFN”设置为2表示长文件名缓冲在栈上不再占用全局数组节省RAM。如果设置成1则使用静态缓冲区代价是每次同时只能有一个文件操作。对于F103的64KB SRAM来说两种方式都够用我一般选2写代码时注意栈深度足够。“_USE_MKFS”打开后代码里可以用f_mkfs格式化一个没有文件系统的卡省得每次都插读卡器在电脑上格式化成FAT32。但要注意FATFS的格式化只支持FAT12、FAT16、FAT32不支持exFAT。如果电脑上把卡格式化成exFAT挂载会返回FR_NO_FILESYSTEM。3.3 diskio底层接口SD卡SPI模式的读写函数实现磁盘IO是FATFS访问硬件的唯一通道必须提供disk_status、disk_initialize、disk_read、disk_write、disk_ioctl五个函数。F103用SPI驱动SD卡时这些函数内部的硬件操作就是SPI收发。其中一个关键点是初始化。SD卡上电后要发送至少74个时钟脉冲然后依次发送CMD0进入SPI模式、CMD8确认供电电压、ACMD41反复查询直到卡退出上电状态DRESULT disk_initialize(BYTE pdrv) { BYTE cmd[6]; BYTE res; if (pdrv ! 0) return RES_PARERR; /* 拉低CS发送不少于74个时钟脉冲 */ GPIO_ResetBits(GPIOA, GPIO_Pin_4); for (volatile int i 0; i 10; i) { SD_SPI_SendByte(0xFF); } GPIO_SetBits(GPIOA, GPIO_Pin_4); /* CMD0 进入SPI模式 */ cmd[0] 0x40 | 0x00; cmd[1] 0x00; cmd[2] 0x00; cmd[3] 0x00; cmd[4] 0x00; cmd[5] 0x95; /* 合法CRC进入SPI模式后不再校验 */ res SD_SendCmd(cmd, 0xFF); if (res ! 0x01) return STA_NOINIT; /* CMD8 确认支持2.7V~3.6V供电 */ cmd[0] 0x40 | 0x08; cmd[1] 0x00; cmd[2] 0x00; cmd[3] 0x01; cmd[4] 0xAA; cmd[5] 0x87; res SD_SendCmd(cmd, 0xFF); /* 循环发送ACMD41直到卡返回0x00表示就绪 */ for (volatile int i 0; i 1000; i) { cmd[0] 0x40 | 0x37; cmd[1] 0x00; cmd[2] 0x00; cmd[3] 0x00; cmd[4] 0x00; cmd[5] 0x01; res SD_SendCmd(cmd, 0xFF); if (res 0x00) break; } if (res ! 0x00) return STA_NOINIT; /* 初始化完成后把SPI时钟提高到18MHz */ SPI1_Init(SPI_BaudRatePrescaler_4); /* 72M/4 18MHz */ return RES_OK; }这里最容易踩的坑是CMD0的CRC值。SD卡两个模式都用SPI*0进入SPI模式时这两条命令必须带合法CRC之后的命令卡不再校验CRC字段。如果CMD0的CRC发错卡永远不会回应0x01初始化会卡死。另一个坑是ACMD41前必须先发CMD55这个顺序不满足则卡返回0x05非法命令很多自写驱动就在这里失败。代码里SPI初始化分为两段上电时用低速分频128确保卡能响应等ACMD41成功后再切到高速。上电直接用18MHz发送CMD0部分老卡会不识别。3.4 把文件系统调用接到main函数里第一步先执行f_mount完成底层接口后main函数里的调用顺序直接决定成败。FATFS的使用有一条铁律f_mount必须先于任何其他文件操作。但f_mount语义比较特殊它只是注册一个文件系统对象真正挂载是后续第一次访问卷时自动完成的。很多网上的代码在f_mount后立刻f_open然后指向失败就一脸懵。正确的写法是FATFS fs; FIL file; FRESULT res; UINT bw; /* 注册文件系统对象 */ res f_mount(fs, 0:, 1); if (res ! FR_OK) { printf(mount fail: %d\r\n, res); return; } /* 延时等待然后执行一个最简单的写操作 */ res f_open(file, 0:test.txt, FA_CREATE_ALWAYS | FA_WRITE); if (res FR_OK) { res f_write(file, hello fatfs\r\n, 13, bw); f_close(file); }f_mount的第三个参数为1时表示立即挂载0表示延迟挂载。这个参数如果设为0返回可能一直是FR_OK直到f_open才报错不利于排查问题。建议在初始化阶段设为1。f_open的FA_CREATE_ALWAYS会创建一个新文件并清空同名旧文件内容如果只想追加数据改成FA_OPEN_APPEND。f_write最后一个参数bw是实际写入字节数返回后检查它是否等于请求长度——如果小于请求长度说明磁盘已满或者卡异常。4. 编译下载和仿真验证FATFS在开发板上真正写完一个文件4.1 Keil工程文件结构和关键编译选项函数库版实验源码用Keil MDK打开是最省事的。工程里通常包含的目录是标准外设库的src和inc、用户代码目录User、以及FATFS源码目录。打开.uvprojx后第一件事不是编译而是检查Target选项卡里芯片型号和Use MicroLIB选项。FATFS的f_open内部会用到大量栈空间勾选Use MicroLIB能把printf的底层改为更精简的实现RAM占用大幅下降。编译前把STM32F10x_StdPeriph_Driver下的源文件全部加入F103ZET6是高容量型号启动文件选择startup_stm32f10x_hd.sHD代表高容量Flash。如果错选成ld或md文件会导致中断向量表错乱程序一跑就进HardFault。在C/C选项卡里加上两个宏USE_STDPERIPH_DEVICE和STM32F10X_HD前者告诉标准外设库打开设备驱动层后者让库函数中的容量相关代码走对分支。4.2 ST-Link下载设置和常见Flash校验错误下载器设置路径是Options for Target → Debug → SettingsST-Link选SW模式速率设在4MHz以下比较稳定。F103的内核是Cortex-M3SWD只需要PA13的SWDIO和PA14的SWCLK两根线。下载遇到报错“Internal command error”或者“Flash Download failed - Cortex-M3”基本逃不出三个原因芯片读保护没关、Flash容量选择错误、复位引脚被拉低。读保护在ST-Link Utility里执行Mass Erase全片擦除即可。Flash选择在Flash Download选项卡里勾选STM32F10x High-density Flash否则下载器按中容量型号的256KB地址写超过就报错。4.3 串口日志和返回值对照表快速定位卡在哪个环节程序跑起来后通过USART1打印日志是定位FATFS调用最直观的手段。判断运行状态主要依赖f_mount和f_open的返回值建议把下面这个错误码映射表写进调试代码里const char* fr_err_str(FRESULT res) { switch (res) { case FR_OK: return OK; case FR_DISK_ERR: return DISK ERR; case FR_NOT_READY: return NOT READY; case FR_NO_FILE: return NO FILE; case FR_NO_PATH: return NO PATH; case FR_INVALID_NAME: return INVALID NAME; case FR_DENIED: return DENIED; case FR_EXIST: return EXIST; case FR_WRITE_PROTECTED: return WRITE PROTECTED; case FR_NO_FILESYSTEM: return NO FS; default: return UNKNOWN; } }FR_NOT_READY几乎都是卡初始化失败多半是SPI引脚配置错*0卡返回0x01但ACMD41超时就是卡供电不稳或者时钟太快。FR_NO_FILESYSTEM则说明卡没格式化不是代码问题此时调用f_mkfs对卡做一次格式化。FR_DENIED一般出现在以FA_CREATE_ALWAYS打开一个位于只读目录中的文件或者卡的分区表被标记为只读。4.4 用读卡器把板子写的文件放到电脑上验证内容验证实验是否真正成功最终标准是拔下SD卡插到电脑USB读卡器里能在资源管理器里看到test.txt并且打开后内容正确。但要注意几个细节热插拔SD卡前必须执行f_mount(fs, 0:, 0)注销卷或者至少等待f_close完成否则卡上的FAT表可能没写完。板子写文件后马上断电偶尔发现文件大小为0字节原因在于FATFS写文件是分层缓存到内存的f_close才真正把目录项和FAT链写入卡中。如果代码里忘记f_close或程序在f_close前被复位就会出现这个现象。还有一个易踩的坑是文件系统写入后Windows提示“需要修复”这通常是逻辑分区起始扇区没有对齐SD卡是单片式Flash无所谓对齐忽略提示即可。5. 三个能立刻改善FATFS项目的实用技巧5.1 掉电保护模式用f_sync把关键文件及时落盘嵌入式设备经常直接断电FATFS运行过程中掉电导致文件损坏是常见问题。f_sync函数的作用是把文件对象的缓存刷新到磁盘不需要关闭文件即可落盘。做法是每写一小块数据就调用一次f_sync日志采集类的场景里这个函数比f_open/f_close组合安全得多。f_sync的代价是每次触发一次扇区写入频繁调用会缩短Flash寿命合理的频率是每10条日志写一次。5.2 通过GetFattime回调写入可靠的文件时间戳FATFS默认的get_fattime函数需要用户自己实现否则文件日期时间一直是0。利用STM32F103的RTC可以生成时间戳但注意FATFS的时间格式是位域组合年从1980年起算月1~12日1~31时0~23分0~59秒0~59但精度只到两秒DWORD get_fattime(void) { RTC_Get(); return ((DWORD)(RTC_Year - 1980) 25) | ((DWORD)RTC_Month 21) | ((DWORD)RTC_Date 16) | ((DWORD)RTC_Hour 11) | ((DWORD)RTC_Minute 5) | ((DWORD)RTC_Second 1); }这段代码返回的DWORD可以直接被FATFS写入目录项这样用读卡器在电脑上看test.txt时显示的创建时间就是真实时间避免文件全是1980年的困惑。5.3 排查写文件后读回数据不一致的SPI时钟边界文件系统数据显示不全排除文件系统本身后在SPI时钟频率上找原因。SD卡SPI模式标称支持25MHz但实际很多复用IO走线过长的板子在18MHz以上就会出错表现为写入数据正常但读回偶发错位。处理办法是把SPI_BaudRatePrescaler从2改成4让时钟回到18MHz以下。如果改完稳定了说明是硬件信号完整性问题不用去查FATFS代码。SPI模式下的MISO线建议接一个10k上拉电阻避免卡空闲时引脚悬空产生误码。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询