SPI2外设驱动-W25Q64 SPI接口初始化

前言

(1)本系列是基于STM32的项目笔记,内容涵盖了STM32各种外设的使用,由浅入深。

(2)小编使用的单片机是STM32F105RCT6,项目笔记基于小编的实际项目,但是博客中的内容适用于各种单片机开发的同学学习和使用。

学习目标

  1. W25Q64硬件设计。
  2. 学习SPI通讯协议。
  3. 完成25Q64芯片的SPI驱动程序编写。

硬件原理图

从上图可以看出 25Q64连接的是单片机的SPI2接口,通过SPI2来通讯的。

SPI通讯原理简单介绍(理解)

典型连线图

简单原理分析

SCK:决定SPI的通信速率,即 数据传输速率。

数据:1高电平 0 低电平。

SPI的四种通讯模式

https://mp.weixin.qq.com/s/ytAad2jdKczzdhD3b92apA

可以看一下上面的资料。

首先我们要了解两个特殊寄存器 分别是 CPOL (Clock POlarity)和 CPHA (Clock PHAse)。

CPOL:配置SPI总线的极性

CPHA:配置SPI总线的相位

SPI总线极性的概念: 空闲的时候时钟信号是高电平还是低电平

CPOL = 1; SCK 空闲是高电平

CPOL = 0; SCK 空闲是低电平

SPI总线的相位的概念

一个时钟周期有2个跳变沿,相位决定从那个跳变开始采集数据

CPHA = 0; 表示从第一个跳变 开始采集

CPHA = 1; 表示从第二个跳变 开始采集

SPI四种模式

模式0: CPOL = 0; CPHA = 0;

模式1:CPOL = 0; CPHA = 1;

模式2:CPOL = 1; CPHA = 0;

模式3:CPOL = 1; CPHA = 1;

数据传输方向

高位在前:MSB

低位在前: LSB

SPI的单线 和双线 模式

单线:一般用于OLED屏幕单向通讯

双向:一般用于芯片之间的双向通讯

特别说明: 一般情况下,我们不用刻意去学习四种模式的具体细节,一般芯片资料里面都会告诉你芯片支持的模式。

25Q64 SPI2的初始化操作

hal_flash.c代码

#include "stm32F10x.h"
#include "hal_flash.h"

void hal_spi2Init(void)
{
	SPI_InitTypeDef  SPI_InitStructure;
	GPIO_InitTypeDef GPIO_InitStructure;
	
	/* Enable SPI2 and GPIOA clocks */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
	
	/* Configure SPI2 pins: NSS, SCK, MISO and MOSI */
	GPIO_InitStructure.GPIO_Pin = SPI2_SCK_PIN | SPI2_MISO_PIN | SPI2_MOSI_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_Init(SPI2_SCK_PORT, &GPIO_InitStructure);
	
	//SPI2 NSS 
	GPIO_InitStructure.GPIO_Pin = SPI2_NSS_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(SPI2_NSS_PORT, &GPIO_InitStructure);
	GPIO_SetBits(SPI2_NSS_PORT,SPI2_NSS_PIN);
	
	/* SPI2 configuration */ 
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //SPI1设置为两线全双工
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;	                     //设置SPI1为主模式
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                  //SPI发送接收8位帧结构
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;	 		                   //串行时钟在不操作时,时钟为高电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;		                   //第二个时钟沿开始采样数据
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;			                     //NSS信号由软件(使用SSI位)管理
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; //定义波特率预分频的值:波特率预分频值为8
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;				         //数据传输从MSB位开始
	SPI_InitStructure.SPI_CRCPolynomial = 7;						               //CRC值计算的多项式
	SPI_Init(SPI2, &SPI_InitStructure);
	/* Enable SPI2  */
	SPI_Cmd(SPI2, ENABLE); 											  //使能SPI2外设
	
	hal_spi2CSDrive(1);//空闲时将片选信号拉高,初始化为空闲状态
	
}  

void hal_spi2CSDrive(unsigned char sta)
{
	if(sta)
		GPIO_SetBits(SPI2_NSS_PORT,SPI2_NSS_PIN);		
	else
		GPIO_ResetBits(SPI2_NSS_PORT,SPI2_NSS_PIN);
}

//SPIx 读写一个字节
//返回值:读取到的字节
unsigned char  hal_spi2ReadWriteByte(unsigned char  TxData)
{		
	unsigned char retry=0;				 
	while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE)==RESET)//等待发送区空	
	{
		retry++;
		if(retry>200)
			return 0;
	}	
  SPI_I2S_SendData(SPI2,TxData);	
	retry=0;
	while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE)==RESET)//等待发送区空	
	{
		retry++;
		if(retry>200)
			return 0;
	}	  						    
	return SPI_I2S_ReceiveData(SPI2);//SPI2->DR;          //返回收到的数据						    
}

hal_flash.h代码

#ifndef _HAL_FLASH_H
#define _HAL_FLASH_H

#define SPI2_SCK_PORT       GPIOB
#define SPI2_SCK_PIN        GPIO_Pin_13

#define SPI2_MOSI_PORT       GPIOB
#define SPI2_MOSI_PIN        GPIO_Pin_15

#define SPI2_MISO_PORT       GPIOB
#define SPI2_MISO_PIN        GPIO_Pin_14

#define SPI2_NSS_PORT       GPIOB
#define SPI2_NSS_PIN        GPIO_Pin_12
 

void hal_spi2Init(void);
void hal_spi2CSDrive(unsigned char sta);
unsigned char  hal_spi2ReadWriteByte(unsigned char  TxData);

#endif

SPI2接口初始化流程(拆解代码分析)

● 定义SPI通讯的端口

● 打开相关时钟

● 初始化SPI2相关的GPIO口

● 初始化SPI2相关参数

● 片选CS初始化 拉高

定义SPI通讯的端口
#define SPI2_SCK_PORT       GPIOB
#define SPI2_SCK_PIN        GPIO_Pin_13

#define SPI2_MOSI_PORT       GPIOB
#define SPI2_MOSI_PIN        GPIO_Pin_15

#define SPI2_MISO_PORT       GPIOB
#define SPI2_MISO_PIN        GPIO_Pin_14

#define SPI2_NSS_PORT       GPIOB//其实就是CS,片选引脚
#define SPI2_NSS_PIN        GPIO_Pin_12
打开相关时钟
/* Enable SPI2 and GPIOA clocks */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
初始化SPI2相关的GPIO口
/* Configure SPI2 pins: NSS, SCK, MISO and MOSI */
	GPIO_InitStructure.GPIO_Pin = SPI2_SCK_PIN | SPI2_MISO_PIN | SPI2_MOSI_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_Init(SPI2_SCK_PORT, &GPIO_InitStructure);

	//SPI2 NSS   
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	GPIO_SetBits(GPIOB,GPIO_Pin_12);
初始化SPI2相关参数
/* SPI2 configuration */ 
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //SPI2设置为两线全双工
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;	   //设置SPI2为主模式
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;   //SP2发送接收8位帧结构
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;	//串行时钟在不操作时,时钟为高电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;	//第二个时钟沿开始采样数据
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由软件(使用SSI位)管理
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; //定义波特率预分频的值:波特率预分频值为8
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//数据传输从MSB位开始
	SPI_InitStructure.SPI_CRCPolynomial = 7;		//CRC值计算的多项式
	SPI_Init(SPI2, &SPI_InitStructure);
	/* Enable SPI2  */
	SPI_Cmd(SPI2, ENABLE); 					//使能SPI2外设
25Q64片选操作,拉高
void hal_spi2CSDrive(unsigned char sta)
{
	if(sta)
		GPIO_SetBits(GPIOB,GPIO_Pin_12);		
	else
		GPIO_ResetBits(GPIOB,GPIO_Pin_12);
}
SPI数据读写函数
SPI读写数据操作原理
SPI 读写操作图示分析
代码分析
//SPIx 读写一个字节
//返回值:读取到的字节
unsigned char  hal_spi2ReadWriteByte(unsigned char  TxData)
{		
	unsigned char retry=0;				 
	while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE)==RESET)//等待发送区空	
	{
		retry++;
		if(retry>200)
			return 0;
	}	
  SPI_I2S_SendData(SPI2,TxData);	
	retry=0;
	while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE)==RESET)//	
{
		retry++;
		if(retry>200)
			return 0;
	}	  						    
	return SPI_I2S_ReceiveData(SPI2);//SPI2->DR;          //返回收到的数据			    
}
相关推荐
爱米的前端小笔记1 分钟前
前端八股自学笔记分享—页面布局(二)
前端·笔记·学习·面试·求职招聘
alikami25 分钟前
【前端】前端学习
学习
一只小菜鸡..32 分钟前
241118学习日志——[CSDIY] [ByteDance] 后端训练营 [06]
学习
lantiandianzi1 小时前
基于单片机的多功能跑步机控制系统
单片机·嵌入式硬件
文弱书生6561 小时前
输出比较简介
stm32
哔哥哔特商务网1 小时前
高集成的MCU方案已成电机应用趋势?
单片机·嵌入式硬件
跟着杰哥学嵌入式1 小时前
单片机进阶硬件部分_day2_项目实践
单片机·嵌入式硬件
Hacker_Oldv2 小时前
网络安全的学习路线
学习·安全·web安全
蒟蒻的贤2 小时前
vue学习11.21
javascript·vue.js·学习
高 朗2 小时前
【GO基础学习】基础语法(2)切片slice
开发语言·学习·golang·slice