stm32 SPI通信外设(硬件SPI读写W25Q64)

news2025/1/16 6:33:58

理论

1.SPI外设简介

STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担

可配置8位/16位数据帧、高位先行/低位先行

时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)

支持多主机模型、主或从操作 可精简为半双工/单工通信

支持DMA

兼容I2S协议

STM32F103C8T6 硬件SPI资源:SPI1、SPI2

2.SPI框图

全双工模式:必须只有在发送数据时,才会触发接受数据,因为在这种模式下只能是发送数据时才会触发时钟线(单纯接受不会触发时钟线),实现全双工模式,发送,接收同时进行

同时可以通过标志位TXE与RXEN判断运行状态

3.SPI基本结构

没有NSS线,它可以通过软件进行控制

 4.主模式传输

(1)主模式全双工连续传输

这个工作模式了解即可,实际上常用非连续传输

 (2)非连续传输

(1)判断TXE标志位

判断是否没有数据,如果发送缓存器(发送数据寄存器TDR)没有数据,则置为SET(1)

(2)发送数据

调用SPI_I2S_SendData发送数据

(3)检测RXNE标志位

因为在TXE重新变回SET(1)后,硬件还在发送数据,无法判断是否发送完一个字节,则需要RXNE进行判断,在移位寄存器移完时,会发送字节到接受缓存区RXNE置SET(1)来进行判断

5. GPIO输出与复用输出

在STM32微控制器中,为了使GPIO口能够由硬件外设(如定时器、USART、SPI、I2C等)控制,GPIO口通常需要配置为复用推挽输出模式。以下是这样做的原因:

1. 硬件外设控制的需求

硬件外设(如定时器或串口)通常需要将GPIO引脚用于特定的功能,比如生成PWM信号、传输数据或产生时钟信号。为了实现这些功能,GPIO引脚必须支持复用功能和特定的输出模式。这是因为:

  • 复用功能:STM32的GPIO引脚通常具有复用功能,允许它们被配置为多种不同的外设功能。要让一个引脚工作在某个硬件外设的模式下,必须将其配置为该外设所需的特定复用功能模式。推挽输出模式是很多外设的要求,因为它能提供稳定的电平和较强的驱动能力。

  • 推挽输出模式:推挽输出模式在数字电路中是一种常见的输出配置,它能够通过两个晶体管(NPN和PNP)来驱动引脚提供强大的电流。对于需要稳定的高电平和低电平的外设信号(如PWM输出或高速通信),推挽输出模式可以提供较强的驱动能力和较快的响应速度。

2. 稳定性和驱动能力

  • 稳定性:推挽输出模式能够提供稳定的高电平和低电平,减少信号的抖动和不稳定性。这对于外设的正常运行至关重要,尤其是当需要精确的时间控制和稳定的信号传输时。

  • 驱动能力:推挽输出模式具有较强的电流驱动能力,适合驱动外设的输入。外设(如LED、继电器、外部模块等)通常需要较强的驱动能力来确保信号的可靠传输。

3. 引脚复用和外设配置

STM32微控制器的引脚可以配置为多种功能,通过复用配置选择具体的外设功能。在这种配置下,引脚可能需要具备推挽输出模式来实现以下目的:

  • 灵活性:允许同一引脚在不同的功能模式下工作,提高了引脚的利用效率和设计灵活性。
  • 简化设计:通过复用功能,可以减少外部电路的复杂性和数量,实现更紧凑的设计。

4. 示例

例如,STM32的定时器(TIM)可以配置为产生PWM信号,这时相应的GPIO引脚需要配置为推挽输出模式,以确保PWM信号的稳定性和准确性。同样,USART(串行通信)外设也需要将GPIO引脚配置为推挽输出模式,以保证数据传输的稳定和高效。

总结

为了使STM32的GPIO口能够由硬件外设控制,并且满足外设对信号稳定性和驱动能力的需求,需要将GPIO口配置为复用推挽输出模式。这种配置不仅满足了外设的功能要求,还提供了引脚的多功能复用能力,使得系统设计更为灵活和高效。

代码

硬件SPI读写W25Q64

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"

uint8_t MID;							//定义用于存放MID号的变量
uint16_t DID;							//定义用于存放DID号的变量

uint8_t ArrayWrite[] = {0x01, 0x02, 0x03, 0x04};	//定义要写入数据的测试数组
uint8_t ArrayRead[4];								//定义要读取数据的测试数组

int main(void)
{
	/*模块初始化*/
	OLED_Init();						//OLED初始化
	W25Q64_Init();						//W25Q64初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "MID:   DID:");
	OLED_ShowString(2, 1, "W:");
	OLED_ShowString(3, 1, "R:");
	
	/*显示ID号*/
	W25Q64_ReadID(&MID, &DID);			//获取W25Q64的ID号
	OLED_ShowHexNum(1, 5, MID, 2);		//显示MID
	OLED_ShowHexNum(1, 12, DID, 4);		//显示DID
	
	/*W25Q64功能函数测试*/
	W25Q64_SectorErase(0x000000);					//扇区擦除
	W25Q64_PageProgram(0x000000, ArrayWrite, 4);	//将写入数据的测试数组写入到W25Q64中
	
	W25Q64_ReadData(0x000000, ArrayRead, 4);		//读取刚写入的测试数据到读取数据的测试数组中
	
	/*显示数据*/
	OLED_ShowHexNum(2, 3, ArrayWrite[0], 2);		//显示写入数据的测试数组
	OLED_ShowHexNum(2, 6, ArrayWrite[1], 2);
	OLED_ShowHexNum(2, 9, ArrayWrite[2], 2);
	OLED_ShowHexNum(2, 12, ArrayWrite[3], 2);
	
	OLED_ShowHexNum(3, 3, ArrayRead[0], 2);			//显示读取数据的测试数组
	OLED_ShowHexNum(3, 6, ArrayRead[1], 2);
	OLED_ShowHexNum(3, 9, ArrayRead[2], 2);
	OLED_ShowHexNum(3, 12, ArrayRead[3], 2);
	
	while (1)
	{
		
	}
}

MySPI.h

#ifndef __MYSPI_H
#define __MYSPI_H

void MySPI_Init(void);
void MySPI_Start(void);
void MySPI_Stop(void);
uint8_t MySPI_SwapByte(uint8_t ByteSend);

#endif

MySPI.c

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:SPI写SS引脚电平,SS仍由软件模拟
  * 参    数:BitValue 协议层传入的当前需要写入SS的电平,范围0~1
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SS为低电平,当BitValue为1时,需要置SS为高电平
  */
void MySPI_W_SS(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);		//根据BitValue,设置SS引脚的电平
}

/**
  * 函    数:SPI初始化
  * 参    数:无
  * 返 回 值:无
  */
void MySPI_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);	//开启SPI1的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA4引脚初始化为推挽输出
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA5和PA7引脚初始化为复用推挽输出
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA6引脚初始化为上拉输入
	
	/*SPI初始化*/
	SPI_InitTypeDef SPI_InitStructure;						//定义结构体变量
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;			//模式,选择为SPI主模式
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;	//方向,选择2线全双工
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//数据宽度,选择为8位
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;		//先行位,选择高位先行
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;	//波特率分频,选择128分频
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;				//SPI极性,选择低极性
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;			//SPI相位,选择第一个时钟边沿采样,极性和相位决定选择SPI模式0
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;				//NSS,选择由软件控制
	SPI_InitStructure.SPI_CRCPolynomial = 7;				//CRC多项式,暂时用不到,给默认值7
	SPI_Init(SPI1, &SPI_InitStructure);						//将结构体变量交给SPI_Init,配置SPI1
	
	/*SPI使能*/
	SPI_Cmd(SPI1, ENABLE);									//使能SPI1,开始运行
	
	/*设置默认电平*/
	MySPI_W_SS(1);											//SS默认高电平
}

/**
  * 函    数:SPI起始
  * 参    数:无
  * 返 回 值:无
  */
void MySPI_Start(void)
{
	MySPI_W_SS(0);				//拉低SS,开始时序
}

/**
  * 函    数:SPI终止
  * 参    数:无
  * 返 回 值:无
  */
void MySPI_Stop(void)
{
	MySPI_W_SS(1);				//拉高SS,终止时序
}

/**
  * 函    数:SPI交换传输一个字节,使用SPI模式0
  * 参    数:ByteSend 要发送的一个字节
  * 返 回 值:接收的一个字节
  */
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);	//等待发送数据寄存器空
	
	SPI_I2S_SendData(SPI1, ByteSend);								//写入数据到发送数据寄存器,开始产生时序
	
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);	//等待接收数据寄存器非空
	
	return SPI_I2S_ReceiveData(SPI1);								//读取接收到的数据并返回
}

W25Q64.c

#include "stm32f10x.h"                  // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"

/**
  * 函    数:W25Q64初始化
  * 参    数:无
  * 返 回 值:无
  */
void W25Q64_Init(void)
{
	MySPI_Init();					//先初始化底层的SPI
}

/**
  * 函    数:MPU6050读取ID号
  * 参    数:MID 工厂ID,使用输出参数的形式返回
  * 参    数:DID 设备ID,使用输出参数的形式返回
  * 返 回 值:无
  */
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
	MySPI_Start();								//SPI起始
	MySPI_SwapByte(W25Q64_JEDEC_ID);			//交换发送读取ID的指令
	*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);	//交换接收MID,通过输出参数返回
	*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);	//交换接收DID高8位
	*DID <<= 8;									//高8位移到高位
	*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);	//或上交换接收DID的低8位,通过输出参数返回
	MySPI_Stop();								//SPI终止
}

/**
  * 函    数:W25Q64写使能
  * 参    数:无
  * 返 回 值:无
  */
void W25Q64_WriteEnable(void)
{
	MySPI_Start();								//SPI起始
	MySPI_SwapByte(W25Q64_WRITE_ENABLE);		//交换发送写使能的指令
	MySPI_Stop();								//SPI终止
}

/**
  * 函    数:W25Q64等待忙
  * 参    数:无
  * 返 回 值:无
  */
void W25Q64_WaitBusy(void)
{
	uint32_t Timeout;
	MySPI_Start();								//SPI起始
	MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);				//交换发送读状态寄存器1的指令
	Timeout = 100000;							//给定超时计数时间
	while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)	//循环等待忙标志位
	{
		Timeout --;								//等待时,计数值自减
		if (Timeout == 0)						//自减到0后,等待超时
		{
			/*超时的错误处理代码,可以添加到此处*/
			break;								//跳出等待,不等了
		}
	}
	MySPI_Stop();								//SPI终止
}

/**
  * 函    数:W25Q64页编程
  * 参    数:Address 页编程的起始地址,范围:0x000000~0x7FFFFF
  * 参    数:DataArray	用于写入数据的数组
  * 参    数:Count 要写入数据的数量,范围:0~256
  * 返 回 值:无
  * 注意事项:写入的地址范围不能跨页
  */
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
	uint16_t i;
	
	W25Q64_WriteEnable();						//写使能
	
	MySPI_Start();								//SPI起始
	MySPI_SwapByte(W25Q64_PAGE_PROGRAM);		//交换发送页编程的指令
	MySPI_SwapByte(Address >> 16);				//交换发送地址23~16位
	MySPI_SwapByte(Address >> 8);				//交换发送地址15~8位
	MySPI_SwapByte(Address);					//交换发送地址7~0位
	for (i = 0; i < Count; i ++)				//循环Count次
	{
		MySPI_SwapByte(DataArray[i]);			//依次在起始地址后写入数据
	}
	MySPI_Stop();								//SPI终止
	
	W25Q64_WaitBusy();							//等待忙
}

/**
  * 函    数:W25Q64扇区擦除(4KB)
  * 参    数:Address 指定扇区的地址,范围:0x000000~0x7FFFFF
  * 返 回 值:无
  */
void W25Q64_SectorErase(uint32_t Address)
{
	W25Q64_WriteEnable();						//写使能
	
	MySPI_Start();								//SPI起始
	MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);	//交换发送扇区擦除的指令
	MySPI_SwapByte(Address >> 16);				//交换发送地址23~16位
	MySPI_SwapByte(Address >> 8);				//交换发送地址15~8位
	MySPI_SwapByte(Address);					//交换发送地址7~0位
	MySPI_Stop();								//SPI终止
	
	W25Q64_WaitBusy();							//等待忙
}

/**
  * 函    数:W25Q64读取数据
  * 参    数:Address 读取数据的起始地址,范围:0x000000~0x7FFFFF
  * 参    数:DataArray 用于接收读取数据的数组,通过输出参数返回
  * 参    数:Count 要读取数据的数量,范围:0~0x800000
  * 返 回 值:无
  */
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
	uint32_t i;
	MySPI_Start();								//SPI起始
	MySPI_SwapByte(W25Q64_READ_DATA);			//交换发送读取数据的指令
	MySPI_SwapByte(Address >> 16);				//交换发送地址23~16位
	MySPI_SwapByte(Address >> 8);				//交换发送地址15~8位
	MySPI_SwapByte(Address);					//交换发送地址7~0位
	for (i = 0; i < Count; i ++)				//循环Count次
	{
		DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);	//依次在起始地址后读取数据
	}
	MySPI_Stop();								//SPI终止
}

W25Q64.h

#ifndef __W25Q64_H
#define __W25Q64_H

void W25Q64_Init(void);
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID);
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count);
void W25Q64_SectorErase(uint32_t Address);
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count);

#endif

接线图

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++ | Leetcode C++题解之第393题UTF-8编码验证

题目&#xff1a; 题解&#xff1a; class Solution { public:static const int MASK1 1 << 7;static const int MASK2 (1 << 7) (1 << 6);bool isValid(int num) {return (num & MASK2) MASK1;}int getBytes(int num) {if ((num & MASK1) 0) …

WhatsApp修复重大隐私漏洞,‘阅后即焚’功能安全隐患引关注

据BleepingComputer报道&#xff0c;全球拥有20亿用户的即时通讯应用WhatsApp近期修复了一个关键的隐私漏洞。该漏洞允许攻击者多次查看用户发送的“阅后即焚”&#xff08;View once&#xff09;内容。 WhatsApp的“阅后即焚”功能于三年前推出&#xff0c;允许用户发送只能查…

VSCode 渲染 markdown md , 设置插件的背景颜色 Markdown Preview Enhanced

起因&#xff0c; 目的: VSCode 中&#xff0c; 安装 Markdown Preview Enhanced 这个插件之后&#xff0c;能渲染&#xff0c;但是背景颜色太亮了。 最近正在学习 css, 所以一试身手。 先看效果&#xff1a; 过程: Ctrl Shift P 打开命令面板。输入: Markdown Preview…

【踩坑】装了显卡,如何让显示器从主板和显卡HDMI都输出

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 背景介绍 装了显卡后&#xff0c;开机默认是从显卡的HDMI输出&#xff0c;但这很不方便。如何让视频仍然从主板输出&#xff1f;或者说让显卡HDMI和主板…

漏洞披露-信呼-OA

更多网安漏洞复现&#xff0c;可前往无问社区查看http://wwlib.cn/index.php/artread/artid/16564.html 0x01 产品简介 泛微数字化运营管理平台OA为组织提供从“可信数字身份、电子化流程审批、个性化岗位信息门户、 知识文档管理、电子化签署到内外协同的业务管理” 0x02 漏…

QT 基础学习

1> 使用绘制事件完成钟表的绘制 头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPainter> #include <QDebug> #include <QTime> #include <QTimer> #include <QDateTime> //#include <string> #includ…

发送成绩的app或小程序推荐

老师们&#xff0c;新学期的第一次月考马上开始&#xff0c;是不是还在为如何高效、便捷地发布成绩而头疼呢&#xff1f;别担心&#xff0c;都2024年了&#xff0c;我们有更智能的方式来解决这个问题&#xff01; 给大家安利一个超级实用的工具——易查分小程序。这个小程序简…

esp32-C2 对接火山引擎实现智能语音(一)

目录 一、火山引擎大模型简介 1)火山引擎网址: 2)首先需要先注册火山引擎账号 3)语音识别——即语音转为文本 一句话识别 流式语音识别 录音文件识别标准版 录音文件识别极速版 4)语音合成——文本转音频 一、火山引擎大模型简介 火山引擎的智能语音技术,基于业界先…

ORCA-3D避障算法解析

二维ORCA原理参考&#xff1a; https://zhuanlan.zhihu.com/p/669426124 ORCA原理图解 1. 找到避障速度增量 u 碰撞处理分为三种情况&#xff1a; &#xff08;1&#xff09;没有发生碰撞&#xff0c;且相对速度落在小圆里 &#xff08;2&#xff09;没有发生碰撞&#xff0…

C++---string类常见接口

介绍 string类详情>>>https://cplusplus.com/reference/string/string/?kwstring 1. string是表示字符串的字符串类&#xff08;感觉就像一个动态的字符数组&#xff09; 2. 该类的接口与常规容器的接口基本相同&#xff0c;再添加了一些专门用来操作string的常规操作…

9月19日与MongoDB相约2024云栖大会

2024云栖大会来了&#xff01; 9月19日至9月21日 在杭州云栖小镇召开 汇集全球最新云计算和AI硬科技 MongoDB互动展区 2号馆2-8展区 MongoDB技术演讲 9月20日 分论坛&#xff1a;D2-5 2024云栖大会将设置 人工智能、计算、前沿应用三大主题馆&#xff0c; 围绕云计算与AI&#…

面试官:说说停止线程池的执行流程?

对于我们使用的线程池 ThreadPoolExecutor 来说&#xff0c;停止线程池的方法有以下两个&#xff1a; shutdown()&#xff1a;优雅的关闭线程池&#xff0c;即不再接受新任务&#xff0c;但会等待已提交任务&#xff08;包括正在执行的任务和在队列中等待的任务&#xff09;执行…

【C++高阶】解锁C++的深层魅力——探索特殊类的奥秘

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;C “ 登神长阶 ” &#x1f921;往期回顾&#x1f921;&#xff1a;C 类型转换 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀C特殊类 &#x1f4d2;1. 不能被拷贝…

外贸网站建设该怎么做

外贸网站已成为企业拓展国际市场、促进贸易合作的重要工具。然而&#xff0c;要想在竞争激烈的国际贸易舞台上脱颖而出&#xff0c;就需要一套科学有效的外贸网站建设方案。本文将为您介绍成功打造外贸网站的关键步骤&#xff0c;助您在数字化时代实现更广阔的国际商机。 第一步…

嗨! 大叔,什么时候退休?

先祝各位中秋快乐&#xff01;&#xff01;&#xff01;接下我们吃瓜。娱乐至上&#xff01; 纯属娱乐&#xff0c;没验证是否正确&#xff01; 2024年9月13日第十四届全国人民代表大会常务委员会第十一次会议通过关于实施渐进式延迟法定退休年龄的决定,我将规则给ChatGPT&am…

如何绕过Cloudflare的403 禁止错误?

Cloudflare 的 403 错误与常规 HTTP 403 错误代码并无二致&#xff0c;都表示禁止访问。这通常意味着你没有权限访问该文档。然而&#xff0c;在使用 Cloudflare 的情况下&#xff0c;当你尝试网页抓取时&#xff0c;可能会遇到这种情况&#xff0c;因为它可能表明你的 IP 地址…

【练习10】链表相加

链接&#xff1a;链表相加(二)_牛客题霸_牛客网 (nowcoder.com) 分析&#xff1a; 算法原理是逆序高精度算法 逆序的原因是为了实现从低位&#xff08;个位&#xff09;开始相加。 public class Solution {//逆序链表public ListNode reverse(ListNode head){ListNode newHead …

尤雨溪推荐的拖拽插件,支持Vue2/Vue3 VueDraggablePlus

大家好,我是「前端实验室」爱分享的了不起~ 今天在网上看到尤雨溪推荐的这款拖拽组件,试了一下非常不错,这里推荐给大家。 说到拖拽工具库,非大名鼎鼎的的 Sortablejs 莫属。它是前端领域比较知名的,且功能强大的工具。但我们直接使用Sortablejs的情况很少,一般都是使用…

java项目之基于Spring Boot智能无人仓库管理源码(springboot+vue)

项目简介 智能无人仓库管理实现了以下功能&#xff1a; 基于Spring Boot智能无人仓库管理的主要使用者分为&#xff1a; 管理员的功能有&#xff1a;员工信息的查询管理&#xff0c;可以删除员工信息、修改员工信息、新增员工信息 &#x1f495;&#x1f495;作者&#xff1a…