首页 > 其他分享 >stm32 SPI通信外设(硬件SPI读写W25Q64)

stm32 SPI通信外设(硬件SPI读写W25Q64)

时间:2024-09-13 20:53:09浏览次数:14  
标签:void MySPI W25Q64 stm32 SPI InitStructure GPIO

理论

1.SPI外设简介

STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担

可配置8位/16位数据帧、高位先行/低位先行

时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)

支持多主机模型、主或从操作 可精简为半双工/单工通信

支持DMA

兼容I2S协议

STM32F103C8T6 硬件SPI资源:SPI1、SPI2

2.SPI框图

全双工模式:必须只有在发送数据时,才会触发接受数据,因为在这种模式下只能是发送数据时才会触发时钟线(单纯接受不会触发时钟线),实现全双工模式,发送,接收同时进行

同时可以通过标志位TXE与RXEN判断运行状态

3.SPI基本结构

没有NSS线,它可以通过软件进行控制

 4.主模式传输

(1)主模式全双工连续传输

这个工作模式了解即可,实际上常用非连续传输

 (2)非连续传输

(1)判断TXE标志位

判断是否没有数据,如果发送缓存器(发送数据寄存器TDR)没有数据,则置为SET(1)

(2)发送数据

调用SPI_I2S_SendData发送数据

(3)检测RXNE标志位

因为在TXE重新变回SET(1)后,硬件还在发送数据,无法判断是否发送完一个字节,则需要RXNE进行判断,在移位寄存器移完时,会发送字节到接受缓存区RXNE置SET(1)来进行判断

5. GPIO输出与复用输出

在STM32微控制器中,为了使GPIO口能够由硬件外设(如定时器、USART、SPI、I2C等)控制,GPIO口通常需要配置为复用推挽输出模式。以下是这样做的原因:

1. 硬件外设控制的需求

硬件外设(如定时器或串口)通常需要将GPIO引脚用于特定的功能,比如生成PWM信号、传输数据或产生时钟信号。为了实现这些功能,GPIO引脚必须支持复用功能和特定的输出模式。这是因为:

  • 复用功能:STM32的GPIO引脚通常具有复用功能,允许它们被配置为多种不同的外设功能。要让一个引脚工作在某个硬件外设的模式下,必须将其配置为该外设所需的特定复用功能模式。推挽输出模式是很多外设的要求,因为它能提供稳定的电平和较强的驱动能力。

  • 推挽输出模式:推挽输出模式在数字电路中是一种常见的输出配置,它能够通过两个晶体管(NPN和PNP)来驱动引脚提供强大的电流。对于需要稳定的高电平和低电平的外设信号(如PWM输出或高速通信),推挽输出模式可以提供较强的驱动能力和较快的响应速度。

2. 稳定性和驱动能力

  • 稳定性:推挽输出模式能够提供稳定的高电平和低电平,减少信号的抖动和不稳定性。这对于外设的正常运行至关重要,尤其是当需要精确的时间控制和稳定的信号传输时。

  • 驱动能力:推挽输出模式具有较强的电流驱动能力,适合驱动外设的输入。外设(如LED、继电器、外部模块等)通常需要较强的驱动能力来确保信号的可靠传输。

3. 引脚复用和外设配置

STM32微控制器的引脚可以配置为多种功能,通过复用配置选择具体的外设功能。在这种配置下,引脚可能需要具备推挽输出模式来实现以下目的:

  • 灵活性:允许同一引脚在不同的功能模式下工作,提高了引脚的利用效率和设计灵活性。
  • 简化设计:通过复用功能,可以减少外部电路的复杂性和数量,实现更紧凑的设计。

4. 示例

例如,STM32的定时器(TIM)可以配置为产生PWM信号,这时相应的GPIO引脚需要配置为推挽输出模式,以确保PWM信号的稳定性和准确性。同样,USART(串行通信)外设也需要将GPIO引脚配置为推挽输出模式,以保证数据传输的稳定和高效。

总结

为了使STM32的GPIO口能够由硬件外设控制,并且满足外设对信号稳定性和驱动能力的需求,需要将GPIO口配置为复用推挽输出模式。这种配置不仅满足了外设的功能要求,还提供了引脚的多功能复用能力,使得系统设计更为灵活和高效。

代码

硬件SPI读写W25Q64

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"

uint8_t MID;							//定义用于存放MID号的变量
uint16_t DID;							//定义用于存放DID号的变量

uint8_t ArrayWrite[] = {0x01, 0x02, 0x03, 0x04};	//定义要写入数据的测试数组
uint8_t ArrayRead[4];								//定义要读取数据的测试数组

int main(void)
{
	/*模块初始化*/
	OLED_Init();						//OLED初始化
	W25Q64_Init();						//W25Q64初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "MID:   DID:");
	OLED_ShowString(2, 1, "W:");
	OLED_ShowString(3, 1, "R:");
	
	/*显示ID号*/
	W25Q64_ReadID(&MID, &DID);			//获取W25Q64的ID号
	OLED_ShowHexNum(1, 5, MID, 2);		//显示MID
	OLED_ShowHexNum(1, 12, DID, 4);		//显示DID
	
	/*W25Q64功能函数测试*/
	W25Q64_SectorErase(0x000000);					//扇区擦除
	W25Q64_PageProgram(0x000000, ArrayWrite, 4);	//将写入数据的测试数组写入到W25Q64中
	
	W25Q64_ReadData(0x000000, ArrayRead, 4);		//读取刚写入的测试数据到读取数据的测试数组中
	
	/*显示数据*/
	OLED_ShowHexNum(2, 3, ArrayWrite[0], 2);		//显示写入数据的测试数组
	OLED_ShowHexNum(2, 6, ArrayWrite[1], 2);
	OLED_ShowHexNum(2, 9, ArrayWrite[2], 2);
	OLED_ShowHexNum(2, 12, ArrayWrite[3], 2);
	
	OLED_ShowHexNum(3, 3, ArrayRead[0], 2);			//显示读取数据的测试数组
	OLED_ShowHexNum(3, 6, ArrayRead[1], 2);
	OLED_ShowHexNum(3, 9, ArrayRead[2], 2);
	OLED_ShowHexNum(3, 12, ArrayRead[3], 2);
	
	while (1)
	{
		
	}
}

MySPI.h

#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

MySPI.c

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:SPI写SS引脚电平,SS仍由软件模拟
  * 参    数:BitValue 协议层传入的当前需要写入SS的电平,范围0~1
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SS为低电平,当BitValue为1时,需要置SS为高电平
  */
void MySPI_W_SS(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);		//根据BitValue,设置SS引脚的电平
}

/**
  * 函    数:SPI初始化
  * 参    数:无
  * 返 回 值:无
  */
void MySPI_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);	//开启SPI1的时钟
	
	/*GPIO初始化*/
	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);					//将PA4引脚初始化为推挽输出
	
	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);					//将PA5和PA7引脚初始化为复用推挽输出
	
	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);					//将PA6引脚初始化为上拉输入
	
	/*SPI初始化*/
	SPI_InitTypeDef SPI_InitStructure;						//定义结构体变量
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;			//模式,选择为SPI主模式
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;	//方向,选择2线全双工
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//数据宽度,选择为8位
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;		//先行位,选择高位先行
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;	//波特率分频,选择128分频
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;				//SPI极性,选择低极性
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;			//SPI相位,选择第一个时钟边沿采样,极性和相位决定选择SPI模式0
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;				//NSS,选择由软件控制
	SPI_InitStructure.SPI_CRCPolynomial = 7;				//CRC多项式,暂时用不到,给默认值7
	SPI_Init(SPI1, &SPI_InitStructure);						//将结构体变量交给SPI_Init,配置SPI1
	
	/*SPI使能*/
	SPI_Cmd(SPI1, ENABLE);									//使能SPI1,开始运行
	
	/*设置默认电平*/
	MySPI_W_SS(1);											//SS默认高电平
}

/**
  * 函    数:SPI起始
  * 参    数:无
  * 返 回 值:无
  */
void MySPI_Start(void)
{
	MySPI_W_SS(0);				//拉低SS,开始时序
}

/**
  * 函    数:SPI终止
  * 参    数:无
  * 返 回 值:无
  */
void MySPI_Stop(void)
{
	MySPI_W_SS(1);				//拉高SS,终止时序
}

/**
  * 函    数:SPI交换传输一个字节,使用SPI模式0
  * 参    数:ByteSend 要发送的一个字节
  * 返 回 值:接收的一个字节
  */
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);								//读取接收到的数据并返回
}

W25Q64.c

#include "stm32f10x.h"                  // Device header
#include "MySPI.h"
#include "W25Q64_Ins.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_Init(void);
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID);
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

接线图

标签:void,MySPI,W25Q64,stm32,SPI,InitStructure,GPIO
From: https://blog.csdn.net/2302_79504723/article/details/142219302

相关文章

  • SPI协议看这一篇就够了!(图文+代码+解析+仿真)
    目录SPI协议简介SPI工作原理实验需求模块图时序图小结SPI协议简介1.高速传输,SPI作为三大低速总线(UART、IIc、SPI)之一,其传输速度是这个个中最快的一个。它是一种高速、全双工、同步串行通信总线。所谓高速,指的是传输速度,最高能达到几十M/s,具体速度取决于硬件实现和时钟......
  • STM32定时器
    定时器简介定时器,核心就是计数器。STM32的定时器不仅具有基本定时器中断功能,还具备捕获脉冲宽度,PWM输出,互补输出以及编码器计数等功能。F103中共有8个定时器,TIM1-TIM8,不同定时器功能不一样,可分为三类定时器类型主要功能基本定时器TIM6和TIM7,没有输入输出通道,常用作......
  • Proteus 仿真 STM32 之 OLED显示屏
    介绍本博客适合有一定基础的单片机学习爱好者。OLED是一款具有良好显示性能的屏幕,在单片机领域中常见尺寸为0.96英寸(128*64),具体介绍详见博客:OLED详解本文针对OLED显示屏做一个简单的基于按键控制的计算器。器件采用stm32f103c8(主控芯片),OLED12864I2C(OLED显示屏),及基础硬件若干......
  • STM32 -中断
    并发(Concurrency)并发是指同时处理多个任务的能力,但并不意味着这些任务实际上是在同一时刻物理上同时运行的。在单处理器或多处理器系统中,并发通过时间片轮转(time-slicing)等技术实现,这些技术使得操作系统可以在多个任务之间快速切换,从而在宏观上给用户造成所有任务都在同时运行......
  • 毕业设计—Spingboot休闲食品类微博数据的可视分析方法研究 (案例分析)
    摘要信息化社会内需要与之针对性的信息获取途径,但是途径的扩展基本上为人们所努力的方向,由于站在的角度存在偏差,人们经常能够获得不同类型信息,这也是技术最为难以攻克的课题。针对休闲食品类微博数据等问题,对休闲食品类微博数据进行研究分析,然后开发设计出休闲食品类微博数......
  • stm32之硬件SPI读写W25Q64存储器应用案例
    系列文章目录1.stm32之SPI通信协议2.stm32之软件SPI读写W25Q64存储器应用案例3.stm32之SPI通信外设文章目录系列文章目录前言一、电路接线图二、应用案例代码三、应用案例代码分析3.1基本思路3.2相关库函数介绍3.3MySPI模块3.3.1模块初始化3.3.2SPI基本时序......
  • stm32 SPI通信协议&W25Q64(软件SPI读写W25Q64)
    理论SPI1.SPI通信SPI(SerialPeripheralInterface)是由Motorola公司开发的一种通用数据总线四根通信线:SCK(SerialClock)、MOSI(MasterOutputSlaveInput)、MISO(MasterInputSlaveOutput)、SS(SlaveSelect)同步,全双工支持总线挂载多设备(一主多从)SCK:时钟线MOSI:主机输出,从......
  • [20240912]记录使用tnsping遇到的问题.txt
    [20240912]记录使用tnsping遇到的问题.txt--//tnsping用来检测数据库是否连接存在许多局限性,记录自己在使用tnsping遇到的问题.1.环境:--//关闭数据库开启监听.SYS@book>shutdownimmediate;Databaseclosed.Databasedismounted.ORACLEinstanceshutdown.--//服务端监听配置......
  • STM32 之 SDRAM 详解
     目录 前言一、SDRAM简介二、SDRAM的组成原理 2.1存储单元阵列2.1.1地址译码2.1.2存储电容2.2控制逻辑2.2.1时钟同步2.2.2命令解码2.2.3模式寄存器2.3数据输入/输出缓冲2.3.1数据总线2.3.2数据锁存2.4刷新电路2.4.1自动刷新2.4.2自刷新三、STM32与S......
  • SPiT:超像素驱动的非规则ViT标记化,实现更真实的图像理解 | ECCV 2024
    VisionTransformer(ViT)架构传统上采用基于网格的方法进行标记化,而不考虑图像的语义内容。论文提出了一种模块化的超像素非规则标记化策略,该策略将标记化和特征提取解耦,与当前将两者视为不可分割整体的方法形成了对比。通过使用在线内容感知标记化以及尺度和形状不变的位置嵌入......