第十五篇,STM32的SPI串行通信协议

1.SPI概念

spi是摩托罗拉公司设计的用于板间的串行通信方式,应用场景类似于IIC,速度快于IIC。

spi属于高速,全双工,同步的通信总线;和设备连接占用4根线,比较占用引脚,速度最快达到40Mbps。

2.硬件连接

CS(chip select):片选引脚

主设备控制,用于选择当前通信的从设备 ,  一个 Slave 设备要想能够接收到 Master 发过来的控制信号, 必须在此之前能够被 Master 设备进行访问 (Access),所以, Master 设备必须首先通过 CS对 Slave 设备进行片选, 把想要访问的 Slave 设备选上.

SCLK:时钟线

产生时钟信号,由主设备控制

MISO(Master Input Slave output)

主设备输入,从设备输出

MOSI(Master Output Slave Input)

主设备输出,从设备输入(MISO和MOSI实现全双工)

3.spi协议

 

spi每一位数据的发送和接收是在时钟信号的边沿完成,根据选择的上升沿/下降沿和时钟信号的高低电平顺序,一共有4种情况。

扫描二维码关注公众号,回复: 14478956 查看本文章


CPOL表示高低电平顺序,叫做极性;若CPOL = 0,串行同步时钟的空闲状态为低电平;

若CPOL = 1,串行同步时钟的空闲状态为高电平;

CPHA表示第一个/第二个边沿,叫做相位。

SPI输出串行同步时钟极性和相位可以根据外设工作要求进行配置。 SPI 设备间的数据传输又被称为数据交换, 是因为 SPI 协议规定一个 SPI 设备不能在数据通信过程中仅仅只充当一个 "发送者(Transmitter)" 或者 "接收者(Receiver)". 在每个 Clock 周期内, SPI 设备都会发送并接收一个 bit 大小的数据, 相当于该设备有一个 bit 大小的数据被交换了.

    

分别是以下四种情况:

MODE0:CPOL= 0,CPHA=0。串行时钟SCLK空闲状态时为低电平,数据在SCLK时钟的上升沿采样,下降沿输出;

MODE1:CPOL= 0,CPHA=1。串行时钟SCLK空闲状态时为低电平,数据在SCLK时钟的下降沿采样,上升沿输出;

MODE2:CPOL= 1,CPHA=0。串行时钟SCLK空闲状态时为高电平,数据在SCLK时钟的下降沿采样,上升沿输出;

MODE3:CPOL= 1,CPHA=1。串行时钟SCLK空闲状态时为高电平,数据在SCLK时钟的上升沿采样,下降沿输出;

 

 起始信号

NSS信号线由高变低,是SPI通讯的起始信号
 结束信号

NSS信号由低变高,是SPI通讯的停止信号
 数据传输

SPI使用MOSI以及MISO信号来传输数据,使用SCK信号线进行数据同步。

MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,且数据输入输出时同时进行的。

SPI每次传输数据可以8位或16位为单位,每次传输的单位数不受限制
 


 

4.spi flash

原理图:

spi接口的CS连接到了PB14,SCLK,MISO,MOSI分别连接到了PB3 PB4 PB5,这三个IO口具有SPI的复用功能。

5.spi通信的实现(两种方法)

(1)使用GPIO接口模拟SPI的时序,只要是IO口即可以使用。

(2)使用SPI控制器,只需要配置好SPI的通信参数后,直接进行传输,接口必须有SPI复用功能。

6.spi控制器的库函数编程

添加spi的库函数源码:

(1)开启时钟

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);

(2)将GPIO配置为SPI复用功能(CS配置为输出)

GPIO_Init(...); GPIO_AFPinConfig(...);

(3)初始化SPI

void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
 参数: 
          SPIx - 哪个SPI
          SPI_InitStruct - SPI初始化结构
 typedef struct
 {
 uint16_t SPI_Direction; /*!< 传输方向 单向/双向 @ref SPI_data_direction */ 
uint16_t SPI_Mode; /*!< 主/从设备选择 @ref SPI_mode */ 
uint16_t SPI_DataSize; /*!< 数据位长度 @ref SPI_data_size */ 
uint16_t SPI_CPOL; /*!< 极性 @ref SPI_Clock_Polarity */ 
uint16_t SPI_CPHA; /*!< 相位 @ref SPI_Clock_Phase */ 
uint16_t SPI_NSS; /*!< 片选信号选择 软件/硬件 @ref SPI_Slave_Select_management */ 
uint16_t SPI_BaudRatePrescaler; /*!< 参考时钟的预分频系数 10M左右*/ 
uint16_t SPI_FirstBit; /*!< 传输位顺序 高位/低位 @ref SPI_MSB_LSB_transmission */ 
uint16_t SPI_CRCPolynomial; /*!< CRC校验,无需配置 */
 }SPI_InitTypeDef;

(4)使能SPI

SPI_Cmd(...);

(5)使用SPI传输数据

//发送和接收同时进行 void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data); uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);

(6)查询SPI传输状态

FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG); 
参数: 
* @arg SPI_I2S_FLAG_TXE: Transmit buffer empty flag. 
* @arg SPI_I2S_FLAG_RXNE: Receive buffer not empty flag. 
* @arg SPI_I2S_FLAG_BSY: Busy flag. 
* @arg SPI_I2S_FLAG_OVR: Overrun flag. 
* @arg SPI_FLAG_MODF: Mode Fault flag. 
* @arg SPI_FLAG_CRCERR: CRC Error flag. 
* @arg SPI_I2S_FLAG_TIFRFE: Format Error. 
* @arg I2S_FLAG_UDR: Underrun Error flag. 
* @arg I2S_FLAG_CHSIDE: Channel Side flag.

7.W25Q128芯片

(1)接口和状态寄存器

CS WP HOLD低电平有效,WP和HOLD接VCC,功能关闭,片选是低电平选中。

SPI接口支持CPOL和CPHA为0,0和1,1的模式

(2)操作时序

在进行任何操作前开启片选,操作完成后关闭片选。

1)读设备ID

发送90H ===> 发送24位0地址 ===> 收到厂家ID(0xef)和设备ID(0x17)

*************************************************************************************************************

//代码实现W25Q128芯片的SPI通信

//功能函数

#include <stm32f4xx.h>
#include <spi_flash.h>
#include <delay.h>
#include <stdio.h>

void spi1_init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	SPI_InitTypeDef SPI_InitStruct;
	
	//1.开始时钟
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
	
	//2.配置GPIO为SPI功能
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;//输出模式
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;//推挽输出
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//高速
	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;//无上下拉
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_14;//PB14
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	//片选关闭
	W25Q128_CS = 1;
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;//复用模式
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;//PB3 PB4 PB5
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1);
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1);
	
	
	//3.SPI初始化
	SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//双向全双工
	SPI_InitStruct.SPI_Mode = SPI_Mode_Master;//主设备
	SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;//数据长度8位
	SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;//极性 mode 0
	SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;//相位 mode 0
	SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;//软件片选
	SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;//APB2总线时钟 84M/16分频 = 5.25M
	SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;//高位先出
	SPI_Init(SPI1,&SPI_InitStruct);
	
	//4.使能SPI1
	SPI_Cmd(SPI1,ENABLE);
}

//发送接收数据 ----- 同时进行
//data是要发送的数据,返回接收的数据
u8 spi1_send_recv_byte(u8 data)
{
	//等待发送缓冲区为空
	while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)!=SET);
	//发送数据
	SPI_I2S_SendData(SPI1,data);
	//等待接收缓冲区非空
	while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)!=SET);
	//接收数据
	return SPI_I2S_ReceiveData(SPI1);
}

//读取W25Q128的ID
u16 w25q128_read_id(void)
{
	u16 id = 0;
	
	//片选选中
	W25Q128_CS = 0;
	
	//发送90H
	spi1_send_recv_byte(0x90);
	//发送24位0地址
	spi1_send_recv_byte(0x00);
	spi1_send_recv_byte(0x00);
	spi1_send_recv_byte(0x00);
	
	//接收厂家ID放在高8位
	id |= spi1_send_recv_byte(0xff)<<8;
	//接收设备ID放在低8位
	id |= spi1_send_recv_byte(0xff);
	
	//片选关闭
	W25Q128_CS = 1;
	
	return id;
}

//读取W25Q128的数据
void w25q128_read_data(u32 addr,u8 *data,u8 len)
{
	//片选选中
	W25Q128_CS = 0;
	
	//发送03H
	spi1_send_recv_byte(0x03);
	//发送24位读的地址,高位先出
	spi1_send_recv_byte((addr>>16)&0xff);//16~23位
	spi1_send_recv_byte((addr>>8)&0xff);//8~15位
	spi1_send_recv_byte((addr>>0)&0xff);//0~7位
	
	//读取数据
	while(len--){
		*data++ = spi1_send_recv_byte(0xff);
	}
	
	//片选关闭
	W25Q128_CS = 1;
}

//开启/关闭写使能  enable = 0 开启写使能  enable = 1 关闭写使能
void w25q128_write_enable_disable(u8 enable)
{
	//片选选中
	W25Q128_CS = 0;
	
	if(enable){
		spi1_send_recv_byte(0x04);
	}
	else{
		spi1_send_recv_byte(0x06);
	}
	
	//片选关闭
	W25Q128_CS = 1;
}

//读状态寄存器1
u8 w25q128_read_status(void)
{
	u8 status = 0;
	
	//片选选中
	W25Q128_CS = 0;
	
	//发送05H
	spi1_send_recv_byte(0x05);
	
	status = spi1_send_recv_byte(0xff);
	
	//片选关闭
	W25Q128_CS = 1;
	
	return status;
}

//扇区擦除
void w25q128_sector_erase(u32 addr)
{
	//开启写使能
	w25q128_write_enable_disable(0);
	
	//延时,让片选信号保持一段时间,使W25Q128感受到
	delay_us(50);
	
	//片选选中
	W25Q128_CS = 0;
	
	//发送20H
	spi1_send_recv_byte(0x20);
	
	//发送24位擦除地址,高位先出
	spi1_send_recv_byte((addr>>16)&0xff);//16~23位
	spi1_send_recv_byte((addr>>8)&0xff);//8~15位
	spi1_send_recv_byte((addr>>0)&0xff);//0~7位
	
	//片选关闭
	W25Q128_CS = 1;
	
	delay_us(50);
	
	//等待擦除完成
	while(w25q128_read_status()&0x1){
		//1ms查询一次
		delay_ms(1);
	}
	
	delay_us(50);
	
	//关闭写使能
	w25q128_write_enable_disable(1);
}

//写数据(按页)
void w25q128_write_page(u32 addr,u8 *data,u8 len)
{
	//开启写使能
	w25q128_write_enable_disable(0);
	
	//延时,让片选信号保持一段时间,使W25Q128感受到
	delay_us(50);
	
	//片选选中
	W25Q128_CS = 0;
	
	
	//发送02H
	spi1_send_recv_byte(0x02);
	
	//发送24位写地址,高位先出
	spi1_send_recv_byte((addr>>16)&0xff);//16~23位
	spi1_send_recv_byte((addr>>8)&0xff);//8~15位
	spi1_send_recv_byte((addr>>0)&0xff);//0~7位
	
	//发送写的数据
	while(len--){
		spi1_send_recv_byte(*data++);
	}
	
	//片选关闭
	W25Q128_CS = 1;
	
	delay_us(50);
	
	//等待写完成
	while(w25q128_read_status()&0x1){
		//1ms查询一次
		delay_ms(1);
	}
	
	delay_us(50);
	
	//关闭写使能
	w25q128_write_enable_disable(1);
}

void flash_write_temp(u8 temp)
{
	u8 buf[16] = {0},i,data = 0;
	u32 addr = 0;
	
	//找到未被写过的块
	while(1){
		w25q128_read_data(addr,buf,16);
		if(buf[0]!=0x55)
			break;
		
		addr += 16;
	}
	
	data = 0x55;
	//写入0x55
	w25q128_write_page(addr,&data,1);
	//写入temp
	w25q128_write_page(addr+1,&temp,1);
	
	data = 0;
	//求校验和
	w25q128_read_data(addr,buf,16);
	for(i=0;i<15;i++){
		data = (data + buf[i])&0xff;
	}
	
	//写入校验和
	w25q128_write_page(addr+15,&data,1);
}

void flash_read_temp(void)
{
	u8 buf[16] = {0},i,data;
	u32 addr = 0;
	
	//找出所有写入温度的块
	while(1){
		data = 0;
		w25q128_read_data(addr,buf,16);
		if(buf[0]!=0x55)
			break;
		
		//求校验和
		for(i=0;i<15;i++){
			data = (data + buf[i])&0xff;
		}
		
		if(data!=buf[15]){
			printf("checksum error!data = %#x,buf[15] = %#x\r\n",data,buf[15]);
			break;
		}
		
		printf("0x%x temp = %d\r\n",addr,buf[1]);
		
		addr += 16;
	}
}

//头文件声明

#ifndef _SPI_FLASH_H_
#define _SPI_FLASH_H_

#include <sys.h>

#define W25Q128_CS PBout(14) 

void spi1_init(void);
u16 w25q128_read_id(void);
void w25q128_read_data(u32 addr,u8 *data,u8 len);
void w25q128_sector_erase(u32 addr);
void w25q128_write_page(u32 addr,u8 *data,u8 len);

void flash_write_temp(u8 temp);
void flash_read_temp(void);

#endif

猜你喜欢

转载自blog.csdn.net/weixin_44651073/article/details/125832907