SPI通信

SPI接口

Serial Peripheral interface

串行外围设备接口
一种高速、全双工、同步的通信主线
芯片管脚只占用四根线 节约芯片管脚 PCB布局节省空间
主要应用 EEPROM FLASH 实时时钟 AD转换器 数字信号处理器 数字信号解码器之间
在这里插入图片描述

概要图:
在这里插入图片描述
4条线通信:
MISO : 主设备数据输入 从设备数据输入
MOSI : 主设备数据输出 从设备数据输入
SCLK : 时钟信号,主设备产生
CS : 设备片选信号,主设备控制

工作原理

① 硬件上为4根线
② 主机和从机都有一个串行移位寄存器 主机通过向它SPI串行寄存器写入一个字节来发起一次传输
③ 串行移位寄存器通过MOSI信号线将字节传送给从机,从机通过MISO信号线返回。交换主从机内容
④ 外设的写操作读操作是同步完成的
如果只进行写操作,主机忽略接收的字节
若主机要读取从机 的一个字节必须发送一个空字节来引发从机传输

特点

==== 3线全双工同步运输
==== 8或16位传输帧格式选择
==== 主或从操作
==== 支持多主模式
==== 8个主模式波特率预分频系数
==== 从模式频率
==== 主模式和从模式快速通信
==== NSS管理
==== 可编程的时钟极性和相位
==== 可编程的数据顺序 MSB(Most Significant Bit 最高有效位)在前或LSB(Least Significant Bit最低有效位)在前
==== 可触发中断的专用接收发送装置
==== SPI总线忙状态标志
==== 支持可靠通信的硬件CRC
--------- 发送模式下,CRC可以被作为最后一字节发送
--------- 全双工模式下对接收到的最后一个字节自动进行CRC校验-
==== 可触发中断的主模式故障 过载及CRC错误标志
==== 支持DMA功能的缓冲器 产生发送和接收请求

从器件选择引脚管理

SPI_CR1寄存器的SSM位设置硬件管理或软件管理
SSM=1 软件管理
SSM=0 硬件管理
SSM=0 SSOE=1 NSS输出使能
SSM=0 SSOE=0 NSS输出禁止

时钟信号的相位和极性

SPI_CR寄存器 的 CPOL位 控制空闲状态 的电平
与CPHA(时钟相位)可以组合成四种可能的时序关系

CPOL位值0/1 SCK引脚在空闲状态保持 低/高电平
在这里插入图片描述
在这里插入图片描述

数据帧格式

设置SPI_CR1寄存器中LSBFIRST位,输出数据位时可设置MSB LSB的优先级

设置DFF位 每个数据帧可以是8位或者16位 发送接收都有效

状态标志

发送缓冲器空闲标志TXE 标志为1 缓冲器为空
接收缓冲器非空RXNE 标志为1
忙标志BSY 表明SPI通信层的状态
在这里插入图片描述

SPI相关引脚复用

在这里插入图片描述

SPI常用寄存器

SPI 控制寄存器1/2(SPI_CR1/2)
SPI 状态寄存器 (SPI_SR)
SPI 数据寄存器(SPI_DR)
SPI_I2S配置寄存器(SPI_I2S_CFGR)
SPI_I2S预分频寄存器 (SPI_I2SPR)

SPI相关库函数

void SPI_I2S_DeInit(SPI_TypeDef* SPIx);
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
void I2S_FullDuplexConfig(SPI_TypeDef* I2Sxext, I2S_InitTypeDef* I2S_InitStruct);
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);


void SPI_CalculateCRC(SPI_TypeDef* SPIx, FunctionalState NewState);
void SPI_TransmitCRC(SPI_TypeDef* SPIx);
uint16_t SPI_GetCRC(SPI_TypeDef* SPIx, uint8_t SPI_CRC);
uint16_t SPI_GetCRCPolynomial(SPI_TypeDef* SPIx);
void SPI_DataSizeConfig(SPI_TypeDef* SPIx, uint16_t SPI_DataSize);
void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState);

FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
ITStatus SPI_I2S_GetITStatus(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
void SPI_I2S_ClearITPendingBit(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);

SPI配置的参数

typedef struct
{
  uint16_t SPI_Direction;           //传送方向

  uint16_t SPI_Mode;                //模式

  uint16_t SPI_DataSize;            //数据大小

  uint16_t SPI_CPOL;               
  uint16_t SPI_CPHA;               //极性和相位
  uint16_t SPI_NSS;                //硬件或软件的片选
 
  uint16_t SPI_BaudRatePrescaler;   //波特率与预分频系数

  uint16_t SPI_FirstBit;            //MSB LSB
  
  uint16_t SPI_CRCPolynomial;       //CRC校验

}SPI_InitTypeDef;

W25Q128

作为SPI的flash
在这里插入图片描述
16M 分为 256 个块 每块大小64K 字节 每块16个扇区 每扇区4K个字节

W25Q128的最小擦除单位为一个扇区即4K个字节

实验程序

程序配置过程

① 使能SPIx和IO口时钟

RCC_AHBxPeriphClockCmd()/RCC_APBxPeriphClockCmd();

② 初始化IO口复用功能

void GPIO_Init(GPIO_TypeDef* GPIOx,GPIO_InitTypeDef* GPIO_InitStruct);

③ 设置引脚复用映射

GPIO_PinAFConfig();

④初始化SPIx,设置其工作模式

void SPI_Init(SPI_TypeDef* SPIx,SPI_InitTypeDef* SPI_InitStruct);

⑤使能SPIx

void SPI_Cmd(SPI_TypeDef*SPIx,FunctionalState NewState);

⑥SPI传输数据

void SPI_I2S_SendData(SPI_TypeDef*SPIx,uint16_t Data);
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);

⑦查看SPI传输状态

SPI_I2S_GetFlagStatus(SPI2,SPI_I2s_FLAG_RXNE);

库函数

初始化

void SPI1_Init(void)
{	 
  GPIO_InitTypeDef  GPIO_InitStructure;
  SPI_InitTypeDef  SPI_InitStructure;
	
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);//使能SPI1时钟
 
  //GPIOFB3,4,5初始化
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;//PB3~5复用功能输出	
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1); //PB3复用为SPI1
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1); //PB4复用为SPI1
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1); //PB5复用为SPI1
 
	//SPI口初始化
	RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE);//¸´复位SPI1
	RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE);//停止复位SPI1

	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //设置SPI单向或双向的数据模式
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;		//工作模式 主SPI
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//数据大小 发送接收8位帧结构
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;		//串行同步时钟空闲状态为高电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;	//串行同步时钟第二个跳变沿数据被采样
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;		//NSS信号由硬件(NSS) 管脚还是软件(SSI)管理
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;		//定义波特率预分频值256
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//数据传输由MSB位开始
	SPI_InitStructure.SPI_CRCPolynomial = 7;	//CRC值计算的多项式
	SPI_Init(SPI1, &SPI_InitStructure);  //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
 
	SPI_Cmd(SPI1, ENABLE); //使能SPI外设

	SPI1_ReadWriteByte(0xff);//启动传输	 
}   

SPI速度设置

void SPI1_SetSpeed(u8 SPI_BaudRatePrescaler)
{
  assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//判断有效性
	SPI1->CR1&=0XFFC7;//位3~5清零 设置波特率
	SPI1->CR1|=SPI_BaudRatePrescaler;	//设置SPI1速度 
	SPI_Cmd(SPI1,ENABLE); //使能SPI1
} 

SPI1读写字节

u8 SPI1_ReadWriteByte(u8 TxData)
{		 			 
 
  while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET){}//等待发送区空 
	
	SPI_I2S_SendData(SPI1, TxData); //通过外设SPIx发送一个byte数据
		
  while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET){} //等待接收完毕 
 
	return SPI_I2S_ReceiveData(SPI1); //返回通过SPIx最近接收的数据
 		    
}

W25Qxx初始化

void W25QXX_Init(void)
{ 
  GPIO_InitTypeDef  GPIO_InitStructure;
 
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//GPIOB使能
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE);//GPIOG使能

	  //GPIOB14
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;//PB14
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//输出
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;//PG7
  GPIO_Init(GPIOG, &GPIO_InitStructure);//初始化
 
	GPIO_SetBits(GPIOG,GPIO_Pin_7);//PG7输出1 防止NRF干扰SPI FLASH 通信 
	W25QXX_CS=1;			//SPI FLASH不选中
	SPI1_Init();		   			//初始化SPI
	SPI1_SetSpeed(SPI_BaudRatePrescaler_4);		//设置为21M时钟
	W25QXX_TYPE=W25QXX_ReadID();	//读取FLASH ID
}  

主函数

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "lcd.h"
#include "spi.h"
#include "w25qxx.h"
#include "key.h"  
 
//要写入W25Q16的字符串数组
const u8 TEXT_Buffer[]={"Explorer STM32F4 SPI TEST"};
#define SIZE sizeof(TEXT_Buffer)	 
	
int main(void)
{ 
	u8 key;
	u16 i=0;
	u8 datatemp[SIZE];
	u32 FLASH_SIZE;
    u16 id = 0;
    
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	delay_init(168);     
	uart_init(115200);	
	LED_Init();				
 	LCD_Init();				
	KEY_Init(); 		
	W25QXX_Init();			
 	POINT_COLOR=RED; 
	LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");	
	LCD_ShowString(30,70,200,16,16,"SPI TEST");	
	LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
	LCD_ShowString(30,110,200,16,16,"2014/5/6");	 
	LCD_ShowString(30,130,200,16,16,"KEY1:Write  KEY0:Read");		
	while(1)
	{
		id = W25QXX_ReadID();
		if (id == W25Q128 || id == NM25Q128)
			break;
		LCD_ShowString(30,150,200,16,16,"W25Q128 Check Failed!");
		delay_ms(500);
		LCD_ShowString(30,150,200,16,16,"Please Check!        ");
		delay_ms(500);
		LED0=!LED0;		//DS0闪烁
	}
	LCD_ShowString(30,150,200,16,16,"W25Q128 Ready!"); 
	FLASH_SIZE=16*1024*1024;	//FLASH 大小16
  	POINT_COLOR=BLUE;			  
	while(1)
	{
		key=KEY_Scan(0);
		if(key==KEY1_PRES)//KEY1按下 写入
		{
			LCD_Fill(0,170,239,319,WHITE);//清除半屏   
 			LCD_ShowString(30,170,200,16,16,"Start Write W25Q128....");
			W25QXX_Write((u8*)TEXT_Buffer,FLASH_SIZE-100,SIZE);		//从倒数第100个地址处开始 写入SIZE长度的数据
			LCD_ShowString(30,170,200,16,16,"W25Q128 Write Finished!");	//提示传送完成
		}
		if(key==KEY0_PRES)//KEY0按下 读取字符串显示
		{
 			LCD_ShowString(30,170,200,16,16,"Start Read W25Q128.... ");
			W25QXX_Read(datatemp,FLASH_SIZE-100,SIZE);					//从倒数第100个地址处开始写入SIZE长度的数据
			LCD_ShowString(30,170,200,16,16,"The Data Readed Is:   ");	//提示传送完成
			LCD_ShowString(30,190,200,16,16,datatemp);					//显示读到的字符串
		} 
		i++;
		delay_ms(10);
		if(i==20)
		{
			LED0=!LED0;//提示系统正在运行	
			i=0;
		}		   
	}       
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

。✧* ꧁流痕꧂✧*。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值