一、简介
SPI
(
Serial Peripheral Interface
)是由
Motorola
公司开发的一种通用数据总线
四根通信线:
SCK
(
Serial Clock
)、
MOSI
(
Master Output Slave Input
)、
MISO
(
Master Input Slave Output
)、
SS
(
Slave Select
)
同步,全双工(数据接收和数据发送各占一根线)(全双工存在资源浪费的现象)
支持总线挂载多设备(一主多从)
所有SPI
设备的
SCK
、
MOSI
、
MISO
分别连在一起主机
另外引出多条SS
控制线,分别接到各从机的
SS
引脚
输出引脚:配置为推挽输出(所以电平变化迅速,所以速度快。不像I2C那样,下降沿迅速,上升沿缓慢),
输入引脚:配置为浮空或上拉输入
存在冲突:因为MISO都是推挽输出,且都接在一根线上,所以容易发生冲突
解决:SPI规定SS为1时(未被选中),MISO置高阻态。
![](https://img-blog.csdnimg.cn/direct/35fdda16b8b6459d887b288c071f8344.png)
二、SPI内部的移位寄存器
上升沿两个移位寄存器数据左移到数据线(输出数据寄存器)上。下降沿经过采样进入另一个寄存器。
SPI的数据收发,都是基于字节交换 。(主机和从机移位寄存器的数据交换)
![](https://img-blog.csdnimg.cn/direct/a90075b60a6547f38169f4b39af661b4.png)
三、SPI的时序基本单元
1.起始和终止(SS低电平有效)
起始条件:
SS
从高电平切换到低电平
终止条件:
SS
从低电平切换到高电平
2.交换一个字节
CPOL和CPHA决定模式x
(Clock Polarity时钟极性)(Clock Phase 时钟相位(是表示第一个时钟采样移入还是第二个移入,不是规定上升沿采样还是下降沿采样))
模式0(比模式1提前半个SCK时钟,相当于把SS的第一个变化当作时钟下降沿作用(在此时移出数据))用的最多
CPOL=0
:空闲状态时,SCK为低电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
模式1(SCK上升沿输入数据,下降沿输出)
CPOL=0
:空闲状态时,
SCK
为低电平
CPHA=1
:
SCK
第一个边沿移出数据,第二个边沿移入数据
模式2(SCK的极性时模式0的取反)
CPOL=1
:空闲状态时,
SCK
为高电平
CPHA=0
:
SCK
第一个边沿移入数据,第二个边沿移出数据
![](https://img-blog.csdnimg.cn/direct/7e649c2858a9492facd8644e26a4f840.png)
模式3(SCK的极性时模式1的取反)
CPOL=1
:空闲状态时,
SCK
为高电平
CPHA=1
:
SCK
第一个边沿移出数据,第二个边沿移入数据
![](https://img-blog.csdnimg.cn/direct/3972f5072c484ac2bd90f798fb89c6bd.png)
四、软件SPI实现
1.写SS,SCK,MOSI 读MISO(写数据GPIO设置为推挽输出。读数据GPIO为上拉)
void MySPI_W_SS(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}
void MySPI_W_SCK(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);
}
void MySPI_W_MOSI(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);
}
uint8_t MySPI_R_MISO(void)
{
return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}
2.Start 和Stop
SS空闲时为1.
void MySPI_Start(void)
{
MySPI_W_SS(0);
}
void MySPI_Stop(void)
{
MySPI_W_SS(1);
}
3.交换一个字节(write and read byte)
uint8_t MySPI_SwapByte(uint8_t SendByte)//模式0
{
uint8_t ByteReceive = 0x00;
//SS下降沿,移出数据(就是Start)
for(uint8_t i = 0; i<8; ++i)
{
MySPI_W_MOSI(SendByte & (0x80 >> i));
MySPI_W_SCK(1);//SCK上升沿,移入数据
if(MySPI_R_MISO() == 1 ){ByteReceive |= (0x80 >> i);}
MySPI_W_SCK(0);//SCK下降沿,移出数据
}
return ByteReceive;
}
五、硬件SPI(STM32)
1.简介
STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担
可配置8位/16位数据帧、高位先行/低位先行(串口是低位先行)
时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)
支持多主机模型、主或从操作
可精简为半双工/单工通信
支持DMA(有大量数据传输时可以用DMA,少量数据没必要用)
兼容I2S协议(主要传输数字音频信号)
STM32F103C8T6 硬件SPI资源:SPI1、SPI2
SPI框图
简化框图
2.代码
1.初始化。NSS配置麻烦,所以用软件模拟
void MySPI_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
/*SPI SS输出GPIO设置为推挽输出*/
GPIO_InitTypeDef GPIO_InitSTructure;
GPIO_InitSTructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitSTructure.GPIO_Pin = GPIO_Pin_4 ;
GPIO_InitSTructure.GPIO_Speed =GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitSTructure);
/*SPI输入GPIO设置为上拉输入*/
GPIO_InitSTructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitSTructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitSTructure.GPIO_Speed =GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitSTructure);
/*SPI SS输出GPIO设置为复用推挽输出*/
GPIO_InitSTructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitSTructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitSTructure.GPIO_Speed =GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitSTructure);
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;//时钟分频
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CRCPolynomial = 7;//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;//STM32是主机
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//NSS不管,靠软件模拟SS
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE);
/*空闲状态为 SS:1 */
MySPI_W_SS(1);
}
2. SS模拟。Start 和Stop。 发送一个字节
void MySPI_Start(void)
{
MySPI_W_SS(0);
}
void MySPI_Stop(void)
{
MySPI_W_SS(1);
}
uint8_t MySPI_SwapByte(uint8_t SendByte)
{//TXE
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);
SPI_I2S_SendData(SPI1, SendByte);
//RXNE
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);
return SPI_I2S_ReceiveData(SPI1);
}