STM32存储左右互搏 USB接口FATS文件读写U盘

STM32存储左右互搏 USB接口FATS文件读写U盘

STM32的USB接口可以例化为Host主机从而对U盘进行操作。SD卡/MicroSD/TF卡也可以通过读卡器转换成U盘使用。这里介绍STM32CUBEIDE开发平台HAL库实现U盘FATS文件访问的例程。

USB接口介绍

常见的USB接口电路部分相似而有不同的连接器应用,连接器有USB-A, USB-MINI, USB-MICRO, USB-TYPEC等。除了USB-A可以直接插入U盘,其它连接器可以通过转接板和转接线和U盘连接。如果用USB-TYPEC公头的U盘,则可以直接插入USB-TYPEC母座的主机。

常见USB-TYPEC接口电路如下:

在这里插入图片描述
去繁化简,主要是5V电源输入,接地,差分信号+ (DP), 差分信号- (DN)4个有效连接。对于STM32, DP连接到了PA12管脚, DN连接到了PA11管脚.

例程采用STM32F401CCU6芯片(兼容STM32F401RCT6, 仅封装不同)对U盘进行识别和读写操作。工程平台为STM32CUBEIDE。

STM32工程配置

首先建立基本工程并设置时钟,USB应用需要采用外部晶体时钟:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
配置UART1作为通讯控制口:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
配置USB接口:
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
配置U盘接口:
在这里插入图片描述
需要单独配置一个不用的GPIO作为输出,并在U盘接口配置参数里选择,这个管脚实际是对应对U盘供电的开关控制,很多板上没有设计出来。
在这里插入图片描述
在这里插入图片描述
再增加一个LED指示灯的控制管脚,这里是PC13,用于U盘操作过程中的指示。

在这里插入图片描述
然后配置FATS文件操作参数:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
保存并生成初始工程代码:
在这里插入图片描述

STM32工程代码

UART串口printf打印输出实现参考:STM32 UART串口printf函数应用及浮点打印代码空间节省 (HAL)

功能代码里实现对USB进行轮询检测,当检测到U盘插入后进行闪灯,当U盘准好操作时保持亮灯。
通过串口发送单字节指令,进行控制操作:
0x01: 装载USB FATS系统
0x02: 创建/打开文件并从头位置写入数据
0x02: 打开文件并从头位置读入数据
0x02: 创建/打开文件并从特定位置写入数据
0x02: 打开文件并从特定位置读入数据

完整的main.c文件如下:

/* USER CODE BEGIN Header */
/********************************************************************************* @file           : main.c* @brief          : Main program body******************************************************************************* @attention** Copyright (c) 2023 STMicroelectronics.* All rights reserved.** This software is licensed under terms that can be found in the LICENSE file* in the root directory of this software component.* If no LICENSE file comes with this software, it is provided AS-IS.********************************************************************************/
//Written by Pegasus Yu in 2023
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "fatfs.h"
#include "usb_host.h"/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "ctype.h"
#include "string.h"#include "usart.h"
/* USER CODE END Includes *//* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
__IO float usDelayBase;
void PY_usDelayTest(void)
{__IO uint32_t firstms, secondms;__IO uint32_t counter = 0;firstms = HAL_GetTick()+1;secondms = firstms+1;while(uwTick!=firstms) ;while(uwTick!=secondms) counter++;usDelayBase = ((float)counter)/1000;
}void PY_Delay_us_t(uint32_t Delay)
{__IO uint32_t delayReg;__IO uint32_t usNum = (uint32_t)(Delay*usDelayBase);delayReg = 0;while(delayReg!=usNum) delayReg++;
}void PY_usDelayOptimize(void)
{__IO uint32_t firstms, secondms;__IO float coe = 1.0;firstms = HAL_GetTick();PY_Delay_us_t(1000000) ;secondms = HAL_GetTick();coe = ((float)1000)/(secondms-firstms);usDelayBase = coe*usDelayBase;
}void PY_Delay_us(uint32_t Delay)
{__IO uint32_t delayReg;__IO uint32_t msNum = Delay/1000;__IO uint32_t usNum = (uint32_t)((Delay%1000)*usDelayBase);if(msNum>0) HAL_Delay(msNum);delayReg = 0;while(delayReg!=usNum) delayReg++;
}
/* USER CODE END PTD *//* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD *//* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM *//* USER CODE END PM *//* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart1;/* USER CODE BEGIN PV */
extern ApplicationTypeDef Appli_state; //UDISK available statusuint8_t uart1_rxd[256]; //Uart1 rx buffer
uint8_t uart1_txd[256]; //Uart1 tax buffer
uint8_t cmd; //Uart1 command indication
uint8_t ustatus = 0; //UDISK ready to operation indication (0: not ready; 1: ready)
uint8_t disk_mount_status = 0; //Disk fats mount status indication (0: unmount; 1: mount)
uint8_t FATS_Buff[_MAX_SS]; //Buffer for f_mkfs() operationFIL file; //File object for fats operation
UINT bytesread; //Byte number of read operation
UINT byteswritten;//Byte number of write operation
uint8_t rBuffer[20];      //Buffer for read
uint8_t WBuffer[20] ={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20}; //Buffer for write
/* USER CODE END PV *//* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
void MX_USB_HOST_Process(void);/* USER CODE BEGIN PFP *//* USER CODE END PFP *//* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
#define LED_OFF HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET)
#define LED_ON HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET)
#define LED_FLASH HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13)
/* USER CODE END 0 *//*** @brief  The application entry point.* @retval int*/
int main(void)
{/* USER CODE BEGIN 1 */FRESULT retUSB; //Operation return result statusdisk_mount_status = 0;uint32_t USB_Read_Size; //Read operation byte numberconst TCHAR* filepath = "0:test.txt"; //File partition number and name/* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();MX_FATFS_Init();MX_USB_HOST_Init();MX_USART1_UART_Init();/* USER CODE BEGIN 2 */PY_usDelayTest();PY_usDelayOptimize();HAL_UART_Receive_IT(&huart1, uart1_rxd, 1);/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE */MX_USB_HOST_Process();/* USER CODE BEGIN 3 */if(Appli_state==APPLICATION_START){LED_FLASH;PY_Delay_us_t(500000);ustatus=0;}else if(Appli_state==APPLICATION_READY) //Status shift time from APPLICATION_START to APPLICATION_READY is about 90 second.{LED_ON;ustatus=1;}else{LED_OFF;ustatus = 0;}if(cmd>0){if(ustatus!=1){cmd = 0;printf("\r\nUSB disk not ready!\r\n");}else{if(cmd==0x01) //Mount USB{cmd = 0;if(disk_mount_status==1) printf("\r\nUSB mounted already\r\n");else{retUSB = f_mount(&USBHFatFS,(TCHAR const*)USBHPath,1); //SD mountif(retUSB==FR_NO_FILESYSTEM){printf("\r\nFile system doesn't exist. Now to format......\r\n");retUSB = f_mkfs((TCHAR const*)USBHPath, FM_FAT, 1024, FATS_Buff, sizeof(FATS_Buff)); //USB formatif(retUSB != FR_OK ){printf("\r\nFormat error: %d\r\n",retUSB);}else{printf("\r\nFormat OK\r\n");}}else if(retUSB==FR_OK){disk_mount_status = 1;printf("\r\nUSB mount successful\r\n");}else{printf("\r\nUSB mount error: %d\r\n",retUSB);}}}else if(cmd==2) //File creation and write{cmd = 0;if(disk_mount_status==0) printf("\r\nUSB not mounted: %d\r\n",retUSB);else{retUSB = f_open( &file, filepath, FA_CREATE_ALWAYS | FA_WRITE );  //Open or create fileif(retUSB == FR_OK){printf("\r\nFile open or creation successful\r\n");retUSB = f_write( &file, (const void *)WBuffer, sizeof(WBuffer), &byteswritten); //Write dataif(retUSB == FR_OK){printf("\r\nFile write successful\r\n");}else{printf("\r\nFile write error: %d\r\n",retUSB);}f_close(&file);   //Close file}else{printf("\r\nFile open or creation error %d\r\n",retUSB);}}}else if(cmd==3) //File read{cmd = 0;if(disk_mount_status==0) printf("\r\nUSB not mounted: %d\r\n",retUSB);else{retUSB = f_open( &file, filepath, FA_OPEN_EXISTING | FA_READ); //Open fileif(retUSB == FR_OK){printf("\r\nFile open successful\r\n");retUSB = f_read( &file, (void *)rBuffer, sizeof(rBuffer), &bytesread); //Read dataif(retUSB == FR_OK){printf("\r\nFile read successful\r\n");PY_Delay_us_t(200000);USB_Read_Size = sizeof(rBuffer);for(uint16_t i = 0;i < USB_Read_Size;i++){printf("%d ", rBuffer[i]);}printf("\r\n");}else{printf("\r\nFile read error: %d\r\n", retUSB);}f_close(&file); //Close file}else{printf("\r\nFile open error: %d\r\n", retUSB);}}}else if(cmd==4) //File locating write{cmd = 0;if(disk_mount_status==0) printf("\r\nUSB not mounted: %d\r\n",retUSB);else{retUSB = f_open( &file, filepath, FA_CREATE_ALWAYS | FA_WRITE);  //Open or create fileif(retUSB == FR_OK){printf("\r\nFile open or creation successful\r\n");retUSB=f_lseek( &file, f_tell(&file) + sizeof(WBuffer) ); //move file operation pointer, f_tell(&file) gets file head locatingif(retUSB == FR_OK){retUSB = f_write( &file, (const void *)WBuffer, sizeof(WBuffer), &byteswritten);if(retUSB == FR_OK){printf("\r\nFile locating write successful\r\n");}else{printf("\r\nFile locating write error: %d\r\n", retUSB);}}else{printf("\r\nFile pointer error: %d\r\n",retUSB);}f_close(&file);   //Close file}else{printf("\r\nFile open or creation error %d\r\n",retUSB);}}}else if(cmd==5) //File locating read{cmd = 0;if(disk_mount_status==0) printf("\r\nUSB not mounted: %d\r\n",retUSB);else{retUSB = f_open(&file, filepath, FA_OPEN_EXISTING | FA_READ); //Open fileif(retUSB == FR_OK){printf("\r\nFile open successful\r\n");retUSB =  f_lseek(&file,f_tell(&file)+ sizeof(WBuffer)/2); //move file operation pointer, f_tell(&file) gets file head locatingif(retUSB == FR_OK){retUSB = f_read( &file, (void *)rBuffer, sizeof(rBuffer), &bytesread);if(retUSB == FR_OK){printf("\r\nFile locating read successful\r\n");PY_Delay_us_t(200000);USB_Read_Size = sizeof(rBuffer);for(uint16_t i = 0;i < USB_Read_Size;i++){printf("%d ",rBuffer[i]);}printf("\r\n");}else{printf("\r\nFile locating read error: %d\r\n",retUSB);}}else{printf("\r\nFile pointer error: %d\r\n",retUSB);}f_close(&file);}else{printf("\r\nFile open error: %d\r\n",retUSB);}}}else;}}}/* USER CODE END 3 */
}/*** @brief System Clock Configuration* @retval None*/
void SystemClock_Config(void)
{RCC_OscInitTypeDef RCC_OscInitStruct = {0};RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};/** Configure the main internal regulator output voltage*/__HAL_RCC_PWR_CLK_ENABLE();__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);/** Initializes the RCC Oscillators according to the specified parameters* in the RCC_OscInitTypeDef structure.*/RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;RCC_OscInitStruct.HSEState = RCC_HSE_ON;RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;RCC_OscInitStruct.PLL.PLLM = 25;RCC_OscInitStruct.PLL.PLLN = 336;RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4;RCC_OscInitStruct.PLL.PLLQ = 7;if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK){Error_Handler();}/** Initializes the CPU, AHB and APB buses clocks*/RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK){Error_Handler();}
}/*** @brief USART1 Initialization Function* @param None* @retval None*/
static void MX_USART1_UART_Init(void)
{/* USER CODE BEGIN USART1_Init 0 *//* USER CODE END USART1_Init 0 *//* USER CODE BEGIN USART1_Init 1 *//* USER CODE END USART1_Init 1 */huart1.Instance = USART1;huart1.Init.BaudRate = 115200;huart1.Init.WordLength = UART_WORDLENGTH_8B;huart1.Init.StopBits = UART_STOPBITS_1;huart1.Init.Parity = UART_PARITY_NONE;huart1.Init.Mode = UART_MODE_TX_RX;huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;huart1.Init.OverSampling = UART_OVERSAMPLING_16;if (HAL_UART_Init(&huart1) != HAL_OK){Error_Handler();}/* USER CODE BEGIN USART1_Init 2 *//* USER CODE END USART1_Init 2 */}/*** @brief GPIO Initialization Function* @param None* @retval None*/
static void MX_GPIO_Init(void)
{GPIO_InitTypeDef GPIO_InitStruct = {0};/* GPIO Ports Clock Enable */__HAL_RCC_GPIOC_CLK_ENABLE();__HAL_RCC_GPIOH_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();/*Configure GPIO pin Output Level */HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);/*Configure GPIO pin Output Level */HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);/*Configure GPIO pin : PC13 */GPIO_InitStruct.Pin = GPIO_PIN_13;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);/*Configure GPIO pin : PA8 */GPIO_InitStruct.Pin = GPIO_PIN_8;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);}/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{if(huart==&huart1){cmd = uart1_rxd[0];HAL_UART_Receive_IT(&huart1, uart1_rxd, 1);}
}
/* USER CODE END 4 *//*** @brief  Period elapsed callback in non blocking mode* @note   This function is called  when TIM1 interrupt took place, inside* HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment* a global variable "uwTick" used as application time base.* @param  htim : TIM handle* @retval None*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{/* USER CODE BEGIN Callback 0 *//* USER CODE END Callback 0 */if (htim->Instance == TIM1) {HAL_IncTick();}/* USER CODE BEGIN Callback 1 *//* USER CODE END Callback 1 */
}/*** @brief  This function is executed in case of error occurrence.* @retval None*/
void Error_Handler(void)
{/* USER CODE BEGIN Error_Handler_Debug *//* User can add his own implementation to report the HAL error return state */__disable_irq();while (1){}/* USER CODE END Error_Handler_Debug */
}#ifdef  USE_FULL_ASSERT
/*** @brief  Reports the name of the source file and the source line number*         where the assert_param error has occurred.* @param  file: pointer to the source file name* @param  line: assert_param error line source number* @retval None*/
void assert_failed(uint8_t *file, uint32_t line)
{/* USER CODE BEGIN 6 *//* User can add his own implementation to report the file name and line number,ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) *//* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

注意其中的f_mkfs格式化U盘库函数在不同的库版本参数数量不一样,上面范例是参数较多的版本,根据实际库函数调整即可。

STM32例程测试

串口指令0x01测试效果如下:
在这里插入图片描述

串口指令0x02测试效果如下:
在这里插入图片描述

串口指令0x03测试效果如下:
在这里插入图片描述

串口指令0x04测试效果如下:
在这里插入图片描述

串口指令0x05测试效果如下:
在这里插入图片描述

注意事项

STM32从识别U盘连接到可操作状态准备好时间比较长,实测约90秒
STM32做U盘容量识别时间也比较长,实测4GB U盘容量识别时间达到3~4分钟。

STM32例程下载

STM32F401CCU6 USB接口FATS文件读写U盘例程下载

–End–

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/697679.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SSL/TLS协议信息泄露漏洞(CVE-2016-2183)解法

1.运行gpedit.msc&#xff0c;进入本地组策略编辑器。 2. 本地组策略编辑器-->计算机配置-->管理模板-->网络-->SSL配置设置-->启用“SSL密码套件顺序”。 3. 将原有的密码套件值清空&#xff0c;拷入下面的值&#xff0c;保存设置&#xff0c;并重启服务器即…

48-Qt控件详解:Buttons Containers2

一 Group Box:组合框 #include "widget.h"#include<QGroupBox> #include<QRadioButton> #include<QPushButton> #include<QVBoxLayout>//可以在水平方向和垂直方向进行排列的控件&#xff0c;QHBoxLayout/QVBoxLayout #include <QGridLa…

Adobe Premiere Pro v24.3.0 解锁版 (领先的视频编辑软件)

Adobe系列软件安装目录 一、Adobe Photoshop PS 25.6.0 解锁版 (最流行的图像设计软件) 二、Adobe Media Encoder ME v24.3.0 解锁版 (视频和音频编码渲染工具) 三、Adobe Premiere Pro v24.3.0 解锁版 (领先的视频编辑软件) 四、Adobe After Effects AE v24.3.0 解锁版 (视…

读人工智能时代与人类未来笔记02_技术变革

1. 目标 1.1. AlphaZero的目标是在遵守规则的前提下赢得国际象棋比赛 1.2. 发现Halicin的人工智能的目标是灭杀尽可能多的致病菌&#xff1a;它在不伤害宿主的情况下灭杀的致病菌越多&#xff0c;就越成功 1.2.1. 人工智能成功了&#xff0…

添砖Java之路(其四)——面向对象的编程,类和对象

目录 前言&#xff1a; 面向对象的编程&#xff1a; this关键字&#xff1a; 构造方法&#xff1a; 前言&#xff1a; 其实中间我还有很多地方没有去讲&#xff0c;因为我觉得里面的很多东西和c/c差不太多&#xff0c;就比如逻辑运算&#xff0c;方法重载&#xff0c;以及数…

解决SpringBoot整合MyBatis和MyBatis-Plus,请求后不打印sql日志

问题发现 在整合springBootmyBatis时&#xff0c;发现请求不打印sql日志&#xff0c;示例代码如下&#xff1a; RestController public class MyController {AutowiredProductMapper productMapper;GetMapping("/test")public void test() {System.out.println(&qu…

原有系统是Windows7,想另外安装一个Windows10作为双系统

前言 上次小白分享了Windows10Windows10的双系统方案&#xff0c;在这个安装的过程中也是写得比较明白&#xff0c;如果需要安装双Windows10系统的小伙伴可以点击下方蓝字跳转&#xff1a; 双Windows10系统安装教程&#xff08;点我跳转&#xff09; 今天小白来分享一下&…

【Linux】多线程相关第一篇:从进程谈起理解线程概念

文章目录 为什么需要线程初步认识Linux线程Linux操作系统的线程为什么要这么设计进程、线程关系梳理理解线程是CPU调度的基本单位简单认识多执行流如何划分代码 为什么需要线程 线程和进程的关系密不可分。 操作系统教材对于进程、线程的概念是这样描述的&#xff1a; 进程是…

linux不小心将/etc/passwd用户文件清空或删除解决方法

大概思路&#xff1a;进入单用户模式将passwd-引子程序复制为删除的passwd用户文件&#xff0c;关闭selinux 此系统为&#xff1a;centos 7 1.在GRUB引导的时候按e进入编辑模式&#xff0c;linux16那一行的ro 修改为rw rd.break ‘ ’ 2.ctrlx执行 3.进入单用户模式后修改根…

Apache访问控制与虚拟主机

目录 一. Web服务简介 以下是一些 Web 服务的基本概念和特征 以下是一些主流的 Web 服务器 WEB 服务协议 二. Apache 服务的搭建与配置 2.1 Apache 介绍 2.2 Apache安装 2.3 Apache目录介绍 三. 访问控制 四. 修改默认网站发布目录 五. 虚拟主机 5.1 基于域名的虚拟…

必知必会:Java Map接口的灵活应用

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。运营社区&#xff1a;C站/掘金/腾讯云&#xff1b;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一…

韵搜坊(全栈开发)-- 项目介绍

文章目录 项目介绍技术栈前端后端 业务流程 后端地址&#xff1a; https://github.com/IMZHEYA/zhesou-backend 前端地址&#xff1a; https://github.com/IMZHEYA/zhesou-frontend 图标设计&#xff08;AI生成&#xff09;&#xff1a; 项目介绍 一个聚合搜素平台&#xff…