STM32单片机入门学习——第40节: [11-5] 硬件SPI读写W25Q64

发布于:2025-04-19 ⋅ 阅读:(66) ⋅ 点赞:(0)

写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难,但我还是想去做!

本文写于:2025.04.18

STM32开发板学习——第一节: [1-1]课程简介第40节: [11-5] 硬件SPI读写W25Q64

前言

   本次笔记是用来记录我的学习过程,同时把我需要的困难和思考记下来,有助于我的学习,同时也作为一种习惯,可以督促我学习,是一个激励自己的过程,让我们开始32单片机的学习之路。
   欢迎大家给我提意见,能给我的嵌入式之旅提供方向和路线,现在作为小白,我就先学习32单片机了,就跟着B站上的江协科技开始学习了.
   在这里会记录下江协科技32单片机开发板的配套视频教程所作的实验和学习笔记内容,因为我之前有一个开发板,我大概率会用我的板子模仿着来做.让我们一起加油!
   另外为了增强我的学习效果:每次笔记把我不知道或者问题在后面提出来,再下一篇开头作为解答!

开发板说明

   本人采用的是慧净的开发板,因为这个板子是我N年前就买的板子,索性就拿来用了。另外我也购买了江科大的学习套间。
   原理图如下
1、开发板原理图
在这里插入图片描述
2、STM32F103C6和51对比
在这里插入图片描述
3、STM32F103C6核心板
在这里插入图片描述

视频中的都用这个开发板来实现,如果有资源就利用起来。另外也计划实现江协科技的套件。

下图是实物图
在这里插入图片描述

引用

【STM32入门教程-2023版 细致讲解 中文字幕】
还参考了下图中的书籍:
STM32库开发实战指南:基于STM32F103(第2版)
在这里插入图片描述
数据手册
在这里插入图片描述

解答和科普

一、硬件接线

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
默认情况下,是作为JATG调试端口使用的,都需要先接触调试端口的复用。
SCK(PA5),MISO(PA6),MOSI(PA7),NSS(PA4或其他)。
我们的任务就是修改底层的这个MySPI.c文件,把这些初始化,和时序的执行步骤,由软件实现变成硬件实现。所以,我们把底层的实现,由软件改成硬件,不会影响到上层代码的。
这个就不移出了。
在这里插入图片描述
第一步,开启时钟,开启SPI和GPIO的时钟;
第二步,初始化GPIO口,其中SCK、和MOSI,是由硬件外设控制的输出信号,所以配置为复用推挽输出,MISO是硬件外设的输入信号,我们可以配置为上拉输入;因为输入设备可以有多个,不存在复用输入这个东西,直接上拉输入就可,普通GPIO口可以输入,外设也可以输入;SS引脚,是软件控制的输出信号,所以配置为通用推挽输出;
第三步,配置SPI外设,这一块,使用一个结构体选参数即可,调用一下SPI_Init,这里的各种参数就都配置好了;
第四步,开关控制,SPI_Cmd,给SPI使能。
写DR、读DR、获取标志位。

库文件
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
写DR寄存器,写数据到发送数据寄存器TDR.
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);
读DR寄存器,接收数据寄存器RDR.
FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
获取TXE和RXNE标志位的状态;
这样就可以控制时序产生。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
	
	GPIO_InitTypeDef   GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);	
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin=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);
void MySPI_Init(void)
{

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
	
	GPIO_InitTypeDef   GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);	
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin=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);
	
	SPI_InitTypeDef   SPI_InitStructure;
	
	SPI_InitStructure.SPI_Mode=SPI_Mode_Master;				//模式:主机
	SPI_InitStructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex;	//单双向模式
	SPI_InitStructure.SPI_DataSize=SPI_DataSize_8b;						//数据帧长度
	SPI_InitStructure.SPI_FirstBit=SPI_FirstBit_MSB;					//高位先行
	SPI_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_128;	//外设时钟128分频
	SPI_InitStructure.SPI_CPOL=SPI_CPOL_Low;			//模式0
	SPI_InitStructure.SPI_CPHA=SPI_CPHA_1Edge;
	SPI_InitStructure.SPI_NSS=SPI_NSS_Soft;				//软件
	SPI_InitStructure.SPI_CRCPolynomial=7;				//CRC校验7
	SPI_Init(SPI1,&SPI_InitStructure);
	
	SPI_Cmd(SPI1,ENABLE);
	MySPI_W_SS(1);		//默认不选中从机
}

SS

void MySPI_W_SS(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);
}

void MySPI_Start(void)
{
	MySPI_W_SS(0);
}

void MySPI_Stop(void)
{
	MySPI_W_SS(1);
}

第一步,等待TXE为1,第二步,写入发送的数据至TDR,第三步,等待RXNE为1,第四步,读取RDR接收的数据,之后交换第二个字节,重复这4步。

uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
	while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)!=SET);
	
	SPI_I2S_SendData(SPI1,ByteSend);
	
	while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)!=SET);
	
	return SPI_I2S_ReceiveData(SPI1);
	
}

硬件SPI初始化

void MySPI_Init(void)
{

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
	
	GPIO_InitTypeDef   GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);	
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin=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);
	
	SPI_InitTypeDef   SPI_InitStructure;
	
	SPI_InitStructure.SPI_Mode=SPI_Mode_Master;				//模式:主机
	SPI_InitStructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex;	//单双向模式
	SPI_InitStructure.SPI_DataSize=SPI_DataSize_8b;						//数据帧长度
	SPI_InitStructure.SPI_FirstBit=SPI_FirstBit_MSB;					//高位先行
	SPI_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_128;	//外设时钟128分频
	SPI_InitStructure.SPI_CPOL=SPI_CPOL_Low;			//模式0
	SPI_InitStructure.SPI_CPHA=SPI_CPHA_1Edge;
	SPI_InitStructure.SPI_NSS=SPI_NSS_Soft;				//软件
	SPI_InitStructure.SPI_CRCPolynomial=7;				//CRC校验7
	SPI_Init(SPI1,&SPI_InitStructure);
	
	SPI_Cmd(SPI1,ENABLE);
	MySPI_W_SS(1);		//默认不选中从机
}
#ifndef    __MYSPI_H
#define    __MYSPI_H

void MySPI_Init(void);
void MySPI_Start(void);
void MySPI_Stop(void);
uint8_t MySPI_SwapByte(uint8_t ByteSend);

#endif

二、W25Q64

应用层不变

#include "stm32f10x.h"                  // Device header
#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);				//读ID号的指令,抛玉引砖,返回值没有意义没用
	*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);		//0XFF没有意义,抛砖引玉,就是为了把对面有意义的数据置换过来
	*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);		// 读取设备ID高8位
	*DID <<=8;
	*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);		//获得16位的DID
	MySPI_Stop();
}

void W25Q64_WriteEnable(void)
{
	MySPI_Start();
	MySPI_SwapByte(W25Q64_WRITE_ENABLE);
	MySPI_Stop();
}

void W25Q64_WaiteBusy(void)
{
	uint32_t TimeOut;
	
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
	TimeOut=10000;
	while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE)& 0x01)==0x01)		//用掩码取出低位
	{
		TimeOut--;
		if(TimeOut==0)
		{
		break;
		}
	
	}
	MySPI_Stop();
}

void W25Q64_PageProgram(uint32_t Address,uint8_t *DataArray,uint16_t Count)
{
	uint16_t  i;
	W25Q64_WriteEnable();
	
	MySPI_Start();
	MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
	MySPI_SwapByte(Address>>16);			//0x123456 变为 0x12
	MySPI_SwapByte(Address>>8);				//0x123456 变为 0x1234  高位自动舍去就是0x34	
	MySPI_SwapByte(Address);				//0x123456 高位自动舍去就是0x56	
	for(i=0;i<Count;i++)
	{
	MySPI_SwapByte(DataArray[i]);
	}
	MySPI_Stop();
	W25Q64_WaiteBusy();	//事后等待
}

void W25Q64_SectorErase(uint32_t Address)
{
	W25Q64_WriteEnable();	//写使能
	MySPI_Start();
	MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
	MySPI_SwapByte(Address>>16);			//0x123456 变为 0x12
	MySPI_SwapByte(Address>>8);				//0x123456 变为 0x1234  高位自动舍去就是0x34	
	MySPI_SwapByte(Address);
	MySPI_Stop();
	W25Q64_WaiteBusy();		//事后等待
}
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);			//0x123456 变为 0x12
	MySPI_SwapByte(Address>>8);				//0x123456 变为 0x1234  高位自动舍去就是0x34	
	MySPI_SwapByte(Address);	
	for(i=0;i<Count;i++)
	{
	DataArray[i]=MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	}
	MySPI_Stop();
	
}

#ifndef    __W25Q64_H
#define    __W25Q64_H


void W25Q64_Init(void);
void W25Q64_ReadID(uint8_t *MID ,uint16_t *DID);
void W25Q64_ReadData(uint32_t Address,uint8_t *DataArray,uint32_t Count);
void W25Q64_SectorErase(uint32_t Address);
void W25Q64_PageProgram(uint32_t Address,uint8_t *DataArray,uint16_t Count);

#endif

#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

问题

总结

本节课主要是了解硬件SPI读写W25Q64,在硬件层面对整个SPI进行写,主要是各种时序,标志位和事件来进行,硬件会自动开启时序,需要进行配置。


网站公告

今日签到

点亮在社区的每一天
去签到