STM32使用硬件SPI实现W25Q64的读取

SPI外设简介

STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担。
可配置8位/16位数据帧、高位先行/低位先行
时钟频率: f PCLK / (2, 4, 8, 16, 32, 64, 128, 256)
支持多主机模型、主或从操作
可精简为半双工/单工通信
支持DMA
兼容I2S协议
        I2S是一种数字音频信号传输的专用协议。(比如我们的主控芯片,芯片里面存了一首音乐的数据,这个数据其实就是一个点一个点的电压数值,是数字信号。如果要把这个音乐给播放出来,就需要一个外挂音频解码器,这个音频解码器,实际上就是DAC,数模转换器。它负责把数字信号转换成模拟信号,然后输出到扬声器,把音乐播放出来。)

SPI框图

移位寄存器:右边的数据低位,一位一位从MOSI移出去,然后MISO的数据,一位一位移入到左边的数据高位。显然移位寄存器是一个右移的状态,所以图上表示的低位先行的配置。

LSBFIRST控制位:它可以控制是低位先行还是高位先行。

发送缓冲区:就是发送数据寄存器TDR

接收缓冲区:就是接收数据寄存器RDR

接收和发送缓冲区:实际上就是数据寄存器DR。TDR和RDR占用同一个地址,统一叫作DR。

写入DR时:数据从数据总线,写入到TDR

读取DR时:数据从RDR读出

移位寄存器配合数据寄存器实现连续数据流的过程:就是发送数据先写入到TDR,再转到移位寄存器发送,发送的同时,接收数据。接收到的数据,转到RDR,我们再从RDR读取数据。数据寄存器和移位寄存器配合,可以实现无延迟的连续传输。 

波特率发生器:这个主要就是用来产生SCK时钟。它的内部,主要就是一个分频器。输入时钟是PCLK,72M或36M,经过分频器之后,输出到SCK引脚。这里生成的时钟肯定是和移位寄存器同步的,没产生一个周期的时钟,移入移出一个bit。

CR1寄存器:BR0、BR1、BR2这三位是用来控制分频系数。分频之后,就是SCK时钟。

SPE,是SPI使能,就是SPI_Cmd函数配置的位。

MSTR:配置主从模式。1是主模式,0是从模式,一般使用主模式。

SR状态寄存器:TXE,发送寄存器空,RXNE,接收寄存器非空。

SPI基本结构

这里的移位寄存器是左移,高位移出去,通过GPIO,到MOSI,从MOSI输出,显然这是SPI的主机。之后移入的数据,从MISO进来,通过GPIO,到移位寄存器的低位,这样循环8次,就能实现主机和从机交换一个字节。

TDR数据,整体移入寄存器的时刻,置TXE标志位。移位寄存器数据,整体移入RDR的时刻,置RXNE标志位。

主模式全双工连续传输

非连续传输

交换一个字节
CPOL=1:空闲状态时,SCK为高电平
CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据

SCK默认高电平。发送数据时,如果检测到TXE=1,TDR为空。就软件写入0xF1至SPI_DR,这时TDR的值就变为F1,TXE变为0。目前移位寄存器也是空,所以这个F1会立刻转入移位寄存器开始发送,波形产生,并且TXE置回1,表示可以把下一个数据放在TDR了。TXE=1,并不是立刻把数据写进去,而是一直等待,等第一个字节时序结束。时序结束,意味着接收第一个字节也完成了这时接收的RXNE会置1,等RXNE置1后,先把接收后的数据读出来,之后,再写入下一个字节数据。

代码例程(硬件读写W25Q64)

硬件电路

mian.c

#include "led.h"
#include "delay.h"
#include "key.h"
#include "usart.h"
#include "stdio.h"
#include "myiic.h"
#include "at24c0x.h"
#include "BS8116_IIC1.h"
#include "BS8116.h"
#include "SPI1.h"
#include "W25Q64.h"

uint8_t Tx_Buffer[6]={0x01,0x02,0x03,0x04,0x05,0x06};
uint8_t Rx_Buffer[6];

int main(void)
{
//	uint8_t key;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	SysTick_Config(SystemCoreClock/1000);   //配置1ms的中断
	Led_Config();
	USART1_Config();
//	At24c02_Config();
//	BS8116_Config();
	W25Q64_Config();
	W25Q64_Read_ID();
	
	W25Q64_SectorErase(0x123000);
	W25Q64_PageProgram(0x123000,Tx_Buffer,6);
	W25Q64_ReadData(0x123000,Rx_Buffer,6);
	for(uint8_t i = 0; i<6; i++)
	{
		printf("%x\r\n",Rx_Buffer[i]);
	}
	while(1)
	{	
//		if(LED_Period[0]>=LED_Period[1])
//		{
//			LED_Period[0]=0;
//			led3_T();
//			led4_T();
//		}
//			if(!BS8116_IRQ)//判断按键是否按下    //电容按键
//			{
//				key=BS8116ReadKey();
//				if(key&&key!=0xFF)
//				{
//					printf("按键:%c\r\n",key);
//				}	
//					while(!BS8116_IRQ);
//			}
	}
	
	
}

SPI1.C

#include "spi1.h"


/*************************
PA4      SPI1_CS      通用推挽输出  
PA5      SPI1_SCLK    复用推挽输出
PA6      SPI1_MISO    浮空输入
PA7      SPI1_MOSI    复用推挽输出
**************************/

void SPI1_Config(void)
{
	//开启GPIOA的时钟和SPI1的时钟
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
	
	
	//配置GPIOA
	GPIO_InitTypeDef GPIO_InitStruct;
	//GPIOA 初始化设置: 复用功能输出
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;  //复用
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; //推挽
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;
	GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed;
	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; //浮空
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;  //输出
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; //推挽
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4; 
	GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed;
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	

	
	 //配置引脚复用映射
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource5,GPIO_AF_SPI1); //PB5 复用为 SPI1
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource6,GPIO_AF_SPI1); //PB5 复用为 SPI1
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource7,GPIO_AF_SPI1); //PB7 复用为 SPI1

	//配置SPI1
	SPI_InitTypeDef SPI_InitStruct;
	
	SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //波特率分频,选择256分频
	SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge;		//时钟相位  第二个跳变沿数据被采样
	SPI_InitStruct.SPI_CPOL = SPI_CPOL_High;  //时钟极性  时钟极性默认高电平
	SPI_InitStruct.SPI_CRCPolynomial = 0;    //0;  //CRC校验 
	SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;  // SPI 发送接收 8 位帧结构
	SPI_InitStruct.SPI_Direction =	SPI_Direction_2Lines_FullDuplex;	//双线双向全双工
	SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;	//数据传输从高位开始
	SPI_InitStruct.SPI_Mode = SPI_Mode_Master; //主 SPI
	SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;	//NSS 信号由软件控制
	
	SPI_Init(SPI1,&SPI_InitStruct);
	
	//使能SPI1
	SPI_Cmd(SPI1,ENABLE);
	
	//拉高片选线,让从机处于未选中状态
	GPIO_SetBits(GPIOA,GPIO_Pin_4);
}


uint8_t SPI1_ReadWriteByte(uint8_t TXdata)
{
	//上一次未发送完成  在这里等待
	while(!(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)));
	//上一次发送完成,发送新的数据
	SPI_I2S_SendData(SPI1,TXdata);
	//判断有没有接收完成  未接收完成在这里等待
	while(!(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)));
	//接收完成 接收新的数据
	return SPI_I2S_ReceiveData(SPI1);
}

















SPI1.h

#ifndef _SPI1_H_
#define _SPI1_H_

#include "stm32f4xx.h"

void SPI1_Config(void);
uint8_t SPI1_ReadWriteByte(uint8_t TXdata);

#endif

W25Q64.C

#include "W25Q64.h"
#include "spi1.h"
#include "stdio.h"

void W25Q64_Config(void)
{
	SPI1_Config();
}

void W25Q64_Read_ID(void)
{
	uint8_t buff[3];
	//拉低片选线
	GPIO_ResetBits(GPIOA,GPIO_Pin_4);
	
	//发送命令  
	SPI1_ReadWriteByte(W25Q64_JEDEC_ID);
	//连续发送3个字节
	buff[0] = SPI1_ReadWriteByte(W25Q64_DUMMY_BYTE);
	buff[1] = SPI1_ReadWriteByte(W25Q64_DUMMY_BYTE);
	buff[2] = SPI1_ReadWriteByte(W25Q64_DUMMY_BYTE);
	
	//将片选拉高
	GPIO_SetBits(GPIOA,GPIO_Pin_4);
	
	printf("厂家信息:%x\r\n",buff[0]);
	printf("Mermory Type ID:%x\r\n",buff[1]);
	printf("Capacity ID:%x\r\n",buff[2]);	
}

//W25Q64写使能
void W25Q64_WriteEnable(void)
{
	//拉低片选线,让从机被选中
	GPIO_ResetBits(GPIOA,GPIO_Pin_4);   
	//交换发送写使能的指令
	SPI1_ReadWriteByte(W25Q64_WRITE_ENABLE);		
	//将片选拉高
	GPIO_SetBits(GPIOA,GPIO_Pin_4);
}

//W25Q64等待忙
void W25Q64_WaitBusy(void)
{
	uint32_t Timeout;
	
	//拉低片选线,让从机被选中
	GPIO_ResetBits(GPIOA,GPIO_Pin_4); 
  
	//交换发送读状态寄存器1的指令
	SPI1_ReadWriteByte(W25Q64_READ_STATUS_REGISTER_1);	
	
	Timeout = 100000;							//给定超时计数时间
	
	//循环等待忙标志位
	while ((SPI1_ReadWriteByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)	
	{
		Timeout --;								//等待时,计数值自减
		if (Timeout == 0)						//自减到0后,等待超时
		{
			/*超时的错误处理代码,可以添加到此处*/
			break;								//跳出等待,不等了
		}
	}
	//将片选拉高
	GPIO_SetBits(GPIOA,GPIO_Pin_4);
}


/**
  * 函    数: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();						//写使能
	
	//拉低片选线,让从机被选中
	GPIO_ResetBits(GPIOA,GPIO_Pin_4); 
	SPI1_ReadWriteByte(W25Q64_PAGE_PROGRAM);		//交换发送页编程的指令
	SPI1_ReadWriteByte(Address >> 16);				//交换发送地址23~16位
	SPI1_ReadWriteByte(Address >> 8);				//交换发送地址15~8位
	SPI1_ReadWriteByte(Address);					//交换发送地址7~0位
	for (i = 0; i < Count; i ++)				//循环Count次
	{
		SPI1_ReadWriteByte(DataArray[i]);			//依次在起始地址后写入数据
	}
	//将片选拉高
	GPIO_SetBits(GPIOA,GPIO_Pin_4);
	
	W25Q64_WaitBusy();							//等待忙
}

/**
  * 函    数:W25Q64扇区擦除(4KB)
  * 参    数:Address 指定扇区的地址,范围:0x000000~0x7FFFFF
  * 返 回 值:无
  */
void W25Q64_SectorErase(uint32_t Address)
{
	W25Q64_WriteEnable();						//写使能
	
	//拉低片选线,让从机被选中
	GPIO_ResetBits(GPIOA,GPIO_Pin_4); 
	SPI1_ReadWriteByte(W25Q64_SECTOR_ERASE_4KB);	//交换发送扇区擦除的指令
	SPI1_ReadWriteByte(Address >> 16);				//交换发送地址23~16位
	SPI1_ReadWriteByte(Address >> 8);				//交换发送地址15~8位
	SPI1_ReadWriteByte(Address);					//交换发送地址7~0位
	//将片选拉高
	GPIO_SetBits(GPIOA,GPIO_Pin_4);
	
	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;
	//拉低片选线,让从机被选中
	GPIO_ResetBits(GPIOA,GPIO_Pin_4); 							
	SPI1_ReadWriteByte(W25Q64_READ_DATA);			//交换发送读取数据的指令
	SPI1_ReadWriteByte(Address >> 16);				//交换发送地址23~16位
	SPI1_ReadWriteByte(Address >> 8);				//交换发送地址15~8位
	SPI1_ReadWriteByte(Address);					//交换发送地址7~0位
	for (i = 0; i < Count; i ++)				//循环Count次
	{
		DataArray[i] = SPI1_ReadWriteByte(W25Q64_DUMMY_BYTE);	//依次在起始地址后读取数据
	}
	//将片选拉高
	GPIO_SetBits(GPIOA,GPIO_Pin_4);
}

W25Q64.H

#ifndef _W25Q64_H_
#define _W25Q64_H_

#include "stm32f4xx.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

void W25Q64_Config(void);
void W25Q64_Read_ID(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

  • 53
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值