【STM32学习】——续上:软件SPI读写W25Q64SPI通信外设硬件SPI读写W25Q64

news2024/11/16 2:15:25

 四、软件SPI读写W25Q64

工程思路与I2C类似,MySPI.c是通信底层,主要包括通信引脚封装、初始化、SPI通信的三个拼图(起始、终止和交换一个字节);基于此文件建立W25Q64.c,调用MySPI三个拼图,拼接成各种指令和功能的完整时序,如写使能、擦除、页编程、读数据等,可看作W25Q64芯片的硬件驱动层。在主函数调用驱动层函数:

//MySPI.c
#include "stm32f10x.h"  

void MySPI_W_SS(uint8_t BitValue){//SS或CS,片选或从机选择。
	GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);	
}
void MySPI_W_SCK(uint8_t BitValue){
	GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)BitValue);	
}
void MySPI_W_MOSI(uint8_t BitValue){
	GPIO_WriteBit(GPIOA,GPIO_Pin_7,(BitAction)BitValue);	
}
uint8_t MySPI_R_MISO(void){
	return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6);
}
//上面这样的函数,也可用有参宏定义实现


void MySPI_Init(void){
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;//推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4| GPIO_Pin_5 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	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);
	
	MySPI_W_SS(1);//初始化后默认电平
	MySPI_W_SCK(0);//初始化后默认电平
}

void MySPI_Start(void){//起始
	MySPI_W_SS(0);
}
void MySPI_Stop(void){//终止
	MySPI_W_SS(1);
}
uint8_t MySPI_SwapByte(uint8_t ByteSend){
	uint8_t i,ByteReceive = 0x00;
	for(i=0;i<8;i++){
		MySPI_W_MOSI(ByteSend & (0x80)>> i);//放到线上。与从机同步的
		MySPI_W_SCK(1);//线上的数据被分别读走(交换)
		if(MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}//主机接收到的数据位存到ByteReceive
			MySPI_W_SCK(0);
	}
	return ByteReceive;
}
//0x80 >> i的作用是用来挑出数据的某一位或者某几位(或屏蔽其他的无关位,是掩码操作思想)。好处是没改变传入参数ByteSend
//另一种更简洁的方法,但会改变ByteSend变量:
//uint8_t MySPI_SwapByte(uint8_t ByteSend){
//	uint8_t i;
//	for(i=0;i<8;i++){
//		MySPI_W_MOSI(ByteSend & 0x80);
//		ByteSend <<= 1;
//		MySPI_W_SCK(1);
//		if(MySPI_R_MISO() == 1){ByteSend |= 0x01;}
//			MySPI_W_SCK(0);
//	}
//	return ByteSend;//传入的参数变了,当作返回值返回
//}
//W25Q64.c
#include "stm32f10x.h"   
#include "MySPI.h"
#include "W25Q64_Ins.h"

void W25Q64_Init(void){
	MySPI_Init();
}

void W25Q64_ReadID(uint8_t* MID,uint16_t* DID){
	MySPI_Start();
	MySPI_SwapByte(W25Q64_JEDEC_ID);
	*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE );
	*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	*DID <<= 8;
	*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	MySPI_Stop();
}

void W25Q64_WriteEnable(void){//写使能
	MySPI_Start();
	MySPI_SwapByte(W25Q64_WRITE_ENABLE);
	MySPI_Stop();
}

void W25Q64_WaitBusy(void){//等待忙
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
	while((MySPI_SwapByte(W25Q64_DUMMY_BYTE)&0x01) == 0x01);//可设置等待时间,超时退出
	MySPI_Stop();
}

void W25Q64_PageProgram(uint32_t Address,uint8_t* DataArray,uint16_t Count){//指定起始地址,给个数据数组和元素数量,就可帮我们发送数据(写)
	W25Q64_WriteEnable();//写使能(注意事项)
	uint16_t i;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >>8);
	MySPI_SwapByte(Address);
	for(i=0;i<Count;i++){
		MySPI_SwapByte(DataArray[i]);
	}
	MySPI_Stop();
	W25Q64_WaitBusy();//等待Busy(注意事项)
}	

void W25Q64_SectorErase(uint32_t Address){//擦除指定地址所在扇区(写)
	W25Q64_WriteEnable();//写使能(注意事项)
	MySPI_Start();
	MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >>8);
	MySPI_SwapByte(Address);
	MySPI_Stop();
	W25Q64_WaitBusy();//等待Busy(注意事项)
}

void W25Q64_ReadData(uint32_t Address,uint8_t* DataArray,uint32_t Count){//指定起始地址,给个接收数组和元素数量,就可帮我们读取数据(读)
	uint32_t i;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_DATA);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >>8);
	MySPI_SwapByte(Address);
	for(i=0;i<Count;i++){
		DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	}
	MySPI_Stop();
}
//main.c
#include "stm32f10x.h"   // Device header
#include "Delay.h"   
#include "OLED.h"
#include "W25Q64.h"

uint8_t MID;
uint16_t DID;

uint8_t ArrayWrite[] = {0x01,0x02,0x03,0x04};
uint8_t ArrayRead[4];

int main(void){
	OLED_Init();
	W25Q64_Init();
	
	OLED_ShowString(1,1,"MID:    DID:");
	OLED_ShowString(2,1,"W:");
	OLED_ShowString(3,1,"R:");
	
	W25Q64_ReadID(&MID,&DID);
	OLED_ShowHexNum(1,5,MID,2);
	OLED_ShowHexNum(1,12,DID,4);
	
	W25Q64_SectorErase(0x000000);//后三位随便变,都是擦除该扇区(最小擦除单位)
	W25Q64_PageProgram(0x000000,ArrayWrite,4);//写入0x01,0x02,0x03,0x04
	
	W25Q64_ReadData(0x000000,ArrayRead,4);//读出结果,实际上也是0x01,0x02,0x03,0x04
	
	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){
		
	}
}

五、SPI通信硬件外设

与I2C一样,除了软件读写方法,STM32内部也集成了硬件SPI收发电路,自动执行时钟生成、数据收发等功能,减轻CPU负担。

硬件手册的说明内容较为繁杂,可采用主线+分支的学习方法,先把简单的实现了,偏难知识点可不深究,后续再增加更多的功能&选择其他配置模式!下面加粗的是随后要代码实测的!

①可配置8位/16位数据帧、高位先行/低位先行。(串口是低位先行)

②时钟频率:外设时钟频率/(2,4,5,16,32,64,128,246)。最大72MHz/2=36MHz

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

④兼容I2S协议:I2S是一种音频传输协议。

这里使用得STM32F103C8T6芯片外设资源:SPI1、SPI2SPI1挂载在APB2,外设时钟频率是72MHz,SPI2挂载在APB1,外设时钟频率是36MHz。

如下为SPI硬件框图

大致可以分为两部分,左上角是数据寄存器和移位寄存器的配合过程,与串口、I2C等的设计思路相似,可对比学习,都是为了实现连续的数据流。核心部分是移位寄存器(右移状态 低位先行),即右侧的数据一位一位从MOSI移出,MISO的数据一位一位移到左侧数据高位;改变LSBFIRST控制位可变为高位先行。MOSI与MISO处方框的交叉线是为了切换为从机模式使用。接收缓冲区RDR发送缓冲区TDR,实际是同一地址统称数据寄存器DR,配合移位寄存器实现连续的数据流,过程如下:

发送数据从数据总线先到TDR,再转到移位寄存器发送,发送的同时接收数据,接收到的数据转到RDR,再从RDR读取数据到数据总线,与串口、I2C的设计相似,但也有些许不同。SPI是全双工,发送和接收是同步进行的,DR的发送和接收缓冲区是分离的,移位寄存器发送和接收可共用;I2C是半双工,发送和接收不同步进行,它的DR和移位寄存器都是共用的;串口是全双工,且发送和接收可异步进行,DR和移位寄存器都是分离的。

右下角是一些控制逻辑,“寄存器的哪些位,控制哪些部分,会产生哪些效果”可通过手册查阅。波特率发生器主要用来产生SCK时钟,内部主要是分频器,输入时钟是PCLK(72M/36M),经过分频后输出到SCK引脚,输出时钟与移位寄存器同步,一个周期移入/出1bit。SPI_CR1寄存器的三个位BR0、BR1、BR2用来控制分频系数,其他具体可看手册。

只保留核心部的简化图:(移位寄存器这里为左移)

如何产生具体的时序呢?什么时候写DR,什么时候读DR呢?

 传输模式主模式全双工连接传输(效率高、程序复杂)和非连续传输(程序简单,推荐初学者)!

上图示例使用的是SPI模式3,所以第一行SCK默认是高电平,在第一个下降沿,MOSI和MISO移出数据,上升沿移入数据;第二行是MOSI和MISO的输出波形,跟随时钟变化,可见示例演示的是低位先行模式,实际使用时高位先行更多;第三行TXE是发送寄存器空标志位;第四行为TDR;第五行BSY由硬件自动设置和清除。后面三行演示的是数据输入(接收数据)的时序。每一个关键点都引出在图下标注,可对照理解。


若没有强制需求,我们更倾向于使用下面的非连续传输,更加简单适合入门者,且对于程序设计非常友好,实际使用时只需要4行代码

这里只演示了输出数据(发送数据),没演示输入数据(接收数据)!区别在于当TXE为1时,不着急立即把下一个数据写进去,而是一直等待,等到第一个时序结束,意味着接收第一个字节也结束了,这时接收的RXNE会置1,先把第一个接收的数据读出来,之后再写入下一个数据。这样拖慢了传输速度,但简洁了流程。


六、硬件SPI读写W25Q64

线路连接和软件SPI一样,但是硬件SPI只有这以一种接法(根据芯片手册连接,不得任意选择),软件SPI的接线可以任意选择。

PA5是SPI1的SCK,所以接到了W25Q64的CLK引脚;PA6是MISO,接到了W25Q64的DO引脚;PA7是MOSI,接到了W25Q64的DI引脚,这几个不可接错。W25Q64的CS接到PA4,VCC和GND不必多说。

//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)
	{
		
	}
}

时序如下W25Q64.c文件:

//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终止
}

由于涉及的文件较多,这里不全部列举,下面是文件下载链接:

百度网盘打开icon-default.png?t=N7T8http://xn--https-bl8js66z7n7i//pan.baidu.com/s/1T0yRNeCorQgHZuZWtdnf7g?pwd=eqld%20%20%E6%8F%90%E5%8F%96%E7%A0%81%EF%BC%9Aeqld软硬件波形对比:

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

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

相关文章

Jmeter内置变量 vars 和props的使用详解

JMeter是一个功能强大的负载测试工具&#xff0c;它提供了许多有用的内置变量来支持测试过程。其中最常用的变量是 vars 和 props。 vars 变量 vars 变量是线程本地变量&#xff0c;它们只能在同一线程组内的所有线程中使用&#xff08;线程组内不同线程之间变量不共享&#…

(十二)【Jmeter】线程(Threads(Users))之setUp 线程组

简述 操作路径如下: 作用:在正式测试开始前执行预加载或预热操作,为测试做准备。配置:设置预加载或预热操作的采样器、循环次数等参数。使用场景:确保在正式测试开始前应用程序已经达到稳定状态,减少测试结果的偏差。优点:提供预加载或预热操作,确保测试的准确性。缺…

[newstarctf2023] --RE wp

AndroGenshin: rc4加密表&#xff0c;base64换表&#xff1a; 脚本梭就行 python username b"genshinimpact" base64_table [125, 239, 101, 151, 77, 163, 163, 110, 58, 230, 186, 206, 84, 84, 189, 193, 30, 63, 104, 178, 130, 211,164, 94, 75, 16, 32, 33…

SpringBoot启动报错:Failed to load property source from ‘file:/D:.....

SpringBoot启动报错&#xff1a;Failed to load property source from file:/D:… SpringBoot启动爆如图的错误 2024-02-22 20:57:42.865 ERROR 23024 --- [ restartedMain] o.s.boot.SpringApplication : Application run failedjava.lang.IllegalStateExce…

基于SpringBoot的教师宿舍管理系统设计与实现(源码+调试)

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。今天给大家介绍一篇基于SpringBoot的教师宿…

考研高数(高阶导数的计算)

1.归纳法 常见高阶导数 2.泰勒展开式 3.莱布尼兹公式 4.用导数定义证明导函数在某一点连续的例题

并发编程(2)基础篇-管程

4 共享模型之管程 本章内容 共享问题synchronized线程安全分析Monitorwait/notify线程状态转换活跃性Lock 4.1 共享带来的问题 4.1.1 小故事 老王&#xff08;操作系统&#xff09;有一个功能强大的算盘&#xff08;CPU&#xff09;&#xff0c;现在想把它租出去&#xff…

unity学习(22)——客户端与服务器合力完成注册功能(4)数据库化

单纯的账号密码这种非频繁读写&#xff0c;实现起来很简单的&#xff0c;游戏的属性信息到时候也许会比较麻烦。 思路&#xff1a;每次加入有新键值TryAdd&#xff0c;如果加入成功&#xff0c;直接重写账号密码文件即可。 C#JsonConvert.DeserializeObject反序列化与JsonCon…

Another Redis Desktop Manager工具连接集群

背景&#xff1a;使用Another Redis Desktop Manager连接redsi集群 win10安装 使用 下载 某盘&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1dg9kPm9Av8-bbpDfDg9DsA 提取码&#xff1a;t1sm 使用

PLC远程控制网关如何助力企业提升生产效率与运维能力

PLC远程控制网关如何助力企业提升生产效率与运维能力 PLC远程控制网关是一种用于连接PLC设备与物联网系统的设备。它通过以太网、Wi-Fi、蜂窝网络等通信方式&#xff0c;将PLC设备连接到EMCP物联网云平台。PLC远程控制网关可以实现数据采集、协议转换、边缘计算等功能&#xf…

IO进程线程day6

思维导图&#xff1a; 1.将互斥机制的代码实现重新敲一遍。 #include<myhead.h> int num520;//临界资源//创建一个互斥锁变量 pthread_mutex_t mutex;//定义任务&#xff11;函数 void *task1(void *arg) {printf("我是任务&#xff11;&#xff1a;\n");//3.…

程序员可以做哪些副业?

如果你经常玩知乎、看公众号&#xff08;软件、工具、互联网这几类的&#xff09;你就会发现&#xff0c;好多资源连接都变成了夸克网盘、迅雷网盘的资源链接。 例如&#xff1a;天涯神贴&#xff0c;基本上全是夸克、UC、迅雷网盘的资源链接。 有资源的前提下&#xff0c;迅雷…

SpringCloud全家桶---常用微服务组件(1)

注册中心: *作用: 服务管理 Eureka(不推荐)[读音: 优瑞卡] Nacos(推荐) Zookeeper [读音: 如k波] Consul [读音:康寿] **注册中心的核心功能原理(nacos)** 服务注册: 当服务启动时,会通过rest接口请求的方式向Nacos注册自己的服务 服务心跳: NacosClient 会维护一个定时心跳持…

【Java 面试题】MySQL与Redis 如何保证双写一致性

目录 方案一:延时双删方案二: 删除缓存重试机制方案三:读取biglog异步删除缓存系列文章版本记录方案一:延时双删 延时双删流程 先删除缓存再更新数据库休眠一会(比如1秒),再次删除缓存。这个休眠一会,一般多久呢?都是1秒? 这个休眠时间 = 读业务逻辑数据

七、计算机视觉-图像的ROI区域

文章目录 1、什么是ROI2、ROI如何实现的3、一个案例总结 1、什么是ROI 在计算机视觉中&#xff0c;ROI代表感兴趣区域&#xff08;Region of Interest&#xff09;&#xff0c;它是指图像或视频中被指定为需要特别关注或处理的区域。ROI可以帮助减少计算量&#xff0c;并且在处…

口吃矫正:1.缓解口吃预感与紧张感的心态引导

一、我的经验 首先可以看看电影《心灵捕手》的一个片段&#xff1a; 这个片段大致讲的是威尔遇到了一个自己心仪的女生&#xff0c;第一次约会后不敢再约&#xff0c;因为他想保持第一次约会时给女生的完美状态&#xff1b;心理医生查克则给威尔讲述了他的妻子“睡觉时放屁把自…

学习JAVA的第三天(基础)

目录 流程控制语句 顺序结构 分支结构 循环结构 分类&#xff1a; 练习 跳转控制语句 练习 数组 数组介绍 数组的定义和静态初始化 数组定义 数组的静态初始化 数组元素访问 数组遍历 数组动态初始化 JAVA内存分配 流程控制语句 顺序结构 是Java程序默认的执行流程…

Nginx跳转模块之location

目录 一、location模块基本介绍 1、location模块是什么&#xff1f; 2、三种匹配类别 3、常用的匹配规则 4、匹配优先级 优先级总结 二、location模块使用实例 1、精确匹配优先级小于一般匹配的特殊情况 2、解决方法 3、实际网站中使用的三个匹配规则 ①第一个必选规…

MyBatis--02-1- MybatisPlus----条件构造器

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言AbstractWrapper 条件构造器官网文档https://baomidou.com/pages/10c804/#abstractwrapper](https://baomidou.com/pages/10c804/#abstractwrapper)![在这里插入…

MyBatisPlus:PG数组类型自动映射问题

引言: PostGreSQL数据库提供了丰富的数据类型,通过查看官网文档,我们也可以发现,PG也提供了对数组类型的支持。 但是在实际开发中,我们通常是使用MyBatis/MyBatisPlus这种半自动ORM映射框架来实现数据库/表数据基本的增删改查,以及其它操作。那么,问题来了,如何…