手把手教你用STM32和CH376芯片读写U盘(附完整SPI驱动代码)

张开发
2026/4/19 19:37:14 15 分钟阅读

分享文章

手把手教你用STM32和CH376芯片读写U盘(附完整SPI驱动代码)
STM32与CH376芯片U盘读写实战从SPI驱动到文件系统全解析在嵌入式系统中扩展USB主机功能一直是开发者面临的挑战。想象一下你的STM32项目需要记录传感器数据到U盘或者从U盘中读取配置文件——这正是CH376这类USB主机控制芯片大显身手的场景。不同于直接使用MCU内置USB外设的复杂协议栈CH376通过简单的SPI接口为嵌入式系统提供了即插即用的U盘读写能力。本文将带你从零构建一个完整的解决方案涵盖硬件连接、SPI驱动调试、文件系统操作等关键环节并提供可直接复用的代码模块。1. 硬件设计与SPI接口配置1.1 CH376硬件连接要点CH376支持三种通信接口其中SPI模式因其硬件资源占用少、速率适中最受青睐。典型连接方案需要关注以下关键点电源设计CH376T3.3V版本需稳定供电建议在VCC引脚附近放置0.1μF去耦电容SPI信号线SCK时钟线建议使用GPIO推挽输出SDOMISO主设备输入需配置为上拉输入SDIMOSI主设备输出推挽输出模式SCS片选信号低电平有效中断信号INT引脚需配置为下拉输入用于事件通知U盘接口VBUS需提供5V电源可通过MOSFET控制提示若使用软件模拟SPI所有GPIO速度建议设置为2MHz以避免信号振铃1.2 STM32端GPIO初始化代码void CH376_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; // 启用GPIO时钟根据实际连接修改 __HAL_RCC_GPIOB_CLK_ENABLE(); // 配置SCK, SDI, SCS为推挽输出 GPIO_InitStruct.Pin CH376_SCK_PIN | CH376_SDI_PIN | CH376_SCS_PIN; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(CH376_PORT, GPIO_InitStruct); // 配置SDO为上拉输入 GPIO_InitStruct.Pin CH376_SDO_PIN; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(CH376_PORT, GPIO_InitStruct); // 配置INT为下拉输入 GPIO_InitStruct.Pin CH376_INT_PIN; GPIO_InitStruct.Pull GPIO_PULLDOWN; HAL_GPIO_Init(CH376_INT_PORT, GPIO_InitStruct); // 初始状态片选置高 HAL_GPIO_WritePin(CH376_PORT, CH376_SCS_PIN, GPIO_PIN_SET); }2. SPI通信层实现与调试2.1 软件SPI时序精准控制硬件SPI虽然方便但在某些引脚受限的场景下软件模拟SPI反而更具灵活性。关键是要确保时序符合CH376的规格要求时钟空闲状态为高电平数据在时钟上升沿采样最小半周期延迟为0.5μs对应最大1MHz时钟// 精确的微秒级延迟函数需根据CPU频率校准 void CH376_DelayUS(uint32_t us) { uint32_t ticks SystemCoreClock / 1000000 * us / 5; while(ticks--); } // 软件SPI写一个字节 void CH376_SPI_WriteByte(uint8_t data) { HAL_GPIO_WritePin(CH376_PORT, CH376_SCS_PIN, GPIO_PIN_RESET); for(uint8_t i0; i8; i) { HAL_GPIO_WritePin(CH376_PORT, CH376_SCK_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(CH376_PORT, CH376_SDI_PIN, (data 0x80) ? GPIO_PIN_SET : GPIO_PIN_RESET); CH376_DelayUS(1); HAL_GPIO_WritePin(CH376_PORT, CH376_SCK_PIN, GPIO_PIN_SET); CH376_DelayUS(1); data 1; } HAL_GPIO_WritePin(CH376_PORT, CH376_SCS_PIN, GPIO_PIN_SET); } // 软件SPI读一个字节 uint8_t CH376_SPI_ReadByte(void) { uint8_t data 0; HAL_GPIO_WritePin(CH376_PORT, CH376_SCS_PIN, GPIO_PIN_RESET); for(uint8_t i0; i8; i) { HAL_GPIO_WritePin(CH376_PORT, CH376_SCK_PIN, GPIO_PIN_RESET); CH376_DelayUS(1); data 1; if(HAL_GPIO_ReadPin(CH376_PORT, CH376_SDO_PIN)) data | 0x01; HAL_GPIO_WritePin(CH376_PORT, CH376_SCK_PIN, GPIO_PIN_SET); CH376_DelayUS(1); } HAL_GPIO_WritePin(CH376_PORT, CH376_SCS_PIN, GPIO_PIN_SET); return data; }2.2 通信链路验证技巧在进入文件操作前必须确保SPI通信可靠。CH376提供了专门的测试命令uint8_t CH376_TestConnection(void) { CH376_SPI_WriteByte(CMD11_CHECK_EXIST); CH376_SPI_WriteByte(0x55); // 测试模式 uint8_t response CH376_SPI_ReadByte(); if(response ! 0xAA) { printf(SPI通信测试失败收到:0x%02X\r\n, response); return 0; } printf(SPI通信测试成功\r\n); return 1; }常见通信问题排查表现象可能原因解决方案无响应片选信号异常检查SCS引脚连接和电平返回0xFF时钟相位错误调整SCK边沿采样点随机错误电源不稳定增加电源去耦电容间歇性失败中断冲突检查INT引脚配置3. U盘文件系统操作实战3.1 文件操作基本流程CH376内置了FAT文件系统引擎简化了文件操作。典型工作流程如下初始化芯片设置USB主机模式检测设备连接轮询或中断方式打开文件支持创建新文件或打开现有文件读写操作支持顺序和随机存取关闭文件确保数据写入物理设备// 初始化USB主机模式 uint8_t CH376_InitUSBHost(void) { CH376_SPI_WriteByte(CMD11_SET_USB_MODE); CH376_SPI_WriteByte(0x06); // 模式6USB主机模式 uint8_t status CH376_SPI_ReadByte(); if(status ! CMD_RET_SUCCESS) { printf(USB主机模式设置失败:0x%02X\r\n, status); return 0; } return 1; } // 等待U盘连接超时单位毫秒 uint8_t CH376_WaitDiskReady(uint32_t timeout) { uint32_t start HAL_GetTick(); while(HAL_GetTick() - start timeout) { CH376_SPI_WriteByte(CMD01_DISK_CONNECT); uint8_t status CH376_SPI_ReadByte(); if(status USB_INT_SUCCESS) return 1; HAL_Delay(100); } return 0; }3.2 文件创建与写入示例// 创建并写入文本文件 uint8_t CH376_WriteTestFile(void) { // 打开/创建文件 CH376_SPI_WriteByte(CMD0H_FILE_OPEN); uint8_t status CH376_SPI_ReadByte(); if(status ! USB_INT_SUCCESS) { printf(文件打开失败:0x%02X\r\n, status); return 0; } // 设置文件指针到开始 CH376_SPI_WriteByte(CMD4H_BYTE_LOCATE); CH376_SPI_WriteByte(0x00); CH376_SPI_WriteByte(0x00); CH376_SPI_WriteByte(0x00); CH376_SPI_WriteByte(0x00); status CH376_SPI_ReadByte(); // 准备写入数据 uint8_t data[] STM32 CH376测试数据\r\n; uint16_t length sizeof(data) - 1; // 开始写入 CH376_SPI_WriteByte(CMD2H_BYTE_WRITE); CH376_SPI_WriteByte((uint8_t)length); CH376_SPI_WriteByte((uint8_t)(length 8)); for(uint16_t i0; ilength; i) { CH376_SPI_WriteByte(data[i]); } status CH376_SPI_ReadByte(); // 关闭文件 CH376_SPI_WriteByte(CMD0H_FILE_CLOSE); CH376_SPI_WriteByte(0x01); // 更新文件长度 status CH376_SPI_ReadByte(); return 1; }注意文件名必须使用大写字母且包含完整路径如/DATA.TXT4. 高级应用与性能优化4.1 中断驱动设计轮询方式效率低下利用INT引脚中断可大幅提升系统响应速度// 中断服务例程 void CH376_INT_IRQHandler(void) { if(__HAL_GPIO_EXTI_GET_IT(CH376_INT_PIN) ! RESET) { __HAL_GPIO_EXTI_CLEAR_IT(CH376_INT_PIN); CH376_SPI_WriteByte(CMD01_GET_STATUS); uint8_t status CH376_SPI_ReadByte(); switch(status) { case USB_INT_DISK_READ: // 处理磁盘读取完成 break; case USB_INT_DISK_WRITE: // 处理磁盘写入完成 break; case USB_INT_DISK_CONNECT: // 处理U盘插入 break; case USB_INT_DISK_DISCONNECT: // 处理U盘拔出 break; } } }4.2 文件插入操作技巧在现有文件中插入数据是个常见需求但直接插入会覆盖后续内容。正确做法是定位到插入位置读取后续内容到缓冲区写入新数据追加原缓冲区内容uint8_t CH376_FileInsert(const char* filename, uint32_t offset, uint8_t* data, uint16_t length) { // 1. 打开文件 // 2. 定位到offsetlength位置 // 3. 读取后续数据到缓冲区 // 4. 定位回offset位置 // 5. 写入新数据 // 6. 追加缓冲区内容 // 7. 关闭文件 // 错误处理省略... }4.3 性能优化对比表优化措施执行时间(ms)内存占用实现复杂度轮询模式1200低简单中断驱动300中中等DMA传输150高复杂双缓冲100较高复杂在实际项目中使用中断驱动配合合理的缓冲区大小通常4KB能达到最佳性价比。

更多文章