前言:
作为Motorola的又一伟大发明的SPI总线通信协议,在理解和应用上也是十分复杂且难以理解,博主想通过这篇文章想把SPI的原理和应用大概讲一下,同时也是记录自己对于I2C的学习和理解。
SPI概述:
SPI 是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI 接口主要应用在 EEPROM,FLASH,实时时钟,AD 转换器,还有数字信号处理器和数字信号解码器之间。
SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。
SPI信号线:
SCK:主机的时钟线,由主机产生脉冲,一个脉冲传输、接收一位数据,另名:C SCL SCLK
CS:使能控制端(主机控制从机),选定哪个从机可以工作,当有多个从机连接主机的SPI总线时,从机的CS分别连接主机的不同引脚,主机通过控制引脚输出低电平(通常是低电平)来选中指定从机进行通信,输出高电平来结束或者不选中从机通信。
MOSI:Master Output Slave Input,主机输出,从机输入
MISO:Master Input Slave Output, 主机输入,从机输出
这里我们重点讲一下这张图,在SPI中,这里我们考虑一主多从模式,当连接在主机上的从机的CS输出低电平时,从机被选中指定与主机进行SPI通信。
主机的SCLK每发出一个脉冲,主机的移位寄存器高位移出一位数据,(高位先行,SPI通常是高位先行),通过MOSI线移位数据到从机的移位寄存器低位,同时,从机的移位寄存器高位移位一位数据,通过MISO线移位到主机的移位寄存器的低位,这样主从机的一位数据就交换完成。
如此往复八次脉冲,主机和从机的移位寄存器的八位数据就成功交换。
SPI工作原理总结:
硬件上为4根线:SCLK(传输脉冲),CS(片选使能线),MISO(主机输入,从机输出端线),MOSI(主机输出,从机输出端线)
主机和从机都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节来发起一次传输。
串行移位寄存器通过MOSI信号线将字节传送给从机,从机也将自己的串行移位寄存器中的内容通过MISO信号线返回给主机。这样,两个移位寄存器中的内容就被交换。
外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字符(任意字符)节来引发从机的传输。
SPI四种工作方式:
SPI有四种工作方式,根据输出串行同步时钟极性CPOL和相位CPHA进行选择工作方式,具体可通过从机设备的手册查找从机的SPI工作方式为哪种。
时钟极性选择CPOL:为0时SPI总线空闲为低电平,时钟线工作开始电平为低电平;为1时SPI总线空闲为高电平,时钟线工作开始电平为高电平;
时钟相位选择CPHA,为0时在SCK输出脉冲的第一个跳变沿采样,为1时在SCK输出脉冲的第二个跳变沿采样。
时钟极性CPOL为0时,SPI总线空闲电平即SPI开始电平为低电平,
时钟极性CPOL为1时,SPI总线空闲电平即SPI开始电平为高电平。
CPHA时钟相位为0时,在SCK第一个跳变沿采样,
CPHA时钟相位为1时,在SCK第二个跳变沿采样。
SPI工作方式0:
CPOL=0,CPHA=0,SPI总线工作方式为工作方式0,SCK空闲电平即开始电平为低电平,主机的MISO在SCK时钟线的上升沿进行采样,MOSI在SCK的下降沿把数据发送出去。
SPI工作方式1:
CPOL=1,CPHA=0,SPI总线工作方式为工作方式1,SCK空闲电平即开始电平为高电平,主机的MISO在SCK时钟线的下降沿进行采样,MOSI在SCK的上升沿把数据发送出去。
SPI工作方式2:
CPOL=0,CPHA=1,SPI总线工作方式为工作方式2,SCK空闲电平即开始电平为低电平,主机的MISO在SCK时钟线的下降沿进行采样,MOSI在SCK的上升沿把数据发送出去。
SPI工作方式3:
CPOL=1,CPHA=1,SPI总线工作方式为工作方式3,SCK空闲电平即开始电平为高电平,主机的MISO在SCK时钟线的上升沿进行采样,MOSI在SCK的下降沿把数据发送出去。
SPI软件实现:(STM32F407主机和Flash从机)
接线:
从设备(这里指SPI串行Flash)的F_CS接到STMF407的F_CS使能端,低电平有效,SO(从设备SPI输出引脚)接到主机的SPI1_MISO输入引脚,SI(从设备SPI输入引脚)接到主机的SPI1_MOSI引脚,CLK(从设备时钟线)接到主机的SPI1_SCK时钟线。
代码思路:
(1)使能SPIx和IO口时钟
RCC_AHBxPeriphClockCmd() / RCC_APBxPeriphClockCmd();
(2)初始化IO口为复用功能
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
(3)设置引脚复用映射:
GPIO_PinAFConfig();
(4)初始化SPIx,设置SPIx工作模式
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
(5)使能SPIx
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
(6)SPI传输数据
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx) ;
(7)查看SPI传输状态
SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE);
SPIFlash.c:
#include "spiflash.h"
/*********************************
引脚说明
使用SPI1
SCK连接PB3
MISO连接PB4
MOSI连接PB5
CS连接PB14
**********************************/
void Spiflash_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStruct;
//使能SPIx和IO口时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
//GPIO 初始化设置:要设置模式为复用功能。
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5; //引脚3 4 5
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz; //速度
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ; //上拉
//初始化IO口为复用功能
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14; //引脚14
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //输出模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz; //速度
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ; //上拉
//初始化IO口为复用功能
GPIO_Init(GPIOB, &GPIO_InitStructure);
//设置引脚复用映射:
GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_SPI1);
SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; //84/4 = 21MHZ
//工作方式0
SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; //字长:1字节
SPI_InitStruct.SPI_Direction= SPI_Direction_2Lines_FullDuplex; //全双工
SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB; //先发高位
SPI_InitStruct.SPI_Mode = SPI_Mode_Master; //主机
SPI_InitStruct.SPI_NSS = SPI_NSS_Soft; //软件控制
SPI_InitStruct.SPI_CRCPolynomial = 7; //CRC校验
//初始化SPIx,设置SPIx工作模式
SPI_Init(SPI1, &SPI_InitStruct);
//使能SPIx
SPI_Cmd( SPI1, ENABLE);
//不使能芯片
F_CS = 1;
}
u8 Spi1_Send_Recv_Byte(u8 txdata)
{
u8 rxdata = 0x00;
//查找是否满足发送条件
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == 0);
//缓冲区为空,发送数据
SPI_I2S_SendData(SPI1, txdata);
//查找是否满足接受条件
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == 0);
//缓冲区为非空,接受数据
rxdata = SPI_I2S_ReceiveData(SPI1);
return rxdata;
}
u16 W25q128_id(void)
{
u16 id = 0x00;
//使能芯片
F_CS = 0;
//发送读生产商与设备ID命令
Spi1_Send_Recv_Byte(0x90);
//地址拆分,发送地址
Spi1_Send_Recv_Byte(0x00);
Spi1_Send_Recv_Byte(0x00);
Spi1_Send_Recv_Byte(0x00);
//主机发送任意字节,得到从机的数据
id |= Spi1_Send_Recv_Byte(0xFF)<<8; //生产商ID存储在高八位
id |= Spi1_Send_Recv_Byte(0x66); //设备ID存储在低八位
//不使能芯片
F_CS = 1;
return id;
}
SPIFlash.h:
#ifndef __SPIFLASH_H
#define __SPIFLASH_H
#include "stm32f4xx.h"
#include "sys.h"
#include "delay.h"
#define F_CS PBout(14)
#define SCK PBout(3)
#define MOSI PBout(5)
#define MISO PBin(4)
void Spiflash_Init(void);
u16 W25q128_id(void);
void Erase_Sector(u32 addr);
void Page_Write(u32 addr, u8 *write_buff, u32 len);
void Read_Data(u32 addr, u8 *read_buff, u32 len);
#endif
结语:
感谢您能读到这里,希望通过这篇博客能解答您心中关于SPI的疑惑!