概念
SPI(Serial Peripheral interface),串行外设接口。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,比如AT91RM9200。SPI接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。
SPI优点:
(1)支持全双工通信
(2)通信简单
(3)数据传输速率块
缺点:
(1)没有指定的流控制
(2)没有应答机制确认是否接收到数据
(3)抗干扰能力差
特点:
(1)高速、同步、全双工、非差分、总线式
(2)主从机通信模式
接口介绍
SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少4根线,事实上3根也可以(单向传输时)。
(1)SDO/MOSI ---- 主器件数据输出,从器件数据输入
(2)SDI/MISO ---- 主器件数据输入,从器件数据输出
(3)SCLK ---- 时钟信号,由主器件产生
(4)CS/SS ---- 片选,从器件使能信号,由主器件控制,当有多个从设备的时候,因为每个从设备上都有一个片选引脚接入到主设备机中,当我们的主设备和某个从设备通信时将需要将从设备对应的片选引脚电平拉低或者是拉高。
通讯是通过数据交换完成的,这里先要知道SPI是串行通讯协议,也就是说数据是一位一位的传输的。这就是SCK时钟线存在的原因,由SCK提供时钟脉冲,SDI,SDO则基于此脉冲完成数据传输。数据输出通过 SDO线,数据在时钟上升沿或下降沿时改变,在紧接着的下降沿或上升沿被读取。完成一位数据传输,输入也使用同样原理。这样,在至少8次时钟信号的改变(上沿和下沿为一次),就可以完成8位数据的传输。
SCK信号线只由主设备控制,从设备不能控制信号线。同样,在一个基于SPI的设备中,至少有一个主控设备。
这样传输的特点:这样的传输方式有一个优点,与普通的串行通讯不同,普通的串行通讯一次连续传送至少8位数据,而SPI允许数据一位一位的传送,甚至允许暂停,因为SCK时钟线由主控设备控制,当没有时钟跳变时,从设备不采集或传送数据,也就是说,主设备通过对SCK时钟线的控制可以完成对通讯的控制。SPI还是一个数据交换协议:因为SPI的数据输入和输出线独立,所以允许同时完成数据的输入和输出。不同的SPI设备的实现方式不尽相同,主要是数据改变和采集的时间不同,在时钟信号上沿或下沿采集有不同定义,具体请参考相关器件的文档。
在点对点的通信中,SPI接口不需要进行寻址操作,且为全双工通信,显得简单高效。在多个从设备的系统中,每个从设备需要独立的使能信号,硬件上比I2C系统要稍微复杂一些。
最后,SPI接口的一个缺点:没有指定的流控制,没有应答机制确认是否接收到数据。
四种工作模式
四种工作模式是按照 SPI时钟极性CPOL 和 SPI时钟相位CPHA 来划分的。
不同的从设备可能在出厂是就是配置为某种模式,这是不能改变的;但我们的通信双方必须是工作在同一模式下,所以我们可以对我们的主设备的SPI模式进行配置,通过CPOL(时钟极性)和CPHA(时钟相位)来控制我们主设备的通信模式。
模式 CPOL CPHA
Mode0 0 0
Mode1 0 1
Mode2 1 0
Mode3 1 1
时钟极性CPOL是用来配置SCLK的电平出于哪种状态时是空闲态或者有效态,时钟相位CPHA是用来配置数据采样是在第几个边沿。CPOL:时钟极性选择,为0时SPI总线空闲为低电平,为1时SPI总线空闲为高电平;CPHA:时钟相位选择,为0时在SCK第一个跳变沿采样,为1时在SCK第二个跳变沿采样。
CPOL=0,表示当SCLK=0时处于空闲态,所以有效状态就是SCLK处于高电平时
CPOL=1,表示当SCLK=1时处于空闲态,所以有效状态就是SCLK处于低电平时
CPHA=0,表示数据采样是在第1个边沿,数据发送在第2个边沿
CPHA=1,表示数据采样是在第2个边沿,数据发送在第1个边沿
其中,采样边沿在中间位置,采样和输出在同一个脉冲内。
GPIO模拟SPI
void SPIInit(void){
SPI_GPIO_Init(); /* 初始化引脚 */
}
static void SPI_GPIO_Init(void){ //初始化引脚
/* GPF1 OLED_CSn output */
GPFCON &= ~(3<<(1*2));
GPFCON |= (1<<(1*2));
GPFDAT |= (1<<1);
/* GPG2 FLASH_CSn output //设置为输出/入引脚
* GPG4 OLED_DC output
* GPG5 SPIMISO input
* GPG6 SPIMOSI output
* GPG7 SPICLK output */
GPGCON &= ~((3<<(2*2)) | (3<<(4*2)) | (3<<(5*2)) | (3<<(6*2)) | (3<<(7*2)));
GPGCON |= ((1<<(2*2)) | (1<<(4*2)) | (1<<(6*2)) | (1<<(7*2)));
GPGDAT |= (1<<2);
}
static void SPI_Set_CLK(char val){ //设置时钟
if (val)
GPGDAT |= (1<<7);
else
GPGDAT &= ~(1<<7);
}
static void SPI_Set_DO(char val){ //设置发送数据引脚
if (val)
GPGDAT |= (1<<6);
else
GPGDAT &= ~(1<<6);
}
void SPISendByte(unsigned char val){ //发送一个字节
for (int i = 0; i < 8; i++){ //8位分别发送
SPI_Set_CLK(0);
SPI_Set_DO(val & 0x80); //高位到低位
SPI_Set_CLK(1);
val <<= 1;
}
}
寄存器
SPCONn(SPI CONTROL REGISTER)控制寄存器:选择模式,使能时钟,主/从,CPOL,CPHA,使能自动从获取数据
SPSTAn(SPI STATUS REGISTER)状态寄存器:bit[0] 数据就绪
SPPINn(SPI PIN CONTROL REGISTER)
SPPREn(SPI BAUD RATE PRESCALER REGISTER)波特率寄存器:Baud rate = PCLK / 2 / (Prescaler value + 1)
SPTDATn(SPI TX DATA REGISTER):发送数据寄存器
SPRDATn (SPI RX DATA REGISTER):接受数据寄存器
void SPIInit(void){
SPI_GPIO_Init(); /* 初始化引脚 */
SPIControllerInit(); /* 初始化控制器 */
}
static void SPI_GPIO_Init(void){ //设置引脚
/* GPF1 OLED_CSn output */
GPFCON &= ~(3<<(1*2));
GPFCON |= (1<<(1*2));
GPFDAT |= (1<<1);
/* GPG2 FLASH_CSn output //设置为输入/出引脚
* GPG4 OLED_DC output
* GPG5 SPIMISO //设置为功能引脚
* GPG6 SPIMOSI
* GPG7 SPICLK */
GPGCON &= ~((3<<(2*2)) | (3<<(4*2)) | (3<<(5*2)) | (3<<(6*2)) | (3<<(7*2)));
GPGCON |= ((1<<(2*2)) | (1<<(4*2)) | (3<<(5*2)) | (3<<(6*2)) | (3<<(7*2)));
GPGDAT |= (1<<2);
}
static void SPIControllerInit(void){ //设置波特率和控制器
/* OLED : 100ns, 10MHz
* FLASH : 104MHz
* 取10MHz
* 10 = 50 / 2 / (Prescaler value + 1)
* Prescaler value = 1.5 = 2
* Baud rate = 50/2/3=8.3MHz */
SPPRE0 = 2; //设置波特率
SPPRE1 = 2;
/* [6:5] : 00, polling mode
* [4] : 1 = enable
* [3] : 1 = master
* [2] : 0
* [1] : 0 = format A
* [0] : 0 = normal mode */
SPCON0 = (1<<4) | (1<<3); //设置控制器
SPCON1 = (1<<4) | (1<<3);
}
void SPISendByte(unsigned char val){ //发送数据
while (!(SPSTA1 & 1)); //等待就绪
SPTDAT1 = val;
}
unsigned char SPIRecvByte(void){ //接受数据
SPTDAT1 = 0xff; //控制寄存器不自动获取数据,向发送数据寄存器写FF,等待数据就绪时读数据
while (!(SPSTA1 & 1));
return SPRDAT1;
}