STM32CubeMX学习笔记14 ---SPI总线

1. 简介

1.1 SPI总线介绍

        SPI 是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola(摩托罗拉)首先在其MC68HCXX系列处理器上定义的。

        SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。

SPI主从模式
        SPI分为主、从两种模式,一个SPI通讯系统需要包含一个(且只能是一个)主设备,一个或多个从设备。提供时钟的为主设备(Master),接收时钟的设备为从设备(Slave),SPI接口的读写操作,都是由主设备发起。当存在多个从设备时,通过各自的片选信号进行管理。

SPI是全双工且SPI没有定义速度限制,一般的实现通常能达到甚至超过10 Mbps

SPI信号线
SPI接口一般使用四条信号线通信:
SDI(数据输入),SDO(数据输出),SCK(时钟),CS(片选)

  • MISO: 主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。
  • MOSI: 主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。
  • SCLK:串行时钟信号,由主设备产生。
  • CS/SS:从设备片选信号,由主设备控制。它的功能是用来作为“片选引脚”,也就是选择指定的从设备,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。

硬件上为4根线。

SPI一对一

 SPI一对多

SPI数据发送接收
SPI主机和从机都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节来发起一次传输。

首先拉低对应SS信号线,表示与该设备进行通信
主机通过发送SCLK时钟信号,来告诉从机写数据或者读数据
这里要注意,SCLK时钟信号可能是低电平有效,也可能是高电平有效,因为SPI有四种模式,这个我们在下面会介绍
主机(Master)将要发送的数据写到发送数据缓存区(Menory),缓存区经过移位寄存器(0~7),串行移位寄存器通过MOSI信号线将字节一位一位的移出去传送给从机,,同时MISO接口接收到的数据经过移位寄存器一位一位的移到接收缓存区。
从机(Slave)也将自己的串行移位寄存器(0~7)中的内容通过MISO信号线返回给主机。同时通过MOSI信号线接收主机发送的数据,这样,两个移位寄存器中的内容就被交换。

SPI只有主模式和从模式之分,没有读和写的说法,外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。也就是说,你发一个数据必然会收到一个数据;你要收一个数据必须也要先发一个数据。

SPI工作模式
根据时钟极性(CPOL)及相位(CPHA)不同,SPI有四种工作模式。
时钟极性(CPOL)定义了时钟空闲状态电平:

  • CPOL=0为时钟空闲时为低电平
  • CPOL=1为时钟空闲时为高电平

时钟相位(CPHA)定义数据的采集时间。

  • CPHA=0:在时钟的第一个跳变沿(上升沿或下降沿)进行数据采样。
  • CPHA=1:在时钟的第二个跳变沿(上升沿或下降沿)进行数据采样。

1.2 W25QXX芯片介绍

W25QXX芯片是华邦公司推出的大容量SPI FLASH产品,该系列有W25Q16/32/62/128等。本例程使用W25Q64,W25Q64容量为64Mbits(8M字节):8MB的容量分为128个块(Block)(块大小为64KB),每个块又分为16个扇区(Sector)(扇区大小为4KB);W25Q64的最小擦除单位为一个扇区即4KB,因此在选择芯片的时候必须要有4K以上的SRAM(可以开辟4K的缓冲区)。W25Q64的擦写周期多达10万次,具有20年的数据保存期限。 下表是W25QXX的常用命令表

 

常用指令:

写使能(Write Enable) (06h)

向FLASH发送0x06 写使能命令即可开启写使能,首先CS片选拉低,控制写入字节函数写入命令,CS片选拉高。

扇区擦除指令(Sector Erase) (0x20h)

 

扇区擦除指令,数据写入前必须擦除对应的存储单元,该指令先拉低/CS引脚电平,接着传输“20H”指令和要24位要擦除扇区的地址。

读命令(Read Data) (03h)

读数据指令可从存储器依次一个或多个数据字节,该指令通过主器件拉低/CS电平使能设备开始传输,然后传输“03H”指令,接着通过DI管脚传输24位地址,从器件接到地址后,寻址存储器中的数据通过DO引脚输出。每传输一个字节地址自动递增,所以只要时钟继续传输,可以不断读取存储器中的数据。

 

状态读取命令(Read Status Register)

读状态寄存器1(05H),状态寄存器2(35H),状态寄存器3(15H)
写入命令0x05,即可读取状态寄存器的值。

 写入命令(Page Program) (02h)

 

在对W25Q128 FLASH的写入数据的操作中一定要先擦出扇区,在进行写入,否则将会发生数据错误。
W25Q128 FLASH一次性最大写入只有256个字节。
在进行写操作之前,一定要开启写使能(Write Enable)。
当只接收数据时不但能只检测RXNE状态 ,必须同时向发送缓冲区发送数据才能驱动SCK时钟跳变。

2. 硬件设计

LED2指示灯用来提示系统运行状态,S1按键用来控制W25Q64数据写入,S2按键用来控制W25Q64数据读取,串口1用来打印写入和读取的数据信息

  • LED2指示灯
  • S1和S2按键

  • USART1
  • SPI
  • W25Q64

3、STM32CubeMX设置

  • RCC设置外接HSE,时钟设置为72M
  • PE5(LED2)设置为GPIO推挽输出模式、上拉、高速、默认输出电平为高电平
  • USART1选择为异步通讯方式,波特率设置为115200Bits/s,传输数据长度为8Bit,无奇偶校验,1位停止位
  • PE3,PE4设置为GPIO输入模式、上拉模式
  • 激活SPI2,选择全双工主机模式

不使能硬件NSS
在这里插入图片描述

STM32有硬件NSS(片选信号),可以选择使能,也可以使用其他IO口接到芯片的NSS上进行代替
其中SIP2的片选NSS : SPI2_NSS(PB12)

如果片选引脚没有连接 SPI1_NSS(PA4)或者SPI2_NSS(PB12),则需要选择软件片选

NSS管脚及我们熟知的片选信号,作为主设备NSS管脚为高电平,从设备NSS管脚为低电平。当NSS管脚为低电平时,该spi设备被选中,可以和主设备进行通信。在stm32中,每个spi控制器的NSS信号引脚都具有两种功能,即输入和输出。所谓的输入就是NSS管脚的信号给自己。所谓的输出就是将NSS的信号送出去,给从机。
对于NSS的输入,又分为软件输入和硬件输入。
软件输入:
NSS分为内部管脚和外部管脚,通过设置spi_cr1寄存器的ssm位和ssi位都为1可以设置NSS管脚为软件输入模式且内部管脚提供的电平为高电平,其中SSM位为使能软件输入位。SSI位为设置内部管脚电平位。同理通过设置SSM和SSI位1和0则此时的NSS管脚为软件输入模式但内部管脚提供的电平为0。若从设备是一个其他的带有spi接口的芯片,并不能选择NSS管脚的方式,则可以有两种办法,一种是将NSS管脚直接接低电平。另一种就是通过主设备的任何一个gpio口去输出低电平选中从设备。
硬件输入:
主机接高电平,从机接低电平。

左键对应的软件片选引脚,选择GPIO_Output(输出模式),然后点击GPIO,设置一下备注。

我这里虽然PB12是SPI2的硬件片选NSS,但是我想用软件片选,所以关闭了硬件NSS

PB12设置为GPIO推挽输出模式、上拉、高速(片选引脚)

SPI配置默认如下

SPI配置中设置格式为Motorla格式,数据长度为8bit,MSB先输出。

分频为256分频,则波特率为140KBits/s,CPOL设置为HIGH,CPHA设置为第二个边沿。

不开启CRC检验,NSS为软件控制。

其他为默认设置。

  • 输入工程名,选择工程路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码

4、程序编程

在stm32f1xx_hal_spi.h头文件中可以看到spi的操作函数。分别对应轮询,中断和DMA三种控制方式。

 

  • 轮询: 最基本的发送接收函数,就是正常的发送数据和接收数据
  • 中断: 在SPI发送或者接收完成的时候,会进入SPI回调函数,用户可以编写回调函数,实现设定功能
  • DMA: DMA传输SPI数据

利用SPI接口发送和接收数据主要调用以下两个函数:

HAL_StatusTypeDef  HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//发送数据
/*
*hspi: 选择SPI1/2,比如&hspi1,&hspi2
*pData : 需要发送的数据,可以为数组
Size: 发送数据的字节数,1 就是发送一个字节数据
Timeout: 超时时间,就是执行发送函数最长的时间,超过该时间自动退出发送函数
*/
HAL_StatusTypeDef  HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//接收数据
/*
*hspi: 选择SPI1/2,比如&hspi1,&hspi2
*pData : 接收发送过来的数据的数组
Size: 接收数据的字节数,1 就是接收一个字节数据
Timeout: 超时时间,就是执行接收函数最长的时间,超过该时间自动退出接收函数
*/

SPI接收回调函数:

 HAL_SPI_TransmitReceive_IT(&hspi1, TXbuf,RXbuf,CommSize);

当SPI上接收出现了 CommSize个字节的数据后,中断函数会调用SPI回调函数:

   HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)

 用户可以重新定义回调函数,编写预定功能即可,在接收完成之后便会进入回调函数

  • 在spi.c文件下可以看到SPI2的初始化函数,片选管脚的初始化在gpio.c中
void MX_SPI2_Init(void){
  hspi2.Instance = SPI2;
  hspi2.Init.Mode = SPI_MODE_MASTER;    //设置为主模式
  hspi2.Init.Direction = SPI_DIRECTION_2LINES;  //双线模式
  hspi2.Init.DataSize = SPI_DATASIZE_8BIT;  //  8位数据长度
  hspi2.Init.CLKPolarity = SPI_POLARITY_HIGH;   //串行同步时钟空闲状态为高电平
  hspi2.Init.CLKPhase = SPI_PHASE_2EDGE;    //第二个跳变沿采样
  hspi2.Init.NSS = SPI_NSS_SOFT;    //NSS软件控制
  hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; //分配因子256
  hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;   //MSB先行
  hspi2.Init.TIMode = SPI_TIMODE_DISABLE;   //关闭TI模式
  hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;   //关闭硬件CRC校验
  hspi2.Init.CRCPolynomial = 10;
  if (HAL_SPI_Init(&hspi2) != HAL_OK){
    Error_Handler();
  }
}

void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle){
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(spiHandle->Instance==SPI2){
  __HAL_RCC_SPI2_CLK_ENABLE();  
  __HAL_RCC_GPIOB_CLK_ENABLE();
  /**SPI2 GPIO Configuration    
  PB13     ------> SPI2_SCK
  PB14     ------> SPI2_MISO
  PB15     ------> SPI2_MOSI */
  GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_15;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  GPIO_InitStruct.Pin = GPIO_PIN_14;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  }
}

 创建包含W25Q64芯片的相关操作函数及驱动函数的文件w25qxx.c和w25qxx.h

#ifndef _W25QXX_H_
#define _W25QXX_H_

#include "main.h"
#include "spi.h"
#include "usart.h"

#define W25Q64 	0XC816

extern uint16_t W25QXX_TYPE;	
//修改CS片选引脚,W25Qx_Enable(),W25Qx_Disable()分别为使能和失能SPI设备,即拉低和拉高/CS电平
#define W25Qx_Enable()				HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_RESET)
#define W25Qx_Disable() 			HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_SET)

#define W25X_WriteEnable				0x06 
#define W25X_WriteDisable				0x04 
#define W25X_ReadStatusReg1			    0x05 
#define W25X_ReadStatusReg2			    0x35 
#define W25X_ReadStatusReg3			    0x15 
#define W25X_WriteStatusReg1            0x01 
#define W25X_WriteStatusReg2            0x31 
#define W25X_WriteStatusReg3            0x11 
#define W25X_ReadData					0x03 
#define W25X_FastReadData				0x0B 
#define W25X_FastReadDual				0x3B 
#define W25X_PageProgram				0x02 
#define W25X_BlockErase					0xD8 
#define W25X_SectorErase				0x20 
#define W25X_ChipErase					0xC7 
#define W25X_PowerDown					0xB9 
#define W25X_ReleasePowerDown		    0xAB 
#define W25X_DeviceID					0xAB 
#define W25X_ManufactDeviceID			0x90 
#define W25X_JedecDeviceID			    0x9F 
#define W25X_Enable4ByteAddr            0xB7
#define W25X_Exit4ByteAddr              0xE9

void W25QXX_Init(void);
uint16_t W25QXX_ReadID(void);  	    		
uint8_t  W25QXX_ReadSR(uint8_t regno);             
void W25QXX_Write_SR(uint8_t regno,uint8_t sr);   
void W25QXX_Write_Enable(void);  		
void W25QXX_Write_Disable(void);	
void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead); 

void W25QXX_Write_Page(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);
void W25QXX_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite); 
void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);

void W25QXX_Erase_Chip(void);    	  	
void W25QXX_Erase_Sector(uint32_t Dst_Addr);	
void W25QXX_Wait_Busy(void);           	
void W25QXX_PowerDown(void);        	
void W25QXX_WAKEUP(void);	


#endif

 

#include "w25qxx.h"
#include <stdio.h>

uint16_t W25QXX_TYPE;

void W25QXX_Init(void)
{
	W25Qx_Disable();
	//MX_SPI2_Init();
	W25QXX_TYPE = W25QXX_ReadID(); //读取芯片ID
	printf1("FLASH ID:%X\r\n",W25QXX_TYPE);
	if(W25QXX_TYPE == 0xc816)
		printf1("FLASH TYPE:W25Q64\r\n");
}
	
//读取芯片ID	
uint16_t W25QXX_ReadID(void)
{
	uint16_t ID;
	uint8_t id[2]={0};
	uint8_t cmd[4] = {W25X_ManufactDeviceID,0x00,0x00,0x00};//读取ID命令  0x90  
	
	W25Qx_Enable();//芯片使能
	
	HAL_SPI_Transmit(&hspi2,cmd,4,1000);//spi发送读取ID命令,超时1000ms
	HAL_SPI_Receive(&hspi2,id,2,1000); //spi读取ID存放在数组id中,超时1000ms
	W25Qx_Disable(); //取消片选  
	
	ID = (((uint16_t)id[0])<<8)|id[1];
	return ID;
}

//读取片区
uint8_t  W25QXX_ReadSR(uint8_t regno)
{
	uint8_t byte=0,cmd=0;
	switch(regno)
	{
		case 1:
			cmd = W25X_ReadStatusReg1;
			break;
		case 2:
			cmd = W25X_ReadStatusReg2;
			break;
		case 3:
			cmd = W25X_ReadStatusReg3;
			break;
		default:
			cmd = W25X_ReadStatusReg1;
			break;
	}
	W25Qx_Enable();
	HAL_SPI_Transmit(&hspi2,&cmd,1,1000);
	HAL_SPI_Receive(&hspi2,&byte,1,1000);
	W25Qx_Disable();
	
	return byte;
}
	
	//写片区
void W25QXX_Write_SR(uint8_t regno,uint8_t sr)
{
	uint8_t cmd=0;
	switch(regno)
	{
		case 1:
			cmd = W25X_WriteStatusReg1;
			break;
		case 2:
			cmd = W25X_WriteStatusReg2;
			break;
		case 3:
			cmd = W25X_WriteStatusReg3;
			break;
		default:
			cmd = W25X_WriteStatusReg1;
			break;
	}
	W25Qx_Enable();
	HAL_SPI_Transmit(&hspi2,&cmd,1,1000);
	HAL_SPI_Receive(&hspi2,&sr,1,1000);
	W25Qx_Disable();
}

//写使能	
void W25QXX_Write_Enable(void)
{
	uint8_t cmd = W25X_WriteEnable; //写使能命令 0x06
	
	W25Qx_Enable();
	HAL_SPI_Transmit(&hspi2,&cmd,1,1000);
	W25Qx_Disable();
}
	

void W25QXX_Write_Disable(void)
{
	uint8_t cmd = W25X_WriteDisable; //写失能命令 0x04
	
	W25Qx_Enable();
	HAL_SPI_Transmit(&hspi2,&cmd,1,1000);
	W25Qx_Disable();	
}


void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead)
{
	uint8_t cmd[4] = {0};
	cmd[0] = W25X_ReadData; //读取命令
	cmd[1] = ((uint8_t)(ReadAddr>>16));
	cmd[2] = ((uint8_t)(ReadAddr>>8));
	cmd[3] = ((uint8_t)ReadAddr);
	
	W25Qx_Enable();
	HAL_SPI_Transmit(&hspi2,cmd,4,1000);
	if(HAL_SPI_Receive(&hspi2,pBuffer,NumByteToRead,1000) != HAL_OK)
	{
		printf1("SPI read failed!\r\n");
	}
	W25Qx_Disable();
}

void W25QXX_Write_Page(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
	uint8_t cmd[4] = {0};
	if(NumByteToWrite > 256)
	{
		NumByteToWrite = 256;
		printf1("写数据量过大,超过一页的大小!\n");
	}
	
	W25QXX_Write_Enable();
	W25Qx_Enable();
	cmd[0] = W25X_PageProgram;
	cmd[1] = ((uint8_t)(WriteAddr>>16));
	cmd[2] = ((uint8_t)(WriteAddr>>8));
	cmd[3] = ((uint8_t)WriteAddr);
	HAL_SPI_Transmit(&hspi2,cmd,4,1000);
	HAL_SPI_Transmit(&hspi2,pBuffer,NumByteToWrite,1000);
	W25Qx_Disable();
	W25QXX_Wait_Busy();
}
	
	
void W25QXX_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
	uint16_t pageremain;
	pageremain = 256 - WriteAddr%256;
	if(NumByteToWrite <= pageremain)
		pageremain = NumByteToWrite;
	
	while(1)
	{
		W25QXX_Write_Page(pBuffer,WriteAddr,pageremain);
		if(NumByteToWrite == pageremain)
			break;
		else
		{
			pBuffer += pageremain;
			WriteAddr += pageremain;
			NumByteToWrite -= pageremain;
			if(NumByteToWrite > 256)
				pageremain = 256;
			else
				pageremain = NumByteToWrite;
		}
	}
}
		
uint8_t W25QXX_BUFFER[4096]={0};
void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
	uint32_t secpos;
	uint16_t secoff;
	uint16_t secremain;
	uint16_t i;
	uint8_t *W25QXX_BUF;
	
	W25QXX_BUF = W25QXX_BUFFER;
	secpos = WriteAddr/4096;  //扇区地址
	secoff = WriteAddr%4096;  //在扇区里的偏移
	secremain = 4096-secoff;	//扇区剩余空间大小
	printf1("WriteAddr:0x%X,NumByteToWrite:%d\r\n",WriteAddr,NumByteToWrite);
	if(NumByteToWrite <= secremain)  //不大于4K字节
		secremain = NumByteToWrite;
	while(1)
	{
		W25QXX_Read(W25QXX_BUF,secpos*4096,4096); //读取整个扇区内容
		for(i=0;i<secremain;i++) //校验数据 
		{
			if(W25QXX_BUF[secoff+i] != 0xff) //需要擦除
				break;
		}
		if(i < secremain)   //需要擦除
		{
			W25QXX_Erase_Sector(secpos);  //擦除扇区
			for(i=0;i<secremain;i++)
			{printf1("4\r\n");
				W25QXX_BUF[i+secoff] = pBuffer[i];
			}
			W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096); //写入整个扇区
		}
		else
		{
			W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);	  //写入扇区剩余空间
		}
		if(NumByteToWrite == secremain) //写入结束了
			break;
		else //写入未结束
		{
			secpos++;  //扇区地址增1
			secoff = 0;  //偏移位置为0  
			
			pBuffer += secremain; //指针偏移
			WriteAddr += secremain; //写地址偏移
			NumByteToWrite -= secremain; //字节数递减
			if(NumByteToWrite > 4096)
				secremain = 4096;   //下个扇区还没是写不完
			else
				secremain = NumByteToWrite; //下个扇区可以写完了
		}
	}
}
	
	

void W25QXX_Erase_Chip(void)
{
	uint8_t cmd = W25X_ChipErase;
	
	W25QXX_Write_Enable();
	W25QXX_Wait_Busy();
	W25Qx_Enable();
	HAL_SPI_Transmit(&hspi2,&cmd,1,1000);
	W25Qx_Disable();
	W25QXX_Wait_Busy();
}

//擦除扇区	
void W25QXX_Erase_Sector(uint32_t Dst_Addr)
{
	uint8_t cmd[4] = {0};
	Dst_Addr *= 4096;
	
	W25QXX_Write_Enable();//写使能
	W25QXX_Wait_Busy();
	W25Qx_Enable();
	cmd[0] = W25X_SectorErase;
	cmd[1] = ((uint8_t)(Dst_Addr>>16));
	cmd[2] = ((uint8_t)(Dst_Addr>>8));
	cmd[3] = ((uint8_t)Dst_Addr);
	HAL_SPI_Transmit(&hspi2,cmd,4,1000);
	W25Qx_Disable();
	W25QXX_Wait_Busy();	
}


void W25QXX_Wait_Busy(void)
{
	while((W25QXX_ReadSR(1)&0x01)==0x01);
}
	

void W25QXX_PowerDown(void)
{
	uint8_t cmd = W25X_PowerDown;
	
	W25Qx_Enable();
	HAL_SPI_Transmit(&hspi2,&cmd,1,1000);
	W25Qx_Disable();
	HAL_Delay(1);
}

	
void W25QXX_WAKEUP(void)
{
	uint8_t cmd = W25X_ReleasePowerDown;
	
	W25Qx_Enable();
	HAL_SPI_Transmit(&hspi2,&cmd,1,1000);
	W25Qx_Disable();
	HAL_Delay(1);
}
  •  在main.c文件下编写SPI测试代码
uint8_t wData[0x100];
uint8_t rData[0x100];
uint32_t i;
unsigned char tx_buf[256];

int main(void)
{
uint8_t key;
	 W25QXX_Init();
	 
  for(i=0;i<0x100;i++){
    wData[i] = 0xff-i;
    rData[i] = 0;
  } 
	
  while (1)
  {
		  key = KEY_Scan(0);
    if(key == 1){
        printf1("S1 write data...\r\n");
        W25QXX_Erase_Sector(0);//擦除扇区
        W25QXX_Write(wData,0,256);
			printf1("S1 write data success\r\n");

    }

    if(key == 2){
    printf1("S2 read data...\r\n");
        W25QXX_Read(rData,0,256);
        for(i=0;i<256;i++){
          printf1("0x%02X ",rData[i]);

        }
    }

    HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5);
    HAL_Delay(500);
		
		
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

5、下载验证

编译无误下载到开发板后,可以看到LED2指示灯不断闪烁,当按下S1按键后数据写入到W25Q64芯片内,当按下S2按键后读取W25Q64芯片的值,同时串口打印出相应信息

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何把网页调用变为代码调用

1.背景 最近有一个需求&#xff0c;猜测一段十六进制流的校验方式&#xff0c;挨个尝试非常耗时&#xff0c;需要写代码&#xff0c;调用网页上的功能。 2.解决方案 可以使用Python的 requests 库来发起HTTP请求&#xff0c;并通过POST请求将数据发送给服务器进行计算CRC校验和…

类和对象周边知识

再谈构造函数 前几期我们把六个默认成员函数一一说明后&#xff0c;构造函数还有一些周边知识。 初始化列表 我们在没有了解初始化列表的时候一般都是使用构造函数初始化或者在声明哪里给予缺省值&#xff0c;那么为什么好药存在初始化列表呢&#xff1f;是因为①.有些值必须…

Java后台面试相关知识点解析

文章目录 JavaJava中四种引用类型及使用场景集合HashMap源码及扩容策略HashMap死循环问题ConcurrentHashMap与HashtableConCurrentHashMap 1.8 相比 1.7 判断单链表是否有环&#xff0c;并且找出环的入口IO线程池线程池的几种创建方式判断线程是否可以回收线程池的7大核心参数线…

【实战】K8S集群部署nacos并接入Springcloud项目容器化运维

文章目录 前言Nacos集群搭建Spring cloud配置nacos将Springcloud项目部署在k8s写在最后 前言 相信很多同学都开发过以微服务为架构的系统&#xff0c;开发微服务必不可少要使用注册中心&#xff0c;比如nacos\consul等等。当然在自动化运维流行的今天&#xff0c;我们也会将注…

Qt6.6搭建WebAssembly

1.首先安装python &#xff0c; 链接&#xff1a;https://www.python.org/ 2.下载并安装qt6. 3.克隆emsdk工程 3.1 进入emsdk目录&#xff0c;然后更新emsdk代码 3.2 下载并安装最新的SDK工具。&#xff08;C:\Qt\emsdk>emsdk install --global latest&#xff09; 3.3…

一周学会Django5 Python Web开发-Django5修改视图UpdateView

锋哥原创的Python Web开发 Django5视频教程&#xff1a; 2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~共计31条视频&#xff0c;包括&#xff1a;2024版 Django5 Python we…

Sui RFP两个提案正在悬赏Grant,4月1日截止速来申请

项目1&#xff1a;智能合约模板市场 概述 创建一个供开发人员购买或提供智能合约模板的市场。针对这样一个特定用户群体&#xff0c;制定支持所需动态的激励机制至关重要。 问题描述 随着众多区块链使用EVM&#xff0c;这些生态中的开发人员受益于各种应用程序和智能合约示…

Linux系统 -- 信号

一 信号的概念 在我们的日常生活中&#xff0c;红绿灯其实也叫信号灯&#xff0c;因此&#xff0c;我们将用红绿灯给大家讲解一下信号的概念。 当你开着车经过红绿灯路口时&#xff0c;你也在收到红绿灯给你的通知&#xff0c;假设是红灯&#xff0c;那么你就需要停下来等待&am…

【MySQL】lower_case_table_names作用及使用

知识点&#xff1a; lower_case_table_names 是mysql设置大小写是否敏感的一个参数。 场景&#xff1a;在使用dataease时&#xff0c;连接外部数据库&#xff0c;启动报错&#xff01;后查看官方文档&#xff0c;特别要求改数据库配置文件&#xff1a;lower_case_table_names …

linux系统---selinux

目录 前言 一、SELinux 的作用及权限管理机制 1.SELinux 的作用 1.1DAC 1.2MAC 1.3DAC 和 MAC 的对比 2.SELinux 基本概念 2.1主体&#xff08;Subject&#xff09; 2.2对象&#xff08;Object&#xff09; 2.3政策和规则&#xff08;Policy & Rule&#xff09; …

MybatisPlus入门详解

一、MyBatisPlus 简介 1.1 创建新模块 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.1</version></dependency> 由于mp并未被收录到idea的系统内置配置,无法…

停工待料,责任真的全在PMC吗?天行健深度剖析背后的原因

在现代制造业中&#xff0c;停工待料的现象时有发生&#xff0c;这不仅影响了生产进度&#xff0c;还增加了企业的运营成本。很多人会自然而然地将责任归咎于生产物料控制&#xff08;PMC&#xff09;部门&#xff0c;认为是他们没有做好物料计划和管理。但事实上&#xff0c;停…

CentOS7 Sqoop 1.4.7 安装 (Hadoop 3.3.0)

CentOS7 Sqoop 1.4.7 安装 (Hadoop 3.3.0) 1、 Sqoop 1.4.7 官网链接下载&#xff1a; https://archive.apache.org/dist/sqoop/1.4.7/ 2、把压缩包用mobaxterm拖到 /tools文件夹 3、解压 tar -zvxf /tools/sqoop-1.4.7.bin__hadoop-2.6.0.tar.gz -C /training/4、进入 /t…

基于redis实现用户登陆

因为session有数据共享问题&#xff0c;不同tomcat服务器中的session不能共享&#xff0c;之后负载均衡就无法实现。所以我们用redis代替session。redis可以被多个tomcat服务器共享&#xff0c;redis基于内存。 之前的session可以看做登陆凭证&#xff0c;本次登陆凭证由sessi…

应用案例 | Softing echocollect e网关助力汽车零部件制造商构建企业数据库,提升生产效率和质量

为了提高生产质量和效率&#xff0c;某知名汽车零部件制造商采用了Softing echocollect e多协议数据采集网关——从机器和设备中获取相关数据&#xff0c;并直接将数据存储在中央SQL数据库系统中用于分析处理&#xff0c;从而实现了持续监控和生产过程的改进。 一 背景 该企业…

FreeRTOS操作系统学习——空闲任务及其钩子函数

空闲任务 当 FreeRTOS 的调度器启动以后就会自动的创建一个空闲任务&#xff0c;这样就可以确保至少有一任务可以运行。但是这个空闲任务使用最低优先级&#xff0c;如果应用中有其他高优先级任务处于就绪态的话这个空闲任务就不会跟高优先级的任务抢占 CPU 资源。空闲任务还有…

鸿蒙NEXT实战开发:【截屏】

展示全屏截图和屏幕局部截图。通过[screenshot]模块实现屏幕截图 &#xff0c;通过[window]模块实现隐私窗口切换&#xff0c;通过[display]模块查询当前隐私窗口。 效果预览 全屏截图局部截图选择区域局部截图 使用说明&#xff1a; 点击右上角图标打开弹窗&#xff0c;选…

常见的验证码

一、短信验证码 前端&#xff1a; 用户填写手机号&#xff0c;点击按钮发送请求用户短信得到验证码后&#xff0c;用户填写表单提交 form 表单&#xff0c;进行验证 后台&#xff1a; 随机生成几位验证码并将生成时间、手机号、验证码存储起来&#xff0c;可以存到session、…

windows安装程序无法将windows配置为此计算机

目录 问题描述 问题原因 解决办法 方法一 方法二 方法三&#xff1a; 问题描述 重装系统时显示windows安装程序无法将windows配置在此计算机硬件上. 问题原因 安装介质已损坏 如果可引导的安装介质&#xff08;如DVD或USB驱动器&#xff09;损坏或损坏&#xff0c;安装过…

C++并发编程 -5. 基于锁实现线程安全的队列和栈容器

如何通过互斥锁和条件变量构建线程安全的队列&#xff08;栈&#xff09; 在C并发编程 -2.线程间共享数据 已经介绍过构建线程安全的栈&#xff0c;现在重新回顾。 一.构建线程安全的栈 1.异常发生在接口处 有时候我们可以将对共享数据的访问和修改聚合到一个函数&#xff0c…