4.STM32通信接口之SPI通信(含源码)---硬件SPI与W25Q64存储模块通信实战《精讲》
开胃简介
根据上一节对STM32的SPI介绍!本节将进行硬件SPI的实现,片选用软件实现!跟着Whappy走起!W25Q64的驱动层,我们不需要更改,仅仅需要更改一下SPI的协议,即:由软件实现改成硬件实现。跟着Whappy同志步伐学起来!
SPI硬件整体框架如下图,代码配置也是根据这个SPI的基本结构进行实现的,如下五步实现过程。
STM32的SPI相关库函数的介绍如下:
初始化和配置函数
函数名 功能 参数介绍 使用方法 SPI_I2S_DeInit
将 SPI/I2S 外设寄存器重置为默认值 SPIx
: SPI 外设地址在重新配置 SPI 前调用,清除所有之前的配置 SPI_Init
根据指定配置初始化 SPI SPIx
: SPI 外设地址<br>SPI_InitStruct
: SPI 配置结构体配置 SPI 通信参数,如时钟极性、数据大小等 I2S_Init
根据指定配置初始化 I2S SPIx
: SPI 外设地址<br>I2S_InitStruct
: I2S 配置结构体配置 I2S 通信参数,如标准、数据长度等 SPI_StructInit
填充 SPI 初始化结构体默认值 SPI_InitStruct
: SPI 配置结构体指针在手动配置 SPI 前,快速获取默认配置 SPI_I2S_StructInit
填充 I2S 初始化结构体默认值 I2S_InitStruct
: I2S 配置结构体指针在手动配置 I2S 前,快速获取默认配置
控制和命令函数
函数名 功能 参数介绍 使用方法 SPI_Cmd
使能或禁用 SPI SPIx
: SPI 外设地址<br>NewState
: 使能/禁用状态开启或关闭 SPI 外设 I2S_Cmd
使能或禁用 I2S SPIx
: SPI 外设地址<br>NewState
: 使能/禁用状态开启或关闭 I2S 外设 SPI_I2S_ITConfig
配置 SPI/I2S 中断 SPIx
: SPI 外设地址<br>SPI_I2S_IT
: 中断类型<br>NewState
: 使能/禁用状态配置中断,如发送完成、接收完成等 SPI_I2S_DMACmd
配置 SPI/I2S DMA 请求 SPIx
: SPI 外设地址<br>SPI_I2S_DMAReq
: DMA 请求类型<br>NewState
: 使能/禁用状态启用或禁用 DMA 传输
数据传输函数
函数名 功能 参数介绍 使用方法 SPI_I2S_SendData
通过 SPI/I2S 发送数据 SPIx
: SPI 外设地址<br>Data
: 待发送的 16 位数据发送单个数据字 SPI_I2S_ReceiveData
从 SPI/I2S 接收数据 SPIx
: SPI 外设地址读取接收缓冲区的 16 位数据
高级配置函数
函数名 功能 参数介绍 使用方法 SPI_NSSInternalSoftwareConfig
配置内部片选信号 SPIx
: SPI 外设地址<br>SPI_NSSInternalSoft
: NSS 信号配置软件控制从设备片选 SPI_SSOutputCmd
配置 SS 输出 SPIx
: SPI 外设地址<br>NewState
: 使能/禁用状态控制从设备片选输出 SPI_DataSizeConfig
配置数据大小 SPIx
: SPI 外设地址<br>SPI_DataSize
: 数据大小设置每次传输的数据位数 SPI_BiDirectionalLineConfig
配置双向线路方向 SPIx
: SPI 外设地址<br>SPI_Direction
: 传输方向设置单工或半双工模式下的传输方向
CRC 校验函数
函数名 功能 参数介绍 使用方法 SPI_TransmitCRC
发送 CRC SPIx
: SPI 外设地址手动发送 CRC 值 SPI_CalculateCRC
使能/禁用 CRC 计算 SPIx
: SPI 外设地址<br>NewState
: 使能/禁用状态开启或关闭 CRC 硬件计算 SPI_GetCRC
获取 CRC 值 SPIx
: SPI 外设地址<br>SPI_CRC
: CRC 类型读取发送或接收 CRC 值 SPI_GetCRCPolynomial
获取 CRC 多项式 SPIx
: SPI 外设地址读取当前使用的 CRC 多项式
状态和标志函数
函数名 | 功能 | 参数介绍 | 使用方法 |
---|---|---|---|
SPI_I2S_GetFlagStatus | 获取状态标志 | SPIx : SPI 外设地址<br>SPI_I2S_FLAG : 标志类型 | 检查传输、错误等各种状态 |
SPI_I2S_ClearFlag | 清除状态标志 | SPIx : SPI 外设地址<br>SPI_I2S_FLAG : 标志类型 | 手动清除特定状态标志 |
SPI_I2S_GetITStatus | 获取中断状态 | SPIx : SPI 外设地址<br>SPI_I2S_IT : 中断类型 | 检查中断是否触发 |
SPI_I2S_ClearITPendingBit | 清除中断挂起位 | SPIx : SPI 外设地址<br>SPI_I2S_IT : 中断类型 | 手动清除中断挂起状态 |
代码实现SPI步骤--五步实现(采用非连续传输发送)
GPIO 模式 | 十六进制值 | 模式说明 | 典型应用场景 |
---|---|---|---|
GPIO_Mode_AIN | 0x0 | 模拟输入模式 | 用于模拟信号输入,如传感器、ADC采样 |
GPIO_Mode_IN_FLOATING | 0x04 | 浮空输入模式 | 外部悬空输入,无上拉/下拉电阻 |
GPIO_Mode_IPD | 0x28 | 下拉输入模式 | 输入引脚默认为低电平 |
GPIO_Mode_IPU | 0x48 | 上拉输入模式 | 输入引脚默认为高电平 |
GPIO_Mode_Out_OD | 0x14 | 开漏输出模式 | 需要外部上拉电阻,常用于 I2C 通信 |
GPIO_Mode_Out_PP | 0x10 | 推挽输出模式 | 通用数字输出模式,驱动能力强 |
GPIO_Mode_AF_OD | 0x1C | 开漏复用功能 | 复用功能的开漏输出,如 I2C |
GPIO_Mode_AF_PP | 0x18 | 推挽复用功能 | 复用功能的推挽输出,如 USART、SPI |
第一步:开启时钟,开启SPI和GPIO的时钟;
第二步:初始化GPIO,其中SCK和MOSI是由硬件外设控制的输出信号,配置称复用推挽输出,MISO是硬件外设的输入信号,配置上拉输入。SN片选引脚,是由软件控制的输出信号,配置成通用推挽输出。
第三步:配置SPI外设,这一步,我们使用一个结构体进行配置(STM32的库函数)
第四步:使能SPI。
void MySPI_Init(void) {// 开启GPIOA时钟/* 开启GPIOA的时钟 */RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 开启GPIOA的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); // 开启SPI1的时钟/* GPIO的配置 */// 配置SCK和MOSI引脚GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 设置为复用推挽输出模式 (Alternate Function Push-Pull)GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; // 选择引脚5 (SCK) 和 引脚7 (MOSI)GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚速率为50MHzGPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化GPIOA的这两个引脚// 配置MISO引脚GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 设置为上拉输入模式 (Input with Pull-Up)GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; // 选择引脚6 (MISO)GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚速率为50MHzGPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化GPIOA的MISO引脚// 配置NSS引脚 (片选)GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 设置为推挽输出模式 (Push-Pull Output)GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; // 选择引脚4 (NSS)GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚速率为50MHzGPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化GPIOA的NSS引脚/* SPI的配置 */SPI_InitTypeDef SPI_InitStructure;SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128; // 设置SPI波特率预分频器为128,确定SPI通信速度SPI_InitStructure.SPI_CRCPolynomial = 7; // 设置CRC多项式为7,用于数据完整性检测SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 设置数据位宽为8位SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // 设置SPI为全双工模式(即同时发送和接收数据)SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // 设置数据传输顺序为先发送最高有效位(MSB)SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // 设置SPI为主模式SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // 设置为软件控制的片选(NSS)SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; // 设置时钟相位 (CPHA) 为第一沿变化SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; // 设置时钟极性 (CPOL) 为低电平SPI_Init(SPI1, &SPI_InitStructure); // 初始化SPI1外设// 使能SPISPI_Cmd(SPI1, ENABLE); // 启动SPI1外设,开始进行SPI通信 }
详细解释:
时钟使能:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE)
:启用GPIOA外设时钟,以便能够配置和使用GPIOA引脚。RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE)
:启用SPI1外设时钟,确保SPI外设可以正常工作。GPIO配置:
- SCK (Serial Clock) 和 MOSI (Master Out Slave In) 引脚配置为复用推挽输出模式(
GPIO_Mode_AF_PP
),通过这些引脚来传输数据。- MISO (Master In Slave Out) 引脚配置为上拉输入模式(
GPIO_Mode_IPU
),用于接收从外设发送来的数据。- NSS (Chip Select) 引脚配置为推挽输出模式(
GPIO_Mode_Out_PP
),用于控制SPI外设的选通。SPI配置:
- 波特率预分频器:设置为128,这会影响SPI的时钟频率,确保SPI通信速度适合与外设交互。
- CRC多项式:设置为7,SPI传输过程中可以使用CRC校验以提高数据的完整性。
- 数据位宽:设置为8位,即每次传输一个字节的数据。
- SPI方向:设置为全双工模式,意味着数据可以在同一时间同时进行接收和发送。
- 数据位顺序:设置为MSB先传输(
SPI_FirstBit_MSB
),即先发送最高有效位(MSB)。- SPI模式:设置为主模式(
SPI_Mode_Master
),意味着此外设将控制通信。- 片选控制:选择软件控制片选模式(
SPI_NSS_Soft
),即通过软件控制NSS信号的状态。- 时钟极性和相位:
SPI_CPOL_Low
表示时钟的空闲状态为低电平,SPI_CPHA_1Edge
表示数据将在时钟的第一沿变化时采样。使能SPI:
SPI_Cmd(SPI1, ENABLE)
:使能SPI1外设,开始SPI通信,允许SPI发送和接收数据。这段代码配置了STM32的SPI1外设,使其能够与其他SPI外设进行通信(例如传感器、外部存储设备等)。
第五步:最后,参考一下非连续传输,进行一个字节的交换!如下图
uint8_t MySPI_SwapByte(uint8_t Byte) {// 等待SPI1的发送数据寄存器空,确保可以发送数据while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);// 发送数据SPI_I2S_SendData(SPI1, Byte); // 通过SPI发送一个字节的数据// 等待SPI1的接收数据寄存器非空,确保数据已接收while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);// 读取接收到的数据并返回return SPI_I2S_ReceiveData(SPI1); // 返回接收到的字节数据 }
详细解释:
等待发送寄存器空:
SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE)
:检查SPI1的数据发送寄存器(TXE标志)是否为空。如果为“空”(即可以发送数据),则返回SET
,否则继续等待。- 这是通过循环检查,确保数据寄存器准备好可以发送数据,避免数据溢出或丢失。
发送数据:
SPI_I2S_SendData(SPI1, Byte)
:将参数Byte
中的数据通过SPI总线发送出去。SPI的发送操作是同步的,当数据发送寄存器可用时,数据将被传送到外部设备。等待接收寄存器非空:
SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE)
:检查SPI1的接收数据寄存器(RXNE标志)是否非空。若接收寄存器有数据(即数据已经接收到),则返回SET
,否则继续等待。- 这确保了只有在数据完全接收后,才进行下一步操作。
读取接收数据:
SPI_I2S_ReceiveData(SPI1)
:从SPI1接收数据寄存器中读取接收到的字节并返回。- 在交换字节数据时,发送和接收操作是并行进行的。因此,读取接收到的数据通常就是发送数据时外设返回的数据。
总结:
该函数实现了SPI的数据发送与接收。它通过等待SPI硬件标志位来确保发送和接收过程的同步,确保数据的完整传输。在使用时,传入一个字节,发送后等待接收另一个字节并返回。这个函数可以用于SPI设备之间的字节交换操作。
源码
把上一节软件部分改一下即可
#include "stm32f10x.h" // Device header//片选
void MySPI_W_CS(uint8_t BitValue)
{GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);
}void MySPI_Init(void)
{// 开启GPIOA时钟/* 开启GPIOA的时钟 */RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 开启GPIOA的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); // 开启SPI1的时钟/* GPIO的配置 */// 配置SCK和MOSI引脚GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 设置为复用推挽输出模式 (Alternate Function Push-Pull)GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; // 选择引脚5 (SCK) 和 引脚7 (MOSI)GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚速率为50MHzGPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化GPIOA的这两个引脚// 配置MISO引脚GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 设置为上拉输入模式 (Input with Pull-Up)GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; // 选择引脚6 (MISO)GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚速率为50MHzGPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化GPIOA的MISO引脚// 配置NSS引脚 (片选)GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 设置为推挽输出模式 (Push-Pull Output)GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; // 选择引脚4 (NSS)GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚速率为50MHzGPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化GPIOA的NSS引脚/* SPI的配置 */SPI_InitTypeDef SPI_InitStructure;SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128; // 设置SPI波特率预分频器为128,确定SPI通信速度SPI_InitStructure.SPI_CRCPolynomial = 7; // 设置CRC多项式为7,用于数据完整性检测SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 设置数据位宽为8位SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // 设置SPI为全双工模式(即同时发送和接收数据)SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // 设置数据传输顺序为先发送最高有效位(MSB)SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // 设置SPI为主模式SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // 设置为软件控制的片选(NSS)SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; // 设置时钟相位 (CPHA) 为第一沿变化SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; // 设置时钟极性 (CPOL) 为低电平SPI_Init(SPI1, &SPI_InitStructure); // 初始化SPI1外设// 使能SPISPI_Cmd(SPI1, ENABLE); // 启动SPI1外设,开始进行SPI通信
}//SPI模式0
/*** @brief 开始SPI通信,拉低片选信号* @note 通常在发送数据前调用,选中从设备*/
void MySPI_Start(void)
{MySPI_W_CS(0); // 拉低CS(片选)信号,选中从设备
}/*** @brief 结束SPI通信,拉高片选信号* @note 通常在数据传输完成后调用,取消从设备选择*/
void MySPI_Stop(void)
{MySPI_W_CS(1); // 拉高CS(片选)信号,取消从设备选择
}uint8_t MySPI_SwapByte(uint8_t Byte)
{// 等待SPI1的发送数据寄存器空,确保可以发送数据while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);// 发送数据SPI_I2S_SendData(SPI1, Byte); // 通过SPI发送一个字节的数据// 等待SPI1的接收数据寄存器非空,确保数据已接收while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);// 读取接收到的数据并返回return SPI_I2S_ReceiveData(SPI1); // 返回接收到的字节数据
}
知识普及:
SPI 的四种模式详细解析:
模式定义
SPI 模式由两个参数决定:
- CPOL (Clock Polarity):时钟极性
- CPHA (Clock Phase):时钟相位
详细模式说明
- 模式0 (CPOL=0, CPHA=0)
- 时钟空闲态为低电平
- 在上升沿采样数据
- 在下降沿发送数据
- 最常用的模式
- 模式1 (CPOL=0, CPHA=1)
- 时钟空闲态为低电平
- 在下降沿采样数据
- 在上升沿发送数据
- 模式2 (CPOL=1, CPHA=0)
- 时钟空闲态为高电平
- 在下降沿采样数据
- 在上升沿发送数据
- 模式3 (CPOL=1, CPHA=1)
- 时钟空闲态为高电平
- 在上升沿采样数据
- 在下降沿发送数据
时序图解释
SPI Timing Diagram
Click to open code
选择建议
- 确保主从设备的模式一致
- 根据从设备的具体通信要求选择
- 模式0最为通用
- 特定外设可能有特定模式要求
配置示例(STM32)
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; // 时钟极性 SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; // 时钟相位
SPI的学习到此就圆满结束了!相较于I2C,尽管SPI需要多几条物理线路,但它的实现要简单得多。I2C在多设备通信时的复杂性和时序管理常常让人感到头疼,而SPI通过其清晰、简洁的同步时序,极大简化了实现过程。与此不同,USART通信尽管也能够由软件模拟,但相较于SPI和I2C,它的实现要复杂得多。原因在于,USART是一种异步通信协议,没有统一的时钟信号,数据传输完全依赖于发送和接收设备之间的同步,时序完全靠软件来控制和测量,带来了更高的实现难度和出错的风险。
在实际应用中,许多设备的基础通信都使用USART,这也是因为USART协议在很多情况下是最为通用且具备足够灵活性的标准。尽管它实现较为复杂,但其异步特性在一些特定场合(如无需时钟信号、长距离通信等)下仍具有不可替代的优势。因此,虽然USART的时序和同步管理比SPI和I2C复杂,但它仍在很多嵌入式应用中占有一席之地。
进军下一章节!!!!! 时间2024.12.26 20:51 地点:苏州
相关文章:

4.STM32通信接口之SPI通信(含源码)---硬件SPI与W25Q64存储模块通信实战《精讲》
开胃简介 根据上一节对STM32的SPI介绍!本节将进行硬件SPI的实现,片选用软件实现!跟着Whappy走起!W25Q64的驱动层,我们不需要更改,仅仅需要更改一下SPI的协议,即:由软件实现改成硬件…...
生信技能63 - 构建gnomAD变异位点的SQLite查询数据库
将数据量巨大的gnomAD数据库,通过SQLite数据库寻找gnomAD中存在的各种变异注释信息(如等位基因计数,深度,次要等位基因频率等),查询300.000个变量的查询需要大约40秒,通过染色体编号+位置+REF+ALT即可进行快速查询。 1. gnomAD变异注释VCF文件字段 gnomAD VCF各版本包…...
0x0118消息 WM_SYSTIMER
0x0118消息就是WM_SYSTIMER 编辑框出现输入光标时,产生的消息. 0x0118 would be the undocumented WM_SYSTIMER, which appears to be used for caret blinks. 0x0118是一个undocument 消息, 微软没有记录。 但在一些库的源码中可以看到,比如ATL的库文…...
【机器学习】机器学习的基本分类-无监督学习(Unsupervised Learning)
无监督学习(Unsupervised Learning) 无监督学习是一种机器学习方法,主要用于没有标签的数据集。其目标是从数据中挖掘出潜在的结构和模式。常见的无监督学习任务包括 聚类、降维、密度估计 和 异常检测。 1. 无监督学习的核心目标 1.1 聚类…...

[代码随想录09]字符串2的总结
前言 处理字符串主要是有思路,同时总结方法。 题目链接 151. 反转字符串中的单词 - 力扣(LeetCode) 55. 右旋字符串(第八期模拟笔试) 一、翻转字符串里的单词 这个题目的主要思路,代码采用从后往前遍历字…...

java注解(一):什么是注解?什么是元注解?如何自定义注解?注解的原理是什么?
目录 1、什么是注解? 2、什么是元注解 1、Target() 2、Retention() 3、Documented 4、Inherited 3、如何自定义注解以解使用 4、注解的原理 本篇文章主要是介绍注解的概念、原理,以及通过代码演示4种元注解、如何自定义注解。通过反编译的形式进…...

AD20 原理图库更新到原理图
一 点击工具,从库更新。快捷键TL 二 点击完成 三 执行变更,最后点击关闭...

.NET用C#导入Excel数据到数据库
将Excel文件中的数据导入到数据库中不仅能够提升数据处理的效率和准确性,还能极大地促进数据分析和决策制定的过程。尤其在企业级应用中,Excel作为数据输入和初步整理的工具非常普遍,但其功能对于复杂查询、大规模数据管理和跨部门的数据共享…...

小身躯大能量-供热系统通过EtherCAT转Profinet网关进行升级
在现代工业自动化领域,通信技术的进步对于提高系统效率、稳定性和可靠性起着至关重要的作用。EtherCAT(Ethernet for Control Automation Technology)作为一种实时以太网解决方案,因其高性能及成本效益高等特点,在众多…...
Android11.0系统关闭App所有通知
通过广播接收方式,根据包名关闭App所有通知。 packages/apps/Settings$ git diff diff --git a/AndroidManifest.xml b/AndroidManifest.xml index d4c54c6ed8..1ce7d4136f 100644 --- a/AndroidManifest.xmlb/AndroidManifest.xml-106,6 106,7 <uses-permissio…...

# issue 8 TCP内部原理和UDP编程
TCP 通信三大步骤: 1 三次握手建立连接; 2 开始通信,进行数据交换; 3 四次挥手断开连接; 一、TCP内部原理--三次握手 【第一次握手】套接字A∶"你好,套接字B。我这儿有数据要传给你,建立连接吧。" 【第二次…...
力扣100题--移动零
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 请注意 ,必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0]示例 2: 输入: nums [0] 输出: […...

Spring 邮件发送
Spring 邮件发送 1. 主要内容(了解) 2. JavaMail 概述(了解) JavaMail,顾名思义,提供给开发者处理电⼦邮件相关的编程接⼝。JavaMail 是由 Sun 定义的⼀套收发电⼦邮件的 API,它可以⽅便地执⾏⼀…...

利用 360 安全卫士极速版关闭电脑开机自启动软件教程
在使用电脑的过程中,过多的开机自启动软件会严重拖慢电脑的开机速度,影响我们的使用体验。本教程中简鹿办公将详细介绍如何使用 360 安全卫士极速版关闭电脑开机自启动软件,让您的电脑开机更加迅速流畅。 一、打开 360 安全卫士极速版 在电…...
楼房销售系统
文末获取源码和万字论文,制作不易,感谢点赞支持。 毕 业 设 计(论 文) 题目:楼房销售系统设计与实现 摘 要 现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储…...

UML箭线图的理解和实践
在软件开发的世界里,UML(统一建模语言)作为一种标准化的建模语言,扮演着举足轻重的角色。UML类图更是软件开发设计和架构过程中的核心工具,它不仅能帮助开发者明确系统中的类及其关系,还能为后续的代码实现…...

Qt入门8——Qt文件
1. Qt文件概述 文件操作是应用程序必不可少的部分。Qt作为⼀个通用开发库,提供了跨平台的文件操作能力。Qt 提供了很多关于文件的类,通过这些类能够对文件系统进行操作,如文件读写、文件信息获取、文件复制或重命名等。 2. 输入输出设备类 在…...

鸿翼受邀出席2024海峡两岸档案暨缩微学术交流会
近日,由中国档案学会、中国文献影像技术协会共同主办,中华档案暨资讯微缩管理学会参加的2024年海峡两岸档案暨缩微学术交流会在乌鲁木齐召开。鸿翼联合创始人兼CTO罗永秀受邀出席本次交流会并作主题分享。 自1992年以来,该学术交流会已连续举…...

支持win7系统的onnxruntime
在win7 X86系统上,使用了onnxruntime.dll库做AI识别,但是在win7上运行报0xc0000005的错误 经查,ONNX Runtime从v1.15.0版本开始不再支持Windows 7及其之前的操作系统,即便尝试重新编译源代码亦无法在这些老系统上运行,…...

如何利用内链策略提升网站的整体权重?
内链是谷歌SEO中常常被低估的部分,实际上,合理的内链策略不仅能帮助提升页面间的关联性,还可以增强网站的整体权重。通过正确的内链布局,用户可以更流畅地浏览你的网站,谷歌爬虫也能更快地抓取到更多页面,有…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...

7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
ubuntu搭建nfs服务centos挂载访问
在Ubuntu上设置NFS服务器 在Ubuntu上,你可以使用apt包管理器来安装NFS服务器。打开终端并运行: sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享,例如/shared: sudo mkdir /shared sud…...

黑马Mybatis
Mybatis 表现层:页面展示 业务层:逻辑处理 持久层:持久数据化保存 在这里插入图片描述 Mybatis快速入门 
华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...

AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机
这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机,因为在使用过程中发现 Airsim 对外部监控相机的描述模糊,而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置,最后在源码示例中找到了,所以感…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...
CSS | transition 和 transform的用处和区别
省流总结: transform用于变换/变形,transition是动画控制器 transform 用来对元素进行变形,常见的操作如下,它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...
Go语言多线程问题
打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...