STM32学习笔记10(SPI)

一、SPI协议简介

        SPI 协议是一种高速全双工的通信总线。它被广泛地使用在 ADC、 LCD 等设备与 MCU 间,
要求通讯速率较高的场合。 

1、SPI物理层 

 SPI 通讯使用 3 条总线及片选线, 3 条总线分别为 SCK、 MOSI、 MISO,片选线为 SS。
1、SS:当主机要选择从设备时,把该从设备的 NSS 信号线设置为低电平,该从设备即被选中,即片选有效。SPI通讯以 SS 线置低电平为开始信号,以 SS 线被拉高作为结束信号。
2、SCK:时钟信号线,用于通讯数据同步。它由通讯主机产生,STM32 的 SPI 时钟频率最大为
fpclk/2
,两个设备之间通讯时,通讯速率受限于低速设备。
3、 MOSI:主设备输出/从设备输入引脚。这条线上数据的方向为主机到从机。
4 、MISO:主设备输入/从设备输出引脚。在这条线上数据的方向为从机到主机。

2、协议层 

1. SPI 基本通讯过程

SPI 通讯的通讯时序: 

NSS、 SCK、 MOSI 信号都由主机控制产生,而 MISO 的信号由从机产生。

2、CPOL/CPHA 及通讯模式 

        时钟极性 CPOL 是指 SPI 通讯设备处于空闲状态时, SCK 信号线的电平信号。 CPOL=0 时, SCK 在空闲状态时为低电平,CPOL=1 时,则相反。 

        时钟相位 CPHA 是指数据的采样的时刻,当 CPHA=0 时, MOSI 或 MISO 数据线上的信号将会在 SCK 时钟线的“奇数边沿” 被采样。当 CPHA=1 时,数据线在 SCK 的“偶数边沿” 采样。CPHA=0 时的 SPI 通讯模式: 

CPHA=1 时的 SPI 通讯模式: 

二 、STM32 的 SPI 特性及架构

1、 STM32 的 SPI 架构剖析

(1)通讯引脚: 

(2)时钟控制逻辑: 

        SCK线的时钟信号,由波特率发生器根据“控制寄存器”CR1中的BR[0:2]位控制,该位是对 fpclk 时钟的分频因子,对 fpclk 的分频结果就是 SCK 引脚的输出时钟频率。

(3)数据控制逻辑 

(4)整体控制逻辑 

三、SPI初始化结构体 

SPI_NSS:在硬件模式中的 SPI 片选信号由 SPI 硬件自动产生,而软件模式则需要我们亲自把相应的 GPIO 端口拉高或置低产生非片选和片选信号。实际中软件模式应用比较多。GPIO端口设置成推挽输出。

四、SPI—读写串行 FLASH  

        FLSAH 存储器又称闪存,它与 EEPROM 都是掉电后数据不丢失的存储器,但 FLASH存储器容量普遍大于 EEPROM。在存储控制上,FLASH 芯片只能一大片一大片地写,EEPROM 可以单个字节擦写。

1、硬件设计 

 FLASH的存储特性:

1、在写入数据之前必须先擦除

2、擦除时会把数据全重置为1

3、写入数据时只能把为1的数据改为0

4、擦除时必须按最小单位来擦除(一般为扇区)

norflash可以一个字节写入,nandflash必须以块或扇区为单位进行读写

2、软件设计

①初始化并使能SPI :

void SPI_GPIO_Config()
{
	SPI_InitTypeDef  SPI_InitStructure;
	GPIO_InitTypeDef  GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);
	
	//SCK
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	//MISO
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_14;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	//MOSI
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	// SPI的CS引脚,软件控制,设置成推挽输出,类似于LED
	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);
	
	//CS引脚设置为高电平
	GPIO_SetBits(GPIOB, GPIO_Pin_12);
	
	SPI_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_2;
	SPI_InitStructure.SPI_CPHA=SPI_CPHA_2Edge;
	SPI_InitStructure.SPI_CPOL=SPI_CPOL_High;
	SPI_InitStructure.SPI_CRCPolynomial=0;   //不使用CRC功能,数值随便写
	SPI_InitStructure.SPI_DataSize=SPI_DataSize_8b;
	SPI_InitStructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex;  //双线全双工
	SPI_InitStructure.SPI_FirstBit=SPI_FirstBit_MSB;
	SPI_InitStructure.SPI_Mode=SPI_Mode_Master;
	SPI_InitStructure.SPI_NSS=SPI_NSS_Soft;
	
	SPI_Init(SPI2, &SPI_InitStructure);
	SPI_Cmd(SPI2, ENABLE);
}

②发送并接收一个字节:

uint8_t SPI_FLASH_SendByte(uint8_t data)
{
	SPITimeout = SPIT_LONG_TIMEOUT;
	
	//检查并等待至TX缓冲区为空
	while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE)==RESET);
	
	SPI_I2S_SendData(SPI2, data);
	
	//检查并等待至RX缓冲区非空
	while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE)==RESET);
	
	return SPI_I2S_ReceiveData(SPI2);
}

③接收字节: 

uint8_t SPI_FLASH_Read_Byte()
{
	SPI_FLASH_SendByte(0x00);
}

④写使能: 

void SPI_Write_Enable()
{
	GPIO_ResetBits(GPIOB, GPIO_Pin_12);
	SPI_FLASH_SendByte(0x06);
	GPIO_SetBits(GPIOB, GPIO_Pin_12);
}

⑤等待FLASH内部时序完成: 

读取状态寄存器时序: 

FLASH芯片的状态寄存器: 

void SPI_WaitForWriteEnd()
{
	uint8_t FLASH_Status = 0;
	
	GPIO_ResetBits(GPIOB, GPIO_Pin_12);
	
	//读取FLASH状态寄存器
	SPI_FLASH_SendByte(0x05);
	
	do
	{
		FLASH_Status=SPI_FLASH_SendByte(0x00);
	}
	while((FLASH_Status&0x01)==1);//忙碌
	
	GPIO_SetBits(GPIOB, GPIO_Pin_12);
}

⑥读FLASH芯片ID: 

uint32_t SPI_Read_ID()
{
	uint32_t flash_id;
	
    //片选
	GPIO_ResetBits(GPIOB, GPIO_Pin_12);
	
	SPI_FLASH_SendByte(0x9F);
	
    //读取FLASH需要不断发送数据,保证时钟,可以发送任意数据
	flash_id=SPI_FLASH_SendByte(0x00);
	flash_id<<=8;
	flash_id|=SPI_FLASH_SendByte(0x00);
	flash_id<<=8;
	flash_id|=SPI_FLASH_SendByte(0x00);
	
	GPIO_SetBits( GPIOB, GPIO_Pin_12);
	
	return flash_id;
}

⑦擦除FLASH指定扇区

        由于 FLASH 存储器的特性决定了它只能把原来为“1”的数据位改写成“0”,而原来为“0”的数据位不能直接改写为“1”。在写入前,必须要对目标存储矩阵进行擦除操作,把矩阵中的数据位擦除为“1”,在数据写入的时候,如果要存储数据“1”,那就不修改存储矩阵 ,在要存储数据“0”时,才更改该位。 

        FLASH内部有128个block。 

        要注意的是在扇区擦除指令前,还需要先发送“写使能”指令,发送扇区擦除指令后,通过读取寄存器状态等待扇区擦除操作完毕。

void SPI_FLASH_SectorErase(uint32_t addr)
{
	SPI_Write_Enable();
	SPI_WaitForWriteEnd();

	GPIO_ResetBits(GPIOB, GPIO_Pin_12);
	
	SPI_FLASH_SendByte(0x20);
	
	SPI_FLASH_SendByte((addr>>16)&0xff);
	SPI_FLASH_SendByte((addr>>8)&0xff);
	SPI_FLASH_SendByte(addr&0xff);
	
	GPIO_SetBits(GPIOB, GPIO_Pin_12);

	SPI_WaitForWriteEnd();
}

⑧FLASH的页写入 

        使用页写入命令最多可以一次向 FLASH 传输 256 个字节的数据,我们把这个单位为页大小。
 FLASH 页写入的时序:

void SPI_FLASH_PageWrite(uint32_t addr,uint8_t *writeData,uint16_t numToWrite)
{
	SPI_Write_Enable();
	
	GPIO_ResetBits(GPIOB, GPIO_Pin_12);
	
	SPI_FLASH_SendByte(0x02);
	SPI_FLASH_SendByte((addr>>16)&0xff);
	SPI_FLASH_SendByte((addr>>8)&0xff);
	SPI_FLASH_SendByte(addr&0xff);
	
	while(numToWrite--)
	{
		SPI_FLASH_SendByte(*writeData);
		writeData++;
	}
	
	GPIO_SetBits(GPIOB, GPIO_Pin_12);
	SPI_WaitForWriteEnd();
}

⑨多页写入 

void SPI_FLASH_BufferWrite(uint32_t addr,uint8_t *writeData,uint16_t numToWrite)
{
	uint8_t NumofPage=0,NumofSingle=0,Addr=0,count=0,temp=0;
	
	Addr=addr%256;
	count=256-Addr;
	NumofPage=numToWrite/256;
	NumofSingle=numToWrite%256;
	
	if(Addr==0)
	{
		if(NumofPage==0)
		{
			SPI_FLASH_PageWrite(addr,writeData,numToWrite);
		}
		else
		{
			while(NumofPage--)
			{
				SPI_FLASH_PageWrite(addr,writeData,256);
				writeData+=256;
				addr+=256;
			}
			SPI_FLASH_PageWrite(addr,writeData,NumofSingle);
		}
	}
	else
	{
		if(NumofPage==0)
		{
			temp=numToWrite-count;
			SPI_FLASH_PageWrite(addr,writeData,count);
			addr+=count;
			writeData+=count;
			SPI_FLASH_PageWrite(addr,writeData,temp);
		}
		else
		{
			numToWrite-=count;
			NumofPage=numToWrite/256;
			NumofSingle=numToWrite%256;
			
			if(count!=0)
			{
				SPI_FLASH_PageWrite(addr,writeData,count);
				addr+=count;
				writeData+=count;
			}
			while(NumofPage--)
			{
				SPI_FLASH_PageWrite(addr,writeData,256);
				writeData+=256;
				addr+=256;
			}
			if(NumofSingle!=0)
			{
				SPI_FLASH_PageWrite(addr,writeData,NumofSingle);
			}
		}
	}
}

⑩读取 FLASH内容

void SPI_FLASH_Read_Data(uint32_t addr,uint8_t *readData,uint32_t numToRead)
{
	GPIO_ResetBits(GPIOB, GPIO_Pin_12);
	SPI_FLASH_SendByte(0x03);
	
	SPI_FLASH_SendByte((addr>>16)&0xff);
	SPI_FLASH_SendByte((addr>>8)&0xff);
	SPI_FLASH_SendByte(addr&0xff);
	
	while(numToRead--)
	{
		*readData=SPI_FLASH_SendByte(0x00);
		readData++;
	}
	
	GPIO_SetBits(GPIOB, GPIO_Pin_12);
	SPI_WaitForWriteEnd();
}

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值