ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

STM32F103通过SPI操作CH376实现TF卡文件读写:简化嵌入式存储方案

STM32F103通过SPI操作CH376实现TF卡文件读写:简化嵌入式存储方案 简介面向STM32F103开发者与嵌入式存储学习者这份资料围绕CH376 TF卡读写模块给出完整工程方案采用SPI通信实现主控与模块交互解决从芯片初始化到文件读写落地的关键问题适合课程设计、毕业设计及快速验证CH376应用的场景。压缩包共163个文件体积约4.41MB以C源码、头文件为主体并包含Keil工程配置、链接脚本、启动汇编以及hex、axf、map等编译产物既能直接打开工程阅读也能重新编译烧录标准外设库中的定时器、ADC、USART驱动一并收纳O、CRF、LST等中间文件也得以保留便于排查链接阶段的细节问题。目前已有993人学习下载。工程中TF卡读写主逻辑与CH376命令交互清晰配套keilkilll.bat清理脚本可一键删除中间文件、快速还原干净工程能帮助理解SPI通信时序、命令帧构造以及文件系统挂载流程对需要在真实硬件上快速验证读写、排查通信故障的开发者提供了直接素材。1. 为什么说 CH376 把 TF 卡读写从“移植 FATFS”降级成了“调几个 SPI 命令”很多人在 STM32F103 上第一次做 TF 卡读写第一反应是移植 FatFS、自己写 SDIO 或 SPI 驱动、处理 CRC 校验和扇区擦写对齐。这套路线不是不行但调试周期通常以周为单位。CH376 的方案完全是另一条路径它把 SD 卡协议、FAT12/16/32 文件系统、长文件名解析全部封装在模块内部MCU 只需要通过 SPI 发送“打开文件”“写数据”“关闭文件”这类高层命令就能完成 TF 卡读写。对只做数据记录、配置读取、固件升级包加载的开发者来说这套方案能省掉大量底层工作量尤其适合 F103 最小系统这类管脚紧张、不想外扩 SDIO 线的硬件。CH376 模块支持并口、UART、SPI 三种接口标题场景里明确走 SPI正好发挥 F103 硬件 SPI 外设的能力。下文从硬件接线、SPI 初始化、文件读写命令、排错验证四个层面讲透命令码部分以 CH376 手册指令表为准代码风格按标准库和 HAL 库两种习惯给出。2. 硬件连接与 SPI 接口选型CH376 模块在 STM32F103 上怎么接线、为什么用硬件 SPI2.1 CH376 三种接口模式里SPI 模式为什么最适合 F103 最小系统CH376 芯片本身自带 USB 主机接口但本方案走 TF 卡不涉及 USB 枚举所以完全用不到 USB 那部分电路。芯片支持 8 位并口、异步串口、SPI 从机三种连接方式其中 SPI 模式引脚占用最少只需要 CS、SCK、MOSI、MISO 加一个中断脚 INT总共 5 根线。常见做法是买现成的 CH376 TF 卡读写模块模块上已经把 CH376、TF 卡座、去耦电容、LDO 都集成好了MCU 侧只需要按 SPI 从机的方式连接。硬件设计上要注意 CH376 的 SPI 是从机角色F103 作为 SPI 主机决定时钟极性和相位。F103 的硬件 SPI 最高可跑 18 MHzAPB1 为 36 MHz 时但 CH376 的 SPI 时钟上限通常低于这个值保守起见把 SPI 时钟分频到不超过 6 MHz能显著降低长线干扰导致的读写出错。2.1.1 让 CH376 进入 SPI 模式的三个前提第一模块的接口模式选择CH376 的 SPI 模式由引脚配置决定具体来说要把模块上的模式跳线或芯片的 MODE 引脚接到对应电平让芯片在复位后识别为 SPI 从机。第二CS 片选必须在收发全程保持低电平任何一条 SPI 命令结束前不能提前拉高。第三INT 引脚要接 MCU 的 GPIO 输入不能用普通查询代替否则在写文件时无法感知 FAT 表更新完成事件。这些前提看起来琐碎但绝大多数 CH376 初始化失败都发生在模式脚没配对或上电时序不对。上电时先给 CH376 供电再拉高复位脚最后操作 SPI 发送命令顺序反了芯片可能一直不回正常状态码。2.2 引脚与供电连错一个都会让文件系统“假死”下面这套接线对应典型的 CH376 TF 卡读写模块和 STM32F103 最小系统板注意 F103 的 PA4 作为软件片选使用PA5、PA6、PA7 是 SPI1 的默认引脚CH376 模块引脚STM32F103 引脚说明CSPA4GPIO 输出软件控制片选低有效SCKPA5SPI1_SCKSPI 时钟MOSIPA7SPI1_MOSI主机输出、从机输入MISOPA6SPI1_MISO主机输入、从机输出INTPB0GPIO 输入中断请求下降沿有效VCC3.3V模块 LDO 输入接 5V 也能工作但建议 3.3VGNDGND共地必须可靠供电是这里最容易出问题的一环。CH376 芯片工作电压是 3.3V模块上若有 LDO 可以把 5V 降到 3.3V但如果你的 F103 板子本身就是 3.3V 供电直接把 VCC 接 3.3V 更干净。电平匹配上F103 的 GPIO 是 5V 容忍但 SPI 引脚输出高电平是 3.3VCH376 识别没有问题反过来 CH376 输出给 F103 的 MISO 也是 3.3V同样安全。TF 卡座的供电同样由模块引出不要尝试在 TF 卡座和 CH376 之间串什么电平转换芯片CH376 内部已经处理了卡侧逻辑电平。若 TF 卡供电电压不稳表现为文件打开失败或写入后校验不符先量卡座的 VDD 对地电压是否稳定在 3.3V 附近。2.3 硬件片选还是软件片选TF 卡读写稳定性从这里分叉STM32F103 的 SPI1 支持 NSS 硬件片选但使用 CH376 时我更推荐把 CS 配成普通 GPIO用软件拉低拉高。原因是硬件 NSS 在多字节传输时可能出现自动翻转而 CH376 的每条命令都可能包含不定长度的数据阶段硬件片选没法精确匹配“命令阶段结束、数据阶段开始”的时刻。软件片选的实现非常简单发送命令前把 CS 拉低发送完最后一个字节且确认 CH376 处理完成后把 CS 拉高。注意一点若你的 F103 板子把 SPI1_NSS 的 PB12 空闲着千万别把它复用为硬件 NSS否则 GPIO 配置和 SPI 外设寄存器会互相干扰。SPI 的时钟极性和相位我建议直接配置为模式 0CPOL0CPHA0这也是 CH376 手册里最常用的时序。模式 0 下数据在 SCK 上升沿采样、下降沿输出与大多数 SPI 从机的默认行为一致。3. 用 SPI 把 CH376“叫醒”STM32F103 的 SPI 初始化与 CH376 命令时序3.1 SPI 时钟参数怎么设模式 0、分频系数和串行时钟频率CH376 的 SPI 命令流程分三个阶段先把命令码通过 SPI 发进去然后根据命令需要读写数据最后芯片通过 INT 引脚产生中断或在状态寄存器给出回应。所以 MCU 侧最重要的不是把 SPI 速率拉到多高而是让时序不被打断、命令阶段和数据阶段切换正确。在 STM32F103 上SPI1 挂在 APB2 总线系统时钟 72 MHz 时 APB2 是 72 MHz。常见的做法是把 SPI 波特率预分频配成 16这样 SCK 实际为 4.5 MHz留足信号完整性裕量。若你的 F103 主频被降到 36 MHz分频可以调到 8得到同样 4.5 MHz 的 SCK。HAL 库的配置如下这段代码在 CubeMX 生成的基础上只改关键参数SPI_HandleTypeDef hspi1; void MX_SPI1_Init(void) { hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; // F103 作为主机 hspi1.Init.Direction SPI_DIRECTION_2LINES; // 全双工 hspi1.Init.DataSize SPI_DATASIZE_8BIT; // CH376 命令按字节收发 hspi1.Init.CLKPolarity SPI_POLARITY_LOW; // CPOL0空闲时钟为低 hspi1.Init.CLKPhase SPI_PHASE_1EDGE; // CPHA0第一个边沿采样 hspi1.Init.NSS SPI_NSS_SOFT; // 软件片选CS 用 GPIO 控制 hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_16; // 72MHz/164.5MHz hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; // 高位先行 HAL_SPI_Init(hspi1); }HAL_SPI_Init 完成后CS 引脚要手动拉高等 CH376 复位完成后才能真正发命令。参数里最容易被忽视的是 NSS 配置为软件模式若这里沿用默认硬件 NSS软件拉 CS 会和外设内部的 NSS 判断打架。标准库 v3.50 的写法则更加直接直接操作寄存器结构体SPI_InitTypeDef SPI_InitStructure; SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_16; SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE);写法只是形式差异关键参数一致。若你用的是 CubeMX 一类的图形工具需要在 SPI1 的 Parameter Settings 里把 NSS 选成 SoftwareClock Polarity 选 LowClock Phase 选 1st Edge分频选 16。3.1.1 为什么要避免把 SCK 配到 18 MHzCH376 的手册推荐时序SPI 时钟上限受芯片内部逻辑影响且模块上的走线和 TF 卡座都会引入寄生电容。F103 的 SPI1 在 72 MHz 主频下最高可输出 18 MHz SCK实际接上 CH376 后线长超过 10 厘米或杜邦线质量一般就会出现偶发命令无响应、读取数据错位。SCK 降到 4.5 MHz 后一条文件写入命令的执行时间从几十微秒变成几百微秒对比 CH376 写 TF 卡本身的毫秒级耗时性能影响可忽略。3.2 最小命令序列发一个 CMD_STATUS 看芯片是否应答先把硬件和 SPI 初始化跑通再谈文件系统。上电后第一步是查询设备状态CH376 提供了状态查询命令MCU 发送该命令后可读取芯片返回的状态字。下面逻辑使用 HAL 库和自封装函数命令码需要对照 CH376 手册的指令表填入。uint8_t CH376_ReadStatus(void) { uint8_t cmd CH376_CMD_GET_STATUS; // 命令码见 CH376 手册指令表 uint8_t status 0xFF; CS_LOW(); // 拉低片选进入 SPI 传输 SPI_ExchangeByte(cmd); status SPI_ExchangeByte(0xFF); // 发送一个假字节读回状态 CS_HIGH(); // 片选拉高命令结束 return status; } uint8_t SPI_ExchangeByte(uint8_t txData) { uint8_t rxData 0; HAL_SPI_TransmitReceive(hspi1, txData, rxData, 1, 100); return rxData; }这段代码里的 CS_LOW 和 CS_HIGH 对应 PA4 的 GPIO 控制注意 CH376 的 SPI 读取往往需要先发送一个字节命令再发送一个填充字节来获取返回值这是多数 SPI 从机的标准做法。若状态码一直是 0xFF 或 0x00先检查 SPI 引脚是否接反尤其 MISO 和 MOSI 对调是最高频的接线错误。3.3 等待 INT# 中断的两种方式和查询式读 TF 卡的区别CH376 在执行完一条命令后会用 INT 引脚产生低电平脉冲通知主机。主机有两种选择将 INT 接到 F103 的 EXTI 外部中断线在中断服务函数里置一个标志位或者在发送命令后轮询 INT 引脚的电平。在 TF 卡读写场景里文件打开这类操作耗时可能从几百微秒到几十毫秒不等轮询的忙等待会浪费 CPU但代码更简单。我通常的做法是给轮询加超时uint8_t CH376_WaitRespond(uint16_t timeout_ms) { while (timeout_ms--) { if (GPIO_ReadPin(GPIOB, GPIO_PIN_0) GPIO_PIN_RESET) { return CH376_RESP_SUCCESS; // INT 引脚拉低表示事件发生 } delay_ms(1); } return CH376_RESP_TIMEOUT; }配合外部中断则是另一套思路EXTI 下降沿触发中断里只做置位主循环里根据标志位继续后续的读状态、读数据操作。哪一种更适合取决于应用若你同时要处理多个外设用查询方式会让 SPI 总线占用时间长建议把 CH376 的 INT 接到 EXTI让 F103 在等待 TF 卡时去干别的事。4. 在 CH376 上读写 TF 卡文件从打开文件到数据落盘的最小代码4.1 文件级读写命令把路径、文件名交给 CH376 内置 FATCH376 内部固件已经实现了 FAT16/FAT32 文件系统MCU 不用关心 FAT 表、目录项、簇链这些概念。要写一个文件只需要按顺序完成“打开或创建文件 → 写入数据 → 更新长度并关闭”。下面以新建一个日志文件为例给出核心代码框架。#define CH376_DATA_BUF_LEN 128 uint8_t ch376_buf[CH376_DATA_BUF_LEN]; uint8_t CH376_FileOpen(const char *path) { uint8_t i 0; CS_LOW(); SPI_ExchangeByte(CH376_CMD_FILE_OPEN); // 打开文件命令 while (path[i] ! 0) { SPI_ExchangeByte(path[i]); // 逐个字节发送文件路径 i; } SPI_ExchangeByte(0x00); // 字符串结束符 CS_HIGH(); if (CH376_WaitRespond(500) ! CH376_RESP_SUCCESS) { return CH376_ERR_NOT_FOUND; // 文件不存在后续可走创建流程 } return CH376_OK; } uint8_t CH376_FileWrite(const uint8_t *data, uint16_t len) { uint16_t i 0; CS_LOW(); SPI_ExchangeByte(CH376_CMD_WRITE_DATA); // 写数据命令 SPI_ExchangeByte((uint8_t)(len 0xFF)); // 数据长度低字节 SPI_ExchangeByte((uint8_t)(len 8)); // 数据长度高字节 for (i 0; i len; i) { SPI_ExchangeByte(data[i]); // 数据体 } CS_HIGH(); if (CH376_WaitRespond(1000) ! CH376_RESP_SUCCESS) { return CH376_ERR_WRITE; // 写失败多为卡异常或满 } return CH376_OK; } uint8_t CH376_FileClose(void) { CS_LOW(); SPI_ExchangeByte(CH376_CMD_FILE_CLOSE); // 关闭文件命令 SPI_ExchangeByte(0x00); // 参数 0正常更新文件长度 CS_HIGH(); return CH376_WaitRespond(500); }这段代码里文件路径使用类似0:/LOG.TXT的格式0 代表第一块逻辑盘。CH376 内部支持的路径分隔符是斜杠/别名盘号和目录层级都包含在路径字符串中。中途断电时若没执行关闭文件命令文件长度可能没有更新掉电恢复后看到文件还在但内容可能停留在上次正常关闭的边界。写文件前若不确定文件是否存在可以先尝试用打开命令返回文件不存在错误后再发创建命令。CH376 手册里把“打开已存在文件”和“新建文件”分开设计错误处理要按顺序走不能直接默认打开成功。4.2 扇区级读写命令绕过文件系统按 512 字节直接访问 TF 卡文件级命令适合日志记录、配置存储但有些场景需要直接操作 SD 卡扇区比如量产测试、批量刷写、修复文件系统损坏的 TF 卡。CH376 同样支持底层扇区读写命令按 512 字节对齐的方式访问。uint8_t sector_buf[512]; uint8_t CH376_SectorRead(uint32_t sector, uint8_t *buf) { uint8_t i; CS_LOW(); SPI_ExchangeByte(CH376_CMD_SECTOR_READ); // 读扇区命令 SPI_ExchangeByte((uint8_t)(sector 0xFF)); // LBA 地址低字节 SPI_ExchangeByte((uint8_t)((sector 8) 0xFF)); SPI_ExchangeByte((uint8_t)((sector 16) 0xFF)); SPI_ExchangeByte((uint8_t)((sector 24) 0xFF)); CS_HIGH(); if (CH376_WaitRespond(1000) ! CH376_RESP_SUCCESS) { return CH376_ERR_SECTOR_READ; // 扇区不存在或卡异常 } for (i 0; i 512; i) { buf[i] SPI_ExchangeByte(0xFF); // 读取一个扇区数据 } CS_HIGH(); return CH376_OK; }扇区读写的 LBA 地址是绝对扇区号与 FAT 文件系统无关。使用该命令前最好先读取 TF 卡的容量参数确认目标扇区号没有超出卡的 LBA 范围否则返回错误的时间点可能发生在数据阶段导致 SPI 总线状态异常。底层扇区写操作风险更高误写会把 MBR 或 FAT 表破坏现场验证时建议准备一张不重要的卡。4.2.1 为什么扇区级读写能解决“文件系统坏了”的问题当 TF 卡经过长期插拔、掉电后出现文件打不开文件级命令会一直报错但扇区级命令可能仍能访问。这种情况下我一般会把整个卡按扇区读出在 PC 端用镜像工具检查分区表和 FAT 结构判断是硬件坏块还是逻辑损坏。CH376 在扇区模式下不会介入文件系统让排查问题变得更直接。4.3 初始化 TF 卡时的超时、重试参数建议CH376 对 TF 卡的上电识别需要时间不同品牌卡差异很大。代码里做三次初始化尝试每次间隔 200 毫秒是比较稳妥的思路。初始化包括设置 SPI 模式、检测 TF 卡是否插入、检查卡类型、准备文件系统。任何一步返回超时都不建议直接判死因为机械卡座的接触不良可能造成偶发失败。卡容量也决定文件系统类型CH376 对 2GB 以下卡通常按 FAT16 处理2GB 以上按 FAT32。首次使用新卡建议先在 PC 上格式化为 FAT32分配单元大小选默认值不要选 exFATCH376 不支持 exFAT。格式化时若把卡分成多个分区CH376 只访问第一个活动分区其余分区无法通过文件级命令看到。5. 读写 TF 卡的验证方法与工程技巧回读对比、掉电安全和目录操作5.1 用回读校验判断写卡是否真的成功文件写完之后立即关闭再重新打开读取内容并逐字节比对是验证写卡是否可靠的首选方法。CH376 有文件读命令和读到结尾的状态返回代码只需把数据读进缓冲区再和原数据做一次 memcmp。对比时要注意边界条件一是文件长度跨扇区边界二是写入长度不是整数扇区时文件系统补齐的部分不需要参与比对。实际测试中往同一个文件写入 1MB 数据回读不一致的概率如果超过万分之一优先检查 SCK 频率和供电稳定性而不是怀疑 CH376 芯片本身。5.2 掉电安全与 FAT32 格式化的坑文件在写入过程中如果突然断电FAT 表的目录项和文件长度很可能没有同步更新重启后文件系统报错。应对方法是写文件时先写临时文件写完关闭后改名覆盖目标文件。CH376 的命令集支持文件重命名可以保证旧文件在写入期间始终有效。FAT32 格式化时分配单元大小选过大会造成空间浪费选过小会降低吞吐默认值即可。另外TF 卡在 CH376 模块上首次使用前最好用 PC 读卡器执行一次完整格式化避免买到的卡带有隐藏分区或厂商的调试分区。5.3 目录操作与文件名匹配CH376 按“/A/B.TXT”解析路径的注意点文件路径中目录名和文件名按 8.3 短文件名规则匹配时最稳定。使用长文件名时要注意CH376 内部对长文件名的支持是需要对应命令开关的切换后目录项的读取方式不同。路径以0:/开头目录深度没有硬编码限制但每层目录名不能包含中文字符。若需要按目录归集数据先发创建目录命令再进入目录创建文件。很多开发者在这里犯的错是路径中用了反斜杠\在 C 字符串里被当作转义符真正发送给 CH376 的字符已经变了。统一用正斜杠0:/DATA/LOG.TXT或0:/DATA/LOG.TXT这类写法可以少踩一个坑。最后还有一个工程技巧给 CH376 的每个命令都封装一个返回码和计数器调试时通过串口把这些信息打出来能快速定位是与 TF 卡通信失败还是文件系统操作失败。读写 TF 卡的本质没有变只是 CH376 帮我们把最枯燥的部分扛了。本文还有配套的精品资源点击获取
返回列表