【ESP32 IDF 软件模拟SPI驱动 W25Q64存储与读取数组】

目录

  • SPI
    • SPI介绍
    • SPI时序
    • 代码编写(spi&w25q64)
  • 代码调试

SPI

SPI介绍

SPI(Serial Peripheral Interface,串行外围设备接口)是一种高速全双工同步的串行通信总线,常用于微控制器与各种外围设备(如传感器、存储器、显示器等)之间的数据传输。SPI总线由摩托罗拉公司在20世纪80年代初提出,因其高效的通信方式被广泛应用于嵌入式系统中。在这里插入图片描述

在这里插入图片描述

SPI通信特点

  • 全双工通信:SPI是一种全双工通信协议,意味着数据可以同时在两个方向上传输。
  • 主从架构:SPI通信通常涉及一个主设备(Master)和一个或多个从设备(Slave)主设备控制时钟信号和通信时序从设备按照主设备的指令响应。
  • 同步通信:SPI使用一个共享的 时钟信号(SCLK) 来同步数据传输。主设备生成时钟信号从设备根据该时钟进行数据采集和发送。
  • 高速度:由于使用硬件时钟同步,SPI可以实现非常高的数据传输速率。

SPI通信速度
SPI通信速度(也称为SPI时钟频率)取决于多个因素,包括主设备和从设备的硬件能力、通信距离、电路设计等。一般来说,SPI通信速度可以从几kHz到几十MHz不等。以下是一些常见的情况和典型值:

  • 低速应用:一些简单的传感器和外围设备可能只需要几kHz到几百kHz的SPI时钟频率。例如,很多低速SPI设备100kHz到1MHz的范围内工作。

  • 中速应用:一些存储器(如EEPROM、Flash)和显示器可能需要几MHz的SPI时钟频率。例如,常见的SPI Flash存储器通常支持到10MHz或更高的频率。

  • 高速应用:一些高性能设备(如高速ADC/DAC、FPGA、快速存储器)可以支持几十MHz的SPI时钟频率。例如,高速Flash存储器和FPGA之间的通信可以达到50MHz甚至更高。

SPI通信接口
SPI通信通常包括四条主要信号线

  • SCLK(Serial Clock):时钟信号,由主设备生成,控制数据传输速率。
  • MOSI(Master Out Slave In):主设备发送数据线,主设备的数据通过这条线发送到从设备。
    - MISO(Master In Slave Out):从设备发送数据线,从设备的数据通过这条线发送到主设备。
  • SS/CS(Slave Select/Chip Select):从设备选择信号,低电平有效。主设备通过这条线选择具体的从设备进行通信。

SPI通信模式
SPI有四种工作模式,通过时钟极性(CPOL)和时钟相位(CPHA)的组合来定义:

- 模式0:CPOL = 0,CPHA = 0(空闲时钟为低电平,数据在时钟上升沿采样)

  • 模式1:CPOL = 0,CPHA = 1(空闲时钟为低电平,数据在时钟下降沿采样)
  • 模式2:CPOL = 1,CPHA = 0(空闲时钟为高电平,数据在时钟下降沿采样)
  • 模式3:CPOL = 1,CPHA = 1(空闲时钟为高电平,数据在时钟上升沿采样)

SPI通信协议

  • 主设备选择从设备:主设备通过拉低对应从设备的CS信号,选择目标从设备进行通信。
  • 数据传输:主设备生成时钟信号并在MOSI线上发送数据,同时从设备在MISO线上发送数据。数据传输按照字节为单位进行。
  • 数据采集:在时钟信号的上升沿或下降沿,主从设备分别采集数据位。
  • 传输结束:主设备释放CS信号(拉高),结束一次通信。

SPI优缺点
优点:

  • 高速传输:由于硬件时钟同步,SPI能实现比I2C更高的传输速率。
  • 简单协议:协议简单,没有复杂的仲裁和确认机制。
  • 全双工通信:能够实现双向数据同时传输,提高通信效率。

缺点:

  • 占用引脚多:每个从设备都需要一个独立的CS引脚,主设备需要更多的GPIO引脚来选择多个从设备。
  • 没有确认机制:没有像I2C那样的ACK/NACK确认机制,数据传输可靠性需要其他手段保证。

字节交换通信
SPI(Serial Peripheral Interface)是一种基于字节交换的同步串行通信协议。在SPI通信中,数据传输是全双工的,即主设备和从设备可以同时发送和接收数据。这种数据交换方式称为“字节交换”或“位交换”。

  • 选择性读取和写入
    在SPI通信中,虽然数据是在每个时钟周期中双向传输的,但主设备和从设备可以选择忽略接收到的数据。例如:

  • 只发送数据:如果主设备只需要发送数据给从设备,而不关心接收到的数据,它可以简单地忽略接收缓冲区中的数据。

  • 只接收数据:如果主设备只需要从从设备接收数据,它可以在发送过程中发送“空数据”(如0xFF)去置换从机的数据,从机此时可以选择不读(因为是无用的0xff),并只处理接收缓冲区中的数据。

SPI时序

起始条件:SS从高电平切换到低电平
在这里插入图片描述
终止条件:SS从低电平切换到高电平

在这里插入图片描述

主从通信(交换一个字节)
这里以模式0为例,其他的模式类似:
在这里插入图片描述
如果上面的看不懂可以看下面的,两个结合去看就可以看明白了。
在这里插入图片描述
上面的图的解释:

  • 1.在ss片选拉低时,即第0个边沿时(提前将待移出的数据移至 各自数据线上,即移位寄存器的一个数据跑到MOSI,此时如果是1,则MOSI是高电平,反之低电平。MISO同理)

  • 2. SCK上升沿时(第一个边沿)将数据移入,即MOSI的一个数据跑到MISO,MISO的一个数据跑到MOSI

  • 3. SCL下降沿(第二个人边沿),将第二个数据移出,重复步骤1和2 共8次即可完成一个字节的交换。

代码编写(spi&w25q64)

mySPI.c

/** @Author: i want to 舞动乾坤* @Date: 2024-07-26 17:25:14* @LastEditors: i want to 舞动乾坤* @LastEditTime: 2024-07-26 21:47:45* @FilePath: \spi_software_drivet_w25q64\main\mySPI.c* @Description: * * Copyright (c) 2024 by i want to 舞动乾坤, All Rights Reserved. */
#include <driver/gpio.h>
#include <stdint.h>
#include <esp_log.h>#define mySPI_SS_Pin   GPIO_NUM_5 //片选
#define mySPI_SCL_Pin  GPIO_NUM_18 //时钟
#define mySPI_MISO_Pin GPIO_NUM_19//主机输入,从机输出
#define mySPI_MOSI_Pin GPIO_NUM_23//主机输出,从机输入/*** @description: 给片选信号写入高低电平* @param {uint8_t} BitValue 写入的高低电平 (1:高电平) (0:低电平) 取值:0-1* @return {无}*/
void mySPI_Write_SS(uint8_t BitValue)
{gpio_set_level(mySPI_SS_Pin,(uint8_t)BitValue);
}/*** @description: 给时钟信号写入高低电平* @param {uint8_t} BitValue 写入的高低电平 (1:高电平) (0:低电平) 取值:0-1* @return {无}*/
void mySPI_Write_SCL(uint8_t BitValue)
{gpio_set_level(mySPI_SCL_Pin,(uint8_t)BitValue);
}/*** @description:SPI写MOSI引脚电平* @param {uint8_t} 议层传入的当前需要写入MOSI的电平,范围0~0xFF* @tip:此函数需要用户实现内容,当BitValue为0时,需要置MOSI为低电平,当BitValue非0时,需要置MOSI为高电平* @return {无}*/
void MySPI_Write_MOSI(uint8_t BitValue)
{gpio_set_level(mySPI_MOSI_Pin,(uint8_t)BitValue);
}/*** @description:SPI读MISO引脚电平* @param :无* @tip:此函数需要用户实现内容,当前MISO为低电平时,返回0,当前MISO为高电平时,返回1* @return {协议层需要得到的当前MISO的电平,范围0~1}*/
uint8_t mySPI_Read_MISO()
{return   (uint8_t)gpio_get_level(mySPI_MISO_Pin);
}/*** @description: spi初始化* @return {无}*/
void mySPI_Init(void)
{//ss片选,mosi,scl配置为输出模式gpio_config_t gpio_config_InitStructure;//配置spi 的结构体gpio_config_InitStructure.intr_type=GPIO_INTR_DISABLE;//中断失能gpio_config_InitStructure.mode=GPIO_MODE_OUTPUT;//配置为输出模式gpio_config_InitStructure.pull_down_en=GPIO_PULLDOWN_DISABLE;//下拉失能gpio_config_InitStructure.pull_up_en=GPIO_PULLUP_DISABLE;//上拉失能gpio_config_InitStructure.pin_bit_mask=((1ull<<mySPI_SS_Pin) |(1ull<<mySPI_SCL_Pin) |(1ull<<mySPI_MOSI_Pin));//引脚掩码,配置为输出模式gpio_config(&gpio_config_InitStructure);//miso需要配置为输入模式gpio_config_InitStructure.intr_type=GPIO_INTR_DISABLE;//中断失能gpio_config_InitStructure.mode=GPIO_MODE_INPUT;//配置为输入模式gpio_config_InitStructure.pull_down_en=GPIO_PULLDOWN_DISABLE;//下拉失能gpio_config_InitStructure.pull_up_en=GPIO_PULLUP_ENABLE;//上拉使能gpio_config_InitStructure.pin_bit_mask=1ull<<mySPI_MISO_Pin;//引脚掩码,配置为输入模式gpio_config(&gpio_config_InitStructure);ESP_LOGI(" init miso level is","%d",mySPI_Read_MISO());//默认初始化引脚mySPI_Write_SS(1);//,默认将片选置高电平,不选中从机mySPI_Write_SCL(0);//spi模式0,SCK置低电平
}/*** @description: spi开始信号* @return {无}*/
void mySPI_Start()
{mySPI_Write_SS(0);//拉低片选 }/*** @description: spi终止信号* @return {无}*/
void mySPI_Stop()
{mySPI_Write_SS(1);//拉高片选 }/*** @description: spi主机和从机交换一个字节 ,使用SPI模式0* @param {uint8_t} SendByte 要发送的一个字节* @return {返回读取到的字节数据}*/
uint8_t mySPI_SwapByte(uint8_t SendByte)
{uint8_t receiveByte=0x00,i;for(i=0;i<8;i++){MySPI_Write_MOSI(SendByte & (0x80>>i));//发送一个字节,移出数据mySPI_Write_SCL(1);//sck置高电平 ,第一个上升沿,移入数据,数据采样if(mySPI_Read_MISO()==1)//主机负责将从机交换来的数据拿走{receiveByte|=(0x80>>i);}mySPI_Write_SCL(0);//拉低scl时钟信号}return receiveByte;
}

mySPI.h

#ifndef _MYSPI__H
#define _MYSPI__H
void mySPI_Init(void);
void mySPI_Start();
void mySPI_Stop();
uint8_t mySPI_SwapByte(uint8_t SendByte);
#endif

W25Q64.c


#include <stdint.h>
#include "W25Q64_Ins.h"
#include "mySPI.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_ReadID(uint8_t *MID ,uint16_t *DID);
void W25Q64_Init(void);
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

W25Q64_Ins.h

#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H#define W25Q64_WRITE_ENABLE							0x06
#define W25Q64_WRITE_DISABLE						0x04
#define W25Q64_READ_STATUS_REGISTER_1				0x05
#define W25Q64_READ_STATUS_REGISTER_2				0x35
#define W25Q64_WRITE_STATUS_REGISTER				0x01
#define W25Q64_PAGE_PROGRAM							0x02
#define W25Q64_QUAD_PAGE_PROGRAM					0x32
#define W25Q64_BLOCK_ERASE_64KB						0xD8
#define W25Q64_BLOCK_ERASE_32KB						0x52
#define W25Q64_SECTOR_ERASE_4KB						0x20
#define W25Q64_CHIP_ERASE							0xC7
#define W25Q64_ERASE_SUSPEND						0x75
#define W25Q64_ERASE_RESUME							0x7A
#define W25Q64_POWER_DOWN							0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE				0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET			0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID		0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID				0x90
#define W25Q64_READ_UNIQUE_ID						0x4B
#define W25Q64_JEDEC_ID								0x9F
#define W25Q64_READ_DATA							0x03
#define W25Q64_FAST_READ							0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT				0x3B
#define W25Q64_FAST_READ_DUAL_IO					0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT				0x6B
#define W25Q64_FAST_READ_QUAD_IO					0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO				0xE3#define W25Q64_DUMMY_BYTE							0xFF#endif

main.c

#include <esp_log.h>
#include "W25Q64.h"
#include <stdint.h>
uint8_t MID;//厂商号
uint16_t DID;//设备号
uint8_t ArrayWrite[]={0xA1,0xA2,0xA3,0xA4};
uint8_t ArrayRead[4];void app_main(void)
{/*模块初始化*/					W25Q64_Init();						//W25Q64初始化/*显示ID号*/W25Q64_ReadID(&MID, &DID);			//获取W25Q64的ID号ESP_LOGI("MID","%x",MID);//显示MIDESP_LOGI("DID","%x",DID);//显示DID/*W25Q64功能函数测试*/W25Q64_SectorErase(0x000000);					//扇区擦除W25Q64_PageProgram(0x000000, ArrayWrite, 4);	//将写入数据的测试数组写入到W25Q64中W25Q64_ReadData(0x000000, ArrayRead, 4);		//读取刚写入的测试数据到读取数据的测试数组中/*显示数据*/ESP_LOGI("ArrayWrite[0]","%x",ArrayWrite[0]);//显示写入数据的测试数组ESP_LOGI("ArrayWrite[1]","%x",ArrayWrite[1]);ESP_LOGI("ArrayWrite[2]","%x",ArrayWrite[2]);ESP_LOGI("ArrayWrite[3]","%x",ArrayWrite[3]);	ESP_LOGI("ArrayRead[0]","%x",ArrayRead[0]);//显示读取数据的测试数组	ESP_LOGI("ArrayRead[1]","%x",ArrayRead[1]);	ESP_LOGI("ArrayRead[2]","%x",ArrayRead[2]);	ESP_LOGI("ArrayRead[3]","%x",ArrayRead[3]);	}

代码调试

目录结构
在这里插入图片描述

效果
在这里插入图片描述

------------------------------完结--------------------------

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

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

相关文章

苍穹外卖浏览器前端界面修改

背景&#xff1a; 客户原始方案是期望做一个Spring Boot Vue的饿了么系统&#xff0c;但时间上太仓促&#xff0c;所以建议选择开源的苍穹外码目作为作业提交。 客户接受了建议的方案后&#xff0c;期望对前端页面做一些个性化的定制修改。 过程&#xff1a; 苍穹外卖简单介…

【HTML+CSS】HTML超链接:构建网页导航的基石

目录 什么是HTML超链接&#xff1f; 基本语法 示例 链接到另一个网页 链接到同一页面内的不同部分 常用属性 在Web开发的广阔世界中&#xff0c;HTML&#xff08;HyperText Markup Language&#xff09;作为网页内容的标准标记语言&#xff0c;扮演着至关重要的角色。而在…

重拾CSS,前端样式精读-函数(颜色,计算,图像和图形)

前言 本文收录于CSS系列文章中&#xff0c;欢迎阅读指正 在计算机编程中&#xff0c;函数有着重要的作用和意义&#xff0c;它可以实现封装&#xff0c;复用&#xff0c;模块化&#xff0c;参数等功能效果&#xff0c;在如何在CSS中写变量&#xff1f;一文带你了解前端样式利…

操作系统杂项(十)

目录 一、简述socket中select、epoll的使用场景和区别 1、使用场景 2、区别 二、epoll水平触发和边缘触发的区别 三、简述Reactor和Proactor模式 1、Reactor 2、Proactor 3、区别 四、简述同步和异步的区别&#xff0c;阻塞和非阻塞的区别 1、同步与异步 2、阻塞与非…

深入分析 Android ContentProvider (五)

文章目录 深入分析 Android ContentProvider (五)ContentProvider 的性能优化和实践案例1. 性能优化技巧1.1. 数据库索引优化示例&#xff1a;添加索引 1.2. 批量操作与事务管理示例&#xff1a;批量插入操作 1.3. 使用异步操作示例&#xff1a;使用 AsyncTask 进行异步查询 1.…

Linux:基础

一、安装 二、 一些组件 2.1 git管理 集中式版本控制系统:版本库是集中存放在中央服务器的,需要时要先从中央服务器取得最新的版本进行修改,修改后再推送给中央服务器。集中式版本控制系统最大的毛病就是必须联网才能工作,网速慢的话影响太大。 分布式版本控制系统:分布…

Linux网络-wget命令

作者介绍&#xff1a;简历上没有一个精通的运维工程师。希望大家多多关注我&#xff0c;我尽量把自己会的都分享给大家&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 Linux服务器作为一个常用的网络服务器&#xff0c;主要的作用就是向客户端提供网络…

设计模式14-享元模式

设计模式14-享元模式 由来动机定义与结构代码推导特点享元模式的应用总结优点缺点使用享元模式的注意事项 由来动机 在很多应用中&#xff0c;可能会创建大量相似对象&#xff0c;例如在文字处理器中每个字符对象。在这些场景下&#xff0c;如果每个对象都独立存在&#xff0c…

PyCharm 2024.1.4:一站式教程与新特性解析

简介 PyCharm是由JetBrains开发的一款Python集成开发环境&#xff08;IDE&#xff09;&#xff0c;自发布以来&#xff0c;凭借其强大的功能、智能的代码补全、广泛的插件支持和用户友好的界面&#xff0c;成为了Python开发者的首选工具之一。无论是数据科学、Web开发还是其他…

Redis - SpringDataRedis - RedisTemplate

目录 概述 创建项目 引入依赖 配置文件 测试代码 测试结果 数据序列化器 自定义RedisTemplate的序列化方式 测试报错 添加依赖后测试 存入一个 String 类型的数据 测试存入一个对象 优化 -- 手动序列化 测试存入一个Hash 总结&#xff1a; 概述 SpringData 是 S…

在 ArchLinux 上编译运行 axmol 引擎

本文将在 Windows 10 上安装 Arch WSL 中编译 axmol 请确保 WSL2 已正确安装 1. 在微软应用商店安装 ArchLinux 2. 打开 Arch&#xff0c;按照提示输入用户名和密码&#xff0c;尽量简单 3. 配置清华源&#xff0c;速度快的起飞&#xff0c;否则&#xff0c;各种包会安装失败…

光伏电站气象站:现代光伏系统的重要组成部分

光伏电站气象站&#xff0c;作为现代光伏系统的重要组成部分&#xff0c;集成了气象学、电子信息技术、数据处理与分析等多学科技术于一体&#xff0c;能够实时监测并记录包括温度、湿度、风速、风向、太阳辐射强度、降雨量在内的多种气象参数。这些数据不仅是评估光伏板发电效…

GLSL教程 第8章:几何着色器

目录 8.1 几何着色器的介绍 几何着色器的主要功能&#xff1a; 几何着色器的工作流程&#xff1a; 8.2 实现基本的几何变换 示例&#xff1a;将三角形扩展成多个三角形 8.3 几何着色器的高级应用 1. 粒子系统 2. 光晕效果 3. 线框模型 小结 几何着色器是图形管线中的一…

应用层自定义协议以及序列化和反序列化

文章目录 应用层自定义协议以及序列化和反序列化1、应用层自定义协议1.1、应用层1.2、协议 2、序列化和反序列化3、TCP 为什么支持全双工4、jsoncpp基础4.1、序列化4.2、反序列化 5、实现网络版计算器6、手写序列化和反序列化 应用层自定义协议以及序列化和反序列化 1、应用层…

爬取贴吧的标题和链接

免责声明 感谢您学习本爬虫学习Demo。在使用本Demo之前&#xff0c;请仔细阅读以下免责声明&#xff1a; 学习和研究目的&#xff1a;本爬虫Demo仅供学习和研究使用。用户不得将其用于任何商业用途或其他未经授权的行为。合法性&#xff1a;用户在使用本Demo时&#xff0c;应确…

智能算法驱动的爬虫平台:解锁网络数据的无限潜力

摘要 在信息爆炸的时代&#xff0c;网络数据如同深海宝藏&#xff0c;等待着有识之士发掘其无尽价值。本文将探索智能算法驱动的爬虫平台如何成为解锁这一宝库的关键&#xff0c;不仅剖析其技术优势&#xff0c;还通过实例展示它如何助力企业与开发者高效、稳定地采集数据&…

C语言 ——— 数组指针的定义 数组指针的使用

目录 前言 数组指针的定义 数组指针的使用 前言 之前有编写过关于 指针数组 的相关知识 C语言 ——— 指针数组 & 指针数组模拟二维整型数组-CSDN博客 指针数组 顾名思义就是 存放指针的数组 那什么是数组指针呢&#xff1f; 数组指针的定义 何为数组指针&#xf…

【QT】UDP

目录 核心API 示例&#xff1a;回显服务器 服务器端编写&#xff1a; 第一步&#xff1a;创建出socket对象 第二步&#xff1a; 连接信号槽 第三步&#xff1a;绑定端口号 第四步&#xff1a;编写信号槽所绑定方法 第五步&#xff1a;编写第四步中处理请求的方法 客户端…

JAVA开发工具IDEA如何连接操作数据库

一、下载驱动 下载地址&#xff1a;【免费】mysql-connector-j-8.2.0.jar资源-CSDN文库 二、导入驱动 鼠标右击下载到IDEA中的jar包&#xff0c;选择Add as Library选项 如图就导入成功 三、加载驱动 Class.forName("com.mysql.cj.jdbc.Driver"); 四、驱动管理…

【C++】——红黑树(手撕红黑树,彻底弄懂红黑树)

目录 前言 一 红黑树简介 二 为什么需要红黑树 三 红黑树的特性 四 红黑树的操作 4.1 变色操作 4.2 旋转操作 4.3 插入操作 4.4 红黑树插入代码实现 4.5 红黑树的删除 五 红黑树迭代器实现 总结 前言 我们之前都学过ALV树&#xff0c;AVL树的本质就是一颗平…