stm32入门学习12-软件和硬件SPI操作W25Q64

news2024/9/22 11:29:18

(一)SPI协议

SPI和I2C同样是一种通信协议,SPI相对I2C的优势是更快的传输速度,其和I2C一样为同步传输,即拥有一根时钟线,但是SPI拥有两根数据线,一根用于主机发送,一根用于主机接收,每个使用SPI通信的从机还拥有一根寻址线连接主机,主机直接在此线拉低某个从机低电平以实现呼唤从机

通信协议时钟线数据线全/半双工
I2CSCLSDA半双工
SPISCKMISO、MOSI全双工

SPI有四种工作模式,我们可以配置默认电平和在第一个还是第二个触发沿进行采样,我们常用的是模式0,即时钟空闲时为低电平,在上升沿时进行数据采样,和I2C的时钟逻辑一样

  • 模式0:CPOL=0,CPHA=0,SCK空闲为低电平,数据在SCK的上升沿被采样。
  • 模式1:CPOL=0,CPHA=1,SCK空闲为低电平,数据在SCK的下降沿被采样。
  • 模式2:CPOL=1,CPHA=0,SCK空闲为高电平,数据在SCK的下降沿被采样。
  • 模式3:CPOL=1,CPHA=1,SCK空闲为高电平,数据在SCK的上升沿被采样。

SPI的发送数据和读取数据是同时进行的,在对指定位置进行写操作时,该位置原本的数据会被读出,即使我们不需要这个数据,和I2C一样,SPI也是高位先行,主机数据的高位通过MOSI进入到从机低位,同时从机高位通过MISO进入主机低位,如图

(二)软件SPI

我们可以通过软件模拟SPI通信,和之前所说,我们需要呼叫某个从设备时,就将其寻址先(SS)拉低为低电平,同时我们选择SPI的模式0,即SCK默认为低电平,上升沿开始采样,这里我们把寻址线(SS)接在PA4口,将时钟线接在PA5口,主机输入(MISO)和主机输出(MOSI)线分别接在PA6和PA7口

#define ss GPIO_Pin_4
#define sck GPIO_Pin_5
#define miso GPIO_Pin_6
#define mosi GPIO_Pin_7

由于我们在软件模拟时需要频繁跳转和读取端口的电平,因此我们把跳转电平函数和读取端口函数进行封装,分别为设置端口高电平,设置端口低电平和读取MISO端口的电平

void spi_set(uint16_t io)
{
	GPIO_SetBits(GPIOA, io);
}

void spi_reset(uint16_t io)
{
	GPIO_ResetBits(GPIOA, io);
}

char spi_read()
{
	return GPIO_ReadInputDataBit(GPIOA, miso);
}

(1)SPI初始化

我们要把对应的连接端口进行初始化,其中时钟线sck和主机发送线MOSI我们设置为推挽输出,这样可以更快地翻转电平,寻址线也可以设置为推挽输出,主机接收线MISO可以设置为上拉输入

void spi_init()
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef gpio_init;
	gpio_init.GPIO_Mode = GPIO_Mode_Out_PP;
	gpio_init.GPIO_Pin = ss | sck | mosi;
	gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpio_init);
	
	gpio_init.GPIO_Mode = GPIO_Mode_IPU;
	gpio_init.GPIO_Pin = miso;
	gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpio_init);
	
	spi_set(ss);
	spi_reset(sck);
}

最后我们把ss置高电平,即不呼唤从机,sck置低电平,即空闲状态

(2)开始和结束传输

我们要对指定的从机进行呼叫,只需要把其SS线置低电平,如果要停止对从机的传输,只要把SS线置高电平,开始和结束都只需要一句代码

开始传输

void spi_start()
{
	spi_reset(ss);
}

结束传输

void spi_end()
{
	spi_set(ss);
}

(3)交换数据

前面说过,我们选择的是模式0,即在上升沿采样,同时从机会自动地把其高位放置到主机的最低位中,我们循环八次,就可以完成主机和从机的数据交换了

这里我们在时钟sck低电平期间把发送的数据放置在MOSI上,拉高时钟电平发送,与此同时从机会自动把高位数据放置MISO上,我们只需要读取MISO引脚上的电平即可,随后恢复时钟线为默认值,为下一次交换数据做准备,循环八次

unsigned char spi_swap_byte(unsigned char inf)
{
	unsigned char result = 0x00;
	unsigned char i;
	for (i = 0; i < 8; i++)
	{
		if (inf & (0x80>>i))
		{
			spi_set(mosi);
		}
		else
		{
			spi_reset(mosi);
		}
		spi_set(sck);
		if (spi_read())
		{
			result |= (0x80>>i);
		}
		spi_reset(sck);
	}
	return result;
}

如果我们只要发送数据,不必理会返回值即可,如果我们要接收数据,我们要先发送一些无用的数据,例如0xFF等;

(4)封装及声明

我们只要声明初始化函数、开始结束传输函数和交换数据函数即可,最后.c 和 .h如下

#include "stm32f10x.h"                  // Device header

#define ss GPIO_Pin_4
#define sck GPIO_Pin_5
#define miso GPIO_Pin_6
#define mosi GPIO_Pin_7

void spi_set(uint16_t io)
{
	GPIO_SetBits(GPIOA, io);
}

void spi_reset(uint16_t io)
{
	GPIO_ResetBits(GPIOA, io);
}

char spi_read()
{
	return GPIO_ReadInputDataBit(GPIOA, miso);
}

void spi_init()
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef gpio_init;
	gpio_init.GPIO_Mode = GPIO_Mode_Out_PP;
	gpio_init.GPIO_Pin = ss | sck | mosi;
	gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpio_init);
	
	gpio_init.GPIO_Mode = GPIO_Mode_IPU;
	gpio_init.GPIO_Pin = miso;
	gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpio_init);
	
	spi_set(ss);
	spi_reset(sck);
}

void spi_start()
{
	spi_reset(ss);
}

void spi_end()
{
	spi_set(ss);
}

unsigned char spi_swap_byte(unsigned char inf)
{
	unsigned char result = 0x00;
	unsigned char i;
	for (i = 0; i < 8; i++)
	{
		if (inf & (0x80>>i))
		{
			spi_set(mosi);
		}
		else
		{
			spi_reset(mosi);
		}
		spi_set(sck);
		if (spi_read())
		{
			result |= (0x80>>i);
		}
		spi_reset(sck);
	}
	return result;
}
#ifndef __SPI_H__
#define __SPI_H__

void spi_init(void);
void spi_start(void);
void spi_end(void);
unsigned char spi_swap_byte(unsigned char inf);

#endif

(三)硬件SPI

stm32中集成有SPI硬件电路,如果我们要用硬件SPI,则要把对应的端口接入对应的引脚,这里使用的是SPI1,对应引脚和之前软件引脚一样

打开SPI的标准函数库,可以了解一些硬件SPI常用的函数

void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
//通过初始化结构体初始化SPI

void SPI_StructInit(SPI_InitTypeDef* SPI_InitStruct);
//给SPI结构体配置默认值

void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
//使能SPI,初始化完成后必须打开SPI才开始工作

void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
//发送数据

uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);
//接收数据

void SPI_DataSizeConfig(SPI_TypeDef* SPIx, uint16_t SPI_DataSize);
//选择数据大小,8字节或16字节,初始化中有配置,这里没用到

FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
//获取标志位信息,我们要获取发送寄存器空以写入数据,获取接收寄存器非空以读取数据

void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
//清除标志位,这里发送寄存器和接收寄存器的标志位都是硬件清除,其他标志位可能要使用软件手动清除

ITStatus SPI_I2S_GetITStatus(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
//获取中断标志位

void SPI_I2S_ClearITPendingBit(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
//清除中断标志位

有了这些函数,我们就可以使用硬件SPI了

(1)SPI初始化

(1)打开时钟

我们要打开SPI时钟和对应GPIO时钟,这里SPI1是APB2的外设,应该调用APB2的时钟函数

void spi_rcc_init()
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
}

(2)初始化GPIO

我们的寻址先SS仍然采用软件操控,而时钟线和主机发送、接收数据线则交给硬件控制,与软件模拟SPI不同的是时钟线和主机发送线采用复用推挽模式,其余不变,主机接收线采用上拉输入模式,SS仍采用推挽输出模式

void spi_gpio_init()
{
	GPIO_InitTypeDef gpio_init;
	gpio_init.GPIO_Pin = GPIO_Pin_4;
	gpio_init.GPIO_Mode = GPIO_Mode_Out_PP;
	gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpio_init);
	
	gpio_init.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
	gpio_init.GPIO_Mode = GPIO_Mode_AF_PP;
	gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpio_init);
	
	gpio_init.GPIO_Pin = GPIO_Pin_6;
	gpio_init.GPIO_Mode = GPIO_Mode_IPU;
	gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpio_init);
	
	GPIO_SetBits(GPIOA, GPIO_Pin_4);    //ss->1
}

(3)初始化SPI

先看一下SPI的初始化结构体中的变量

第一个参数是选择数据传输模式,可以是双线双向全双工,也可以是双线单向接收/发送等,这里选择双线双向全双工;

第二个参数是选择stm32作为spi主从模式,这里选择主模式;

第三个参数选择的是数据的大小,有8位和16位,这里选择8位;

第四个参数是选择时钟的默认电平,我们使用的是模式0,默认电平为低电平;

第五个参数选择的是第一个触发沿采样数据还是第二个触发沿采样数据,这里选择第一个触发沿采样,也就是上升沿采样;

第六个参数是选择SS使用硬件控制还是软件控制,这里使用软件控制;

第七个参数是预分频,时钟是72MHz(SPI1),我们要进行分频,最小2分频,最大256分频,分频系数越小,传输速度越快;

第八个参数是选择发送模式,可以选择高位先行或低位先行,我们选择高位先行;

第九个参数用于CRC值的计算,给默认值7;

这样我们就可以初始化SPI了

void spi_spi_init()
{
	SPI_InitTypeDef spi_init;
	spi_init.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    spi_init.SPI_Mode = SPI_Mode_Master;
    spi_init.SPI_DataSize = SPI_DataSize_8b;
    spi_init.SPI_CPOL = SPI_CPOL_Low;
    spi_init.SPI_CPHA = SPI_CPHA_1Edge;
    spi_init.SPI_NSS = SPI_NSS_Soft;
    spi_init.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;
    spi_init.SPI_FirstBit = SPI_FirstBit_MSB;	
    spi_init.SPI_CRCPolynomial = 7;
	SPI_Init(SPI1, &spi_init);
}

(4)初始化封装

我们把所有的初始化函数封装为一个函数供外部调用,最后再使能一下SPI

void spi_init()
{
	spi_rcc_init();
	spi_gpio_init();
	spi_spi_init();
	
	SPI_Cmd(SPI1, ENABLE);
}

(2)开始和结束传输

和之前的相同,开始和结束传输就是把SS置0和置1

void spi_start()
{
	GPIO_ResetBits(GPIOA, GPIO_Pin_4);	//ss->0
}

void spi_end()
{
	GPIO_SetBits(GPIOA, GPIO_Pin_4);	//ss->1
}

(3)交换数据

在每次发送数据时,我们要判断发送寄存器是否为空,接着我们发送数据,发送时从机的数据同步发送到接收寄存器,因此我们判断接收寄存器是否为非空,接收数据

unsigned char spi_swap_inf(unsigned char inf)
{
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);
	SPI_I2S_SendData(SPI1, inf);
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);
	return SPI_I2S_ReceiveData(SPI1);
}

这里发送函数已经自动帮助我们在软件模拟时候执行的操作,且发送寄存器标志位在写入时会自动清零,接收寄存器标志位在读取数据时会自动清零,因此我们只要四句代码即可完成数据交换;

(5)封装与声明

和软件模拟的一样,把初始化、开始和结束、交换数据函数声明为外部可调用,最终.c 和 .h文件如下

#include "stm32f10x.h"                  // Device header


void spi_rcc_init()
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
}

void spi_gpio_init()
{
	GPIO_InitTypeDef gpio_init;
	gpio_init.GPIO_Pin = GPIO_Pin_4;
	gpio_init.GPIO_Mode = GPIO_Mode_Out_PP;
	gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpio_init);
	
	gpio_init.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
	gpio_init.GPIO_Mode = GPIO_Mode_AF_PP;
	gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpio_init);
	
	gpio_init.GPIO_Pin = GPIO_Pin_6;
	gpio_init.GPIO_Mode = GPIO_Mode_IPU;
	gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpio_init);
	
	GPIO_SetBits(GPIOA, GPIO_Pin_4);
}

void spi_spi_init()
{
	SPI_InitTypeDef spi_init;
	spi_init.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    spi_init.SPI_Mode = SPI_Mode_Master;
    spi_init.SPI_DataSize = SPI_DataSize_8b;
    spi_init.SPI_CPOL = SPI_CPOL_Low;
    spi_init.SPI_CPHA = SPI_CPHA_1Edge;
    spi_init.SPI_NSS = SPI_NSS_Soft;
    spi_init.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;
    spi_init.SPI_FirstBit = SPI_FirstBit_MSB;	
    spi_init.SPI_CRCPolynomial = 7;
	SPI_Init(SPI1, &spi_init);
}

void spi_init()
{
	spi_rcc_init();
	spi_gpio_init();
	spi_spi_init();
	
	SPI_Cmd(SPI1, ENABLE);
}

void spi_start()
{
	GPIO_ResetBits(GPIOA, GPIO_Pin_4);	//ss->0
}

void spi_end()
{
	GPIO_SetBits(GPIOA, GPIO_Pin_4);	//ss->1
}

unsigned char spi_swap_inf(unsigned char inf)
{
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);
	SPI_I2S_SendData(SPI1, inf);
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);
	return SPI_I2S_ReceiveData(SPI1);
}
#ifndef __SPI_H__
#define __SPI_H__

void spi_init(void);
void spi_start(void);
void spi_end(void);
unsigned char spi_swap_inf(unsigned char inf);

#endif

(三)SPI操作W25Q64

W25Q64是一个64Mbits的掉电不丢失的非易失存储器,其写入的数据可以实现掉电不丢失,其使用SPI进行通信,支持SPI的模式0和模式3;

和陀螺仪一样,W25Q64也有自己的通信协议,由于其内部存储空间大,所以其地址是24位的,其通信协议包括这些

(1)开始时,其要先发送特定的指令码以明确下面执行的操作,再发送地址码操作指定区域;

(2)写入操作(包括擦除)前必须写使能;

(3)写入数据之前必须擦除,擦除后所有数据位变为1;

(4)写入和擦除数据需要一定时间,芯片进入忙状态,此时不能写入或读出数据;

W25Q64的指令码如下:

第一列是指令内容,第二列是16进制的指令码,后面为发送接收内容,如果后面没有数据则表示可以停止,没有括号表示应该输入数据,右括号表示该接收数据,通过这些指令码,我们就可以编写操作W25Q64的函数了;

(1)读取存储器状态

我们在操作存储器之前都要读取存储器状态,判断其是否在忙,通过指令码可知,我们只要发送读取存储器状态指令0x05,后面即可连续接受存储器状态

void wq_can_use()
{
	spi_start();
	spi_swap_inf(0x05);
	while ((spi_swap_inf(0xFF) & 0x01) != 0);	//if busy?
	spi_end();
}

(2)写入一个数组

由于W25Q64写入操作可以自动地址增加,我们可以写入一个数组的数据,根据之前所说,执行操作前我们要判断存储器状态,即调用上一个函数,下一个时序发送写使能指令,这样才可以修改存储器,然后我们就可以发送写指令和对应要写的地址,最后可以不断写入数据,这里的写入并没有翻页功能,超过了一页的数据会覆盖页开始的数据(W25Q64的内存结构被细致地划分为块、扇区和页。整个8M字节的存储空间被切割成128个块,每个块的大小为64K字节。每个块进一步被分为16个扇区,每个扇区包含4K字节。每个扇区又被细分为16个页,每页便是256字节)

void wq_write(int address, unsigned char* inf, unsigned char length)
{
	unsigned char i;
	wq_can_use();
	
	spi_start();
	spi_swap_inf(0x06);	// write enable
	spi_end();
	
	spi_start();
	spi_swap_inf(0x02);	// write
	spi_swap_inf(address>>16);
	spi_swap_inf(address>>8);
	spi_swap_inf(address);
	
	for (i = 0; i < length; i++)
	{
		spi_swap_inf(inf[i]);
	}
	spi_end();
}

这里传递地址需要3此交换数据,我们把一个32位的数据右移16位,低八位为16~25,转化为8位数据会自动舍弃高位,同理右移8位后第八位为8~15,没有移动时为0~7,这里注意传输模式是高位先行,即先传输高位;

(3)读取操作

和写入操作相似,读取数据之前也要判断芯片是否在忙,但是不用发送写使能指令,我们直接发送读指令,即可连续不断往后读取数据,其地址也会自动增加,其会自动翻页

void wq_read(int address, unsigned char* inf, unsigned int length)
{
	unsigned int i;
	wq_can_use();
	
	spi_start();
	spi_swap_inf(0x03);	// read
	
	spi_swap_inf(address>>16);
	spi_swap_inf(address>>8);
	spi_swap_inf(address);
	
	for (i = 0; i < length; i++)
	{
		inf[i] = spi_swap_inf(0xFF);
	}
	spi_end();
}

我们把读取的数据存在一个数组中,由于读取会自动翻页,因此读取的数量可能比写入数量大得多,可以用一个int类型的长度来接收长度变量;

(4)擦除数据

擦除数据没有指定地址擦除,其只有按块擦除、按扇区擦除和按页擦除,擦除范围从大到小,这里写的是按页擦除,我们可以给写入数据的地址,写入数据地址所属的整页数据都会被清除

和写入相似,擦除前也要判断芯片是否在忙,然后下一个时序发送写使能,然后发送擦除指令和指定地址即可

void wq_erase_4kb(int address)
{
	wq_can_use();
		
	spi_start();
	spi_swap_inf(0x06);	// write enable
	spi_end();
	
	spi_start();
	spi_swap_inf(0x20);	// erase 4kb
	
	spi_swap_inf(address>>16);
	spi_swap_inf(address>>8);
	spi_swap_inf(address);
	
	spi_end();
}

(5)封装和声明

最后我们把SPI的初始化也放在这个文件中,这样头文件只要引用一个文件即可,最后的.c 和 .h文件如下

#include "stm32f10x.h"                  // Device header
#include "spi.h"

void wq_init()
{
	spi_init();
}

void wq_can_use()
{
	spi_start();
	spi_swap_inf(0x05);
	while ((spi_swap_inf(0xFF) & 0x01) != 0);	//if busy?
	spi_end();
}

void wq_write(int address, unsigned char* inf, unsigned char length)
{
	unsigned char i;
	wq_can_use();
	
	spi_start();
	spi_swap_inf(0x06);	// write enable
	spi_end();
	
	spi_start();
	spi_swap_inf(0x02);	// write
	spi_swap_inf(address>>16);
	spi_swap_inf(address>>8);
	spi_swap_inf(address);
	
	for (i = 0; i < length; i++)
	{
		spi_swap_inf(inf[i]);
	}
	spi_end();
}

void wq_read(int address, unsigned char* inf, unsigned int length)
{
	unsigned int i;
	wq_can_use();
	
	spi_start();
	spi_swap_inf(0x03);	// read
	
	spi_swap_inf(address>>16);
	spi_swap_inf(address>>8);
	spi_swap_inf(address);
	
	for (i = 0; i < length; i++)
	{
		inf[i] = spi_swap_inf(0xFF);
	}
	spi_end();
}

void wq_erase_4kb(int address)
{
	wq_can_use();
		
	spi_start();
	spi_swap_inf(0x06);	// write enable
	spi_end();
	
	spi_start();
	spi_swap_inf(0x20);	// erase 4kb
	
	spi_swap_inf(address>>16);
	spi_swap_inf(address>>8);
	spi_swap_inf(address);
	
	spi_end();
}
#ifndef __W25Q64_H__
#define __W25Q64_H__

void wq_init(void);
void wq_write(int address, unsigned char* inf, unsigned char length);
void wq_read(int address, unsigned char* inf, unsigned char length);
void wq_erase_4kb(int address);

#endif

(四)主函数调用

我们可以先在存储器上写入一些数据再读出来认证我们的函数没有问题,注意写入之前最好先擦除

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "w25q64.h"

int main()
{
	unsigned char write_inf[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
	unsigned char read_inf[5];
	unsigned char i;
	OLED_Init();
	wq_init();
	wq_erase_4kb(0x00000000);
	wq_write(0x00000000, write_inf, 10);
	wq_read(0x00000000, read_inf, 5);
	OLED_ShowString(1, 1, "write:");
	for (i = 0;i < 10; i++)
	{
		OLED_ShowNum(2, i+1, write_inf[i], 1);
	}
	OLED_ShowString(3, 1, "read:");
	for (i = 0; i < 5; i++)
	{
		OLED_ShowNum(4, i+1, read_inf[i], 1);
	}
	while(1);
	return 0;
}

(五)总结

通过SPI来操作一个存储器,我们认识了SPI通信的基本原理和通信方式,学习了存储器W25Q64的通信协议

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

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

相关文章

Kspider:超级给力的图形化爬虫平台

Kspider&#xff1a;超级给力的图形化爬虫平台 在如今数据驱动的世界&#xff0c;Kspider 作为一款功能强大的图形化爬虫平台&#xff0c;为用户提供了全面的网页数据抓取解决方案。本文将介绍 Kspider 的基本信息、特点以及它相对于其他爬虫工具的优势。 软件简介 Kspider 是…

【黑马】MyBatis

目录 MyBatis简介JDBC缺点&#xff1a;MyBatis针对于JDBC进行简化&#xff0c;简化思路&#xff1a; MyBatis快速入门具体构建步骤解决SQL映射文件的警告提示 Mapper代理开发案例&#xff1a;使用Mapper代理方式完成案例具体步骤详解&#xff1a;Mapper代理方式 Mapper核心配置…

《向量数据库指南》——企业采用非结构化数据的场景及其深远影响

引言 在当今数字化转型的浪潮中,企业数据的种类与规模正以前所未有的速度增长,其中非结构化数据作为信息时代的重要组成部分,其价值日益凸显。Lynn提出的关于企业最先采用非结构化数据的观察,引发了我们对这一领域深入探索的兴趣。Charles的见解则为我们揭示了非结构化数据…

JavaWeb-01(Java进阶内容详解,Html、CSS、JS)

一、前端技术结构分析 网页的结构&#xff08;HTML&#xff09;、表现(CSS)、行为(JS) 1.HTML定义界面整体结构 2.CSS定义页面样式 3.JS实现动态效果 二、HTML 2.1安装VS Code及前端开发插件 Chinese (Simplified) (简体中文) Language Pack for Visual Studio Code Code Spe…

高职院校云计算人才培养成果导向系统构建、实施要点与评量方法

一、引言 随着“十四五”规划的深入实施&#xff0c;云计算作为新一代信息技术的关键组成部分&#xff0c;已成为推动各行业数字化转型的重要驱动力。高职院校作为技术技能人才培养的重要阵地&#xff0c;如何根据云计算产业的发展需求&#xff0c;培养具备云计算技术应用与运…

展馆室内导航系统:增强现实技术与数据可视化分析在展馆中的应用

随着科技的飞速发展&#xff0c;展览行业正经历着前所未有的变革。作为信息交流与文化传播的重要场所&#xff0c;展馆在吸引访客、展示展品方面扮演着至关重要的角色。然而&#xff0c;在信息爆炸、时间宝贵以及访客需求日益多样化的今天&#xff0c;传统展馆在导览、管理和服…

PI案例分享--基于DDR4 PHY的VDDQ封装电源完整性分析

随着核心电源网络的电压裕度持续降低&#xff0c;端到端电源完整性建模变得愈发困难&#xff0c;究其原因&#xff0c;是作为系统设计者&#xff0c;我们通常无法得知供应商提供的芯片die模型&#xff08;die model&#xff09;的准确性。 通过一个案例对该问题进行研究&#x…

小智纯前端js报表实战4-绝对坐标纵向扩展

绝对坐标-纵向扩展 概述 绝对层次坐标 用于获取扩展后某一位置上的值。如获取A1扩展后的A3单元格的值&#xff0c;就可以在别的单元格如B1中输入A1[A1:3]&#xff0c;其公式意义在于获取A1扩展后的第三个单元格的值&#xff0c;其效果如下 绝对坐标-纵向扩展&#xff1a;绝…

JavaEE-多线程编程单例模式

一、等待通知 系统内部&#xff0c;线程之间是抢占式执行的&#xff0c;随即调度&#xff0c;程序可以通过手动干预的方式&#xff0c;能够让线程一定程度的按咱们想要的顺序执行&#xff0c;无法主动让某个线程被调度&#xff0c;但可以主动让某个线程等待。等待通知可以安排…

嵌入式人工智能(45-基于树莓派4B的扩展板-舵机驱动板PCA9685)

1、简介 智能小车、机械臂、摄像头云台会有多个舵机&#xff0c;而微控制器芯片的PWM输出引脚不够的情况下&#xff0c;就可以用PCA9685&#xff08;16路舵机&#xff09;来解决这一问题。 PCA9685是一款I2C总线控制的16通道LED控制器&#xff0c;专为红/绿/蓝/琥珀&#xff…

Spring Boot - 在Spring Boot中实现灵活的API版本控制(下)_ 封装场景启动器Starter

文章目录 Pre设计思路ApiVersion 功能特性使用示例配置示例 ProjectStarter Code自定义注解 ApiVersion配置属性类用于管理API版本自动配置基于Spring MVC的API版本控制实现WebMvcRegistrations接口&#xff0c;用于自定义WebMvc的注册逻辑扩展RequestMappingHandlerMapping的类…

医院预约挂号小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;医生管理&#xff0c;科室分类管理&#xff0c;医生信息管理&#xff0c;预约挂号管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;医生信息&#…

Python酷库之旅-第三方库Pandas(074)

目录 一、用法精讲 301、pandas.Series.dt.components属性 301-1、语法 301-2、参数 301-3、功能 301-4、返回值 301-5、说明 301-6、用法 301-6-1、数据准备 301-6-2、代码示例 301-6-3、结果输出 302、pandas.Series.dt.to_pytimedelta方法 302-1、语法 302-2、…

17.1 分布式限流组件Sentinel

17.1 分布式限流组件Sentinel 1. Sentinel介绍1.1 Sentinel 介绍1.2 Sentinel 功能和设计理念流量控制2. Sentinel安装控制台2.1 概述2.2 启动控制台*****************************************************************************1. Sentinel介绍 github 官方中文文档 1.…

Rest风格快速开发

Rest风格开发简介 简单点来说&#xff0c;Rest风格的开发就是让别人不知道你在做什么&#xff0c;以deleteUserById和selectUserById为例&#xff1a; 普通开发&#xff1a;路径 /users/deleteById?Id666 /users/selectById?Id666 别人很容易知道你这是在干什么 Rest风…

半导体行业人士宋仕强谈生产力

近日&#xff0c;半导体行业人士&#xff0c;金航标电子和萨科微创始人宋仕强强调了技术进步与管理创新在提升生产效率中的作用。深圳作为中国效率驱动发展模式的典范&#xff0c;其核心竞争力在于高效利用资源。从早期的快速城市建设到现今华强北电子市场的繁荣&#xff0c;深…

批量ncm转mp3

软件上线一段时间后发现大家用ncm转MP3功能比较多&#xff0c;并且很多用户都是同时转换好几个音乐&#xff0c;为了方便大家使用这里就给大家提供了一个批量ncm转MP3的功能&#xff0c;下面简单介绍一下如何使用 打开智游剪辑&#xff08;zyjj.cc&#xff09;&#xff0c;搜索…

Mouser中元件特性对比功能

搜索所需的元件&#xff0c;并点击比对 在比对界面里搜索所需比对的另外元器件&#xff0c;并比对3.得到的结果

深入探索 Wireshark——网络封包分析的利器

一、引言 在当今数字化的时代&#xff0c;网络通信变得日益复杂和关键。无论是企业的网络运维&#xff0c;还是网络安全研究&#xff0c;都需要深入了解网络中传输的数据。Wireshark 作为一款强大的网络封包分析工具&#xff0c;成为了网络工程师、安全研究人员和技术爱好者不…

linux 查看端口占用并处理

lsof 命令 lsof -i:端口注意pid netstat 命令 netstat -tnpla | grep 端口注意pid 查看详情 ps -ef | grep 3766607删除 kill -9 PIDkill -9 3766607