STM32F407寄存器操作(DMA+SPI)

news2024/11/25 13:11:01

1.前言

前面看B站中有些小伙伴吐槽F4的SPI+DMA没有硬件可控的CS引脚,那么今天我就来攻破这个问题

我这边暂时没有SPI的从机芯片,并且接收的过程与发送的过程类似,所以这里我就以发送的过程为例了。

2.理论

手册上给出了如下的描述

我们关注一下黑点的两行,这是DMA操作的核心,我们可以理解为TXE与DMA的触发挂钩,这样理解上与程序上都比较好写。

手册上还给出了DMA的触发流程,如下。

我们详细剖析一下TXE与DMA操作关联,可以看到每一次TXE变高,DMA就会进行一次搬运,直到通讯结束,这样一来我们就可以通过等待TXE置位来联动DMA。

除此之外,我们还需监控BSY位,等待TXE=1然后BSY=0后再关闭SPI,进而完成通讯

然后是DMA通道,本次实验我用的是DMA2的数据流3的通道3

3.程序

3.1 SPI初始化

void init_spi1(void)
{
	RCC->AHB1ENR|=1<<1;		//开启PB时钟
	RCC->AHB1ENR|=1<<0;		//开启PA时钟
	RCC->APB2ENR|=1<<12;	//开启SPI1时钟
	
	
	#if	SPI1_NSSMODE==0
	init_spi1_nss1();
	#else
	GPIOA->MODER|=2<<8;		//PA4功能复用
	GPIOA->OSPEEDR|=2<<8;	//端口速度50MHZ
	GPIOA->PUPDR|=1<<8;		//PA4上拉输出
	GPIOA->AFR[0]|=5<<16;			//功能复用到SPI1
	#endif
	
	GPIOA->MODER|=2<<10;		//PA5功能复用
	GPIOA->OSPEEDR|=2<<10;	//端口速度50MHZ
	GPIOA->PUPDR|=1<<10;		//PA3上拉输出
	GPIOA->AFR[0]|=5<<20;			//功能复用到SPI1
	
	GPIOA->MODER|=2<<12;		//PA6功能复用
	GPIOA->OSPEEDR|=2<<12;	//端口速度50MHZ
	GPIOA->PUPDR|=1<<12;		//PA6上拉输出
	GPIOA->AFR[0]|=5<<24;			//功能复用到SPI1
			
	GPIOA->MODER|=2<<14;		//PA7功能复用
	GPIOA->OSPEEDR|=2<<14;	//端口速度50MHZ
	GPIOA->PUPDR|=1<<14;		//PA7上拉输出
	GPIOA->AFR[0]|=5<<28;			//功能复用到SPI1
	
	
	SPI1->CR1&=~(1<<10);		//全双工模式
	
	#if	SPI1_NSSMODE==0
	SPI2->CR1|=1<<9;	//软件控制nss
	SPI2->CR1|=1<<8;	//选择芯片上的引脚
	#else
	SPI1->CR2|=1<<2;		//硬件控制NSS引脚
	#endif
	
	SPI1->CR1|=1<<2;	//作为SPI主机
	
	#if	SPI1_DATALENGTH==8
	SPI1->CR1&=~(1<<11);	//数据长度为8位
	#else
	SPI1->CR1|=(1<<11);		//数据长度为16位
	#endif
	
	#if SPI1_DMA_TX_EN==1
	SPI1->CR2|=1<<1;	//开启DMA传输
	#else
	SPI1->CR2&=~(1<<1);	//开启DMA传输
	#endif
	
	SPI1->CR1|=1<<0;	//从第二位开始采集数据
	SPI1->CR1|=1<<1;	//空闲状态下时钟保持高电平
	
	SPI1->CR1|=SPI_SPEED_8<<3;		//APB2上84MHz,8分频
	SPI1->CR1&=~(1<<7);	//先发送MSB,高位先发送
	
	
	SPI1->I2SCFGR&=~(1<<11);	//关闭I2S功能,使用SPI
}

说一下区别吧,很少,就一句话

SPI的CR2的第一位,解释如下

这里注意一下SPI的发送与接收是分开的,我们可以根据需要开启其中的DMA。

3.2 DMA初始化

//初始化DMA2 组3 通道3
//SPI1_TX
void init_DMA2_S3C3(unsigned char *SPIData,unsigned short SPIWEI)
{	
	DMA2_Stream3 ->CR   = 0;//禁止数据流 ,才能写寄存器 
	
	//外设地址寄存器
	//将所需寄存器的地址放入PAR寄存器
	DMA2_Stream3 ->PAR  = (unsigned int)(&SPI1->DR);
	
	//数据流地址寄存器
	//M1AR仅在双通道模式下有用
	//将数据所在地址给M0AR寄存器
	DMA2_Stream3 ->M0AR = (unsigned int)(SPIData);
	
	DMA2_Stream3 ->NDTR = SPIWEI;			// 一次传输数量
	DMA2_Stream3 ->FCR  = 0x21;		//FIFO所有配置失效
	DMA2_Stream3 ->CR |= 1<< 6;		//储存器到外设模式
	
	//循环模式:
	//当NDTR寄存器减到0时自动重装
	//单次模式(普通模式):
	//NDTR减到0后停止DMA
	DMA2_Stream3 ->CR &=~(1<<8);	//非循环模式
	DMA2_Stream3 ->CR &=~(3<<11);	//外设数据长度:8位
	DMA2_Stream3 ->CR &=~(3<<13);	//存储器数据长度:8位
	
	DMA2_Stream3 ->CR &= ~(1<<9); //外设非增量模式
	DMA2_Stream3 ->CR |= 1<<10;   //存储器增量模式,指针增加,可用于传输数组
	DMA2_Stream3 ->CR |= 1<<16;   //中等优先级
	
	//突发传输
	//DMA占用CPU总线时间,此时CPU无法工作
	//一个节拍:传输多少次32位变量
	//应用场景:从ram里读出字节
	DMA2_Stream3 ->CR &= ~(3<<21);   //外设突发单次传输
	DMA2_Stream3 ->CR &= ~(3<23);   //存储器突发单次传输
	
	DMA2_Stream3 ->CR |= 3<<25;   //通道3
	DMA2_Stream3 ->CR |= 1<<0;    //使能数据流
}

没有什么特别的地方,和存储器去寄存器的操作方式一致。

3.3 发送

unsigned char SPI1_WR(unsigned char SPI1MODE,unsigned char SPI1Data)
{
	unsigned char temp=0;
	switch(SPI1MODE)
	{
		case SPI1_WRMODE:
				
			//清除全部设置
			SPI1->CR1&=~(1<<15);	
			SPI1->CR1&=~(1<<10);
		
			#if SPI1_NSSMODE==0
		
			#else
			SPI1->CR1|=(1<<6);	//开启SPI
			#endif
		
			while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空
			SPI1->DR=SPI1Data;	//发送数据
			while((SPI1->SR&1<<0)==0);	//等待接受缓冲为空
			temp=SPI1->DR;		//接受数据
			while((SPI1->SR&1<<7)==1);	//等待发送缓冲为空
			
			#if SPI1_NSSMODE==0
			;
			#else
			SPI1->CR1&=~(1<<6);	//关闭SPI
			#endif
		break;
		
		
		
		case SPI1_WOMODE:
			
			#if SPI1_NSSMODE==0
		
			#else
			SPI1->CR1|=(1<<6);	//开启SPI
			#endif
			SPI1->CR1&=~(1<<15);	//清除模式设置
			SPI1->CR1&=~(1<<10);	//清除模式设置
            while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空
		
			#if SPI1_DMA_TX_EN==1
			while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空
			#else
			while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空
			SPI1->DR=SPI1Data;	//发送数据
			while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空
			#endif
		
			#if SPI1_NSSMODE==0
			#else
			#endif
			
			while((SPI1->SR&1<<7)==1);	//等待总线空闲
			SPI1->CR1&=~(1<<6);	//关闭SPI
		break;
		
		
		case SPI1_ROMODE:
			SPI1->CR1&=~(1<<10);//清除模式设置
			SPI1->CR1|=1<<10;	//半双工模式只读
			temp=SPI1->DR;		//接受数据
		break;
	}
	return temp;
}

这里稍微说说区别

核心在于两个TXE的判断

第一个TXE就是手册上的第一个判断

第二个也就是后面的,但是由于DMA的存在,所以下面无需我们再判断,当一个数据搬运完成,就会重新再次搬运直达搬运完所有数据TXE才会拉高,所以这里我们无需进行循环判断

4.测试

最终程序

spi.c

#include "spi.h"


void init_spi1(void)
{
	RCC->AHB1ENR|=1<<1;		//开启PB时钟
	RCC->AHB1ENR|=1<<0;		//开启PA时钟
	RCC->APB2ENR|=1<<12;	//开启SPI1时钟
	
	
	#if	SPI1_NSSMODE==0
	init_spi1_nss1();
	#else
	GPIOA->MODER|=2<<8;		//PA4功能复用
	GPIOA->OSPEEDR|=2<<8;	//端口速度50MHZ
	GPIOA->PUPDR|=1<<8;		//PA4上拉输出
	GPIOA->AFR[0]|=5<<16;			//功能复用到SPI1
	#endif
	
	GPIOA->MODER|=2<<10;		//PA5功能复用
	GPIOA->OSPEEDR|=2<<10;	//端口速度50MHZ
	GPIOA->PUPDR|=1<<10;		//PA3上拉输出
	GPIOA->AFR[0]|=5<<20;			//功能复用到SPI1
	
	GPIOA->MODER|=2<<12;		//PA6功能复用
	GPIOA->OSPEEDR|=2<<12;	//端口速度50MHZ
	GPIOA->PUPDR|=1<<12;		//PA6上拉输出
	GPIOA->AFR[0]|=5<<24;			//功能复用到SPI1
			
	GPIOA->MODER|=2<<14;		//PA7功能复用
	GPIOA->OSPEEDR|=2<<14;	//端口速度50MHZ
	GPIOA->PUPDR|=1<<14;		//PA7上拉输出
	GPIOA->AFR[0]|=5<<28;			//功能复用到SPI1
	
	
	SPI1->CR1&=~(1<<10);		//全双工模式
	
	#if	SPI1_NSSMODE==0
	SPI2->CR1|=1<<9;	//软件控制nss
	SPI2->CR1|=1<<8;	//选择芯片上的引脚
	#else
	SPI1->CR2|=1<<2;		//硬件控制NSS引脚
	#endif
	
	SPI1->CR1|=1<<2;	//作为SPI主机
	
	#if	SPI1_DATALENGTH==8
	SPI1->CR1&=~(1<<11);	//数据长度为8位
	#else
	SPI1->CR1|=(1<<11);		//数据长度为16位
	#endif
	
	#if SPI1_DMA_TX_EN==1
	SPI1->CR2|=1<<1;	//开启DMA传输
	#else
	SPI1->CR2&=~(1<<1);	//开启DMA传输
	#endif
	
	SPI1->CR1|=1<<0;	//从第二位开始采集数据
	SPI1->CR1|=1<<1;	//空闲状态下时钟保持高电平
	
	SPI1->CR1|=SPI_SPEED_256<<3;		//APB2上84MHz,8分频
	SPI1->CR1&=~(1<<7);	//先发送MSB,高位先发送
	
	
	SPI1->I2SCFGR&=~(1<<11);	//关闭I2S功能,使用SPI
}

unsigned char SPI1_WR(unsigned char SPI1MODE,unsigned char SPI1Data)
{
	unsigned char temp=0;
	switch(SPI1MODE)
	{
		case SPI1_WRMODE:
				
			//清除全部设置
			SPI1->CR1&=~(1<<15);	
			SPI1->CR1&=~(1<<10);
		
			#if SPI1_NSSMODE==0
		
			#else
			SPI1->CR1|=(1<<6);	//开启SPI
			#endif
		
			while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空
			SPI1->DR=SPI1Data;	//发送数据
			while((SPI1->SR&1<<0)==0);	//等待接受缓冲为空
			temp=SPI1->DR;		//接受数据
			while((SPI1->SR&1<<7)==1);	//等待发送缓冲为空
			
			#if SPI1_NSSMODE==0
			;
			#else
			SPI1->CR1&=~(1<<6);	//关闭SPI
			#endif
		break;
		
		
		
		case SPI1_WOMODE:
			
			#if SPI1_NSSMODE==0
		
			#else
			SPI1->CR1|=(1<<6);	//开启SPI
			#endif
			SPI1->CR1&=~(1<<15);	//清除模式设置
			SPI1->CR1&=~(1<<10);	//清除模式设置
			while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空
		
			#if SPI1_DMA_TX_EN==1
			while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空
			#else
			while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空
			SPI1->DR=SPI1Data;	//发送数据
			while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空
			#endif
		
			#if SPI1_NSSMODE==0
			#else
			#endif
			
			while((SPI1->SR&1<<7)==1);	//等待总线空闲
			SPI1->CR1&=~(1<<6);	//关闭SPI
		break;
		
		
		case SPI1_ROMODE:
			SPI1->CR1&=~(1<<10);//清除模式设置
			SPI1->CR1|=1<<10;	//半双工模式只读
			temp=SPI1->DR;		//接受数据
		break;
	}
	return temp;
}

spi.h

#ifndef SPI_H__
#define SPI_H__

#include "stm32f4xx.h"

#define SPI_SPEED_2 	0
#define SPI_SPEED_4 	1
#define SPI_SPEED_8 	2
#define SPI_SPEED_16 	3
#define SPI_SPEED_32 	4
#define SPI_SPEED_64 	5
#define SPI_SPEED_128 6
#define SPI_SPEED_256 7

//定义空闲状态下的时钟状态,为1则是高电平,否则是低电平
#define SPI1_CPOL	1
//定义数据长度
#define SPI1_DATALENGTH	8

#define SPI1_NSS1UP			do{GPIOB->ODR|=1<<12;}while(0)
#define SPI1_NSS1DOWN		do{GPIOB->ODR&=~(1<<12);}while(0)

//是否软件管理NSS引脚
//0	软件管理
//1	硬件管理
#define SPI1_NSSMODE	1

//是否开启SPI1发送的DMA功能
//0 关闭
//1 开启
#define SPI1_DMA_TX_EN	1
//是否开启SPI1接收的DMA功能
//0 关闭
//1 开启
#define SPI1_DMA_RX_EN	0

//SPI2通信模式
//0	全双工通信
//1	只发送
//2	只接收
#define SPI1_WRMODE	0
#define SPI1_WOMODE	1
#define SPI1_ROMODE	2



#endif

DMA

//初始化DMA2 组3 通道3
//SPI1_TX
void init_DMA2_S3C3(unsigned char *SPIData,unsigned short SPIWEI)
{	
	DMA2_Stream3 ->CR   = 0;//禁止数据流 ,才能写寄存器 
	
	//外设地址寄存器
	//将所需寄存器的地址放入PAR寄存器
	DMA2_Stream3 ->PAR  = (unsigned int)(&SPI1->DR);
	
	//数据流地址寄存器
	//M1AR仅在双通道模式下有用
	//将数据所在地址给M0AR寄存器
	DMA2_Stream3 ->M0AR = (unsigned int)(SPIData);
	
	DMA2_Stream3 ->NDTR = SPIWEI;			// 一次传输数量
	DMA2_Stream3 ->FCR  = 0x21;		//FIFO所有配置失效
	DMA2_Stream3 ->CR |= 1<< 6;		//储存器到外设模式
	
	//循环模式:
	//当NDTR寄存器减到0时自动重装
	//单次模式(普通模式):
	//NDTR减到0后停止DMA
	DMA2_Stream3 ->CR &=~(1<<8);	//非循环模式
	DMA2_Stream3 ->CR &=~(3<<11);	//外设数据长度:8位
	DMA2_Stream3 ->CR &=~(3<<13);	//存储器数据长度:8位
	
	DMA2_Stream3 ->CR &= ~(1<<9); //外设非增量模式
	DMA2_Stream3 ->CR |= 1<<10;   //存储器增量模式,指针增加,可用于传输数组
	DMA2_Stream3 ->CR |= 1<<16;   //中等优先级
	
	//突发传输
	//DMA占用CPU总线时间,此时CPU无法工作
	//一个节拍:传输多少次32位变量
	//应用场景:从ram里读出字节
	DMA2_Stream3 ->CR &= ~(3<<21);   //外设突发单次传输
	DMA2_Stream3 ->CR &= ~(3<23);   //存储器突发单次传输
	
	DMA2_Stream3 ->CR |= 3<<25;   //通道3
	DMA2_Stream3 ->CR |= 1<<0;    //使能数据流
}

我们在主程序里如何使用呢?首先初始化SPI,然后是DMA,最后触发传输即可。这里我传输5个数据0x01,0x02,0x04,0x01最后一位应该是00

unsigned char spi_test_data[5]={0x01,0x02,0x04,0x01};
init_spi1();//初始化SPI1
init_DMA2_S3C3(spi_test_data,5);//初始化DMA
SPI1_WR(SPI1_WOMODE,5);//发送

可以看到效果拔群啊,CS管脚也没问题。

5.结语

至此完整的SPI完全出来了,手册上说这样的效果可以实现SPI的最高速率,但是我没有测试过。刚刚看手册的时候发现DMA有乒乓功能,嗯?难道这样一来速率还能在高?那么还是老样子有问题评论区见,我们下篇文章见。

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

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

相关文章

Graphviz是一个开源的图形可视化软件

官网没有给出代码示例&#xff0c;所以需要自己琢磨&#xff0c; 这里最底下给了一些简单的&#xff0c; 确实可以出很好看的图片 Graphviz介绍 Graphviz是一个开源的图形可视化软件&#xff0c;主要用于绘制各种类型的图表&#xff0c;如流程图、结构图、网络拓扑图等。它通…

【黑马点评】5 Redisson分布式锁

【黑马点评】5 Redisson分布式锁 5 分布式锁-redisson5.1 分布式锁-redission功能介绍5.2 分布式锁-Redission快速入门5.3 分布式锁-redission可重入锁原理5.4 分布式锁-redission锁重试和WatchDog机制5.5 分布式锁-redission锁的MutiLock原理5.6 总结 黑马点评跟做笔记之 5 Re…

如何使用ssm实现学生工作管理系统

TOC ssm794学生工作管理系统jsp 绪论 1.1 研究背景 当前社会各行业领域竞争压力非常大&#xff0c;随着当前时代的信息化&#xff0c;科学化发展&#xff0c;让社会各行业领域都争相使用新的信息技术&#xff0c;对行业内的各种相关数据进行科学化&#xff0c;规范化管理。…

Java爬虫技术:解锁1688商品搜索的新维度

Java爬虫技术简介 Java爬虫技术是指使用Java语言编写的程序&#xff0c;模拟浏览器行为&#xff0c;自动化地从互联网上获取信息。随着技术的发展&#xff0c;Java爬虫技术已经非常成熟&#xff0c;有多种框架和库可以使用&#xff0c;如Jsoup、HttpClient、WebMagic等。 1688…

LSTM-Transformer时间序列预测(单输入单预测)——基于Pytorch框架

1 介绍 在本篇文章中&#xff0c;将介绍如何使用Transformer和LSTM模型进行时间序列预测。这两种模型分别擅长处理序列数据和捕捉时间序列中的长短期依赖关系。我们将结合这两种模型的优势&#xff0c;构建一个强大的预测模型。单输入单输出预测&#xff0c;适合风电预测&…

与C++类和对象的宿命(下)

本文 1.取地址运算符重载const成员函数取地址成员函数的重载 2. 再探构造函数3. 类型转换1. 隐式类型转换注意事项&#xff1a; 2. 显式类型转换2.1 static_cast2.2 dynamic_cast2.3 const_cast2.4 reinterpret_cast 3. C风格类型转换4. 类型转换操作符5. 注意事项6. 总结 4.st…

MySQL 绪论

数据库相关概念 数据库&#xff08;DB&#xff09;&#xff1a;存储数据的仓库数据库管理系统&#xff08;DBMS&#xff09;&#xff1a;操纵和管理数据库的大型软件SQL&#xff1a;操纵关系型数据库的编程语言&#xff0c;定义了一套操作关系型数据库的统一标准主流的关系型数…

域渗透之: 域渗透环境搭建详解基于VMware

域控环境介绍 在域架构中&#xff0c;最核心的就是域控主机&#xff0c;域控主机分为三种: 普通域控额外域控只读域控 域控环境相关知识点介绍 创建域环境首先就是要创建域控主机。域控主机创建完成以后&#xff0c;需要把所有的计算机拉入域中&#xff0c;这样就形成了域控…

权威认证:中国信通院表彰上海斯歌信创成就!

颁奖现场&#xff1a;左二为上海斯歌业务副总裁陈娅香 2024年9月24日-25日&#xff0c;由中国通信标准化协会主办、中国信息通信研究院&#xff08;简称“中国信通院”&#xff09;承办、中国通信企业协会支持的“2024数字化转型发展大会”在北京召开。本届大会以“拥抱数智化无…

Network - Telnet协议

Telnet 是一种网络协议&#xff0c;允许用户使用基于文本的界面通过网络与远程设备通信。它在早期的网络应用中被广泛用于远程管理和故障诊断&#xff0c;使用户能够连接到远程机器和服务&#xff0c;通常是通过 TCP/IP 网络。 Telnet is a network protocol that allows a use…

Python使用nuitka进行打包简易教程(终极教程以后只用它打包了)

目录 专栏导读库的介绍(优点)使用nuitka --help可查看所有命令库的安装1、虚拟环境安装2、在打包时候缺少某些组件&#xff0c;会提示你是否安装&#xff0c;输入是(第3步有截图)个人喜好&#xff0c;可以加上 icon参数 3、开始打包4、打包完成查看大小总结 专栏导读 &#x1f…

Linux TFTP服务器搭建

话得多说 先水一波字 TFTP&#xff08;Trivial File Transfer Protocol&#xff09;是一种简单的文件传输协议。它用于在计算机网络中传输文件&#xff0c;特别适用于在网络设备&#xff08;如开发板和Linux系统下&#xff09;代码调试等操作。TFTP使用UDP&#xff08;User Da…

春季台球行业招商和宣传大会,2025郑州台球展会3月举办

3月招商季&#xff0c;壹肆柒2025郑州台球展助力企业开拓全国台球消费市场&#xff1b; 2025中国&#xff08;郑州&#xff09;国际台球产业博览会&#xff08;壹肆柒台球展&#xff09; The 2025 China (Zhengzhou) International Billiards Industry Expo 开展时间&#xf…

[OS] 再探 kernel_threads-1

Linux内核线程&#xff08;kernel threads&#xff09;是运行在内核空间的线程&#xff0c;它们不拥有独立的地址空间&#xff0c;因此不能访问用户空间&#xff0c;但可以访问内核空间的数据结构。内核线程通常用于执行一些需要并行处理的任务&#xff0c;例如文件系统的任务、…

升维定位在开源AI智能名片2+1链动模式S2B2C商城小程序中的应用与价值

摘要&#xff1a;本文探讨了升维定位理论在开源AI智能名片21链动模式S2B2C商城小程序中的应用。阐述了升维定位对于创新型产品和创业阶段企业的适用性&#xff0c;分析开源AI智能名片21链动模式S2B2C商城小程序如何利用升维定位创造新的需求市场、成为新市场领导者&#xff0c;…

【Linux】自主shell编写

如果学习了常见的Linux命令&#xff0c;exec*函数&#xff0c;环境变量&#xff0c;那你就可以尝试一下写一个简单的shell; 下面跟着我的步骤来吧&#xff01;&#xff01;&#x1f929;&#x1f929; 输入命令行 既然要写一个shell&#xff0c;我们第一步先把这个输入命令行…

定制化的新生代 Layer1 代币经济学

原文标题&#xff1a;《Next-Gen Layer 1 Tokenomics: Three Pillars for the Token Flywheel》 撰文&#xff1a;Eren&#xff0c;Four Pillars 编译&#xff1a;Tia&#xff0c;Techub News Layer1 代币经济学的转变 最近获得大量关注和大量投资的项目&#xff08;如 Berac…

避免误修改:如何在Word中锁定指定内容?

在工作中&#xff0c;保护Word文档的某些部分免于被他人修改是一项常见需求。无论是分享给同事、客户&#xff0c;还是用作正式的合同文件&#xff0c;都需要确保关键内容不被随意更改。今天我们一起来看看&#xff0c;如何在Word文档中锁定部分内容&#xff0c;使其无法编辑修…

数据结构--线性表双向链表的实现

目录 思路设计 总体思维导图 插入部分 头插法尾插法 任意位置插入 删除部分 头结点 尾节点 中间节点 只有头结点且删除的就是头结点 ​编辑 清空链表部分 遍历清空链表的所有节点 不遍历清空 各部分代码 Main部分 MyListedList部分 IndexOutOfException部分 …

微软发布Windows 11 2024更新,新型Copilot+ AI PC功能亮相

前言 微软在Windows 11的2024更新中加强了对人工智能的应用&#xff0c;推出了新功能Copilot。 此次更新的版本号为26100.1742&#xff0c;Copilot将首先在Windows Insider中推出&#xff0c;计划于11月向特定设备和市场推广&#xff0c;用户需开启“尽快获取最新更新”选项以…